1 // Copyright 2013-2017 the openage authors. See copying.md for legal info. 2 3 #pragma once 4 5 #include <cstdlib> 6 #include <cstring> 7 #include <fstream> 8 #include <string> 9 #include <unordered_map> 10 #include <vector> 11 12 #include "../error/error.h" 13 #include "compiler.h" 14 #include "fslike/native.h" 15 #include "path.h" 16 17 18 namespace openage { 19 namespace util { 20 21 22 /** 23 * Collection of multiple csv files. 24 * Read from a packed csv that contains all the data. 25 * 26 * Then, data can be read recursively. 27 */ 28 class CSVCollection { 29 public: 30 31 /** 32 * Type for storing csv data: 33 * {filename: [line, ...]}. 34 */ 35 using csv_file_map_t = std::unordered_map<std::string, std::vector<std::string>>; 36 37 /** 38 * Initialize the collection by reading the given file. 39 * This file must contain the data that this collection is made up of. 40 */ 41 explicit CSVCollection(const Path &entryfile); 42 virtual ~CSVCollection() = default; 43 44 45 /** 46 * This function is the entry point to load the whole file tree recursively. 47 * 48 * Should be called again from the .recurse() method of the struct. 49 * 50 * The internal flow is as follows: 51 * * read entries of the given files 52 * (call to the generated field parsers (the fill function)) 53 * * then, recurse into referenced subdata entries 54 * (this implementation is generated) 55 * * from there, reach this function again to read each subdata entry. 56 * 57 */ 58 template<class lineformat> read(const std::string & filename)59 std::vector<lineformat> read(const std::string &filename) const { 60 61 // first read the content from the data 62 auto result = this->get_data<lineformat>(filename); 63 64 std::string dir = dirname(filename); 65 66 size_t line_count = 0; 67 68 // then recurse into each subdata entry. 69 for (auto &entry : result) { 70 line_count += 1; 71 72 if (not entry.recurse(*this, dir)) { 73 throw Error{ 74 MSG(err) 75 << "Failed to read follow-up files for " 76 << filename << ":" << line_count 77 }; 78 } 79 } 80 81 return result; 82 } 83 84 85 /** 86 * Parse the data from one file in the map. 87 */ 88 template<typename lineformat> get_data(const std::string & filename)89 std::vector<lineformat> get_data(const std::string &filename) const { 90 91 size_t line_count = 0; 92 93 std::vector<lineformat> ret; 94 95 // locate the data in the collection 96 auto it = this->data.find(filename); 97 98 if (it != std::end(this->data)) { 99 const std::vector<std::string> &lines = it->second; 100 101 for (auto &line : lines) { 102 line_count += 1; 103 lineformat current_line_data; 104 105 // use the line copy to fill the current line struct. 106 int error_column = current_line_data.fill(line); 107 if (error_column != -1) { 108 throw Error{ 109 ERR 110 << "Failed to read CSV file: " 111 << filename << ":" << line_count << ":" << error_column 112 << ": error parsing " << line 113 }; 114 } 115 116 ret.push_back(current_line_data); 117 } 118 } 119 else { 120 throw Error{ 121 ERR << "File was not found in csv cache: '" 122 << filename << "'" 123 }; 124 } 125 126 return ret; 127 } 128 129 protected: 130 csv_file_map_t data; 131 }; 132 133 134 /** 135 * Referenced file tree structure. 136 * 137 * Used for storing information for subtype members 138 * that need to be recursed. 139 */ 140 template<class lineformat> 141 struct csv_subdata { 142 /** 143 * File where to read subdata from. 144 * This name is relative to the file that defined the subdata! 145 */ 146 std::string filename; 147 148 /** 149 * Data that was read from the file with this->filename. 150 */ 151 std::vector<lineformat> data; 152 153 /** 154 * Read the data of the lineformat from the collection. 155 * Can descend recursively into dependencies. 156 */ readcsv_subdata157 bool read(const CSVCollection &collection, const std::string &basedir) { 158 std::string next_file = basedir; 159 if (basedir.size() > 0) { 160 next_file += fslike::PATHSEP; 161 } 162 next_file += this->filename; 163 164 this->data = collection.read<lineformat>(next_file); 165 return true; 166 } 167 168 /** 169 * Convenience operator to access data 170 */ 171 const lineformat &operator [](size_t idx) const { 172 return this->data[idx]; 173 } 174 }; 175 176 177 /** 178 * read a single csv file. 179 * call the destination struct .fill() method for actually storing line data 180 */ 181 template<typename lineformat> read_csv_file(const Path & path)182std::vector<lineformat> read_csv_file(const Path &path) { 183 184 File csv = path.open(); 185 186 std::vector<lineformat> ret; 187 size_t line_count = 0; 188 189 for (auto &line : csv.get_lines()) { 190 line_count += 1; 191 192 // ignore comments and empty lines 193 if (line.empty() || line[0] == '#') { 194 continue; 195 } 196 197 lineformat current_line_data; 198 199 // use the line copy to fill the current line struct. 200 int error_column = current_line_data.fill(line); 201 if (error_column != -1) { 202 throw Error{ 203 ERR 204 << "Failed to read CSV file: " 205 << csv << ":" << line_count << ":" << error_column 206 << ": error parsing " << line 207 }; 208 } 209 210 ret.push_back(current_line_data); 211 } 212 213 return ret; 214 } 215 216 }} // openage::util 217