Gestion des fichiers XML avec GTK+

Bernard SIAUD

- Titre : Gestion des fichiers XML avec GTK+

- Auteur : Bernard SIAUD

- Parution : 29 août 2011

- Mise à jour : 4 mars 2013

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2013 Bernard SIAUD. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- 1. Présentation

- 2. Les structures utilisées :

- 2-A. types personnels

- 2-A-1. GmarkupDomContext

- 2-A-2. GmarkupDomNode

- 3. Le format du stockage des données

- 4. Les fonctions de base

- 4-A. Les fonctions personnelles publiques

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

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

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

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

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

- 4-B. Les fonctions personnelles privées

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

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

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

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

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

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

- 5. Fonctions évoluées

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

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

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

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

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

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

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

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

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

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

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

- 5-L. void xml_sup_node(GMarkupDomNode *node)

- 6. Fonctions pour les documents ODT

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

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

- 7. Remerciements

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 https://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

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

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

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 :

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.

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

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

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

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

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

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

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

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

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

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

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

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

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)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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. Commentez