#include #include #include "MiscSupport.h" #include "TwoDLookup.h" static void readRow(std::istream &stream, TwoDArray::StringList &list) { list.clear(); std::string inProgress; enum { mNormal, mInQuotes, mAfterQuote, mDone } mode = mNormal; while (stream && (mode != mDone)) { char ch; stream.get(ch); if (stream.eof()) // This is like adding a \n to the end of every file. That makes the // logic below simpler. ch = '\n'; if (ch == '"') { switch (mode) { case mNormal: // assert current.empty() mode = mInQuotes; break; case mInQuotes: mode = mAfterQuote; break; case mAfterQuote: inProgress += '"'; mode = mInQuotes; break; default: break; } } else if (mode == mInQuotes) { inProgress += ch; } else { switch (ch) { case '\r': if (stream.peek() != '\n') { // cr lf is a valid end of line. Otherwise cr is a normal // character. inProgress += '\r'; } mode = mNormal; break; case ',': list.push_back(inProgress); inProgress.clear(); mode = mNormal; break; case '\n': if ((!list.empty()) || (mode == mAfterQuote) || (!inProgress.empty())) { // A blank line has no cells. A line with just two quotes // contains a single cell containing the empty string. list.push_back(inProgress); } mode = mDone; break; default: inProgress += ch; mode = mNormal; break; } } } } void TwoDArray::appendFromCSV(std::string const &fileName) { std::ifstream stream(fileName.c_str(), std::ios_base::in); StringList firstRow; readRow(stream, firstRow); if (firstRow.size() <= 1) { // No columns. return; } for (unsigned int i = 1; i < firstRow.size(); i++) { std::string const &colHeader = firstRow[i]; if ((!colHeader.empty()) && (!_allCols.count(colHeader))) { _allCols.insert(colHeader); _colsInOrder.push_back(colHeader); } } while (stream) { StringList row; readRow(stream, row); const int end = std::min(row.size(), firstRow.size()); if (end > 1) { std::string const &rowHeader = row[0]; if (!rowHeader.empty()) { if (!_allRows.count(rowHeader)) { _allRows.insert(rowHeader); _rowsInOrder.push_back(rowHeader); } for (int i = 1; i < end; i++) { add(firstRow[i], rowHeader, row[i]); } } } } } static bool needsQuotes(std::string const &value) { char const *data = value.data(); int i = value.size(); while (i) { switch (*data) { case '"': case '\n': case ',': return true; } i--; data++; } return false; } static void writeRow(std::ostream &stream, TwoDArray::StringList const &list) { if (list.empty()) { stream<<'\n'; return; } if ((list.size() == 1) && (list[0].empty())) { stream<<"\"\"\n"; return; } bool first = true; for (TwoDArray::StringList::const_iterator it = list.begin(); it != list.end(); it++) { if (first) { first = false; } else { stream<<','; } if (needsQuotes(*it)) { stream<<'"'; char const *data = it->data(); int i = it->size(); while (i) { if (*data == '"') { stream<<"\"\""; } else { stream<<*data; } data++; i--; } stream<<'"'; } else { stream<<*it; } } stream<<'\n'; } void TwoDArray::writeToCSV(std::string const &fileName, bool writeHeader) const { std::ofstream stream(fileName.c_str(), std::ios_base::out | std::ios_base::trunc); writeToCSV(stream, writeHeader); } std::string TwoDArray::writeToCSV(bool writeHeader) const { std::stringstream stream(std::stringstream::out); writeToCSV(stream, writeHeader); return stream.str(); } void TwoDArray::writeToCSV(std::ostream &stream, bool writeHeader) const { if (writeHeader) writeRow(stream, _colsInOrder); for (StringList::const_iterator row = _rowsInOrder.begin(); row != _rowsInOrder.end(); row++) { StringList currentRow; currentRow.push_back(*row); for (StringList::const_iterator col = _colsInOrder.begin(); col != _colsInOrder.end(); col++) { if (!col->empty()) { currentRow.push_back(get(*col, *row)); } } writeRow(stream, currentRow); } } std::string TwoDArray::get(std::string const &colHeader, std::string const &rowHeader) const { return getPropertyDefault(_values, Key(colHeader, rowHeader)); } void TwoDArray::add(std::string const &colHeader, std::string const &rowHeader, std::string const &value) { if (!(colHeader.empty() || rowHeader.empty())) { if (value.empty()) { // A minor optimization. _values.erase(Key(colHeader, rowHeader)); } else { _values[Key(colHeader, rowHeader)] = value; } if (!_allCols.count(colHeader)) { _allCols.insert(colHeader); _colsInOrder.push_back(colHeader); } if (!_allRows.count(rowHeader)) { _allRows.insert(rowHeader); _rowsInOrder.push_back(rowHeader); } } } TwoDArray::TwoDArray() { _colsInOrder.push_back(""); } void TwoDArray::clear() { _allCols.clear(); _allRows.clear(); _colsInOrder.clear(); _rowsInOrder.clear(); _values.clear(); _colsInOrder.push_back(""); } void TwoDArray::eraseAllRows() { _allRows.clear(); _rowsInOrder.clear(); _values.clear(); } bool TwoDArray::containsRow(std::string rowHeader) const { // I wonder we we have this, but not containsColumn(). I copied this // directly from the Delphi code, so presumably some piece of code uses this, // but not the other one. return _allRows.count(rowHeader); }