VBA et développement Web

Image non disponible


précédentsommairesuivant

V. Librairie XML

Pour ce chapitre, ajoutez d'abord une référence à la librairie Microsoft XML.
La version 3.0 suffit pour notre besoin.

Si vous n'avez aucune connaissance XML, vous pouvez d'abord consulter les Cours XML.

V-A. Créer un fichier XML

Un fichier XML est en fait un simple fichier texte formaté selon une norme.
On peut donc écrire ce fichier tout simplement comme n'importe quel fichier texte, ou utiliser les méthodes de la librairie XML.

V-A-1. Avec un éditeur texte

Il suffit d'ouvrir NotePad, WordPad, ou Word par exemple, et y écrire du code XML :

 
Sélectionnez

<?xml version="1.0" encoding="ISO-8859-1"?>
<racine>
    <info1>test élément 1</info1>
    <info2>test élément 2</info1>
    <info>
        <subinfo1>test sous-élément 1</subinfo1>
        <subinfo2>test sous-élément 2</subinfo2>
    </info>
</racine>


Notez la première ligne qui est appelée "Instructions processeurs" (Processing Instruction).
On y définit notamment l'encodage du fichier.

Il ne peut y avoir qu'un seul élément à la racine, que j'ai d'ailleurs appelé racine mais qui pourrait avoir un autre nom.
Tous les éléments sont ajoutés à cette racine.

L'indentation (tabulations) et les retours à la ligne ne sont pas obligatoires.

Le fichier doit être sauvegardé en texte brut.
On donnera au fichier une extension xml plutôt que txt mais l'extension n'est pas obligatoire.

V-A-2. Avec les fonctions de fichiers VBA

Il est également facile de générer un fichier avec les fonctions standards de VBA.
Il suffit d'écrire le fichier XML comme n'importe quel fichier texte séquentiel.

 
Sélectionnez

Function CreateXMLFileVBA()
Dim fic As Integer
fic = FreeFile
Open "C:\XMLFileVBA.xml" For Output As #fic
Print #fic, "<?xml version=""1.0"" encoding=""ISO-8859-1""?>"
Print #fic, "<racine>"
Print #fic, "    <info1>test élément 1</info1>"
Print #fic, "    <info2>test élément 2</info1>"
Print #fic, "    <info>"
Print #fic, "        <subinfo1>test sous-élément 1</subinfo1>"""
Print #fic, "        <subinfo2>test sous-élément 2</subinfo2>"
Print #fic, "    </info>"
Print #fic, "</racine>"
Close #fic
End Function


Notez qu'il faut doubler les guillemets dans la chaîne de caractères.

V-A-3. Avec la librairie Microsoft XML

Nous avons ici besoin d'un objet DOMDocument.
C'est cet objet qui va représenter notre fichier XML.

 
Sélectionnez

Function CreateXMLFileXML()
Dim oXML As MSXML2.DOMDocument
Dim oNode As MSXML2.IXMLDOMNode
Set oXML = New MSXML2.DOMDocument
oXML.Save "C:\XMLFileXML.xml"
End Function


Un objet oNode a également été déclaré. Il servira à créer chacun des nœuds du XML.

Pour sauvegarder le document, il suffit d'appeler sa méthode Save.
La fonction crée pour l'instant un fichier vide.
Nous allons ajouter les éléments avant cette instruction de sauvegarde.
Ajoutez les instructions processeur.

 
Sélectionnez

Set oNode = oXML.createProcessingInstruction("xml", "version=""1.0"" encoding=""ISO-8859-1""")
oXML.appendChild oNode


Comme pour un document HTML, on doit d'abord créer un élément (le nœud) puis on l'ajoute au bon emplacement.

Pour créer la racine, nous allons utiliser l'instruction With, la créer et l'ajouter en une ligne.

 
Sélectionnez

With oXML.appendChild(oXML.createElement("racine"))
End With


On crée un élément avec CreateElement, c'est en fait un objet de type IXMLDOMElement qui est un type dérivé de IXMLDOMNode.
On l'ajoute directement avec appendChild qui nous renvoie l'objet ajouté.
On a alors accès à l'élément racine entre les instructions With et End With.
Écrire un point à l'intérieur de ces instructions est un appel à cet objet.

On ajoute alors notre élément info1.

 
Sélectionnez

With oXML.appendChild(oXML.createElement("racine"))
   With .appendChild(oXML.createElement("info1"))
       .Text = "test élément 1"
   End With
End With


On peut imbriquer les instructions With mais seule la dernière est utilisable.
Le .Text appelle l'élément info1 mais on ne peut remonter jusqu'à la racine.
Si on souhaite appeler l'élément supérieur, on peut utiliser parentNode.

Ajoutez de la même manière les autres éléments :

 
Sélectionnez

Function CreateXMLFileXML()
Dim oXML As msxml2.DOMDocument
Dim oNode As msxml2.IXMLDOMNode
Set oXML = New msxml2.DOMDocument
Set oNode = oXML.createProcessingInstruction("xml", "version=""1.0"" encoding=""ISO-8859-1""")
oXML.appendChild oNode
With oXML.appendChild(oXML.createElement("racine"))
   With .appendChild(oXML.createElement("info1"))
       .Text = "test élément 1"
   End With
   With .appendChild(oXML.createElement("info2"))
       .Text = "test élément 2"
   End With
   With .appendChild(oXML.createElement("info"))
       With .appendChild(oXML.createElement("subinfo1"))
           .Text = "test sous-élément 1"
       End With
       With .appendChild(oXML.createElement("subinfo2"))
           .Text = "test sous-élément 2"
       End With
   End With
End With
oXML.Save "C:\XMLFileXML.xml"
End Function


L'instruction With est très pratique, on note qu'on n'a même pas besoin de l'objet oNode.
Les instructions processeur auraient pu être ajoutées en une ligne sans l'objet oNode qui deviendrait complètement inutile.

Si on ouvre le fichier xml ainsi généré dans un éditeur texte, on remarque que le code n'est pas mis en forme.
Cela n'empêche pas le fichier d'être correct.
Si on souhaite le mettre en forme, il y a la possibilité d'utiliser un objet MXXMLWriter comme expliqué dans cet article : Manipuler des fichiers XML en VBScript avec Xpath

Autre possibilité : ajouter des nœuds de type texte avec des tabulations et des sauts de ligne.
Un texte est créé avec la méthode createTextNode.

 
Sélectionnez

Function CreateXMLFileXMLIndent()
Dim oXML As msxml2.DOMDocument
Dim oNode As msxml2.IXMLDOMNode
Set oXML = New msxml2.DOMDocument
Set oNode = oXML.createProcessingInstruction("xml", "version=""1.0"" encoding=""ISO-8859-1""")
oXML.appendChild oNode
With oXML.appendChild(oXML.createElement("racine"))
   .appendChild oXML.createTextNode(vbCrLf)
   .appendChild oXML.createTextNode(vbTab)
   With .appendChild(oXML.createElement("info1"))
       .Text = "test élément 1"
   End With
   .appendChild oXML.createTextNode(vbCrLf)
   .appendChild oXML.createTextNode(vbTab)
   With .appendChild(oXML.createElement("info2"))
       .Text = "test élément 2"
   End With
   .appendChild oXML.createTextNode(vbCrLf)
   .appendChild oXML.createTextNode(vbTab)
   With .appendChild(oXML.createElement("info"))
       .appendChild oXML.createTextNode(vbCrLf)
       .appendChild oXML.createTextNode(vbTab)
       .appendChild oXML.createTextNode(vbTab)
       With .appendChild(oXML.createElement("subinfo1"))
           .Text = "test sous-élément 1"
       End With
       .appendChild oXML.createTextNode(vbCrLf)
       .appendChild oXML.createTextNode(vbTab)
       .appendChild oXML.createTextNode(vbTab)
       With .appendChild(oXML.createElement("subinfo2"))
           .Text = "test sous-élément 2"
       End With
       .appendChild oXML.createTextNode(vbCrLf)
       .appendChild oXML.createTextNode(vbTab)
   End With
   .appendChild oXML.createTextNode(vbCrLf)
End With
oXML.Save "C:\XMLFileXMLIndent.xml"
End Function


C'est beaucoup de travail pour une mise en forme.
Pour un fichier complexe, on pourra préférer la méthode avec l'objet MXXMLWriter (qui requiert ADODB).
Si la mise en forme n'est pas indispensable, sachez que le fichier xml pourra être parcouru sans problème même si tout est écrit sur la même ligne.

V-B. Charger un fichier XML

Pour lire un fichier xml à partir d'un fichier local, il suffit d'appeler la méthode Load.
La méthode LoadXML sert à charger une chaîne de caractères contenant du code xml.
Par défaut le chargement est asynchrone. Pour attendre la fin du chargement avant que le code ne continue, définissez la propriété asynch.

Écrivons une fonction qui charge notre fichier xml indenté généré précédemment :

 
Sélectionnez

Function ReadXMLFileXMLIndent()
Dim oXML As msxml2.DOMDocument
Dim oNode As msxml2.IXMLDOMNode
Set oXML = New msxml2.DOMDocument
oXML.async = False
oXML.Load "C:\XMLFileXMLIndent.xml"
End Function


Nous avons ici aussi besoin d'un objet DOMDocument.
L'objet oNode est prévu pour la suite.

Si vous placez un point d'arrêt à la fin de la fonction et que vous regardez dans la fenêtre Variables locales, vous voyez toute l'arborescence du fichier.

Image non disponible

On retrouve dans childNodes les instructions processeur et la racine.
Dans documentElement, on trouve directement l'élément racine et son contenu.

V-C. Parcourir un fichier XML

Pour parcourir les éléments du XML, on peut utiliser la collection childNodes.
Par exemple pour écrire le nom de chaque élément contenu dans la racine :

 
Sélectionnez

For Each oNode In oXML.documentElement.childNodes
   Debug.Print oNode.baseName
Next


Si on souhaite maintenant parcourir un niveau plus bas, il faut un autre objet, par exemple oSubNode.

 
Sélectionnez

Dim oSubNode As msxml2.IXMLDOMNode


On peut ensuite parcourir les fils de chaque élément de la racine.

 
Sélectionnez

For Each oNode In oXML.documentElement.childNodes
   For Each oSubNode In oNode.childNodes
       Debug.Print oSubNode.baseName, oSubNode.Text
   Next
Next


On obtient ce résultat :

test élément 1
test élément 2
subinfo1 test sous-élément 1
subinfo2 test sous-élément 2


L'élément info1 par exemple contient un élément texte qui se trouve dans les childNodes.
Si on ne souhaite que voir afficher les sous-éléments, il faut tester le type de nœud.

 
Sélectionnez

For Each oNode In oXML.documentElement.childNodes
   For Each oSubNode In oNode.childNodes
       If TypeOf oSubNode Is MSXML2.IXMLDOMElement Then
           Debug.Print oSubNode.baseName, oSubNode.Text
       End If
   Next
Next


Le résultat obtenu est alors :

subinfo1 test sous-élément 1
subinfo2 test sous-élément 2


Si par exemple nous souhaitons récupérer le texte de l'élément subinfo2, il y a bien plus pratique que de naviguer dans toute l'arborescence niveau par niveau.
Il est en effet possible de chercher un élément grâce à son chemin, nommé XPATH.

Vous trouverez la syntaxe de ces XPATH dans cet article : Tutoriel Xpath.

Pour les utiliser il faut exécuter les méthodes selectNodes ou selectSingleNode :
- selectSingleNode renvoie le premier nœud trouvé ;
- selectNodes renvoie une collection de nœuds que l'on peut parcourir avec For Each ;

Pour trouver directement l'élément subinfo2, on va donc utiliser selectSingleNode.

 
Sélectionnez

Set oNode = oXML.selectSingleNode("/racine/info/subinfo1")
Debug.Print oNode.baseName, oNode.Text


La syntaxe est assez simple :
- Un / représente la racine ;
- Chaque / est un niveau.

on peut utiliser le joker * pour rechercher plusieurs éléments :

 
Sélectionnez

For Each oNode In oXML.selectNodes("/racine/info/*")
   Debug.Print oNode.baseName, oNode.Text
Next


Pour recherche un élément dont le nom est connu et unique dans le xml, utilisez un slash double // :

 
Sélectionnez

Set oNode = oXML.selectSingleNode("//subinfo2")
Debug.Print oNode.baseName, oNode.Text


Avec ces fonctions, il est donc très facile de retrouver un élément dans le XML.

V-D. Télécharger un fichier XML

Il peut être utile de télécharger un fichier XML sur un serveur internet (ou intranet).
Pour cela nous utiliserons un objet MSXML2.XMLHTTP.

V-D-1. Méthode synchrone


On commence par créer notre objet.

 
Sélectionnez

Function DownloadXML()
Dim oXMLHTTP As MSXML2.XMLHTTP
Set oXMLHTTP = New MSXML2.XMLHTTP
End Function


Ensuite on demande l'ouverture de la requête en donnant l'emplacement du réseau.

 
Sélectionnez

oXMLHTTP.Open "POST", "http://arkham46.developpez.com/articles/office/officeweb/fichiers/XMLFileXMLIndent.xml", False


Utilisez "POST" en premier argument pour être sûr de ne pas utiliser le cache et récupérer la dernière version du serveur.
Remplacez "POST" par "GET" pour utiliser le cache.

En deuxième argument, mettez l'adresse http du fichier.

Le troisième argument (à false) définit une requête synchrone, c'est-à-dire que le code est suspendu en attente du téléchargement complet.

À ce stade, la requête est ouverte mais n'a pas été envoyée au serveur : c'est le rôle de la méthode Send.

 
Sélectionnez

oXMLHTTP.send


Au retour de la fonction Send, on peut retrouver dans la propriété ResponseXML le contenu du XML dans un objet DOMDocument.
Pensez à tester le retour, propriété status, qui vaut 200 si la requête a été correctement traitée.
Un dernier test consiste à vérifier le code retour de l'objet ParseError qui est différent de 0 si le XML n'est pas correct.

Ensuite on peut parcourir l'objet XML comme vu auparavant pour un fichier chargé depuis le disque.

 
Sélectionnez

Function DownloadXML()
Dim oXMLHTTP As MSXML2.XMLHTTP
Dim oXML As MSXML2.DOMDocument
Dim oNode As MSXML2.IXMLDOMNode
Set oXMLHTTP = New MSXML2.XMLHTTP
oXMLHTTP.Open "POST", "http://arkham46.developpez.com/articles/office/officeweb/fichiers/XMLFileXMLIndent.xml", False
oXMLHTTP.send
If oXMLHTTP.Status = 200 Then
   Set oXML = oXMLHTTP.responseXML
   If oXML.parseError.errorCode = 0 Then
       Set oNode = oXML.selectSingleNode("//subinfo2")
       Debug.Print oNode.baseName, oNode.Text
   End If
End If
End Function


Cette fonctionnalité peut s'avérer pratique pour par exemple télécharger des informations sur une version d'application.
Plutôt que d'utiliser un fichier texte, on peut donc utiliser un fichier XML bien formaté.

V-D-2. Méthode asynchrone

La méthode du chapitre précédent attend la fin du téléchargement avant de poursuivre le code.
On peut avoir besoin de lancer un téléchargement en arrière-plan, c'est-à-dire de manière asynchrone.
Pour cela, dans le code précédent, il faut mettre le troisième argument de la méthode open à true.

 
Sélectionnez

oXMLHTTP.Open "POST", "http://arkham46.developpez.com/articles/office/officeweb/fichiers/XMLFileXMLIndent.xml", True


Mais on ne peut pas laisser la suite du code dans cet état.
En effet le code se poursuit après l'instruction send alors que le téléchargement n'est pas terminé.
Il serait peu utile de faire une boucle d'attente, autant ouvrir en mode asynchrone si l'on veut attendre.

Il faut définir une procédure qui recevra les événements de changement de statut grâce à la propriété onreadystatechange.

Comme pour le gestionnaire d'événements des éléments d'une page HTML vu dans le chapitre IV-E, nous allons créer un objet chargé de recevoir les événements.

Cet objet devra posséder une méthode par défaut.
Pour créer un tel objet, il faut d'abord ajouter un module de classe : menu Insertion => Module de classe.
Le code de ce module est le suivant :

 
Sélectionnez

Option Explicit
            
Dim oXMLHTTP As MSXML2.XMLHTTP
            
' Définit l'objet XMLHTTP
Public Property Let XMLHTTP(Value As MSXML2.XMLHTTP)
  Set oXMLHTTP = Value
End Property
            
' Procédure par défaut qui reçoit les événements
Sub OnReadyStateChange()
'Attribute OnReadyStateChange.VB_UserMemId = 0
End Sub


Il contient :
- une propriété XMLHTTP : elle nous permettra de transmettre notre objet XMLHTTP pour l'avoir à disposition dans ce module ;
- une procédure OnReadyStateChange (son nom est arbitraire) qui est la procédure exécutée lorsqu'un événement est levé.

Sauvegardez ce module, avec le nom RequestEvents.

Ensuite il faut définir la procédure OnReadyStateChange comme procédure par défaut de l'objet.
Pour cela il faut exporter, modifier, puis réimporter le module.

- Exportez le module : menu Fichier => Exporter un fichier....
- Supprimez le module : menu Fichier => Supprimer RequestEvents....
- Modifiez le fichier exporté dans un éditeur texte : décommentez la première ligne de la procédure.
- Importez le fichier modifié : menu Fichier => Importer un fichier....

La ligne Attribute OnReadyStateChange.VB_UserMemId = 0 est prise en compte à l'import, mais n'est pas affichée dans l'éditeur VBA.

Le module RequestEvents est prêt.
Pour l'utiliser, il faut déclarer un objet de type RequestEvents dans la procédure de téléchargement.

 
Sélectionnez

Dim oRequestEvent As RequestEvents


Ensuite il faut instancier cet objet, après l'instruction open par exemple.

 
Sélectionnez

Set oRequestEvent = New RequestEvents


L'objet est prêt à servir de gestionnaire d'événements.
Il suffit de l'affecter à la propriété correspondant à l'événement à gérer.

 
Sélectionnez

oXMLHTTP.OnReadyStateChange = oRequestEvent


Ceci est à écrire avant le send, ce qui nous donne la fonction suivante :

 
Sélectionnez

Function DownloadXMLAsynch()
Dim oXMLHTTP As MSXML2.XMLHTTP
Dim oXML As MSXML2.DOMDocument
Dim oNode As MSXML2.IXMLDOMNode
Dim oRequestEvent As RequestEvents
Set oXMLHTTP = New MSXML2.XMLHTTP
oXMLHTTP.Open "POST", "http://arkham46.developpez.com/articles/office/officeweb/fichiers/XMLFileXMLIndent.xml", True
Set oRequestEvent = New RequestEvents
oRequestEvent.XMLHTTP = oXMLHTTP
oXMLHTTP.OnReadyStateChange = oRequestEvent
oXMLHTTP.send
End Function


La fonction se déroule complètement avant que le fichier n'ait été téléchargé complètement.
Il suffit de gérer dans le module RequestEvents la lecture du fichier xml lorsque le statut est OK.

 
Sélectionnez

' Procédure par défaut qui reçoit les événements
Sub OnReadyStateChange()
'Attribute OnReadyStateChange.VB_UserMemId = 0
Dim oXML As MSXML2.DOMDocument
Dim oNode As MSXML2.IXMLDOMNode
If oXMLHTTP.Status = 200 Then
   Set oXML = oXMLHTTP.responseXML
   If oXML.parseError.errorCode = 0 Then
       Set oNode = oXML.selectSingleNode("//subinfo2")
       Debug.Print oNode.baseName, oNode.Text
   End If
End If
End Sub


Cette astuce est pratique si le fichier à télécharger est volumineux et si le traitement peut se faire en arrière-plan.


précédentsommairesuivant

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

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 © 2011 Thierry GASPERMENT. 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. Droits de diffusion permanents accordés à Developpez LLC.