Bien utiliser QtXml

| avito | dourouc05 |

Bienvenue dans le minitutoriel pour apprendre à utiliser QtXml qui permet d'utiliser des données XML dans une application Qt. Nous y apprendrons à utiliser le XML via DOM !

Il est recommandé de connaître les bases de Qt. Lisez donc en premier le tutoriel Qt si ce n'est pas encore fait.

Il est aussi recommandé de connaître le langage XML et de savoir comment il est fait. Si vous ne le savez pas, consultez un guide sur les bases de XML.

Commentez

- Titre : Bien utiliser QtXml

- Auteurs : avito, dourouc05

- Parution : 29 avril 2011

- Mise à jour : 5 août 2011

- Licence :

Sommaire

- I. L'objet DOM

- I-A. Le code

- I-B. Les explications

- II. Agir sur le document DOM : techniques de base

- III. Agir sur le document DOM : techniques avancées

- III-A. Les méthodes

- III-A-1. text()

- III-A-2. attribute()

- III-A-3. attributeNode()

- III-B. Leur utilisation

- IV. Écrire dans le document XML

- V. Remerciements

I - L'objet DOM

QtXml est un module qui permet d'utiliser des données XML dans un programme Qt. Pour que le compilateur sache que l'on veut utiliser QtXml, il faut ajouter dans le fichier .pro :

QT += xml

Puis, il ne reste plus qu'à préciser au compilateur que l'on veut utiliser le module en ajoutant un include, celui qui suit :

#include <QtXml>

Cet include, comme vous avez dû le remarquer, permet d'utiliser le module dans son code. Maintenant, le code requis pour chaque fichier.

I-A - Le code

Main.cpp

#include <QApplication>
#include <QtGui>
#include <QtXml>
#include "Xml_Dom.h"
int main(int argc, char *argv[])
{
QApplication app(argc,argv);
Xml_Dom *Dom = new Xml_Dom();
Dom->show();

return app.exec();
}

Xml_Dom.h

#ifndef XML_DOM
#define XML_DOM

#include <QtGui>
#include <QtXml>
#include <QWidget>

class Xml_Dom : public QWidget
{
public:
Xml_Dom();
~Xml_Dom();
// On ajoutera des variables et des méthodes après.
};

#endif

Xml_Dom.cpp

#include "Xml_Dom.h"
#include <iostream>
using namespace std;

Xml_Dom::Xml_Dom() : QWidget()
{
/*//////////////////////////////////////////////////////////////////////*/
/* On définira ici le document XML et les actions que l'on va effectuer */
/*//////////////////////////en premier lieu/////////////////////////////*/
/*//////////////////////////////////////////////////////////////////////*/
}

Xml_Dom::~Xml_Dom()
{

}

I-B - Les explications

Afin d'utiliser du XML, on va utiliser un objet DOM (Document Objet Model). Il faut donc définir un objet de ce type qui nous permettra d'agir sur le document XML. Afin de créer cet objet, il suffit d'utiliser la classe QDomDocument. Il faut l'instancier comme ceci :

QDomDocument *dom = new QDomDocument("Nom de votre document QDomDocument");]></code>
<paragraph>
Cette façon est la plus simple d'instancier un objet QDomDocument. Il en existe d'autres, qui ne seront d'aucune utilité pour le moment.
Il faut maintenant ouvrir le document XML ; pour cela, on utilise le code suivant :
</paragraph>
<code langage="qt" ><![CDATA[QFile xml_doc("nom_du_document_xml.xml");

Pour savoir si le fichier a bien été ouvert en lecture seule, on fait :

if(!xml_doc.open(QIODevice::ReadOnly)) // Si l'on n'arrive pas à ouvrir le fichier XML.
{
QMessageBox::warning(this, "Erreur à l'ouverture du document XML", "Le document XML n'a pas pu être ouvert.");
return;
}

Enfin, il faut attribuer le fichier XML à l'objet QDomDocument. On fait donc :

dom->setContent(&xml_doc);

Pour aller plus vite, on peut directement tester ce code. On écrira donc :

if (!dom->setContent(&xml_doc)) // Si l'on n'arrive pas à associer le fichier XML à l'objet DOM.
{
xml_doc.close();
QMessageBox::warning(this, "Erreur à l'ouverture du document XML", "Le document XML n'a pas pu être attribué à l'objet QDomDocument.");
return;
}

L'objet QDomDocument est maintenant créé ! Si vous avez bien suivi, vous devriez avoir ce code :

QDomDocument *dom = new QDomDocument("mon_xml");
QFile xml_doc("xml_doc.xml"); // On choisit le fichier contenant les informations XML.
if(!xml_doc.open(QIODevice::ReadOnly)) // Si l'on n'arrive pas à ouvrir le fichier XML.
{
QMessageBox::warning(this,"Erreur à l'ouverture du document XML","Le document XML n'a pas pu être ouvert.");
return;
}
if (!dom->setContent(&xml_doc)) // Si l'on n'arrive pas à associer le fichier XML à l'objet DOM.
{
xml_doc.close();
QMessageBox::warning(this, "Erreur à l'ouverture du document XML", "Le document XML n'a pas pu être attribué à l'objet QDomDocument.");
return;
}
xml_doc.close(); // Dans tous les cas, on doit fermer le document XML : on n'en a plus besoin, tout est compris dans l'objet DOM.

Vous n'avez qu'à placer ce code dans le constructeur de la classe pour l'instant. On peut maintenant utiliser l'objet juste créé !

Ne pas oublier de fournir les DLL QtXmld4.dll ou QtXml4.dll, en fonction des options de compilation ! Ne pas oublier non plus de mettre le document XML dans le même dossier que l'exécutable, sinon rien ne se passera !

Il ne sert à rien d'exécuter ce code maintenant, il ne fait rien.

II - Agir sur le document DOM : techniques de base

Maintenant que l'objet DOM est créé, on peut essayer de récupérer les données XML pour les utiliser. On va donc utiliser un objet QDomElement afin de récupérer tous les éléments du document que l'on veut étudier. Pour ce faire, on doit effectuer :

QDomElement dom_element = dom.documentElement();

Explication :

- on crée un objet QDomElement appelé dom_element ;

- on lui donne comme valeur tout le document XML : l'objet QDomDocument récupère le XML mais ne sait pas l'analyser !

On essaye maintenant d'obtenir le premier nœud afin de récupérer les autres et aussi les balises. Pour faire cela, on va utiliser un objet QDomNode. On fait donc :

QDomNode noeud = dom_element.firstChild();

Explication :

- on crée un objet QDomNode appelé noeud ;

- on lui donne comme valeur le premier nœud que dom_element a. Ce nœud contiendra tous les autres nœuds présents dans le document XML.

Maintenant, on regarde ce qu'on trouve ! Imaginons un fichier XML comme ceci :

<?xml version="1.0" encoding="ISO-8859-1"?>
<info_de_TF1>
<information>Un avion s'est crashé dans l'océan.</information>
<information>Une femme a gagné à l'Euro Millions.</information>
</info_de_TF1>

On va effectuer une boucle qui va nous donner des résultats tant qu'il y en a. on va donc faire :

while(!noeud.isNull())// Tant que le nœud n'est pas vide.
{
// Du code
}

On va ensuite créer un nouvel objet (QDomElement) qui contiendra les éléments courants (ceux que l'on va analyser). On va donc écrire dans le while :

QDomElement element = noeud.toElement();

On utilise la propriété toElement() afin de transformer le nœud en éléments. Cela permet aussi de récupérer l'élément ou le nœud courant.

On vérifie ensuite si le résultat n'est pas vide. Devinez... Quelle méthode va-t-on utiliser ?

if(!element.isNull())
{
// On indique le code à effectuer ici.
}

Maintenant, on va indiquer ce que l'on veut faire. Si l'on veut afficher le nom de la balise, on n'a qu'à faire element.tagName(), mais ce n'est pas le plus intéressant. On verra toutes les méthodes de programmation possibles dans la prochaine sous-partie. On va quand même rester sur le nom de la balise. Donc, pour afficher le nom de la balise dans un QMessageBox, on va mettre ce code :

QMessageBox::information(this, "Nom de la balise", "Le nom de la balise est :" + element.tagName());

Explication :

- on crée un objet QMessageBox::information qui affichera « Le nom de la balise est », suivi du nom de la balise courante ;

- on utilise la méthode tagName() afin de récupérer le nom de la balise.

Puis enfin, on demande à noeud de prendre le prochain résultat qu'il trouve. À insérer dans le while mais après le if :

noeud = noeud.nextSibling(); // Ce code permet d'aller à l'élément suivant.

On n'a plus qu'à insérer ce code dans le if, lui-même dans le while ! On devrait avoir le code complet suivant :

Xml_Dom.cpp

#include "Xml_Dom.h"
#include <iostream>
using namespace std;

Xml_Dom::Xml_Dom() : QWidget()
{
QDomDocument dom("mon_xml");
QFile xml_doc("xml_doc.xml");

if(!xml_doc.open(QIODevice::ReadOnly))
{
QMessageBox::warning(this, "Erreur à l'ouverture du document XML", "Le document XML n'a pas pu être ouvert.");
return;
}

if (!dom.setContent(&xml_doc))
{
xml_doc.close();
QMessageBox::warning(this, "Erreur à l'ouverture du document XML", "Le document XML n'a pas pu être attribué à l'objet QDomDocument.");
return;
}

QDomElement dom_element = dom.documentElement();
QDomNode noeud = dom_element.firstChild();

while(!noeud.isNull())
{
if(!dom_element.isNull())
QMessageBox::information(this, "Nom de la balise", "Le nom de la balise est " + dom_element.tagName());
noeud = noeud.nextSibling();
}
}

Xml_Dom::~Xml_Dom()
{

}

Et voilà votre premier document DOM avec Qt réussi ! Mais il faudrait quand même préciser d'autres choses sur ce module qu'est QtXml !

III - Agir sur le document DOM : techniques avancées

On a écrit un bon code juste au-dessus, mais il ne sert à rien dans cet état : les utilisateurs se fichent pas mal de connaître le nom de la balise. C'est pourquoi on va voir les différentes méthodes pour récupérer les informations.

III-A - Les méthodes

III-A-1 - text()

Afin de trouver le texte ou d'autres choses dans le document XML, on peut avoir recours aux méthodes de la classe QDomElement. La première est, sans aucun doute, la méthode text(). Elle sert à récupérer le texte situé à l'intérieur d'une balise (avec le document XML fourni plus haut, cela pourrait nous donner : une femme a gagné à l'Euro Millions). Elle ne demande aucun paramètre. On considère maintenant le code XML suivant :

<?xml version="1.0" encoding="ISO-8859-1"?>
<info_de_TF1>
<information id="0" value="Un avion s'est crashé dans l'océan" />
<information id="1" value="Une femme a gagné à l'Euro Millions" />
</info_de_TF1>

III-A-2 - attribute()

Si on veut trouver la valeur d'un des attributs d'une balise, il suffit d'utiliser la méthode attribute(). Son prototype est :

QString QDomElement::attribute (const QString & name, const QString & defValue = QString() ) const

Elle s'utilise comme ceci :

element.attribute("id", 0);

Explication :

- on recherche dans element si une balise a un attribut id (dans cet exemple) ;

- si l'attribut n'est pas trouvé, on renvoie le deuxième argument, ici 0. Cet argument est facultatif.

III-A-3 - attributeNode()

Une autre méthode utilisée est la méthode attributeNode(). Son prototype est :

QDomAttr QDomElement::attributeNode(const QString & name )

Cette fonction permet de donner la valeur d'un attribut (name). Elle renvoie 0 si l'attribut n'existe pas. Il existe bien d'autres méthodes, mais elles ne seront pas utiles ici. Vous pouvez aller les voir dans la documentation de QtXml (l'objet QDomElement).

III-B - Leur utilisation

Imaginons que nous voulions afficher une information en fonction de son id, il nous faudrait utiliser attribute (ou attributeNode si vous voulez). Comme on va le faire, il suffira de tester si l'identifiant que l'on récupère est égal à celui précisé. On utilisera aussi cette fonction afin d'afficher l'information. Créez une fenêtre et mettez le code suivant dans le constructeur de la classe Xml_Dom :

QSpinBox *id_demand = new QSpinBox(this);
QPushButton *open = new QPushButton("Voir la prochaine news", this);
QHBoxLayout *layout = new QHBoxLayout();
layout->addWidget(demand);
layout->addWidget(open);
setLayout(layout);

Au final, pour l'instant, l'interface devrait ressembler à ceci :

Maintenant, on va établir les connexions. Toujours dans le constructeur :

connect(id_demand, SIGNAL(valueChanged(int)), this, SLOT(change(int)));
connect(open, SIGNAL(clicked()), this, SLOT(openDom()));

Comme on peut le voir, on va utiliser des slots personnalisés : l'un servira à changer la valeur d'une variable qui sera l'identifiant demandé, l'autre ouvrira le document DOM et agira dessus.

Avant de commencer à vraiment coder, petite précision : ce petit programme sert à afficher une news en fonction de l'identifiant précisé dans le QSpinBox (la variable id_demand).

On s'occupe, dans un premier temps, de l'actualisation de la variable qui variera. En un premier lieu, on va remplacer le fichier Xml_Dom.h par :

Xml_Dom.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QtGui>
#include <QWidget>
#include <QtXml>

class Xml_Dom : public QWidget
{
Q_OBJECT

public:
Xml_Dom(QWidget *parent = 0);
~Xml_Dom();
private:
QFile xml_doc;
QDomElement dom;
QDomNode noeud;
QDomElement element
QSpinBox *id_demand;
QPushButton *open;
int ids;
private slots:
void openDom();
void change(int change);

};

#endif // WIDGET_H

Ce code donne les variables nécessaires ainsi que le constructeur, le destructeur et les slots.

On peut voir que la variable qui va donner l'identifiant s'appelle ids. Maintenant, dans votre constructeur, on ajoute :

ids = 0; // On initialise ids à 0.

On va maintenant voir le slot change(). Il est appelé quand le texte de id_demand est modifié. On va donc mettre ce texte dans une nouvelle variable. Voici le code de la fonction :

void Xml_Dom::change(int change)
{
ids = change;
}

Ce code attribue la valeur change à ids. Maintenant que l'on a défini ids, on va pouvoir exécuter des opérations sur le document DOM. Créez la fonction Xml_Dom::openDom() comme ceci :

void Xml_Dom::openDom()
{
QDomDocument dom("mon_xml");
QFile file("xml_doc.xml");
if (!file.open(QIODevice::ReadOnly))
return;
if (!dom.setContent(&file))
{
file.close();
return;
}
file.close();
QDomElement docElem = dom.documentElement();
QDomNode n = docElem.firstChild();
while(!n.isNull())
{
QDomElement e = n.toElement();
// C'est ici que l'on va marquer le code
n = n.nextSibling();
}
}

On va maintenant ajouter ce qui va permettre de vérifier si l'identifiant demandé est égal à ids. Pour cela, on va chercher l'attribut id. Comme ids est un int, on va aussi utiliser la fonction toInt(). Le code de test sera donc :

if(element.attribute("id").toInt() == ids)
QMessageBox::information(NULL, "Information", "La nouvelle news est :<br />" + e.attribute("value"));

Il suffit d'intégrer ce code entre la déclaration de element et l'autre partie du code pour le compléter.

IV - Écrire dans le document XML

Pour écrire dans un document XML, il faut d'abord passer par le document DOM afin de récupérer le fichier déjà pris. On crée un nouveau bouton et on l'appelle write_button, on utilise la méthode connect() comme ceci :

connect(write_button,SIGNAL(clicked()),this,SLOT(writeDom()));

write_button va servir à lancer la fonction writeDom(), qui va écrire dans le document XML. On crée la fonction writeDom() et on ajoute sous private slots son prototype. On va y ajouter le code qui va lire le document XML :

QDomDocument dom("mon_xml");
QFile doc_xml("xml_doc.xml");
if(!doc_xml.open(QIODevice::ReadOnly))
{
QMessageBox::critical(this,"Erreur","Impossible d'ouvrir le ficher XML");
doc_xml.close();
return;
}
if(!dom.setContent(&doc_xml))
{
QMessageBox::critical(this,"Erreur","Impossible d'ouvrir le ficher XML");
doc_xml.close();
return;
}
doc_xml.close();

On ajoute ensuite le code qui va explorer le nouveau document DOM :

QDomElement docElem = dom.documentElement();

On peut maintenant écrire dans le document XML. Pour cela, on va d'abord passer par le document DOM. On crée un nouveau QDomElement et on l'appelle write_elem. On va en même temps préciser comment s'appelle la balise.

QDomElement write_elem = dom.createElement("information"); // On crée un QDomElement qui a comme nom "information".

Le code ci-dessus va « préparer » une balise XML en la créant et en lui donnant déjà un nom (ici, information). On va ensuite lui donner des attributs avec setAttribute() :

write_elem.setAttribute("id", 2); // L'attribut id vaudra 2.
write_elem.setAttribute("value", "L'inflation arrive en France"); // L'attribut value vaudra "L'inflation arrive en France".

Le code ci-dessus ajoute à l'élément write_elem deux attributs : id et value. On leur donne une valeur prédéfinie, mais ce n'est pas obligatoire. Enfin, on met tout dans le document DOM en passant par un QDomElement. On passera donc par docElem :

docElem.appendChild(write_elem);// On associe write_elem à domElem.

appendChild() est une fonction qui permet d'ajouter au document DOM un nœud ou un élément. Son prototype est :

QDomNode QDomNode::appendChild (const QDomNode & newChild)

Le code ci-dessus ajoute l'élément write_elem au nœud docElem. Maintenant que la balise XML est créée et intégrée au document DOM, on peut actualiser le document XML. Pour ce faire, on va devoir écrire directement dans le fichier XML.

Pour écrire dans le fichier XML, il faut donner une chaîne de caractères de type QString. On va donc devoir transformer un objet QDomDocument en QString. Voici le code pour créer un objet QString, write_elem, qui reçoit un objet de type QDomDocument :

QString write_doc = doc.toString();

On utilise la méthode toString() pour transformer l'objet QDomDocument en QString. Cela donnera peut-être la même chose qu'un cast. Cette méthode n'est pas disponible partout !

On crée un objet de type QFile qui contiendra le fichier dans lequel écrire et on le teste aussi :

QFile fichier("xml_doc.xml");
if(!fichier.open(QIODevice::WriteOnly))
{
fichier.close();
QMessageBox::critical(this,"Erreur","Impossible d'écrire dans le document XML");
return;
}

Ensuite, on crée un objet de type QTextStream :

QTextStream stream(&fichier);

Puis on va enfin écrire dans ce fichier :

stream << write_doc; // On utilise l'opérateur << pour écrire write_doc dans le document XML.

À la fin de l'opération, on peut fermer le fichier :

fichier.close();

Avant le passage du programme :

Le programme :

Après le passage du programme :

V - Remerciements

Merci à Thibaut Cuvelier et à Claude Leloup pour leur relecture !