Traitement des fichiers CSV en C#
Le format CSV (Comma-Separated Values) est le standard le plus universel pour échanger des données tabulaires entre bases de données, tableurs (Excel) et pipelines de données (Data Pipelines).
Dans ce chapitre, nous allons voir comment générer un fichier CSV propre et comment lire, parser et stocker des données tabulaires en mémoire dans des tableaux de structures en C.
1. Structure et anatomie d'un fichier CSV#
Un fichier CSV est un simple fichier texte respectant les conventions suivantes :
1. Chaque ligne représente un enregistrement (record).
2. Chaque colonne représente un champ (field), séparé par un délimiteur (généralement la virgule , ou le point-virgule ; dans les environnements francophones).
3. La première ligne contient habituellement l'en-tête (header) précisant les noms de colonnes.
id,nom,filiere,moyenne
101,Alice Dubois,Data Science,16.50
102,Bob Martin,Software Eng,14.20
103,Chloe Diallo,Cybersecurity,18.00
2. Écriture et export de données au format CSV#
Générer un fichier CSV en C est particulièrement intuitif grâce à fprintf(). Il suffit d'écrire chaque champ suivi du séparateur et de conclure chaque enregistrement par un retour à la ligne \n.
#include <stdio.h>
#include <stdlib.h>
typedef struct
{
int id;
char nom[50];
char filiere[30];
float moyenne;
} Etudiant;
void ExporterCSV(const char *nom_fichier, const Etudiant *promo, int nb_etudiants)
{
FILE *fichier = fopen(nom_fichier, "w");
if (fichier == NULL)
{
perror("Impossible de creer le fichier CSV");
return;
}
// 1. Écriture de la ligne d'en-tête
fprintf(fichier, "id,nom,filiere,moyenne\n");
// 2. Écriture des enregistrements
for (int i = 0; i < nb_etudiants; i++)
{
fprintf(fichier, "%d,%s,%s,%.2f\n",
promo[i].id,
promo[i].nom,
promo[i].filiere,
promo[i].moyenne);
}
fclose(fichier);
printf("Exportation terminee dans '%s'.\n", nom_fichier);
}
3. Parsing de fichiers CSV en C#
Pour lire et découper les lignes d'un fichier CSV, deux approches complémentaires existent :
1. L'approche par découpage de jetons (strtok) : très flexible et robuste pour les champs de longueur variable.
2. L'approche par masque de saisie (sscanf) : très concise si les formats de chaque colonne sont parfaitement fixes et prévisibles.
Approche 1 : Découpage avec strtok()#
La fonction char *strtok(char *str, const char *delim) de <string.h> découpe une chaîne en jetons (tokens) successifs en remplaçant les délimiteurs trouvés par le caractère '\0'.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void ParserLigneStrtok(char *ligne)
{
// Découpage du premier champ
char *token = strtok(ligne, ",\r\n");
int colonne = 0;
while (token != NULL)
{
printf(" Col %d : %s\n", colonne, token);
// Les appels suivants nécessitent NULL comme premier argument
token = strtok(NULL, ",\r\n");
colonne++;
}
}
[!WARNING] La fonction
strtok()modifie directement la chaîne de caractères passée en paramètre en y insérant des'\0'. De plus,strtok()utilise un pointeur statique interne et n'est donc pas réentrante (not thread-safe). En environnement multi-thread, privilégiezstrtok_r().
Approche 2 : Extraction directe avec sscanf() et masques#
Le spécificateur d'ensemble de recherche (scanset) %[^,] permet de capturer tous les caractères jusqu'à la prochaine virgule :
Etudiant e;
char ligne[] = "101,Alice Dubois,Data Science,16.50\n";
int lus = sscanf(ligne, "%d,%49[^,],%29[^,],%f",
&e.id, e.nom, e.filiere, &e.moyenne);
if (lus == 4)
{
printf("Lu : %s (%.2f)\n", e.nom, e.moyenne);
}
4. Étude de cas complète : Ingestion et analyse d'un CSV#
Concevons un programme complet qui :
1. Crée un jeu de données CSV sur disque.
2. Lit et parse le fichier ligne par ligne pour peupler un tableau dynamique d'étudiants.
3. Ignore proprement la ligne d'en-tête.
4. Calcule des indicateurs statistiques (moyenne générale, major de promotion).
5. Exporte un nouveau fichier admis.csv contenant uniquement les étudiants ayant la moyenne (\(\ge 10\)).
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_LIGNE 256
#define CAPACITE_MAX 50
typedef struct
{
int id;
char nom[50];
char filiere[30];
float moyenne;
} Etudiant;
// Écrit un fichier CSV de démonstration
void GenererFichierExemple(const char *chemin)
{
FILE *f = fopen(chemin, "w");
if (!f) return;
fprintf(f, "id,nom,filiere,moyenne\n");
fprintf(f, "101,Amina Traore,Big Data,15.75\n");
fprintf(f, "102,Kevin Lefebvre,Software,08.50\n");
fprintf(f, "103,Fatou Ndiaye,Data Science,17.20\n");
fprintf(f, "104,Marc Dupont,Cybersecurity,09.80\n");
fprintf(f, "105,Sara Gomez,DevOps,14.00\n");
fclose(f);
}
// Lit et parse le fichier CSV dans un tableau de structures
int ChargerEtudiants(const char *chemin, Etudiant promo[], int max_capacite)
{
FILE *fichier = fopen(chemin, "r");
if (fichier == NULL)
{
perror("Erreur lors de la lecture du CSV");
return -1;
}
char tampon[MAX_LIGNE];
int compteur = 0;
// 1. Ignorer la première ligne (en-tête)
if (fgets(tampon, sizeof(tampon), fichier) == NULL)
{
fclose(fichier);
return 0; // Fichier vide
}
// 2. Parcourir chaque ligne de données
while (fgets(tampon, sizeof(tampon), fichier) != NULL && compteur < max_capacite)
{
// Nettoyer les sauts de ligne
tampon[strcspn(tampon, "\r\n")] = '\0';
// Ligne vide éventuelle
if (strlen(tampon) == 0) continue;
Etudiant *e = &promo[compteur];
int assignes = sscanf(tampon, "%d,%49[^,],%29[^,],%f",
&e->id, e->nom, e->filiere, &e->moyenne);
if (assignes == 4)
{
compteur++;
}
else
{
fprintf(stderr, "Avertissement : ligne malformee ignoree : %s\n", tampon);
}
}
fclose(fichier);
return compteur;
}
// Exporte les étudiants admis (moyenne >= 10)
void FiltrerAdmis(const char *chemin_sortie, const Etudiant promo[], int nb_etudiants)
{
FILE *f = fopen(chemin_sortie, "w");
if (!f)
{
perror("Impossible de creer le fichier des admis");
return;
}
fprintf(f, "id,nom,filiere,moyenne\n");
for (int i = 0; i < nb_etudiants; i++)
{
if (promo[i].moyenne >= 10.0f)
{
fprintf(f, "%d,%s,%s,%.2f\n",
promo[i].id, promo[i].nom, promo[i].filiere, promo[i].moyenne);
}
}
fclose(f);
}
int main(void)
{
const char *fichier_source = "promotions.csv";
const char *fichier_admis = "admis.csv";
// 1. Initialisation des données
GenererFichierExemple(fichier_source);
// 2. Chargement en mémoire
Etudiant promotion[CAPACITE_MAX];
int total = ChargerEtudiants(fichier_source, promotion, CAPACITE_MAX);
if (total <= 0)
{
printf("Aucun etudiant charge.\n");
return EXIT_FAILURE;
}
printf(">>> %d etudiants charges avec succes depuis %s\n\n", total, fichier_source);
// 3. Calculs statistiques
float somme_notes = 0.0f;
int index_major = 0;
printf("%-5s | %-18s | %-15s | %s\n", "ID", "Nom", "Filiere", "Moyenne");
printf("------------------------------------------------------\n");
for (int i = 0; i < total; i++)
{
printf("%-5d | %-18s | %-15s | %05.2f\n",
promotion[i].id, promotion[i].nom, promotion[i].filiere, promotion[i].moyenne);
somme_notes += promotion[i].moyenne;
if (promotion[i].moyenne > promotion[index_major].moyenne)
{
index_major = i;
}
}
printf("------------------------------------------------------\n");
printf("Moyenne generale de la promotion : %.2f / 20\n", somme_notes / total);
printf("Major de promo : %s (%.2f / 20)\n\n",
promotion[index_major].nom, promotion[index_major].moyenne);
// 4. Export du sous-ensemble
FiltrerAdmis(fichier_admis, promotion, total);
printf(">>> Fichier des etudiants admis exporte : '%s'\n", fichier_admis);
return EXIT_SUCCESS;
}
5. Synthèse des bonnes pratiques#
| Problématique rencontrée | Solution recommandée |
|---|---|
Séparateurs variables (ex: , vs ;) |
Vérifier la première ligne pour détecter le délimiteur avant de parser. |
| Lignes blanches en fin de fichier | Vérifier la longueur de chaîne après fgets() et ignorer si strlen(tampon) == 0. |
| Dépassement de capacité mémoire | Définir une taille maximale de tampon et restreindre les masques sscanf("%49[^,]"). |
Caractère \r sur fichiers Windows |
Toujours nettoyer le retour chariot avec strcspn(str, "\r\n") = '\0'. |
6. Défi pratique#
Étendez le programme d'analyse pour calculer et afficher la moyenne par filière (Data Science, Software, DevOps), puis exportez un fichier statistiques_filieres.csv contenant les colonnes : filiere,effectif,moyenne.
Commentaires