| 177 | namespace Database { |
| 178 | |
| 179 | void databaseToCSVFile(::Database::DBStorage &dbStorage, |
| 180 | std::ostream &csvStream, |
| 181 | std::function<bool(const DBKey &, const DBRecord &)> filter) |
| 182 | { |
| 183 | constexpr size_t BatchSize = 2000; |
| 184 | constexpr char Sep = ','; |
| 185 | |
| 186 | DBStorage::Cursor cursor {0}; |
| 187 | std::vector<std::pair<DBKey, DBRecord>> dbKeyRecords; |
| 188 | dbKeyRecords.reserve(BatchSize); |
| 189 | |
| 190 | csvStream << "index" << Sep << "key" << Sep << "label" << Sep << "value" << Sep << "depth" |
| 191 | << Sep << "bound" << Sep << "text" << '\n'; |
| 192 | |
| 193 | size_t count = 0; |
| 194 | std::stringstream ss; |
| 195 | do { |
| 196 | dbKeyRecords.clear(); |
| 197 | cursor = dbStorage.scan(cursor, BatchSize, dbKeyRecords); |
| 198 | |
| 199 | for (const auto &[dbKey, dbRecord] : dbKeyRecords) { |
| 200 | if (filter && !filter(dbKey, dbRecord)) |
| 201 | continue; |
| 202 | |
| 203 | csvStream << count << Sep << dbKey << Sep; |
| 204 | |
| 205 | if (dbRecord.isNull()) |
| 206 | csvStream << "(null)"; |
| 207 | else if (dbRecord.label == LABEL_NONE) |
| 208 | csvStream << "(none)"; |
| 209 | else if (std::isprint(dbRecord.label) && !std::isspace(dbRecord.label)) |
| 210 | csvStream << (char)dbRecord.label; |
| 211 | else |
| 212 | csvStream << '(' << (int)dbRecord.label << ')'; |
| 213 | |
| 214 | csvStream << Sep << dbRecord.value << Sep << dbRecord.depth() << Sep; |
| 215 | |
| 216 | switch (dbRecord.bound()) { |
| 217 | case BOUND_EXACT: csvStream << "exact"; break; |
| 218 | case BOUND_LOWER: csvStream << "lower"; break; |
| 219 | case BOUND_UPPER: csvStream << "upper"; break; |
| 220 | default: csvStream << "none"; break; |
| 221 | } |
| 222 | |
| 223 | std::stringstream ss; |
| 224 | ss << std::quoted(dbRecord.text); |
| 225 | std::string escapedText = ss.str(); |
| 226 | // Esacpe all '\n' with "\\n" in text |
| 227 | // (\n is used to seperate different board texts and may appear in comments) |
| 228 | replaceAll(escapedText, "\n", "\\n"); |
| 229 | // Esacpe all '\b' with "\\b" in text |
| 230 | // (\b is used to seperate sub-sections in the text) |
| 231 | replaceAll(escapedText, "\b", "\\b"); |
| 232 | // Remove all '\0' in text that may exist due to bug in early implementation |
| 233 | replaceAll(escapedText, std::string_view {"\0", 1}, ""); |
| 234 | csvStream << Sep << escapedText << '\n'; |
| 235 | count++; |
| 236 | } |