8. DOM et XPath

8.1. Rechercher un noeud avec XPath

Un des intérêts grands intérêts de libxml2 et de son implémentation de DOM est que libxml2 implémente aussi des fonctions tirant profit de XPath. Par conséquent, plutôt que de parcourir l'arbre comme nous l'avons vu jusqu'à maintenant, il suffit de construire un chemin XPath, de lancer libxml2 dessus, et on récupère le noeud recherché.

Pour effectuer cela, il y a plusieurs étapes à respecter:

  • initialisation d'un contexte;

  • évaluation de la requète XPath;

  • récupération du résultat.

Avant d'initialiser le contexte, initialisons l'environnement XPath avec xmlXPathInit() qui ne prend pas d'arguments et ne renvoie rien. L'initialisation d'un contexte se fait facilement avec xmlXPathNewContext(), qui prend en argument l'arbre DOM (une variable de type xmlNodePtr, mais je me répète...), et qui renvoie une variable de type xmlXPathContextPtr.

Cette variable contexte va nous servir à effectuer les requêtes XPath, avec xmlXPathEval(), qui prend le chemin XPath en premier argument, de type xmlChar*, et qui prend en second argument le contexte que nous avons initialisé ci-dessus. La valeur renvoyée, de type xmlXPathObjectPtr, contient le résultat.

Pour récupérer ce que nous voulons dans le résultat, il faut taper dans la structure de données xmlXPathObject, que voici:

 1 struct xmlXPathObject
 2 {
 3   xmlXPathObjectType type;
 4   xmlNodeSetPtr nodesetval;
 5   int boolval;
 6   double floatval;
 7   xmlChar *stringval;
 8   void *user;
 9   int index;
10   void *user2;
11   int index2;
12 };

Le type conditionne ce que nous allons lire. Voici les différents types possibles:

 1 typedef enum
 2 {
 3   XPATH_UNDEFINED = 0,
 4   XPATH_NODESET = 1,
 5   XPATH_BOOLEAN = 2,
 6   XPATH_NUMBER = 3,
 7   XPATH_STRING = 4,
 8   XPATH_POINT = 5,
 9   XPATH_RANGE = 6,
10   XPATH_LOCATIONSET = 7,
11   XPATH_USERS = 8,
12   XPATH_XSLT_TREE = 9
13 } xmlXPathObjectType;

Par exemple, si nous avons un type XPathNumber, la valeur que nous allons lire sera dans l'élément floatval du résultat. Mais quand on récupère des noeuds, ce qui va nous arriver un peu plus loin, le type est XPATH_NODESET, et nous utilisons la structure nodesetval, dont le type xmlNodeSetPtr définit un pointeur sur ceci:

1 struct xmlNodeSet
2 {
3   int nodeNr;			/* nombre de noeuds dans le jeu de noeuds */
4   int nodeMax;			/* taille mémoire du tableau */
5   xmlNodePtr *nodeTab;		/* tableau de noeuds (non ordonné) */
6 };

Si le type est XPATH_NODESET, nous avons donc juste à tester que nodesetval est non nul (on ne sait jamais), qu'il contient des noeuds (nodeNr strictement positif), et si tout va bien, nous avons les noeuds les uns à la suite des autres dans le tableau nodeTab.

8.2. Exemple: tester les permissions

Dans notre exemple de permissions, voici une nouvelle fonction qui renvoie 1 si l'utilisateur est autorisé à effectuer une action donnée sur un objet donné. Voici la fonction:

 1 int
 2 is_user_authorized (const char *filename, const char *user,
 3 		    const char *action, const char *object)
 4 {
 5   xmlDocPtr xmlperms_doc = NULL;
 6   xmlXPathContextPtr xmlperms_context = NULL;
 7   xmlXPathObjectPtr xmlobject;
 8   const char path_template[] =
 9     "/authorizations/group[@name=/authorizations/action[@name='%s' and @object='%s']/authorization/@grantedto]/user/text()[.='%s']";
10   char *path;
11   int authorization = 0;
12 
13   if (!is_file (filename))
14     {
15       fprintf (stderr, "%s:%d File not found\n", __FILE__, __LINE__);
16       return (0);
17     }
18   xmlperms_doc = xmlParseFile (filename);
19   if (!xmlperms_doc)
20     {
21       fprintf (stderr, "%s:%d Could not parse the document\n", __FILE__,
22 	       __LINE__);
23       return (0);
24     }

Jusqu'ici, nous sommes en terrain connu, donc rien de nouveau sous le soleil. Lignes suivantes, nous initialisons l'environnement XPath, puis nous initialisons le contexte XPath.

25   xmlXPathInit ();
26   xmlperms_context = xmlXPathNewContext (xmlperms_doc);
27   path =
28     malloc ((sizeof (path_template) + strlen (user) + strlen (action) +
29 	     strlen (object)) * sizeof (char));
30   if (!path)
31     {
32       fprintf (stderr, "%s:%d Not enough memory\n", __FILE__, __LINE__);
33       return (0);
34     }
35   sprintf (path, path_template, action, object, user);

Lignes 27 à 29, nous avons créé la chaîne de caractères de la requête XPath, dont le modèle se trouve ligne 9. Pour mieux comprendre cette requête, veuillez vous référer à l'article sur XPath dans ce même numéro. Nous allons proposer cette requête à la fonction xmlXPathEval ci-dessous.

36   xmlobject = xmlXPathEval (path, xmlperms_context);
37   free (path);
38 

A partir de maintenant, nous récupérons le résultat. Le code suivant est incomplet: il ne teste que le cas des jeux de noeuds. D'un autre côté, d'un point de vue fonctionnel, si nous trouvons un autre type de données, cela est bizarre et ne donne pas pour autant les permissions voulues par l'utilisateur.

39   if ((xmlobject->type == XPATH_NODESET) && (xmlobject->nodesetval))
40     {
41       if (xmlobject->nodesetval->nodeNr)
42 	{

Ci-dessous, l'exemple est encore une fois un peu incomplet. En effet, si d'un point de vue toujours fonctionnel il suffit que l'on trouve un noeud pour que l'utilisateur aie la permission d'effectuer son action sur son objet, cela ne se passe pas toujours de même. Souvent, il faudra effectuer une boucle sur tous les noeuds, et ne pas prendre seulement le premier noeud comme cela est fait ligne 44.

43 	  xmlNodePtr node;
44 	  node = xmlobject->nodesetval->nodeTab[0];
45 	  if ((node->type == XML_TEXT_NODE) ||
46 	      (node->type == XML_CDATA_SECTION_NODE))
47 	    {
48 	      authorization = 1;
49 	    }
50 	}
51     }

Je n'en ai pas parlé plus haut, mais il est évident qu'après avoir joué avec XPath, il faut faire un peu de nettoyage. Cela est fait ci-dessous avec xmlXPathFreeObject() et xmlXPathFreeContext().

52   xmlXPathFreeObject (xmlobject);
53   xmlXPathFreeContext (xml_perms_context);
54   return (authorization);
55 }

Je vous laisse en exercice la réalisation d'une fonction qui rajoute un noeud <action> car cela n'a pas été traité. Mais avec tout ce qui précède, vous n'aurez aucune difficulté à faire cela.

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