1. Вступ
Уявіть собі гігантський XML, який надіслав вам певний SOAP‑сервіс або банківський API. У ньому сотні вкладених елементів, неоднорідні структури, а вам потрібно знайти всього одну‑єдину суму платежу за минулий місяць. Прийом із серіалізацією тут не підходить — надто громіздкий, і заздалегідь невідомо, куди саме дивитися. Потрібна «лупа» для XML‑дерева й уміння швидко пересуватися його гілками.
Саме для таких випадків у .NET уже багато років існує потужний інструмент — клас XmlDocument, доповнений мовою запитів XPath. Сьогодні ви навчитеся:
- Завантажувати XML у DOM‑дерево;
- Пересуватися та знаходити потрібні елементи вручну та за допомогою XPath;
- Модифікувати вміст XML‑документа;
- Додавати, видаляти й змінювати вузли;
- Використовувати XPath для складних вибірок.
2. Що таке XmlDocument?
XmlDocument — клас з простору імен System.Xml. Це реалізація DOM (Document Object Model) — подання всього XML‑файла у вигляді дерева об’єктів у пам’яті.
Трішки теорії й аналогій
Якщо XmlSerializer схожий на конструктор LEGO, коли ви перетворюєте об’єкт на набір кубиків і навпаки, то XmlDocument — це як робота зі справжнім деревом: у нього є корінь, гілки (елементи), листки (текстові вузли), і ви можете ходити цим деревом, додавати гілки, зрізати листки й пересаджувати їх куди потрібно.
Просте завантаження XML‑документа
Нехай у нас є такий XML:
<users>
<user id="1">
<name>Ольга</name>
<age>28</age>
</user>
<user id="2">
<name>Ігор</name>
<age>35</age>
</user>
</users>
Завантажимо його в пам’ять:
using System.Xml;
string xml = @"
<users>
<user id='1'>
<name>Ольга</name>
<age>28</age>
</user>
<user id='2'>
<name>Ігор</name>
<age>35</age>
</user>
</users>";
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml); // або doc.Load("шлях_до_файлу.xml");
Після виклику LoadXml (або Load, якщо XML з файлу) у вас є повний доступ до вмісту документа.
3. Навігація DOM‑деревом
Структура DOM‑дерева
Кожен XML‑документ після завантаження стає деревом, що складається з вузлів різних типів:
| Тип вузла | Клас у .NET | Приклад |
|---|---|---|
| Document | XmlDocument | |
| Element | XmlElement | |
| Attribute | XmlAttribute | |
| Text | XmlText | Ольга, 28 |
| Comment | XmlComment | |
Щоб звертатися до потрібних елементів, вам доведеться «ходити деревом», використовуючи властивості ChildNodes, Attributes, ParentNode тощо.
Отримуємо кореневий елемент
XmlElement root = doc.DocumentElement;
Console.WriteLine(root.Name); // users
Перебираємо дочірні елементи
foreach (XmlNode node in root.ChildNodes)
{
if (node is XmlElement user)
{
// user — це <user id="...">...</user>
string id = user.GetAttribute("id");
string name = user["name"].InnerText;
string age = user["age"].InnerText;
Console.WriteLine($"Користувач {id}: {name}, вік {age}");
}
}
ВАЖЛИВО: Доступ user["name"] можливий лише, якщо серед безпосередніх нащадків є елемент <name>.
Доступ до атрибутів і тексту
var firstUser = root.FirstChild as XmlElement;
string id = firstUser.GetAttribute("id"); // "1"
string name = firstUser["name"].InnerText; // "Ольга"
4. Модифікація XML‑документа
Зміна значення
Нехай Ольга раптом вирішила «омолодитися»:
var olga = root.FirstChild as XmlElement;
olga["age"].InnerText = "22"; // тепер <age>22</age>
Додавання нового користувача
XmlElement newUser = doc.CreateElement("user");
newUser.SetAttribute("id", "3");
XmlElement name = doc.CreateElement("name");
name.InnerText = "Василіса";
XmlElement age = doc.CreateElement("age");
age.InnerText = "19";
newUser.AppendChild(name);
newUser.AppendChild(age);
root.AppendChild(newUser);
Видалення елемента
Видалимо другого користувача («Ігор»):
XmlNode userToDelete = root.SelectSingleNode("user[@id='2']");
if (userToDelete != null)
root.RemoveChild(userToDelete);
Збереження змін
doc.Save("users_updated.xml");
// Або doc.OuterXml — щоб отримати рядок з XML
5. XPath — мова пошуку та вибірок у XML
Робота з DOM‑деревом стає виснажливою, якщо треба шукати елементи «за умовою» — наприклад, усіх користувачів старших за 25 років. Для цього і існує XPath — мова навігації у XML‑дереві.
Базове використання XPath
XPath-запити можна виконувати через методи SelectSingleNode (поверне перший знайдений вузол) і SelectNodes (поверне колекцію вузлів).
Приклад: знайти користувача з id = 1
XmlNode user = root.SelectSingleNode("user[@id='1']");
Console.WriteLine(user["name"].InnerText); // Ольга
Приклад: знайти всіх користувачів старших за 25 років
XmlNodeList nodes = root.SelectNodes("user[age>25]");
foreach (XmlNode u in nodes)
{
Console.WriteLine(u["name"].InnerText); // Виведе Ольга і Ігор (якщо Ігоря ще не видалили)
}
XPath — короткий синтаксис
| Вираз XPath | Що зробить |
|---|---|
|
Усі елементи <user> у корені <users> |
|
Користувач з id=3 |
|
Користувачі старші за 25 років |
|
Усі елементи <name> усіх користувачів |
|
Останній <user> |
|
Перші два <user> |
Більше прикладів і синтаксису: Документація щодо XPath.
Ще один приклад: вибірка за вкладеними елементами
Припустімо, XML складніший:
<library>
<book>
<title>Володар мух</title>
<author>
<firstname>Вільям</firstname>
<lastname>Голдінг</lastname>
</author>
</book>
<book>
<title>На боці Свана</title>
<author>
<firstname>Марсель</firstname>
<lastname>Пруст</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);
Результат:
Голдінг
Пруст
6. XPath: фільтрація, логіка, обчислення
Логічні фільтри
Вивести імена всіх користувачів, чий вік між 18 і 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);
Робота з атрибутами
Атрибути вибираються через @. Знайдемо користувачів, у яких id починається на "1":
XmlNodeList nodes = root.SelectNodes("user[starts-with(@id, '1')]");
Підрахунок кількості вузлів
Безпосередньо в C# у методі SelectNodes не можна використати функцію count() — він повертає колекцію, а не число. Але можна зробити так:
int count = root.SelectNodes("user").Count;
Вкладені фільтри
XmlNodeList nodes = doc.SelectNodes("/library/book[author/lastname='Голдінг']");
7. XPath і простори імен
Якщо ваш XML використовує простори імен (xmlns), з’являються додаткові нюанси. Для таких випадків використовуйте XmlNamespaceManager:
<catalog xmlns="http://books.example.com">
<book>
<title>Алгоритми</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. Модифікація XML за допомогою DOM
Додавання вузлів «на льоту»
Додамо новий елемент <email> до першого користувача:
XmlElement email = doc.CreateElement("email");
email.InnerText = "olga@gmail.com";
olga.AppendChild(email);
Зміна атрибутів
olga.SetAttribute("id", "99"); // Тепер id="99"
Видалення вузлів за допомогою XPath
XmlNodeList nodesToRemove = root.SelectNodes("user[age<20]");
foreach (XmlNode node in nodesToRemove)
root.RemoveChild(node);
9. Корисні нюанси
Пошук і масова зміна структури
Припустімо, вам надходить великий XML з різними елементами <record type="...">. Потрібно залишити лише ті, у яких в атрибуті type стоїть "customer", і додати кожному дочірній елемент <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);
}
Дерево вузлів XML (DOM)
users
├─ user (id="1")
│ ├─ name ("Ольга")
│ └─ age ("28")
├─ user (id="2")
│ ├─ name ("Ігор")
│ └─ age ("35")
Приклад вибірки за XPath
| XPath-запит | Що поверне |
|---|---|
|
Перший користувач (id="1") |
|
Останній користувач (id="2") |
|
Усі старші за 30 років (Ігор) |
|
Користувач з id="2" |
Практичне застосування
- Інтеграції зі «старими» API. У багатьох державних установах і банках SOAP/XML досі домінують. Можливість швидко знайти й змінити щось у великій XML‑відповіді може заощадити десятки годин.
- Міграція даних. Під час переходу з однієї системи на іншу часто потрібно парсити XML і виконувати складні вибірки, перетворення та масові зміни.
- Імпорт‑експорт в Excel. У багатьох B2B‑продуктах і досі на вхід приходить XML. Там XPath — швидкий спосіб дістати потрібне без створення великої моделі даних.
10. Помилки, нюанси й типові пастки
NullReferenceException: Якщо спробувати звернутися до неіснуючого елемента, наприклад, el["something"].InnerText, а такого дочірнього елемента взагалі немає. Завжди перевіряйте на null.
XPath і вкладеність: Пам’ятайте, що вираз без початкового / шукає серед нащадків поточного вузла, а з / — від кореня документа. Різні точки відліку можуть призвести до відсутності результату, хоча дані є.
Робота з просторами імен: Якщо не використовувати XmlNamespaceManager, XPath-запити до XML із xmlns повертатимуть порожній результат.
Модифікація під час перебору: Якщо хочете видаляти вузли за результатами SelectNodes, спочатку збережіть їх у масив, а потім ітеруйте — інакше колекція зміниться під час перебору, і можуть виникнути помилки.
Відмінність текстових і елементних вузлів: Між елементами можуть бути текстові вузли — пробіли та перенесення рядків, які XML вважає вмістом. Для суворої вибірки використовуйте лише XmlElement або фільтруйте за NodeType.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ