Fichier CSV : gestion de données tabulaires

Expression du besoin

De nombreuses applications manipulent des données sous forme de tableau et il est souvent nécessaire des les stocker dans des fichiers. Ces fichiers peuvent être créés ou exploités par des outils externes comme les logiciels de type tableur (Microsoft Excel, LibreOffice/OpenOffice calc, …). Le format CSV répond à ces besoins en étant un format d’échange non propriétaire et extrêmement simple à gérer.

Fichier CSV

Le format CSV (Comma-separated values) est un format informatique ouvert représentant des données tabulaires sous forme de valeurs séparées par un délimiteur (initialement des virgules).

Remarque : Le format CSV n’a jamais vraiment fait l’objet d’une spécification formelle. Toutefois, la RFC 4180 décrit la forme la plus courante et établit son type MIME « text/csv », enregistré auprès de l’IANA.

Les séparateurs ne sont pas standardisés (virgules, points-virgules, etc…) rend ce format peu pratique pour une utilisation autre que des échanges de données ponctuels. Ce format est toutefois très populaire parce qu’il est très facile à générer.

Remarque : de plus en plus de logiciels utilisent le format XML (Extensible Markup Language) pour l’échange de données (voir l’activité sur les fichiers XML).

Les fichiers CSV sont des fichiers texte : ils peuvent donc être manipulés avec un éditeur de texte. Mais les fichiers CSV sont essentiellement utilisés par des logiciels de type tableur (Microsoft Excel, LibreOffice/OpenOffice calc, …).

Chaque ligne du texte correspond à une ligne du tableau et les virgules correspondent aux séparations entre les colonnes. Les portions de texte séparées par une virgule correspondent ainsi aux contenus des cellules du tableau.

Une ligne est une suite ordonnée de caractères terminée par un caractère de fin de ligne (CRLF), la dernière ligne pouvant en être exemptée.

Exemple de fichier CSV :

Sexe,Prénom,Année de naissance
M,Alphonse,1932
F,Béatrice,1964
F,Charlotte,1988

Lorsque l’on ouvre ce type de fichier avec un tableur, celui peut demander à choisir le séparateur à utiliser :

La feuille de calcul que l’on obtient ici :

Remarque : Les champs texte peuvent également être délimités par des guillemets. Lorsqu’un champ contient lui-même des guillemets, ils sont doublés afin de ne pas être considérés comme début ou fin du champ. Si un champ contient un signe utilisé comme séparateur de colonne (virgule, point-virgule, tabulation, etc.) ou comme séparateur de ligne (généralement le caractère de retour à la ligne), les guillemets sont obligatoires afin que ce signe ne soit pas confondu avec un séparateur.

API Qt

Qt ne fournit pas de classe capable de gérer directement des fichiers CSV. On utilisera donc la classe QFile qui permet de manipuler des fichiers.

Objectifs

Être capable de lire et écrire dans un fichier CSV des données tabulaires.

Séquence 1 : écrire un fichier CSV

On va créer une application GUI qui simule une acquisition de mesures de température et qui les affiche dans un tableau. L’application devra être capable d’enregistrer dans un fichier datas.csv ces mesures horodatées au format CSV.

Il est possible de choisir le nombre de mesures à réaliser puis, on lance l’acquisition en cliquant sur le bouton Acquérir :

On utilisera le séparateur ‘;’ et on protégera les données avec des guillemets ‘"’ :

Horodatage;Température °C
"24/11/2015 12:23";"25,12"
"24/11/2015 12:24";"31,37"
"24/11/2015 12:25";"38,63"
"24/11/2015 12:26";"37,34"
"24/11/2015 12:27";"33,93"
"24/11/2015 12:28";"20,19"
"24/11/2015 12:29";"34,28"
"24/11/2015 12:30";"29,42"
"24/11/2015 12:31";"25,99"
"24/11/2015 12:32";"31,82"

Pour manipuler ces mesures dans le programme, on définira cette structure de données :

typedef struct
{
    QString horodatage; // de la forme "dd/MM/yyyy hh:mm"
    double  valeur; // ici une température en °C
} Mesure;

L’objectif est d’écrire dans un fichier CSV lorsqu’on clique sur le bouton Enregistrer.

Pour cela, on utilisera la méthode ecrireCSV() :

bool MaFenetre::ecrireCSV()
{
    // Le fichier CSV
    QFile *fichierCSV = new QFile("datas.csv");

    // Ouverture du fichier en mode texte et en écriture seule
    if (fichierCSV->open(QFile::WriteOnly | QIODevice::Text))
    {
        // Ecriture de l'en-tête
        QTextStream entete(fichierCSV);
        entete << QString::fromUtf8("Horodatage;Température °C") << endl;
    }
    else
    {
        QMessageBox::critical(0,"Erreur !",("Impossible d'ouvrir le fichier datas.csv"));
        delete fichierCSV;

        return false;
    }

    // Ecriture des données
    QTextStream datas(fichierCSV);
    QString data;

    // on récupère toutes les mesures et on les écrit dans le fichier
    int n = 0;
    while ( n < mesures.size() )
    {
        data = mesures[n].horodatage;
        datas << "\"" << data << "\""; // on protège la data avec des "
        datas << ";";
        data = QString::number(mesures[n].valeur);
        //data.sprintf("%.2f", mesures[n].valeur);
        // A la française ?
        data.replace(QString("."), QString(","));
        datas << "\"" << data << "\""; // on protège la data avec des "
        datas << endl;
        ++n;
    }

    // On ferme le fichier
    fichierCSV->close();

    delete fichierCSV;

    return true;
}

Code source complet : test-mo-fichier-csv.zip

Séquence 2 : lire dans un fichier CSV

La méthode lireCSV() s’occupera du chargement et de l’affichage des mesures en provenance d’un fichier CSV :

bool MaFenetre::lireCSV(const QString &fichier)
{
    // Le fichier CSV
    QFile *fichierCSV = new QFile(fichier);

    // Ouverture du fichier en mode texte et en écriture seule
    if (fichierCSV->open(QFile::ReadOnly | QIODevice::Text))
    {
        // nouvelle lecture
        mesures.clear();

        // Lecture des données
        QTextStream datas(fichierCSV);
        QString ligne;
        QString horodatage;
        QString valeur;
        int nb = 0;

        while(!datas.atEnd())
        {
            ligne = datas.readLine();
            // sauf la ligne d'en-tête
            if(nb != 0)
            {
                // traitement des données de la ligne
                // on découpe avec le séparateur ';'
                horodatage = ligne.section(';', 0, 0);
                // on retire les '"'
                horodatage.replace(QString("\""), QString(""));
                // A la française ?
                valeur = ligne.section(';', 1, 1);
                // on retire les '"'
                valeur.replace(QString("\""), QString(""));
                valeur.replace(QString(","), QString("."));
                // on lit la mesure horodatée
                Mesure mesure;
                mesure.horodatage = horodatage;
                mesure.valeur = valeur.toDouble();
                qDebug() << QString::fromUtf8("<MaFenetre::lireCSV()> %1 : %2 °C").arg(mesure.horodatage).arg(QString::number(mesure.valeur));
                // on ajoute la nouvelle mesure lue
                mesures.push_back(mesure);
            }
            ++nb;
        }

        // on met à jour le nombre d'échantillon
        editNbMesures->setValue(mesures.size());
        // on met à jour l'affichage du tableau
        afficher();
    }
    else
    {
        QMessageBox::critical(0, "Erreur !", ("Impossible d'ouvrir le fichier datas.csv"));
        delete fichierCSV;

        return false;
    }

    // On ferme le fichier
    fichierCSV->close();

    delete fichierCSV;

    return true;
}

Le slot charger() permet d’ouvrir le fichier CSV de son choix (cf. QFileDialog::getOpenFileName()) :

void MaFenetre::charger()
{
    QString fichier = QFileDialog::getOpenFileName(NULL, QObject::tr("Ouvrir"), ".", "*.csv");
    if(!fichier.isEmpty())
        lireCSV(fichier);
}

On obtient au final :

Code source complet : test-mo-fichier-csv.zip

Retour au sommaire