JavaRush /Курси /C# SELF /Поглиблена робота з XML: X...

Поглиблена робота з XML: XmlDocument і XPath

C# SELF
Рівень 48 , Лекція 2
Відкрита

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
<users>...</users>
Element XmlElement
<user>, <name>
Attribute XmlAttribute
id="1"
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 Що зробить
/users/user
Усі елементи <user> у корені <users>
user[@id='3']
Користувач з id=3
user[age>25]
Користувачі старші за 25 років
user/name
Усі елементи <name> усіх користувачів
user[last()]
Останній <user>
user[position()<3]
Перші два <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-запит Що поверне
/users/user[1]
Перший користувач (id="1")
/users/user[last()]
Останній користувач (id="2")
/users/user[age>30]
Усі старші за 30 років (Ігор)
/users/user[@id='2']
Користувач з 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.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ