Extraction d'informations depuis un fichier de données en C

Franck Hecht

Dans certains projets, il peut être nécessaire d'extraire des données depuis un fichier. Que ce soit une liste de contacts, des données pour remplir des tableaux ou pour des tableurs, des options utilisateurs, etc. Ce tutoriel va vous expliquer les bases pour lire différents formats et en extraire les données.

Commentez cet article : Commentez ♪

- Titre : Extraction d'informations depuis un fichier de données en C

- Auteur : Franck Hecht

- Parution : 1 décembre 2009

- Mise à jour : 4 décembre 2009

- Licence : Copyright ® 2009 Franck Hecht. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- II. Prérequis

- III. Traitement d'un fichier avec une structure personnalisée

- III-1. Étude algorithmique

- III-2. Implémentation

- III-2-1. Fonction : get_infos

- III-2-2. Fonction : get

- III-3. Code source complet

- IV. Traitement d'un fichier de type INI

- IV-1. Étude algorithmique

- IV-2. Implémentation

- IV-2-1. Fonction : get

- IV-2-2. Fonction : get_string

- IV-2-3. Fonction : get_integer

- IV-3. Code source complet

- V. Traitement d'un fichier de type CSV

- V-1. Fonction : get_contact

- V-2. Code source complet

- VI. Résumé

- VII. Remerciements

I - Introduction

Dans certains projets, il peut être nécessaire d'extraire des données depuis un fichier. Que ce soit une liste de contacts, des données pour remplir des tableaux ou pour des tableurs, des options utilisateurs, etc. Ce tutoriel va vous expliquer les bases pour lire différents formats et en extraire les données.

Dans un premier temps (Chapitre III), nous allons voir comment créer un fichier avec une structure appropriée au stockage d'une liste de contacts pour par exemple, un programme de gestion de contacts. Dans le Chapitre IV, nous verrons comment lire des fichiers de type INI et en extraire les données suivant leur type (entiers, chaînes de caractères). Nous finirons par le format CSV au Chapitre V.

Le format XML (eXtended Markup Langage) ne sera pas traité ici, un tutoriel lui est entièrement consacré dans le tutoriel suivant : Utilisation de la bibliothèque libxml2 en C

II - Prérequis

Quelques prérequis sont indispensables pour les chapitres à suivre. Des fonctions que tout le monde devrait avoir constamment sous le coude, voici celles indispensables pour ce tutoriel et qui traitent des chaînes de caractères :

Fonction : str_dup

static char * str_dup (const char * str)
{
char * dup = NULL;

if (str != NULL)
{
size_t size = strlen (str) + 1;
dup = malloc (size);

if (dup != NULL)
{
memcpy (dup, str, size);
}
}

return dup;
}

Cette fonction permet de copier simplement une chaîne de caractères. Le nouveau pointeur retourné par la fonction doit explicitement être libéré avec la fonction standard free.

La seconde fonction indispensable dans ce tutoriel permet de terminer une chaîne de caractères avec un zéro de fin de chaîne, ce qui n'est pas toujours le cas surtout dans des chaînes extraites d'un fichier :

Fonction : str_finalize

static void str_finalize (char * str)
{
char * p = strchr (str, '\n');

if (p != NULL)
{
*p = 0;
}
}

Cette fonction recherche un éventuel saut de ligne et le remplace par un zéro terminal. Les sauts de ligne sont surtout présents lorsqu'on récupère des lignes de texte à partir d'un fichier, mais les fonctions standards attendent un zéro de fin de chaîne.

III - Traitement d'un fichier avec une structure personnalisée

Dans ce premier chapitre, nous allons voir comment récupérer des informations dans un fichier avec une structure de données personnalisée soit, la structure suivante :

{
name:Franck.H
age:32
address:16 rue du General de Gaulle, 67000 Strasbourg
phone:0618325015
}

Dans ce type de structure, les accolades délimitent un bloc de données, ce qui nous permet donc de déterminer à quel moment dans le code, il faut commencer à tester et récupérer les informations. Le caractère ':' sert de séparateur entre une donnée et son nom de champ dans le fichier, on pourrait aussi mettre par exemple le caractère '=' qui est plus souvent utilisé dans ce cas de figure.

III-1 - Étude algorithmique

Il faut pour commencer, déterminer un algorithme et donc un cheminement permettant de partir de l'ouverture d'un bloc de données jusqu'à dans notre cas, la recherche du nom d'un contact puis de récupérer ses données personnelles. Cela vaut pour tout autre programme. Voici un algorithme possible :

Ouvrir fichier
Tant que ligne <> NIL lire ligne fichier
Si caractère 0 de ligne égal à '{'
alors ouvrir bloc
sinon si bloc ouvert
Si recherche label égal à "name"
alors
Récupérer l'âge
Récupérer l'adresse
Récupérer le téléphone
Fsi
Fsi
Fin tant que
Fermer fichier

Voici une brève explication du cheminement.

1. On ouvre le fichier à lire.

2. On lit le fichier ligne par ligne par le biais d'une boucle Tant que(while) tant qu'on ne se trouve pas à la fin du fichier.

3. On teste sur chaque ligne et tant qu'un bloc n'a pas été ouvert, si le premier caractère de la chaîne est un caractère '{'

4. Si un bloc n'a pas encore été ouvert et que le premier caractère est '{', on ouvre le bloc en initialisant une variable d'état (généralement de type booléen, mais un type entier convient très bien aussi). On saute ensuite à la prochaine ligne.

5. Si un bloc a déjà été ouvert, on recherche le nom du contact à retrouver en commençant par rechercher l'enregistrement portant l'identifiant « name ». Si cet identifiant a été trouvé, on compare le nom qui le suit avec le nom à retrouver.

6. Si le contact a été retrouvé, on récupère une à une ses informations.

7. Fermeture du fichier.

III-2 - Implémentation

Attelons-nous maintenant à l'écriture du code en C. Il nous faut pour commencer, inclure les fichiers d'en-tête adéquats :

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

Nous devons maintenant préparer diverses parties pour pouvoir développer notre futur code. Nous allons plutôt utiliser des directives du préprocesseur pour définir quelques constantes symboliques ce qui permettra une plus grande facilité dans la maintenance future du code (il faut toujours penser à la maintenance du code) et aussi, rendra le code plus lisible.

#define BEGIN        '{'
#define SEP          ':'
#define BUF_SIZE     128
#define FILENAME     "perso.txt"

#define TAG_NAME     "name"
#define TAG_AGE      "age"
#define TAG_ADRESS   "address"
#define TAG_PHONE    "phone"

Les constantes commençant par « TAG_ » sont en fait les différents champs qu'on retrouve dans un bloc de données du fichier. La constante BUF_SIZE permet de définir la limite d'un tableau de caractères donc le nombre maximum de caractères que peut contenir une chaîne après la lecture.

III-2-1 - Fonction : get_infos

contact_infos * get_info (const char * contact_name);

Voici la fonction principale qui va faire presque tout le travail, elle retourne une structure de type contact_infos dont voici la définition :

typedef struct
{
char *   name;
char *   adress;
char *   phone;
int      age;
}
contact_infos;

Et prend comme unique paramètre le nom du contact à retrouver. Voici l'implémentation de la fonction :

contact_infos * get_info (const char * contact_name)
{
contact_infos  *  ret;
FILE           *  file     = NULL;
char              buff     [BUF_SIZE];
int               opened   = 0;

file = fopen (FILENAME, "r");                      /* 1.- */

if (file != NULL)
{
while ((fgets (buff, BUF_SIZE, file) != NULL))   /* 2.- */
{
if (buff[0] == BEGIN && !opened)              /* 3.- */
{
opened = 1;                               /* 4.- */
}
else if (opened)
{
char * s = strstr (buff, TAG_NAME);        /* 5.- */

if (s != NULL)
{
s += strlen (TAG_NAME) + 1;
str_finalize (s);

if (strcmp (s, contact_name) == 0)     /* 5.- */
{
ret = malloc (sizeof (* ret));

if (ret != NULL)
{
ret->name = str_dup (s);

get (ret, file, AGE);            /* 6.- */
get (ret, file, ADDRESS);        /* 6.- */
get (ret, file, PHONE);          /* 6.- */
}

break;
}
else
{
opened = 0;
}
}
}
}

fclose (file);                                  /* 7.- */
}

return ret;
}

On peut revoir grâce à la numérotation en commentaire, les principales actions de l'explication du cheminement du chapitre sur l'étude algorithmique. J'ai parlé durant cette étude d'un entier qui peut servir de drapeau booléen pour déterminer l'ouverture d'un bloc de données, c'est ce que fait la variable opened. On peut voir dans la partie 5 du code, la recherche du tag name. Juste après, dans la condition if, on décale le pointeur sur la longueur du tag plus 1 caractère pour passer après le séparateur de champs ':' donc au début du nom du contact.

S'en suit la comparaison entre le nom du contact retrouvé et celui passé en argument à la fonction. Si c'est la bonne personne, on alloue dynamiquement un espace pour la structure que doit renvoyer la fonction et on la remplit par le biais d'une seconde fonction nommée get que nous allons voir juste après.

On peut apercevoir l'utilisation de constantes en troisième argument de cette seconde fonction, voici l'énumération correspondante qui servira à récupérer la suite des informations du contact :

typedef enum
{
NAME,
AGE,
ADDRESS,
PHONE,

NB_TAG
}
contact_tags;

III-2-2 - Fonction : get

void get (contact_infos * p, FILE * file, contact_tags tag);

Cette fonction permet de terminer la récupération des informations restantes d'un contact. Ses trois arguments sont dans l'ordre :

- un pointeur valide sur la structure que doit renvoyer la fonction get_infos ;

- un pointeur valide sur le fichier en cours de lecture ;

- le tag de type contact_tags correspondant à l'information que doit récupérer la fonction.

Voici l'implémentation de la fonction :

get (contact_infos * p, FILE * file, contact_tags tag)
{
char     buff  [BUF_SIZE];
char *   s     = NULL;

if ((fgets (buff, BUF_SIZE, file)) != NULL)      /* 1.- */
{
s = strchr (buff, SEP);                       /* 2.- */

if (s != NULL)
{
s++;                                      /* 3.- */
str_finalize (s);                         /* 4.- */

switch (tag)                              /* 5.- */
{
case AGE:
{
p->age = strtol (s, NULL, 10);
}
break;

case ADDRESS:
{
p->adress = str_dup (s);
}
break;

case PHONE:
{
p->phone = str_dup (s);
}
break;

default:
break;
}
}
}
}

À chaque appel de la fonction, on lit la ligne suivante (1) du fichier. À partir de la ligne récupérée, on recherche (2) la position du séparateur de champs. Si le séparateur a été trouvé, on décale le pointeur de 1 caractère (3) pour se retrouver au début de la donnée à récupérer. On finalise enfin la chaîne (4) afin d'y ajouter un caractère de fin de chaîne en lieu et place du saut de ligne. Nous pouvons maintenant récupérer la donnée courante (5) suivant le tag passé en argument.

Vous pouvez remarquer la conversion de l'âge en entier lors de la récupération de cette donnée. Ici l'ordre de récupération des données est immuable, mais il est tout à fait possible de créer une fonction plus souple, mais qui demande davantage de temps et de lignes de code.

III-3 - Code source complet

Voici le code source complet avec en plus le contenu du fichier perso.txt et la sortie sur la console. Dans le code vous pourrez apercevoir les fonctions décrites jusque là et une fonction contact_infos_free qui permet de libérer la mémoire allouée dynamiquement pour la structure contact_infos.

Fichier : perso.txt

{
name:Pascal Antoine
age:25
address:10 rue du General Leclerc, 67000 Strasbourg
phone:0622325418
}

{
name:Franck.H
age:32
address:16 rue du General de Gaulle, 67000 Strasbourg
phone:0618325015
}

{
name:Alain DuPont
age:21
address:120 Bld Clemenceau, 67000 Strasbourg
phone:0615256978
}

Fichier : main.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define BEGIN        '{'
#define SEP          ':'
#define BUF_SIZE     128
#define FILENAME     "perso.txt"

#define TAG_NAME     "name"
#define TAG_AGE      "age"
#define TAG_ADRESS   "address"
#define TAG_PHONE    "phone"

typedef struct
{
char *   name;
char *   adress;
char *   phone;
int      age;
}
contact_infos;

typedef enum
{
NAME,
AGE,
ADDRESS,
PHONE,

NB_TAG
}
contact_tags;

static char * str_dup (const char * str)
{
char * dup = NULL;

if (str != NULL)
{
size_t size = strlen (str) + 1;
dup = malloc (size);

if (dup != NULL)
{
memcpy (dup, str, size);
}
}

return dup;
}

static void str_finalize (char * str)
{
char * p = strchr (str, '\n');

if (p != NULL)
{
*p = 0;
}
}

static void get (contact_infos * p, FILE * file, contact_tags tag)
{
char     buff  [BUF_SIZE];
char *   s     = NULL;

fgets (buff, BUF_SIZE, file);

s = strchr (buff, SEP);

if (s != NULL)
{
s++;
str_finalize (s);

switch (tag)
{
case AGE:
{
p->age = strtol (s, NULL, 10);
}
break;

case ADDRESS:
{
p->adress = str_dup (s);
}
break;

case PHONE:
{
p->phone = str_dup (s);
}
break;

default:
break;
}
}
}

static contact_infos * get_info (const char * contact_name)
{
contact_infos  *  ret;
FILE           *  file     = NULL;
char              buff     [BUF_SIZE];
int               opened   = 0;

file = fopen (FILENAME, "r");

if (file != NULL)
{
while ((fgets (buff, BUF_SIZE, file) != NULL))
{
if (buff[0] == BEGIN && !opened)
{
opened = 1;
}
else if (opened)
{
char * s = strstr (buff, TAG_NAME);

if (s != NULL)
{
s += strlen (TAG_NAME) + 1;
str_finalize (s);

if (strcmp (s, contact_name) == 0)
{
ret = malloc (sizeof (* ret));

if (ret != NULL)
{
ret->name = str_dup (s);

get (ret, file, AGE);
get (ret, file, ADDRESS);
get (ret, file, PHONE);
}

break;
}
else
{
opened = 0;
}
}
}
}

fclose (file);
}

return ret;
}

static void contact_infos_free (contact_infos ** p)
{
if (p != NULL && *p != NULL)
{
free ((*p)->name);
free ((*p)->adress);
free ((*p)->phone);

free (*p);
*p = NULL;
}
}

int main (void)
{
contact_infos * info = get_info ("Franck.H");

printf (
"%s\n%d\n%s\n%s\n\n",
info->name, info->âge, info->adress, info->phone
);

contact_infos_free (& info);

return EXIT_SUCCESS;
}

Sortie sur la console

Franck.H
32
16 rue du General de Gaulle, 67000 Strasbourg
0618325015

IV - Traitement d'un fichier de type INI

Dans ce chapitre nous allons étudier un format plus commun, un fichier de type *.ini comme ceux qu'on trouve sur le système Windows ou encore dans le même esprit, les fichier *.conf qu'on peut trouver sur les Unixoïdes. Ce type de fichier est le plus souvent utilisé pour stocker les préférences utilisateurs d'un programme. Voici l'exemple sur lequel nous allons travailler :

[user]
alias=Franck.H
name=HECHT Franck

[server]
server=192.160.1.10
port=147

[files]
backup=backup.dat
log=log.dat
error=error.dat

IV-1 - Étude algorithmique

Comme le chapitre précédent et tout code digne de ce nom, il faut établir un algorithme au moins minimaliste avec les étapes principales qui serviront de fil rouge au code qu'on produira par la suite :

Ouvrir fichier
Tant que ligne <> NIL lire ligne fichier
Si section non ouverte et caractère 0 de ligne égal à '['
Test de la chaîne entre crochets

Si section égal OK
Ouvrir section
Fsi
Sinon si section ouverte
Si caractère 0 de ligne égal à '['
Fermeture section
Sortie de la boucle
Fsi

Recherche de la clé

Si clé trouvée
Récupération donnée
Sortie de la boucle
Fsi
Fsi
Fin tant que
Fermer fichier

Le principe reste assez simple ! Nous lisons le fichier ligne par ligne, il faut tester le premier caractère de la ligne courante si la section n'a pas encore été ouverte/trouvée, ceci permet de détecter une section. Si la section a été trouvée et ouverte, on commence par tester la même condition qu'au départ dans le cas où on se trouverait en dehors de la section où il faut chercher les informations. Nous recherchons ensuite simplement la clé puis on en extrait la donnée.

IV-2 - Implémentation

Nous allons découper le code en plusieurs fonctions comme dans le chapitre précédent, une fonction nommée get qui servira à lire un enregistrement et qui ne sera pas visible directement par l'utilisateur et pour notre exemple, une fonction get_string (qui va récupérer une chaîne) et get_integer (qui servira à récupérer une valeur entière).

Voici le nécessaire pour commencer (ajoutez également les deux fonctions fournies dans les prérequis) :

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define FILENAME     "conf.ini"
#define BUF_SIZE     128

#define BEGIN        '['
#define SEP          '='

Ceci est le début du code, soit les fichiers d'en-tête ainsi que quelques constantes symboliques nécessaires à la suite du code. On précise ici tout de suite le nom du fichier par la constante FILENAME, on ajoute à cela la constante BEGIN qui symbolise le début d'une section et le séparateur par la constante SEP.

IV-2-1 - Fonction : get

char * get (const char * filename, const char * section, const char * key)
{
char *   ret         = NULL;
char     buff        [BUF_SIZE];
FILE *   file        = NULL;
int      opened      = 0;

file = fopen (filename, "r");                                       /* 1.- */

if (file != NULL)
{
while ((fgets (buff, BUF_SIZE, file)) != NULL)                   /* 2.- */
{
str_finalize (buff);

if (! opened && buff [0] == BEGIN)                            /* 3.- */
{
char * p = buff;

if (memcmp (p + 1, section, strlen (buff) - 2) == 0)       /* 4.- */
{
opened = 1;
continue;
}
}
else if (opened)
{
if (buff [0] == BEGIN)                                     /* 5.- */
{
opened = 0;
break;
}

if (strstr (buff, key) != NULL)                            /* 6.- */
{
char * p = strchr (buff, SEP);                          /* 7.- */

if (p != NULL)
{
p++;
ret = str_dup (p);                                   /* 8.- */

break;
}
}
}
}

fclose (file);
}

return ret;
}

Cette fonction prend trois arguments

1. Le nom du fichier INI ;

2. Le nom de la section ;

3. Le nom de la clé d'enregistrement.

Comme d'habitude, on ouvre le fichier passé en argument (1), on lit le fichier ligne par ligne avec une boucle while et avec la fonction fgets. Avant toute autre opération sur la chaîne récupérée, on remplace le saut de ligne qui est automatiquement lu et ajouté par la fonction fgets, par un caractère de fin de chaîne. On teste si la section n'est pas déjà ouverte et en même temps, si le premier caractère est '['(3). Si la condition est vraie, on compare la chaîne (en l'occurrence le nom de la section courante) avec celle passée en argument (4). Si c'est la bonne section, on l'ouvre puis on boucle directement sur la ligne suivante, sinon on continue à chercher la bonne section.

Dans le second cas où la section est déjà ouverte, on commence par tester si le premier caractère de la chaîne n'est pas un début de section. Si tel est le cas, nous avons dépassé la section ouverte sans trouver la clé passée en argument, on quitte alors la boucle et la fonction renvoie NULL. On recherche ensuite avec la fonction strstr(6) la clé dans la chaîne courante, si la clé est trouvée, on recherche la position du séparateur (7) avec la fonction strchr. Si le séparateur est trouvé, on déplace la position du pointeur de 1 caractère pour se trouver sur le premier caractère de la valeur à récupérer puis on copie la chaîne (8) dont le pointeur sera retourné par la fonction.

IV-2-2 - Fonction : get_string

char * get_string (const char * section, const char * key)
{
return get (FILENAME, section, key);
}

Rien de bien compliqué dans cette fonction, elle renvoie simplement la chaîne retournée par la fonction get.

IV-2-3 - Fonction : get_integer

int get_integer (const char * section, const char * key)
{
int      ret   = 0;
char *   s     = NULL;

s = get (FILENAME, section, key);         /* 1.- */

if (s != NULL)
{
ret = (int) strtol (s, NULL, 10);      /* 2.- */
free (s);                              /* 3.- */
}

return ret;
}

La fonction get_integer permet de renvoyer une valeur entière, ceci est possible grâce à la conversion de la chaîne renvoyée (1) par get. Si le pointeur retourné est différent de NULL, on tente une conversion avec la fonction strtol(2) puis on libère la chaîne (3) qui je le rappelle, est copiée par allocation dynamique.

IV-3 - Code source complet

Fichier : conf.ini

[user]
alias=Franck.H
name=HECHT Franck

[server]
server=192.160.1.10
port=147

[files]
backup=backup.dat
log=log.dat
error=error.dat

Fichier : main.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define FILENAME     "conf.ini"
#define BUF_SIZE     128

#define BEGIN        '['
#define SEP          '='

static char * str_dup (const char * str)
{
char * dup = NULL;

if (str != NULL)
{
size_t size = strlen (str) + 1;
dup = malloc (size);

if (dup != NULL)
{
memcpy (dup, str, size);
}
}

return dup;
}

static void str_finalize (char * str)
{
char * p = strchr (str, '\n');

if (p != NULL)
{
*p = 0;
}
}

static char * get (const char * filename, const char * section, const char * key)
{
char *   ret         = NULL;
char     buff        [BUF_SIZE];
FILE *   file        = NULL;
int      opened      = 0;

file = fopen (filename, "r");

if (file != NULL)
{
while ((fgets (buff, BUF_SIZE, file)) != NULL)
{
str_finalize (buff);

if (! opened && buff [0] == BEGIN)
{
char * p = buff;

if (memcmp (p + 1, section, strlen (buff) - 2) == 0)
{
opened = 1;
continue;
}
}
else if (opened)
{
if (buff [0] == BEGIN)
{
opened = 0;
break;
}

if (strstr (buff, key) != NULL)
{
char * p = strchr (buff, SEP);

if (p != NULL)
{
p++;
ret = str_dup (p);

break;
}
}
}
}

fclose (file);
}

return ret;
}

static char * get_string (const char * section, const char * key)
{
return get (FILENAME, section, key);
}

static int get_integer (const char * section, const char * key)
{
int      ret   = 0;
char *   s     = NULL;

s = get (FILENAME, section, key);

if (s != NULL)
{
ret = (int) strtol (s, NULL, 10);
free (s);
}

return ret;
}

int main (void)
{
char *   s  = NULL;
int      i  = 0;

s = get_string ("user", "alias");
printf ("%s\n", s);
free (s);

s = get_string ("server", "server");
printf ("%s\n", s);
free (s);

i = get_integer ("server", "port");
printf ("%d\n", i);

return EXIT_SUCCESS;
}

Sortie sur la console

Franck.H
192.160.1.10
147

V - Traitement d'un fichier de type CSV

Un fichier CSV (Comma-separated values) est un fichier représentant des données tabulaires. Chaque valeur est délimitée par une virgule ou point-virgule (suivant la localisation ou le programme qui génère le fichier). Si vous ne connaissez pas ou pas trop ce type de fichier, vous pouvez en savoir plus en suivant ce lien. Ici nous allons voir la forme la plus simple de ce type, mais il en existe plusieurs formes, voici notre exemple :

Robert;Dupont;12 rue du verger;Strasbourg;67000

Le code étant assez simple à mettre en œuvre, nous n'allons pas passer par une étude algorithmique, mais nous allons nous plonger directement dans le vif du sujet !

V-1 - Fonction : get_contact

contact_infos * get_contact (const char * filename)
{
contact_infos  *  infos = NULL;
FILE           *  file  = NULL;
char           *  token = NULL;
char              buff [BUFF_SIZE];

file = fopen (filename, "r");                            /* 1.- */

if (file != NULL)
{
if ((fgets (buff, BUFF_SIZE, file)) != NULL)          /* 2.- */
{
char *   p = buff;
int      i = 0;

infos = malloc (sizeof (* infos));                 /* 3.- */

if (infos != NULL)
{
while ((token = strtok (p, DELIM)) != NULL)     /* 4.- */
{
if (i == 0)
p = NULL;                                 /* 5.- */

switch (i)                                   /* 6.- */
{
case NOM:
infos->nom = str_dup (token);
break;

case PRENOM:
infos->prenom = str_dup (token);
break;

case ADRESSE:
infos->adresse = str_dup (token);
break;

case VILLE:
infos->ville = str_dup (token);
break;

case CODE:
{
int code = strtol (token, NULL, 10);
infos->code = code;
}
break;

default:
break;
}

i++;
}
}
}

fclose (file);
}

return infos;
}

Dans notre exemple, cette fonction ne lit que la première ligne du fichier passé en argument, mais avec quelques modifications, il est tout à fait possible de lui faire rechercher un contact précis !

La fonction retourne une structure dans le même genre que celle que nous avons utilisée dans le Chapitre III :

typedef struct
{
char *   nom;
char *   prenom;
char *   adresse;
char *   ville;
int      code;
}
contact_infos;

Les constantes utilisées dans l'instruction switch sont définies ainsi :

typedef enum
{
NOM,        /* commence a l'index O par defaut */
PRENOM,
ADRESSE,
VILLE,
CODE,

NB_TYPE
}
infos_type;

Voyons le déroulement de la fonction :

On commence comme d'habitude par ouvrir notre fichier (1), ici celui dont le nom a été passé en argument. On lit la première ligne du fichier (2), si la ligne a été lue avec succès, on alloue un espace pour notre structure (3). Après le test d'allocation, nous utilisons la fonction strtok pour extraire les informations (4), parlons-en un peu de cette fonction…

strtok permet d'extraire des mots d'une chaîne (éléments lexicaux) en lui fournissant une suite de délimiteurs. Il faut cependant être très prudent avec cette fonction, car elle modifie la chaîne passée en argument. Après chaque mot trouvé, elle met un caractère '\0' après le délimiteur, si vous devez donc réutiliser la chaîne plus tard, il est conseillé de travailler sur une copie de la chaîne ! Vous pouvez voir dans le code ci-dessus (5) que nous mettons le pointeur p, qui pointe sur la chaîne, à la valeur NULL. Ceci pour la simple et bonne raison que cet argument doit être à cette valeur (c'est pour cette raison également que nous utilisons ce pointeur, car on ne peut mettre un tableau statique à NULL) pour les appels suivants, sinon la fonction recommence au début de la chaîne.

On termine ensuite par une simple instruction switch pour récupérer les informations une à une (6), à chaque mot (token ou élément lexical) trouvé correspond donc une information.

V-2 - Code source complet

Fichier : contacts.csv

Robert;Dupont;12 rue du verger;Strasbourg;67000

Fichier : main.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define DELIM        ";"
#define BUFF_SIZE    128

typedef struct
{
char *   nom;
char *   prenom;
char *   adresse;
char *   ville;
int      code;
}
contact_infos;

typedef enum
{
NOM,
PRENOM,
ADRESSE,
VILLE,
CODE,

NB_TYPE
}
infos_type;

static char * str_dup (const char * str)
{
char * dup = NULL;

if (str != NULL)
{
size_t size = strlen (str) + 1;
dup = malloc (size);

if (dup != NULL)
{
memcpy (dup, str, size);
}
}

return dup;
}

static contact_infos * get_contact (const char * filename)
{
contact_infos  *  infos = NULL;
FILE           *  file  = NULL;
char           *  token = NULL;
char              buff [BUFF_SIZE];

file = fopen (filename, "r");

if (file != NULL)
{
if ((fgets (buff, BUFF_SIZE, file)) != NULL)
{
char *   p = buff;
int      i = 0;

infos = malloc (sizeof (* infos));

if (infos != NULL)
{
while ((token = strtok (p, DELIM)) != NULL)
{
if (i == 0)
p = NULL;

switch (i)
{
case NOM:
infos->nom = str_dup (token);
break;

case PRENOM:
infos->prenom = str_dup (token);
break;

case ADRESSE:
infos->adresse = str_dup (token);
break;

case VILLE:
infos->ville = str_dup (token);
break;

case CODE:
{
int code = strtol (token, NULL, 10);
infos->code = code;
}
break;

default:
break;
}

i++;
}
}
}

fclose (file);
}

return infos;
}

static void free_contact_infos (contact_infos ** p)
{
if (p != NULL && *p != NULL)
{
free ((*p)->nom);
free ((*p)->prenom);
free ((*p)->adresse);
free ((*p)->ville);

free (*p);
*p = NULL;
}
}

int main (void)
{
contact_infos * infos = get_contact ("contacts.csv");

if (infos != NULL)
{
printf (
"Nom complet : %s %s\nAdresse : %s\nVille : %d %s\n\n",
infos->nom, infos->prenom, infos->adresse, infos->code, infos->ville
);

free_contact_infos (& infos);
}

return EXIT_SUCCESS;
}

Sortie sur la console

Nom complet : Robert Dupont
Adresse : 12 rue du verger
Ville : 67000 Strasbourg

VI - Résumé

Nous voilà arrivés au terme de ce tutoriel ! Nous avons vu comment extraire des données depuis un fichier avec une structure bien définie. Vous aurez pu juger qu'il reste assez facile avec un peu de réflexion, de créer puis gérer une structure de fichier personnalisée comme nous l'avons vu dans le chapitre III. Nous avons continué par l'étude d'un type de fichier plus connu et commun, les fichiers INI puis un exemple sur le format CSV. Les principes décrits dans ce tutoriel s'appliquent également à d'autres types de fichiers et peu importe le type des données à extraire.

VII - Remerciements

Un article ne se faisant jamais seul, je remercie BiM pour la relecture et correction ainsi que gorgonite et diogene pour leurs suggestions !