CodeGym /Kurse /C# SELF /Fortgeschrittene Arbeit mit XML:

Fortgeschrittene Arbeit mit XML: XmlDocument und XPath

C# SELF
Level 48 , Lektion 2
Verfügbar

1. Einführung

Stell dir ein riesiges XML vor, das dir irgendein SOAP‑Service oder eine Banken-API schickt. Darin sind Hunderttausende verschachtelter Elemente, heterogene Strukturen, und du musst nur eine einzige Zahlungsbetragssumme vom letzten Monat finden. Serialisierung funktioniert hier nicht — zu sperrig und vorher nicht klar, wohin man schauen soll. Du brauchst eine "Lupe" fürs XML-Baum und die Fähigkeit, schnell durch seine Zweige zu laufen.

Genau für solche Fälle gibt es in .NET seit vielen Jahren ein mächtiges Werkzeug — die Klasse XmlDocument, ergänzt durch die Abfragesprache XPath. Heute lernst du:

  • XML in einen DOM-Baum zu laden;
  • sich manuell und mit XPath zu bewegen und Elemente zu finden;
  • den Inhalt eines XML-Dokuments zu modifizieren;
  • Knoten hinzuzufügen, zu löschen und zu ändern;
  • XPath für komplexe Selektionen zu nutzen.

2. Was ist XmlDocument?

XmlDocument ist eine Klasse aus dem Namespace System.Xml. Es ist eine Implementierung des DOM (Document Object Model) — eine Darstellung der gesamten XML-Datei als Baum von Objekten im Speicher.

Ein bisschen Theorie und Analogien

Wenn XmlSerializer wie ein LEGO-Baukasten ist, mit dem du ein Objekt in Bausteine verwandelst und zurück, dann ist XmlDocument eher wie das Arbeiten an einem echten Baum: Es hat eine Wurzel, Äste (Elemente), Blätter (Textknoten), und du kannst im Baum herumgehen, Äste hinzufügen, Blätter abschneiden und sie woanders einpflanzen.

Einfache XML-Ladung

Angenommen, wir haben so ein XML:

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

Laden wir es in den Speicher:

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); // oder doc.Load("pfad_zur_datei.xml");

Nach dem Aufruf von LoadXml (oder Load, wenn das XML aus einer Datei kommt) hast du vollen Zugriff auf den Dokumentinhalt.

3. Navigation im DOM-Baum

Struktur des DOM-Baums

Jedes XML-Dokument wird nach dem Laden zu einem Baum aus Knoten verschiedener Typen:

Knotentyp Klasse in .NET Beispiel
Document XmlDocument
<users>...</users>
Element XmlElement
<user>, <name>
Attribute XmlAttribute
id="1"
Text XmlText Olga, 28
Comment XmlComment
<!-- kommentar -->

Um auf die richtigen Elemente zuzugreifen, musst du "im Baum laufen" und Eigenschaften wie ChildNodes, Attributes, ParentNode usw. benutzen.

Wir bekommen das Root-Element

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

Durchlaufen der Kind-Elemente

foreach (XmlNode node in root.ChildNodes)
{
    if (node is XmlElement user)
    {
        // user — das ist <user id="...">...</user>
        string id = user.GetAttribute("id");
        string name = user["name"].InnerText;
        string age = user["age"].InnerText;
        Console.WriteLine($"Benutzer {id}: {name}, Alter {age}");
    }
}

WICHTIG: Der Zugriff user["name"] funktioniert nur, wenn among den direkten Kindern ein Element <name> existiert.

Zugriff auf Attribute und Text

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

4. Modifikation des XML-Dokuments

Wert ändern

Angenommen, Olga hat sich plötzlich entschlossen, "jünger" zu werden:

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

Neuen Benutzer hinzufügen

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);

Element löschen

Wir löschen den zweiten Benutzer ("Igor"):

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

Änderungen speichern

doc.Save("users_updated.xml");
// Oder doc.OuterXml — um den XML-String zu bekommen

5. XPath — Sprache zum Suchen und Auswählen in XML

Arbeit mit dem DOM-Baum wird mühsam, wenn du Elemente "nach Bedingungen" suchen musst — z.B. alle Benutzer älter als 25 Jahre. Dafür gibt es XPath — eine Sprache zur Navigation im XML-Baum.

Grundlegende Nutzung von XPath

XPath-Abfragen kannst du mit den Methoden SelectSingleNode (gibt den ersten gefundenen Knoten zurück) und SelectNodes (gibt eine Knoten-Collection zurück) ausführen.

Beispiel: Benutzer mit id = 1 finden

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

Beispiel: Alle Benutzer älter als 25 finden

XmlNodeList nodes = root.SelectNodes("user[age>25]");
foreach (XmlNode u in nodes)
{
    Console.WriteLine(u["name"].InnerText); // Gibt Olga und Igor aus (falls Igor noch nicht gelöscht wurde)
}

XPath — kurze Syntax

XPath-Ausdruck Was er tut
/users/user
Alle <user>-Elemente im Root <users>
user[@id='3']
Benutzer mit id=3
user[age>25]
Benutzer älter als 25 Jahre
user/name
Alle <name>-Elemente aller Benutzer
user[last()]
Letztes <user>
user[position()<3]
Die ersten zwei <user>

Mehr Beispiele und Syntax: XPath-Dokumentation.

Noch ein Beispiel: Auswahl nach verschachtelten Elementen

Angenommen, das XML ist komplexer:

<library>
  <book>
    <title>Herr der Fliegen</title>
    <author>
      <firstname>William</firstname>
      <lastname>Golding</lastname>
    </author>
  </book>
  <book>
    <title>Auf der Suche nach der verlorenen Zeit</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);

Was ausgegeben wird:

Golding
Proust

6. XPath: Filter, Logik, Berechnungen

Logische Filter

Die Namen aller Benutzer ausgeben, deren Alter zwischen 18 und 30 liegt:

// [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);

Arbeiten mit Attributen

Attribute werden über @ angesprochen. Finden wir Benutzer, deren id mit "1" beginnt:

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

Anzahl der Knoten zählen

Direkt in der C#-Methode SelectNodes kann man die Funktion count() nicht verwenden — sie gibt eine Collection zurück, kein Number. Aber man kann so vorgehen:

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

Verschachtelte Filter

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

7. XPath und Namespaces

Wenn dein XML Namespaces verwendet (xmlns), wird alles etwas fröhlicher! In solchen Fällen benutze XmlNamespaceManager:

<catalog xmlns="http://books.example.com">
  <book>
    <title>Algorithmen</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. Modifikation von XML mit DOM

Knoten "on the fly" hinzufügen

Füge das neue Element <email> zum ersten Benutzer hinzu:

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

Attribute ändern

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

Knoten mit XPath löschen

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

9. Nützliche Feinheiten

Suche und Massenänderung der Struktur

Stell dir vor, du bekommst ein großes XML mit verschiedenen <record type="...">-Elementen. Du musst nur diejenigen behalten, deren Attribut type auf "customer" steht, und jedem ein Kind-Element <status>active</status> hinzufügen.

XmlNodeList customerNodes = root.SelectNodes("record[@type='customer']");
foreach (XmlElement record in customerNodes)
{
    XmlElement status = doc.CreateElement("status");
    status.InnerText = "active";
    record.AppendChild(status);
}

XML-Knotenbaum (DOM)


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

Beispielauswahl mit XPath

XPath-Abfrage Was zurückkommt
/users/user[1]
Erster Benutzer (id="1")
/users/user[last()]
Letzter Benutzer (id="2")
/users/user[age>30]
Alle älter als 30 (Igor)
/users/user[@id='2']
Benutzer mit id="2"

Praktische Anwendung

  • Integrationen mit "alten" APIs. In vielen Staatsunternehmen und Banken dominieren SOAP/XML noch. Die Fähigkeit, schnell etwas in einer großen XML-Antwort zu finden und zu ändern, kann dutzende Stunden sparen.
  • Datenmigration. Beim Wechsel von Systemen muss man oft XML parsen und komplexe Selektionen, Transformationen und Massenänderungen durchführen.
  • Import/Export nach Excel. In vielen B2B-Produkten kommt Eingangs noch XML an. Dort ist XPath ein schneller Weg, um das Nötige zu extrahieren, ohne ein großes Datenmodell zu bauen.

10. Fehler, Feinheiten und typische Fallen

NullReferenceException: Wenn du versuchst, auf ein nicht existentes Element zuzugreifen, z.B. el["something"].InnerText, und so ein Kind-Element überhaupt nicht existiert. Prüfe immer auf null.

XPath und Verschachtelung: Denk dran, dass ein Ausdruck ohne anfängliches / unter den Nachkommen des aktuellen Knotens sucht, während mit / vom Root des Dokuments aus gesucht wird. Unterschiedliche Bezugspunkte können zu keinem Ergebnis führen, obwohl die Daten vorhanden sind.

Arbeiten mit Namespaces: Wenn du keinen XmlNamespaceManager benutzt, werden XPath-Abfragen an XML mit xmlns leer zurückgeben.

Modifikation während der Iteration: Wenn du Knoten nach Ergebnissen von SelectNodes löschen willst, sichere sie zuerst in ein Array und iteriere dann — sonst ändert sich die Collection während der Iteration und es können Fehler auftreten.

Unterschied zwischen Text- und Elemente-Knoten: Zwischen Elementen können Textknoten — Leerzeichen und Zeilenumbrüche — liegen, die XML als Inhalt betrachtet. Für strikte Selektionen nutze gezielt XmlElement oder filtere nach NodeType.

Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION