1. Présentation

glib contient des « parser » de XML : http://library.gnome.org/devel/glib/unstable/glib-Simple-XML-Subset-Parser.html

Nous allons étudier un ensemble de fonctions qui utilisent ces fonctions afin de lire un fichier XML. À l'origine, ces fonctions ont été données par gege2061 fournies ici http://c.developpez.com/sources/c/?page=X#GTK_dom . J'y ai apporté quelques corrections et je vais aussi rajouter d'autres fonctions que j'ai développées pour mes propres besoins.

Il me semble aussi indispensable aussi d'expliquer la structure pour pouvoir par la suite faire soi-même ses propres fonctions.

2. Les structures utilisées :

2-A. types personnels

Ils sont à définir dans notre programme.

2-A-1. GmarkupDomContext

GmarkupDomContext
Sélectionnez
typedef struct _GMarkupDomContext GmarkupDomContext;

struct _GMarkupDomContext 
{ 
  gint level; 
  GMarkupDomNode *root; 
  GMarkupDomNode *last_root; 
  GMarkupDomNode *last_node; 
  GMarkupDomNode *current; 
};

Elle servira de variable interne pour la fonction de parcours de la structure XML : pas besoin de la mettre dans un fichier en-tête.

2-A-2. GmarkupDomNode

Cette structure devra être mise dans un fichier en-tête gmarkup-dom.h par exemple car on l'utilise pour mémoriser notre arborescence XML : c'est l'élément de base de la construction de notre arborescence.

GmarkupDomNode
Sélectionnez
typedef struct _GMarkupDomNode GMarkupDomNode;

struct _GMarkupDomNode
{
  gchar *name;
  gint item; /* numéro de l'item dans la liste des items du père */
  gint level;
  contenu * content;
  gint texte; /* nombre d'entrées texte */
  xml_attr *attributs;
  gint nb_att;
  contenu * com;
  gint nb_com;
  struct _GMarkupDomNode *parent;
  struct _GMarkupDomNode *child;
  gint fils; /* nombre de nodes fille */
};

On utilise dans GmarkupDomNode les deux structures personnelles suivantes qui devront être mises dans un fichier en-tête gmarkup-dom.h par exemple car on l'utilise pour mémoriser notre arborescence :

Struct contenu
Sélectionnez
typedef struct _xml_attr xml_attr; 


/* structures =============================================================== */ 
struct _xml_attr 
{ 
  gchar *name; 
  gchar *value; 
}; 

typedef struct { 
           gchar * texte; 
           gint    item; 
         } contenu;

3. Le format du stockage des données

Les structures définies précédemment nous donnent cette représentation pour l'élément de base stockage des données :

Image non disponible

Les flèches partent d'un entier dans une case jaune qui indique le nombre d'éléments du tableau vert pointé par le champ de la case saumon.

Les attributs arrivant tous en premier : dans la balise même. Par contre, pour les trois autres éléments, il faut mémoriser leur ordre d'arrivée afin de pouvoir le restituer. C'est pour ça que j'ai mis un champ item qui retient l'ordre d'arrivée de l'item en question parmi les éléments de la balise dans les deux structures qui gèrent les trois éléments suivants. Ces structures sont : contenu pour le texte et les commentaires et GmarkupDomNode pour les balises.

Image non disponible

4. Les fonctions de base

4-A. Les fonctions personnelles publiques

Elles sont à écrire (copier) nous-mêmes dans notre programme, ce sont les fonctions visibles de l'extérieur, celles qu'on va appeler. Le mieux est de les mettre dans un fichier spécifique comme gmarkup-dom.c et de mettre leur prototype dans un fichier en-tête : gmarkup-dom.h .

4-A-1. GMarkupDomNode *g_markup_dom_new (const gchar *, GError **error);

C'est la fonction d'entrée pour récupérer notre arborescence sous forme de liste chaînée.

g_markup_dom_new
Sélectionnez
GMarkupDomNode *g_markup_dom_new (const gchar *filename, GError **error) 
{ 
  GMarkupParseContext *markup_parse_context; 
  GMarkupDomNode * pere = (GMarkupDomNode *)malloc(sizeof(GMarkupDomNode)); 
   /* pere doit être alloué dynamiquement car il est utilisé par la fonction appelante */ 
  GMarkupDomContext context={0,0,pere,pere}; 
   /* ce n'est pas le cas de context qui n'est utilisé que les fonctions appelées  */ 

  pere->name=NULL; /* initialisation de la racine du fichier XML */ 
  pere->level=0; 
  pere->item=0; 
  pere->content=NULL; 
  pere->texte=0; 
  pere->attributs=NULL; 
  pere->nb_att=0; 
  pere->com=NULL; 
  pere->nb_com=0; 
  pere->parent=NULL; 
  pere->child=NULL; 
  pere->fils=0; 

  g_return_val_if_fail (filename != NULL, context.root); 
                  /* sort de la fonction si filename ne pointe pas sur une chaîne de caractères */ 

  {         /* création du contexte : fonctions à appeler suivant la nature de l'élément traité */ 
    GMarkupParser markup_parser; /* http://library.gnome.org/devel/glib/stable/glib-Simple-XML-Subset-Parser.html#GMarkupParser */ 

    markup_parser.start_element = xml_start_element; /* cas : ouverture de balise               */ 
    markup_parser.end_element = xml_end_element;     /* cas : fermeture de balise               */ 
    markup_parser.text = xml_text;                   /* cas : texte entre les balises           */ 
    markup_parser.passthrough = xml_com;             /* cas : commentaires entre les balises    */ 
    markup_parser.error = NULL;                      /* cas : erreur dans le fichier XML        */ 
    markup_parse_context = g_markup_parse_context_new (&markup_parser, 0, 
                                                       &context, NULL); 
  /* http://library.gnome.org/devel/glib/stable/glib-Simple-XML-Subset-Parser.html#g-markup-parse-context-new */ 
  } /* maintenant, on sait comment parcourir le fichier */ 
  { 
    gchar *text = NULL; 
    gsize length = -1; 

    g_file_get_contents (filename, &text, &length, error); 
    /* passe tout le fichier filename dans la chaîne de caractères text 
                                                        dont la longueur sera dans lenght */ 
    if (text != NULL) /* si on a récupéré le fichier */ 
    { 
      g_markup_parse_context_parse (markup_parse_context, text, length, error);  /* on lance le "parsage" du fichier XML */ 
      g_free (text), text = NULL;                                                /* on lance le parsage du fichier XML */ 
    } 
    g_free (markup_parse_context), markup_parse_context = NULL; 
 }
  return pere;
} 

On l'utilise très simplement :

Utilisation
Sélectionnez
GMarkupDomNode *node = g_markup_dom_new ("chemin vers le fichier XML", NULL);

Le premier paramètre est le chemin vers le fichier XML, le second est un pointeur sur un système d'erreur (voir http://library.gnome.org/devel/glib/stable/glib-Error-Reporting.html ).

4-A-2. void g_markup_dom_free (GMarkupDomNode *);

Comme l'arborescence est récupérée sous forme de liste chaînée et que le nombre d'éléments de la chaîne dépend du fichier XML, il est donc obligatoire que l'allocation mémoire pour la liste soit dynamique. Il faut donc libérer la mémoire après utilisation. Cette manipulation INDISPENSABLE sera faite par cette fonction :

g_markup_dom_free
Sélectionnez
void g_markup_dom_free (GMarkupDomNode *node)
{
  if (node != NULL)
  {
    gint i;

    g_free (node->name), node->name = NULL;
    for (i = 0; node->nb_att>i; i++)
    {
      g_free (node->attributs[i].name), node->attributs[i].name = NULL;
      g_free (node->attributs[i].value), node->attributs[i].value = NULL;
    }
    g_free (node->attributs), node->attributs = NULL;
    for (i = 0; node->texte>i; i++)
    {
      g_free (node->content[i].texte);
    }
    g_free(node->content);
    for (i = 0; node->nb_com>i; i++)
    {
      g_free (node->com[i].texte);
    }
    g_free(node->com);
    for (i = 0; node->fils>i; i++)
    {
      g_markup_dom_free (node->child+i);
    }
    g_free(node->child), node->child = NULL;

    if (node->level==0) g_free(node), node = NULL; /* il faut juste libérer la racine, les autres, sont dans des tableaux */
   }
}

L'appel est tout simple :

Utilisation
Sélectionnez
g_markup_dom_free (node) ;

4-A-3. La fonction d'écriture

4-A-3-A. Sortie sur l'écran

Cette fonction ne fait que l'affichage à l'écran de votre fichier XML parsé. À vous de l'adapter pour une autre forme de sortie. C'est une fonction récursive, c'est pour cela qu'on doit lui donner deux paramètres lors de son appel :

Utilisation
Sélectionnez
dom_print (node,0);

Le premier paramètre est la racine du système de mémorisation du fichier XML.

Voici la fonction :

dom_print
Sélectionnez
void dom_print (GMarkupDomNode *root,gint pos) 
{ 
#define INDENT(niv) \ 
  do                                \ 
  {                                 \ 
    gint i;                         \ 
    for (i = 1; i < (niv) - 1; i++) \ 
      g_print ("\t");               \ 
  } while (0) 

  gint texte=0; 
  gint fils=0; 
  gint com=0; 

  if (root != NULL) 
  { 

    if (root->level!=0) 
    { 
      gint i = 0; 

        INDENT (root->level); 
        g_print ("<%s", root->name); 

      while (root->nb_att>i) 
      { 
        g_print (" %s=\"%s\"", root->attributs[i].name, 
                               root->attributs[i].value); 
        i++; 
      } 
    } 
    if (root->fils+root->texte+root->nb_com!=0) 
    { 
      if (root->level!=0) g_print (">\n"); 
      while (texte<root->texte || fils<root->fils || com<root->nb_com) 
      { 
        if (root->nb_com>com) if (root->com[com].item==(texte+fils+com)) 
        { 
            INDENT (root->level + 1); 
            g_print ("%s\n", root->com[com].texte); 
            com++; 
        } 
        if (root->texte>texte) if (root->content[texte].item==(texte+fils+com)) 
        { 
            INDENT (root->level + 1); 
            g_print ("%s\n", root->content[texte].texte); 
            texte++; 
        } 
        if (root->fils>fils) if (root->child[fils].item==(fils+texte+com)) 
        { 
            dom_print (root->child+fils,pos+1); 
            fils++; 
        } 
      } 
      if (root->level) 
      { 
       INDENT (root->level); 
       g_print ("</%s>\n", root->name); 
      } 
    } 
    else 
      if (root->level) g_print ("/>\n"); 
  } 
  #undef INDENT 
}

4-A-3-B. Sortie dans un fichier

Cette fois la sortie se fait dans un fichier (ou un autre flux), c'est la fonction précédente que j'ai un peu modifiée. La principale différence se fait lors de l'appel : on passe le pointeur sur le flux en second paramètre en plus du premier et du dernier paramètre qui restent les mêmes.

dom_print
Sélectionnez
void dom_print (GMarkupDomNode *root,FILE * sortie,gint pos)
{
#define INDENT(niv,sortie) \
  do                                \
  {                                 \
    gint ii;                         \
    for (ii = 1; ii < (niv) - 1; ii++) \
      fprintf(sortie,"\t");         \
  } while (0)

  gint texte=0;
  gint fils=0;
  gint com=0;
  gint i;

  if (root != NULL)
  {

    if (root->level!=0)
    {
      i = 0;

      INDENT (root->level,sortie);
      fprintf(sortie,"<%s", root->name);

      while (root->nb_att>i)
      {
        fprintf(sortie," %s=\"%s\"", root->attributs[i].name,
                               root->attributs[i].value);
        i++;
      }
    }
    if (root->fils+root->texte+root->nb_com!=0)
    {
      if (root->level!=0) fprintf(sortie,">\n");
      while (texte<root->texte || fils<root->fils || com<root->nb_com)
      {
        if (root->nb_com>com) if (root->com[com].item==(texte+fils+com))
        {
            INDENT (root->level + 1,sortie);
            fprintf(sortie,"%s\n", root->com[com].texte);
            com++;
        }
        if (root->texte>texte) if (root->content[texte].item==(texte+fils+com))
        {
            INDENT (root->level + 1,sortie);
            for (i=0;root->content[texte].texte[i];i++)
            { /* compatibilité avec le xml */
                if (root->content[texte].texte[i]=='&')
                  fprintf(sortie,"&amp;");
                else if (root->content[texte].texte[i]=='<')
                  fprintf(sortie,"&lt;");
                else if (root->content[texte].texte[i]=='>')
                  fprintf(sortie,"&gt;");
                else
                fputc(root->content[texte].texte[i],sortie);
            }
            fputc('\n',sortie);
            texte++;
        }
        if (root->fils>fils) if (root->child[fils].item==(fils+texte+com))
        {
            dom_print (root->child+fils,sortie,pos+1);
            fils++;
        }
      }
      if (root->level)
      {
       INDENT (root->level,sortie);
       fprintf(sortie,"</%s>\n", root->name);
      }
    }
    else
      if (root->level)
      {
        fprintf(sortie,"/>\n");
      }
  }
  #undef INDENT
}

4-B. Les fonctions personnelles privées

Elles ne seront pas appelées par le reste du programme, elles servent aux procédures de parcours du fichier XML. Le mieux est de les mettre dans le fichier spécifique avec les fonctions publiques. Comme elles n'ont pas à être appelées par le reste du programme, on mettra simplement leur prototype au début du fichier contenant nos fonctions XML, pas besoin de les mettre dans un fichier à en-tête.

La compréhension de ces fonctions n'est pas utile pour l'utilisation des fonctions publiques décrites précédemment. Mais il est important de savoir ce qu'elles font si on veut travailler le code source.

4-B-1. int str_isspace (const gchar *s)

Vérifie si une chaîne de caractères contient un texte ou que des espaces, saut de lignes, ou autres éléments formant une chaîne considérée vide. Cette fonction fort pratique peut passer dans les fonctions publiques.

str_isspace
Sélectionnez
int str_isspace (const gchar *s)
{
  if (!s) return 1;
  for (; *s && (*s==' ' || *s=='\t' || *s=='\n'); s++);
  return !*s;
}

4-B-2. void xml_start_element (GMarkupParseContext *, const gchar *, const gchar **, const gchar **, gpointer , GError **)

Cette fonction initialise un nouveau niveau de balise.

xml_start_element
Sélectionnez
void xml_start_element (GMarkupParseContext *context, const gchar *element_name,
                        const gchar **attribute_names,
                        const gchar **attribute_values, gpointer user_data,
                        GError **error)
{
  gint i,j;
  GMarkupDomContext *dom_context = user_data;
  GMarkupDomNode *node = NULL, * parent;

  g_return_if_fail (dom_context != NULL); /* déclaration et initialisation du nouvel élément   */

 /* déclaration et initialisation du nouvel élément   */

  if (dom_context->root) /* si ce n'est pas la première node, la node mère du système */
  {
      parent=dom_context->current;
      //for (parent=dom_context->current;dom_context->level<parent->level;parent=parent->parent);
      parent->fils++;                 /* un for car il est possible qu'on vienne de descendre de niveaux */
      parent->child=realloc(parent->child,sizeof (*node)*parent->fils); /* on rajoute un fils au père     */
      for (i=0;i<parent->fils-1;i++)  /* ne pas oublier que tous les fils ont changé d'adresse           */
        for (j=0;j<parent->child[i].fils;j++)
            parent->child[i].child[j].parent=parent->child+i;
      node=parent->child+parent->fils-1; /* on récupère le lien vers la nouvelle node crée chez le père  */
      node->parent=parent;            /* j'informe sur le parent de cette node                           */
      node->item=dom_context->item;   /* j'informe sur l'ordre de cet élément dans l'ensemble des        */
  }                                   /* éléments (texte, node et commentaires) du père                  */
  else /* si c'est le premier élément */
  {
    node = g_malloc (sizeof (*node));
    dom_context->root = node;
    node->item=0;
    node->parent=NULL;
  }
  dom_context->current = node;

  /* variable générale de l'état actuel du système  */
  /* la construction d'un nouvel élément est toujours demandée par le père donc on */
  dom_context->level++;          /* vient de monter le niveau de 1 . mise dans  la */
  dom_context->item=0;           /* pas encore d'entrée dans cette node            */


  /* initiaisation de la node */
  node->name = g_strdup (element_name);
  node->content = NULL;
  node->texte   = 0;     /* pas encore de texte        */
  node->com = NULL;
  node->nb_com  = 0;     /* pas encore de commentaires */
  node->child = NULL;
  node->fils=0;          /* pas encore de fils         */
  node->level = dom_context->level; /* niveau du noeud */

  /* Copy attributs */
  for (i = 0; attribute_names[i] != NULL; i++); /* on compte le nombre d'attributs */
  if (i!=0 ) node->attributs = g_malloc (sizeof (*node->attributs)*i);
  else       node->attributs = NULL;

  node->nb_att=i;
  for (i = 0; attribute_names[i] != NULL; i++) /* on parcourt les attributs        */
  {
                                            /* on informe leur nom et valeur       */
    node->attributs[i].name = g_strdup (attribute_names[i]);
    node->attributs[i].value = g_strdup (attribute_values[i]);
  }

  /* Unused parameters */
  (void)context;
  (void)error;
}

4-B-3. static void xml_end_element (GMarkupParseContext *, const gchar *, gpointer, GError **)

Cette fonction ferme un niveau de balise.

xml_end_element
Sélectionnez
void xml_end_element (GMarkupParseContext *context,
                             const gchar *element_name, gpointer user_data,
                             GError **error)
{
  GMarkupDomContext *dom_context = user_data;

  g_return_if_fail (dom_context != NULL);
  g_return_if_fail (dom_context->current != NULL);

  /* je rends la main au père */
  dom_context->item=dom_context->current->item+1;        /* je passe à l'élément suivant du père */
  dom_context->level--;                                  /* le père a un niveau de moins         */
  dom_context->current   = dom_context->current->parent; /* la node courante sera celle du père  */

  /* Unused parameters */
  (void)context;
  (void)element_name;
  (void)error;
}

4-B-4. void xml_text (GMarkupParseContext *, const gchar *, gsize, gpointer, GError **)

Cette fonction récupère un texte entre deux autres éléments.

xml_text
Sélectionnez
void xml_text (GMarkupParseContext *context, const gchar *text,gsize text_len, gpointer user_data, GError **error)
{
  GMarkupDomContext *dom_context = user_data;
  gchar * ch=g_strdup (text);

  g_return_if_fail (dom_context != NULL);
  g_return_if_fail (dom_context->current != NULL);

  if (!str_isspace (text))/* si le texte n'est pas vide (saut de ligne ou espace ou tabulation */
  {                       /* on rajoute cet élément (voir rajout d'une node : c'est pareil     */
    dom_context->current->texte++;
    dom_context->current->content=realloc(dom_context->current->content,dom_context->current->texte*sizeof(contenu));
    dom_context->current->content[dom_context->current->texte-1].item=dom_context->item;
    dom_context->item++;
    dom_context->current->content[dom_context->current->texte-1].texte = g_strdup (mintexte(ch));
  }

  free(ch);
  /* Unused parameters */
  (void)context;
  (void)text_len;
  (void)error;
}

4-B-5. void xml_com (GMarkupParseContext *, const gchar *, gsize, gpointer, GError **)

Cette fonction récupère un commentaire entre deux autres éléments.

xml_com
Sélectionnez
void xml_com (GMarkupParseContext *context, const gchar *text,gsize text_len, gpointer user_data, GError **error)
{
  GMarkupDomContext *dom_context = user_data;

  g_return_if_fail (dom_context != NULL);
  g_return_if_fail (dom_context->current != NULL);

  if (!str_isspace (text)) /* si le texte n'est pas vide (saut de ligne ou espace ou tabulation */
  {                        /* on rajoute cet élément (voir rajout d'une node : c'est pareil     */
    dom_context->current->nb_com++;
    dom_context->current->com=realloc(dom_context->current->com,dom_context->current->nb_com*sizeof(contenu));
    dom_context->current->com[dom_context->current->nb_com-1].item=dom_context->item;
    dom_context->item++;
    dom_context->current->com[dom_context->current->nb_com-1].texte = g_strdup (text);
  }

  /* Unused parameters */
  (void)context;
  (void)text_len;
  (void)error;
}

4-B-6. gchar * mintexte(gchar * s)

Note : jusqu'à maintenant, j'ai présenté des fonctions dont le premier auteur est gege2061 même si parfois je les ai corrigées. Maintenant, je présente uniquement des fonctions dont je suis l'auteur.

Cette fonction enlève les blancs en fin et début de chaînes de caractères. Elle reçoit un pointeur sur le début de la chaîne, place un 0 sur le dernier caractère qui est considéré comme un caractère (pas espace, tabulation ou saut de ligne) et renvoi un pointeur sur le premier caractère qui est considéré comme un caractère.

Remarque : si le pointeur renvoyé pointe sur la valeur 0, alors la chaîne est considérée comme vide.

mintexte(gchar * s)
Sélectionnez
gchar * mintexte(gchar * s) 
{ 
  gchar * c; 

  for (; *s && (*s==' ' || *s=='\t' || *s=='\n'); s++); 
  c=s; 
  while(*c) 
   c++; 
  c--; 
  while (*c==' ' || *c=='\t' || *c=='\n') 
   c--; 
  *(c+1)=0; 

  return s; 
}

5. Fonctions évoluées

Ces fonctions servent à travailler avec l'arborescence représentant le fichier XML. On peut y chercher une première occurrence, y modifier un nœud (rajouter un élément, écrire un champ...), y déplacer ou copier des nœuds...

Ces fonctions et les suivantes sont surtout là en exemple de ce qu'on peut faire avec cette structure.

5-A. GMarkupDomNode * g_markup_dom_nom (GMarkupDomNode *node,gchar * nom_val,gchar * attr)

Cette fonction cherche dans l'ensemble des nodes filles de la node indiquée par le paramètre node, la première node qui a l'attribut indiqué par le paramètre attr à la valeur indiquée par le paramètre nom_val.

g_markup_dom_nom
Sélectionnez
GMarkupDomNode * g_markup_dom_nom (GMarkupDomNode *node,gchar * nom_val,gchar * attr) 
{ 

  GMarkupDomNode * tmp; 

  if (node != NULL)                     /* la node existe t-elle vraiment ?                            */ 
  { 
    gint i; 

    for (i = 0; node->nb_att>i; i++)    /* parcourt les attributs de cette node                         */ 
    {                                   /* à la recherche de l'attribut attr dont la valeur est nom_val */ 
      if (strcmp((node->attributs[i].value),nom_val)==0 && strcmp(node->attributs[i].name,attr)==0) 
       return node;                     /* si oui renvoie le pointeur sur la node                       */ 
    }                                   /* si le couple attribut/valeur n'a pas été trouvé              */ 
    for (i = 0; node->fils>i; i++)      /* parcourt des nodes filles                                    */ 
    {                                   /* c'est une procédure récursive                                */ 
      if ((tmp=g_markup_dom_nom (node->child+i,nom_val,attr)))  /* si le couple a été trouvé            */ 
       return tmp;                      /* renvoie de la node qui contient ce couple                    */ 
    } 

  } 
  return NULL;                          /* si rien n'a été trouvé dans la node ou parmi ces enfants,    */
}

Cette fonction est très pratique quand on place des attributs « name » qui doivent être uniques pour trouver une node dans tout un fichier XML.

5-B. GMarkupDomNode * g_markup_dom_node (GMarkupDomNode *node,gchar * nom_val)

Cette fonction recherche parmi les enfants de la node indiquée par le paramètre node, la première node dont le nom est indiqué par le paramètre nom_val

g_markup_dom_node
Sélectionnez
GMarkupDomNode * g_markup_dom_node (GMarkupDomNode *node,gchar * nom_val)
{

  GMarkupDomNode * tmp=NULL;

  if (node != NULL)                     /* marche comme g_markup_dom_nom, mais plus simplement          */
  {
    gint i;

    if (node->name && strcmp(node->name,nom_val)==0)
       return node;
    for (i = 0; node->fils>i; i++)
    {
      if ((tmp=g_markup_dom_node (node->child+i,nom_val)))
       return tmp;
    }

  }
  return NULL;
}

5-C. void copie_node(GMarkupDomNode * arrive,GMarkupDomNode * modele)

Cette fonction copie la node modele sur la node arrive.

copie_node
Sélectionnez
void copie_node(GMarkupDomNode * arrive,GMarkupDomNode * modele)
{
  if (modele != NULL)
  {
    gint i;

    *arrive=*modele;
    arrive->attributs=malloc(modele->nb_att*sizeof(xml_attr));
    for (i = 0; modele->nb_att>i; i++)
    {
        arrive->attributs[i].name=g_strdup( modele->attributs[i].name);
        arrive->attributs[i].value=g_strdup( modele->attributs[i].value);
    }

    arrive->content=(contenu *)malloc(modele->texte*sizeof(contenu));
    for (i = 0; modele->texte>i; i++)
    {
        arrive->content[i].item=modele->content[i].item;
        arrive->content[i].texte=g_strdup(modele->content[i].texte);
    }


    modele->com=(contenu *)malloc(modele->texte*sizeof(contenu));
    for (i = 0; modele->nb_com>i; i++)
    {
        arrive->com[i].item=modele->com[i].item;
        arrive->com[i].texte=g_strdup(modele->com[i].texte);
    }

    arrive->child=(GMarkupDomNode *)malloc(modele->fils*sizeof(GMarkupDomNode));
    for (i = 0; modele->fils>i; i++)
    {
      copie_node(arrive->child+i, modele->child+i);
      arrive->child[i].parent=arrive;
    }
  }
}

5-D. void xml_ajoute_fin(GMarkupDomNode * node,gchar * texte);

Cette fonction met une node fille en fin d'une node mère indiquée par le paramètre node, la node créée aura comme nom la chaîne pointée par le paramètre texte.

xml_ajoute_fin
Sélectionnez
void xml_ajoute_fin(GMarkupDomNode * node,gchar * texte) /* mettre une node fille en fin d'une node, le nom de la node étant indiqué par le texte  */
{
    gint i,j;

    node->child=(GMarkupDomNode *)realloc(node->child,(1+node->fils)*sizeof(GMarkupDomNode));
    node->child[node->fils].item=node->texte+node->nb_com+node->fils;
    /* place de la nouvelle node dans l'ensemble des enfants de la node mère    */
    node->child[node->fils].name=g_strdup(texte);
    /* nom de la nouvelle node                                                  */
    node->child[node->fils].level=node->level+1;
    /* nouveau niveau de la nouvelle node                                       */
    node->child[node->fils].content=NULL;
    /* nouveau contenu de la nouvelle node                                      */
    node->child[node->fils].texte=0;
    node->child[node->fils].attributs=NULL;
    node->child[node->fils].nb_att=0;
    node->child[node->fils].com=NULL;
    node->child[node->fils].nb_com=0;
    node->child[node->fils].parent=node;
    node->child[node->fils].child=NULL;
    node->child[node->fils].fils=0;
    for (i=0;i<node->fils;i++)
    {   /* mettre à jour les fils du fils à cause du réalloc                    */
        for(j=0;j<node->child[i].fils;j++)
            node->child[i].child[j].parent=node->child+i;
    }
    node->fils++;
    /* la node mère a un enfant de plus                                         */
}

5-E. void modif_xml(GMarkupDomNode *item,gchar * ch)

Cette fonction modifie le premier texte de la node indiquée par item. S'il a déjà un premier texte, il sera supprimé et remplacé par le texte pointé par ch. S'il n'y a pas encore de texte, on va ajouter un élément texte à la fin :

modif_xml
Sélectionnez
void modif_xml(GMarkupDomNode *item,gchar * ch) 
{ 
  if (item) 
  { 
      if (item->texte==0)                   /* s'il n'y a pas de texte, rajouter un texte à la fin de la node   */ 
      { 
       item->content=(contenu *)malloc(sizeof(contenu)); 
       item->texte=1; 
       item->content[0].item=item->nb_com+item->fils; 
      } 
      else                                  /* sinon, enlever le texte déjà présent                             */ 
       free(item->content[0].texte); 
      item->content[0].texte=g_strdup(ch);  /* mettre le nouveau texte à la place */ 
  }
}

5-F. void xml_ajoute_fin_texte(GMarkupDomNode * node);

Cette fonction rajoute un élément texte à la fin des enfants d'une node passée en paramètre.

xml_ajoute_fin_texte
Sélectionnez
void xml_ajoute_fin_texte(GMarkupDomNode * node) /* rajouter un élément texte en fin d'une node */ 
{ 
    node->content=(contenu *)realloc(node->content,(1+node->texte)*sizeof(contenu)); 
    node->content[node->texte].item=node->texte+node->nb_com+node->fils; 
    node->content[node->texte].texte=NULL; 
    node->texte++;
}

5-G. void xml_ecrit_dernier_texte(GMarkupDomNode * node,gchar * ch);

Cette fonction remplace le dernier texte d'une node indiquée par le paramètre node. Le pointeur ch indique le texte à mettre.

xml_ecrit_dernier_texte
Sélectionnez
void xml_ecrit_dernier_texte(GMarkupDomNode * node,char *ch) /* remplace le dernier texte d'une node */
{
    if (node->content[node->texte-1].texte!=NULL)
       free(node->content[node->texte-1].texte); /* libère un texte pouvant déjà être là */
    node->content[node->texte-1].texte=g_strdup(ch);
}

Les deux précédentes fonctions peuvent être combinées comme ceci pour créer une nouvelle node avec un texte :

nouvelle node avec un texte
Sélectionnez
void xml_ajoute_fin_attribut(GMarkupDomNode * node) /* rajouter un élément attribut */
{
    node->attributs=(xml_attr *)realloc(node->attributs,(1+node->nb_att)*sizeof(xml_attr));
    node->attributs[node->nb_att].name=NULL;
    node->attributs[node->nb_att].value=NULL;
    node->nb_att++;
}

5-H. void xml_ajoute_fin_attribut(GMarkupDomNode * node);

Cette fonction rajoute un élément attribut à la node pointée par le paramètre node.

xml_ajoute_fin_attribut
Sélectionnez
void xml_ajoute_fin_attribut(GMarkupDomNode * node) /* rajouter un élément attribut */
{
    node->attributs=(xml_attr *)realloc(node->attributs,(1+node->nb_att)*sizeof(xml_attr));
    node->attributs[node->nb_att].name=NULL;
    node->attributs[node->nb_att].value=NULL;
    node->nb_att++;
}

5-I. void xml_ecrit_dernier_attribut(GMarkupDomNode * node,char * nom, char * val);

Cette fonction remplace le dernier attribut d'une node indiquée par le pointeur node par un nouvel attribut dont le nom est indiqué par le pointeur nom et sa valeur par le pointeur valeur.

5.9 ) xml_ecrit_dernier_attribut
Sélectionnez
void xml_ecrit_dernier_attribut(GMarkupDomNode * node,char * nomm, char * val) /* remplace le dernier attribut */
{
    if (node->attributs[node->nb_att-1].name!=NULL) free(node->attributs[node->nb_att-1].name);     /* libère la place pouvant être occupée par le précédent nom                    */
    if (node->attributs[node->nb_att-1].value!=NULL) free(node->attributs[node->nb_att-1].value);   /* libère la place pouvant être occupée par la valeur de l'attribut précédent   */
    node->attributs[node->nb_att-1].name=g_strdup(nomm);
    node->attributs[node->nb_att-1].value=g_strdup(val);
}

Comme pour le précédent couple de deux fonctions, ces deux fonctions peuvent être combinées comme cela :

nouveau derbier attribut
Sélectionnez
xml_ajoute_fin_attribut(node) ;
xml_ecrit_dernier_attribut(node, "nom", "valeur") ;

5-J. void decale_node(GMarkupDomNode *node,unsigned short i);

Cette fonction décale les nodes filles de la node pointée par le paramètre node à partir de la place i (indiqué par le paramètre i) pour mettre la dernière node à la place i.

decale_node
Sélectionnez
void decale_node(GMarkupDomNode *node,unsigned short i)
{                                                       /* ATTENTION : laissez les textes et les commentaires inchangés */ 
    gint tmp,tmp1; 
    unsigned short j; 
    GMarkupDomNode tmp_node; 


    tmp=node->child[node->fils-1].item;                 /* place actuelle dans la hiérarchie de la dernière node        */ 
    tmp_node=node->child[node->fils-1];                 /* la dernière node actuelle. Attention, le tableau commence à 0*/ 

    for(j=node->fils-2;j>=i;j--)                        /* parcourt des nodes de la future avant dernière à la première */ 
    {                                                   /* à être déplacée                                              */ 
       tmp1=node->child[j].item;                        /* On mémorise la place de la node actuelle                     */ 
       node->child[j].item=tmp;                         /* On attribue à la node actuelle la place de la précédente     */ 
       tmp=tmp1;                                        /* on mémorise l'ancienne place de la node actuelle             */ 
       node->child[j+1]=node->child[j];                 /* On modifie le tableau des nodes filles                       */ 
    } 

    node->child[i]=tmp_node;                            /* la dernière node prend la place de la node i                 */ 
    node->child[i].item=tmp;                /* numéro de la node dans la liste des enfants des enfants de la node mère  */ 
}

5-K. void supprime_node(GMarkupDomNode *node,gint i);

Cette fonction supprime la ième node fille (i étant le paramètre i passé en paramètre) de la node mère indiquée par le paramètre node.

supprime_node
Sélectionnez
void supprime_node(GMarkupDomNode * pere,gint i)        /* supprime la ième node                                        */ 
{                                                       /* Attention : non testé avec les textes et les commentaires    */ 
    unsigned short j; 
    gint tmp=pere->child[i].item;                       /* tmp est l'indice de la node laissée vide                     */ 

    g_markup_dom_free(pere->child+i);                   /* libère la placé réservée pour la node                        */ 
    pere->fils--;                                       /* la node mère aura un enfant de moins                         */ 
    for(j=i;j<pere->fils;j++) 
    { 
        pere->child[j]=pere->child[j+1];                /* rapatrie le nœud suivant                                    */ 
        pere->child[j].item--;                          /* décrémenter sa place                                         */ 

    } 
    for (i=0;i>pere->texte;i++)                         /* parcourt des textes                                          */ 
        if (pere->content[i].item>tmp)                  /* si le texte est après la place laissée libre                 */ 
            pere->content[i].item--;                    /* décrémenter sa place                                         */ 
    for (i=0;i>pere->nb_com;i++)                        /* parcourt des commentaires                                    */ 
        if (pere->com[i].item>tmp)                      /* si le commentaire est après la place laissée libre           */ 
            pere->com[i].item--;                        /* décrémenter sa place                                         */ 
}

5-L. void xml_sup_node(GMarkupDomNode *node)

Ce serait presque un exercice à vous faire faire ! À partir de la fonction précédente, faire une fonction qui supprime une node dont on ne passe en paramètre qu'un pointeur vers cette node.

xml_sup_node
Sélectionnez
void xml_sup_node(GMarkupDomNode *node) /* supprime la node passée en paramètre */
{
    if (node==NULL || node->parent) return;
    supprime_node(node->parent,node-node->parent->child);
}

6. Fonctions pour les documents ODT

Toutes ces fonctions ont été réalisées pour travailler sur un document odt (de LibreOffice ou OpenOffice.org). Elles ont été testées et sont régulièrement utilisées dans un programme que vous pouvez récupérer ici : tirage de personnage pour ADD1 et ADD2

6-A. faciliter la lecture d'un fichier XML

Si on extrait le fichier content.xml du fichier ODT, on récupère un fichier illisible, car il est en une seule ligne. Pour pouvoir le lire, il suffit de l'ouvrir et de l'enregistrer :

Lecture fichier xml
Sélectionnez
GMarkupDomNode * ooo=g_markup_dom_new("content.xml",NULL);
enregistre_xml("fichier.xml",ooo);

Le nouveau fichier créé (fichier.xml) sera correctement indenté avec un saut de ligne à chaque nouvel item.

6-B. void xml_ecrit_dernier_texte_f(GMarkupDomNode * node,gchar * ch)

Cette fonction rajoute un texte avec saut de ligne dans le document content.xml qui contient le texte du fichier compressé odt.

La node où on rajoute ce texte est marquée par node. Le teste à rajouter est pointé par ch. Ce dernier texte peut contenir des sauts de ligne (caractère '\n'), ces sauts de lignes seront remplacés par une node fille <text:line-break/> pour faire le saut de ligne dans le document XML.

xml_ecrit_dernier_texte_f
Sélectionnez
void xml_ecrit_dernier_texte_f(GMarkupDomNode * node,gchar * ch) /* Gestion des sauts de ligne dans un texte */
{
    char * pt=ch;

    if (!(*ch)) return; /* texte vide => ne rien écrire */
    while (* pt)
    {
        if (*pt=='\n')
        {
            *pt=0;
            xml_ajoute_fin(node,"text:line-break");
            //if (ch+1!=pt)
            {
                xml_ajoute_fin_texte(node);
                xml_ecrit_dernier_texte(node,ch);
            }
            ch=pt+1;
            *pt='\n';
        }
        pt++;
    }
    if (ch!=pt)
    {
            xml_ajoute_fin(node,"text:line-break");
            xml_ajoute_fin_texte(node);
            xml_ecrit_dernier_texte(node,ch);
    }
}

7. Remerciements

Pour les relectures, je remercie Mahefasoa.16 commentaires Donner une note à l'article (5)