Wie kann ich CSV-Dateien in C ++ lesen und analysieren?

264

Ich muss CSV-Dateidaten in C ++ laden und verwenden. Zu diesem Zeitpunkt kann es sich wirklich nur um einen durch Kommas getrennten Parser handeln (dh Sie müssen sich keine Sorgen machen, dass neue Zeilen und Kommas entkommen). Der Hauptbedarf ist ein zeilenweiser Parser, der bei jedem Aufruf der Methode einen Vektor für die nächste Zeile zurückgibt.

Ich fand diesen Artikel ziemlich vielversprechend: http://www.boost.org/doc/libs/1_35_0/libs/spirit/example/fundamental/list_parser.cpp

Ich habe Boost's Spirit noch nie benutzt, bin aber bereit, es zu versuchen. Aber nur wenn es keine einfachere Lösung gibt, übersehe ich.

Benutzer1
quelle
11
Ich habe nach boost::spiritParsen gesucht . Es ist mehr für das Parsen von Grammatiken dank des Parsens eines einfachen Dateiformats. Jemand in meinem Team hat versucht, damit XML zu analysieren, und das Debuggen war mühsam. Halte dich fern, boost::spiritwenn möglich.
Chrish
50
Sorry chrish, aber das ist ein schrecklicher Rat. Spirit ist nicht immer eine geeignete Lösung, aber ich habe es in einer Reihe von Projekten erfolgreich eingesetzt - und verwende es auch weiterhin. Im Vergleich zu ähnlichen Tools (Antlr, Lex / yacc usw.) hat es erhebliche Vorteile. Nun, für das Parsen von CSV ist es wahrscheinlich übertrieben ...
MattyT
4
@MattyT IMHO spiritist für eine Parser-Kombinator-Bibliothek ziemlich schwer zu verwenden. Nachdem ich einige (sehr angenehme) Erfahrungen mit Haskells- (atto)parsecBibliotheken gemacht hatte, erwartete ich, dass es (Spirit) ähnlich gut funktionieren würde, gab es aber auf, nachdem ich mit 600-Zeilen-Compiler-Fehlern gekämpft hatte.
Für den
1
C CSV-Parser: sourceforge.net/projects/cccsvparser C CSV-Writer: sourceforge.net/projects/cccsvwriter
SomethingSomething

Antworten:

296

Wenn Sie sich nicht dafür interessieren, Komma und Zeilenumbruch zu maskieren,
UND Sie Komma und Zeilenumbruch nicht in Anführungszeichen einbetten können (wenn Sie dann nicht entkommen können ...),
dann sind es nur drei Codezeilen (OK 14 -> Aber es ist nur 15, um die ganze Datei zu lesen).

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

Ich würde nur eine Klasse erstellen, die eine Zeile darstellt.
Dann streame in dieses Objekt:

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

Aber mit ein wenig Arbeit könnten wir technisch einen Iterator erstellen:

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";
    }
}
Martin York
quelle
20
first () next (). Was ist das Java! Nur ein Scherz.
Martin York
4
@DarthVader: Eine überlagerte breite Aussage, die aufgrund ihrer Breite albern ist. Wenn Sie klarstellen möchten, warum es schlecht ist und warum diese Schlechtigkeit in diesem Zusammenhang gilt.
Martin York
12
@DarthVader: Ich finde es dumm, breite Verallgemeinerungen vorzunehmen. Der obige Code funktioniert korrekt, sodass ich tatsächlich alles falsch sehen kann. Wenn Sie jedoch einen konkreten Kommentar zu dem oben Gesagten haben, werde ich dies in diesem Zusammenhang auf jeden Fall berücksichtigen. Aber ich kann sehen, wie Sie zu diesem Schluss kommen können, indem Sie gedankenlos einer Reihe allgemeiner Regeln für C # folgen und sie auf eine andere Sprache anwenden.
Martin York
5
Wenn Sie auf seltsame Verknüpfungsprobleme mit dem obigen Code stoßen, weil irgendwo eine andere Bibliothek definiert ist istream::operator>>(wie Eigen), fügen Sie inlinevor der Operator-Deklaration eine hinzu, um das Problem zu beheben.
sebastian_k
3
Dies ist das einfachste und sauberste Beispiel dafür, wie man eine Iteratorklasse erstellt, die ich je gesehen habe.
Giancarlo Sportelli
46

Lösung mit 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);
}
dtw
quelle
9
Der Boost-Tokenizer unterstützt den vollständigen CSV-Standard nicht vollständig, es gibt jedoch einige schnelle Problemumgehungen. Siehe stackoverflow.com/questions/1120140/csv-parser-in-c/…
Rolf Kristensen
3
Müssen Sie die gesamte Boost-Bibliothek auf Ihrem Computer haben, oder können Sie dazu nur eine Teilmenge ihres Codes verwenden? 256mb scheint viel für CSV-Analyse zu sein ..
NPike
6
@NPike: Sie können das mit Boost gelieferte Dienstprogramm bcp verwenden , um nur die Header zu extrahieren, die Sie tatsächlich benötigen.
ildjarn
46

Meine Version verwendet nur die Standard-C ++ 11-Bibliothek. Es kommt gut mit Excel CSV-Zitaten zurecht:

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

Der Code wird als Finite-State-Maschine geschrieben und verbraucht jeweils ein Zeichen. Ich denke, es ist einfacher, darüber nachzudenken.

#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;
}
Sastanin
quelle
6
danke, ich denke das ist die vollständigste Antwort, schade, dass sie hier vergraben ist.
Mihai
Dieser verschachtelte Vektor von Strings ist für moderne Prozessoren ein No-Go. Wirft ihre Caching-Fähigkeit weg
Nikolaos Giotis
Außerdem
Die Top-Antwort hat bei mir nicht funktioniert, da ich auf einem älteren Compiler bin. Diese Antwort hat funktioniert, die Vektorinitialisierung erfordert möglicherweise const char *vinit[] = {""}; vector<string> fields(vinit, end(vinit));
Folgendes
31

Die C ++ String Toolkit Library (StrTk) verfügt über eine Token-Grid-Klasse, mit der Sie Daten entweder aus Textdateien, Zeichenfolgen oder Zeichenpuffern laden und zeilenspaltenweise analysieren / verarbeiten können.

Sie können die Zeilen- und Spaltenbegrenzer angeben oder einfach die Standardeinstellungen verwenden.

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

Weitere Beispiele finden Sie hier

J Mkdjion
quelle
1
Obwohl strtk doppelt zitierte Felder und sogar das options.trim_dquotes = trueEntfernen der umgebenden Anführungszeichen (via ) unterstützt, unterstützt es nicht das Entfernen doppelter doppelter Anführungszeichen (z. B. das Feld "She said ""oh no"", and left."als C-Zeichenfolge "She said \"oh no\", and left."). Das musst du selbst machen.
Rampion
1
Bei der Verwendung strtkmüssen Sie auch Felder in doppelten Anführungszeichen, die Zeilenumbrüche enthalten, manuell verarbeiten.
Rampion
29

Sie können Boost Tokenizer mit Escape_list_separator verwenden.

entkommene_list_separator analysiert eine Obermenge der CSV. Boost :: Tokenizer

Dies verwendet nur Boost-Tokenizer-Header-Dateien, keine Verknüpfung zu Boost-Bibliotheken erforderlich.

Hier ein Beispiel (siehe CSV-Datei mit Boost Tokenizer in C ++ analysieren für Details oder 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;
    }
}
stefanB
quelle
Und wenn Sie eingebettete neue Zeilen analysieren möchten, mybyteofcode.blogspot.com/2010/11/… .
stefanB
Während diese Technik funktioniert, habe ich festgestellt, dass sie eine sehr schlechte Leistung aufweist. Das Parsen einer 90000-Zeilen-CSV-Datei mit zehn Feldern pro Zeile dauert auf meinem 2-GHz-Xeon etwa 8 Sekunden. Das CSV-Modul der Python Standard Library analysiert dieselbe Datei in etwa 0,3 Sekunden.
Rob Smallshire
@Rob das ist interessant - was macht die Python-CSV anders?
Tofutim
1
@RobSmallshire Es ist ein einfacher Beispielcode, kein Hochleistungscode. Dieser Code erstellt Kopien aller Felder pro Zeile. Für eine höhere Leistung würden Sie verschiedene Optionen verwenden und nur Verweise auf Felder im Puffer zurückgeben, anstatt Kopien zu erstellen.
stefanB
29

Es ist nicht übertrieben, Spirit zum Parsen von CSVs zu verwenden. Spirit eignet sich gut für Micro-Parsing-Aufgaben. Zum Beispiel ist es mit Spirit 2.1 so einfach wie:

bool r = phrase_parse(first, last,

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

    space, v);

Der Vektor v wird mit den Werten gefüllt. In den neuen Spirit 2.1-Dokumenten, die gerade mit Boost 1.41 veröffentlicht wurden, gibt es eine Reihe von Tutorials, die dies ansprechen.

Das Tutorial geht von einfach bis komplex. Die CSV-Parser werden irgendwo in der Mitte präsentiert und berühren verschiedene Techniken bei der Verwendung von Spirit. Der generierte Code ist so eng wie handgeschriebener Code. Schauen Sie sich den generierten Assembler an!

Joel de Guzman
quelle
18
Tatsächlich ist es übertrieben, die Kompilierungszeit ist enorm und macht die Verwendung von Spirit für einfache "Micro-Parsing-Aufgaben" unangemessen.
Gerdiner
13
Ich möchte auch darauf hinweisen, dass der obige Code CSV nicht analysiert, sondern nur einen Bereich des durch Kommas getrennten Vektortyps analysiert. Es werden keine Anführungszeichen, unterschiedliche Arten von Spalten usw. behandelt. Kurz gesagt, 19 Stimmen für etwas, das die Frage überhaupt beantwortet, scheinen mir etwas verdächtig.
Gerdiner
9
@ Gerdiner Unsinn. Die Kompilierungszeit für kleine Parser ist nicht so groß, aber auch irrelevant, da Sie den Code in eine eigene Kompilierungseinheit einfügen und einmal kompilieren . Dann müssen Sie es nur noch verknüpfen und das ist so effizient wie es nur geht. Und für Ihren anderen Kommentar gibt es so viele Dialekte von CSV wie es Prozessoren dafür gibt. Dieser Dialekt ist sicherlich kein sehr nützlicher Dialekt, kann aber trivial erweitert werden, um zitierte Werte zu verarbeiten.
Konrad Rudolph
11
@konrad: Das einfache Einfügen von "#include <boost / spirit / include / qi.hpp>" in eine leere Datei mit nur einer Hauptdatei und nichts anderem dauert 9,7 Sekunden mit MSVC 2012 auf einem Corei7 mit 2 GHz. Es ist unnötig aufzublähen. Die akzeptierte Antwort wird in weniger als 2 Sekunden auf demselben Computer kompiliert. Ich würde mir nur ungern vorstellen, wie lange das Kompilieren des 'richtigen' Boost.Spirit-Beispiels dauern würde.
Gerdiner
11
@Gerdiner Ich muss Ihnen zustimmen, dass der Aufwand für die Verwendung von Spirit für etwas so Einfaches wie die Verarbeitung von Lebensläufen viel zu groß ist.
18

Wenn Sie DO kümmern sich um CSV richtig Parsen, das wird es tun ... relativ langsam , da es ein Zeichen zu einem Zeitpunkt arbeitet.

 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);
    }
Michael
quelle
AFAICT dies behandelt eingebettete Anführungszeichen nicht korrekt (z. B. "Diese Zeichenfolge hat" "eingebettete Anführungszeichen" "," foo ", 1))
Jeremy Friesner
14

Wenn Sie den Boost Tokenizer entkommen_list_separator für CSV-Dateien verwenden, sollten Sie Folgendes beachten:

  1. Es erfordert ein Escape-Zeichen (Standard-Schrägstrich - \)
  2. Es erfordert ein Splitter / Trennzeichen (Standardkomma -,)
  3. Es erfordert ein Anführungszeichen (Standardzitat - ")

Das im Wiki angegebene CSV-Format besagt, dass Datenfelder Trennzeichen in Anführungszeichen enthalten können (unterstützt):

1997, Ford, E350, "Super, luxuriöser LKW"

Das im Wiki angegebene CSV-Format besagt, dass einfache Anführungszeichen mit doppelten Anführungszeichen behandelt werden sollen (Escape_list_separator entfernt alle Anführungszeichen):

1997, Ford, E350, "Super" "Luxus" "LKW"

Das CSV-Format gibt nicht an, dass Back-Slash-Zeichen entfernt werden sollen (Escape_list_separator entfernt alle Escape-Zeichen).

Eine mögliche Problemumgehung, um das Standardverhalten des Boosts zu beheben. Escape_list_separator:

  1. Ersetzen Sie zuerst alle Schrägstriche (\) durch zwei Schrägstriche (\\), damit sie nicht entfernt werden.
  2. Zweitens ersetzen Sie alle doppelten Anführungszeichen ("") durch ein einzelnes Schrägstrichzeichen und ein Anführungszeichen (\ ").

Diese Umgehung hat den Nebeneffekt, dass leere Datenfelder, die durch ein doppeltes Anführungszeichen dargestellt werden, in ein einfaches Anführungszeichen umgewandelt werden. Wenn Sie die Token durchlaufen, müssen Sie überprüfen, ob es sich bei dem Token um ein einfaches Anführungszeichen handelt, und es wie eine leere Zeichenfolge behandeln.

Nicht schön, aber es funktioniert, solange die Anführungszeichen keine Zeilenumbrüche enthalten.

Rolf Kristensen
quelle
8

Vielleicht möchten Sie sich mein FOSS-Projekt CSVfix ( aktualisierter Link ) ansehen, bei dem es sich um einen in C ++ geschriebenen CSV-Stream-Editor handelt. Der CSV-Parser ist kein Preis, erledigt aber die Aufgabe und das gesamte Paket kann das tun, was Sie benötigen, ohne dass Sie Code schreiben.

Ein Verwendungsbeispiel finden Sie in alib / src / a_csv.cpp für den CSV-Parser und in csvlib / src / csved_ioman.cpp ( IOManager::ReadCSV).

cxw
quelle
Scheint großartig ... Was ist mit dem Status Beta / Produktion?
Neuro
Der Status ist "in Entwicklung", wie aus den Versionsnummern hervorgeht. Ich brauche wirklich mehr Feedback von Benutzern, bevor ich zu Version 1.0 gehe. Außerdem möchte ich noch einige weitere Funktionen hinzufügen, die mit der XML-Produktion aus CSV zu tun haben.
Lesezeichen setzen, und werde es versuchen, wenn ich das nächste Mal mit diesen wunderbaren Standard-CSV-Dateien umgehen muss ...
Neuro
8

Da alle CSV-Fragen hier umgeleitet zu werden scheinen, dachte ich, ich würde meine Antwort hier posten. Diese Antwort geht nicht direkt auf die Frage des Fragestellers ein. Ich wollte in der Lage sein, einen Stream einzulesen, von dem bekannt ist, dass er im CSV-Format vorliegt, und auch die Typen der einzelnen Felder waren bereits bekannt. Natürlich kann die folgende Methode verwendet werden, um jedes Feld als Zeichenfolgentyp zu behandeln.

Betrachten Sie als Beispiel dafür, wie ich einen CSV-Eingabestream verwenden möchte, die folgende Eingabe (entnommen aus der Wikipedia-Seite zu 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"
;

Dann wollte ich die Daten folgendermaßen einlesen können:

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

Dies war die Lösung, mit der ich endete.

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

Mit den folgenden Hilfsprogrammen, die durch die neuen Vorlagen für integrale Merkmale in C ++ 11 vereinfacht werden können:

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

Probieren Sie es online aus!

jxh
quelle
6

Ich habe einen Nur-Header-C ++ 11-CSV-Parser geschrieben . Es ist gut getestet, schnell, unterstützt die gesamte CSV-Spezifikation (Felder in Anführungszeichen, Trennzeichen / Abschlusszeichen in Anführungszeichen, Anführungszeichen, Anführungszeichen usw.) und kann so konfiguriert werden, dass CSVs berücksichtigt werden, die nicht der Spezifikation entsprechen.

Die Konfiguration erfolgt über eine fließende Schnittstelle:

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

Das Parsen ist nur ein Bereich, der auf der Schleife basiert:

#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;
  }
}
m0meni
quelle
1
Gute Arbeit, aber Sie müssen drei weitere Dinge hinzufügen: (1) Header lesen (2) Felder nach Namen indizieren (3) Speicher nicht in einer Schleife neu
zuweisen,
@MaksymGanenko Ich mache # 3. Könnten Sie auf # 2 näher eingehen?
m0meni
1
Es ist sehr nützlich, Felder nicht nach Position in einer Zeile, sondern nach Name in der Kopfzeile (in der ersten Zeile der CSV-Tabelle) abzurufen. Zum Beispiel erwarte ich eine CSV-Tabelle mit dem Feld "Datum", aber ich weiß nicht, was der Index "Feld" in einer Zeile ist.
Maksym Ganenko
1
@MaksymGanenko ah ich verstehe was du meinst. Es gibt github.com/ben-strasser/fast-cpp-csv-parser, wenn Sie die Spalten Ihrer CSV zur Kompilierungszeit kennen, und es ist wahrscheinlich besser als meine. Was ich wollte, war ein CSV-Parser für die Fälle, in denen Sie denselben Code für viele verschiedene CSVs verwenden wollten und nicht wissen, wie sie im Voraus aussehen. Also werde ich wahrscheinlich nicht # 2 hinzufügen, aber ich werde irgendwann in Zukunft # 1 hinzufügen.
m0meni
5

Eine weitere CSV-E / A-Bibliothek finden Sie hier:

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
  }
}
Heygard Flisch
quelle
2
Schön, aber es zwingt Sie, die Anzahl der Spalten beim Kompilieren auszuwählen. Für viele Anwendungen nicht sehr nützlich.
quant_dev
5

Eine andere Lösung ähnlich der Antwort von Loki Astari in C ++ 11. Zeilen hier sind std::tuples von einem bestimmten Typ. Der Code scannt eine Zeile, scannt dann bis zu jedem Trennzeichen und konvertiert den Wert dann direkt in das Tupel (mit etwas Vorlagencode) und gibt ihn aus.

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

Fortschritte:

  • ziemlich sauber und einfach zu bedienen, nur C ++ 11.
  • automatische Typumwandlung in std::tuple<t1, ...>via operator>>.

Was fehlt:

  • entkommen und zitieren
  • Keine Fehlerbehandlung bei fehlerhafter CSV.

Der Hauptcode:

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

Ich habe ein kleines Arbeitsbeispiel auf GitHub gestellt . Ich habe es zum Parsen einiger numerischer Daten verwendet und es hat seinen Zweck erfüllt.

Spak
quelle
1
Inlining ist Ihnen vielleicht egal, da die meisten Compiler es selbst entscheiden. Zumindest bin ich mir in Visual C ++ sicher. Es kann die Methode unabhängig von Ihrer Methodenspezifikation inline.
MrPisarik
1
Genau deshalb habe ich sie explizit markiert. Gcc und Clang, die ich meistens benutze, haben auch ihre eigenen Konventionen. Ein "Inline" -Schlüsselwort sollte nur ein Anreiz sein.
Spak
4

Hier ist eine weitere Implementierung eines Unicode-CSV-Parsers (funktioniert mit wchar_t). Ich habe einen Teil davon geschrieben, während Jonathan Leffler den Rest schrieb.

Hinweis: Dieser Parser zielt darauf ab, das Verhalten von Excel so genau wie möglich zu replizieren, insbesondere beim Importieren fehlerhafter oder fehlerhafter CSV-Dateien.

Dies ist die ursprüngliche Frage - Analysieren einer CSV-Datei mit mehrzeiligen Feldern und doppelten Anführungszeichen

Dies ist der Code als SSCCE (kurzes, eigenständiges, korrektes Beispiel).

#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;
}
Sashoalm
quelle
3

Ich brauchte eine benutzerfreundliche C ++ - Bibliothek zum Parsen von CSV-Dateien, konnte aber keine finden und habe am Ende eine erstellt. Rapidcsv ist eine reine C ++ 11-Header-Bibliothek, die direkten Zugriff auf analysierte Spalten (oder Zeilen) als Vektoren im Datentyp Ihrer Wahl bietet. Zum Beispiel:

#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;
}
d99kris
quelle
1
Gute Arbeit, aber die Bibliothek funktioniert nicht richtig, wenn der Header leere Beschriftungen hat. Dies ist typisch für die Excel / LibreOffice NxN-Tabelle. Außerdem wird möglicherweise die letzte Datenzeile übersprungen. Leider ist Ihre Bibliothek nicht robust.
Maksym Ganenko
1
Vielen Dank für das Feedback @MaksymGanenko Ich habe den Fehler "Letzte Datenzeile" für letzte Zeilen ohne Zeilenumbruch behoben. Was das andere erwähnte Problem betrifft - "Überschriften mit leeren Beschriftungen" - bin ich mir nicht sicher, worauf es sich bezieht? Die Bibliothek sollte leere Etiketten (sowohl in Anführungszeichen als auch ohne Anführungszeichen) verarbeiten. Es kann auch CSV ohne Kopfzeile / -spalte lesen, der Benutzer muss dies jedoch angeben (Spalten-Titel-ID -1 und Zeilentitel-ID -1). Bitte geben Sie weitere Details an oder melden Sie einen Fehler auf der GitHub-Seite, wenn Sie einen bestimmten Anwendungsfall haben, der unterstützt werden soll. Vielen Dank!
d99kris
2

Entschuldigen Sie, aber das alles scheint eine Menge ausgefeilter Syntax zu sein, um ein paar Codezeilen zu verbergen.

Warum nicht das:

/**

  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;
}
Ravenspoint
quelle
Ähm, warum sollte es ",\n"in der Saite sein?
Timmmm
@Timmmm Wenn Sie die substr-Methode der String-Klasse nachschlagen, werden Sie feststellen, dass mehrere Zeichen erforderlich sind. \ N ist das Zeilenumbruchzeichen und zählt in diesem Fall als einzelnes Zeichen. Es wird nicht nach dem gesamten Wert als Ganzes gesucht. Es sucht nach jedem einzelnen Charakter; nämlich Komma oder Zeilenumbruch. substr gibt die Position des ersten gefundenen Zeichens zurück und -1, wenn keines gefunden wird, was bedeutet, dass das Lesen der Zeile beendet ist. fp verfolgt die Position in der Datei intern, sodass jeder Aufruf von readCSV sie zeilenweise verschiebt.
Martyn Shutt
2

Hier ist Code zum Lesen einer Matrix. Beachten Sie, dass Sie in matlab auch eine csvwrite-Funktion haben

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;
    }
}
Jim M.
quelle
2

Sie können die CSV-Datei mit den Funktionen fopen und fscanf öffnen und lesen. Wichtig ist jedoch, dass Sie die Daten analysieren. Einfachste Methode zum Analysieren der Daten mit dem Trennzeichen. Bei CSV ist das Trennzeichen ','.

Angenommen, Ihre Datei data1.csv lautet wie folgt:

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

Sie können Daten tokenisieren und im char-Array speichern und später die Funktion atoi () usw. für entsprechende Konvertierungen verwenden

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 invertiert die Logik, bedeutet Übereinstimmung mit einer Zeichenfolge, die kein Komma enthält, und sagt zuletzt, dass das Komma übereinstimmt, das die vorherige Zeichenfolge beendet hat.

Amruta Ghodke
quelle
2

Als erstes müssen Sie sicherstellen, dass die Datei vorhanden ist. Um dies zu erreichen, müssen Sie nur versuchen, den Dateistream im Pfad zu öffnen. Nachdem Sie den Dateistream geöffnet haben, verwenden Sie stream.fail (), um festzustellen, ob er wie erwartet funktioniert hat oder nicht.

bool fileExists(string fileName)
{

ifstream test;

test.open(fileName.c_str());

if (test.fail())
{
    test.close();
    return false;
}
else
{
    test.close();
    return true;
}
}

Sie müssen auch überprüfen, ob die bereitgestellte Datei den richtigen Dateityp hat. Um dies zu erreichen, müssen Sie den angegebenen Dateipfad durchsuchen, bis Sie die Dateierweiterung finden. Wenn Sie die Dateierweiterung haben, stellen Sie sicher, dass es sich um eine CSV-Datei handelt.

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

Diese Funktion gibt die Dateierweiterung zurück, die später in einer Fehlermeldung verwendet wird.

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

Diese Funktion ruft die oben erstellten Fehlerprüfungen auf und analysiert dann die Datei.

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;
    }
}
Elizabeth Card
quelle
2

Du musst stolz sein, wenn du etwas so Schönes wie verwendest boost::spirit

Hier mein Versuch eines Parsers, der (fast) den CSV-Spezifikationen auf diesem Link entspricht. CSV-Spezifikationen (ich brauchte keine Zeilenumbrüche innerhalb von Feldern. Auch die Leerzeichen um die Kommas werden verworfen).

Nachdem Sie die schockierende Erfahrung überwunden haben, 10 Sekunden auf das Kompilieren dieses Codes zu warten :), können Sie sich zurücklehnen und genießen.

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

Kompilieren:

make csvparser

Test (Beispiel aus Wikipedia gestohlen ):

./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
jav
quelle
2

Diese Lösung erkennt diese 4 Fälle

komplette Klasse ist bei

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,",

Es liest die Datei zeichenweise und liest jeweils 1 Zeile in einen Vektor (aus Zeichenfolgen), der daher für sehr große Dateien geeignet ist.

Verwendung ist

Iterieren Sie, bis eine leere Zeile zurückgegeben wird (Dateiende). Eine Zeile ist ein Vektor, bei dem jeder Eintrag eine CSV-Spalte ist.

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

die Klassendeklaration

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

die Umsetzung

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;
}
Pedro Vicente
quelle
1

Sie können sich auch die Funktionen der QtBibliothek ansehen .

Es unterstützt reguläre Ausdrücke und die QString-Klasse verfügt über nützliche Methoden, z. B. die split()Rückgabe von QStringList, einer Liste von Zeichenfolgen, die durch Teilen der ursprünglichen Zeichenfolge mit einem bereitgestellten Trennzeichen erhalten werden. Sollte für CSV-Datei ausreichen ..

Um eine Spalte mit einem bestimmten Headernamen zu erhalten, verwende ich Folgendes: c ++ Vererbung Qt Problem qstring

MadH
quelle
Dies wird keine Kommas in Anführungszeichen behandeln
Ezee
1

Wenn Sie sich nicht mit dem Einbeziehen von Boost in Ihr Projekt befassen möchten (es ist beträchtlich groß, wenn Sie es nur für CSV-Parsing verwenden möchten ...)

Ich hatte Glück mit der CSV-Analyse hier:

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

Es behandelt Felder in Anführungszeichen, jedoch keine Inline-Zeichen \ n (was für die meisten Verwendungen wahrscheinlich in Ordnung ist).

NPike
quelle
1
Sollte der Compiler nicht alles entfernen, was nicht wesentlich ist?
Tofutim
1

Dies ist ein alter Thread, der jedoch immer noch ganz oben in den Suchergebnissen steht. Daher füge ich meine Lösung mit std :: stringstream und einer einfachen Methode zum Ersetzen von Zeichenfolgen von Yves Baumes hinzu, die ich hier gefunden habe.

Im folgenden Beispiel wird eine Datei zeilenweise gelesen, Kommentarzeilen, die mit // beginnen, ignoriert und die anderen Zeilen in eine Kombination aus Zeichenfolgen, Ints und Doubles analysiert. Stringstream führt die Analyse durch, erwartet jedoch, dass Felder durch Leerzeichen begrenzt werden. Daher verwende ich stringreplace, um Kommas zuerst in Leerzeichen umzuwandeln. Es behandelt Tabs in Ordnung, behandelt jedoch keine Zeichenfolgen in Anführungszeichen.

Schlechte oder fehlende Eingaben werden einfach ignoriert, was je nach Ihren Umständen gut oder schlecht sein kann.

#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));
      }
   }
}
marcp
quelle
1

Für das, was es wert ist, hier ist meine Implementierung. Es befasst sich mit der Eingabe von Zeichenfolgen, kann jedoch leicht an Zeichenfolgen angepasst werden. Es behandelt keine Zeilenumbrüche in Feldern (wie meine Anwendung auch nicht, aber das Hinzufügen der Unterstützung ist nicht allzu schwierig) und entspricht nicht dem Zeilenende "\ r \ n" gemäß RFC (vorausgesetzt, Sie verwenden std :: getline), aber es behandelt Leerzeichen und doppelte Anführungszeichen korrekt (hoffentlich).

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

Hier ist eine einsatzbereite Funktion, wenn Sie lediglich eine Datendatei mit Doppelwerten laden müssen (keine Ganzzahlen, kein Text).

#include <sstream>
#include <fstream>
#include <iterator>
#include <string>
#include <vector>
#include <algorithm>

using namespace std;

/**
 * Parse a CSV data file and fill the 2d STL vector "data".
 * Limits: only "pure datas" of doubles, not encapsulated by " and without \n inside.
 * Further no formatting in the data (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;
}
Antonello
quelle
1

Eine andere schnelle und einfache Möglichkeit ist die Verwendung von 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';
}

Ausgänge:

(abc 123 true 3.14)
(def 456 false 2.718)
Maxim Egorushkin
quelle
1

Ich habe eine gute Methode zum Parsen von CSV-Dateien geschrieben und dachte, ich sollte sie als Antwort hinzufügen:

#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;
}
scap3y
quelle
1

Es ist möglich zu verwenden std::regex .

Abhängig von der Größe Ihrer Datei und dem verfügbaren Speicher ist es möglich, sie zeilenweise oder vollständig in einer Datei zu lesen std::string .

Um die Datei zu lesen, kann man verwenden:

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

dann können Sie mit diesem übereinstimmen, das tatsächlich an Ihre Bedürfnisse anpassbar ist.

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

Da ich momentan nicht an Boost gewöhnt bin, werde ich eine einfachere Lösung vorschlagen. Nehmen wir an, Ihre CSV-Datei enthält 100 Zeilen mit 10 Zahlen in jeder Zeile, die durch ein ',' getrennt sind. Sie können diese Daten in Form eines Arrays mit dem folgenden Code laden:

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


}
nikos_k
quelle