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 | |
| Element | XmlElement | |
| Attribute | XmlAttribute | |
| Text | XmlText | Olga, 28 |
| Comment | XmlComment | |
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 |
|---|---|
|
Alle <user>-Elemente im Root <users> |
|
Benutzer mit id=3 |
|
Benutzer älter als 25 Jahre |
|
Alle <name>-Elemente aller Benutzer |
|
Letztes <user> |
|
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 |
|---|---|
|
Erster Benutzer (id="1") |
|
Letzter Benutzer (id="2") |
|
Alle älter als 30 (Igor) |
|
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.
GO TO FULL VERSION