1. Introduction
Imaginez un XML géant envoyé par un service SOAP ou un API bancaire. Il contient des centaines d'éléments imbriqués, des structures hétérogènes, et vous devez trouver une seule et unique somme de paiement du mois dernier. La technique de sérialisation ne convient pas ici — trop lourde et on ne sait pas à l'avance où regarder. Il faut une "loupe" pour l'arbre XML et la capacité à naviguer rapidement dans ses branches.
C'est justement pour ces cas que .NET propose depuis longtemps un outil puissant — la classe XmlDocument, complétée par le langage de requêtes XPath. Aujourd'hui vous allez apprendre à :
- Charger le XML dans un arbre DOM;
- Se déplacer et trouver les éléments manuellement et avec XPath;
- Modifier le contenu d'un document XML;
- Ajouter, supprimer et modifier des nœuds;
- Utiliser XPath pour des sélections complexes.
2. Qu'est-ce que XmlDocument ?
XmlDocument — une classe du namespace System.Xml. C'est une implémentation du modèle DOM (Document Object Model) — la représentation de tout le fichier XML sous forme d'un arbre d'objets en mémoire.
Un peu de théorie et d'analogies
Si XmlSerializer ressemble à un constructeur LEGO, quand vous transformez un objet en ensemble de briques et inversement, alors XmlDocument c'est comme travailler avec un vrai arbre : il a une racine, des branches (éléments), des feuilles (nœuds texte), et vous pouvez parcourir cet arbre, ajouter des branches, couper des feuilles et les replanter où vous voulez.
Chargement simple d'un document XML
Supposons que nous ayons ce XML :
<users>
<user id="1">
<name>Olga</name>
<age>28</age>
</user>
<user id="2">
<name>Igor</name>
<age>35</age>
</user>
</users>
Chargeons-le en mémoire :
using System.Xml;
string xml = @"
<users>
<user id='1'>
<name>Olga</name>
<age>28</age>
</user>
<user id='2'>
<name>Igor</name>
<age>35</age>
</user>
</users>";
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml); // ou doc.Load("chemin_vers_fichier.xml");
Après l'appel de LoadXml (ou Load si le XML vient d'un fichier) vous avez un accès complet au contenu du document.
3. Navigation dans l'arbre DOM
Structure de l'arbre DOM
Chaque document XML après chargement devient un arbre composé de nœuds de différents types :
| Type de nœud | Classe en .NET | Exemple |
|---|---|---|
| Document | XmlDocument | |
| Element | XmlElement | |
| Attribute | XmlAttribute | |
| Text | XmlText | Olga, 28 |
| Comment | XmlComment | |
Pour accéder aux éléments nécessaires, il faudra "parcourir l'arbre" en utilisant les propriétés ChildNodes, Attributes, ParentNode, etc.
Obtenir l'élément racine
XmlElement root = doc.DocumentElement;
Console.WriteLine(root.Name); // users
Itérer les éléments enfants
foreach (XmlNode node in root.ChildNodes)
{
if (node is XmlElement user)
{
// user — c'est <user id="...">...</user>
string id = user.GetAttribute("id");
string name = user["name"].InnerText;
string age = user["age"].InnerText;
Console.WriteLine($"Utilisateur {id} : {name}, âge {age}");
}
}
IMPORTANT : L'accès user["name"] est possible uniquement si parmi les descendants directs il y a un élément <name>.
Accès aux attributs et au texte
var firstUser = root.FirstChild as XmlElement;
string id = firstUser.GetAttribute("id"); // "1"
string name = firstUser["name"].InnerText; // "Olga"
4. Modification du document XML
Changer une valeur
Supposons qu'Olga décide soudainement de "rajeunir" :
var olga = root.FirstChild as XmlElement;
olga["age"].InnerText = "22"; // maintenant <age>22</age>
Ajouter un nouvel utilisateur
XmlElement newUser = doc.CreateElement("user");
newUser.SetAttribute("id", "3");
XmlElement name = doc.CreateElement("name");
name.InnerText = "Vasilisa";
XmlElement age = doc.CreateElement("age");
age.InnerText = "19";
newUser.AppendChild(name);
newUser.AppendChild(age);
root.AppendChild(newUser);
Supprimer un élément
Supprimons le deuxième utilisateur ("Igor") :
XmlNode userToDelete = root.SelectSingleNode("user[@id='2']");
if (userToDelete != null)
root.RemoveChild(userToDelete);
Enregistrer les modifications
doc.Save("users_updated.xml");
// Ou doc.OuterXml — pour obtenir la chaîne XML
5. XPath — langage de recherche et de sélection dans XML
Travailler avec l'arbre DOM devient pénible si vous devez chercher des éléments "selon une condition" — par exemple tous les utilisateurs de plus de 25 ans. Pour ça il existe XPath — langage de navigation dans l'arbre XML.
Utilisation basique de XPath
Les requêtes XPath peuvent être exécutées via les méthodes SelectSingleNode (retourne le premier nœud trouvé) et SelectNodes (retourne une collection de nœuds).
Exemple : trouver l'utilisateur avec id = 1
XmlNode user = root.SelectSingleNode("user[@id='1']");
Console.WriteLine(user["name"].InnerText); // Olga
Exemple : trouver tous les utilisateurs de plus de 25 ans
XmlNodeList nodes = root.SelectNodes("user[age>25]");
foreach (XmlNode u in nodes)
{
Console.WriteLine(u["name"].InnerText); // Affichera Olga et Igor (si Igor n'a pas encore été supprimé)
}
XPath — syntaxe rapide
| Expression XPath | Ce que ça fait |
|---|---|
|
Tous les éléments <user> à la racine <users> |
|
Utilisateur avec id=3 |
|
Utilisateurs de plus de 25 ans |
|
Tous les éléments <name> de tous les utilisateurs |
|
Le dernier <user> |
|
Les deux premiers <user> |
Plus d'exemples et la syntaxe : Documentation XPath.
Encore un exemple : sélection par éléments imbriqués
Supposons que le XML soit plus complexe :
<library>
<book>
<title>Le Seigneur des mouches</title>
<author>
<firstname>William</firstname>
<lastname>Golding</lastname>
</author>
</book>
<book>
<title>À la recherche du temps perdu</title>
<author>
<firstname>Marcel</firstname>
<lastname>Proust</lastname>
</author>
</book>
</library>
XmlDocument doc = new XmlDocument();
doc.LoadXml(libraryXml);
XmlNodeList authors = doc.SelectNodes("/library/book/author/lastname");
foreach (XmlNode lastName in authors)
Console.WriteLine(lastName.InnerText);
Ce qui sera affiché :
Golding
Proust
6. XPath : filtrage, logique, calculs
Filtres logiques
Afficher les noms de tous les utilisateurs dont l'âge est entre 18 et 30 :
// [age>=18 and age<=30]
XmlNodeList youngUsers = root.SelectNodes("user[age>=18 and age<=30]");
foreach (XmlNode u in youngUsers)
Console.WriteLine(u["name"].InnerText);
Travail avec les attributs
Les attributs se sélectionnent avec @. Trouvons les utilisateurs dont l'id commence par "1" :
XmlNodeList nodes = root.SelectNodes("user[starts-with(@id, '1')]");
Compter le nombre de nœuds
Directement dans la méthode C# SelectNodes on ne peut pas utiliser la fonction count() pour obtenir un nombre — elle retourne une collection, pas un nombre. Mais on peut faire ainsi :
int count = root.SelectNodes("user").Count;
Filtres imbriqués
XmlNodeList nodes = doc.SelectNodes("/library/book[author/lastname='Golding']");
7. XPath et espaces de noms
Si votre XML utilise des espaces de noms (xmlns), tout devient plus joyeux ! Pour ces cas utilisez XmlNamespaceManager :
<catalog xmlns="http://books.example.com">
<book>
<title>Algorithmes</title>
</book>
</catalog>
doc.LoadXml(xml);
XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable);
nsmgr.AddNamespace("b", "http://books.example.com");
XmlNodeList books = doc.SelectNodes("/b:catalog/b:book", nsmgr);
foreach (XmlNode book in books)
Console.WriteLine(book.SelectSingleNode("b:title", nsmgr)?.InnerText);
8. Modification du XML avec le DOM
Ajouter des nœuds "à la volée"
Ajoutons un nouvel élément <email> au premier utilisateur :
XmlElement email = doc.CreateElement("email");
email.InnerText = "olga@gmail.com";
olga.AppendChild(email);
Modifier des attributs
olga.SetAttribute("id", "99"); // Maintenant id="99"
Supprimer des nœuds avec XPath
XmlNodeList nodesToRemove = root.SelectNodes("user[age<20]");
foreach (XmlNode node in nodesToRemove)
root.RemoveChild(node);
9. Astuces utiles
Recherche et modification en masse de la structure
Supposons que vous recevez un grand XML avec différents éléments <record type="...">. Il faut ne garder que ceux dont l'attribut type vaut "customer", et ajouter à chacun un élément enfant <status>active</status>.
XmlNodeList customerNodes = root.SelectNodes("record[@type='customer']");
foreach (XmlElement record in customerNodes)
{
XmlElement status = doc.CreateElement("status");
status.InnerText = "active";
record.AppendChild(status);
}
Arbre des nœuds XML (DOM)
users
├─ user (id="1")
│ ├─ name ("Olga")
│ └─ age ("28")
├─ user (id="2")
│ ├─ name ("Igor")
│ └─ age ("35")
Exemple de sélection par XPath
| Requête XPath | Ce que ça retournera |
|---|---|
|
Le premier utilisateur (id="1") |
|
Le dernier utilisateur (id="2") |
|
Tous ceux de plus de 30 ans (Igor) |
|
Utilisateur avec id="2" |
Application pratique
- Intégrations avec des API "anciennes". Dans beaucoup d'organisations gouvernementales et banques SOAP/XML est encore largement utilisé. La capacité à trouver et modifier rapidement quelque chose dans une grande réponse XML peut sauver des dizaines d'heures.
- Migration de données. Lors du passage d'un système à un autre il faut souvent parser le XML et faire des sélections complexes, transformations et modifications en masse.
- Import-export vers Excel. Dans beaucoup de produits B2B l'entrée est toujours en XML. Là XPath est un moyen rapide d'extraire ce qu'il faut sans créer un gros modèle de données.
10. Erreurs, détails et pièges typiques
NullReferenceException : Si vous tentez d'accéder à un élément inexistant, par exemple el["something"].InnerText, et qu'un tel élément enfant n'existe pas du tout. Vérifiez toujours la valeur null.
XPath et profondeur : Rappelez-vous qu'une expression sans le caractère initial / cherche parmi les descendants du nœud courant, alors qu'avec / c'est à partir de la racine du document. Différents points de départ peuvent entraîner un résultat vide même si les données existent.
Travail avec les espaces de noms : Si vous n'utilisez pas XmlNamespaceManager, les requêtes XPath sur un XML avec xmlns retourneront vide.
Modification pendant l'itération : Si vous voulez supprimer des nœuds selon les résultats de SelectNodes, stockez d'abord ces nœuds dans un tableau, puis itérez — sinon la collection changera pendant l'itération et des erreurs peuvent survenir.
Différence entre nœuds texte et éléments : Entre les éléments peuvent exister des nœuds texte — espaces et sauts de ligne — que XML considère comme contenu. Pour des sélections strictes utilisez uniquement XmlElement ou filtrez par NodeType.
GO TO FULL VERSION