我如何读取和parsingC ++中的CSV文件?
我需要在C ++中加载和使用CSV文件数据。 在这一点上,它可以真的只是一个逗号分隔的parsing器(即不要担心逃脱新的行和逗号)。 主要的需求是逐行parsing器,每次调用方法时都会返回下一行的向量。
我发现这篇文章看起来很有希望: http : //www.boost.org/doc/libs/1_35_0/libs/spirit/example/fundamental/list_parser.cpp
我从来没有使用过升压的精神,但愿意尝试。 但是,只有当我没有看到更简单的解决scheme的时候。
如果你不关心转义逗号和换行符,
而且你不能在逗号中join逗号和换行符(如果你不能逃避那么…)
那么它只有三行左右的代码(OK 14 – >但是只有15行来读取整个文件)。
std::vector<std::string> getNextLineAndSplitIntoTokens(std::istream& str) { std::vector<std::string> result; std::string line; std::getline(str,line); std::stringstream lineStream(line); std::string cell; while(std::getline(lineStream,cell, ',')) { result.push_back(cell); } // This checks for a trailing comma with no data after it. if (!lineStream && cell.empty()) { // If there was a trailing comma then add an empty element. result.push_back(""); } return result; }
我只是创build一个代表一行的类。
然后stream入该对象:
#include <iterator> #include <iostream> #include <fstream> #include <sstream> #include <vector> #include <string> class CSVRow { public: std::string const& operator[](std::size_t index) const { return m_data[index]; } std::size_t size() const { return m_data.size(); } void readNextRow(std::istream& str) { std::string line; std::getline(str, line); std::stringstream lineStream(line); std::string cell; m_data.clear(); while(std::getline(lineStream, cell, ',')) { m_data.push_back(cell); } // This checks for a trailing comma with no data after it. if (!lineStream && cell.empty()) { // If there was a trailing comma then add an empty element. m_data.push_back(""); } } private: std::vector<std::string> m_data; }; std::istream& operator>>(std::istream& str, CSVRow& data) { data.readNextRow(str); return str; } int main() { std::ifstream file("plop.csv"); CSVRow row; while(file >> row) { std::cout << "4th Element(" << row[3] << ")\n"; } }
但是通过一些工作,我们可以在技术上创build一个迭代器:
class CSVIterator { public: typedef std::input_iterator_tag iterator_category; typedef CSVRow value_type; typedef std::size_t difference_type; typedef CSVRow* pointer; typedef CSVRow& reference; CSVIterator(std::istream& str) :m_str(str.good()?&str:NULL) { ++(*this); } CSVIterator() :m_str(NULL) {} // Pre Increment CSVIterator& operator++() {if (m_str) { if (!((*m_str) >> m_row)){m_str = NULL;}}return *this;} // Post increment CSVIterator operator++(int) {CSVIterator tmp(*this);++(*this);return tmp;} CSVRow const& operator*() const {return m_row;} CSVRow const* operator->() const {return &m_row;} bool operator==(CSVIterator const& rhs) {return ((this == &rhs) || ((this->m_str == NULL) && (rhs.m_str == NULL)));} bool operator!=(CSVIterator const& rhs) {return !((*this) == rhs);} private: std::istream* m_str; CSVRow m_row; }; int main() { std::ifstream file("plop.csv"); for(CSVIterator loop(file); loop != CSVIterator(); ++loop) { std::cout << "4th Element(" << (*loop)[3] << ")\n"; } }
使用Boost Tokenizer的解决scheme:
std::vector<std::string> vec; using namespace boost; tokenizer<escaped_list_separator<char> > tk( line, escaped_list_separator<char>('\\', ',', '\"')); for (tokenizer<escaped_list_separator<char> >::iterator i(tk.begin()); i!=tk.end();++i) { vec.push_back(*i); }
C ++string工具包库(StrTk)有一个令牌网格类,允许您从文本文件,string或字符缓冲区加载数据,并以行列方式parsing/处理它们。
您可以指定行分隔符和列分隔符或仅使用默认值。
void foo() { std::string data = "1,2,3,4,5\n" "0,2,4,6,8\n" "1,3,5,7,9\n"; strtk::token_grid grid(data,data.size(),","); for(std::size_t i = 0; i < grid.row_count(); ++i) { strtk::token_grid::row_type r = grid.row(i); for(std::size_t j = 0; j < r.size(); ++j) { std::cout << r.get<int>(j) << "\t"; } std::cout << std::endl; } std::cout << std::endl; }
更多的例子可以在这里find
使用SpiritparsingCSV并不是过分的。 Spirit非常适合微parsing任务。 例如,在Spirit 2.1中,它就像下面这样简单:
bool r = phrase_parse(first, last, // Begin grammar ( double_ % ',' ) , // End grammar space, v);
向量v被塞满值。 有一系列的教程在刚刚在Boost 1.41中发布的新的Spirit 2.1文档中涉及到这一点。
教程从简单到复杂。 CSVparsing器在中间的某个地方呈现,并触及使用Spirit的各种技巧。 生成的代码和手写代码一样紧密。 检查出生成的汇编程序!
您可以使用带有escaped_list_separator的Boost Tokenizer。
escaped_list_separatorparsingcsv的超集。 升压::标记生成器
这只使用Boost标记器头文件,不需要链接来增强库。
下面是一个例子,(详见C ++ Boost TokenizerparsingCSV文件或者Boost::tokenizer
):
#include <iostream> // cout, endl #include <fstream> // fstream #include <vector> #include <string> #include <algorithm> // copy #include <iterator> // ostream_operator #include <boost/tokenizer.hpp> int main() { using namespace std; using namespace boost; string data("data.csv"); ifstream in(data.c_str()); if (!in.is_open()) return 1; typedef tokenizer< escaped_list_separator<char> > Tokenizer; vector< string > vec; string line; while (getline(in,line)) { Tokenizer tok(line); vec.assign(tok.begin(),tok.end()); // vector now contains strings from one row, output to cout here copy(vec.begin(), vec.end(), ostream_iterator<string>(cout, "|")); cout << "\n----------------------" << endl; } }
我的版本不使用任何标准的C ++ 11库。 它能很好地处理Excel CSV报价:
spam eggs,"foo,bar","""fizz buzz""" 1.23,4.567,-8.00E+09
代码被写为一个有限状态机,并且一次只消耗一个字符。 我认为这是更容易理解。
#include <istream> #include <string> #include <vector> enum class CSVState { UnquotedField, QuotedField, QuotedQuote }; std::vector<std::string> readCSVRow(const std::string &row) { CSVState state = CSVState::UnquotedField; std::vector<std::string> fields {""}; size_t i = 0; // index of the current field for (char c : row) { switch (state) { case CSVState::UnquotedField: switch (c) { case ',': // end of field fields.push_back(""); i++; break; case '"': state = CSVState::QuotedField; break; default: fields[i].push_back(c); break; } break; case CSVState::QuotedField: switch (c) { case '"': state = CSVState::QuotedQuote; break; default: fields[i].push_back(c); break; } break; case CSVState::QuotedQuote: switch (c) { case ',': // , after closing quote fields.push_back(""); i++; state = CSVState::UnquotedField; break; case '"': // "" -> " fields[i].push_back('"'); state = CSVState::QuotedField; break; default: // end of quote state = CSVState::UnquotedField; break; } break; } } return fields; } /// Read CSV file, Excel dialect. Accept "quoted fields ""with quotes""" std::vector<std::vector<std::string>> readCSV(std::istream &in) { std::vector<std::vector<std::string>> table; std::string row; while (!in.eof()) { std::getline(in, row); if (in.bad() || in.fail()) { break; } auto fields = readCSVRow(row); table.push_back(fields); } return table; }
如果你关心parsing正确的CSV,这将做相对缓慢,因为它一次只能处理一个字符。
void ParseCSV(const string& csvSource, vector<vector<string> >& lines) { bool inQuote(false); bool newLine(false); string field; lines.clear(); vector<string> line; string::const_iterator aChar = csvSource.begin(); while (aChar != csvSource.end()) { switch (*aChar) { case '"': newLine = false; inQuote = !inQuote; break; case ',': newLine = false; if (inQuote == true) { field += *aChar; } else { line.push_back(field); field.clear(); } break; case '\n': case '\r': if (inQuote == true) { field += *aChar; } else { if (newLine == false) { line.push_back(field); lines.push_back(line); field.clear(); line.clear(); newLine = true; } } break; default: newLine = false; field.push_back(*aChar); break; } aChar++; } if (field.size()) line.push_back(field); if (line.size()) lines.push_back(line); }
对于CSV文件使用Boost Tokenizer escaped_list_separator时,应该注意以下几点:
- 它需要一个转义字符(默认反斜线 – \)
- 它需要一个splitter / seperator-character(默认逗号 – ,)
- 它需要一个引号字符(默认引号 – “)
wiki指定的CSV格式表示数据字段可以包含引号中的分隔符(支持):
1997年,福特,E350,“超级豪华卡车”
wiki指定的CSV格式指出,应该用双引号处理单引号(escaped_list_separator将除去所有引号字符):
1997年,福特,E350,“超级”“豪华”“卡车”
CSV格式不指定任何反斜杠字符应该被剥离(escaped_list_separator将剥离所有转义字符)。
解决boost escaped_list_separator的默认行为的一个可能的解决方法:
- 首先用两个反斜线字符(\\)replace所有反斜杠字符(\),以免它们被剥离。
- 其次用一个反斜杠字符和一个引号(\“)replace所有的双引号(”“)
这种解决方法的副作用是由双引号表示的空数据字段将被转换为单引号标记。 当迭代令牌时,必须检查令牌是否为单引号,并将其视为空string。
不漂亮,但它的工作原理,只要引号内没有换行符。
您可能想看看我的FOSS项目CSVfix ( 更新的链接 ),这是一个用C ++编写的CSVstream编辑器。 CSVparsing器没有奖励,但是这个工作和整个包可以做你所需要的,而不需要你编写任何代码。
有关CSVparsing器的信息,请参见alib / src / a_csv.cpp ;有关用法示例,请参阅csvlib / src / csved_ioman.cpp ( IOManager::ReadCSV
)。
由于所有的CSV问题似乎在这里redirect,我想我会在这里发表我的答案。 这个答案不直接解决提问者的问题。 我希望能够读取已知为CSV格式的stream,并且每个字段的types都是已知的。 当然,下面的方法可以用来把每个字段都当成一个stringtypes。
作为我想如何使用CSVinputstream的一个例子,请考虑以下input(从维基百科的CSV页面中获取 ):
const char input[] = "Year,Make,Model,Description,Price\n" "1997,Ford,E350,\"ac, abs, moon\",3000.00\n" "1999,Chevy,\"Venture \"\"Extended Edition\"\"\",\"\",4900.00\n" "1999,Chevy,\"Venture \"\"Extended Edition, Very Large\"\"\",\"\",5000.00\n" "1996,Jeep,Grand Cherokee,\"MUST SELL!\n\ air, moon roof, loaded\",4799.00\n" ;
然后,我想能够读取像这样的数据:
std::istringstream ss(input); std::string title[5]; int year; std::string make, model, desc; float price; csv_istream(ss) >> title[0] >> title[1] >> title[2] >> title[3] >> title[4]; while (csv_istream(ss) >> year >> make >> model >> desc >> price) { //...do something with the record... }
这是我结束的解决scheme。
struct csv_istream { std::istream &is_; csv_istream (std::istream &is) : is_(is) {} void scan_ws () const { while (is_.good()) { int c = is_.peek(); if (c != ' ' && c != '\t') break; is_.get(); } } void scan (std::string *s = 0) const { std::string ws; int c = is_.get(); if (is_.good()) { do { if (c == ',' || c == '\n') break; if (s) { ws += c; if (c != ' ' && c != '\t') { *s += ws; ws.clear(); } } c = is_.get(); } while (is_.good()); if (is_.eof()) is_.clear(); } } template <typename T, bool> struct set_value { void operator () (std::string in, T &v) const { std::istringstream(in) >> v; } }; template <typename T> struct set_value<T, true> { template <bool SIGNED> void convert (std::string in, T &v) const { if (SIGNED) v = ::strtoll(in.c_str(), 0, 0); else v = ::strtoull(in.c_str(), 0, 0); } void operator () (std::string in, T &v) const { convert<is_signed_int<T>::val>(in, v); } }; template <typename T> const csv_istream & operator >> (T &v) const { std::string tmp; scan(&tmp); set_value<T, is_int<T>::val>()(tmp, v); return *this; } const csv_istream & operator >> (std::string &v) const { v.clear(); scan_ws(); if (is_.peek() != '"') scan(&v); else { std::string tmp; is_.get(); std::getline(is_, tmp, '"'); while (is_.peek() == '"') { v += tmp; v += is_.get(); std::getline(is_, tmp, '"'); } v += tmp; scan(); } return *this; } template <typename T> const csv_istream & operator >> (T &(*manip)(T &)) const { is_ >> manip; return *this; } operator bool () const { return !is_.fail(); } };
通过C ++ 11中新的整体特征模板可以简化下列帮助程序:
template <typename T> struct is_signed_int { enum { val = false }; }; template <> struct is_signed_int<short> { enum { val = true}; }; template <> struct is_signed_int<int> { enum { val = true}; }; template <> struct is_signed_int<long> { enum { val = true}; }; template <> struct is_signed_int<long long> { enum { val = true}; }; template <typename T> struct is_unsigned_int { enum { val = false }; }; template <> struct is_unsigned_int<unsigned short> { enum { val = true}; }; template <> struct is_unsigned_int<unsigned int> { enum { val = true}; }; template <> struct is_unsigned_int<unsigned long> { enum { val = true}; }; template <> struct is_unsigned_int<unsigned long long> { enum { val = true}; }; template <typename T> struct is_int { enum { val = (is_signed_int<T>::val || is_unsigned_int<T>::val) }; };
另一个解决scheme类似于Loki Astari的答案 ,在C ++ 11中。 这里的行是给定types的std::tuple
。 代码扫描一行,然后扫描直到每个分隔符,然后转换并将值直接转储到元组(带有一些模板代码)。
for (auto row : csv<std::string, int, float>(file, ',')) { std::cout << "first col: " << std::get<0>(row) << std::endl; }
Advanges:
- 相当干净和简单的使用,只有C ++ 11。
- 通过
operator>>
自动转换成std::tuple<t1, ...>
。
less了什么东西:
- 逃避和引用
- 在CSV格式不正确的情况下无error handling。
主要代码:
#include <iterator> #include <sstream> #include <string> namespace csvtools { /// Read the last element of the tuple without calling recursively template <std::size_t idx, class... fields> typename std::enable_if<idx >= std::tuple_size<std::tuple<fields...>>::value - 1>::type read_tuple(std::istream &in, std::tuple<fields...> &out, const char delimiter) { std::string cell; std::getline(in, cell, delimiter); std::stringstream cell_stream(cell); cell_stream >> std::get<idx>(out); } /// Read the @p idx-th element of the tuple and then calls itself with @p idx + 1 to /// read the next element of the tuple. Automatically falls in the previous case when /// reaches the last element of the tuple thanks to enable_if template <std::size_t idx, class... fields> typename std::enable_if<idx < std::tuple_size<std::tuple<fields...>>::value - 1>::type read_tuple(std::istream &in, std::tuple<fields...> &out, const char delimiter) { std::string cell; std::getline(in, cell, delimiter); std::stringstream cell_stream(cell); cell_stream >> std::get<idx>(out); read_tuple<idx + 1, fields...>(in, out, delimiter); } } /// Iterable csv wrapper around a stream. @p fields the list of types that form up a row. template <class... fields> class csv { std::istream &_in; const char _delim; public: typedef std::tuple<fields...> value_type; class iterator; /// Construct from a stream. inline csv(std::istream &in, const char delim) : _in(in), _delim(delim) {} /// Status of the underlying stream /// @{ inline bool good() const { return _in.good(); } inline const std::istream &underlying_stream() const { return _in; } /// @} inline iterator begin(); inline iterator end(); private: /// Reads a line into a stringstream, and then reads the line into a tuple, that is returned inline value_type read_row() { std::string line; std::getline(_in, line); std::stringstream line_stream(line); std::tuple<fields...> retval; csvtools::read_tuple<0, fields...>(line_stream, retval, _delim); return retval; } }; /// Iterator; just calls recursively @ref csv::read_row and stores the result. template <class... fields> class csv<fields...>::iterator { csv::value_type _row; csv *_parent; public: typedef std::input_iterator_tag iterator_category; typedef csv::value_type value_type; typedef std::size_t difference_type; typedef csv::value_type * pointer; typedef csv::value_type & reference; /// Construct an empty/end iterator inline iterator() : _parent(nullptr) {} /// Construct an iterator at the beginning of the @p parent csv object. inline iterator(csv &parent) : _parent(parent.good() ? &parent : nullptr) { ++(*this); } /// Read one row, if possible. Set to end if parent is not good anymore. inline iterator &operator++() { if (_parent != nullptr) { _row = _parent->read_row(); if (!_parent->good()) { _parent = nullptr; } } return *this; } inline iterator operator++(int) { iterator copy = *this; ++(*this); return copy; } inline csv::value_type const &operator*() const { return _row; } inline csv::value_type const *operator->() const { return &_row; } bool operator==(iterator const &other) { return (this == &other) or (_parent == nullptr and other._parent == nullptr); } bool operator!=(iterator const &other) { return not (*this == other); } }; template <class... fields> typename csv<fields...>::iterator csv<fields...>::begin() { return iterator(*this); } template <class... fields> typename csv<fields...>::iterator csv<fields...>::end() { return iterator(); }
我在GitHub上做了一个小小的工作例子。 我一直在使用它来parsing一些数字数据,并达到了它的目的。
另一个CSV I / O库可以在这里find:
http://code.google.com/p/fast-cpp-csv-parser/
#include "csv.h" int main(){ io::CSVReader<3> in("ram.csv"); in.read_header(io::ignore_extra_column, "vendor", "size", "speed"); std::string vendor; int size; double speed; while(in.read_row(vendor, size, speed)){ // do stuff with the data } }
这是Unicode CSVparsing器的另一个实现(与wchar_t一起使用)。 我写了一部分,而乔纳森·莱弗勒写了其余的。
注意:此parsing器旨在尽可能地复制Excel的行为,尤其是在导入损坏或格式错误的 CSV文件时。
这是原来的问题 – parsing带有多行字段的CSV文件并转义双引号
这是作为SSCCE的代码(简短,独立,正确的例子)。
#include <stdbool.h> #include <wchar.h> #include <wctype.h> extern const wchar_t *nextCsvField(const wchar_t *p, wchar_t sep, bool *newline); // Returns a pointer to the start of the next field, // or zero if this is the last field in the CSV // p is the start position of the field // sep is the separator used, ie comma or semicolon // newline says whether the field ends with a newline or with a comma const wchar_t *nextCsvField(const wchar_t *p, wchar_t sep, bool *newline) { // Parse quoted sequences if ('"' == p[0]) { p++; while (1) { // Find next double-quote p = wcschr(p, L'"'); // If we don't find it or it's the last symbol // then this is the last field if (!p || !p[1]) return 0; // Check for "", it is an escaped double-quote if (p[1] != '"') break; // Skip the escaped double-quote p += 2; } } // Find next newline or comma. wchar_t newline_or_sep[4] = L"\n\r "; newline_or_sep[2] = sep; p = wcspbrk(p, newline_or_sep); // If no newline or separator, this is the last field. if (!p) return 0; // Check if we had newline. *newline = (p[0] == '\r' || p[0] == '\n'); // Handle "\r\n", otherwise just increment if (p[0] == '\r' && p[1] == '\n') p += 2; else p++; return p; } static wchar_t *csvFieldData(const wchar_t *fld_s, const wchar_t *fld_e, wchar_t *buffer, size_t buflen) { wchar_t *dst = buffer; wchar_t *end = buffer + buflen - 1; const wchar_t *src = fld_s; if (*src == L'"') { const wchar_t *p = src + 1; while (p < fld_e && dst < end) { if (p[0] == L'"' && p+1 < fld_s && p[1] == L'"') { *dst++ = p[0]; p += 2; } else if (p[0] == L'"') { p++; break; } else *dst++ = *p++; } src = p; } while (src < fld_e && dst < end) *dst++ = *src++; if (dst >= end) return 0; *dst = L'\0'; return(buffer); } static void dissect(const wchar_t *line) { const wchar_t *start = line; const wchar_t *next; bool eol; wprintf(L"Input %3zd: [%.*ls]\n", wcslen(line), wcslen(line)-1, line); while ((next = nextCsvField(start, L',', &eol)) != 0) { wchar_t buffer[1024]; wprintf(L"Raw Field: [%.*ls] (eol = %d)\n", (next - start - eol), start, eol); if (csvFieldData(start, next-1, buffer, sizeof(buffer)/sizeof(buffer[0])) != 0) wprintf(L"Field %3zd: [%ls]\n", wcslen(buffer), buffer); start = next; } } static const wchar_t multiline[] = L"First field of first row,\"This field is multiline\n" "\n" "but that's OK because it's enclosed in double quotes, and this\n" "is an escaped \"\" double quote\" but this one \"\" is not\n" " \"This is second field of second row, but it is not multiline\n" " because it doesn't start \n" " with an immediate double quote\"\n" ; int main(void) { wchar_t line[1024]; while (fgetws(line, sizeof(line)/sizeof(line[0]), stdin)) dissect(line); dissect(multiline); return 0; }
你需要做的第一件事是确保文件存在。 要做到这一点,你只需要尝试打开path上的文件stream。 打开文件stream后,使用stream.fail()来查看它是否按预期工作。
bool fileExists(string fileName) { ifstream test; test.open(fileName.c_str()); if (test.fail()) { test.close(); return false; } else { test.close(); return true; } }
您还必须validation提供的文件是否是正确的文件types。 要做到这一点,您需要查看提供的文件path,直到find文件扩展名。 一旦你有文件扩展名,请确保它是一个.csv文件。
bool verifyExtension(string filename) { int period = 0; for (unsigned int i = 0; i < filename.length(); i++) { if (filename[i] == '.') period = i; } string extension; for (unsigned int i = period; i < filename.length(); i++) extension += filename[i]; if (extension == ".csv") return true; else return false; }
该函数将返回稍后在错误消息中使用的文件扩展名。
string getExtension(string filename) { int period = 0; for (unsigned int i = 0; i < filename.length(); i++) { if (filename[i] == '.') period = i; } string extension; if (period != 0) { for (unsigned int i = period; i < filename.length(); i++) extension += filename[i]; } else extension = "NO FILE"; return extension; }
这个函数实际上会调用上面创build的错误检查,然后通过文件parsing。
void parseFile(string fileName) { if (fileExists(fileName) && verifyExtension(fileName)) { ifstream fs; fs.open(fileName.c_str()); string fileCommand; while (fs.good()) { string temp; getline(fs, fileCommand, '\n'); for (unsigned int i = 0; i < fileCommand.length(); i++) { if (fileCommand[i] != ',') temp += fileCommand[i]; else temp += " "; } if (temp != "\0") { // Place your code here to run the file. } } fs.close(); } else if (!fileExists(fileName)) { cout << "Error: The provided file does not exist: " << fileName << endl; if (!verifyExtension(fileName)) { if (getExtension(fileName) != "NO FILE") cout << "\tCheck the file extension." << endl; else cout << "\tThere is no file in the provided path." << endl; } } else if (!verifyExtension(fileName)) { if (getExtension(fileName) != "NO FILE") cout << "Incorrect file extension provided: " << getExtension(fileName) << endl; else cout << "There is no file in the following path: " << fileName << endl; } }
不好意思,但是这一切似乎都是为了隐藏几行代码。
为什么不这样做:
/** Read line from a CSV file @param[in] fp file pointer to open file @param[in] vls reference to vector of strings to hold next line */ void readCSV( FILE *fp, std::vector<std::string>& vls ) { vls.clear(); if( ! fp ) return; char buf[10000]; if( ! fgets( buf,999,fp) ) return; std::string s = buf; int p,q; q = -1; // loop over columns while( 1 ) { p = q; q = s.find_first_of(",\n",p+1); if( q == -1 ) break; vls.push_back( s.substr(p+1,qp-1) ); } } int _tmain(int argc, _TCHAR* argv[]) { std::vector<std::string> vls; FILE * fp = fopen( argv[1], "r" ); if( ! fp ) return 1; readCSV( fp, vls ); readCSV( fp, vls ); readCSV( fp, vls ); std::cout << "row 3, col 4 is " << vls[3].c_str() << "\n"; return 0; }
Here is code for reading a matrix, note you also have a csvwrite function in matlab
void loadFromCSV( const std::string& filename ) { std::ifstream file( filename.c_str() ); std::vector< std::vector<std::string> > matrix; std::vector<std::string> row; std::string line; std::string cell; while( file ) { std::getline(file,line); std::stringstream lineStream(line); row.clear(); while( std::getline( lineStream, cell, ',' ) ) row.push_back( cell ); if( !row.empty() ) matrix.push_back( row ); } for( int i=0; i<int(matrix.size()); i++ ) { for( int j=0; j<int(matrix[i].size()); j++ ) std::cout << matrix[i][j] << " "; std::cout << std::endl; } }
You can open and read .csv file using fopen ,fscanf functions ,but the important thing is to parse the data.Simplest way to parse the data using delimiter.In case of .csv , delimiter is ','.
Suppose your data1.csv file is as follows :
A,45,76,01 B,77,67,02 C,63,76,03 D,65,44,04
you can tokenize data and store in char array and later use atoi() etc function for appropriate conversions
FILE *fp; char str1[10], str2[10], str3[10], str4[10]; fp = fopen("G:\\data1.csv", "r"); if(NULL == fp) { printf("\nError in opening file."); return 0; } while(EOF != fscanf(fp, " %[^,], %[^,], %[^,], %s, %s, %s, %s ", str1, str2, str3, str4)) { printf("\n%s %s %s %s", str1, str2, str3, str4); } fclose(fp);
[^,], ^ -it inverts logic , means match any string that does not contain comma then last , says to match comma that terminated previous string.
You could also take a look at capabilities of Qt
library.
It has regular expressions support and QString class has nice methods, eg split()
returning QStringList, list of strings obtained by splitting the original string with a provided delimiter. Should suffice for csv file..
To get a column with a given header name I use following: c++ inheritance Qt problem qstring
If you don't want to deal with including boost in your project (it is considerably large if all you are going to use it for is CSV parsing…)
I have had luck with the CSV parsing here:
http://www.zedwood.com/article/112/cpp-csv-parser
It handles quoted fields – but does not handle inline \n characters (which is probably fine for most uses).
This is an old thread but its still at the top of search results, so I'm adding my solution using std::stringstream and a simple string replace method by Yves Baumes I found here.
The following example will read a file line by line, ignore comment lines starting with // and parse the other lines into a combination of strings, ints and doubles. Stringstream does the parsing, but expects fields to be delimited by whitespace, so I use stringreplace to turn commas into spaces first. It handles tabs ok, but doesn't deal with quoted strings.
Bad or missing input is simply ignored, which may or may not be good, depending on your circumstance.
#include <string> #include <sstream> #include <fstream> void StringReplace(std::string& str, const std::string& oldStr, const std::string& newStr) // code by Yves Baumes // http://stackoverflow.com/questions/1494399/how-do-i-search-find-and-replace-in-a-standard-string { size_t pos = 0; while((pos = str.find(oldStr, pos)) != std::string::npos) { str.replace(pos, oldStr.length(), newStr); pos += newStr.length(); } } void LoadCSV(std::string &filename) { std::ifstream stream(filename); std::string in_line; std::string Field; std::string Chan; int ChanType; double Scale; int Import; while (std::getline(stream, in_line)) { StringReplace(in_line, ",", " "); std::stringstream line(in_line); line >> Field >> Chan >> ChanType >> Scale >> Import; if (Field.substr(0,2)!="//") { // do your stuff // this is CBuilder code for demonstration, sorry ShowMessage((String)Field.c_str() + "\n" + Chan.c_str() + "\n" + IntToStr(ChanType) + "\n" +FloatToStr(Scale) + "\n" +IntToStr(Import)); } } }
For what it is worth, here is my implementation. It deals with wstring input, but could be adjusted to string easily. It does not handle newline in fields (as my application does not either, but adding its support isn't too difficult) and it does not comply with "\r\n" end of line as per RFC (assuming you use std::getline), but it does handle whitespace trimming and double-quotes correctly (hopefully).
using namespace std; // trim whitespaces around field or double-quotes, remove double-quotes and replace escaped double-quotes (double double-quotes) wstring trimquote(const wstring& str, const wstring& whitespace, const wchar_t quotChar) { wstring ws; wstring::size_type strBegin = str.find_first_not_of(whitespace); if (strBegin == wstring::npos) return L""; wstring::size_type strEnd = str.find_last_not_of(whitespace); wstring::size_type strRange = strEnd - strBegin + 1; if((str[strBegin] == quotChar) && (str[strEnd] == quotChar)) { ws = str.substr(strBegin+1, strRange-2); strBegin = 0; while((strEnd = ws.find(quotChar, strBegin)) != wstring::npos) { ws.erase(strEnd, 1); strBegin = strEnd+1; } } else ws = str.substr(strBegin, strRange); return ws; } pair<unsigned, unsigned> nextCSVQuotePair(const wstring& line, const wchar_t quotChar, unsigned ofs = 0) { pair<unsigned, unsigned> r; r.first = line.find(quotChar, ofs); r.second = wstring::npos; if(r.first != wstring::npos) { r.second = r.first; while(((r.second = line.find(quotChar, r.second+1)) != wstring::npos) && (line[r.second+1] == quotChar)) // WARNING: assumes null-terminated string such that line[r.second+1] always exist r.second++; } return r; } unsigned parseLine(vector<wstring>& fields, const wstring& line) { unsigned ofs, ofs0, np; const wchar_t delim = L','; const wstring whitespace = L" \t\xa0\x3000\x2000\x2001\x2002\x2003\x2004\x2005\x2006\x2007\x2008\x2009\x200a\x202f\x205f"; const wchar_t quotChar = L'\"'; pair<unsigned, unsigned> quot; fields.clear(); ofs = ofs0 = 0; quot = nextCSVQuotePair(line, quotChar); while((np = line.find(delim, ofs)) != wstring::npos) { if((np > quot.first) && (np < quot.second)) { // skip delimiter inside quoted field ofs = quot.second+1; quot = nextCSVQuotePair(line, quotChar, ofs); continue; } fields.push_back( trimquote(line.substr(ofs0, np-ofs0), whitespace, quotChar) ); ofs = ofs0 = np+1; } fields.push_back( trimquote(line.substr(ofs0), whitespace, quotChar) ); return fields.size(); }
Here is a ready-to use function if all you need is to load a data file of doubles (no integers, no text).
#include <sstream> #include <fstream> #include <iterator> #include <string> #include <vector> #include <algorithm> using namespace std; /** * Parse a CSV data file and fill the 2d STL vector "data". * Limits: only "pure datas" of doubles, not encapsulated by " and without \n inside. * Further no formatting in the data (eg scientific notation) * It however handles both dots and commas as decimal separators and removes thousand separator. * * returnCodes[0]: file access 0-> ok 1-> not able to read; 2-> decimal separator equal to comma separator * returnCodes[1]: number of records * returnCodes[2]: number of fields. -1 If rows have different field size * */ vector<int> readCsvData (vector <vector <double>>& data, const string& filename, const string& delimiter, const string& decseparator){ int vv[3] = { 0,0,0 }; vector<int> returnCodes(&vv[0], &vv[0]+3); string rowstring, stringtoken; double doubletoken; int rowcount=0; int fieldcount=0; data.clear(); ifstream iFile(filename, ios_base::in); if (!iFile.is_open()){ returnCodes[0] = 1; return returnCodes; } while (getline(iFile, rowstring)) { if (rowstring=="") continue; // empty line rowcount ++; //let's start with 1 if(delimiter == decseparator){ returnCodes[0] = 2; return returnCodes; } if(decseparator != "."){ // remove dots (used as thousand separators) string::iterator end_pos = remove(rowstring.begin(), rowstring.end(), '.'); rowstring.erase(end_pos, rowstring.end()); // replace decimal separator with dots. replace(rowstring.begin(), rowstring.end(),decseparator.c_str()[0], '.'); } else { // remove commas (used as thousand separators) string::iterator end_pos = remove(rowstring.begin(), rowstring.end(), ','); rowstring.erase(end_pos, rowstring.end()); } // tokenize.. vector<double> tokens; // Skip delimiters at beginning. string::size_type lastPos = rowstring.find_first_not_of(delimiter, 0); // Find first "non-delimiter". string::size_type pos = rowstring.find_first_of(delimiter, lastPos); while (string::npos != pos || string::npos != lastPos){ // Found a token, convert it to double add it to the vector. stringtoken = rowstring.substr(lastPos, pos - lastPos); if (stringtoken == "") { tokens.push_back(0.0); } else { istringstream totalSString(stringtoken); totalSString >> doubletoken; tokens.push_back(doubletoken); } // Skip delimiters. Note the "not_of" lastPos = rowstring.find_first_not_of(delimiter, pos); // Find next "non-delimiter" pos = rowstring.find_first_of(delimiter, lastPos); } if(rowcount == 1){ fieldcount = tokens.size(); returnCodes[2] = tokens.size(); } else { if ( tokens.size() != fieldcount){ returnCodes[2] = -1; } } data.push_back(tokens); } iFile.close(); returnCodes[1] = rowcount; return returnCodes; }
Another quick and easy way is to use Boost.Fusion I/O
:
#include <iostream> #include <sstream> #include <boost/fusion/adapted/boost_tuple.hpp> #include <boost/fusion/sequence/io.hpp> namespace fusion = boost::fusion; struct CsvString { std::string value; // Stop reading a string once a CSV delimeter is encountered. friend std::istream& operator>>(std::istream& s, CsvString& v) { v.value.clear(); for(;;) { auto c = s.peek(); if(std::istream::traits_type::eof() == c || ',' == c || '\n' == c) break; v.value.push_back(c); s.get(); } return s; } friend std::ostream& operator<<(std::ostream& s, CsvString const& v) { return s << v.value; } }; int main() { std::stringstream input("abc,123,true,3.14\n" "def,456,false,2.718\n"); typedef boost::tuple<CsvString, int, bool, double> CsvRow; using fusion::operator<<; std::cout << std::boolalpha; using fusion::operator>>; input >> std::boolalpha; input >> fusion::tuple_open("") >> fusion::tuple_close("\n") >> fusion::tuple_delimiter(','); for(CsvRow row; input >> row;) std::cout << row << '\n'; }
输出:
(abc 123 true 3.14) (def 456 false 2.718)
I wrote a nice way of parsing CSV files and I thought I should add it as an answer:
#include <algorithm> #include <fstream> #include <iostream> #include <stdlib.h> #include <stdio.h> struct CSVDict { std::vector< std::string > inputImages; std::vector< double > inputLabels; }; /** \brief Splits the string \param str String to split \param delim Delimiter on the basis of which splitting is to be done \return results Output in the form of vector of strings */ std::vector<std::string> stringSplit( const std::string &str, const std::string &delim ) { std::vector<std::string> results; for (size_t i = 0; i < str.length(); i++) { std::string tempString = ""; while ((str[i] != *delim.c_str()) && (i < str.length())) { tempString += str[i]; i++; } results.push_back(tempString); } return results; } /** \brief Parse the supplied CSV File and obtain Row and Column information. Assumptions: 1. Header information is in first row 2. Delimiters are only used to differentiate cell members \param csvFileName The full path of the file to parse \param inputColumns The string of input columns which contain the data to be used for further processing \param inputLabels The string of input labels based on which further processing is to be done \param delim The delimiters used in inputColumns and inputLabels \return Vector of Vector of strings: Collection of rows and columns */ std::vector< CSVDict > parseCSVFile( const std::string &csvFileName, const std::string &inputColumns, const std::string &inputLabels, const std::string &delim ) { std::vector< CSVDict > return_CSVDict; std::vector< std::string > inputColumnsVec = stringSplit(inputColumns, delim), inputLabelsVec = stringSplit(inputLabels, delim); std::vector< std::vector< std::string > > returnVector; std::ifstream inFile(csvFileName.c_str()); int row = 0; std::vector< size_t > inputColumnIndeces, inputLabelIndeces; for (std::string line; std::getline(inFile, line, '\n');) { CSVDict tempDict; std::vector< std::string > rowVec; line.erase(std::remove(line.begin(), line.end(), '"'), line.end()); rowVec = stringSplit(line, delim); // for the first row, record the indeces of the inputColumns and inputLabels if (row == 0) { for (size_t i = 0; i < rowVec.size(); i++) { for (size_t j = 0; j < inputColumnsVec.size(); j++) { if (rowVec[i] == inputColumnsVec[j]) { inputColumnIndeces.push_back(i); } } for (size_t j = 0; j < inputLabelsVec.size(); j++) { if (rowVec[i] == inputLabelsVec[j]) { inputLabelIndeces.push_back(i); } } } } else { for (size_t i = 0; i < inputColumnIndeces.size(); i++) { tempDict.inputImages.push_back(rowVec[inputColumnIndeces[i]]); } for (size_t i = 0; i < inputLabelIndeces.size(); i++) { double test = std::atof(rowVec[inputLabelIndeces[i]].c_str()); tempDict.inputLabels.push_back(std::atof(rowVec[inputLabelIndeces[i]].c_str())); } return_CSVDict.push_back(tempDict); } row++; } return return_CSVDict; }
It is possible to use std::regex
.
Depending on the size of your file and the memory available to you , it is possible read it either line by line or entirely in an std::string
.
To read the file one can use :
std::ifstream t("file.txt"); std::string sin((std::istreambuf_iterator<char>(t)), std::istreambuf_iterator<char>());
then you can match with this which is actually customizable to your needs.
std::regex word_regex(",\\s]+"); auto what = std::sregex_iterator(sin.begin(), sin.end(), word_regex); auto wend = std::sregex_iterator(); std::vector<std::string> v; for (;what!=wend ; wend) { std::smatch match = *what; v.push_back(match.str()); }
Since i'm not used to boost right now, I will suggest a more simple solution. Lets suppose that your .csv file has 100 lines with 10 numbers in each line separated by a ','. You could load this data in the form of an array with the following code:
#include <iostream> #include <fstream> #include <sstream> #include <string> using namespace std; int main() { int A[100][10]; ifstream ifs; ifs.open("name_of_file.csv"); string s1; char c; for(int k=0; k<100; k++) { getline(ifs,s1); stringstream stream(s1); int j=0; while(1) { stream >>A[k][j]; stream >> c; j++; if(!stream) {break;} } } }
I needed an easy-to-use C++ library for parsing CSV files but couldn't find any available, so I ended up building one. Rapidcsv is a C++11 header-only library which gives direct access to parsed columns (or rows) as vectors, in datatype of choice. 例如:
#include <iostream> #include <vector> #include <rapidcsv.h> int main() { rapidcsv::Document doc("../tests/msft.csv"); std::vector<float> close = doc.GetColumn<float>("Close"); std::cout << "Read " << close.size() << " values." << std::endl; }
You can use this library: https://github.com/vadamsky/csvworker
Code for example:
#include <iostream> #include "csvworker.h" using namespace std; int main() { // CsvWorker csv; csv.loadFromFile("example.csv"); cout << csv.getRowsNumber() << " " << csv.getColumnsNumber() << endl; csv.getFieldRef(0, 2) = "0"; csv.getFieldRef(1, 1) = "0"; csv.getFieldRef(1, 3) = "0"; csv.getFieldRef(2, 0) = "0"; csv.getFieldRef(2, 4) = "0"; csv.getFieldRef(3, 1) = "0"; csv.getFieldRef(3, 3) = "0"; csv.getFieldRef(4, 2) = "0"; for(unsigned int i=0;i<csv.getRowsNumber();++i) { //cout << csv.getRow(i) << endl; for(unsigned int j=0;j<csv.getColumnsNumber();++j) { cout << csv.getField(i, j) << "."; } cout << endl; } csv.saveToFile("test.csv"); // CsvWorker csv2(4,4); csv2.getFieldRef(0, 0) = "a"; csv2.getFieldRef(0, 1) = "b"; csv2.getFieldRef(0, 2) = "r"; csv2.getFieldRef(0, 3) = "a"; csv2.getFieldRef(1, 0) = "c"; csv2.getFieldRef(1, 1) = "a"; csv2.getFieldRef(1, 2) = "d"; csv2.getFieldRef(2, 0) = "a"; csv2.getFieldRef(2, 1) = "b"; csv2.getFieldRef(2, 2) = "r"; csv2.getFieldRef(2, 3) = "a"; csv2.saveToFile("test2.csv"); return 0; }
I wrote a header-only, C++11 CSV parser . It's well tested, fast, supports the entire CSV spec (quoted fields, delimiter/terminator in quotes, quote escaping, etc.), and is configurable to account for the CSVs that don't adhere to the specification.
Configuration is done through a fluent interface:
// constructor accepts any input stream CsvParser parser = CsvParser(std::cin) .delimiter(';') // delimited by ; instead of , .quote('\'') // quoted fields use ' instead of " .terminator('\0'); // terminated by \0 instead of by \r\n, \n, or \r
Parsing is just a range based for loop:
#include <iostream> #include "../parser.hpp" using namespace aria::csv; int main() { std::ifstream f("some_file.csv"); CsvParser parser(f); for (auto& row : parser) { for (auto& field : row) { std::cout << field << " | "; } std::cout << std::endl; } }
You gotta feel proud when you use something so beautiful as boost::spirit
Here my attempt of a parser (almost) complying with the CSV specifications on this link CSV specs (I didn't need line breaks within fields. Also the spaces around the commas are dismissed).
After you overcome the shocking experience of waiting 10 seconds for compiling this code :), you can sit back and enjoy.
// csvparser.cpp #include <boost/spirit/include/qi.hpp> #include <boost/spirit/include/phoenix_operator.hpp> #include <iostream> #include <string> namespace qi = boost::spirit::qi; namespace bascii = boost::spirit::ascii; template <typename Iterator> struct csv_parser : qi::grammar<Iterator, std::vector<std::string>(), bascii::space_type> { qi::rule<Iterator, char() > COMMA; qi::rule<Iterator, char() > DDQUOTE; qi::rule<Iterator, std::string(), bascii::space_type > non_escaped; qi::rule<Iterator, std::string(), bascii::space_type > escaped; qi::rule<Iterator, std::string(), bascii::space_type > field; qi::rule<Iterator, std::vector<std::string>(), bascii::space_type > start; csv_parser() : csv_parser::base_type(start) { using namespace qi; using qi::lit; using qi::lexeme; using bascii::char_; start = field % ','; field = escaped | non_escaped; escaped = lexeme['"' >> *( char_ -(char_('"') | ',') | COMMA | DDQUOTE) >> '"']; non_escaped = lexeme[ *( char_ -(char_('"') | ',') ) ]; DDQUOTE = lit("\"\"") [_val = '"']; COMMA = lit(",") [_val = ',']; } }; int main() { std::cout << "Enter CSV lines [empty] to quit\n"; using bascii::space; typedef std::string::const_iterator iterator_type; typedef csv_parser<iterator_type> csv_parser; csv_parser grammar; std::string str; int fid; while (getline(std::cin, str)) { fid = 0; if (str.empty()) break; std::vector<std::string> csv; std::string::const_iterator it_beg = str.begin(); std::string::const_iterator it_end = str.end(); bool r = phrase_parse(it_beg, it_end, grammar, space, csv); if (r && it_beg == it_end) { std::cout << "Parsing succeeded\n"; for (auto& field: csv) { std::cout << "field " << ++fid << ": " << field << std::endl; } } else { std::cout << "Parsing failed\n"; } } return 0; }
Compile:
make csvparser
Test (example stolen from Wikipedia ):
./csvparser Enter CSV lines [empty] to quit 1999,Chevy,"Venture ""Extended Edition, Very Large""",,5000.00 Parsing succeeded field 1: 1999 field 2: Chevy field 3: Venture "Extended Edition, Very Large" field 4: field 5: 5000.00 1999,Chevy,"Venture ""Extended Edition, Very Large""",,5000.00" Parsing failed