Comment puis-je lire et analyser des fichiers CSV en C ++?

264

J'ai besoin de charger et d'utiliser des données de fichier CSV en C ++. À ce stade, il ne peut s'agir que d'un analyseur délimité par des virgules (c'est-à-dire, ne vous inquiétez pas d'échapper aux nouvelles lignes et virgules). Le besoin principal est un analyseur ligne par ligne qui retournera un vecteur pour la ligne suivante à chaque appel de la méthode.

J'ai trouvé cet article qui semble assez prometteur: http://www.boost.org/doc/libs/1_35_0/libs/spirit/example/fundamental/list_parser.cpp

Je n'ai jamais utilisé Boost's Spirit, mais je suis prêt à l'essayer. Mais seulement s'il n'y a pas de solution plus simple que je néglige.

Utilisateur1
la source
11
J'ai regardé l' boost::spiritanalyse. C'est plus pour l'analyse des grammaires que pour l'analyse d'un format de fichier simple. Quelqu'un de mon équipe essayait de l'utiliser pour analyser XML et c'était difficile de déboguer. Éloignez-vous boost::spiritsi possible.
ChrisH
50
Désolé chrish, mais c'est un conseil terrible. Spirit n'est pas toujours une solution appropriée, mais je l'ai utilisée - et continue de l'utiliser - avec succès dans un certain nombre de projets. Comparé à des outils similaires (Antlr, Lex / yacc etc.), il présente des avantages importants. Maintenant, pour analyser CSV, c'est probablement exagéré ...
MattyT
4
@MattyT IMHO spiritest assez difficile à utiliser pour une bibliothèque de combinateurs d'analyseurs. Ayant eu une expérience (très agréable) avec les (atto)parsecbibliothèques Haskells , je m'attendais à ce que (l'esprit) fonctionne de manière similaire, mais j'ai abandonné après avoir combattu avec des erreurs de compilation de 600 lignes.
fho
1
C CSV Parser: sourceforge.net/projects/cccsvparser C CSV Writer: sourceforge.net/projects/cccsvwriter
SomethingSomething

Réponses:

296

Si vous ne vous souciez pas d'échapper à la virgule et à la nouvelle ligne,
ET que vous ne pouvez pas incorporer la virgule et la nouvelle ligne entre guillemets (si vous ne pouvez pas vous échapper alors ...),
alors ce n'est qu'environ trois lignes de code (OK 14 -> Mais sa seulement 15 pour lire l'intégralité du dossier).

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

Je voudrais simplement créer une classe représentant une ligne.
Puis diffusez dans cet objet:

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

Mais avec un peu de travail, nous pourrions techniquement créer un itérateur:

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
la source
20
premier () suivant (). Quel est ce Java! Je plaisante seulement.
Martin York
4
@DarthVader: Une déclaration générale superposée qui, par sa largeur, est idiote. Si vous souhaitez clarifier pourquoi elle est mauvaise et pourquoi cette méchanceté s'applique dans ce contexte.
Martin York
12
@ DarthVader: Je pense qu'il est idiot de faire de larges généralisations. Le code ci-dessus fonctionne correctement, donc je peux voir quoi que ce soit de mal. Mais si vous avez des commentaires spécifiques à faire sur ce qui précède, je les considérerai certainement dans ce contexte. Mais je peux voir comment vous pouvez arriver à cette conclusion en suivant sans réfléchir un ensemble de règles généralisées pour C # et en l'appliquant à un autre langage.
Martin York
5
aussi, si vous rencontrez des problèmes de liaison étranges avec le code ci-dessus parce qu'une autre bibliothèque définit quelque part istream::operator>>(comme Eigen), ajoutez un inlineavant la déclaration de l'opérateur pour le corriger.
sebastian_k
3
Ceci est l'exemple le plus simple et le plus propre de la façon de créer une classe d'itérateur que j'ai jamais vu.
Giancarlo Sportelli
46

Solution utilisant 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
la source
9
Le tokenizer boost ne prend pas entièrement en charge la norme CSV complète, mais il existe quelques solutions rapides. Voir stackoverflow.com/questions/1120140/csv-parser-in-c/…
Rolf Kristensen
3
Devez-vous avoir toute la bibliothèque de boost sur votre machine, ou pouvez-vous simplement utiliser un sous-ensemble de leur code pour ce faire? 256 Mo semble beaucoup pour l'analyse de CSV ..
NPike
6
@NPike: vous pouvez utiliser l' utilitaire bcp fourni avec boost pour extraire uniquement les en-têtes dont vous avez réellement besoin.
ildjarn
46

Ma version n'utilise rien d'autre que la bibliothèque C ++ 11 standard. Il s'adapte bien à la citation Excel CSV:

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

Le code est écrit comme une machine à états finis et consomme un caractère à la fois. Je pense qu'il est plus facile de raisonner.

#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
la source
6
merci, je pense que c'est la réponse la plus complète, dommage qu'elle soit enterrée ici.
mihai
ce vecteur imbriqué de chaînes est un no-go pour les processeurs modernes. Jette leur capacité de mise en cache
Nikolaos Giotis
De plus , vous avez toutes les instructions switch
Nikolaos Giotis
La meilleure réponse n'a pas fonctionné pour moi, car je suis sur un ancien compilateur. Cette réponse a fonctionné, l'initialisation vectorielle peut nécessiter ceci:const char *vinit[] = {""}; vector<string> fields(vinit, end(vinit));
dr_rk
31

La bibliothèque C ++ String Toolkit (StrTk) possède une classe de grille de jetons qui vous permet de charger des données à partir de fichiers texte, de chaînes ou de tampons de caractères , et de les analyser / les traiter de manière ligne-colonne.

Vous pouvez spécifier les délimiteurs de ligne et les délimiteurs de colonne ou simplement utiliser les valeurs par défaut.

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

Plus d'exemples peuvent être trouvés ici

J Mkdjion
la source
1
Bien que strtk prenne en charge les champs entre guillemets doubles et même en options.trim_dquotes = truesupprimant les guillemets environnants (via ), il ne prend pas en charge la suppression des doubles guillemets doubles (par exemple, le champ en "She said ""oh no"", and left."tant que chaîne c "She said \"oh no\", and left."). Vous devrez le faire vous-même.
rampion
1
Lors de l'utilisation strtk, vous devrez également gérer manuellement les champs entre guillemets doubles qui contiennent des caractères de nouvelle ligne.
rampion
29

Vous pouvez utiliser Boost Tokenizer avec escaped_list_separator.

escaped_list_separator analyse un sur-ensemble du csv. Boost :: tokenizer

Cela utilise uniquement les fichiers d'en-tête de tokenizer Boost, aucun lien pour booster les bibliothèques requis.

Voici un exemple (voir Parse CSV File With Boost Tokenizer In C ++ pour plus de détails ou 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
la source
Et si vous voulez pouvoir analyser de nouvelles lignes intégrées mybyteofcode.blogspot.com/2010/11/… .
stefanB
Bien que cette technique fonctionne, j'ai trouvé qu'elle avait de très mauvaises performances. L'analyse d'un fichier CSV de 90000 lignes avec dix champs par ligne prend environ 8 secondes sur mon Xeon 2 GHz. Le module csv de la bibliothèque standard Python analyse le même fichier en environ 0,3 seconde.
Rob Smallshire
@Rob c'est intéressant - qu'est-ce que le csv Python fait différemment?
tofutim
1
@RobSmallshire, c'est un exemple de code simple, pas un code haute performance. Ce code fait des copies de tous les champs par ligne. Pour de meilleures performances, vous utiliseriez différentes options et renverriez uniquement des références aux champs dans le tampon au lieu de faire des copies.
stefanB
29

Il n'est pas exagéré d'utiliser Spirit pour analyser les CSV. Spirit est bien adapté aux tâches de micro-analyse. Par exemple, avec Spirit 2.1, c'est aussi simple que:

bool r = phrase_parse(first, last,

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

    space, v);

Le vecteur, v, est bourré de valeurs. Il existe une série de didacticiels sur ce sujet dans les nouveaux documents Spirit 2.1 qui viennent d'être publiés avec Boost 1.41.

Le didacticiel passe de simple à complexe. Les analyseurs CSV sont présentés quelque part au milieu et abordent diverses techniques d'utilisation de Spirit. Le code généré est aussi serré que du code manuscrit. Découvrez l'assembleur généré!

Joel de Guzman
la source
18
En fait, c'est exagéré, le temps de compilation est énorme et rend déraisonnable l'utilisation de Spirit pour de simples "tâches de micro-analyse".
Gerdiner
13
Je voudrais également souligner que le code ci-dessus n'analyse pas CSV, il analyse simplement une plage du type du vecteur délimité par des virgules. Il ne gère pas les citations, les différents types de colonnes, etc. En bref, 19 votes pour quelque chose qui répond à la question me semble un peu suspect.
Gerdiner
9
@Gerdiner Nonsense. Le temps de compilation atteint pour les petits analyseurs n'est pas si grand, mais il est également hors de propos car vous placez le code dans sa propre unité de compilation et le compilez une fois . Ensuite, il vous suffit de le lier et c'est aussi efficace que possible. Et comme pour votre autre commentaire, il y a autant de dialectes de CSV qu'il y a de processeurs. Celui-ci n'est certainement pas un dialecte très utile, mais il peut être trivialement étendu pour gérer les valeurs citées.
Konrad Rudolph
11
@konrad: Il suffit d'inclure "#include <boost / spirit / include / qi.hpp>" dans un fichier vide avec seulement un principal et rien d'autre ne prend 9.7sec avec MSVC 2012 sur un corei7 fonctionnant à 2.ghz. C'est un ballonnement inutile. La réponse acceptée se compile en moins de 2 secondes sur la même machine, je détesterais imaginer combien de temps l'exemple «approprié» de Boost.Spirit prendrait pour être compilé.
Gerdiner
11
@Gerdiner Je dois convenir avec vous que les frais généraux liés à l'utilisation de l'esprit pour quelque chose d'aussi simple que le traitement des CV sont bien trop importants.
18

Si vous VOULEZ se soucier de l' analyse syntaxique correctement CSV, ce fera ... relativement lentement car il fonctionne un omble chevalier à la fois.

 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
la source
AFAICT cela ne gérera pas correctement les guillemets intégrés (par exemple "Cette chaîne a" "des guillemets intégrés" "", "foo", 1))
Jeremy Friesner
14

Lors de l'utilisation du Boost Tokenizer escaped_list_separator pour les fichiers CSV, alors il faut être conscient des points suivants:

  1. Il nécessite un caractère d'échappement (barre oblique inverse par défaut - \)
  2. Il nécessite un caractère séparateur / séparateur (virgule par défaut -,)
  3. Il nécessite un guillemet (citation par défaut - ")

Le format CSV spécifié par wiki indique que les champs de données peuvent contenir des séparateurs entre guillemets (pris en charge):

1997, Ford, E350, "Super, camion de luxe"

Le format CSV spécifié par wiki stipule que les guillemets simples doivent être traités avec des guillemets doubles (escaped_list_separator supprimera tous les caractères de guillemet):

1997, Ford, E350, "Super" "luxueux" "camion"

Le format CSV ne spécifie pas que tous les caractères de barre oblique inverse doivent être supprimés (escaped_list_separator supprimera tous les caractères d'échappement).

Une solution de contournement possible pour corriger le comportement par défaut du boost escaped_list_separator:

  1. Remplacez d'abord tous les caractères de barre oblique inverse (\) par deux caractères de barre oblique inverse (\\) afin qu'ils ne soient pas supprimés.
  2. Remplacez ensuite tous les guillemets doubles ("") par un seul caractère barre oblique inverse et un guillemet (\ ")

Cette solution de contournement a pour effet secondaire que les champs de données vides représentés par un guillemet double seront transformés en jeton de guillemet simple. Lors de l'itération à travers les jetons, il faut alors vérifier si le jeton est un guillemet simple et le traiter comme une chaîne vide.

Pas joli mais ça marche, tant qu'il n'y a pas de retour à la ligne dans les guillemets.

Rolf Kristensen
la source
8

Vous voudrez peut-être regarder mon projet FOSS CSVfix ( lien mis à jour ), qui est un éditeur de flux CSV écrit en C ++. L'analyseur CSV n'est pas un prix, mais fait le travail et l'ensemble du package peut faire ce dont vous avez besoin sans avoir à écrire de code.

Voir alib / src / a_csv.cpp pour l'analyseur CSV et csvlib / src / csved_ioman.cpp ( IOManager::ReadCSV) pour un exemple d'utilisation.

cxw
la source
Ça a l'air génial ... Et le statut beta / production?
neuro
Le statut est "en développement", comme le suggèrent les numéros de version. J'ai vraiment besoin de plus de commentaires des utilisateurs avant de passer à la version 1.0. De plus, j'ai quelques fonctionnalités supplémentaires à ajouter, concernant la production XML à partir de CSV.
Le mettre en signet, et je vais l'essayer la prochaine fois que je devrai gérer ces merveilleux fichiers CSV standard ...
neuro
8

Comme toutes les questions CSV semblent être redirigées ici, j'ai pensé publier ma réponse ici. Cette réponse ne répond pas directement à la question du demandeur. Je voulais pouvoir lire dans un flux qui est connu pour être au format CSV, et aussi les types de chaque champ étaient déjà connus. Bien sûr, la méthode ci-dessous peut être utilisée pour traiter chaque champ comme un type de chaîne.

Comme exemple de la façon dont je voulais pouvoir utiliser un flux d'entrée CSV, considérez l'entrée suivante (tirée de la page de wikipedia sur 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"
;

Ensuite, je voulais pouvoir lire les données comme ceci:

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

C'est la solution que j'ai trouvée.

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

Avec les assistants suivants qui peuvent être simplifiés par les nouveaux modèles de traits intégraux en C ++ 11:

template <typename T> struct is_signed_int { enum { val = false }; };
template <> struct is_signed_int<short> { enum { val = true}; };
template <> struct is_signed_int<int> { enum { val = true}; };
template <> struct is_signed_int<long> { enum { val = true}; };
template <> struct is_signed_int<long long> { enum { val = true}; };

template <typename T> struct is_unsigned_int { enum { val = false }; };
template <> struct is_unsigned_int<unsigned short> { enum { val = true}; };
template <> struct is_unsigned_int<unsigned int> { enum { val = true}; };
template <> struct is_unsigned_int<unsigned long> { enum { val = true}; };
template <> struct is_unsigned_int<unsigned long long> { enum { val = true}; };

template <typename T> struct is_int {
    enum { val = (is_signed_int<T>::val || is_unsigned_int<T>::val) };
};

Essayez-le en ligne!

jxh
la source
6

J'ai écrit un analyseur CSV C ++ 11 uniquement en-tête . Il est bien testé, rapide, prend en charge l'intégralité de la spécification CSV (champs entre guillemets, délimiteur / terminateur entre guillemets, échappement de guillemets, etc.) et est configurable pour tenir compte des CSV qui n'adhèrent pas à la spécification.

La configuration se fait via une interface fluide:

// constructor accepts any input stream
CsvParser parser = CsvParser(std::cin)
  .delimiter(';')    // delimited by ; instead of ,
  .quote('\'')       // quoted fields use ' instead of "
  .terminator('\0'); // terminated by \0 instead of by \r\n, \n, or \r

L'analyse n'est qu'une plage basée sur une boucle:

#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
la source
1
Beau travail, mais vous devez ajouter trois autres choses: (1) lire l'en-tête (2) fournir des champs d'indexation par nom (3) ne pas réallouer la mémoire en boucle en réutilisant le même vecteur de chaînes
Maksym Ganenko
@MaksymGanenko je fais # 3. Pourriez-vous élaborer sur # 2?
m0meni
1
Il est très utile d'obtenir des champs non pas par position dans une ligne, mais par nom donné dans l'en-tête (dans la première ligne du tableau CSV). Par exemple, je m'attends à une table CSV avec le champ "Date", mais je ne sais pas ce qu'est l'index du champ "Date" dans une rangée.
Maksym Ganenko
1
@MaksymGanenko ah je vois ce que tu veux dire. Il y a github.com/ben-strasser/fast-cpp-csv-parser pour quand vous connaissez les colonnes de votre CSV au moment de la compilation, et c'est probablement mieux que le mien. Ce que je voulais, c'était un analyseur CSV pour les cas où vous vouliez utiliser le même code pour de nombreux CSV différents et ne savez pas à quoi ils ressemblent à l'avance. Donc je n'ajouterai probablement pas # 2, mais j'ajouterai # 1 dans le futur.
m0meni
5

Une autre bibliothèque d'E / S CSV peut être trouvée ici:

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
la source
2
Bien, mais cela vous oblige à choisir le nombre de colonnes au moment de la compilation. Pas très utile pour de nombreuses applications.
quant_dev
5

Une autre solution similaire à la réponse de Loki Astari , en C ++ 11. Les lignes ici sont des std::tuples d'un type donné. Le code scanne une ligne, puis scanne jusqu'à chaque délimiteur, puis convertit et sauvegarde la valeur directement dans le tuple (avec un peu de code de modèle).

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

Advanges:

  • assez propre et simple à utiliser, uniquement C ++ 11.
  • conversion automatique de type en std::tuple<t1, ...>via operator>>.

Ce qui manque:

  • s'échapper et citer
  • aucune gestion des erreurs en cas de CSV mal formé.

Le code principal:

#include <iterator>
#include <sstream>
#include <string>

namespace csvtools {
    /// Read the last element of the tuple without calling recursively
    template <std::size_t idx, class... fields>
    typename std::enable_if<idx >= std::tuple_size<std::tuple<fields...>>::value - 1>::type
    read_tuple(std::istream &in, std::tuple<fields...> &out, const char delimiter) {
        std::string cell;
        std::getline(in, cell, delimiter);
        std::stringstream cell_stream(cell);
        cell_stream >> std::get<idx>(out);
    }

    /// Read the @p idx-th element of the tuple and then calls itself with @p idx + 1 to
    /// read the next element of the tuple. Automatically falls in the previous case when
    /// reaches the last element of the tuple thanks to enable_if
    template <std::size_t idx, class... fields>
    typename std::enable_if<idx < std::tuple_size<std::tuple<fields...>>::value - 1>::type
    read_tuple(std::istream &in, std::tuple<fields...> &out, const char delimiter) {
        std::string cell;
        std::getline(in, cell, delimiter);
        std::stringstream cell_stream(cell);
        cell_stream >> std::get<idx>(out);
        read_tuple<idx + 1, fields...>(in, out, delimiter);
    }
}

/// Iterable csv wrapper around a stream. @p fields the list of types that form up a row.
template <class... fields>
class csv {
    std::istream &_in;
    const char _delim;
public:
    typedef std::tuple<fields...> value_type;
    class iterator;

    /// Construct from a stream.
    inline csv(std::istream &in, const char delim) : _in(in), _delim(delim) {}

    /// Status of the underlying stream
    /// @{
    inline bool good() const {
        return _in.good();
    }
    inline const std::istream &underlying_stream() const {
        return _in;
    }
    /// @}

    inline iterator begin();
    inline iterator end();
private:

    /// Reads a line into a stringstream, and then reads the line into a tuple, that is returned
    inline value_type read_row() {
        std::string line;
        std::getline(_in, line);
        std::stringstream line_stream(line);
        std::tuple<fields...> retval;
        csvtools::read_tuple<0, fields...>(line_stream, retval, _delim);
        return retval;
    }
};

/// Iterator; just calls recursively @ref csv::read_row and stores the result.
template <class... fields>
class csv<fields...>::iterator {
    csv::value_type _row;
    csv *_parent;
public:
    typedef std::input_iterator_tag iterator_category;
    typedef csv::value_type         value_type;
    typedef std::size_t             difference_type;
    typedef csv::value_type *       pointer;
    typedef csv::value_type &       reference;

    /// Construct an empty/end iterator
    inline iterator() : _parent(nullptr) {}
    /// Construct an iterator at the beginning of the @p parent csv object.
    inline iterator(csv &parent) : _parent(parent.good() ? &parent : nullptr) {
        ++(*this);
    }

    /// Read one row, if possible. Set to end if parent is not good anymore.
    inline iterator &operator++() {
        if (_parent != nullptr) {
            _row = _parent->read_row();
            if (!_parent->good()) {
                _parent = nullptr;
            }
        }
        return *this;
    }

    inline iterator operator++(int) {
        iterator copy = *this;
        ++(*this);
        return copy;
    }

    inline csv::value_type const &operator*() const {
        return _row;
    }

    inline csv::value_type const *operator->() const {
        return &_row;
    }

    bool operator==(iterator const &other) {
        return (this == &other) or (_parent == nullptr and other._parent == nullptr);
    }
    bool operator!=(iterator const &other) {
        return not (*this == other);
    }
};

template <class... fields>
typename csv<fields...>::iterator csv<fields...>::begin() {
    return iterator(*this);
}

template <class... fields>
typename csv<fields...>::iterator csv<fields...>::end() {
    return iterator();
}

J'ai mis un petit exemple de travail sur GitHub ; Je l'ai utilisé pour analyser certaines données numériques et il a rempli son rôle.

Spak
la source
1
Vous pouvez ne pas vous soucier de l'inlining, car la plupart des compilateurs le décident d'eux-mêmes. Au moins, je suis sûr en Visual C ++. Il peut incorporer une méthode indépendamment de vos spécifications de méthode.
MrPisarik
1
C'est précisément pourquoi je les ai marqués explicitement. Gcc et Clang, ceux que j'utilise principalement, ont également leurs propres conventions. Un mot clé "en ligne" ne devrait être qu'une incitation.
Spak
4

Voici une autre implémentation d'un analyseur CSV Unicode (fonctionne avec wchar_t). J'en ai écrit une partie, tandis que Jonathan Leffler a écrit le reste.

Remarque: cet analyseur vise à reproduire le comportement d'Excel le plus fidèlement possible, en particulier lors de l'importation de fichiers CSV cassés ou mal formés .

C'est la question d'origine - Analyser un fichier CSV avec des champs multilignes et des guillemets doubles échappés

Il s'agit du code en tant que SSCCE (exemple court, autonome et correct).

#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
la source
3

J'avais besoin d'une bibliothèque C ++ facile à utiliser pour analyser les fichiers CSV mais je n'en ai trouvé aucune disponible, j'ai donc fini par en créer une. Rapidcsv est une bibliothèque uniquement en-tête C ++ 11 qui donne un accès direct aux colonnes (ou lignes) analysées en tant que vecteurs, dans le type de données de choix. Par exemple:

#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
la source
1
Beau travail, mais la bibliothèque ne fonctionne pas correctement si l'en-tête a des étiquettes vides. C'est typique de la table Excel / LibreOffice NxN. En outre, il peut ignorer la dernière ligne de données. Malheureusement, votre bibliothèque n'est pas robuste.
Maksym Ganenko
1
Merci pour les commentaires @MaksymGanenko J'ai corrigé le bogue "dernière ligne de données" pour les lignes finales sans rupture de ligne de fin. Quant à l'autre problème mentionné - "en-têtes avec des étiquettes vides" - je ne sais pas à quoi il se réfère? La bibliothèque doit gérer les étiquettes vides (à la fois entre guillemets et sans guillemets). Il peut également lire CSV sans ligne / colonne d'en-tête, mais il requiert ensuite que l'utilisateur le spécifie (id de titre de colonne -1 et id de titre de ligne -1). Veuillez fournir plus de détails ou signaler un bug sur la page GitHub si vous avez un cas d'utilisation spécifique que vous souhaitez voir pris en charge. Merci!
d99kris
2

Excusez-moi, mais tout cela ressemble à beaucoup de syntaxe élaborée pour cacher quelques lignes de code.

Pourquoi pas ça:

/**

  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
la source
Euh, pourquoi y aurait-il ",\n"dans la chaîne?
Timmmm
@Timmmm recherche la méthode substr de la classe String, et vous verrez qu'elle prend plusieurs caractères, \ n est le caractère de nouvelle ligne, donc il compte comme un seul caractère, dans ce cas. Il ne recherche pas la valeur entière dans son ensemble. C'est la recherche de chaque personnage individuel; à savoir la virgule ou la nouvelle ligne. substr retournera la position du premier caractère qu'il trouve, et -1 s'il ne trouve ni l'un ni l'autre, ce qui signifie qu'il a fini de lire la ligne. fp garde une trace de la position dans le fichier en interne, donc chaque appel à readCSV le déplace d'une ligne à la fois.
Martyn Shutt
2

Voici le code pour lire une matrice, notez que vous avez également une fonction csvwrite dans matlab

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

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

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

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

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

        std::cout << std::endl;
    }
}
Jim M.
la source
2

Vous pouvez ouvrir et lire le fichier .csv à l'aide des fonctions fopen, fscanf, mais l'important est d'analyser les données. La manière la plus simple d'analyser les données à l'aide du délimiteur. Dans le cas de .csv, le délimiteur est ','.

Supposons que votre fichier data1.csv soit le suivant:

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

vous pouvez symboliser les données et les stocker dans un tableau de caractères, puis utiliser la fonction atoi (), etc. pour les conversions appropriées

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 inverse la logique, signifie correspondre à n'importe quelle chaîne qui ne contient pas de virgule puis en dernier, dit de faire correspondre la virgule qui a terminé la chaîne précédente.

Amruta Ghodke
la source
2

La première chose que vous devez faire est de vous assurer que le fichier existe. Pour ce faire, il vous suffit d'essayer d'ouvrir le flux de fichiers sur le chemin. Après avoir ouvert le flux de fichiers, utilisez stream.fail () pour voir s'il a fonctionné comme prévu ou non.

bool fileExists(string fileName)
{

ifstream test;

test.open(fileName.c_str());

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

Vous devez également vérifier que le fichier fourni est le bon type de fichier. Pour ce faire, vous devez parcourir le chemin de fichier fourni jusqu'à ce que vous trouviez l'extension de fichier. Une fois que vous avez l'extension de fichier, assurez-vous qu'il s'agit d'un fichier .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;
}

Cette fonction renverra l'extension de fichier qui sera utilisée plus tard dans un message d'erreur.

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

Cette fonction appellera en fait les contrôles d'erreur créés ci-dessus, puis analysera le fichier.

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;
    }
}
Carte Elizabeth
la source
2

Tu dois être fier quand tu utilises quelque chose d'aussi beau que boost::spirit

Voici ma tentative d'un analyseur (presque) conforme aux spécifications CSV sur ce lien Spécifications CSV (je n'ai pas eu besoin de sauts de ligne dans les champs. Les espaces autour des virgules sont également supprimés).

Après avoir surmonté l'expérience choquante d'attendre 10 secondes pour compiler ce code :), vous pouvez vous asseoir et profiter.

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

Compiler:

make csvparser

Test (exemple volé sur Wikipedia ):

./csvparser
Enter CSV lines [empty] to quit

1999,Chevy,"Venture ""Extended Edition, Very Large""",,5000.00
Parsing succeeded
field 1: 1999
field 2: Chevy
field 3: Venture "Extended Edition, Very Large"
field 4: 
field 5: 5000.00

1999,Chevy,"Venture ""Extended Edition, Very Large""",,5000.00"
Parsing failed
jav
la source
2

Cette solution détecte ces 4 cas

la classe complète est à

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

Il lit le fichier caractère par caractère et lit 1 ligne à la fois dans un vecteur (de chaînes), donc adapté aux très gros fichiers.

L'utilisation est

Itérer jusqu'à ce qu'une ligne vide soit renvoyée (fin de fichier). Une ligne est un vecteur où chaque entrée est une colonne CSV.

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

la déclaration de classe

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

la mise en oeuvre

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
la source
1

Vous pouvez également consulter les capacités de la Qtbibliothèque.

Il prend en charge les expressions régulières et la classe QString a de belles méthodes, par exemple split()renvoyer QStringList, liste des chaînes obtenues en divisant la chaîne d'origine avec un délimiteur fourni. Devrait suffire pour le fichier csv ..

Pour obtenir une colonne avec un nom d'en-tête donné, j'utilise ce qui suit: héritage c ++ Qt problème qstring

MadH
la source
cela ne gérera pas les virgules entre guillemets
Ezee
1

Si vous ne voulez pas gérer l'inclusion de boost dans votre projet (il est considérablement important si vous ne l'utilisez que pour l'analyse par CSV ...)

J'ai eu de la chance avec l'analyse CSV ici:

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

Il gère les champs entre guillemets - mais ne gère pas les caractères en ligne \ n (ce qui est probablement bien pour la plupart des utilisations).

NPike
la source
1
Le compilateur ne devrait-il pas supprimer tout ce qui n'est pas essentiel?
tofutim
1

Ceci est un vieux fil mais il est toujours en haut des résultats de recherche, donc j'ajoute ma solution en utilisant std :: stringstream et une méthode de remplacement de chaîne simple par Yves Baumes que j'ai trouvée ici.

L'exemple suivant lira un fichier ligne par ligne, ignorera les lignes de commentaires commençant par // et analysera les autres lignes en une combinaison de chaînes, d'entiers et de doubles. Stringstream effectue l'analyse, mais s'attend à ce que les champs soient délimités par des espaces, donc j'utilise stringreplace pour transformer les virgules en espaces en premier. Il gère les onglets, mais ne traite pas les chaînes entre guillemets.

Les entrées incorrectes ou manquantes sont simplement ignorées, ce qui peut être bon ou non, selon votre situation.

#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
la source
1

Pour ce que ça vaut, voici mon implémentation. Il traite de l'entrée wstring, mais pourrait être ajusté pour une chaîne facilement. Il ne gère pas la nouvelle ligne dans les champs (comme mon application non plus, mais l'ajout de son support n'est pas trop difficile) et il n'est pas conforme à la fin de ligne "\ r \ n" selon RFC (en supposant que vous utilisez std :: getline), mais il gère correctement la suppression des espaces blancs et les guillemets doubles (espérons-le).

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
la source
1

Voici une fonction prête à l'emploi si tout ce dont vous avez besoin est de charger un fichier de données de doubles (pas d'entiers, pas de texte).

#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
la source
1

Un autre moyen rapide et facile consiste à utiliser 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';
}

Les sorties:

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

J'ai écrit une belle façon d'analyser les fichiers CSV et j'ai pensé que je devrais l'ajouter comme réponse:

#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
la source
1

Il est possible d'utiliser std::regex.

Selon la taille de votre fichier et la mémoire dont vous disposez, il est possible de le lire ligne par ligne ou entièrement dans un fichier std::string.

Pour lire le fichier, on peut utiliser:

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

alors vous pouvez faire correspondre ce qui est en fait personnalisable à vos besoins.

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
la source
1

Comme je n'ai pas l'habitude de booster pour le moment, je proposerai une solution plus simple. Supposons que votre fichier .csv comporte 100 lignes avec 10 numéros dans chaque ligne séparés par un «,». Vous pouvez charger ces données sous la forme d'un tableau avec le code suivant:

#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
la source