Utiliser un document DOM via MsXml avec Delphi

BIG

Le but de cet article est de montrer comment utiliser les fonctionnalités d'un document DOM (Document Object Model) en utilisant l'API MsXml.

- Titre : Utiliser un document DOM via MsXml avec Delphi

- Auteur : BIG

- Parution : 3 août 2006

- Mise à jour : 7 décembre 2019

- 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 ® 2006 Big. 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

- I. Introduction

- II. Lire un XML avec MsXml et le parser DOM

- III. Écrire un XML avec MsXml

- IV. Utilisation de Xpath

- V. Utilisation de XSLT

- VI. Conclusion

- VII. Téléchargement

I - Introduction

Le but de cet article est de montrer comment utiliser les fonctionnalités d'un document DOM (Document Object Model) en utilisant l'API MSXML. Borland a porté la DLL de Microsoft (MSXML.dll) en unité du même nom, MSXML.pas. Ce document sert aussi à la compréhension du DOM pour gérer un fichier XML dans vos applications Delphi. Ainsi, vous pourrez utiliser facilement des fichiers XML comme support de base de données, support de communication entre vos applications et/ou Internet, remplacer les fichiers ini…

MSXML est un composant COM de Microsoft, implémentant les interfaces DOM, Xpath,WXS (XML-Schema) et XSLT.

MSXML est disponible en version 4. Pour ce tutoriel, j'ai utilisé Delphi 7 qui implémente MSXML v3.

DOM est une recommandation du W3C (World Wide Web Consortium), décrivant une interface totalement indépendante du langage de programmation et de la plate-forme utilisée. De ce fait, l'utilisation de DOM est quasi identique, quel que soit le langage utilisé.

DOM-1 (Level 1) a été publié en 1998 pour standardiser les documents. Le W3C a défini une manière précise de représenter un document (XML) sous forme d'un arbre. Le consortium a également proposé des méthodes de navigation standard, notamment pour la gestion des formulaires HTML.

En 2000, le W3C publie DOM-2, qui est le standard actuel. Depuis 2004, DOM existe aussi en version 3, apportant 16 modules différents.

Le parser DOM de MSXML est un parser DOM-2.

II - Lire un XML avec MsXml et le parser DOM

Soit le document XML suivant (adresses.xml) :

adresses.xml

<?xml version="1.0"?>
<Carnet>
<Personne>
<Nom>Milbal</Nom>
<Prenom>Jessie</Prenom>
<Adresse>Rue Sax, 43</Adresse>
<Localite CP='1000'>Bruxelles</Localite>
</Personne>
<Personne>
<Nom>Hainerve</Nom>
<Prenom>Kim</Prenom>
<Adresse>Avenue du Roy,17</Adresse>
<Localite CP='4000'>Liège</Localite>
</Personne>
</Carnet>

Pour lire ce document, il faut initialiser le parser DOM. Plaçons sur une fiche un bouton, qui va charger le document XML, et un mémo qui permettra d'afficher le document.

La création du parser se fait via l'interface IXMLDOMDocument et l'objet associé, CoDOMDocument.

procedure TForm1.LoadButtonClick(Sender: Tobject);
var DOMDoc : IXMLDOMDocument; //l'interface d'un document DOM
begin
DOMDoc := CoDOMDocument.Create; //création de l'objet correspondant.
with DOMDoc do begin
async := False; //Permet de charger un document XML en mode synchrone
if load('adresses.xml') then begin
MemoResult.Lines.Add(xml); //affiche le document xml en entier dans un mémo
ShowMessage(documentElement.nodeName); //affiche la racine du document
end;
end;
end;

Expliquons un peu ce code.

L'objet document XML est créé via CoDOMDoc, qui renvoie une interface de type IXMLDOMDocument.

La propriété async permet de charger le document xml en mode synchrone ou asynchrone.
Par défaut, le chargement se fait toujours en mode synchrone. La différence résulte dans le mode de fonctionnement : en mode synchrone, le document va être chargé en mémoire, puis le parser va rendre la main au programme pour continuer le traitement. En mode asynchrone, un thread va être créé pour charger le document au fur et à mesure, permettant ainsi au programme de continuer son exécution.
Il faut alors tester la propriété readyState afin de savoir si le document est chargé en mémoire. ReadyState vaut 'S_OK' si le chargement a été correctement effectué, sinon il renvoie un code d'erreur.
La méthode asynchrone est conseillée pour charger de gros documents en mémoire sans bloquer l'application.
On préférera donc utiliser le mode asynchrone pour le préchargement des données ou leur lecture. On préférera le mode synchrone pour travailler directement sur le document.

Le chargement d'un document XML se fait par plusieurs méthodes. La plus courante est l'appel de load(xmlSource: OLEVariant). Cette méthode va charger le document en mémoire, puis l'analyser avec le parser DOM.

Il existe une variante pour charger un document XML en mémoire, à partir d'une String : loadXML(bstrXML: WideString). Cette méthode est généralement utilisée pour créer un document XML avec une racine, ou pour charger un document XML contenu dans un flux, par exemple. La string entrée en paramètre doit posséder la syntaxe correcte du XML.

var DOMDoc: IXMLDOMDocument;
begin
DOMDoc := CoDOMDocument.Create;
DOMDoc.loadXML('<racine/>'); //crée un document XML à partir de la phrase
end;

Il est également possible de déclencher une notification en assignant l'événement onreadystate de l'interface. Cet événement est très utile pour les chargements asynchrones.

La navigation dans ce document se fait à partir de la racine, via les méthodes childNodes pour les nœuds enfants, et attributes, pour les attributs situés dans le nœud courant.

La lecture des nœuds se fait via les propriétés nodeValue et text de l'interface IXMLDOMNode. La méthode NodeName renvoie quant à elle le nom du nœud sélectionné.

var root, node : IXMLDOMNode;
begin
root := DOMDoc.DocumentElement; //recherche de l'élément racine;
node := root.childnodes[0]; //sélection du premier enfant
ShowMessage(node.NodeValue); //affichage du nom de la racine, employes dans l'exemple
end;

Comment savoir quelle propriété utiliser pour obtenir les données ? DOM traite les nœuds selon des types différents. Le type le plus versatile est le type element. Il regroupe les autres types. Il existe aussi des nœuds texte, attributs…
En règle générale, la propriété nodeValue permet de lire la valeur d'un nœud de type element (et de ses dérivés). Or, selon la MSDN (Microsoft Developer Network ), un nœud de type element renvoie toujours Null. Et le type de nœud par défaut est un nœud de type element. On utilisera alors la propriété text, spécifique aux nœuds de type texte, qui renverra le contenu entre les balises du document.

Ce système de navigation est fastidieux pour les grands fichiers xml, aussi il est conseillé d'utiliser Xpath pour atteindre directement l'information souhaitée. Nous verrons Xpath plus loin.

Voici les différentes interfaces de MsXml. Nous n'utiliserons que celles marquées en gras :

IXMLDOMNode

Interface DOM node

IXMLDOMNodeList

Collection de nœuds DOM

IXMLDOMDocument

Interface DOM document

IXMLDOMElement

Interface DOM element

IXMLDOMAttribute

Interface DOM attribute

IXMLDOMCharacterData

Interface de manipulation de character data DOM

IXMLDOMText

Interface DOM texte (text)

IXMLDOMComment

Interface DOM commentaires (comment)

IXMLDOMCDATASection

Interface DOM CDATA

IXMLDOMProcessingInstruction

Interface DOM PI

IXMLDOMParseError

Interface DOM document parse error

IXSLTemplate

Interface pour feuille de style XSL

IXSLProcessor

Interface du processeur XSL

IVBSAXXMLReader

Interface du parser SAX

IVBSAXContentHandler

Interface SAX content event handler

IVBSAXAttributes

Interface SAX attributes content event handler

IVBSAXErrorHandler

Interface SAX error handler event

IXMLHTTPRequest

Interface d'accès HTTP orienté client

IServerXMLHTTPRequest

Interface d'accès HHTP orienté serveur

La documentation complète est disponible dans la MSDN.

III - Écrire un XML avec MsXml

L'écriture d'un fichier XML via DOM est très simple. La création d'un nœud se fait par la méthode CreateNode, ou un de ses dérivés : CreateElement, CreateText, CreateAttribute, CreateComment et CreateCDataSection.

La méthode CreateNode est rarement utilisée, on préférera utiliser les autres méthodes pour créer le type de nœud requis.

procedure TForm1.SaveButtonClick(Sender: TObject);
var
DOMDoc: IXMLDOMDocument;
node: IXMLDOMNode;
element: IXMLDOMElement;
attribute: IXMLDOMAttribute;
texte: IXMLDOMText;
comment: IXMLDOMComment;
CData : IXMLDOMCDATASection;
begin
DOMDoc := CoDOMDocument.Create;
with DOMDoc do begin
async :=false;
documentElement := createElement('root');  //création de l'élément racine
node := createNode('element','node',''); //méthode globale, le premier argument peut être
(*
* element : identique à la méthode createElement
* text : identique à la méthode createText
* cdata: identique à la méthode createComment
* comment: identique à la méthode createCDataSection
* attribute: identique à la méthode createAttribute
*)
documentElement.appendChild(node);
(*appendChild ajoute le nœud à l'élément parent*)

On utilisera plus fréquemment la méthode CreateElement, la plus versatile, car elle dispose de fonctions pour ajouter une série d'attributs, ainsi que du texte.

L'élément étant indépendant, il est nécessaire de l'attacher à un nœud « ancêtre », par la méthode appendChild du nœud parent.

element := createElement('element');
element.setAttribute('attribute','setAttribute method');
element.text := 'Text Element';
documentElement.appendChild(element);
(*La création d'un élément est une des manières les plus simples à
utiliser, car elle permet de créer facilement des nœuds textes et des
attributs, sans passer par la méthode décrite ci-dessous.
La propriété text de l'élément revient à assigner un nœud de type
texte à cet élément*)

L'écriture d'un attribut sur un élément est très simple ; on utilise la méthode setAttribute([nom de l'attribut], [valeur de l'attribut]). De même, la méthode getAttribute([nom de l'attribut]) permet d'atteindre directement la valeur de l'attribut sélectionné.

La méthode createAttribute permet de créer un attribut sur un nœud xml. Un attribut est une valeur non textuelle.

exemple :

<salaire unite= ''USD ''>55000</salaire>

L'attribut étant créé de manière indépendante, il faut l'affecter à un nœud existant, par la méthode setNamedItem de l'objet attribute dépendant du nœud.

Cette méthode est plus longue, mais explique le fonctionnement interne de la méthode setAttribute sur un élément DOM.

On peut lire un attribut via la méthode getNamedItem de l'objet attribute associé à un nœud. Une méthode plus simple, consiste à transtyper un nœud en élément, afin d'utiliser la méthode getAttribute.

attribute := createAttribute('attrib');
attribute.value:= 'attribute method';
documentElement.attributes.setNamedItem(attribute);
(*Cette méthode est plus longue, mais permet d'assigner des attributs aux
divers types de nœuds existants*)

La méthode createTextNode va créer un nœud texte, non attaché au document. On attache le nœud à son ancêtre par la méthode appendChild du nœud parent.

Il est possible d'arriver au même résultat avec un élément, en assignant la propriété text de l'élément (voir plus haut).

texte := createTextNode('text node');
documentElement.appendChild(texte);
(*Cette méthode permet de créer un nœud texte, et de l'assigner à un
nœud quelconque*)

La méthode createComment permet de créer un commentaire xml. C'est une des seules fonctions qu'il est impossible de créer via la méthode createElement.

comment := createComment('comments');
documentElement.insertBefore(comment,node);
(*crée un élément de type commentaire.
La méthode insertBefore permet de définir l'emplacement du commentaire
dans le nœud sélectionné*)

La méthode createCDATASection permet de créer une zone CDATA (Character data). Une section CDATA est utilisée pour délimiter des blocs de texte possédant des caractères qui seraient reconnus comme du balisage par le parser.

Une section CDATA se reconnaît par la forme <![CDATA[ bloc de texte ]] >. Le parser rencontrant cette balise va simplement retransmettre le contenu tel quel, sans l'analyser. On utilise généralement les balises CDATA dans les documents XHTML pour définir des zones de scripts.

cdata := createCDATASection('CData Section');
documentElement.insertBefore(cdata,comment);
(*Permet de créer une section CDATA*)
save('test-xml.xml');
MemoResult.Lines.Add(xml);
end;
end;

Grâce à toutes ces méthodes, vous serez capable de générer un document XML bien formé.

IV - Utilisation de Xpath

Reprenons le document XML du début. Grâce aux méthodes de lecture décrites précédemment, essayons d'afficher dans un label l'adresse Kim Hainerve, à savoir Avenue du Roy, 17 4000 Liège.

Créons pour cela un nouveau projet, posons un mémo et un bouton. Dans la clause uses, ajoutons l'unité msxml.

procedure TForm1.Button1Click(Sender: TObject);
var
xml : IXMLDOMDocument;
node, attrib: IXMLDOMNode;
begin
xml := CoDOMDocument.Create;
xml.async := false;
xml.load('adresses.xml');
node := xml.documentElement.childNodes.item[1].childNodes.item[3];
attrib := node.attributes.getNamedItem('CP');
// ou  attrib := node.attributes.item[0] ;
Memo1.Lines,Add(node.text + ' '+ attrib.nodeValue);
end;

On remarque tout de suite les contraintes liées à ce mode de lecture. Il faut en effet connaître la position exacte de chaque élément, ainsi que son type.

Le problème se situe au niveau de l'arborescence. Imaginez de vouloir récupérer une information se trouvant au 10e niveau. Les lignes de code deviennent vite longues.

Les plus courageux verront un système complexe de boucles et de fonctions récursives, afin de pouvoir récupérer les informations souhaitées. Mais il existe une solution simple et efficace : Xpath !

Xpath est à XML ce que SQL est aux bases de données. Xpath permet de sélectionner un ou plusieurs nœuds selon une requête. Le principe est simple : on imagine le document XML comme un disque dur, contenant divers dossiers et fichiers. Une balise correspond à un dossier, et l'information correspond à un fichier.

Sous Windows, on peut accéder à un fichier en entrant une adresse : c:\documents and settings\Big\documents\msxml.odt

Avec Xpath, on utilisera une syntaxe similaire : /Personne[2]/Prenom

C'est quand même beaucoup plus simple que d'utiliser une série de childnodes.item pour retrouver l'information, non ? Mais Xpath ne se limite pas à ça : si on ne connaît pas l'emplacement exact du nœud, on peut donner un prédicat(1) à la requête. /Personne[Nom = 'Hainerve']/Prenom sélectionne le même élément. L'avantage est visible : plus besoin de connaître la position exacte du nœud !

Nous pouvons également sélectionner plusieurs nœuds en même temps, ainsi que des attributs :

//adresse sélectionne tous les nœuds 'adresse' dans une liste de nœuds (NodeList) ;

/Personne[2]/Localite/@CP sélectionne l'attribut 'CP' (@ est le symbole pour sélectionner un attribut).

Le but de ce tutoriel n'étant pas d'expliquer Xpath en détail, je vous conseille de vous familiariser un peu avec les diverses requêtes, en visitant les liens suivants :

https://jerome.developpez.com/xmlxsl/xpath/
ou http://www.zvon.org/xxl/XPathTutorial/General_fre/examples.html

Une fois aguerri aux requêtes Xpath, utilisons-les avec MsXml.

L'unité MsXml fournit deux méthodes pour sélectionner des nœuds avec des requêtes Xpath :

- selectSingleNode(QueryString: String): IXMLDOMNode permet de sélectionner un élément ;

- SelectNodes(QueryString: String): IXMLDOMNodeList permet de sélectionner plusieurs éléments.

Nous allons refaire le même projet que tout à l'heure, mais en utilisant une requête Xpath. Modifions la procédure du bouton afin d'utiliser selectSingleNode.

procedure TForm1.Button1Click(Sender: TObject);
var
xml : IXMLDOMDocument;
node, attrib: IXMLDOMNode;
begin
xml := CoDOMDocument.Create;
xml.async := false;
xml.load('adresses.xml');
//sélectionne le nœud Prenom du nœud Personne ayant comme nom Hainerve
node := xml.selectSingleNode('//Personne[Nom="Hainerve"]/Prenom');
//sélectionne l'attribut CP du nœud Adresse
attrib := node.selectSingleNode('//Personne[Nom='Hainerve']/Localite/@CP');
Memo1.Lines.Add(node.text + ' '+ attrib.nodeValue);
end;

Je rappelle que les requêtes Xpath peuvent débuter de la racine (/) ou du nœud actuel (./)

Le code est plus compréhensible, non ? Grâce au prédicat, nous effectuons un filtre de recherche. Nous aurions obtenu le même résultat avec d'autres prédicats, tels que :

- //Personne[1]/Prenom, qui sélectionne le nœud 'Prenom' du premier nœud 'Personne' ;

- //Localite[@CP="4000"], qui sélectionne le nœud 'Localite' du premier nœud qui possède l'attribut CP=4000 ;

- //Presonne[prenom="Kim"]/Adresse, qui sélectionne le nœud 'Adresse' du premier nœud 'Personne' qui possède Kim comme prénom ;

- …

Les possibilités sont beaucoup plus grandes avec Xpath, comparé au code de lecture basique.

Si nous voulons sélectionner plusieurs nœuds à la fois, nous pouvons utiliser la méthode selectNodes. Essayons d'afficher un listing de toutes les adresses. Modifions le code comme ceci :

procedure TForm1.Button1Click(Sender: TObject);
var
xml : IXMLDOMDocument;
nodes : IXMLDOMNodeList;
attrib : IXMLDOMNode;
i: integer;
begin
xml := CoDOMDocument.Create;
xml.async :=false;
xml.load('adresses.xml');
//sélectionne tous les nœuds Adresse
nodes := xml.selectNodes('//Personne/Adresse');
for i:=0 to nodes.length-1  do begin
attrib:= nodes.item[i].selectSingleNode('../Localite/@CP');
Memo1.Lines.Add(nodes.item[i].text+' '+attrib.nodeValue);
end;
end;

Essayez la requête //Adresse et comparez le résultat. Nous voyons que la requête peut être simplifiée. Ajoutez ensuite quelques nœuds 'Personne' au document, et faites les petits exercices suivants :

- afficher toutes les adresses avec un CP supérieur à 5000 ;

- afficher les noms, prénoms et adresse par ordre de CP croissant.

Grâce à Xpath, vous pouvez gérer facilement une petite base de données !

V - Utilisation de XSLT

XSLT (eXtended Stylesheet Language Transformations). Le but de XSLT est de transformer un document XML en un autre document texte (texte, html, xml). XSLT se base sur Xpath pour naviguer dans le document source.

Pour se familiariser avec XSLT, je vous conseille les sites suivants :

https://haypo.developpez.com/tutoriel/xml/xslt/
et https://haypo.developpez.com/tutoriel/xml/xslt/programmation/

Nous utiliserons, pour ce tutoriel, le document XSLT suivant :

tohtml.xsl

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output omit-xml-declaration="yes" />
<xsl:template match="Carnet">
<HTML>
<BODY>
<H1>LISTING DES CONTACTS</H1>
<TABLE>
<TR>
<TD>Nom</TD>
<TD>Prenom</TD>
<TD>Adresse</TD>
<TD colspan='2'>Localite</TD>
</TR>
<xsl:for-each select="Personne">
<xsl:sort select="Nom" order="ascending" />
<TR>
<TD><xsl:value-of select="Nom" /></TD>
<TD><xsl:value-of select="Prenom" /></TD>
<TD><xsl:value-of select="Adresse" /></TD>
<TD><xsl:value-of select="Localite/@CP"/></TD>
<TD><xsl:value-of select="Localite"/></TD>
</TR>
</xsl:for-each>
</TABLE>
</BODY>
</HTML>
</xsl:template>
</xsl:stylesheet>

Ce fichier va nous permettre de créer une page html basée sur le document xml adresses.xml.

MsXml dispose de la fonction transformNode(stylesheet: IXMLDOMDocument) : IXMLDOMDocument.

Créons un dernier projet, toujours avec un mémo et un bouton.

procedure TForm1.Button1Click(Sender: Tobject);
var
xml,xsl : IXMLDOMDocument;
begin
xml := CoDOMDocument.Create;
xsl := CoDOMDocument.Create;
xml.load('adresses.xml');
xsl.load('tohtml.xsl');
memo1.Text := xml.transformNode(xsl);
end;

C'est donc très facile d'appliquer une feuille de style XSL à un document. Grâce à XSL, vous pouvez exporter vos données dans d'autres formats de texte !

VI - Conclusion

L'utilisation de MSXML est très simple d'emploi pour gérer des documents XML orientés données.

Comparé aux autres parsers que sont Xerces et Open XML, ses avantages sont :

- légèreté du code ;

- documentation complète (sur la MSDN) ;

- puissance de recherche (via Xpath) et de traitement ;

- possibilité d'utiliser des feuilles de style XSL.

Ses principaux inconvénients sont :

- il n'est disponible que pour la plate forme Windows ;

- il n'est pas prévu pour un affichage direct (les nœuds ne sont pas indentés). Ce problème peut néanmoins être corrigé via une feuille de style XSL.

Il est à noter que le composant OmniXML permet de travailler plus facilement avec l'implémentation de MsXml, sur lequel il est basé.

VII - Téléchargement

Version PDF de cet article :
Miroir 1 : Version PDF
Dans le cas où le miroir 1 ne fonctionne pas :
Miroir 2 : Version PDF

(1)Un prédicat est un critère de recherche. Le prédicat est toujours signalé entre crochets, et s'applique au nœud courant, sauf en cas de présence des modificateurs d'accès (.. , /, @, etc)