Parser un fichier xml

Signaler
Messages postés
393
Date d'inscription
lundi 22 juin 2009
Statut
Membre
Dernière intervention
23 décembre 2011
-
Messages postés
393
Date d'inscription
lundi 22 juin 2009
Statut
Membre
Dernière intervention
23 décembre 2011
-
Bonsoir,

Je voulais parser mon fichier XML pouyr extraire certaines informations pour les utiliser dans mon programme C.

J'ai trouvé la librairie libxml2 qui est codée en C et qui possède deux méthodes DOM et SAX.

J'ai bien installé libxml2 et j'ai décidé de travailler avec la méthode SAX qui permet de parser des fichiers XML assez grand et n'est pas gourmande en mémoire.

J'ai trouvé des exemples en général et non simples pour un débutant sur le site officiel de libxml2. Ces exemples sont plus pour la méthode DOM. Je ne trouve pas un exemple pour SAX.

- Est ce que il y a quelqu'un qui a rencontré ce type de problème ?

- Pouvez vous me donner un exemple typique pour que j'applique sur mon fichier XML car j'ai besoin des informations qui se trouvent dans le dernier noeud de mon fichier XML ?

Merci.

208 réponses

Messages postés
17286
Date d'inscription
mercredi 2 janvier 2002
Statut
Modérateur
Dernière intervention
23 décembre 2019
69
comme je te l'ai dit, en C, y'a pas de bool ...

comme dans mon code, changes le en int...

gaffe, j'ai oublié de faire un
free(s);

de mon malloc...


Renfield - Admin CodeS-SourceS - MVP Visual Basic
Messages postés
17286
Date d'inscription
mercredi 2 janvier 2002
Statut
Modérateur
Dernière intervention
23 décembre 2019
69
Allez, je te redonne le code, commenté... ça te donnera une idée précise du fonctionnement...
(codé et testé avec succès sous Visual Studio 2008 Express)

#include <stdlib.h>
#include <string.h>
#include <libxml/parser.h>
#pragma comment(lib,"libxml2.lib")

int _numberCpt = 0; // Permet de compter les occurrences de <Concept>
int _mustMem = 0;   // Si != 0, le CData du noeud doit être mémorisé (ici, pour l'exemple, on l'écrit dans la console)

void startElement(void *user_data, 	const xmlChar *name, const xmlChar **attrs){
int i;
// Si on est sur un Lattice...
if (!xmlStrcmp(name, (xmlChar*)"Lattice") && attrs) {
// On parcoure les attributs
for (i = 0; attrs[i]; i+=2) {
// Jusqu'à trouver l'atribut numberCpt
            if (!xmlStrcmp(attrs[i], (xmlChar*)"numberCpt")) {
// Que l'on mémorise (strtol permet d'analyser la valeur texte, pour en faire une valeur numérique)
_numberCpt = strtol((const char*)attrs[i+1],NULL,0);
break; // inutile de considérer les attributs suivants
            }
        }
}
else if (!xmlStrcmp(name, (xmlChar*)"Concept") && _numberCpt)
_numberCpt--; // On décrémente notre compteur à chaque <Concept> rencontré
else if (!xmlStrcmp(name, (xmlChar*)"Attribute_Ref") && !_numberCpt)
_mustMem = 1; // On souhaite mémoriser les Attribute_Ref (_numberCpt valant ici 0, nous sommes bien dans le dernier <Concept>)
}

void characters(void* user_data, const xmlChar *ch, int len) {
if (_mustMem && len) // Si on doit mémoriser la valeur
{
// On va l'extraire dans un buffer
char* s = (char *)malloc(sizeof(char)*len+1);
*(s+len) = 0; // On place un \0 terminal de la chaine. Cette instruction génèrera un warning, mais on ne prend pas vraiment de risque ici.
strncpy(s,(const char*)ch,len);
// Et l'afficher dans la console
printf("%s\n", s);

free(s);
_mustMem = 0; // Le prochain CData ne sera pas à mémoriser
}
}

int main()
{
// structure permettant de renseigner les handlers perso à invoquer pour nos deux evenements de lecture
    xmlSAXHandler sh = { 0 };
sh.startElement = startElement;
    sh.characters = characters;

// On appelle l'enalyse du fichier XML
if(xmlSAXUserParseFile(&sh, NULL, "D:\\Documents and Settings\\THOM31R\\Bureau\\libxml2-2.7.3.win32\\ParseSample\\ParseSample\\Input.xml")) {
printf("Un erreur est survenue. Le fichier XML est peut-être mal consitué");
        return -1;
}

system ("pause");
return 0;
}




Renfield - Admin CodeS-SourceS - MVP Visual Basic
Messages postés
393
Date d'inscription
lundi 22 juin 2009
Statut
Membre
Dernière intervention
23 décembre 2011
5
Bonjour,

Je vous remercie pour votre gentillesse.
Votre solution çà marche chez moi.
il m'affiche dans la console:
d
b
c
a

Je voulais stocker "d b c a" comme une chaine de caractère dans un fichier "resultat.txt car j'ai besoin de cette chaine de caractère dans le reste de mon programme.
c'est à dire nous allons toujours un fichier ""resultat.txt qui contient une seule ligne contenant une chaine de caractère formé par les chaines de caractères qui sont extraites par votre solution
Dans notre cas, on obtient le fichier "resultat.txt contenant:
"d b c a"

si on teste votre solution sur un autre fichier xml qui contient dans le dernier :
<Concept>
11
<Extent>
</Extent>

lundi
mardi
mercredi
jeudi


<Concept_Ref>10</Concept_Ref>
<Concept_Ref>8</Concept_Ref>
<Concept_Ref>9</Concept_Ref>
<Concept_Ref>7</Concept_Ref>

</Concept>

Dans ce cas, on obtient le fichier "resultat.txt contenant:
"lundi mardi mercredi jeudi"
Dans le reste du mon travail, j'ai besoin de cette chaine de caractère "d b c a" par exemple.

Que ce que il va changer ou ajouter dans votre solution pour obtenir ce fichier "resultat.txt ?

Merci.
Messages postés
17286
Date d'inscription
mercredi 2 janvier 2002
Statut
Modérateur
Dernière intervention
23 décembre 2019
69
libre à toi de faire ce que tu veux avec le texte lu....

suffit de changer le comportement ici:

printf("%s\n", s)

Renfield - Admin CodeS-SourceS - MVP Visual Basic
Messages postés
17286
Date d'inscription
mercredi 2 janvier 2002
Statut
Modérateur
Dernière intervention
23 décembre 2019
69
mais si le but est de t'en servir dans ton soft, pourquoi passer par un fichier ? pourquoi ne pas garder ces résultats en mémoire ?


Renfield - Admin CodeS-SourceS - MVP Visual Basic
Messages postés
17286
Date d'inscription
mercredi 2 janvier 2002
Statut
Modérateur
Dernière intervention
23 décembre 2019
69
Allez, je te le code, mais va falloir commencer à coder un peu, non ?
#include <stdlib.h>
#include <string.h>
#include <libxml/parser.h>
#pragma comment(lib,"libxml2.lib")

int _numberCpt = 0;   // Permet de compter les occurrences de <Concept>
int _mustMem = 0;     // Si != 0, le CData du noeud doit être mémorisé (ici, pour l'exemple, on l'écrit dans la console)
char* _resBuffer = 0; // va stocker nos donnees (évite n accès a un fichier texte
int _resCursor = 0;   // curseur d'ecriture dans notre buffer
int _resSize = 0;     // taille du buffer

void startElement(void *user_data, 	const xmlChar *name, const xmlChar **attrs){
int i;
// Si on est sur un Lattice...
if (!xmlStrcmp(name, (xmlChar*)"Lattice") && attrs) {
// On parcoure les attributs
for (i = 0; attrs[i]; i+=2) {
// Jusqu'à trouver l'atribut numberCpt
            if (!xmlStrcmp(attrs[i], (xmlChar*)"numberCpt")) {
// Que l'on mémorise (strtol permet d'analyser la valeur texte, pour en faire une valeur numérique)
_numberCpt = strtol((const char*)attrs[i+1],NULL,0);
break; // inutile de considérer les attributs suivants
            }
        }
}
else if (!xmlStrcmp(name, (xmlChar*)"Concept") && _numberCpt)
_numberCpt--; // On décrémente notre compteur à chaque <Concept> rencontré
else if (!xmlStrcmp(name, (xmlChar*)"Attribute_Ref") && !_numberCpt)
_mustMem = 1; // On souhaite mémoriser les Attribute_Ref (_numberCpt valant ici 0, nous sommes bien dans le dernier <Concept>)
}

void characters(void* user_data, const xmlChar *ch, int len) {
if (_mustMem && len) // Si on doit mémoriser la valeur
{
if (_resCursor+len+1>_resSize)
{
_resSize += 0xFF; // on agrandit le buffer par blocs de 255 octets.
if (_resBuffer)
_resBuffer = realloc(_resBuffer, _resSize);
else
_resBuffer = malloc(_resSize);
}

if (_resCursor)
*((_resCursor++)+_resBuffer) = ' '; // caractère séparateur de deux items.

// On va l'extraire dans un buffer
strncpy(_resBuffer+_resCursor,(const char*)ch,len);

_resCursor+=len;
*(_resCursor+_resBuffer) = 0; // caractère terminal \0

_mustMem = 0; // Le prochain CData ne sera pas à mémoriser
}
}

int main()
{
FILE* f;

// structure permettant de renseigner les handlers perso à invoquer pour nos deux evenements de lecture
    xmlSAXHandler sh = { 0 };
sh.startElement = startElement;
    sh.characters = characters;

// On appelle l'enalyse du fichier XML
if(xmlSAXUserParseFile(&sh, NULL, "D:\\Documents and Settings\\THOM31R\\Bureau\\libxml2-2.7.3.win32\\ParseSample\\ParseSample\\Input.xml")) {
printf("Un erreur est survenue. Le fichier XML est peut-être mal consitué");
        return -1;
}

// on a obtenu des résultats...
if (_resBuffer) {
// on en fait ici ce que l'on veut: 
// écriture dans la console
printf("%s\n", _resBuffer);

// ou dans un fichier.
f = fopen("resultat.txt", "w");
if (f) {
fputs(_resBuffer, f);
fclose(f);
}

// et on libère le buffer
free(_resBuffer);
}

system ("pause");
return 0;
}




Renfield - Admin CodeS-SourceS - MVP Visual Basic
Messages postés
393
Date d'inscription
lundi 22 juin 2009
Statut
Membre
Dernière intervention
23 décembre 2011
5
Bonjour,

Voici le nouveau code où j'ajoute le sauvegarde dans le fichier "resultat.txt":
#include <stdlib.h>

#include <string.h>

#include <libxml/parser.h>

#pragma comment(lib,"libxml2.lib")



int _numberCpt = 0; // Permet de compter les occurrences de <Concept>

int _mustMem = 0;   // Si != 0, le CData du noeud doit être mémorisé (ici, pour l'exemple, on l'écrit dans la console)



void startElement(void *user_data, 	const xmlChar *name, const xmlChar **attrs){

int i;

// Si on est sur un Lattice...

if (!xmlStrcmp(name, (xmlChar*)"Lattice") && attrs) {

// On parcoure les attributs

for (i = 0; attrs[i]; i+=2) {

// Jusqu'à trouver l'atribut numberCpt

            if (!xmlStrcmp(attrs[i], (xmlChar*)"numberCpt")) {

// Que l'on mémorise (strtol permet d'analyser la valeur texte, pour en faire une valeur numérique)

_numberCpt = strtol((const char*)attrs[i+1],NULL,0);

break; // inutile de considérer les attributs suivants

            }

        }

}

else if (!xmlStrcmp(name, (xmlChar*)"Concept") && _numberCpt)

_numberCpt--; // On décrémente notre compteur à chaque <Concept> rencontré

else if (!xmlStrcmp(name, (xmlChar*)"Attribute_Ref") && !_numberCpt)

_mustMem = 1; // On souhaite mémoriser les Attribute_Ref (_numberCpt valant ici 0, nous sommes bien dans le dernier <Concept>)

}



void characters(void* user_data, const xmlChar *ch, int len) {

if (_mustMem && len) // Si on doit mémoriser la valeur

{

// On va l'extraire dans un buffer

char* s = (char *)malloc(sizeof(char)*len+1);

*(s+len) = 0; // On place un \0 terminal de la chaine. Cette instruction génèrera un warning, mais on ne prend pas vraiment de risque ici.

strncpy(s,(const char*)ch,len);

// Et l'afficher dans la console

printf("%s\n", s);
        fprintf(fp,"%s ",s);


free(s);

_mustMem = 0; // Le prochain CData ne sera pas à mémoriser

}

}



int main()

{
    FILE *fp;

// structure permettant de renseigner les handlers perso à invoquer pour nos deux evenements de lecture

    xmlSAXHandler sh = { 0 };

sh.startElement = startElement;

    sh.characters = characters;

   

// On appelle l'enalyse du fichier XML

if(xmlSAXUserParseFile(&sh, NULL, "essai.xml")) {

printf("Un erreur est survenue. Le fichier XML est peut-être mal consitué");

        return -1;

}
 

   fp = fopen("resultat.txt","w");
   if(fp == NULL)
   {
    printf("erreur pour l'ouverture de fichier\n");
return -1;
   }
  fclose(fp);
system ("pause");
return 0;

}


j'ai un erreur concernant l'écriture dans le fichier :


1>------ Début de la génération : Projet : parseure, Configuration : Debug Win32 ------
1>Compilation en cours...
1>main.c
1>c:\parseure\parseure\main.c(69) : warning C4996: 'strncpy': This function or variable may be unsafe. Consider using strncpy_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.
1> c:\program files\microsoft visual studio 9.0\vc\include\string.h(157) : voir la déclaration de 'strncpy'
1>c:\parseure\parseure\main.c(74) : error C2065: 'fp' : identificateur non déclaré
1>c:\parseure\parseure\main.c(74) : warning C4047: 'fonction' : 'FILE *' diffère de 'int' dans les niveaux d'indirection
1>c:\parseure\parseure\main.c(74) : warning C4024: 'fprintf' : types différents pour le paramètre formel et réel 1
1>Le journal de génération a été enregistré à l'emplacement "file://c:\parseure\parseure\Debug\BuildLog.htm"
1>parseure - 1 erreur(s), 3 avertissement(s)
======== Génération : 0 a réussi, 1 a échoué, 0 mis à jour, 0 a été ignoré ==========


Comment je vais passer le paramètre fp à la foncction "characters ()" ?

où bien avez vous autre proposition ?

Merci
Messages postés
17286
Date d'inscription
mercredi 2 janvier 2002
Statut
Modérateur
Dernière intervention
23 décembre 2019
69
tu as déclaré fp dans main
du coup, tu n'as le droit de t'en servir que dans main.

regarde comme j'ai organisé la chose, dans le code que je t'ai filé.

Renfield - Admin CodeS-SourceS - MVP Visual Basic
Messages postés
393
Date d'inscription
lundi 22 juin 2009
Statut
Membre
Dernière intervention
23 décembre 2011
5
Donc, où je vais définir le fichier "resultat.txt" ?
Messages postés
17286
Date d'inscription
mercredi 2 janvier 2002
Statut
Modérateur
Dernière intervention
23 décembre 2019
69
.........
tu peux définir cette variable tout en haut... tout comme mes variables _resBuffer et autres...

tu as regardé le code que je t'ai filé ? (celui dans lequel je génère resultat.txt)


parce que j'en ai pas l'impression...

Renfield - Admin CodeS-SourceS - MVP Visual Basic
Messages postés
393
Date d'inscription
lundi 22 juin 2009
Statut
Membre
Dernière intervention
23 décembre 2011
5
Bonjour,

Oui merci. j'ai obtenu le fichier "resultat.txt"


mais si le but est de t'en servir dans ton soft, pourquoi passer par un fichier ? pourquoi ne pas garder ces résultats en mémoire ?


Je vais appeler votre solution comme une fonction dans le "main .c" du mon projet.

Je dois sauvegarder le résultat de cette fonction pour les utiliser après dans mon programme.

Que proposez vous surtout je cherche la meilleure solution au niveau rapidité et le moins consommation de la mémoire ?

Merci.
Messages postés
17286
Date d'inscription
mercredi 2 janvier 2002
Statut
Modérateur
Dernière intervention
23 décembre 2019
69
voir mon code... _resBuffer pourrait être conservé tel quel.

possible aussi de faire un char**
séparer les elements par un \0 avec deux \0 final.

comme le sonts les attrs de startElement.

ainsi, tu as un simple tableau :

val[0] = "d"
val[1] = "b"
val[2] = "c"
val[3] = "a"
val[4] = '\0'

Renfield - Admin CodeS-SourceS - MVP Visual Basic
Messages postés
393
Date d'inscription
lundi 22 juin 2009
Statut
Membre
Dernière intervention
23 décembre 2011
5
Bonjour,

Je vous remercie pour votre assistance.
la première fonction est faite grâce à votre aide.
Elle me reste la deuxième fonction que je vais vous l'explique .

- La deuxième fonction permet d'extraire les informations (chaines de caractères) qui existent entre la balise et de tous les identificateurs sauf le dernier identificateur

et si le premier identificateur contient la balise et qui n'est pas vide alors nous mettons l'information entre dans le résultat

Sinon si la balise du premier identificateur est vide comme dans notre cas alors nous ne mettons pas l'information entre dans le résultat.
cette fonction nous donne comme un résultat un tableau ou un fichier contenant les chaines de caractères recherchées.

Dans notre cas ici, on aura des lignes du fichier ou bien des cases du tableau contenant:
"a"
"b"
"c"
"d"
"a c"
"b c"
"b d"
"c d"
"a b d"

Qu'est ce que nous allons changer dans le code de la solution de la première fonction déjà trouvée au dessus pour trouver une solution pour la deuxième fonction ?

Merci.
Messages postés
17286
Date d'inscription
mercredi 2 janvier 2002
Statut
Modérateur
Dernière intervention
23 décembre 2019
69
s'rait pas le temps de relever tes manches ?

Renfield - Admin CodeS-SourceS - MVP Visual Basic
Messages postés
393
Date d'inscription
lundi 22 juin 2009
Statut
Membre
Dernière intervention
23 décembre 2011
5
Bonjour,

OK. Je vais essayer.
Messages postés
17286
Date d'inscription
mercredi 2 janvier 2002
Statut
Modérateur
Dernière intervention
23 décembre 2019
69
hormis le fait que j'ai pas compris cette histoire de mettre le resultat entre ... (donne un exemple)

c'est simplissime.
tu n'as besoin que d'une fonction pour tout.

tu stockes dans un tableau tous tes elements...

ce que tu décris comme 1ere fonction, c'est simplement prender le dernier element de ce tableau.

non ?

Renfield - Admin CodeS-SourceS - MVP Visual Basic
Messages postés
17286
Date d'inscription
mercredi 2 janvier 2002
Statut
Modérateur
Dernière intervention
23 décembre 2019
69
bonne poire, je t'ai codé ça.
à toi d'adapter si cette base de code te convient.
#include <stdlib.h>
#include <string.h>
#include <libxml/parser.h>
#pragma comment(lib,"libxml2.lib")

typedef struct {
char* reference;
void*  next;
} REF;

typedef struct {
char* ID; // a toi de le renseigner si tu en as besoin...
REF* references;
} CONCEPT ;

CONCEPT* _concepts = 0;
int  _countCpt = 0;    // Permet de compter les occurrences de <Concept>
int  _currentCpt = -1;  // Permet de compter les occurrences de <Concept>
int  _inAttribute = 0;

void startElement(void *user_data, 	const xmlChar *name, const xmlChar **attrs){
int i;
// Si on est sur un Lattice...
if (!xmlStrcmp(name, (xmlChar*)"Lattice") && attrs) {
// On parcoure les attributs
for (i = 0; attrs[i]; i+=2) {
// Jusqu'à trouver l'atribut numberCpt
            if (!xmlStrcmp(attrs[i], (xmlChar*)"numberCpt")) {
// Que l'on mémorise (strtol permet d'analyser la valeur texte, pour en faire une valeur numérique)
_countCpt = strtol((const char*)attrs[i+1],NULL,0);
// on créé un tableau qui va contenir les concepts.
_concepts = (CONCEPT*)calloc(_countCpt, sizeof(CONCEPT));

break; // inutile de considérer les attributs suivants
            }
        }
}
else if (!xmlStrcmp(name, (xmlChar*)"Concept") && _countCpt)
_currentCpt++;
else if (!xmlStrcmp(name, (xmlChar*)"Attribute_Ref"))
_inAttribute = 1; // On souhaite mémoriser les Attribute_Ref
}

void characters(void* user_data, const xmlChar *ch, int len) {
if (_inAttribute && len)
{	
// lien vers les references du concept courant
REF* ref = _concepts[_currentCpt].references;
// on créé une nouvelle reference
REF* newRef = (REF*)calloc(1, sizeof(REF));

// permet de relier la nouvelle reference a la structure (liste chainee)
if (ref) {
while(ref->next)
ref = (REF*)ref->next;
ref->next = newRef;
}
else
_concepts[_currentCpt].references = newRef;			

// on recopie le contenu de la reference
newRef->reference = (char*)malloc(len+1);
strncpy(newRef->reference, ch, len);
*(newRef->reference+len) = 0;	

_inAttribute = 0;
}
}

// Permet de désallouer proporement la structure
void cleanup()
{
int i;
REF* ref;
for (i=0; i<_countCpt; i++) {
ref = _concepts[i].references;
while (ref) {
free(ref->reference);
ref = ref->next;
}
}
free(_concepts);
}

// A fin d'exemple uniquement...
void generateResultat()
{
FILE* fp;
int i;
REF* ref;

if(fp=fopen("resultat.txt", "w")) {
for (i=0; i<_countCpt; i++) {
ref = _concepts[i].references;
while (ref) {
fprintf(fp, "%s ", ref->reference);
ref = ref->next;
}
fprintf(fp, "\n");
}
fclose(fp);
}
}

int main()
{
// structure permettant de renseigner les handlers perso à invoquer pour nos evenements de lecture
    xmlSAXHandler sh = { 0 };
sh.startElement = startElement;
    sh.characters = characters;

// On appelle l'enalyse du fichier XML
if(xmlSAXUserParseFile(&sh, NULL, "Input.xml")) {
printf("Un erreur est survenue. Le fichier XML est peut-être mal consitué");
return -1;
}

// Optionnel : génération du fichier resultat.
generateResultat();
system("type resultat.txt");
system("pause");

cleanup();
return 0;
}



Renfield - Admin CodeS-SourceS - MVP Visual Basic
Messages postés
17286
Date d'inscription
mercredi 2 janvier 2002
Statut
Modérateur
Dernière intervention
23 décembre 2019
69
liste chainée... comme ca c'est assez léger en mémoire, mais surtout bien rapide.

Renfield - Admin CodeS-SourceS - MVP Visual Basic
Messages postés
17286
Date d'inscription
mercredi 2 janvier 2002
Statut
Modérateur
Dernière intervention
23 décembre 2019
69
Alors, te convient, comme base de code ?

Renfield - Admin CodeS-SourceS - MVP Visual Basic
Messages postés
393
Date d'inscription
lundi 22 juin 2009
Statut
Membre
Dernière intervention
23 décembre 2011
5
Bonjour,

Je vous remercie pour votre aide.
Cette solution nous donne:

a
b
c
d
c a
b c
d b
d c
d b a
d b c a

Je voulais appeler votre solution comme une fonction dans mon programme main.c :

- si nous sommes dans le cas 1 votre solution nous retourne :

"d b c a"

- si nous sommes dans le cas 2 votre solution nous retourne :
a
b
c
d
c a
b c
d b
d c
d b a
d b c a

Voici un exemple de "main.c"

#include<stdio.h>
#include<conio.h>

int main()
{
........
.......
if(cas == 1)
//appel de votre solution
//le résultat est doit être sauvegardé dans un fichier ou autre structure pour les utiliser après dans le main

if (cas == 2 )
//appel de votre solution
//le résultat est doit être sauvegardé dans un fichier ou autre structure pour les utiliser après dans le main
...........
............
return 0;
}


Merci.