7. Modifier un arbre DOM

7.1. Ajouter un noeud

Pour ajouter un noeud à un arbre DOM, il y a plusieurs techniques. La plus compliquée mais la plus puissante consiste à créer un noeud avec xmlNewNode(), qui prend en premier argument l'espace de nom, de type xmlNsPtr, et en second argument, le nom du noeud, de type xmlChar. Le résultat est un nouveau noeud de type xmlNodePtr. Puis il faut rattacher ce noeud à l'arbre, et c'est là la puissance de l'opération: il existe plusieurs fonctions pour rattacher le noeud à l'arbre: ces fonctions prennent un noeud de référence en premier argument, et le noeud que l'on veut rajouter en second argument.

  • xmlAddChild(): ajoute le noeud en tant que fils au noeud de référence

  • xmlAddSibling(): ajoute le noeud en tant que frère du noeud de référence

  • xmlAddPrevSibling(): ajoute le noeud en tant que frère précédent du noeud de référence

  • xmlAddSibling(): ajoute le noeud en tant que frère suivant du noeud de référence

  • xmlReplaceNode(): remplace le noeud de référence par le noeud que l'on veut rajouter

Dans tous les cas, si le noeud existait déjà, il est a préalable retiré de l'endroit où il était, ce qui permet de déplacer un noeud.

Si la méthode précédente est puissante, elle nécessite deux étapes, création puis rattachement à l'arbre. On peut faire cela en une étape quand il s'agit d'un fils, avec xmlNewTextChild() qui prend le noeud de référence en premier argument, l'espace de noms en second argument, le nom en troisième argument et le contenu en dernier argument. Le premier et le troisième sont obligatoires, les deux autres peuvent être NULL. Et cela ajoute un noeud fils au noeud spécifié en premier argument.

7.2. Supprimer un noeud

Pour détacher un noeud d'un arbre, on utilise xmlUnlinkNode() qui prend le noeud en premier et unique argument. Cela détache le noeud, mais ne libère pas la mémoire pour autant. Il faut pour cela utiliser xmlFreeNode() si on veut libérer la mémoire.

7.3. Modifier le contenu d'un noeud

Modifier le contenu d'un noeud pourrait se faire de manière brutale en récupérant le pointeur sur le contenu du noeud. Cependant, il est préférable de considérer l'arbre et ses noeuds comme une structure de données opaque, et utiliser les fonctions de l'API de libxml2 pour modifier le contenu d'un noeud. Nous avons xmlNodeSetContent() et xmlNodeSetContentLen() qui remplacent le contenu actuel par un nouveau contenu. En premier argument, vous indiquez le noeud de type xmlNodePtr (on commence à avoir l'habitude de ce type, ne trouvez-vous pas ? ), et en second argument la chaîne de caractères du contenu, de type xmlChar*. xmlNodeSetContentLen() porte un nom un peu bizarre car on s'attend à modifier la longueur du contenu. En fait, non. Cela vient simplement qu'elle a un argument supplémentaire par rapport à l'autre: c'est un entier spécifiant la longueur du contenu à prendre en compte.

Si vous voulez concaténer une chaîne de caractères au contenu existant plutôt que de le remplacer, vous pouvez utiliser les deux fonctions suivantes qui ont la même syntaxe que les deux précédentes: xmlNodeAddContent() et xmlNodeAddContentLen().

7.4. Ajouter/supprimer des attributs

Outre le contenu d'un noeud que vous pouvez modifier, il y a les attributs. Pour ajouter un attribut, utilisez xmlSetProp() qui prend en premier argument, comme toujours jusqu'à maintenant, le noeud auquel vous voulez rajouter un attribut. Les deuxième et troisième arguments, de type xmlChar* sont le nom de l'attribut et la valeur de l'attribut.

7.5. Un exemple

Comment ajouter un utilisateur à un groupe, dans le cas de notre exemple de permissions ? Voici un petit bout de code...

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <string.h>
  4 #include <errno.h>
  5 #include <sys/types.h>
  6 #include <sys/stat.h>
  7 #include <unistd.h>
  8 #include <libxml/tree.h>
  9 #include <libxml/xpath.h>
 10 
 11 void
 12 show_help (const char *reason)
 13 {
 14   if (reason)
 15     fprintf (stderr, "Error: %s\n\n", reason);
 16   fprintf (stderr, "check_perms --cf=<permission config file>i\n"
 17 	   "            --user=<user name>\n"
 18 	   "            --group=<group>\n");
 19   if (reason)
 20     exit (EXIT_FAILURE);
 21   exit (EXIT_SUCCESS);
 22 }
 23 
 24 int
 25 is_file (const char *filename)
 26 {
 27   struct stat buf;
 28   if (stat (filename, &buf) == -1)
 29     return (0);
 30   if (S_ISREG (buf.st_mode))
 31     return (1);
 32   return (0);
 33 }
 34 
 35 void
 36 add_user_to_group (const char *filename, const char *user, const char *group)
 37 {
 38   xmlDocPtr xmlperms_doc = NULL;
 39   xmlNodePtr tree, node, node_user;
 40   int group_already_exists = 0;
 41 
 42   if (!is_file (filename))
 43     {
 44       fprintf (stderr, "%s:%d File not found\n", __FILE__, __LINE__);
 45       return;
 46     }
 47   xmlperms_doc = xmlParseFile (filename);
 48   if (!xmlperms_doc)
 49     {
 50       fprintf (stderr, "%s:%d Internal error\n", __FILE__, __LINE__);
 51       return;
 52     }
 53   tree = xmlperms_doc->children;
 54   tree = tree->children;
 55   for (node = tree; node; node = node->next)
 56     {
 57       if ((node->type == XML_ELEMENT_NODE) && (!strcmp (node->name, "group")))
 58 	{
 59 	  char *attr_name;
 60 	  if ((attr_name = xmlGetProp (node, "name")))
 61 	    {
 62 	      if (!strcmp (attr_name, group))
 63 		{
 64 		  int name_already_exists = 0;
 65 		  group_already_exists = 1;
 66 		  for (node_user = node->children; node_user;
 67 		       node_user = node_user->next)
 68 		    {
 69 		      if (!strcmp (node_user->name, "user"))
 70 			{
 71 			  char *text;
 72 			  text = xmlNodeGetContent (node_user);
 73 			  if (!strcmp (text, user))
 74 			    name_already_exists = 1;
 75 			  printf ("'%s'\n", text);
 76 			  xmlFree (text);
 77 			}
 78 		    }
 79 		  xmlFree (attr_name);
 80 		  if (!name_already_exists)
 81 		    xmlNewTextChild (node, NULL, "user", user);
 82 		}
 83 	    }
 84 	}
 85     }
 86   if (!group_already_exists)
 87     {
 88       node = xmlNewTextChild (tree->parent, NULL, "group", NULL);
 89       xmlSetProp (node, "name", group);
 90       xmlNewTextChild (node, NULL, "user", user);
 91     }
 92   xmlDocFormatDump (stdout, xmlperms_doc, 1);
 93   xmlFreeDoc (xmlperms_doc);
 94 }
 95 
 96 int
 97 main (int argc, char *argv[])
 98 {
 99   int i;
100   char *filename = NULL;
101   char *user = NULL;
102   char *group = NULL;
103 
104   for (i = 0; i < argc; i++)
105     {
106       if (!strncmp (argv[i], "--cf=", sizeof ("--cf")))
107 	filename = &(argv[i][sizeof ("--cf")]);
108       else if (!strncmp (argv[i], "--user=", sizeof ("--user")))
109 	user = &(argv[i][sizeof ("--user")]);
110       else if (!strncmp (argv[i], "--group=", sizeof ("--group")))
111 	group = &(argv[i][sizeof ("--group")]);
112     }
113   if (!filename)
114     show_help ("filename is missing");
115   if (!user)
116     show_help ("user is missing");
117   if (!group)
118     show_help ("group is missing");
119 
120   add_user_to_group (filename, user, group);
121   exit (EXIT_SUCCESS);
122 }

La partie intéressante de ce bout de code est manifestement la fonction add_user_to_group() dont vous devez facilement décrypter le début, lignes 35 à 53. Lignes 54, suivie de la boucle, consiste à parcourir les noeuds fils de la racine à la recherche du groupe spécifié en argument. Ligne 60, on récupère son attribut name, pour tester ligne 62 sa valeur. Si c'est bon, lignes 66 à 78 on parcours tous les noeuds fils du groupe, à la recherche de l'utilisateur, pour vérifier qu'il n'est pas déjà dans le groupe. Notez que la ligne 75 est une ligne pour le débogage et n'a rien à faire dans un code final.

Si l'on n'a pas trouvé l'utilisateur dans le groupe (ligne 80), nous l'ajoutons ligne 81. Et si en parcourant tous les groupes on n'a pas trouvé le groupe (ligne 86), alors ligne 88 nous ajoutons un nouveau noeud pour créer un groupe, puis nous lui spécifions son attribut name ligne 89, et ligne 90, nous ajoutons l'utilisateur au nouveau groupe.

Ligne 92, nous affichons le document XML correspondant au nouvel arbre DOM, ce qui peut être considéré comme une ligne pour le débogage si le but est de travailler avec l'arbre par la suite, ou comme la manière d'afficher le résultat si l'on veut rediriger la sortie vers un fichier.

Le reste du code se passe de commentaire, mais me permet de vous montrer comment on parse facilement les arguments fournis en lançant un programme.

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