web-dev-qa-db-fra.com

Comment puis-je lire et manipuler des données de fichier CSV en C ++?

Assez explicite, j'ai essayé google et j'ai obtenu beaucoup des échanges d'experts redoutés, j'ai également cherché ici en vain. Un tutoriel ou un exemple en ligne serait préférable. Merci les gars.

51
zkwentz

Si ce que vous faites réellement est de manipuler un fichier CSV lui-même, la réponse de Nelson est logique. Cependant, je soupçonne que le CSV est simplement un artefact du problème que vous résolvez. En C++, cela signifie probablement que vous avez quelque chose comme ça comme modèle de données:

struct Customer {
    int id;
    std::string first_name;
    std::string last_name;
    struct {
        std::string street;
        std::string unit;
    } address;
    char state[2];
    int Zip;
};

Ainsi, lorsque vous travaillez avec une collection de données, il est logique d'avoir std::vector<Customer> Ou std::set<Customer>.

Dans cet esprit, pensez à votre traitement CSV comme deux opérations:

// if you wanted to go nuts, you could use a forward iterator concept for both of these
class CSVReader {
public:
    CSVReader(const std::string &inputFile);
    bool hasNextLine();
    void readNextLine(std::vector<std::string> &fields);
private:
    /* secrets */
};
class CSVWriter {
public:
    CSVWriter(const std::string &outputFile);
    void writeNextLine(const std::vector<std::string> &fields);
private:
    /* more secrets */
};
void readCustomers(CSVReader &reader, std::vector<Customer> &customers);
void writeCustomers(CSVWriter &writer, const std::vector<Customer> &customers);

Lisez et écrivez une seule ligne à la fois, plutôt que de conserver une représentation complète en mémoire du fichier lui-même. Il y a quelques avantages évidents:

  1. Vos données sont représentées sous une forme qui tient compte de votre problème (clients), plutôt que de la solution actuelle (fichiers CSV).
  2. Vous pouvez facilement ajouter des adaptateurs pour d'autres formats de données, tels que l'importation/exportation SQL en bloc, les fichiers de feuille de calcul Excel/OO, ou même un rendu HTML <table>.
  3. Votre empreinte mémoire est probablement plus petite (dépend de la relative sizeof(Customer) par rapport au nombre d'octets sur une seule ligne).
  4. CSVReader et CSVWriter peuvent être réutilisés comme base pour un modèle en mémoire (comme celui de Nelson) sans perte de performances ou de fonctionnalité. L'inverse est pas vrai.
9
Tom

Plus d'informations seraient utiles.

Mais la forme la plus simple:

#include <iostream>
#include <sstream>
#include <fstream>
#include <string>

int main()
{
    std::ifstream  data("plop.csv");

    std::string line;
    while(std::getline(data,line))
    {
        std::stringstream  lineStream(line);
        std::string        cell;
        while(std::getline(lineStream,cell,','))
        {
            // You have a cell!!!!
        }
    }
 }

Voir aussi cette question: analyseur CSV en C++

56
Martin York

Vous pouvez essayer la bibliothèque Boost Tokenizer, en particulier le Escaped List Separator

21

J'ai travaillé avec beaucoup de fichiers CSV de mon temps. Je voudrais ajouter le conseil:

1 - Selon la source (Excel, etc.), des virgules ou des tabulations peuvent être intégrées dans un champ. Habituellement, la règle est qu'ils seront "protégés" car le champ sera délimité par des guillemets doubles, comme dans "Boston, MA 02346".

2 - Certaines sources ne délimitent pas tous les champs de texte. D'autres sources le feront. D'autres délimiteront tous les champs, même numériques.

3 - Les champs contenant des guillemets doubles sont généralement doublés (et le champ lui-même délimité par des guillemets doubles, comme dans "George" "Babe" "Ruth").

4 - Certaines sources intégreront des CR/LF (Excel en fait partie!). Parfois, ce sera juste un CR. Le champ sera généralement délimité par des guillemets doubles, mais cette situation est très difficile à gérer.

8
Marc Bernier

C'est un bon exercice pour vous de travailler :)

Vous devez diviser votre bibliothèque en trois parties

  • Chargement du fichier CSV
  • Représenter le fichier en mémoire pour pouvoir le modifier et le lire
  • Sauvegarde du fichier CSV sur le disque

Vous envisagez donc d'écrire une classe CSVDocument qui contient:

  • Charger (fichier const char *);
  • Enregistrer (fichier const char *);
  • GetBody

Afin que vous puissiez utiliser votre bibliothèque comme ceci:

CSVDocument doc;
doc.Load("file.csv");
CSVDocumentBody* body = doc.GetBody();

CSVDocumentRow* header = body->GetRow(0);
for (int i = 0; i < header->GetFieldCount(); i++)
{
    CSVDocumentField* col = header->GetField(i);
    cout << col->GetText() << "\t";
}

for (int i = 1; i < body->GetRowCount(); i++) // i = 1 so we skip the header
{
    CSVDocumentRow* row = body->GetRow(i);
    for (int p = 0; p < row->GetFieldCount(); p++)
    {
        cout << row->GetField(p)->GetText() << "\t";
    }
    cout << "\n";
}

body->GetRecord(10)->SetText("hello world");

CSVDocumentRow* lastRow = body->AddRow();
lastRow->AddField()->SetText("Hey there");
lastRow->AddField()->SetText("Hey there column 2");

doc->Save("file.csv");

Ce qui nous donne les interfaces suivantes:

class CSVDocument
{
public:
    void Load(const char* file);
    void Save(const char* file);

    CSVDocumentBody* GetBody();
};

class CSVDocumentBody
{
public:
    int GetRowCount();
    CSVDocumentRow* GetRow(int index);
    CSVDocumentRow* AddRow();
};

class CSVDocumentRow
{
public:
    int GetFieldCount();
    CSVDocumentField* GetField(int index);
    CSVDocumentField* AddField(int index);
};

class CSVDocumentField
{
public:
    const char* GetText();
    void GetText(const char* text);
};

Il ne vous reste plus qu'à remplir les blancs d'ici :)

Croyez-moi quand je dis ceci - investir votre temps dans l'apprentissage de la création de bibliothèques, en particulier celles traitant du chargement, de la manipulation et de la sauvegarde des données, vous permettra non seulement de supprimer votre dépendance à l'existence de telles bibliothèques, mais vous fera également un tout- autour d'un meilleur programmeur.

:)

MODIFIER

Je ne sais pas ce que vous savez déjà sur la manipulation et l'analyse de chaînes; donc si vous êtes coincé, je serais heureux de vous aider.

7
user19302

Voici un code que vous pouvez utiliser. Les données du csv sont stockées dans un tableau de lignes. Chaque ligne est un tableau de chaînes. J'espère que cela t'aides.

#include <iostream>
#include <string>
#include <fstream>
#include <sstream>
#include <vector>
typedef std::string String;
typedef std::vector<String> CSVRow;
typedef CSVRow::const_iterator CSVRowCI;
typedef std::vector<CSVRow> CSVDatabase;
typedef CSVDatabase::const_iterator CSVDatabaseCI;
void readCSV(std::istream &input, CSVDatabase &db);
void display(const CSVRow&);
void display(const CSVDatabase&);
int main(){
  std::fstream file("file.csv", std::ios::in);
  if(!file.is_open()){
    std::cout << "File not found!\n";
    return 1;
  }
  CSVDatabase db;
  readCSV(file, db);
  display(db);
}
void readCSV(std::istream &input, CSVDatabase &db){
  String csvLine;
  // read every line from the stream
  while( std::getline(input, csvLine) ){
    std::istringstream csvStream(csvLine);
    CSVRow csvRow;
    String csvCol;
    // read every element from the line that is seperated by commas
    // and put it into the vector or strings
    while( std::getline(csvStream, csvCol, ',') )
      csvRow.Push_back(csvCol);
    db.Push_back(csvRow);
  }
}
void display(const CSVRow& row){
  if(!row.size())
    return;
  CSVRowCI i=row.begin();
  std::cout<<*(i++);
  for(;i != row.end();++i)
    std::cout<<','<<*i;
}
void display(const CSVDatabase& db){
  if(!db.size())
    return;
  CSVDatabaseCI i=db.begin();
  for(; i != db.end(); ++i){
    display(*i);
    std::cout<<std::endl;
  }
}
6
Ashish Jain

Utilisation du tokenizer boost pour analyser les enregistrements , voir ici pour plus de détails .

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

    /// do something with the record
    if (vec.size() < 3) continue;

    copy(vec.begin(), vec.end(),
         ostream_iterator<string>(cout, "|"));

    cout << "\n----------------------" << endl;
}
2
stefanB

Regardez ' The Practice of Programming ' (TPOP) par Kernighan & Pike. Il comprend un exemple d'analyse des fichiers CSV en C et C++. Mais cela vaut la peine de lire le livre même si vous n'utilisez pas le code.

(URL précédente: http://cm.bell-labs.com/cm/cs/tpop/ )

2
Jonathan Leffler

J'ai trouvé cette approche intéressante:

tilitaire de structure CSV en C

Quote: CSVtoC est un programme qui prend un fichier CSV ou des valeurs séparées par des virgules en entrée et le sauvegarde sous forme de structure C.

Naturellement, vous ne pouvez pas apporter de modifications au fichier CSV, mais si vous avez juste besoin d'un accès en lecture seule en mémoire aux données, cela pourrait fonctionner.

0
Kevin P.