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 :

 
Sélectionnez
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 :

 
Sélectionnez
#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
Sélectionnez
#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
Sélectionnez
#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
Sélectionnez
#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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
dom->setContent(&xml_doc);

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

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
<?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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 ?

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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
Sélectionnez
#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 :

 
Sélectionnez
<?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 :

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

Elle s'utilise comme ceci :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

Image utilisateur

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

 
Sélectionnez
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
Sélectionnez
#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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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.

 
Sélectionnez
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() :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
QTextStream stream(&fichier);

Puis on va enfin écrire dans ce fichier :

 
Sélectionnez
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 :

 
Sélectionnez
fichier.close();

Avant le passage du programme :

Image utilisateur

Le programme :

Image utilisateur

Après le passage du programme :

Image utilisateur

V. Remerciements

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