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


264

Necesito cargar y usar los datos del archivo CSV en C ++. En este punto, realmente puede ser un analizador delimitado por comas (es decir, no se preocupe 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 Boost's Spirit, pero estoy dispuesto a probarlo. Pero solo si no hay una solución más sencilla que estoy pasando por alto.


11
He mirado boost::spiritpara analizar. Es más para analizar gramáticas gracias a analizar un formato de archivo simple. Alguien en mi equipo estaba tratando de usarlo para analizar XML y fue muy difícil de depurar. Manténgase alejado de boost::spiritsi es posible.
chrish

50
Lo siento, pero ese es un consejo terrible. Spirit no siempre es una solución adecuada, pero la he usado, y sigo usándola, con éxito en varios proyectos. En comparación con herramientas similares (Antlr, Lex / yacc, etc.) tiene ventajas significativas. Ahora, para analizar el CSV, probablemente sea exagerado ...
MattyT

44
@MattyT IMHO spirites bastante difícil de usar para una biblioteca de combinador de analizadores. Habiendo tenido una experiencia (muy agradable) con las (atto)parsecbibliotecas de Haskells , esperaba que (espíritu) funcionara de manera similar, pero me di por vencido después de luchar con 600 errores de compilación de línea.
fho

Respuestas:


296

Si no le importa escapar de la coma y la nueva línea,
Y no puede insertar comas y nuevas
líneas entre comillas (Si no puede escapar, entonces ...), entonces solo se trata de tres líneas de código (OK 14 -> Pero es 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 que representa una fila.
Luego transmita 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 podríamos crear técnicamente 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";
    }
}

20
primero () siguiente (). ¿Qué es este Java! Solo bromeo.
Martin York

44
@DarthVader: Una declaración general superpuesta que, por su amplitud, es una tontería. Si desea aclarar por qué es malo y luego por qué esta maldad se aplica en este contexto.
Martin York

12
@DarthVader: Creo que es una tontería hacer amplias generalizaciones. El código anterior funciona correctamente, por lo que puedo ver algo malo en él. Pero si tiene algún comentario específico sobre lo anterior, definitivamente lo consideraré en este contexto. Pero puedo ver cómo puede llegar a esa conclusión siguiendo sin pensar un conjunto de reglas generalizadas para C # y aplicándolo a otro idioma.
Martin York

55
Además, si se encuentra con problemas de enlace extraños con el código anterior porque otra biblioteca en algún lugar define istream::operator>>(como Eigen), agregue un inlineantes de la declaración del operador para solucionarlo.
sebastian_k

3
Este es el ejemplo más simple y limpio de cómo hacer una clase de iterador que haya visto.
Giancarlo Sportelli

46

Solución usando 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);
}

9
El tokenizer boost no es totalmente compatible con el estándar CSV completo, pero hay algunas soluciones rápidas. Ver stackoverflow.com/questions/1120140/csv-parser-in-c/…
Rolf Kristensen el

3
¿Tiene que tener toda la biblioteca de impulso en su máquina, o puede usar un subconjunto de su código para hacer esto? 256mb parece mucho para el análisis CSV ..
NPike

66
@NPike: puede usar la utilidad bcp que viene con boost para extraer solo los encabezados que realmente necesita.
ildjarn

46

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 está escrito como una máquina de estado finito y consume un carácter a la vez. Creo que es más fácil 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;
}

66
gracias, creo que esta es la respuesta más completa, lástima que esté enterrada aquí.
mihai

Este vector anidado de cadenas es un no-go para los procesadores modernos. Desecha su capacidad de almacenamiento en caché
Nikolaos Giotis

además tienes todas esas declaraciones de cambio
Nikolaos Giotis

La respuesta principal no funcionó para mí, ya que estoy en un compilador anterior. Esta respuesta funcionó, la inicialización del vector puede requerir esto:const char *vinit[] = {""}; vector<string> fields(vinit, end(vinit));
dr_rk

31

La biblioteca de C ++ String Toolkit (StrTk) tiene una clase de cuadrícula de token que le permite cargar datos desde archivos de texto, cadenas o buffers de caracteres , y analizarlos / procesarlos en una fila-columna.

Puede especificar los delimitadores de fila y 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 aquí


1
Aunque strtk admite campos con comillas dobles , e incluso eliminando las comillas circundantes (via options.trim_dquotes = true), no admite la eliminación de comillas dobles (por ejemplo, el campo "She said ""oh no"", and left."como la cadena c "She said \"oh no\", and left."). Tendrás que hacerlo tú mismo.
rampion

1
Cuando lo use strtk, también tendrá que manejar manualmente los campos con comillas dobles que contienen caracteres de nueva línea.
rampion

29

Puede usar Boost Tokenizer con escaped_list_separator.

escaped_list_separator analiza un superconjunto del csv. Boost :: tokenizer

Esto solo usa los archivos de encabezado del tokenizer Boost, no se requieren enlaces para impulsar las bibliotecas.

Aquí hay un ejemplo, (vea Parse CSV File With Boost Tokenizer In C ++ para más 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;
    }
}

Y si desea poder analizar nuevas líneas incrustadas mybyteofcode.blogspot.com/2010/11/… .
stefanB

Si bien esta técnica funciona, he descubierto que tiene un rendimiento muy pobre. Analizar un archivo CSV de 90000 líneas con diez campos por línea toma alrededor de 8 segundos en mi Xeon de 2 GHz. El módulo csv de Python Standard Library analiza el mismo archivo en aproximadamente 0.3 segundos.
Rob Smallshire

@Rob eso es interesante: ¿qué hace el csv de Python de manera diferente?
tofutim

1
@RobSmallshire es un código de ejemplo simple, no uno de alto rendimiento. Este código hace copias de todos los campos por línea. Para un mayor rendimiento, usaría diferentes opciones y devolvería solo referencias a campos en el búfer en lugar de hacer copias.
stefanB

29

No es excesivo usar Spirit para analizar CSV. Spirit es muy 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 los nuevos documentos de Spirit 2.1 que se acaba de lanzar con Boost 1.41.

El tutorial progresa de simple a complejo. Los analizadores CSV se presentan en algún lugar en el medio y tocan varias técnicas para usar Spirit. El código generado es tan estricto como el código escrito a mano. ¡Mira el ensamblador generado!


18
En realidad, es excesivo, el tiempo de compilación es enorme y hace que el uso de Spirit para simples "tareas de micro-análisis" no sea razonable.
Gerdiner

13
También me gustaría señalar que el código anterior no analiza CSV, solo analiza un rango del tipo del vector delimitado por comas. No maneja citas, diferentes tipos de columnas, etc. En resumen, 19 votos por algo que sí responde a la pregunta me parece un poco sospechoso.
Gerdiner

9
@Gerdiner Tonterías. El tiempo de compilación para pequeños analizadores no es tan grande, pero también es irrelevante porque inserta el código en su propia unidad de compilación y lo compila una vez . Entonces solo necesita vincularlo y eso es lo más eficiente posible. Y en cuanto a su otro comentario, hay tantos dialectos de CSV como procesadores. Este ciertamente no es un dialecto muy útil, pero se puede extender trivialmente para manejar los valores citados.
Konrad Rudolph

11
@konrad: Simplemente incluir "#include <boost / spirit / include / qi.hpp>" en un archivo vacío con solo un main y nada más toma 9.7 segundos con MSVC 2012 en un corei7 que se ejecuta a 2.ghz. Es una hinchazón innecesaria. La respuesta aceptada se compila en menos de 2 segundos en la misma máquina, me gustaría imaginar cuánto tiempo tomaría compilar el ejemplo 'Boost.Spirit' adecuado.
Gerdiner

11
@Gerdiner Tengo que estar de acuerdo con usted en la sobrecarga en el uso del espíritu para algo tan simple como el procesamiento de CVS es demasiado grande.

18

Si HACES atención acerca de analizar CSV correctamente, esto lo hará ... con relativa lentitud, ya que funciona con un carácter 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);
    }

AFAICT esto no manejará las comillas incrustadas correctamente (por ejemplo, "Esta cadena tiene" "comillas incrustadas" "", "foo", 1))
Jeremy Friesner

14

Al usar el Boost Tokenizer escaped_list_separator para archivos CSV, uno debe tener en cuenta lo siguiente:

  1. Requiere un carácter de escape (barra diagonal inversa predeterminada - \)
  2. Requiere un carácter separador / separador (coma predeterminada -,)
  3. Requiere un carácter de comillas (comillas predeterminadas - ")

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

1997, Ford, E350, "Camión súper lujoso"

El formato CSV especificado por wiki establece que las comillas simples deben manejarse 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 para corregir el comportamiento predeterminado del impulso escaped_list_separator:

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

Esta solución alternativa tiene el efecto secundario de que los campos de datos vacíos que están representados por una comilla doble se transformarán en un token de comillas simples. Al iterar a través de los tokens, 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.


8

Es posible que desee ver mi proyecto CSVfix de FOSS ( enlace actualizado ), que es un editor de flujo 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 tener que escribir 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.


Parece genial ... ¿Qué pasa con el estado beta / producción?
neuro

El estado es "en desarrollo", como lo sugieren los números de versión. Realmente necesito más comentarios de los usuarios antes de pasar a la versión 1.0. Además, tengo un par de características más que quiero agregar, que tienen que ver con la producción de XML desde CSV.

Pega, y le dará una oportunidad próxima vez que tenga que hacer frente a esos archivos CSV estándar maravillosas ...
neuro

8

Como todas las preguntas de CSV parecen ser redirigidas aquí, pensé en publicar mi respuesta aquí. Esta respuesta no aborda directamente 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 los tipos de cada campo ya se conocían. Por supuesto, el siguiente método podría usarse para tratar cada campo como un tipo de cadena.

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

Pruébalo en línea!


6

Escribí un analizador CSV C ++ 11 de solo encabezado . Está bien probado, es rápido, admite toda la especificación CSV (campos entre comillas, delimitador / terminador en comillas, escape de comillas, etc.) y es configurable para dar cuenta de los CSV que no se adhieren a 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 el 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;
  }
}

1
Buen trabajo, pero necesita agregar tres cosas más: (1) leer el encabezado (2) proporcionar indexación de campos por nombre (3) no reasignar memoria en bucle reutilizando el mismo vector de cadenas
Maksym Ganenko

@MaksymGanenko hago # 3. ¿Podría dar más detalles sobre el n. ° 2?
m0meni

1
Es muy útil obtener campos no por posición en una fila, sino por nombre dado en el encabezado (en la primera fila de la tabla CSV). Por ejemplo, espero una tabla CSV con el campo "Fecha", pero no sé qué es el índice de campo "Fecha" en una fila.
Maksym Ganenko

1
@MaksymGanenko ah, entiendo lo que quieres decir. Hay github.com/ben-strasser/fast-cpp-csv-parser para cuando conozca las columnas de su CSV en el momento de la compilación, y probablemente sea mejor que el mío. Lo que quería era un analizador CSV para los casos en los que deseaba usar el mismo código para muchos CSV diferentes y no sabía cómo se veían con anticipación. Así que probablemente no agregaré el # 2, pero agregaré el # 1 en algún momento en el futuro.
m0meni

5

Aquí se puede encontrar otra biblioteca de E / S CSV:

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

2
Agradable, pero te obliga a elegir el número de columnas en tiempo de compilación. No es muy útil para muchas aplicaciones.
quant_dev

5

Otra solución similar a la respuesta de Loki Astari , en C ++ 11. Las filas aquí son std::tuples 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;
}

Avances:

  • bastante limpio y fácil de usar, solo C ++ 11.
  • conversión automática de tipo en std::tuple<t1, ...>via operator>>.

Lo que falta:

  • escapando y citando
  • sin manejo de errores en caso de CSV malformado.

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.


1
Es posible que no le interese la inserción en línea, porque la mayoría de los compiladores lo deciden por su cuenta. Al menos estoy seguro en Visual C ++. Puede alinear el método independientemente de la especificación de su método.
MrPisarik

1
Es precisamente por eso que los marqué explícitamente. Gcc y Clang, los que más uso, también tienen sus propias convenciones. Una palabra clave "en línea" debería ser solo un incentivo.
Spak

4

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

Nota: Este analizador tiene como objetivo replicar el comportamiento de Excel lo más cerca posible, específicamente al importar archivos CSV rotos o malformados .

Esta es la pregunta original: analizar el archivo CSV con campos multilínea 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;
}

3

Necesitaba una biblioteca C ++ fácil de usar para analizar archivos CSV, pero no pude encontrar ninguna disponible, así que terminé compilando una. Rapidcsv es una biblioteca de solo encabezado C ++ 11 que brinda acceso directo a columnas analizadas (o filas) como vectores, en el tipo de datos que elija. 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;
}

1
Buen trabajo, pero la biblioteca no funciona correctamente si el encabezado tiene etiquetas vacías. Eso es típico para la tabla Excel / LibreOffice NxN. Además, puede omitir la última línea de datos. Desafortunadamente, su lib no es robusta.
Maksym Ganenko

1
Gracias por los comentarios @MaksymGanenko He arreglado el error de "última línea de datos" para las líneas finales sin salto de línea final. En cuanto al otro tema mencionado: "encabezados con etiquetas vacías", ¿no estoy seguro de a qué se refiere? La biblioteca debe manejar etiquetas vacías (tanto entre comillas como sin comillas). También puede leer CSV sin fila / columna de encabezado, pero luego requiere que el usuario especifique esto (col title id -1 y row title id -1). Proporcione algunos detalles más o informe un error en la página de GitHub si tiene algún caso de uso específico que le gustaría ver admitido. ¡Gracias!
d99kris

2

Disculpe, pero todo esto parece una gran sintaxis elaborada 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;
}

Erm, ¿por qué habría ",\n"en la cadena?
Timmmm

@Timmmm busque el método substr de la clase String, y verá que toma varios caracteres, \ n es el carácter de nueva línea, por lo que se cuenta como un solo carácter, en este caso. No busca el valor completo como un todo. Está buscando cada personaje individual; a saber, coma o nueva línea. substr devolverá la posición del primer carácter que encuentre, y -1 si no encuentra ninguno, lo que significa que ha terminado de leer la línea. fp realiza un seguimiento interno de la posición en el archivo, por lo que cada llamada a readCSV lo mueve una fila a la vez.
Martyn Shutt

2

Aquí hay un 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;
    }
}

2

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

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

[^,], ^ -it invierte la lógica, significa hacer coincidir cualquier cadena que no contenga coma y luego la última, dice que coincide con la coma que terminó la cadena anterior.


2

Lo primero que debe hacer es asegurarse de que el archivo existe. Para lograr esto, solo necesita intentar abrir la secuencia 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 correcto de archivo. Para lograr esto, debe mirar a través de la ruta del archivo proporcionada hasta que encuentre 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 realmente llamará a las verificaciones de error creadas anteriormente y luego analizará el 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;
    }
}

2

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

Aquí mi intento de un analizador (casi) que cumple con las especificaciones CSV en este enlace Especificaciones CSV (no necesitaba saltos de línea dentro de los campos. También se descartan 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

2

Esta solución detecta estos 4 casos.

la clase completa está a las

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 archivo carácter por carácter y lee 1 fila a la vez en un vector (de cadenas), por lo tanto, 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;
}

1

También puede 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()devolver QStringList, la lista de cadenas obtenidas dividiendo 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 problema qstring


esto no manejará comas entre comillas
Ezee

1

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

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

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

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


1
¿No debería el compilador quitar todo lo que no es esencial?
tofutim

1

Este es un hilo antiguo pero todavía está en la parte superior de los resultados de búsqueda, por lo que estoy agregando mi solución usando std :: stringstream y un método simple de reemplazo de cadena 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 utilizo stringreplace para convertir las comas en espacios primero. Maneja las pestañas bien, pero no trata con cadenas citadas.

La entrada incorrecta o faltante simplemente se ignora, lo que puede o no ser bueno, dependiendo de 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://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));
      }
   }
}

1

Por lo que vale, aquí está mi implementación. Se ocupa de la entrada de wstring, pero podría ajustarse a la cadena fácilmente. No maneja la nueva línea en los campos (como mi aplicación tampoco lo hace, 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();
}

1

Aquí hay una función lista para usar si todo lo que necesita es cargar un archivo de datos de dobles (sin números 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;
}

1

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)

1

Escribí una buena manera 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;
}

1

Es posible de usar 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 usar:

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

entonces puede combinar con esto, que en realidad es personalizable según 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());
}

1

Como no estoy acostumbrado a impulsar 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 separadas 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;}
        }
    }


}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.