5. La structure d'un arbre DOM

Avant d'attaquer la structure, voici un bout de code, celui avec lequel nous allons jouer:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <libxml/tree.h>
 4 #include <libxml/xpath.h>
 5 
 6 int
 7 main (int argc, char *argv[])
 8 {
 9   xmlDocPtr xmldoc = NULL;
10   const char doc[] =
11     "<?xml version='1.0'?><racine><texte>blabla</texte></racine>";
12 
13   xmldoc = xmlParseMemory (doc, sizeof (doc));
14   if (!xmldoc)
15     {
16       fprintf (stderr, "%s:%d bah ca marche pas du tout, en fait\n", __FILE__,
17 	       __LINE__);
18       exit (EXIT_FAILURE);
19     }
20   printf ("%s\n", xmldoc->children->name);
21   xmlFreeDoc (xmldoc);
22 
23   exit (EXIT_SUCCESS);
24 }

A part la ligne 20, vous ne devriez rien découvrir hormis les en-têtes lignes 3 et 4.

Pour compiler un tel programme, rien de plus facile:

$ gcc -Wall -g `xml2-config --libs --cflags` test.c -o test

Il ne vous reste plus qu'à tester ce joli programme qui devrait vous afficher racine. Nous allons voir pourquoi ci-dessous!

5.1. La structure

La structure est une structure d'arbre, complétéede nombreuses informations propres à la bibliothèque. Ainsi, supposons que vous ayez un pointeur sur un noeud node, de type xmlNodePtr. Alors, pour obtenir le premier fils, utilisez node->children. Pour obtenir le noeud parent, c'est node->parent. Et pour obtenir les noeuds précédent et suivant, vous avez node->prev et node->next.

Un noeud a aussi un nom: c'est node->name. Vous comprenez maintenant le pourquoi du comment de la ligne 20 précédente: la racine est le premier noeud fils du document. Et nous en extrayons son nom pour l'afficher.

Voici la structure complète d'un noeud tel que spécifiée dans la documentation de libxml2:

 1 struct xmlNode {
 2     void           *_private;	/* application data */
 3     xmlElementType   type;	/* type number, must be second ! */
 4     const xmlChar   *name;      /* the name of the node, or the entity */
 5     struct _xmlNode *children;	/* parent->childs link */
 6     struct _xmlNode *last;	/* last child link */
 7     struct _xmlNode *parent;	/* child->parent link */
 8     struct _xmlNode *next;	/* next sibling link  */
 9     struct _xmlNode *prev;	/* previous sibling link  */
10     struct _xmlDoc  *doc;	/* the containing document */
11 
12     /* End of common part */
13     xmlNs           *ns;        /* pointer to the associated namespace */
14     xmlChar         *content;   /* the content */
15     struct _xmlAttr *properties;/* properties list */
16     xmlNs           *nsDef;     /* namespace definitions on this node */
17 };

Le seul type intéressant que nous avons laissé de côté pour l'instant est le type. Voyez le tableau contenant les différents types qui font l'énumération xmlElementType. Autre chose, si l'élément content peut vous intéresser, sachez que l'utilisation de content est moins simple qu'il n'y paraît. Et sachez aussi que xmlNodeGetContent() ne renvoie pas que le contenu du noeud, ce à quoi on peut s'attendre, mais en fait la concaténation de tous les contenus des noeuds fils!

typedef enum {
    XML_ELEMENT_NODE=		1,
    XML_ATTRIBUTE_NODE=		2,
    XML_TEXT_NODE=		3,
    XML_CDATA_SECTION_NODE=	4,
    XML_ENTITY_REF_NODE=	5,
    XML_ENTITY_NODE=		6,
    XML_PI_NODE=		7,
    XML_COMMENT_NODE=		8,
    XML_DOCUMENT_NODE=		9,
    XML_DOCUMENT_TYPE_NODE=	10,
    XML_DOCUMENT_FRAG_NODE=	11,
    XML_NOTATION_NODE=		12,
    XML_HTML_DOCUMENT_NODE=	13,
    XML_DTD_NODE=		14,
    XML_ELEMENT_DECL=		15,
    XML_ATTRIBUTE_DECL=		16,
    XML_ENTITY_DECL=		17,
    XML_NAMESPACE_DECL=		18,
    XML_XINCLUDE_START=		19,
    XML_XINCLUDE_END=		20
#ifdef LIBXML_DOCB_ENABLED
   ,XML_DOCB_DOCUMENT_NODE=	21
#endif
} xmlElementType;

En exercice, voyons comment afficher le blabla au lieu du nom du noeud racine dans l'exemple précédent, ligne 20:

20 printf ("%s\n", xmlNodeGetContent (xmldoc->children->children));

5.2. Parcourir un arbre DOM

Parcourir un arbre DOM est aussi simple que de parcourir n'importe quel arbre. Il suffit de parcourir tous les noeuds fils d'un noeuds, de manière récursive. Cependant, parcourir un arbre DOM ne présente pas énormément d'intérêt. En effet, un arbre DOM n'est pas un arbre avec des noeuds comme <texte> dont le contenu est blabla. Déjà, dans ce cas particulier, le noeud <texte> a un contenu vide, ou si vous vous amusez à mettre en forme le XML, il aura un contenu vide. Et il a un noeud fils, qui n'est pas visible explicitement, qui n'a pas de nom, mais dont le contenu est blabla. De plus, si on avait eu <texte><![CDATA[blabla]]></texte> au lieu de <texte>blabla</texte>, le type du noeud fils de <texte> aurait été XML_CDATA_SECTION_NODE au lieu de XML_TEXT_NODE. Ajoutez à cela qu'il faut encore prendre en compte les attributs. Plus ensuite tous les cas un peu plus rares de noeuds que reconnaît libxml2 et qui sont dans le tableau des types!

Parcourir un arbre DOM présente peu d'intérêt par rapport aux moyens à mettre en oeuvre, et par rapport aux fonctions fournies dans libxml2. Cependant, pour illustrer un peu la bibliothèque, je vous présente quand même un petit bout de code, qui affiche tous les noeuds texte (ou CDATA) et leur chemin:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <string.h>
 4 #include <libxml/tree.h>
 5 #include <libxml/xpath.h>
 6 
 7 void
 8 show (xmlNodePtr node)
 9 {
10   if (node->type == XML_ELEMENT_NODE)
11     {
12       xmlNodePtr n;
13       for (n = node; n; n = n->next)
14 	{
15 	  if (n->children)
16 	    {
17 	      show (n->children);
18 	    }
19 	}
20     }
21   else if ((node->type == XML_CDATA_SECTION_NODE)
22 	   || (node->type == XML_TEXT_NODE))
23     {
24       xmlChar *path = xmlGetNodePath (node);
25       printf ("%s -> '%s'\n", path,
26 	      node->content ? (char *) node->content : "(null)");
27       xmlFree (path);
28     }
29 }
30 
31 int
32 main (int argc, char *argv[])
33 {
34   xmlDocPtr xmldoc = NULL;
35   const char doc[] =
36     "<?xml version='1.0'?><racine><texte>blabla</texte><t2>ca</t2><t3>va</t3><t4><![CDATA[<bien?>]]></t4></racine>";
37 
38   xmldoc = xmlParseMemory (doc, sizeof (doc));
39   if (!xmldoc)
40     {
41       fprintf (stderr, "%s:%d bah ca marche pas du tout, en fait\n", __FILE__,
42 	       __LINE__);
43       exit (EXIT_FAILURE);
44     }
45   show (xmldoc->children);
46   xmlFreeDoc (xmldoc);
47 
48   exit (EXIT_SUCCESS);
49 }

Vous devriez avoir reconnu facilement le code principal, avec une petite variation ligne 35. Ligne 45, nous appelons la fonction récursive show() qui affiche tous les noeuds texte ou CDATA et le chemin de ces noeuds.

Ligne 10, nous effectuons un indispensable test sur le type du noeud. Si c'est un élément nous parcourons tous les noeuds frères avec la boucle ligne 13. Et récursivement, nous parcourons, pour chaque noeud, ses fils, ligne 17 si le noeud en a (test ligne 15). Et nous affichons le contenu du noeud et son chemin ligne 25 si le test sur le type de noeud a conclu que nous avions un noeud texte ou CDATA.

Le chemin du noeud est récupéré avec xmlGetNodePath. La chaîne résultante, de type xmlChar*, doit être libérée avec xmlFree() (ligne 27). D'ailleurs, retenez ceci: toute chaîne de caractères allouée par libxml2 que vous devez libérer se fait avec xmlFree(). Revenons au chemin du noeud. Ce chemin est un chemin comme nous en avons l'habitude, mais dont le dernier élément, qui aurait du être vide puisqu'un noeud texte n'a pas de nom, est text(). Cela met donc bien en évidence que les noeuds que l'on croit contenir le texte, ont en fait un noeud fils, et c'est ce noeud fils qui contient réèllement le texte. Cependant, si vous voulez éviter d'afficher text(), il vous suffit de récupérer xmlGetNodePath(node->parent)au lieu de xmlGetNodePath(node) comme je l'ai fait. Voici le résultat:

/racine/texte/text() -> 'blabla'
/racine/t2/text() -> 'ca'
/racine/t3/text() -> 'va'
/racine/t4/text() -> '<bien?>'

Si vous avez compris cet exemple, essayez-le avec le fichier permissions.xml. Vous devriez avoir des surprises. En effet, ici, avec le document XML ligne 36, nous avions un cas favorable: aucun noeud texte (ou CDATA) ne contenait de noeuds fils. Avec le document XML contenu dans permissions.xml, cela n'est pas si simple. En effet, le noeud <autorizations> contient un fils de type texte, que vous n'avez peut-être pas remarqué. Vous ne l'avez probablement pas remarqué parce son contenu n'est rien d'autre que les espaces vides qui servent à l'indentation. Cependant, nous avons affaire à un véritable noeud, de type texte, et qui contient des fils! Ces fils, ce sont dans le cas de permissions.xml, tous les autres noeuds ! C'est pour cela que quasi rien ne s'affiche avec l'exemple précédent. Pour corriger cela, faites un copier/coller des lignes 13 à 18 pour les insérer avant la ligne 28. N'oubliez pas non plus la déclaration ligne 12 que vous placez par exemple après la ligne 24. Voici avant la modification:

/authorizations/text()[1] -> '
        '

Et après:

/authorizations/text()[1] -> '
        '
/authorizations/group[1]/text()[1] -> '
                '
/authorizations/group[1]/user[1]/text() -> 'bob'
/authorizations/group[1]/user[2]/text() -> 'rebecca'
/authorizations/group[1]/user[3]/text() -> 'bic'
/authorizations/group[1]/user[4]/text() -> 'bac'
/authorizations/group[2]/text()[1] -> '
                '
/authorizations/group[2]/user[1]/text() -> 'arkana'
/authorizations/group[2]/user[2]/text() -> 'spartakus'
/authorizations/group[3]/text()[1] -> '
                '
/authorizations/group[3]/user/text() -> 'shagshag'
/authorizations/action[1]/text()[1] -> '
                '
/authorizations/action[2]/text()[1] -> '
                '

Par ailleurs, j'en ai déjà un peu parlé, de ces noeuds CDATA, mais je vais vous en montrer l'utilité si vous ne l'avez pas dejè vue et que vous n'êtes pas un gourou du XML. Question: comment mettre un texte "<balise>", ou même "a<b" dans un document XML ? Les symboles < et > nous gènent. Il suffit pour cela de les mettre dans une section CDATA: tous les caractères sont autorisés dans une section CDATA, sauf la séquence ]]< qui sert à finir la section. C'est là l'intérêt d'une section CDATA, et vous devez toujours la prendre en compte quand vous prenez en compte les noeuds texte.

Enfin, pour rechercher un noeud, il y a beaucoup plus simple que le parcours de l'arbre: cela ôte encore des raisons de parcourir un arbre DOM. Nous allons voir la manière de faire plus loin.

5.3. La construction d'un arbre DOM

Construire un arbre DOM se fait en deux étapes comme pour toute structure de données. La première est de le remplir, et la seconde est d'initialiser l'arbre. Bizarrement, on commence toujours par la seconde étape, aussi, nous traiterons la première dans les paragraphes suivants.

Pour initialiser un arbre DOM, vous pouvez évidemment partir d'un arbre existant et vide ainsi:

1 const char doc[] = "<?xml version='1.0'?><racine/>";
2 xmldoc = xmlParseMemory (doc, sizeof (doc));

L'inconvénient de cette méthode est que l'on démarre en lançant le parseur, ce qui n'est peut-être pas utile.

La méthode la plus logique est d'utiliser la fonction appropriée: xmlNewDoc(). Cette fonction prend en argument le numéro de version XML que nous allons utiliser, à savoir "1.0" puisque jusqu'à nouvel ordre, il n'y a que cette version. Cela donne:

1 xmlDocPtr doc;
2 doc = xmlNewDoc ("1.0");

Contrairement à l'autre méthode, nous avons un document qui n'a pas encore de noeud, pas même la racine. Mais ajouter la racine ou ajouter des noeuds, cela revient au même, et nous voyons cela bientôt

création est mise à disposition sous un contrat Creative Commons