0

I want to compile a CSV file into my Binary so that when I run my EXE application it does not need the required CSV file in the folder, and has the CSV files data within the EXE already.

For instance I have multiple CSV files with 2 columns and 150+ rows of strings which I want to, at runtime, be parsed into a C++ map. When I use this application I dont want it to be in a folder with multiple CSV files, rather just the EXE which can be more portable. If the data in these CSV files had to change then I would simply re-build the solution with updated CSV files.

Is C++ able to do this? If so how would I do this? I'm not looking for alternatives to how I want this designed, I would like to have it set out how I have described, and if it is not possible I will just create an array or enum within a header file.

Sharpie
  • 113
  • 8
  • 3
    It sounds like you should create a compiler for `.csv` files that outputs `.cpp` and/or `.hpp` files. Sure, it can be done in C++ or by a simple script. – Ted Lyngmo Aug 16 '19 at 07:27
  • 1
    Yes it it possible in general. There are a few approaches. What do you use to build your program? Visual Studio? CMake? do you use Qt in your project? – Michał Walenciak Aug 16 '19 at 07:27
  • Have a look at the options given here: [Is there a cross platform way to embed resources in a binary application written with C++?](https://stackoverflow.com/questions/48326287/is-there-a-cross-platform-way-to-embed-resources-in-a-binary-application-written) – acraig5075 Aug 16 '19 at 07:31
  • 2
    I have actually had to do something like this with shaders for OpenGL. The simplest way to do this is to literally store your CSV values into a string in your source code somewhere, this can then be used in the parser later on. Now a more sophisticated and flexible solution would be as @TedLyngmo said: Create a CVS to .cpp and header files which automatically runs with your pre build scripts - the cpp would include the big string with the contents and the header a declaration to the string with the contents, so you can include it in other scripts. – Mat Gomes Aug 16 '19 at 09:56
  • ... and if you have `.csv` files with different formats that you want transformed into different C++ types, you could create a file describing how the conversion should be done. `foo.csv` could have a `foo.csv2cpp` that your `.csv` compiler reads. Jacking in the compiler and build result into the build dependency chain should then be easy. – Ted Lyngmo Aug 16 '19 at 10:23

1 Answers1

2

I like the proposal of some guys here and created a working example for a "csv" to "c++ header" file compiler.

It will even deal with different column sized CSVs.

It stores the result as compile-time-constexpr array of array of std::string_view.

Maybe it can give you the basic idea:

#include <iostream>
#include <string>
#include <array>
#include <vector>
#include <regex>
#include <sstream>
#include <fstream>
#include <algorithm>

// The delimiter
const std::regex re(",");

std::istringstream sourceCSV1{R"(A00,A01,A02
A10,A11,A12
A20,A21,A22)"};

std::istringstream sourceCSV2{R"(B00,B01
B10,B11,B12,B13,B14
B20,B21,B22,B23)"};


// Define Alias for Easier Reading
using Columns = std::vector<std::string>;
using CSV = std::vector<Columns>;


// Proxy for the input Iterator
struct ColumnProxy {    
    // Overload extractor. Read a complete line
    friend std::istream& operator>>(std::istream& is, ColumnProxy& cp) {

        // Read a line
        std::string line; cp.columns.clear();
        std::getline(is, line);

        // Split values and copy into resulting vector
        std::copy(std::sregex_token_iterator(line.begin(), line.end(), re, -1),
            std::sregex_token_iterator(),
            std::back_inserter(cp.columns));
        return is;
    }

    // Type cast operator overload.  Cast the type 'Columns' to std::vector<std::string>
    operator std::vector<std::string>() const { return columns; }
protected:
    // Temporary to hold the read vector
    Columns columns{};
};

void convertCSV2Hpp(std::istream& is, std::ostream& os, std::string& variableName)
{
   // Read complete CSV File
   CSV csvFile { std::istream_iterator<ColumnProxy>(is), std::istream_iterator<ColumnProxy>() };
   // Get maximumn number of columns in CSV file
   size_t maxCols = std::max_element(csvFile.begin(),csvFile.end(),[](const Columns& c1, const Columns& c2){ return c1.size() < c2.size();})->size();

   // Build output header file 
   std::string includeGuard(variableName);
   std::transform(variableName.begin(), variableName.end(),includeGuard.begin(), ::toupper);

   // Print header of file
   os << "#ifndef " << includeGuard << "_HPP" << "\n#define " << includeGuard << "_HPP\n\n#include <string>\n#include <array>\n\n" 
       << "constexpr size_t " << variableName << "NumberOfRows {" << csvFile.size() << "U};\n"
       << "constexpr size_t " << variableName << "NumberOfColumns {" << maxCols << "U};\n\n"
       << "constexpr std::array<std::array<std::string_view," << variableName << "NumberOfColumns" << ">," << variableName << "NumberOfRows" << "> " << variableName << " {{";

    // Print data
    for (size_t row = 0U; row < csvFile.size(); ++row) {
        os << "\n{";
        for (size_t col=0U; col<maxCols; ++col) {
            os << "\"" << ((col < csvFile[row].size())?csvFile[row][col]:"") << "\"" << ((col==maxCols-1)?"":", "); 
        }
        os <<  "},";
    }
    os << "\n}};\n\n#endif\n\n";
}

int main()
{
    std::string name("csv1");
    convertCSV2Hpp(sourceCSV1,std::cout,name);
    name = "csv2";
    convertCSV2Hpp(sourceCSV2,std::cout,name);
    return 0;
}

Because I do not have files here on SO, I used std::istringstream as input and std::cout as output file. You can of course use whatever file (stream) you like.

A M
  • 14,694
  • 5
  • 19
  • 44
  • Hi thanks for all the effort everyone has put into helping me with this question! I haven't been able to implement this yet but am excited to try it! – Sharpie Aug 18 '19 at 09:17