Jak mogę czytać i analizować pliki CSV w C ++?


264

Muszę załadować i użyć danych pliku CSV w C ++. W tym momencie może to być po prostu parser rozdzielany przecinkami (tzn. Nie martw się o ucieczkę nowych wierszy i przecinków). Główną potrzebą jest parser wiersz po wierszu, który zwróci wektor dla następnej linii za każdym razem, gdy wywoływana jest metoda.

Znalazłem ten artykuł, który wygląda dość obiecująco: http://www.boost.org/doc/libs/1_35_0/libs/spirit/example/fundamental/list_parser.cpp

Nigdy nie korzystałem z Ducha Wzmocnienia, ale chętnie go wypróbuję. Ale przeoczę tylko wtedy, gdy nie ma prostszego rozwiązania.


11
Szukałem boost::spiritanalizy. Chodzi bardziej o parsowanie gramatyki, dzięki parsowaniu prostego formatu pliku. Ktoś z mojego zespołu próbował użyć go do analizy XML i debugowanie było uciążliwe. Unikaj, boost::spiritjeśli to możliwe.
chrish

50
Przepraszam, cholera, ale to okropna rada. Spirit nie zawsze jest odpowiednim rozwiązaniem, ale wykorzystałem go - i nadal go używam - z powodzeniem w wielu projektach. W porównaniu do podobnych narzędzi (Antlr, Lex / yacc itp.) Ma znaczące zalety. Teraz, do parsowania CSV, to prawdopodobnie przesada ...
MattyT

4
@MattyT IMHO spiritjest dość trudny w użyciu dla biblioteki kombinacji parserów. Mając (bardzo przyjemne) doświadczenie z (atto)parsecbibliotekami Haskells spodziewałem się, że (duch) będzie działał podobnie, ale zrezygnowałem z niego po walce z błędami kompilatora linii 600.
fho

Odpowiedzi:


296

Jeśli nie przejmujesz się przecinaniem przecinków i znaków nowej linii,
ORAZ nie możesz osadzać przecinków i znaków nowej linii w cudzysłowach (jeśli nie możesz uciec, to ...),
to tylko trzy wiersze kodu (OK 14 -> Ale to jest tylko 15, aby odczytać cały plik).

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

Chciałbym po prostu stworzyć klasę reprezentującą wiersz.
Następnie przesyłaj strumieniowo do tego obiektu:

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

Ale przy odrobinie pracy technicznie możemy stworzyć iterator:

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 (). Czym jest ta Java! Tylko żartowałem.
Martin York,

4
@DarthVader: Nakładka szerokie stwierdzenie, że przez swoją szerokość jest głupie. Jeśli chcesz wyjaśnić, dlaczego jest zły, a następnie, dlaczego to zło ma zastosowanie w tym kontekście.
Martin York,

12
@DarthVader: Myślę, że głupotą jest dokonywanie ogólnych uogólnień. Powyższy kod działa poprawnie, więc naprawdę mogę zobaczyć coś z nim nie tak. Ale jeśli masz jakiś konkretny komentarz do powyższego, zdecydowanie rozważę w tym kontekście. Ale widzę, jak dojść do tego wniosku, bezmyślnie przestrzegając zestawu ogólnych zasad dla języka C # i stosując go w innym języku.
Martin York,

5
Ponadto, jeśli napotkasz dziwne problemy z łączeniem z powyższym kodem, ponieważ inna biblioteka gdzieś definiuje istream::operator>>(jak Eigen), dodaj inlineprzed deklaracją operatora, aby to naprawić.
sebastian_k

3
To najprostszy i najczystszy przykład, jak stworzyć klasę iteratora, jaką kiedykolwiek widziałem.
Giancarlo Sportelli

46

Rozwiązanie wykorzystujące 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
Tokenizer doładowania nie obsługuje w pełni pełnego standardu CSV, ale jest kilka szybkich obejść. Zobacz stackoverflow.com/questions/1120140/csv-parser-in-c/…
Rolf Kristensen

3
Czy musisz mieć całą bibliotekę boostów na swoim komputerze, czy możesz po prostu użyć do tego podzbioru ich kodu? 256 MB wydaje się być bardzo dużo przy
analizie

6
@NPike: Możesz użyć narzędzia bcp , które jest dostarczane z funkcją boost, aby wyodrębnić tylko te nagłówki, których naprawdę potrzebujesz.
ildjarn

46

Moja wersja nie używa niczego poza standardową biblioteką C ++ 11. Dobrze radzi sobie z cytatem Excel CSV:

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

Kod jest zapisywany jako maszyna o stanie skończonym i zużywa jeden znak na raz. Myślę, że łatwiej jest o tym myśleć.

#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
dzięki, myślę, że to jest najbardziej kompletna odpowiedź, szkoda, że ​​jest tu pochowana.
mihai

ten zagnieżdżony wektor ciągów jest nieodzowny dla nowoczesnych procesorów. Wyrzuca ich zdolność buforowania
Nikolaos Giotis,

plus wszystkie te instrukcje zamiany
Nikolaos Giotis,

Najlepsza odpowiedź nie działała dla mnie, ponieważ korzystam ze starszego kompilatora. Ta odpowiedź zadziałała, inicjalizacja wektora może wymagać:const char *vinit[] = {""}; vector<string> fields(vinit, end(vinit));
dr_rk

31

Biblioteka C ++ String Toolkit Library (StrTk) ma klasę siatki tokenów, która pozwala ładować dane z plików tekstowych, łańcuchów znaków lub buforów znaków , a także analizować / przetwarzać je w sposób wiersz-kolumna.

Możesz określić ograniczniki wierszy i ograniczniki kolumn lub po prostu użyć wartości domyślnych.

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

Więcej przykładów można znaleźć tutaj


1
Chociaż strtk obsługuje pola podwójnie cytowane , a nawet usuwa otaczające je cudzysłowy (przez options.trim_dquotes = true), nie obsługuje usuwania podwójnych cudzysłowów (np. Pola "She said ""oh no"", and left."jako ciągu c "She said \"oh no\", and left."). Musisz to zrobić sam.
rampion

1
Podczas korzystania strtkmusisz także ręcznie obsługiwać pola cudzysłowów zawierające znaki nowego wiersza.
rampion

29

Możesz użyć Boost Tokenizer z escaped_list_separator.

escaped_list_separator analizuje nadzbiór csv. Boost :: tokenizer

To używa tylko plików nagłówkowych tokenizera Boost, bez konieczności łączenia z bibliotekami boost.

Oto przykład (zobacz: Analizowanie pliku CSV za pomocą Boost Tokenizer w C ++, aby uzyskać szczegółowe informacje lub 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;
    }
}

A jeśli chcesz móc analizować osadzone nowe linie mybyteofcode.blogspot.com/2010/11/… .
stefanB

Chociaż ta technika działa, stwierdziłem, że ma bardzo słabą wydajność. Przetwarzanie pliku CSV linii 90000 z dziesięcioma polami na linię zajmuje około 8 sekund na moim 2 GHz Xeon. Moduł csv biblioteki standardowej Pythona analizuje ten sam plik w około 0,3 sekundy.
Rob Smallshire

@Rob, który jest interesujący - co CSS Pythona robi inaczej?
tofutim

1
@RobSmallshire to prosty przykładowy kod, ale nie o wysokiej wydajności. Ten kod tworzy kopie wszystkich pól w wierszu. Aby uzyskać wyższą wydajność, należy użyć różnych opcji i zwracać tylko odwołania do pól w buforze zamiast kopiować.
stefanB

29

Używanie Ducha do analizowania plików CSV nie jest przesadą. Spirit doskonale nadaje się do zadań mikroparsowania. Na przykład w Spirit 2.1 jest to tak proste, jak:

bool r = phrase_parse(first, last,

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

    space, v);

Wektor v wypełnia się wartościami. Jest seria samouczków poruszających tę kwestię w nowych dokumentach Spirit 2.1, które właśnie zostały wydane wraz z Boost 1.41.

Samouczek przechodzi od prostego do złożonego. Parsery CSV są przedstawione gdzieś pośrodku i dotyczą różnych technik używania Ducha. Wygenerowany kod jest tak ścisły jak kod pisany ręcznie. Sprawdź wygenerowany asembler!


18
W rzeczywistości jest to przesada, czas kompilacji jest ogromny i sprawia, że ​​używanie Ducha do prostych „zadań mikroparsowania” jest nieuzasadnione.
Gerdiner

13
Chciałbym również zauważyć, że powyższy kod nie analizuje CSV, po prostu analizuje zakres typu wektora rozdzielanego przecinkami. Nie obsługuje cytatów, różnych typów kolumn itp. W skrócie 19 głosów na coś, co odpowiada na pytanie, wydaje mi się nieco podejrzane.
Gerdiner

9
@Gerdiner Nonsense. Czas kompilacji dla małych parserów nie jest tak duży, ale jest również nieistotny, ponieważ wkładasz kod do własnej jednostki kompilacyjnej i kompilujesz go raz . Następnie wystarczy go połączyć, a to jest tak wydajne, jak to możliwe. A jeśli chodzi o twój inny komentarz, istnieje tyle dialektów CSV, ile jest procesorów. Ten z pewnością nie jest bardzo przydatnym dialektem, ale można go w prosty sposób rozszerzyć, aby obsługiwał cytowane wartości.
Konrad Rudolph,

11
@konrad: Po prostu dołączając „#include <boost / spirit / include / qi.hpp>” w pustym pliku z tylko głównym i nic więcej nie zajmuje 9,7 sekundy z MSVC 2012 na Corei7 działającym w 2.ghz. To niepotrzebne wzdęcie. Przyjęta odpowiedź kompiluje się w czasie poniżej 2 sekund na tej samej maszynie, nie chciałbym wyobrażać sobie, jak długo potrwa „właściwy” Boost.
Gerdiner

11
@ Gerdiner Muszę się z tobą zgodzić, że używanie ducha do czegoś tak prostego, jak przetwarzanie CVS jest zdecydowanie zbyt duże.

18

Jeśli NIE dba o parsowania CSV poprawnie, to to zrobi ... stosunkowo powoli, jak to działa jeden char naraz.

 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 nie obsługuje poprawnie osadzonych znaków cudzysłowu (np. „Ten ciąg ma„ ”osadzone znaki cudzysłowu„ ”,„ foo ”, 1))
Jeremy Friesner

14

Korzystając z Boost Tokenizer escaped_list_separator dla plików CSV, należy pamiętać o następujących kwestiach:

  1. Wymaga znaku zmiany znaczenia (domyślny ukośnik odwrotny - \)
  2. Wymaga znaku rozdzielającego / separatora (domyślny przecinek -,)
  3. Wymaga znaku cudzysłowu (domyślny cytat - „)

Format CSV określony przez wiki stwierdza, że ​​pola danych mogą zawierać separatory w cudzysłowach (obsługiwane):

1997, Ford, E350, „Super, luksusowa ciężarówka”

Format CSV określony przez wiki stanowi, że pojedyncze cudzysłowy powinny być obsługiwane z podwójnymi cudzysłowami (escaped_list_separator usunie wszystkie znaki cudzysłowu):

1997, Ford, E350, „Super” „luksusowa” „ciężarówka”

Format CSV nie określa, że ​​wszelkie znaki ukośnika powinny być usunięte (escaped_list_separator usunie wszystkie znaki specjalne).

Możliwe obejście problemu, aby naprawić domyślne zachowanie boost escaped_list_separator:

  1. Najpierw zamień wszystkie znaki odwrotnego ukośnika (\) na dwa znaki odwrotnego ukośnika (\\), aby nie zostały usunięte.
  2. Po drugie, zamień wszystkie podwójne cudzysłowy („”) na pojedynczy ukośnik i cudzysłów (\ ”)

To obejście ma efekt uboczny polegający na tym, że puste pola danych reprezentowane przez podwójny cudzysłów zostaną przekształcone w token pojedynczego cudzysłowu. Podczas iteracji po tokenach należy sprawdzić, czy token jest pojedynczym cudzysłowiem i traktować go jak pusty ciąg.

Nie ładnie, ale działa, o ile w cytatach nie ma nowych linii.


8

Możesz zajrzeć do mojego projektu FOSS CSVfix ( zaktualizowany link ), który jest edytorem strumienia CSV napisanym w C ++. Parser CSV nie jest nagrodą, ale spełnia swoje zadanie, a cała paczka może zrobić to, czego potrzebujesz, bez pisania kodu.

Zobacz alib / src / a_csv.cpp dla parsera CSV, a csvlib / src / csved_ioman.cpp ( IOManager::ReadCSV) dla przykładu użycia.


Wygląda świetnie ... A co ze statusem beta / produkcji?
neuro

Status to „w fazie rozwoju”, jak sugerują numery wersji. Naprawdę potrzebuję więcej informacji zwrotnych od użytkowników, zanim przejdę do wersji 1.0. Dodatkowo mam jeszcze kilka funkcji, które chcę dodać, związanych z produkcją XML z CSV.

Dodanie do zakładek i spróbuję następnym razem, gdy będę miał do czynienia z tymi wspaniałymi standardowymi plikami CSV ...
neuro,

8

Ponieważ wszystkie pytania CSV wydają się być tutaj przekierowywane, pomyślałem, że opublikuję tutaj swoją odpowiedź. Ta odpowiedź nie dotyczy bezpośrednio pytania zadającego. Chciałem móc czytać w strumieniu, o którym wiadomo, że jest w formacie CSV, a także typy każdego pola były już znane. Oczywiście poniższa metoda może być użyta do traktowania każdego pola jako typu łańcucha.

Jako przykład tego, jak chciałem móc używać strumienia wejściowego CSV, rozważ następujące dane wejściowe (wzięte ze strony wikipedii na 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"
;

Potem chciałem móc odczytać takie dane:

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

Takie było rozwiązanie.

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

Dzięki następującym pomocnikom, które można uprościć dzięki nowym zintegrowanym szablonom cech w 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) };
};

Wypróbuj online!


6

Napisałem tylko parser C ++ 11 CSV zawierający tylko nagłówki . Jest dobrze przetestowany, szybki, obsługuje całą specyfikację CSV (pola cytowane, separator / terminator w cudzysłowach, zmiany znaczenia cytatów itp.) I można go konfigurować w celu uwzględnienia CSV, które nie są zgodne ze specyfikacją.

Konfiguracja odbywa się za pomocą płynnego interfejsu:

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

Parsowanie to tylko zakres oparty na pętli:

#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
Dobra robota, ale trzeba dodać jeszcze trzy rzeczy: (1) przeczytać nagłówek (2) zapewnić indeksowania wg nazwy pól (3) nie realokacji pamięci w pętli, wykorzystując ten sam wektor ciągów
Maksym Ganenko

@MaksymGanenko I do # 3. Czy mógłbyś rozwinąć temat # 2?
m0meni

1
Bardzo przydatne jest uzyskanie pól nie według pozycji w wierszu, ale według nazwy podanej w nagłówku (w pierwszym wierszu tabeli CSV). Na przykład oczekuję tabeli CSV z polem „Data”, ale nie wiem, co to jest indeks pola „Data” z rzędu.
Maksym Ganenko

1
@MaksymGanenko ah Rozumiem, co masz na myśli. Jest github.com/ben-strasser/fast-cpp-csv-parser, gdy znasz kolumny swojego pliku CSV w czasie kompilacji i prawdopodobnie jest to lepsze niż moje. To, czego chciałem, to parser CSV dla przypadków, w których chciałeś użyć tego samego kodu dla wielu różnych CSV i nie wiesz, jak wyglądają z wyprzedzeniem. Więc prawdopodobnie nie dodam # 2, ale dodam # 1 w przyszłości.
m0meni

5

Inną bibliotekę we / wy CSV można znaleźć tutaj:

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
Fajnie, ale zmusza cię do wybrania liczby kolumn w czasie kompilacji. Niezbyt przydatne w wielu aplikacjach.
quant_dev,

5

Inne rozwiązanie podobne do odpowiedzi Lokiego Astari w C ++ 11. Rzędy tutaj są std::tupledanego typu. Kod skanuje jedną linię, a następnie skanuje do każdego separatora, a następnie konwertuje i zrzuca wartość bezpośrednio do krotki (z odrobiną kodu szablonu).

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

Zaliczki:

  • dość czysty i prosty w użyciu, tylko C ++ 11.
  • automatyczna konwersja typu na std::tuple<t1, ...>via operator>>.

Czego brakuje:

  • ucieczka i cytowanie
  • brak obsługi błędów w przypadku nieprawidłowego CSV.

Główny kod:

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

Podałem mały działający przykład na GitHub ; Używałem go do analizowania danych liczbowych i spełniło swoje zadanie.


1
Możesz nie przejmować się wstawianiem, ponieważ większość kompilatorów decyduje o tym samodzielnie. Przynajmniej jestem pewien w Visual C ++. Może wstawiać metodę niezależnie od specyfikacji metody.
MrPisarik

1
Właśnie dlatego wyraźnie je oznaczyłem. Gcc i Clang, te, których najczęściej używam, mają również własne konwencje. Słowo kluczowe „inline” powinno być zachętą.
Spak

4

Oto kolejna implementacja analizatora składni Unicode CSV (współpracuje z wchar_t). Napisałem część, a resztę napisał Jonathan Leffler.

Uwaga: ten analizator składni ma na celu jak najwierniejszą replikację zachowania programu Excel, szczególnie podczas importowania uszkodzonych lub źle sformatowanych plików CSV.

To jest oryginalne pytanie - parsowanie pliku CSV z polami wielowierszowymi i unikaniem podwójnych cudzysłowów

To jest kod jako SSCCE (krótki, samodzielny, poprawny przykład).

#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

Potrzebowałem łatwej w użyciu biblioteki C ++ do parsowania plików CSV, ale nie mogłem znaleźć żadnej dostępnej, więc skończyłem z budowaniem jednego. Rapidcsv to biblioteka zawierająca tylko nagłówki C ++ 11, która zapewnia bezpośredni dostęp do analizowanych kolumn (lub wierszy) jako wektorów w wybranym typie danych. Na przykład:

#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
Dobra robota, ale biblioteka nie działa poprawnie, jeśli nagłówek ma puste etykiety. Jest to typowe dla tabeli Excel / LibreOffice NxN. Może również pominąć ostatni wiersz danych. Niestety, twoja biblioteka nie jest solidna.
Maksym Ganenko,

1
Dzięki za opinie @MaksymGanenko Naprawiłem błąd „ostatniej linii danych” dla ostatnich linii bez przerywania linii końcowej. Co do drugiego wspomnianego problemu - „nagłówków z pustymi etykietami” - nie jestem pewien, do czego się odnosi? Biblioteka powinna obsługiwać puste etykiety (zarówno cudzysłowy, jak i cudzysłowy). Może także odczytywać CSV bez wiersza / kolumny nagłówka, ale następnie wymaga podania tego przez użytkownika (identyfikator tytułu kolumna -1 i identyfikator tytułu wiersz -1). Podaj więcej szczegółów lub zgłoś błąd na stronie GitHub, jeśli masz konkretny przypadek użycia, który chcesz, aby był obsługiwany. Dzięki!
d99kris

2

Przepraszam, ale to wszystko wydaje się być bardzo skomplikowaną składnią, aby ukryć kilka linii kodu.

Dlaczego nie to:

/**

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

Eee, dlaczego miałby być ",\n"w sznurku?
Timmmm,

@Timmmm wyszukaj metodę substr klasy String, a zobaczysz, że zajmuje ona wiele znaków, \ n jest znakiem nowej linii, więc w tym przypadku liczy się jako pojedynczy znak. Nie wyszukuje całej wartości jako całości. Szuka każdej indywidualnej postaci; mianowicie przecinek lub znak nowej linii. substr zwróci pozycję pierwszego znalezionego znaku, a -1, jeśli nie znajdzie żadnego, co oznacza, że ​​zakończył czytanie linii. fp wewnętrznie śledzi pozycję w pliku, więc każde wywołanie readCSV przenosi go o jeden rząd na raz.
Martyn Shutt

2

Oto kod do odczytu matrycy, pamiętaj, że masz również funkcję csvwrite w Matlabie

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

Możesz otworzyć i odczytać plik .csv za pomocą funkcji fopen, fscanf, ale ważne jest, aby parsować dane. Najprostszym sposobem na parsowanie danych za pomocą separatora. W przypadku .csv separatorem jest „,”.

Załóżmy, że plik data1.csv wygląda następująco:

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

możesz tokenizować dane i przechowywać je w tablicy char, a następnie użyć funkcji atoi () itp. do odpowiednich konwersji

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 odwraca logikę, oznacza dopasowanie dowolnego łańcucha, który nie zawiera przecinka, a następnie ostatniego, mówi o dopasowaniu przecinka, który zakończył poprzedni ciąg.


2

Pierwszą rzeczą, którą musisz zrobić, to upewnić się, że plik istnieje. Aby to osiągnąć, wystarczy spróbować otworzyć strumień plików na ścieżce. Po otwarciu strumienia plików użyj stream.fail (), aby sprawdzić, czy działa zgodnie z oczekiwaniami, czy nie.

bool fileExists(string fileName)
{

ifstream test;

test.open(fileName.c_str());

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

Musisz również sprawdzić, czy podany plik jest poprawnego typu. Aby to osiągnąć, musisz przejrzeć podaną ścieżkę pliku, aż znajdziesz rozszerzenie pliku. Po rozszerzeniu upewnij się, że jest to plik .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;
}

Ta funkcja zwróci rozszerzenie pliku, które zostanie użyte później w komunikacie o błędzie.

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

Ta funkcja wywoła kontrole błędów utworzone powyżej, a następnie przeanalizuje plik.

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

Musisz być dumny, kiedy używasz czegoś tak pięknego jak boost::spirit

Tutaj moja próba parsera (prawie) zgodnego ze specyfikacjami CSV w tym specyfikacji CSV linku (nie potrzebowałem łamania linii w polach. Również spacje wokół przecinków są odrzucane).

Po przezwyciężeniu szokującego doświadczenia oczekiwania na 10 sekund na skompilowanie tego kodu :) możesz usiąść i cieszyć się.

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

Skompilować:

make csvparser

Test (przykład skradziony z Wikipedii ):

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

To rozwiązanie wykrywa te 4 przypadki

cała klasa jest na

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

Czyta plik znak po znaku i odczytuje 1 wiersz na raz do wektora (ciągów znaków), dlatego nadaje się do bardzo dużych plików.

Wykorzystanie jest

Iteruj, aż zostanie zwrócony pusty wiersz (koniec pliku). Wiersz to wektor, w którym każdy wpis jest kolumną 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;
  }
}

deklaracja klasy

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

implementacja

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

Możesz także przyjrzeć się możliwościom Qtbiblioteki.

Obsługuje wyrażenia regularne, a klasa QString ma ładne metody, np. split()Zwraca QStringList, listę ciągów uzyskanych przez podzielenie oryginalnego ciągu za pomocą ogranicznika. Powinien wystarczyć do pliku csv ..

Aby uzyskać kolumnę o podanej nazwie nagłówka, używam: c ++ dziedziczenie Qt problem qstring


to nie poradzi sobie z przecinkami w cytatach
Ezee

1

Jeśli nie chcesz poradzić sobie z włączeniem ulepszenia do swojego projektu (jest on znacznie duży, jeśli zamierzasz go używać tylko do analizy CSV ...)

Miałem szczęście z analizowaniem CSV tutaj:

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

Obsługuje cytowane pola - ale nie obsługuje znaków \ ​​n wbudowanych (co prawdopodobnie jest dobre dla większości zastosowań).


1
Czy kompilator nie powinien usuwać wszystkiego, co nieistotne?
tofutim

1

To jest stary wątek, ale wciąż znajduje się na górze wyników wyszukiwania, więc dodałem moje rozwiązanie za pomocą std :: stringstream i prostej metody zastępowania łańcucha przez Yves Baumes, którą tu znalazłem.

Poniższy przykład przeczyta plik linia po linii, zignoruje linie komentarza zaczynające się od // i parsuje pozostałe linie w kombinację ciągów, liczb całkowitych i podwójnych. Stringstream wykonuje parsowanie, ale oczekuje, że pola będą oddzielone spacjami, więc używam stringreplace, aby najpierw zamienić przecinki w spacje. Obsługuje tabulatory w porządku, ale nie radzi sobie z ciągami cytowanymi.

Złe lub brakujące dane wejściowe są po prostu ignorowane, co może, ale nie musi być dobre, w zależności od okoliczności.

#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

Za to, co jest warte, oto moja implementacja. Zajmuje się wprowadzaniem ciągu, ale można go łatwo dostosować do łańcucha. Nie obsługuje znaku nowej linii w polach (podobnie jak moja aplikacja, ale dodanie jego obsługi nie jest zbyt trudne) i nie jest zgodny z końcem wiersza „\ r \ n” zgodnie z RFC (zakładając, że używasz std :: getline), ale poprawnie obsługuje przycinanie białych znaków i cudzysłowy (miejmy nadzieję).

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

Oto gotowa do użycia funkcja, jeśli wszystko, czego potrzebujesz, to załadować plik danych podwójnych (bez liczb całkowitych, bez tekstu).

#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

Innym szybkim i łatwym sposobem jest użycie 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';
}

Wyjścia:

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

1

Napisałem dobry sposób na parsowanie plików CSV i pomyślałem, że powinienem dodać go jako odpowiedź:

#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

Możliwe jest użycie std::regex.

W zależności od rozmiaru pliku i dostępnej pamięci można go odczytać wiersz po wierszu lub całkowicie w pliku std::string.

Do odczytania pliku można użyć:

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

wtedy możesz dopasować do tego, co faktycznie można dostosować do swoich potrzeb.

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

Ponieważ nie jestem przyzwyczajony do zwiększania teraz, zasugeruję prostsze rozwiązanie. Załóżmy, że plik .csv ma ​​100 linii z 10 liczbami w każdej linii oddzielonymi znakiem „,”. Możesz załadować te dane w postaci tablicy z następującym kodem:

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


}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.