## 16.9 LINQ to XML LINQ to XML vereinigt die Fähigkeiten von DOM sowie die von XPath beschriebenen Möglichkeiten, die im Speicher befindlichen XML-Daten zu verwalten, abzufragen und darüber hinaus auch zu ändern. Mit LINQ to XML soll dem Entwickler, der sich vorher mit mehreren unterschiedlichen APIs auseinandersetzen musste, die Arbeit mit XML einfacher gemacht werden. Im Folgenden werden wir uns mit der in .NET 3.5 eingeführten Technik beschäftigen. Da LINQ to XML eine eigenständige API (Klassenbibliothek) ist, gestattet LINQ to SQL das Arbeiten XML-Dokumenten, ohne dass dabei LINQ-Abfragen zwangsläufig eingesetzt werden müssen. Damit Sie die Bibliothek einsetzen können, müssen Sie auf die Assembly System.Xml.Linq verweisen und sollten auch den gleichnamigen Namespace bekannt geben. ### 16.9.1 Klassenhierarchie von LINQ to XML Ehe wir uns die Fähigkeiten von LINQ to XML ansehen, sollten wir einen Blick auf die Klassenbibliothek werfen. Allzu viele Klassen hat LINQ to XML nicht zu bieten, was die Einarbeitung sicherlich deutlich vereinfacht. In Abbildung 16.12 ist die Klassenhierarchie dargestellt. Da in diesem Buch nur ein kurzer Abriss von LINQ to XML erfolgt, soll es genügen, wenn an dieser Stelle nur die wichtigsten Klassen beschrieben werden.

Tabelle 16.20 Wichtige Klassen von LINQ to XML

Klasse | Beschreibung
XElement | Bei dieser Klasse handelt es sich wohl um die wichtigste Klasse von LINQ to XML. Sie beschreibt einen XML-Element-Knoten, der untergeordnete Elemente enthalten kann. Die Klasse stellt eine Vielzahl von Methoden zur Verfügung, um innerhalb der XML-Struktur zu navigieren oder auch um XML-Elemente zu bearbeiten.
XDocument | XDocument ist der Klasse XElement sehr ähnlich. Das wichtigste Unterscheidungsmerkmal im Vergleich zur Klasse XElement ist, dass ein XDocument-Objekt auch die XML-Deklaration und mehrere XML-Prozessinstruktionen bearbeiten kann.
XAttribute | Diese Klasse repräsentiert XML-Attribute und stellt passende Methoden zum Verwalten und Erzeugen zur Verfügung. Die Arbeit mit Attributen ähnelt dem Arbeiten mit den Elementen. Intern werden Attribute als Name/Wert-Paar behandelt, das einem Element zugeordnet ist.
XDeclaration | Die XDeclaration wird für die Angabe der XML-Version und der Codierung benötigt.
XComment | Ein Objekt dieser Klasse stellt einen XML-Kommentar dar.
XNode | Die Klasse XNode ist abstrakt definiert und stellt ihren Ableitungen Methoden zur Bearbeitung der Elemente einer XML-Struktur zur Verfügung.
XProcessingInstruction | Diese Klasse repräsentiert eine Processing Instruction (PI).
Hier klicken, um das Bild zu vergrößern Abbildung 16.12 Klassenhierarchie von LINQ to XML ### 16.9.2 Die Klasse »XElement« #### XML-Daten in den Speicher laden Um die Daten einer XML-Datei in den Speicher zu laden, wird die Methode Load der Klasse XElement aufgerufen, zum Beispiel:
    XElement xml = XElement.Load(@"D:\Personen.xml");
Der Methode Load können Sie einen Stream, ein TextReader-Objekt, ein XmlReader-Objekt oder eine Zeichenfolge übergeben. Bei der Zeichenfolge darf es sich auch um einen URI oder eine relative Angabe handeln, die dann relativ zum Anwendungsordner gilt. Darüber hinaus finden sich weitere Überladungen, die einen zweiten Parameter vom Typ LoadOptions beschreiben. Mit der Option LoadOptions.PreserveWhitespace ließe sich beispielsweise angeben, dass die Leerzeichen beibehalten werden sollen.
Anmerkung
Es gibt auch einige wenige Fälle, in denen das XML nicht in einer Datei vorliegt oder aus einem URI bezogen wird. Stattdessen wird das XML durch eine einfache Zeichenfolge beschrieben. Für diesen Fall stellt die Klasse XElement die statische Methode Parse zur Verfügung, die ein XElement-Objekt aus einer XML-Zeichenfolge erstellt, zum Beispiel:
    string xml = "<Name>Andreas</Name>";
    XElement element = XElement.Parse(xml);
#### XML-Daten erzeugen und speichern Ein XElement-Objekt beschreibt als Container weitere untergeordnete Elemente. Über die Konstruktoren werden die Elemente ineinander verschachtelt, um so die gewünschte XML-Struktur abzubilden. Exemplarisch wollen wir die folgende XML-Struktur mit der LINQ-to-XML-API abbilden.
    <?xml version="1.0" encoding="utf-8"?>
    <Personen>
      <Person>
        <Name>Fischer</Name>
        <Vorname>Manfred</Vorname>
        <Alter>45</Alter>
      </Person>
    </Personen>
Der Code, der dieses XML erzeugt, lautet wie folgt:
    XElement persons = new XElement("Personen",
                           new XElement("Person",
                               new XElement("Name", "Fischer"),
                               new XElement("Vorname", "Manfred"),
                               new XElement("Alter", "45")
                           )
                       );
    persons.Save(@"D:\Personen.xml");
Mit einer einzigen Anweisung hat man bereits das XML erzeugt – übrigens einschließlich des Prologs. Diese Anweisung in eine Zeile zu schreiben ist wenig ratsam. Sie sollten stattdessen eine Schachtelung im Code vornehmen, aus der auch die Struktur der XML-Tags ersichtlich ist. Das ist natürlich deutlich intuitiver, als dazu beispielsweise die Klasse XmlWriter zu verwenden (vergleichen Sie dazu bitte das Beispielprogramm XmlWriterSample in Abschnitt 16.5). Zum Schluss wird durch Aufruf der Methode Save das mit Code beschriebene XML in einer Datei gespeichert. Es erweist sich als vorteilhaft, dass Sie sich nicht weiter um die End-Tags Gedanken machen müssen, denn diese sind bereits durch die Klammerung des Programmcodes vorgegeben und werden damit bereits zur Entwurfszeit sichergestellt. Der Konstruktor der Klasse XElement hat drei Überladungen. In unserem Beispiel haben wir die folgende benutzt:
    public XElement(XName name, Object content)
Sehen wir uns zuerst den ersten Parameter an. Dieser ist vom Typ XName. Übergeben wir an dieser Stelle einen String, wird die Zeichenfolge hinter den Kulissen implizit in ein XName-Objekt konvertiert. Das vereinfacht die Übergabe an den ersten Parameter ganz deutlich. Dem zweiten Parameter können wir ein x-beliebiges Objekt übergeben. Am häufigsten kommen hier die folgenden Objekte zum Einsatz: ein XElement-Objekt, das als untergeordnetes Element eingefügt wird eine Zeichenfolge, die als Textinhalt bewertet wird ein XAttribute-Objekt, das als Attribut dem Element hinzugefügt wird eine XProcessingInstruction oder ein XComment ein XText-Objekt, das häufig einen CData-Wert beschreibt #### Namespaces definieren Um einen Namespace festzulegen, benutzen Sie das Element XAttribute.
    XNamespace ns = "http://www.tollsoft.de";
    XElement persons = new XElement(ns + "Personen");
Dabei wird das folgende XML erzeugt:
    <Personen xmlns="http://www.tollsoft.de" />
Um einen Namespace mit einem Präfix zu verbinden, können Sie an das Element ein XAttribute-Element anhängen. Übergeben Sie dem Konstruktor zuerst den durch die Eigenschaft Xmlns des XNamespace-Elements beschriebenen Namespace, und verbinden Sie diesen mit dem Präfix. Dem zweiten Parameter übergeben Sie die XNamespace-Variable. Das hört sich kompliziert an, deshalb sehen wir uns dazu auch sofort ein Beispiel an. Nehmen wir dazu das Beispiel von oben. Es soll ein Namespace definiert werden (http://www.tollsoft.de), der als Präfix pers vor jedem XML-Element angegeben werden soll. Der Code sieht dann wie folgt aus:
    XNamespace ns = "http://www.tollsoft.de";
    XElement persons = new XElement(ns + "Personen",
                           new XAttribute(XNamespace.Xmlns + "pers", ns),
                               new XElement(ns + "Person",
                                   new XElement(ns + "Name", "Fischer"),
                                   new XElement(ns + "Vorname", "Manfred"),
                                   new XElement(ns + "Alter", "45")
                               )
                       );
Aus diesem Codefragment resultiert das folgende XML-Dokument:
    <?xml version="1.0" encoding="utf-8"?>
    <pers:Personen xmlns:pers="http://www.tollsoft.de">
      <pers:Person>
        <pers:Name>Fischer</pers:Name>
        <pers:Vorname>Manfred</pers:Vorname>
        <pers:Alter>45</pers:Alter>
      </pers:Person>
    </pers:Personen>
### 16.9.3 Die Klasse »XDocument« Die beiden Klassen XElement und XDocument sind sich sehr ähnlich. Mit beiden können Sie XML-Daten laden und speichern sowie die XML-Daten bearbeiten. Der wesentlichste Unterschied ist, dass mit XDocument auch die XML-Deklaration bearbeitet werden kann und mehrere XML-Prozessinstruktionen hinzugefügt werden können. Möchten Sie also in den geladenen Daten auf XML-Kommentare, die XML-Deklaration oder XML-Prozessinstruktionen zugreifen, ist die Klasse XDocument jedenfalls die bessere Wahl. Dazu ein Beispiel:
    XDocument doc = new XDocument(
      new XDeclaration("1.0", "utf-8", "yes"),
      new XComment("Dokument enthält Personen"),
      new XProcessingInstruction("XML-StyleSheet", "meine.xsl"),
      new XElement("Personen",
          new XElement( ...));
### 16.9.4 Navigation im XML-Dokument Die beiden Klassen XElement und XDocument unterscheiden sich nicht wesentlich. Das wurde bereits eben erwähnt. Die gleiche Aussage gilt auch hinsichtlich der Navigationsmöglichkeiten. XElement und XDocument weisen zahlreiche Möglichkeiten auf, die eine Navigation durch ein XML-Dokument ermöglichen. Hier auf alle einzugehen, würde den Rahmen des Buches sprengen. Allerdings sollen Sie mit den wichtigsten Navigationsmethoden, die auch oft als Achsenmethoden bezeichnet werden, das Prinzip der Navigation kennenlernen. Als Ausgangspunkt dient uns dabei eine XML-Datei, die die Daten von drei Personen und einem Kunden beschreibt:
    <?xml version="1.0" encoding="utf-8"?>
    <Personen>
      <Person>
        <Name>Fischer</Name>
        <Vorname>Manfred</Vorname>
        <Alter>45</Alter>
        <Adresse Ort="Aachen" Strasse="Gasse 1"></Adresse>
      </Person>
      <Kunde>
        <Name>Tollsoft AG</Name>
      </Kunde>
      <Person>
        <Name>Meier</Name>
        <Vorname>Franz</Vorname>
        <Alter>76</Alter>
        <Adresse Ort="Ulm" Strasse="Avenue 12"></Adresse>
      </Person>
      <Person>
        <Name>Schmidt</Name>
        <Vorname>Detlef</Vorname>
        <Alter>28</Alter>
        <Adresse Ort="Bonn" Strasse="Weg 34"></Adresse>
      </Person>
    </Personen>
#### Die Methode »Element« Die Methode Element gestattet uns, ein Element aufgrund seines Namens zu selektieren. Wir wollen das sofort testen und schreiben den folgenden Code, um auf das erste <Person>-Element zuzugreifen.
    XElement root = XElement.Load(@"..\..\Personen.xml");
    XElement element = root.Element("Person");
    Console.WriteLine(element);
Hier wird die Load-Methode der Klasse XElement benutzt, um auf die Datei Personen.xml zuzugreifen. Die Variable root referenziert danach das Stammelement Personen des geöffneten XML-Dokuments. Darauf rufen Sie die Methode Element auf und übergeben ein XName-Objekt. Alternativ lässt sich auch der Bezeichner des gesuchten Elements als Zeichenfolge angeben, die implizit in den Typ XName konvertiert wird. Die Ausgabe an der Konsole wird wie folgt lauten:
    <Person>
      <Name>Fischer</Name>
      <Vorname>Manfred</Vorname>
      <Alter>45</Alter>
      <Adresse Ort="Aachen" Strasse="Gasse 1"></Adresse>
    </Person>
Ändern Sie die Ausgabeanweisung in der Weise ab, dass Sie das XElement element in eine Zeichenfolge konvertieren. Das ist möglich, weil die Klasse XElement eine ganze Reihe expliziter Typumwandlungen definiert, unter anderem auch in string:
    Console.WriteLine((string)element);
Nun werden nur noch die Werte der Elemente angezeigt, also:
    FischerManfred45
Wenn Sie der Methode Element einen Bezeichner übergeben, der nicht im XML-Dokument gefunden wird, ist der Rückgabewert null. #### Die Methode »Elements« Gehen wir nun einen Schritt weiter. Wie Sie dem XML-Dokument entnehmen können, sind unterhalb des Stammelements drei <Person>-Elemente und ein <Kunde>-Element angeordnet. Uns interessieren jetzt ausschließlich die <Person>-Elemente, aber nicht das <Kunde>-Element. Hier hilft uns die Methode Elements weiter, die im Gegensatz zu Element nicht nur das erste, sondern alle übereinstimmenden Elemente zurückliefert. Der Rückgabewert ist vom Typ IEnumerable<XElement>. Sie können die Methode parameterlos aufrufen oder ein Element vom Typ XName übergeben (oder, wie im Abschnitt zuvor bereits erwähnt, eine Zeichenfolge). Die Liste der <Person>-Elemente erhalten wir mit den folgenden Anweisungen:
    XElement root = XElement.Load(@"..\..\Personen.xml");
    IEnumerable<XElement> elements = root.Elements("Person");
    foreach (XElement item in elements)
      Console.WriteLine(item);
Die Anzeige an der Konsole gibt uns alle <Person>-Elemente einschließlich der untergeordneten Elemente aus:
    <Person>
      <Name>Fischer</Name>
      <Vorname>Manfred</Vorname>
      <Alter>45</Alter>
      <Adresse Ort="Aachen" Strasse="Gasse 1"></Adresse>
    </Person>
    ...
    <Person>
      <Name>Schmidt</Name>
      <Vorname>Detlef</Vorname>
      <Alter>28</Alter>
      <Adresse Ort="Bonn" Strasse="Weg 34"></Adresse>
    </Person>
Die Methode Elements lässt sich auch parameterlos aufrufen, also:
    XElement root = XElement.Load(@"..\..\Personen.xml");
    IEnumerable<XElement> elements = root.Elements();
Es werden daraufhin alle Elemente anzeigt, die dem Element untergeordnet sind, auf dem die Methode aufgerufen wird.
    <Person> ... </Person>
    <Kunde> ... </Kunde>
    <Person> ... </Person>
    <Person> ... </Person>
#### Die Methode »Attribute« Das Element Adresse hat mit Ort und Strasse zwei Attribute. Wir wollen nun in Erfahrung bringen, welcher Wert sich hinter dem Attribut Strasse des ersten Elements verbirgt. Dazu navigieren wir zum ersten <Person>-Element und von dort aus zum Element <Adresse>. Auf dessen Referenz rufen wir die Methode Attribut auf und geben dabei den Attributbezeichner an. Genauso wie die Methode Element liefert uns Attribut das erste passende Element. Wird kein übereinstimmendes Element gefunden, ist der Rückgabewert auch in diesem Fall null.
    XElement root = XElement.Load(@"..\..\Personen.xml");
    XElement element = root.Element("Person");
    XElement address = element.Element("Adresse");
    XAttribute attr = address.Attribute("Strasse");
    Console.WriteLine(attr);
Aus dem Code resultiert die folgende Konsolenausgabe:
    Strasse="Gasse 1"
#### Die Methode »Descendants« Die Methode Descendants (Nachfahren) ermittelt alle Elemente, die einem bestimmten Knoten untergeordnet sind. Wir wollen uns die Methode Descendants wieder am Beispiel unseres XML-Dokuments ansehen. Unser Ziel sei es, alle Elemente zu finden, die dem ersten <Person>-Element untergeordnet sind.
    XElement root = XElement.Load(@"..\..\Personen.xml");
    XElement element = root.Element("Person");
    IEnumerable<XElement> elements = element.Descendants();
    foreach (XElement item in elements)
      Console.WriteLine(item);
Weil wir durch den Aufruf der Methode Element zum ersten passenden <Person>-Element navigieren, wird die Ausgabe wie folgt lauten:
    <Name>Fischer</Name>
    <Vorname>Manfred</Vorname>
    <Alter>45</Alter>
    <Adresse Ort="Aachen" Strasse="Gasse 1"></Adresse>
Vergleichen Sie diese Ausgabe mit der sehr ähnlichen der Methode Element: Das aktuelle Element ist nicht Bestandteil der Ergebnismenge. Sie können der Methode Descendants auch ein XName-Objekt übergeben. Die Methode liefert dann alle untergeordneten Elemente, die dem mit XName beschriebenen Element entsprechen. Vielleicht interessieren Sie sich für sämtliche Namen im Dokument. Hier sind die Anweisungen dazu:
    XElement root = XElement.Load(@"..\..\Personen.xml");
    IEnumerable<XElement> elements = root.Descendants("Name");
    foreach (XElement item in elements)
      Console.WriteLine((string)item);
Da die Laufvariable item in einen String umgewandelt wird, lautet die Ausgabe:
    Fischer
    Tollsoft AG
    Meier
    Schmidt
#### Die Methode »Ancestors« Mit der Methode Descendants suchen Sie nach Elementen, die weiter im Inneren eines XML-Dokuments, also untergeordnet sind. Die Methode Ancestors (Vorfahren) arbeitet in exakt entgegengesetzter Richtung: Sie sucht nach übereinstimmenden Elementen oberhalb des aktuellen Knotens. Nachdem wir bisher gesehen haben, wie wir mit verschiedenen Methoden der Klasse XElement innerhalb eines XML-Dokuments navigieren können, wollen wir die Methode Ancestors dazu benutzen, endlich einen LINQ-Ausdruck zu verwenden, um ein bestimmtes Ziel bei der Auswertung zu erreichen. Es sollen uns dabei die Daten interessieren, die die Person mit dem Namen Meier beschreiben.
    XElement root = XElement.Load(@"..\..\Personen.xml");
    // nach der Person 'Meier' suchen
    XElement person = root.Descendants("Name")
                          .Where(pers => (string)pers == "Meier")
                          .First();
    // Knoten 'Person' des gefundenen Elements referenzieren
    var elemts = person.Ancestors("Person");
    // alle Elemente abfragen
    foreach(var item in elemts.Elements())
      // prüfen, ob das aktuelle Element Attribute hat
      if (item.HasAttributes){
        // Liste aller Attribute abfragen
        foreach (var temp in item.Attributes())
          Console.WriteLine((string) temp);
      }
      else
        Console.WriteLine((string)item);
Nach dem Laden des XML-Dokuments mit der Methode Load suchen wir mit Descendants alle <Name>-Elemente im Dokument. Mit der Erweiterungsmethode Where filtern wir alle Elemente heraus, die die Person Meier beschreiben. Die Erweiterungsmethode First liefert bei mehreren gleichnamigen Personen in der Ergebnismenge die zuerst gefundene Person. Da <Name> nur eines von mehreren datenbeschreibenden Elementen ist, müssen wir mit Ancestors in der Dokumentstruktur eine Ebene höher gehen, um das dazugehörige <Person>-Element referenzieren. Die Methode Elements gestattet es dann, auf dem gefundenen <Person>-Element alle untergeordneten Elemente mit Elements abzurufen. Dabei kommt erschwerend hinzu, dass eines der untergeordneten Elemente Attribute hat, die ebenfalls ausgewertet werden müssen. Daher prüfen wir die Elemente mit HasAttributes dahingehend, ob durch das aktuelle Element auch Attribute beschrieben werden. Sollte das der Fall sein, durchlaufen wir diese in einer Schleife. Die Liste aller Attribute stellt uns die Methode Attributes zur Verfügung.
Hinweis
Die Codefragmente zur Navigation finden Sie auf der Buch-DVD innerhalb des Beispielprogramms NavigationSamples.
### 16.9.5 Änderungen am XML-Dokument vornehmen Sicherlich werden Sie ein XML-Dokument nicht nur einlesen, sondern es darüber hinaus auch bearbeiten wollen. Dazu zählen sowohl das Ändern vorhandener Daten als auch das Hinzufügen oder Löschen vorhandener Elemente. #### XML-Dokument editieren Um den Wert eines bestimmten Elements zu ändern, müssen wir es zunächst einmal referenzieren. Angenommen, die Altersangabe des Herrn Schmidt in unserem Dokument sei deutlich zu jung ausgefallen, und wir müssen sie ändern. Dazu müssen wir Herrn Schmidt zunächst im XML-Dokument ausfindig machen, ehe wir die Anpassung vornehmen können. Zur Änderung eines Elements dient die Methode SetElementValue. Zur Änderung muss ein Aufruf auf dem übergeordneten Element erfolgen. SetElementValue erwartet zwei Parameter: Der erste beschreibt ein XName-Objekt, der zweite ist vom Typ Object. Das XName-Objekt kann durch den Bezeichner des zu ändernden Elements beschrieben werden, der zweite Parameter erwartet den neuen Wert. Das folgende Codefragment zeigt, wie Sie das zu ändernde Element aufspüren und ändern können.
    XElement root = XElement.Load(@"D:\Personen.xml");
    XElement name = root.Descendants("Name")
                    .Where(pers => (string)pers == "Fischer")
                    .First();
    XElement person = name.Parent;
    person.SetElementValue("Alter", 18);
Wie schon im letzten Beispielcode suchen wir zuerst den Eintrag Fischer im XML-Dokument. Im nächsten Schritt muss das direkt übergeordnete Element referenziert werden. Die im letzten Abschnitt vorgestellte Methode Ancestors ist dazu wenig geeignet, da sie uns eine Liste liefert, in der anschließend ein erneuter Suchvorgang ausgeführt werden müsste. Deutlich besser eignet sich die Eigenschaft Parent, die das direkt übergeordnete XElement liefert, also die Referenz auf das <Person>-Element des Herrn Fischer. Darauf wird die SetElementValue-Methode aufgerufen. #### Element hinzufügen Zum Hinzufügen von Elementen stehen Ihnen mehrere Möglichkeiten zur Verfügung. Zuerst seien die Methoden Add, AddAfterSelf, AddBeforeSelf und AddFirst genannt. Diese werden durch das XElement-Objekt zur Verfügung gestellt. Dabei wird AddFirst das neue Element vor dem aktuell referenzierten Element einfügen, AddAfterSelf fügt es nach dem aktuell referenzierten Element ein – jeweils auf derselben Strukturebene. AddFirst hingegen fügt das neue Element als erstes untergeordnetes Element ein, Add als letztes aller untergeordneten. Allen vier Methoden übergeben Sie das neue Element als XElement-Objekt, z. B. so:
    person.Add(new XElement("Telefon", 12345));
Sie können aber auch die im vorigen Abschnitt vorgestellte Methode SetElementValue zum Hinzufügen eines neuen untergeordneten Elements einsetzen. Wenn Sie dazu dem ersten Parameter ein XName-Objekt (oder eine entsprechende Zeichenfolge) übergeben, die namentlich nicht als untergeordnetes Element erkannt wird, wird das XName-Objekt als neu hinzuzufügendes Element interpretiert. Genauso soll es nun dem einzigen Kunden in unserem Dokument ergehen:
    XElement root = XElement.Load(@"D:\Personen.xml");
    XElement name = root.Descendants("Name")
                        .Where(pers => (string)pers == "Tollsoft AG")
                        .First();
    XElement person = name.Parent;
    person.SetElementValue("Telefon", 123456);
#### Element löschen Auch zum Löschen eines Elements werden von der Klasse XElement mehrere Methoden angeboten (z. B. Remove, RemoveAll, RemoveAttribute). Da uns die Methode SetElementValue aber bereits sehr vertraut geworden ist, wollen wir sie dazu benutzen. Sie müssen dazu nur im zweiten Parameter null übergeben. Das ist bereits alles. Um das noch zuvor zugestandene Element für die Telefonnummer des Kunden wieder zu entfernen, genügt die folgende Anweisung:
    person.SetElementValue("Telefon", null);