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 :
<?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.
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.
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.
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.
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.
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 :
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.
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 :
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.
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 :
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.
Dim oSubNode As msxml2.IXMLDOMNode
On peut ensuite parcourir les fils de chaque élément de la racine.
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.
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.
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 :
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 // :
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.
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.
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.
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.
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.
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 :
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.
Dim oRequestEvent As RequestEvents
Ensuite il faut instancier cet objet, après l'instruction open par exemple.
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.
oXMLHTTP.OnReadyStateChange = oRequestEvent
Ceci est à écrire avant le send, ce qui nous donne la fonction suivante :
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.
' 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.


