Developpez.com - Delphi
X

Choisissez d'abord la catégorieensuite la rubrique :


Utiliser un document DOM via MsXml avec Delphi.

Date de publication : 30/10/2005 , Date de mise à jour : 30/10/2005

Par BIG (big.developpez.com)
 

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.


I. Introduction
II. Lire un XML avec MsXml et le parser DOM
III. Ecrire un XML avec MsXml
IV. Utilisation de Xpath
V. Utilisation de XSLT
VI. Conclusion
VII. Téléchargement
VIII. Remerciements


I. Introduction

MsXml est un composant COM de Microsoft, implémentant les interfaces DOM, Xpath et XSLT.

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.

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.

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.

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

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 (employes.xml):
employes.xml
<?xml version="1.0"?>
<employes> 
	<employe no="2">
		<nom>Nelson</nom>
		<prenom>Roberto</prenom>
		<poste_tel>250</poste_tel>
		<salaire unite="UKP">40000</salaire>
	</employe> 
	<employe no="4"> 
		<nom>Young</nom>
		<prenom>Bruce</prenom> 
		<poste_tel>233</poste_tel>
		<salaire unite="USD">55500</salaire> 
	</employe> 
</employes>
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 va 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('employes.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, permettant ainsi au programme de continuer son exécution.

La méthode asynchrone est conseillée pour charger de gros documents en mémoire sans bloquer l'application, mais il faut tester la fin du chargement avant de travailler sur le document. 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.

La propriété readyState permet de savoir si le chargement a été correctement effectué ou non.

ReadyState vaut 'S_OK' si le chargement a été correctement effectué, ou renvoie un code de message d'erreur en cas d'échec.

Il est également possible d'assigner un "notifieur" à l'événement onreadystate de l'interface.

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

La lecture des noeuds se fait via la méthode nodeValue de l'interface IXMLDOMNode. La méthode NodeName renvoie quant à elle le nom du noeud sélectionné.

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

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;
Il existe une variante pour charger un document XML en mémoire, à partir d'une String: loadXML. Cette méthode est généralement utilisée pour créer un document XML avec une racine. 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;
Voici les différentes interfaces de MsXml. Nous n'utiliserons que celles marquées en gras :

IXMLDOMNode Interface DOM node
IXMLDOMNodeList Collection de noeuds 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

III. Ecrire un XML avec MsXml

L'écriture d'un fichier XML via DOM est très simple. La création d'un noeud 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 noeud 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 noeud à 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 noeud "ancêtre", par la méthode appendChild du noeud 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 noeuds 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 noeud 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 noeud 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 noeud existant, par la méthode setNamedItem de l'objet attribute dépendant du noeud.

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 noeud. Une méthode plus simple, consiste à transtyper un noeud 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 noeuds existants*)
La méthode createTextNode va créer un noeud texte, non attaché au document. On attache le noeud à son ancêtre par la méthode appendChild du noeud 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 noeud texte, et de l'assigner à un
     noeud 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 noeud sélectionné*)
La méthode createCDATASection permet de créer une zone CDATA (Character data). Une section CDATA est utilisée pour habiller 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 ignorer son contenu. 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 avant, essayons d'afficher dans un label le salaire de l'employé n°4, à savoir 55500 USD.

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('employees.xml');
  node := xml.documentElement.childNodes.item[1].childNodes.item[3];
  attrib := node.attributes.getNamedItem('unite');
  // 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. Par exemple, il serait tentant de vouloir afficher la valeur du noeud salaire par la méthode nodeValue. Or, selon la MSDN (Microsoft Developer Network ), un noeud de type 'element' renvoie toujours Null. Et le type de noeud par défaut est un noeud de type 'element'.

L'autre problème se situe au niveau de l'arborescence. Imaginez de vouloir récupérer une information se trouvant au 10ème 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 noeuds selon une requête. Le principe est simple : on imagine le document XML comme un disque dur, contenant divers dossiers et fichiers. Une balise est un dossier, et l'information est le fichier.

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

Avec Xpath, on utilisera une syntaxe similaire : /employe[1]/salaire

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 noeud, on peut donner un prédicat(1) à la requête. /employe[nom = 'Young']/salaire sélectionne le même élément. L'avantage est visible : plus besoin de connaître la position exacte du noeud !

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

//employe sélectionne tous les noeuds 'employe' dans une liste de noeuds (NodeList);

/employe[2]/salaire/@unite sélectionne l'attribut 'unite' (@ est le symbole pour sélectionner un attribut)

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

http://jerome.developpez.com/xmlxsl/xpath/
ou http://www.zvon.org/xxl/XPathTutorial/General_fre/examples.html
Une fois à l'aise avec les requêtes Xpath, utilisons-les avec MsXml.

L'unité MsXml fournit deux méthodes pour sélectionner des noeuds 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('employes.xml');
//sélectionne le noeud salaire du noeud employe ayant comme nom Young
  node := xml.selectSingleNode('//employe[nom="Young"]/salaire');
//sélectionne l'attribut unite du noeud courant
  attrib := node.selectSingleNode('./@unite');
  Memo1.Lines.Add(node.text + ' '+ attrib.nodeValue);
end;
Je rappelle que les requêtes Xpath peuvent débuter de la racine (/) ou du noeud 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:

  • //employe[1]/salaire, qui sélectionne le noeud 'salaire' du second noeud 'employe' ;
  • //employe[@no= '' 4'']/salaire, qui sélectionne le noeud 'salaire' du premier noeud 'employe' qui possède l'attribut no=4 ;
  • //employe[prenom= '' Bruce '' ]/salaire, qui sélectionne le noeud 'salaire' du premier noeud 'employe' qui possède Bruce comme prénom;
  • ...
Les possibilités sont beaucoup plus grandes avec Xpath, comparé au code de lecture basique.

Si nous voulons sélectionner plusieurs noeuds à la fois, nous pouvons utiliser la méthode selectNodes. Essayons d'afficher un listing de tous les salaires. 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('employes.xml');
//sélectionne tous les noeuds salaires
  nodes := xml.selectNodes('//employe/salaire');
  for i:=0 to nodes.length-1  do begin
    attrib:= nodes.item[i].selectSingleNode('./@unite');
    Memo1.Lines.Add(nodes.item[i].text+' '+attrib.nodeValue);
  end;
end;
Essayez la requête //salaire et comparez le résultat. Nous voyons que la requête peut être simplifiée. Ajoutez ensuite quelques noeuds 'employe' au document, et faites les petits exercices suivants :

  • afficher tous les salaires supérieurs à 50 000
  • afficher tous les salaires ayant l'unité USD
  • afficher les noms, prénoms des personnes ayant un salaire supérieur à 50 000 USD.
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 :

http://haypo.developpez.com/tutoriel/xml/xslt/
et http://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="employes"> 
  <HTML> 
  <BODY> 
  <H1>LISTING DES EMPLOYES</H1><BR /> 
    <TABLE> 
    <TR><TD>Employ&amp;eacute; n°</TD><TD>Nom</TD> 
      <TD>Pr&amp;eacute;nom</TD><TD>Unit&amp;eacute;</TD></TR> 
    <xsl:for-each select="employe"> 
    <xsl:sort select="nom" order="ascending" /> 
    <TR><TD><xsl:value-of select="@no" /></TD> 
    <TD><xsl:value-of select="nom" /></TD> 
    <TD><xsl:value-of select="prenom" /></TD> 
    <TD><xsl:value-of select="salaire/@unite" /></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 employes.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('employees.xml');
  xsl.load('tohtml.xsl');
  memo1.Text := xml.transformNode(xsl);
end;
Il 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 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 noeuds ne sont pas indentés). Ce problème peut néanmoins être corrigé via une feuille de style XSL.
info 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


VIII. Remerciements

Je tiens à remercier NoisetteProd, premier relecteur et qui m'a aidé à mettre ce premier tutoriel en ligne.

Merci aussi à Bestiol d'avoir corrigé les fautes d'orthographe qui ont pu se glisser par erreur dans ce travail.

Enfin, un grand merci à toute l'équipe de Developpez de m'accepter dans leur grande famille de rédacteurs.



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

Valid XHTML 1.1!Valid CSS!

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 © 2005 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.

Responsables bénévoles de la rubrique Delphi : Gilles Vasseur - Alcatîz -