CodeGym /Kursy /C# SELF /Zaawansowana praca z XML: ...

Zaawansowana praca z XML: XmlDocument i XPath

C# SELF
Poziom 48 , Lekcja 2
Dostępny

1. Wprowadzenie

Wyobraź sobie gigantyczny XML, który przysłał jakiś serwis SOAP albo bankowe API. W nim setki zagnieżdżonych elementów, niejednorodne struktury, a Tobie trzeba znaleźć tylko jedną jedyną kwotę płatności za poprzedni miesiąc. Podejście z serializacją tu nie pasuje — za bardzo klopotliwe i z góry nie wiadomo, gdzie patrzeć. Potrzebna "lupa" do drzewa XML i umiejętność szybkiego poruszania się po jego gałęziach.

Właśnie do takich przypadków w .NET od wielu lat jest potężne narzędzie — klasa XmlDocument, uzupełniona językiem zapytań XPath. Dziś nauczysz się:

  • Wczytywać XML do drzewa DOM;
  • Poruszać się i znajdować potrzebne elementy ręcznie i za pomocą XPath;
  • Modyfikować zawartość dokumentu XML;
  • Dodawać, usuwać i zmieniać węzły;
  • Używać XPath do złożonych selekcji.

2. Czym jest XmlDocument?

XmlDocument — klasa z przestrzeni nazw System.Xml. To implementacja DOM (Document Object Model) — reprezentacji całego pliku XML jako drzewa obiektów w pamięci.

Trochę teorii i analogii

Jeśli XmlSerializer przypomina konstruktor LEGO — kiedy zamieniasz obiekt na zestaw klocków i z powrotem — to XmlDocument jest jak praca z prawdziwym drzewem: ma korzeń, gałęzie (elementy), liście (węzły tekstowe), i możesz chodzić po tym drzewie, dodawać gałęzie, ścinać liście i przesadzać je gdzie chcesz.

Proste wczytanie dokumentu XML

Załóżmy, że mamy taki XML:

<users>
  <user id="1">
    <name>Olga</name>
    <age>28</age>
  </user>
  <user id="2">
    <name>Igor</name>
    <age>35</age>
  </user>
</users>

Wczytamy go do pamięci:

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); // albo doc.Load("ścieżka_do_pliku.xml");

Po wywołaniu LoadXml (albo Load, jeśli XML z pliku) masz pełny dostęp do zawartości dokumentu.

3. Nawigacja po drzewie DOM

Struktura drzewa DOM

Każdy dokument XML po wczytaniu staje się drzewem, składającym się z węzłów różnych typów:

Typ węzła Klasa w .NET Przykład
Document XmlDocument
<users>...</users>
Element XmlElement
<user>, <name>
Attribute XmlAttribute
id="1"
Text XmlText Olga, 28
Comment XmlComment
<!-- komentarz -->

Aby odwołać się do potrzebnych elementów — trzeba "chodzić po drzewie", używając właściwości ChildNodes, Attributes, ParentNode itd.

Pobieramy element root

XmlElement root = doc.DocumentElement;
Console.WriteLine(root.Name); // users

Iterujemy dzieci

foreach (XmlNode node in root.ChildNodes)
{
    if (node is XmlElement user)
    {
        // user — to <user id="...">...</user>
        string id = user.GetAttribute("id");
        string name = user["name"].InnerText;
        string age = user["age"].InnerText;
        Console.WriteLine($"Użytkownik {id}: {name}, wiek {age}");
    }
}

WAŻNE: Dostęp user["name"] jest możliwy tylko jeśli wśród bezpośrednich potomków jest element <name>.

Dostęp do atrybutów i tekstu

var firstUser = root.FirstChild as XmlElement;
string id = firstUser.GetAttribute("id"); // "1"
string name = firstUser["name"].InnerText; // "Olga"

4. Modyfikacja dokumentu XML

Zmiana wartości

Załóżmy, że Olga niespodziewanie postanowiła się "odmłodzić":

var olga = root.FirstChild as XmlElement;
olga["age"].InnerText = "22"; // teraz <age>22</age>

Dodanie nowego użytkownika

XmlElement newUser = doc.CreateElement("user");
newUser.SetAttribute("id", "3");

XmlElement name = doc.CreateElement("name");
name.InnerText = "Wasilisa";
XmlElement age = doc.CreateElement("age");
age.InnerText = "19";

newUser.AppendChild(name);
newUser.AppendChild(age);
root.AppendChild(newUser);

Usuwanie elementu

Usuniemy drugiego użytkownika ("Igor"):

XmlNode userToDelete = root.SelectSingleNode("user[@id='2']");
if (userToDelete != null)
    root.RemoveChild(userToDelete);

Zapisywanie zmian

doc.Save("users_updated.xml");
// Albo doc.OuterXml — aby dostać string z XML

5. XPath — język wyszukiwania i selekcji w XML

Praca z drzewem DOM staje się męcząca, jeśli trzeba szukać elementów "wg warunku" — np. wszystkich użytkowników starszych niż 25 lat. Do tego właśnie służy XPath — język nawigacji po drzewie XML.

Podstawowe użycie XPath

Zapytania XPath można wykonywać przez metody SelectSingleNode (zwróci pierwszy znaleziony węzeł) i SelectNodes (zwróci kolekcję węzłów).

Przykład: znaleźć użytkownika z id = 1

XmlNode user = root.SelectSingleNode("user[@id='1']");
Console.WriteLine(user["name"].InnerText); // Olga

Przykład: znaleźć wszystkich użytkowników starszych niż 25 lat

XmlNodeList nodes = root.SelectNodes("user[age>25]");
foreach (XmlNode u in nodes)
{
    Console.WriteLine(u["name"].InnerText); // Wypisze Olga i Igor (jeśli Igor jeszcze nie został usunięty)
}

XPath — krótka składnia

Wyrażenie XPath Co zrobi
/users/user
Wszystkie elementy <user> w korzeniu <users>
user[@id='3']
Użytkownik z id=3
user[age>25]
Użytkownicy starsi niż 25 lat
user/name
Wszystkie elementy <name> wszystkich użytkowników
user[last()]
Ostatni <user>
user[position()<3]
Pierwsze dwa <user>

Więcej przykładów i składni: Dokumentacja XPath.

Jeszcze przykład: wybór po zagnieżdżonych elementach

Załóżmy, że XML jest bardziej złożony:

<library>
  <book>
    <title>Władca much</title>
    <author>
      <firstname>William</firstname>
      <lastname>Golding</lastname>
    </author>
  </book>
  <book>
    <title>W stronę Swanna</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);

Co zostanie wypisane:

Golding
Proust

6. XPath: filtrowanie, logika, obliczenia

Filtry logiczne

Wypisz imiona wszystkich użytkowników, których wiek jest między 18 i 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);

Praca z atrybutami

Atrybuty wybiera się przez @. Znajdź użytkowników, których id zaczyna się od "1":

XmlNodeList nodes = root.SelectNodes("user[starts-with(@id, '1')]");

Liczenie węzłów

Bezpośrednio w C# metoda SelectNodes nie zwraca liczby przez funkcję count() — zwraca kolekcję, a nie liczbę. Ale można zrobić tak:

int count = root.SelectNodes("user").Count;

Zagnieżdżone filtry

XmlNodeList nodes = doc.SelectNodes("/library/book[author/lastname='Golding']");

7. XPath i przestrzenie nazw

Jeżeli Twój XML używa przestrzeni nazw (xmlns), to robi się o wiele ciekawiej! W takich przypadkach używaj XmlNamespaceManager:

<catalog xmlns="http://books.example.com">
  <book>
    <title>Algorytmy</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. Modyfikacja XML za pomocą DOM

Dodawanie węzłów "w locie"

Dodajmy nowy element <email> do pierwszego użytkownika:

XmlElement email = doc.CreateElement("email");
email.InnerText = "olga@gmail.com";
olga.AppendChild(email);

Zmiana atrybutów

olga.SetAttribute("id", "99"); // Teraz id="99"

Usuwanie węzłów za pomocą XPath

XmlNodeList nodesToRemove = root.SelectNodes("user[age<20]");
foreach (XmlNode node in nodesToRemove)
    root.RemoveChild(node);

9. Przydatne niuanse

Wyszukiwanie i masowe zmiany struktury

Załóżmy, że dostajesz duży XML z różnymi elementami <record type="...">. Trzeba zostawić tylko te, które mają w atrybucie type wartość "customer", i dodać każdemu podrzędny element <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);
}

Drzewo węzłów XML (DOM)


users
├─ user (id="1")
│   ├─ name ("Olga")
│   └─ age ("28")
├─ user (id="2")
│   ├─ name ("Igor")
│   └─ age ("35")

Przykład wyboru przez XPath

Zapytanie XPath Co zwróci
/users/user[1]
Pierwszy użytkownik (id="1")
/users/user[last()]
Ostatni użytkownik (id="2")
/users/user[age>30]
Wszyscy powyżej 30 lat (Igor)
/users/user[@id='2']
Użytkownik z id="2"

Praktyczne zastosowania

  • Integracje ze "starymi" API. W wielu instytucjach rządowych i bankach SOAP/XML nadal rządzi. Możliwość szybkiego znalezienia i zmiany czegoś w dużej odpowiedzi XML może zaoszczędzić dziesiątki godzin.
  • Migracja danych. Przy przejściu z jednego systemu na inny często trzeba parsować XML i robić złożone selekcje, transformacje i masowe zmiany.
  • Import-eksport do Excel. W wielu produktach B2B dane nadal przychodzą w XML. Tam XPath to szybki sposób, by wydobyć to, co potrzebne, bez budowania dużego modelu danych.

10. Błędy, niuanse i typowe pułapki

NullReferenceException: Jeśli spróbujesz odwołać się do nieistniejącego elementu, np. el["something"].InnerText, a takiego potomka wcale nie ma. Zawsze sprawdzaj na null.

XPath i zagnieżdżenie: Pamiętaj, że wyrażenie bez początkowego / szuka wśród potomków bieżącego węzła, a z / — od korzenia dokumentu. Różne punkty odniesienia mogą dać brak wyniku, chociaż dane są.

Praca z przestrzeniami nazw: Jeśli nie użyjesz XmlNamespaceManager, XPath-zapytania do XML z xmlns będą zwracać pusto.

Modyfikacja podczas iteracji: Jeżeli chcesz usuwać węzły na podstawie wyników SelectNodes, najpierw zapisz je w tablicy, a potem iteruj — inaczej kolekcja zmieni się w trakcie pętli i mogą pojawić się błędy.

Różnica między węzłami tekstowymi a elementami: Między elementami mogą być węzły tekstowe — spacje i nowe linie, które XML traktuje jak zawartość. Dla ścisłej selekcji używaj tylko XmlElement albo filtruj po NodeType.

2
Zadanie
C# SELF, poziom 48, lekcja 2
Niedostępne
Wyszukiwanie elementów za pomocą XPath
Wyszukiwanie elementów za pomocą XPath
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION