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.