una txt texto separados por pasar matriz leer escribir datos como comas archivos archivo abrir c++ parsing text csv

txt - leer archivos separados por comas c++



¿Cómo puedo leer y analizar archivos CSV en C++? (30)

Aquí hay otra implementación de un analizador CSV Unicode (funciona con wchar_t). Escribí una parte, mientras que Jonathan Leffler escribió el resto.

Nota: este analizador está orientado a replicar el comportamiento de Excel lo más cerca posible, específicamente al importar archivos CSV rotos o con formato incorrecto .

Esta es la pregunta original: archivo CSV de análisis con campos de varias líneas y comillas dobles escapadas

Este es el código como un SSCCE (Ejemplo Corto, Autocontenido, Correcto).

#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, i.e. 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; }

Necesito cargar y usar datos de archivos CSV en C ++. En este punto, realmente puede ser solo un analizador delimitado por comas (es decir, no te preocupes por escapar de nuevas líneas y comas). La necesidad principal es un analizador línea por línea que devolverá un vector para la siguiente línea cada vez que se llame al método.

Encontré este artículo que parece bastante prometedor: http://www.boost.org/doc/libs/1_35_0/libs/spirit/example/fundamental/list_parser.cpp

Nunca he usado el Espíritu de Boost, pero estoy dispuesto a probarlo. Pero solo si no hay una solución más directa que estoy pasando por alto.


Como todas las preguntas de CSV parecen ser redirigidas aquí, pensé que publicaría mi respuesta aquí. Esta respuesta no responde directamente a la pregunta del autor de la pregunta. Quería poder leer en una secuencia que se sabe que está en formato CSV, y también se conocían los tipos de cada campo. Por supuesto, el método a continuación podría usarse para tratar cada campo como un tipo de cadena.

Como ejemplo de cómo quería poder usar un flujo de entrada CSV, considere la siguiente entrada (tomada de la página de wikipedia en 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" ;

Entonces, quería poder leer en los datos de esta manera:

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... }

Esta fue la solución con la que terminé.

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(); } };

Con los siguientes ayudantes que pueden simplificarse con las nuevas plantillas de rasgos integrales en 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) }; };


Cuando se utiliza el escape_list_separator de Boost Tokenizer para archivos CSV, uno debe tener en cuenta lo siguiente:

  1. Requiere un carácter de escape (barra diagonal inversa predeterminada - /)
  2. Requiere un divisor / separador de caracteres (coma por defecto -,)
  3. Requiere un carácter de cita (cita por defecto - ")

El formato CSV especificado por wiki establece que los campos de datos pueden contener separadores entre comillas (compatibles):

1997, Ford, E350, "Super, camión de lujo"

El formato CSV especificado por wiki establece que las comillas simples se deben manejar con comillas dobles (escaped_list_separator eliminará todos los caracteres de comillas):

1997, Ford, E350, "Super" "lujoso" "camión"

El formato CSV no especifica que se eliminen los caracteres de barra diagonal inversa (escaped_list_separator eliminará todos los caracteres de escape).

Una posible solución alternativa para corregir el comportamiento predeterminado del impulso escaped_list_separator:

  1. Primero reemplace todos los caracteres de barra diagonal invertida (/) con dos caracteres de barra diagonal invertida (//) para que no se eliminen.
  2. En segundo lugar, reemplace todas las comillas dobles ("") con un solo carácter de barra diagonal invertida y una comilla (/ ")

Esta solución temporal tiene el efecto secundario de que los campos de datos vacíos que se representan con una comilla doble se transformarán en un token de comilla simple. Al iterar a través de los tokens, entonces uno debe verificar si el token es una comilla simple, y tratarlo como una cadena vacía.

No es bonito pero funciona, siempre que no haya nuevas líneas dentro de las comillas.


Es posible que desee ver mi proyecto FOSS CSVfix ( enlace actualizado ), que es un editor de secuencias CSV escrito en C ++. El analizador CSV no es un premio, pero hace el trabajo y todo el paquete puede hacer lo que necesita sin que usted escriba ningún código.

Consulte alib/src/a_csv.cpp para el analizador CSV y csvlib/src/csved_ioman.cpp ( IOManager::ReadCSV ) para ver un ejemplo de uso.


La biblioteca del juego de herramientas de cadenas de C ++ (StrTk) tiene una clase de cuadrícula de token que le permite cargar datos desde archivos de texto, cadenas o búferes de caracteres , y analizarlos / procesarlos de forma fila-columna.

Puede especificar los delimitadores de fila y los delimitadores de columna o simplemente usar los valores predeterminados.

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; }

Más ejemplos se pueden encontrar Here


Lo primero que debe hacer es asegurarse de que el archivo existe. Para lograr esto, solo necesita intentar abrir el flujo de archivos en la ruta. Después de abrir la secuencia de archivos, use stream.fail () para ver si funcionó como se esperaba o no.

bool fileExists(string fileName) { ifstream test; test.open(fileName.c_str()); if (test.fail()) { test.close(); return false; } else { test.close(); return true; } }

También debe verificar que el archivo proporcionado sea el tipo de archivo correcto. Para lograr esto, debe revisar la ruta del archivo proporcionada hasta encontrar la extensión del archivo. Una vez que tenga la extensión de archivo, asegúrese de que sea un archivo .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; }

Esta función devolverá la extensión de archivo que se usa más adelante en un mensaje de error.

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; }

Esta función llamará realmente a las verificaciones de errores creadas anteriormente y luego se analizará a través del archivo.

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; } }


Mi versión no usa nada más que la biblioteca estándar de C ++ 11. Se adapta bien a la cita de Excel CSV:

spam eggs,"foo,bar","""fizz buzz""" 1.23,4.567,-8.00E+09

El código se escribe como una máquina de estado finito y consume un carácter a la vez. Creo que es más fácil de razonar.

#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; }


No es excesivo utilizar Spirit para analizar CSV. El espíritu es adecuado para tareas de micro-análisis. Por ejemplo, con Spirit 2.1, es tan fácil como:

bool r = phrase_parse(first, last, // Begin grammar ( double_ % '','' ) , // End grammar space, v);

El vector, v, se rellena con los valores. Hay una serie de tutoriales sobre esto en la nueva documentación de Spirit 2.1 que se acaba de lanzar con Boost 1.41.

El tutorial avanza de lo simple a lo complejo. Los analizadores CSV se presentan en algún lugar en el medio y abordan diversas técnicas en el uso de Spirit. El código generado es tan estricto como el código escrito a mano. Echa un vistazo al ensamblador generado!


Otra biblioteca de E / S CSV se puede encontrar aquí:

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 } }


Otra solución similar a la respuesta de Loki Astari , en C ++ 11. Las filas aquí son std::tuple s de un tipo dado. El código escanea una línea, luego escanea hasta cada delimitador y luego convierte y descarga el valor directamente en la tupla (con un poco de código de plantilla).

for (auto row : csv<std::string, int, float>(file, '','')) { std::cout << "first col: " << std::get<0>(row) << std::endl; }

Advanges:

  • Bastante limpio y fácil de usar, solo C ++ 11.
  • Conversión automática de tipos en std::tuple<t1, ...> través del operator>> .

Lo que falta

  • escapando y citando
  • no hay manejo de errores en caso de CSV mal formado.

El código principal:

#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(); }

Puse un pequeño ejemplo de trabajo en GitHub ; Lo he estado usando para analizar algunos datos numéricos y cumplió su propósito.


Si te preocupa analizar CSV correctamente, esto lo hará ... relativamente lento, ya que funciona de una vez a la vez.

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); }


Si no te importa escapar de coma y nueva línea,
Y no puede insertar comas y líneas nuevas entre comillas (si no puede escapar, entonces ...)
entonces solo son unas tres líneas de código (OK 14 -> Pero solo 15 para leer el archivo completo).

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; }

Simplemente crearía una clase representando una fila.
Luego transmítelo a ese objeto:

#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"; } }

Pero con un poco de trabajo, técnicamente podríamos crear un iterador:

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"; } }


Solución utilizando Boost Tokenizer:

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); }


Escribí un analizador C ++ 11 CSV solo para encabezado . Está bien probado, es rápido, admite todas las especificaciones de CSV (campos entre comillas, delimitador / terminador entre comillas, comillas de escape, etc.) y se puede configurar para tener en cuenta los CSV que no cumplen con la especificación.

La configuración se realiza a través de una interfaz fluida:

// 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

El análisis es solo un rango basado en bucle:

#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; } }


Puedes usar Boost Tokenizer con escaped_list_separator.

escaped_list_separator analiza un superconjunto de csv. Boost::tokenizer

Esto solo usa archivos de encabezado de tokenizer de Boost, no se requiere vinculación para aumentar las bibliotecas.

Aquí hay un ejemplo, (vea Parse CSV File With Boost Tokenizer en C ++ para detalles o 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; } }


Aquí está el código para leer una matriz, tenga en cuenta que también tiene una función csvwrite en 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; } }


Aquí hay una función lista para usar si todo lo que necesita es cargar un archivo de datos de dobles (sin enteros, sin texto).

#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 (e.g. 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; }


Disculpe, pero todo esto parece una gran sintaxis compleja para ocultar algunas líneas de código.

¿Por qué no esto?

/** 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,q-p-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; }


Escribí una buena forma de analizar archivos CSV y pensé que debería agregarlo como respuesta:

#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; }


Esta solución detecta estos 4 casos.

clase completa es a

https://github.com/pedro-vicente/csv-parser

1,field 2,field 3, 1,field 2,"field 3 quoted, with separator", 1,field 2,"field 3 with newline", 1,field 2,"field 3 with newline and separator,",

Lee el carácter del archivo por carácter y lee 1 fila a la vez en un vector (de cadenas), por lo que es adecuado para archivos muy grandes.

El uso es

Iterar hasta que se devuelva una fila vacía (final del archivo). Una fila es un vector donde cada entrada es una columna CSV.

read_csv_t csv; csv.open("../test.csv"); std::vector<std::string> row; while (true) { row = csv.read_row(); if (row.size() == 0) { break; } }

la declaración de clase

class read_csv_t { public: read_csv_t(); int open(const std::string &file_name); std::vector<std::string> read_row(); private: std::ifstream m_ifs; };

la implementación

std::vector<std::string> read_csv_t::read_row() { bool quote_mode = false; std::vector<std::string> row; std::string column; char c; while (m_ifs.get(c)) { switch (c) { ///////////////////////////////////////////////////////////////////////////////////////////////////// //separator '','' detected. //in quote mode add character to column //push column if not in quote mode ///////////////////////////////////////////////////////////////////////////////////////////////////// case '','': if (quote_mode == true) { column += c; } else { row.push_back(column); column.clear(); } break; ///////////////////////////////////////////////////////////////////////////////////////////////////// //quote ''"'' detected. //toggle quote mode ///////////////////////////////////////////////////////////////////////////////////////////////////// case ''"'': quote_mode = !quote_mode; break; ///////////////////////////////////////////////////////////////////////////////////////////////////// //line end detected //in quote mode add character to column //return row if not in quote mode ///////////////////////////////////////////////////////////////////////////////////////////////////// case ''/n'': case ''/r'': if (quote_mode == true) { column += c; } else { return row; } break; ///////////////////////////////////////////////////////////////////////////////////////////////////// //default, add character to column ///////////////////////////////////////////////////////////////////////////////////////////////////// default: column += c; break; } } //return empty vector if end of file detected m_ifs.close(); std::vector<std::string> v; return v; }


Este es un hilo antiguo, pero todavía está en la parte superior de los resultados de búsqueda, así que estoy agregando mi solución usando std :: stringstream y un método simple de reemplazo de cadenas por Yves Baumes que encontré aquí.

El siguiente ejemplo leerá un archivo línea por línea, ignorará las líneas de comentarios que comienzan con // y analizará las otras líneas en una combinación de cadenas, ints y dobles. Stringstream realiza el análisis, pero espera que los campos estén delimitados por espacios en blanco, por lo que uso stringreplace para convertir primero las comas en espacios. Maneja las pestañas bien, pero no se ocupa de las cadenas entre comillas.

La información incorrecta o faltante simplemente se ignora, lo que puede ser bueno o no, según su circunstancia.

#include <string> #include <sstream> #include <fstream> void StringReplace(std::string& str, const std::string& oldStr, const std::string& newStr) // code by Yves Baumes // http://.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)); } } }


Puede abrir y leer el archivo .csv usando fopen, fscanf funciones, pero lo importante es analizar los datos. La forma más simple de analizar los datos mediante delimitador. En el caso de .csv, el delimitador es '',''.

Supongamos que su archivo data1.csv es el siguiente:

A,45,76,01 B,77,67,02 C,63,76,03 D,65,44,04

puede tokenizar datos y almacenarlos en una matriz de caracteres y luego usar la función atoi () etc. para realizar las conversiones apropiadas

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);

[^,], ^ -sinvierte la lógica, significa que coincide con cualquier cadena que no contenga una coma y luego la última, dice que coincida con una coma que terminó con una cadena anterior.


Si no desea lidiar con la inclusión de impulso en su proyecto (es considerablemente grande si todo lo que va a utilizar es el análisis de CSV ...)

He tenido suerte con el análisis de CSV aquí:

http://www.zedwood.com/article/112/cpp-csv-parser

Maneja los campos entre comillas, pero no maneja los caracteres en línea (lo cual probablemente sea correcto para la mayoría de los usos).


Tienes que sentirte orgulloso cuando usas algo tan hermoso como boost::spirit

Aquí mi intento de un analizador (casi) de cumplir con las especificaciones de CSV en este enlace. Especificaciones de CSV (no necesitaba saltos de línea dentro de los campos. También se eliminan los espacios alrededor de las comas).

Después de superar la impactante experiencia de esperar 10 segundos para compilar este código :), puede sentarse y disfrutar.

// 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; }

Compilar:

make csvparser

Prueba (ejemplo robado de 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


Es posible utilizar std::regex.

Dependiendo del tamaño de su archivo y la memoria disponible para usted, es posible leerlo línea por línea o completamente en un archivo std::string.

Para leer el archivo se puede utilizar:

std::ifstream t("file.txt"); std::string sin((std::istreambuf_iterator<char>(t)), std::istreambuf_iterator<char>());

entonces puede coincidir con esto, que es realmente adaptable a sus necesidades.

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()); }


Necesitaba una biblioteca de C ++ fácil de usar para analizar archivos CSV, pero no pude encontrar ninguna disponible, así que terminé creando una. Rapidcsv es una biblioteca de solo encabezado de C ++ 11 que proporciona acceso directo a las columnas (o filas) analizadas como vectores, en el tipo de datos elegido. Por ejemplo:

#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; }


Otra forma rápida y fácil es usar 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''; }

Salidas:

(abc 123 true 3.14) (def 456 false 2.718)


Por lo que vale, aquí está mi implementación. Se trata de la entrada wstring, pero podría ajustarse a la cadena fácilmente. No maneja la nueva línea en los campos (como tampoco lo hace mi aplicación, pero agregar su soporte no es demasiado difícil) y no cumple con el final de línea "/ r / n" según RFC (suponiendo que use std :: getline), pero maneja el recorte de espacios en blanco y las comillas dobles correctamente (con suerte).

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(); }


También puedes echar un vistazo a las capacidades de la Qtbiblioteca.

Tiene soporte para expresiones regulares y la clase QString tiene buenos métodos, por ejemplo, split()devolviendo QStringList, lista de cadenas obtenidas al dividir la cadena original con un delimitador proporcionado. Debería ser suficiente para el archivo csv ...

Para obtener una columna con un nombre de encabezado dado, uso lo siguiente: c ++ herencia Qt problem qstring


Ya que no estoy acostumbrado a mejorar en este momento, sugeriré una solución más simple. Supongamos que su archivo .csv tiene 100 líneas con 10 números en cada línea separados por un '',''. Puede cargar estos datos en forma de matriz con el siguiente código:

#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;} } } }