Come posso leggere e analizzare i file CSV in C ++?


264

Devo caricare e utilizzare i dati del file CSV in C ++. A questo punto può davvero essere solo un parser delimitato da virgole (ovvero non preoccuparti di sfuggire a nuove righe e virgole). L'esigenza principale è un parser riga per riga che restituirà un vettore per la riga successiva ogni volta che viene chiamato il metodo.

Ho trovato questo articolo che sembra abbastanza promettente: http://www.boost.org/doc/libs/1_35_0/libs/spirit/example/fundamental/list_parser.cpp

Non ho mai usato lo spirito di Boost, ma sono disposto a provarlo. Ma solo se non c'è una soluzione più semplice sto trascurando.


11
Ho esaminato boost::spiritper l'analisi. È più per l'analisi delle grammatiche grazie all'analisi di un semplice formato di file. Qualcuno del mio team stava cercando di usarlo per analizzare l'XML ed è stato doloroso eseguire il debug. Stai lontano da boost::spiritse possibile.
chrish,

50
Mi dispiace chrish, ma è un consiglio terribile. Spirit non è sempre una soluzione appropriata, ma l'ho usata - e continuo a usarla - con successo in numerosi progetti. Rispetto a strumenti simili (Antlr, Lex / yacc ecc.) Presenta vantaggi significativi. Ora, per analizzare CSV è probabilmente eccessivo ...
MattyT

4
@MattyT IMHO spiritè piuttosto difficile da usare per una libreria combinatore parser. Avendo avuto una (molto piacevole) esperienza con le (atto)parseclibrerie di Haskells, mi aspettavo che (spirito) funzionasse in modo simile, ma ho rinunciato dopo aver combattuto con errori del compilatore a 600 righe.
dal

Risposte:


296

Se non ti interessa sfuggire a virgola e newline,
E non puoi incorporare virgola e newline tra virgolette (se non puoi scappare allora ...)
allora sono solo circa tre righe di codice (OK 14 -> Ma è solo 15 per leggere l'intero file).

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

Vorrei solo creare una classe che rappresenta una riga.
Quindi stream in quell'oggetto:

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

Ma con un po 'di lavoro potremmo tecnicamente creare un iteratore:

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
first () next (). Cos'è questo Java! Solo scherzando.
Martin York,

4
@DarthVader: un'affermazione generale sovrapposta che per sua ampiezza è sciocca. Se vuoi chiarire perché è cattivo e quindi perché questa cattiveria si applica in questo contesto.
Martin York,

12
@DarthVader: penso che sia sciocco fare generalizzazioni generali. Il codice sopra funziona correttamente, quindi posso davvero vedere qualcosa di sbagliato in esso. Ma se hai qualche commento specifico su quanto sopra, prenderò sicuramente in considerazione in questo contesto. Ma posso vedere come puoi arrivare a questa conclusione seguendo insensatamente una serie di regole generalizzate per C # e applicandolo in un'altra lingua.
Martin York,

5
inoltre, se si verificano strani problemi di collegamento con il codice sopra perché un'altra libreria definisce da qualche parte istream::operator>>(come Eigen), aggiungere una inlineprima della dichiarazione dell'operatore per risolverlo.
sebastian_k,

3
Questo è l'esempio più semplice e pulito di come creare una classe iteratrice che abbia mai visto.
Giancarlo Sportelli,

46

Soluzione con 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
Il tokenizer boost non supporta completamente lo standard CSV completo, ma ci sono alcune soluzioni rapide. Vedi stackoverflow.com/questions/1120140/csv-parser-in-c/…
Rolf Kristensen

3
Devi avere l'intera libreria boost sul tuo computer o puoi semplicemente usare un sottoinsieme del loro codice per farlo?
256 MB

6
@NPike: è possibile utilizzare l' utilità bcp fornita con boost per estrarre solo le intestazioni effettivamente necessarie.
ildjarn,

46

La mia versione non utilizza altro che la libreria C ++ 11 standard. Gestisce bene la citazione CSV di Excel:

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

Il codice è scritto come una macchina a stati finiti e sta consumando un carattere alla volta. Penso che sia più facile ragionare.

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

6
grazie, penso che questa sia la risposta più completa, peccato che sia sepolta qui.
Mihai,

questo vettore nidificato di stringhe non può essere utilizzato dai moderni processori. Butta via la loro abilità di memorizzazione nella cache
Nikolaos Giotis,

in più hai tutte quelle dichiarazioni sugli interruttori
Nikolaos Giotis,

La risposta migliore non ha funzionato per me, dato che sono su un compilatore più vecchio. Questa risposta ha funzionato, l'inizializzazione vettoriale potrebbe richiedere questo:const char *vinit[] = {""}; vector<string> fields(vinit, end(vinit));
dr_rk

31

La libreria C ++ String Toolkit (StrTk) ha una classe di griglia token che consente di caricare dati da file di testo, stringhe o buffer di caratteri e di analizzarli / elaborarli in modo colonna-riga.

È possibile specificare i delimitatori di riga e i delimitatori di colonna o semplicemente utilizzare i valori predefiniti.

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

Altri esempi possono essere trovati qui


1
Sebbene strtk supporti i campi con virgolette doppie e anche la rimozione delle virgolette circostanti (via options.trim_dquotes = true), non supporta la rimozione di doppie virgolette doppie (ad esempio il campo "She said ""oh no"", and left."come stringa di tipo c "She said \"oh no\", and left."). Dovrai farlo da solo.
rampion

1
Durante l'utilizzo strtk, dovrai anche gestire manualmente i campi tra virgolette che contengono caratteri di nuova riga.
rampion

29

Puoi usare Boost Tokenizer con escaped_list_separator.

escaped_list_separator analizza un superset del csv. Boost :: tokenizer

Questo utilizza solo file di intestazione tokenizer Boost, nessun collegamento per aumentare le librerie richieste.

Ecco un esempio ( per ulteriori dettagli , vedere File CSV di analisi con token di boost in C ++ 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;
    }
}

E se vuoi essere in grado di analizzare nuove righe incorporate mybyteofcode.blogspot.com/2010/11/… .
stefanB,

Mentre questa tecnica funziona, ho trovato prestazioni molto scadenti. L'analisi di un file CSV a 90000 linee con dieci campi per riga richiede circa 8 secondi sul mio Xeon da 2 GHz. Il modulo csv della libreria standard Python analizza lo stesso file in circa 0,3 secondi.
Rob Smallshire,

@Rob è interessante: cosa fa diversamente il csv Python?
Tofutim,

1
@RobSmallshire è un semplice codice di esempio non ad alte prestazioni. Questo codice crea copie di tutti i campi per riga. Per prestazioni più elevate, utilizzare diverse opzioni e restituire solo i riferimenti ai campi nel buffer anziché effettuare copie.
stefanB,

29

Non è eccessivo usare Spirit per l'analisi dei CSV. Lo spirito è adatto per le attività di microanalisi. Ad esempio, con Spirit 2.1, è facile come:

bool r = phrase_parse(first, last,

    //  Begin grammar
    (
        double_ % ','
    )
    ,
    //  End grammar

    space, v);

Il vettore, v, viene riempito con i valori. C'è una serie di tutorial che toccano questo nei nuovi documenti di Spirit 2.1 che sono stati appena rilasciati con Boost 1.41.

Il tutorial passa da semplice a complesso. I parser CSV sono presentati da qualche parte nel mezzo e toccano varie tecniche nell'uso dello Spirito. Il codice generato è stretto come il codice scritto a mano. Guarda l'assemblatore generato!


18
In realtà è eccessivo, il tempo di compilazione è enorme e rende irragionevole l'utilizzo di Spirit per semplici "compiti di microanalisi".
Gerdiner,

13
Inoltre, vorrei sottolineare che il codice sopra non analizza CSV, ma analizza solo un intervallo del tipo di vettore delimitato da virgole. Non gestisce le virgolette, i vari tipi di colonne, ecc. In breve 19 voti per qualcosa che non risponde affatto alla domanda mi sembrano un po 'sospetti.
Gerdiner,

9
@Gerdiner Nonsense. Il tempo di compilazione ottenuto per i piccoli parser non è così grande, ma è anche irrilevante perché infili il codice nella sua unità di compilazione e lo compili una volta . Quindi devi solo collegarlo ed è il più efficiente possibile. E per quanto riguarda l'altro tuo commento, ci sono tanti dialetti di CSV quanti sono i processori. Questo certamente non è un dialetto molto utile, ma può essere banalmente esteso per gestire i valori tra virgolette.
Konrad Rudolph,

11
@konrad: Includere semplicemente "#include <boost / spirit / include / qi.hpp>" in un file vuoto con solo un main e nient'altro richiede 9.7sec con MSVC 2012 su un corei7 in esecuzione a 2.ghz. È inutile gonfiarsi. La risposta accettata si compila in meno di 2 secondi sulla stessa macchina, odio immaginare quanto tempo sarebbe il Boost "corretto". L'esempio dello spirito richiederebbe la compilazione.
Gerdiner,

11
@Gerdiner Devo concordare con te il sovraccarico nell'uso dello spirito per qualcosa di così semplice come l'elaborazione CVS è troppo grande.

18

Se FARE cura di analisi correttamente CSV, questo lo farà ... in modo relativamente lento come funziona un carattere alla volta.

 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 questo non gestirà correttamente le virgolette incorporate (ad es. "Questa stringa ha" "virgolette incorporate" "", "pippo", 1))
Jeremy Friesner,

14

Quando si utilizza Escaped_list_separator Boost Tokenizer per i file CSV, è necessario tenere presente quanto segue:

  1. Richiede un carattere di escape (barra rovesciata predefinita - \)
  2. Richiede un carattere splitter / separatore (virgola predefinita -,)
  3. Richiede un carattere di citazione (citazione di default - ")

Il formato CSV specificato da wiki afferma che i campi di dati possono contenere separatori tra virgolette (supportati):

1997, Ford, E350, "Super, camion lussuoso"

Il formato CSV specificato da wiki afferma che le virgolette singole devono essere gestite con virgolette doppie (escaped_list_separator eliminerà tutti i caratteri delle virgolette):

1997, Ford, E350, "Super" "lussuoso" "camion"

Il formato CSV non specifica che qualsiasi carattere di barra rovesciata debba essere rimosso (escaped_list_separator eliminerà tutti i caratteri di escape).

Un possibile rimedio per correggere il comportamento predefinito del boost escaped_list_separator:

  1. Sostituisci innanzitutto tutti i caratteri di barra rovesciata (\) con due caratteri di barra rovesciata (\\) in modo che non vengano rimossi.
  2. In secondo luogo sostituire tutte le virgolette ("") con un singolo carattere barra rovesciata e una virgoletta (\ ")

Questa soluzione ha l'effetto collaterale che campi di dati vuoti che sono rappresentati da una virgoletta doppia, verranno trasformati in token a virgoletta singola. Quando si scorre attraverso i token, è necessario verificare se il token è una virgoletta singola e trattarlo come una stringa vuota.

Non carino ma funziona, purché non ci siano newline tra le virgolette.


8

Potresti voler guardare il mio progetto FOSS CSVfix ( link aggiornato ), che è un editor di flussi CSV scritto in C ++. Il parser CSV non è un premio, ma fa il lavoro e l'intero pacchetto può fare ciò di cui hai bisogno senza che tu scriva alcun codice.

Vedere alib / src / a_csv.cpp per il parser CSV e csvlib / src / csved_ioman.cpp ( IOManager::ReadCSV) per un esempio di utilizzo.


Sembra fantastico ... E lo stato beta / produzione?
neuro,

Lo stato è "in fase di sviluppo", come suggerito dai numeri di versione. Ho davvero bisogno di più feedback dagli utenti prima di passare alla versione 1.0. Inoltre ho un paio di funzionalità che voglio aggiungere, a che fare con la produzione XML da CSV.

Aggiungilo ai segnalibri e lo proverò la prossima volta che dovrò occuparmi di quei meravigliosi file CSV standard ...
Neuro,

8

Poiché tutte le domande CSV sembrano essere reindirizzate qui, ho pensato di pubblicare la mia risposta qui. Questa risposta non affronta direttamente la domanda del richiedente. Volevo poter leggere in un flusso che è noto per essere in formato CSV, e anche i tipi di ciascun campo erano già noti. Naturalmente, il metodo seguente potrebbe essere usato per trattare ogni campo come un tipo di stringa.

Come esempio di come volessi essere in grado di utilizzare un flusso di input CSV, considera il seguente input (tratto dalla pagina di Wikipedia su 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"
;

Quindi, volevo essere in grado di leggere i dati in questo modo:

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

Questa è stata la soluzione con cui sono finito.

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 i seguenti helper che possono essere semplificati dai nuovi modelli di tratti integrali in 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) };
};

Provalo online!


6

Ho scritto un parser CSV solo per C ++ 11 . È ben testato, veloce, supporta l'intera specifica CSV (campi tra virgolette, delimitatore / terminatore tra virgolette, escape di virgolette, ecc.) Ed è configurabile per tenere conto dei CSV che non aderiscono alla specifica.

La configurazione viene eseguita tramite un'interfaccia 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

L'analisi è solo un intervallo basato su loop:

#include <iostream>
#include "../parser.hpp"

using namespace aria::csv;

int main() {
  std::ifstream f("some_file.csv");
  CsvParser parser(f);

  for (auto& row : parser) {
    for (auto& field : row) {
      std::cout << field << " | ";
    }
    std::cout << std::endl;
  }
}

1
Bel lavoro, ma è necessario aggiungere altre tre cose: (1) leggere l'intestazione (2) fornire campi indicizzazione per nome (3) non riallocare la memoria in loop riutilizzando lo stesso vettore di stringhe
Maksym Ganenko

@MaksymGanenko I do # 3. Potresti approfondire il n. 2?
m0meni,

1
È molto utile ottenere campi non per posizione in una riga, ma per nome indicato nell'intestazione (nella prima riga della tabella CSV). Ad esempio, mi aspetto una tabella CSV con il campo "Data", ma non so quale sia l'indice del campo "Data" in una riga.
Maksym Ganenko,

1
@MaksymGanenko ah capisco cosa intendi. C'è github.com/ben-strasser/fast-cpp-csv-parser per quando conosci le colonne del tuo CSV al momento della compilazione, ed è probabilmente migliore della mia. Quello che volevo era un parser CSV per i casi in cui volevi usare lo stesso codice per molti CSV diversi e non sai come si presentano in anticipo. Quindi probabilmente non aggiungerò il n. 2, ma aggiungerò il n. 1 in futuro.
m0meni,

5

Un'altra libreria di I / O CSV è disponibile qui:

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
Bello, ma ti costringe a scegliere il numero di colonne al momento della compilazione. Non molto utile per molte applicazioni.
quant_dev,

5

Un'altra soluzione simile alla risposta di Loki Astari , in C ++ 11. Le righe qui sono std::tupledi un determinato tipo. Il codice esegue la scansione di una riga, quindi esegue la scansione fino a ciascun delimitatore, quindi converte e scarica il valore direttamente nella tupla (con un po 'di codice modello).

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

advanges:

  • abbastanza pulito e semplice da usare, solo C ++ 11.
  • conversione automatica del tipo in std::tuple<t1, ...>via operator>>.

Cosa manca:

  • fuga e citazione
  • nessuna gestione degli errori in caso di CSV non valido.

Il codice principale:

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

Ho messo un piccolo esempio funzionante su GitHub ; L'ho usato per analizzare alcuni dati numerici e ha funzionato allo scopo.


1
Potrebbe non interessarti di inline, perché la maggior parte dei compilatori lo decide da solo. Almeno sono sicuro in Visual C ++. Può incorporare il metodo indipendentemente dalle specifiche del metodo.
MrPisarik,

1
Questo è esattamente il motivo per cui li ho contrassegnati esplicitamente. Gcc e Clang, quelli che uso principalmente, hanno anche le loro convenzioni. Una parola chiave "inline" dovrebbe essere solo un incentivo.
Parlato il

4

Ecco un'altra implementazione di un parser CSV Unicode (funziona con wchar_t). Ne ho scritto una parte, mentre Jonathan Leffler ha scritto il resto.

Nota: questo parser ha lo scopo di replicare il comportamento di Excel il più vicino possibile, in particolare durante l'importazione di elementi rotti o non validi file CSV .

Questa è la domanda originale: analisi del file CSV con campi multilinea e doppie virgolette di escape

Questo è il codice come SSCCE (breve, autonomo, esempio corretto).

#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

Avevo bisogno di una libreria C ++ facile da usare per analizzare i file CSV ma non ne trovai nessuno disponibile, quindi ho finito per crearne uno. Rapidcsv è una libreria di sola intestazione C ++ 11 che consente di accedere direttamente a colonne (o righe) analizzate come vettori, nel tipo di dati scelto. Per esempio:

#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
Bel lavoro, ma la libreria non funziona correttamente se l'intestazione ha etichette vuote. È tipico per la tabella NxN di Excel / LibreOffice. Inoltre, potrebbe saltare l'ultima riga di dati. Sfortunatamente, la tua lib non è robusta.
Maksym Ganenko,

1
Grazie per il feedback @MaksymGanenko ho corretto il bug "ultima riga di dati" per le righe finali senza interruzione di riga finale. Per quanto riguarda l'altro problema menzionato - "intestazioni con etichette vuote" - non sono sicuro di cosa si riferisca? La libreria dovrebbe gestire etichette vuote (sia quotate che non quotate). Può anche leggere CSV senza riga / colonna di intestazione, ma richiede all'utente di specificarlo (ID titolo col -1 e ID titolo riga -1). Fornisci ulteriori dettagli o segnala un bug nella pagina GitHub se hai qualche caso d'uso specifico che vorresti vedere supportato. Grazie!
d99kris,

2

Mi scusi, ma tutto questo sembra una grande sintassi elaborata per nascondere alcune righe di codice.

Perché non questo:

/**

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

Ehm, perché ci dovrebbe essere ",\n"nella stringa?
Timmmm,

@Timmmm cerca il metodo substr della classe String e vedrai che richiede più caratteri, \ n è il carattere di nuova riga, quindi in questo caso conta come un singolo carattere. Non cerca l'intero valore nel suo insieme. Sta cercando ogni singolo personaggio; vale a dire virgola o newline. substr restituirà la posizione del primo carattere che trova, e -1 se non trova nessuno dei due, il che significa che ha finito di leggere la riga. fp tiene traccia internamente della posizione nel file, quindi ogni chiamata a readCSV lo sposta di una riga alla volta.
Martyn Shutt,

2

Ecco il codice per leggere una matrice, nota che hai anche una funzione csvwrite in matlab

void loadFromCSV( const std::string& filename )
{
    std::ifstream       file( filename.c_str() );
    std::vector< std::vector<std::string> >   matrix;
    std::vector<std::string>   row;
    std::string                line;
    std::string                cell;

    while( file )
    {
        std::getline(file,line);
        std::stringstream lineStream(line);
        row.clear();

        while( std::getline( lineStream, cell, ',' ) )
            row.push_back( cell );

        if( !row.empty() )
            matrix.push_back( row );
    }

    for( int i=0; i<int(matrix.size()); i++ )
    {
        for( int j=0; j<int(matrix[i].size()); j++ )
            std::cout << matrix[i][j] << " ";

        std::cout << std::endl;
    }
}

2

Puoi aprire e leggere il file .csv usando le funzioni fopen, fscanf, ma l'importante è analizzare i dati. Il modo più semplice per analizzare i dati usando delimitatore. Nel caso di .csv, delimitatore è ','.

Supponiamo che il tuo file data1.csv sia il seguente:

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

puoi tokenizzare i dati e archiviarli in array di caratteri e successivamente utilizzare la funzione atoi () ecc. per le conversioni appropriate

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 inverte la logica, significa che corrisponde a qualsiasi stringa che non contiene la virgola quindi l'ultima, dice che corrisponde alla virgola che ha terminato la stringa precedente.


2

La prima cosa che devi fare è assicurarti che il file esista. Per fare ciò devi solo provare ad aprire il flusso di file nel percorso. Dopo aver aperto il flusso di file, utilizzare stream.fail () per vedere se ha funzionato come previsto 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;
}
}

È inoltre necessario verificare che il file fornito sia il tipo corretto di file. A tale scopo, è necessario esaminare il percorso del file fornito fino a trovare l'estensione del file. Una volta che hai l'estensione del file assicurati che sia un file .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;
}

Questa funzione restituirà l'estensione del file che verrà utilizzata in seguito in un messaggio di errore.

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

Questa funzione chiamerà effettivamente i controlli degli errori creati sopra e quindi analizzerà il file.

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

Devi sentirti orgoglioso quando usi qualcosa di così bello boost::spirit

Qui il mio tentativo di un parser (quasi) conforme alle specifiche CSV su questo link Specifiche CSV (non ho avuto bisogno di interruzioni di riga all'interno dei campi. Anche gli spazi attorno alle virgole vengono ignorati).

Dopo aver superato l'esperienza scioccante di aspettare 10 secondi per la compilazione di questo codice :), puoi sederti e divertirti.

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

Compilare:

make csvparser

Test (esempio rubato da 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

Questa soluzione rileva questi 4 casi

la lezione completa è 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,",

Legge il file carattere per carattere e legge 1 riga alla volta su un vettore (di stringhe), quindi adatto a file molto grandi.

L'uso è

Scorrere fino a quando non viene restituita una riga vuota (fine del file). Una riga è un vettore in cui ogni voce è una colonna 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 dichiarazione di classe

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

l'implemento

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

Puoi anche dare un'occhiata alle funzionalità della Qtlibreria.

Ha il supporto per le espressioni regolari e la classe QString ha dei metodi piacevoli, ad esempio split()restituendo QStringList, un elenco di stringhe ottenute dividendo la stringa originale con un delimitatore fornito. Dovrebbe essere sufficiente per il file CSV.

Per ottenere una colonna con un nome di intestazione specifico uso quanto segue: ereditarietà c ++ problema Qt qstring


questo non gestirà le virgole tra virgolette
Ezee,

1

Se non vuoi occuparti di includere il boost nel tuo progetto (è considerevolmente grande se tutto ciò per cui lo utilizzerai è l'analisi CSV ...)

Ho avuto fortuna con l'analisi CSV qui:

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

Gestisce i campi tra virgolette, ma non gestisce i caratteri in linea \ n (che probabilmente va bene per la maggior parte degli usi).


1
Il compilatore non dovrebbe eliminare tutto ciò che non è essenziale?
Tofutim,

1

Questo è un vecchio thread ma è ancora in cima ai risultati della ricerca, quindi sto aggiungendo la mia soluzione usando std :: stringstream e un semplice metodo di sostituzione delle stringhe di Yves Baumes che ho trovato qui.

L'esempio seguente leggerà un file riga per riga, ignorerà le righe di commento che iniziano con // e analizzerà le altre righe in una combinazione di stringhe, ints e doppi. Stringstream esegue l'analisi, ma si aspetta che i campi vengano delimitati da spazi bianchi, quindi uso stringreplace per trasformare prima le virgole in spazi. Gestisce le schede ok, ma non si occupa delle stringhe tra virgolette.

Gli input errati o mancanti vengono semplicemente ignorati, il che può essere o meno positivo, a seconda delle circostanze.

#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

Per quello che vale, ecco la mia implementazione. Si occupa dell'input di wstring, ma potrebbe essere facilmente adattato alla stringa. Non gestisce newline nei campi (poiché la mia applicazione non lo è neanche, ma aggiungere il suo supporto non è troppo difficile) e non è conforme alla fine della riga "\ r \ n" come da RFC (supponendo che tu usi std :: getline), ma gestisce correttamente il taglio degli spazi bianchi e le doppie virgolette (si spera).

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

Ecco una funzione pronta per l'uso se tutto ciò che serve è caricare un file di dati di doppio (nessun numero intero, nessun testo).

#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

Un altro modo semplice e veloce è usare 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';
}

Uscite:

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

1

Ho scritto un modo carino per analizzare i file CSV e ho pensato di aggiungerlo come risposta:

#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

È possibile usare std::regex .

A seconda delle dimensioni del file e della memoria disponibile, è possibile leggerlo riga per riga o interamente in un file std::string .

Per leggere il file è possibile utilizzare:

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

allora puoi abbinarlo con questo che è in realtà personalizzabile per le tue esigenze.

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

Dal momento che non sono abituato a potenziare in questo momento, suggerirò una soluzione più semplice. Supponiamo che il tuo file .csv abbia 100 righe con 10 numeri in ogni riga separate da un ','. È possibile caricare questi dati sotto forma di un array con il seguente codice:

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


}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.