Das .NET Framework bringt bereits alle Hilfsmittel mit, um mit XML-Daten umzugehen. Innerhalb des Namensraums System.Xml bietet Ihnen .NET alle benötigten Klassen an. Bevor es an die Verarbeitung eines XML-Dokumentes geht, sollte zunächst ein einfaches Dokument vorhanden sein, damit die Beispiele in diesem Artikel ohne Probleme nachvollzogen werden können. Als Basis setzten wir hierzu das XML-Dokument aus Listing 1 ein.
Listing 1
<?xml version="1.0" encoding="utf-8" ?><buecher><buch isbn="3453061187"><titel>Die Jury</titel><autor>John Grisham</autor><preis>9,95</preis></buch><buch isbn="345307565X"><titel>Die Akte</titel><autor>John Grisham</autor><preis>9,95</preis></buch><buch isbn="3453879961"><titel>Die Firma</titel><autor>John Grisham</autor><preis>4,70</preis></buch></buecher>
Als Beispiel dient uns eine einfache Bücherliste mit drei Einträgen im XML-Format. Als Informationen speichern wir jeweils den Titel der Bücher, den Autor, den Preis und die ISBN-Nummer, die das Buch eindeutig identifiziert.
Als erstes Beispiel soll diese Datei eingelesen werden und die Datensätze sollen innerhalb eines ListView-Steuerelementes ausgegeben werden. Wir verwenden hierbei die Klasse System.Xml.XmlTextReader. XmlTextReader ermöglicht ein sequenzielles Einlesen der XML-Daten. Dies bedeutet, dass die Anwendung dieser Klasse sehr ressourcenschonend ist. Der Nachteil dieser Vorgehensweise ist aber ebenso offensichtlich. Das XML-Dokument wird sequenziell von vorne nach hinten durchlaufen. Es ist hierbei nicht möglich, gezielt in der XML-Struktur zu suchen oder zu navigieren. Für diese Aufgabe existiert das Document Object Model (DOM), welches genau diese Features unterstützt. Ein Beispiel zur Anwendung des DOM wird ebenfalls weiter unten in diesem Artikel besprochen. Für unser erstes Beispiel konzentrieren wir uns zunächst auf die Arbeit mit der Klasse XmlTextReader (Listing 2).
Listing 2
private void einlesen_btn_Click(object sender, System.EventArgs e){ArrayList buecher = new ArrayList();Buch buch = null;try{XmlTextReader xmlTextReader = new XmlTextReader("beispiel.xml");while (xmlTextReader.Read()){if (xmlTextReader.NodeType == XmlNodeType.Element){if (xmlTextReader.Name == "buch"){buch = new Buch();buecher.Add(buch);buch.isbn = xmlTextReader.GetAttribute("isbn");}else{if (xmlTextReader.Name == "titel"){if (xmlTextReader.MoveToContent() != XmlNodeType.None)buch.titel = xmlTextReader.ReadString();}else if (xmlTextReader.Name == "autor"){if (xmlTextReader.MoveToContent() != XmlNodeType.None)buch.autor = xmlTextReader.ReadString();}else if (xmlTextReader.Name == "preis"){if (xmlTextReader.MoveToContent() != XmlNodeType.None)buch.preis = xmlTextReader.ReadString();}}}}xmlTextReader.Close();}catch (Exception ex){MessageBox.Show("Beim Einlesen der XML-Datei ist ein Fehler aufgetreten: " + ex.Message);}// eingelesenen Datenbestand im ListView ausgebenforeach (Buch b in buecher){ListViewItem lvi = xmlListView.Items.Add(b.isbn);lvi.SubItems.Add(b.titel);lvi.SubItems.Add(b.autor);lvi.SubItems.Add(b.preis);}}}
Zur Verwaltung der eingelesenen Bücherliste kommt in unserem Beispiel eine ArrayList zum Einsatz. Dies hat den Vorteil, dass wir nicht an eine bestimmte Anzahl Datensätze gebunden sind und den zur Verfügung stehenden Speicherplatz dynamisch erweitern können. Die eingelesen Informationen zu einem Datensatz kapseln wir in der Klasse Buch. Zum Einlesen der XML-Daten verwenden wir eine Instanz der Klasse XmlTextReader. Dem Konstruktor dieser Klasse übergeben wir dabei den Dateinamen unserer XML-Datei als String. Der Lesevorgang wird über die Methode Read gestartet, deren Aufruf im Kopf einer While-Schleife untergebracht ist. Die Methode Read wird also so lange aufgerufen, bis das Ende der Datei erreicht ist und der Rückgabewert false wird. Innerhalb der while-Schleife prüfen wir nun mit der Bedingung xmlTextReader.NodeType == XmlNodeType.Element ob sich unsere Leseposition aktuell an einem XML Tag befindet. Dies ist dann der Fall, wenn die Leseposition bei
XML-Dateien schreiben mit XmlTextWriter
Analog zu unserem ersten Beispiel, welches den Einsatz der Klasse XmlTextReader zum sequenziellen Einlesen einer XML-Datei zeigt, existiert mit XmlTextWriter eine Klasse zum sequenziellen Erzeugen einer XML-Datei. Auch hierbei gilt, dass das sequenzielle Erzeugen einer XML-Datei die effizienteste Möglichkeit ist, diese Aufgabe zu erledigen. Das Erzeugen von XML-Dateien durch die Verwendung eines XmlDocument-Objektes (DOM) werden wir weiter unten in diesem Artikel besprechen. Das Schreiben mit der Klasse XmlTextWriter setzt voraus, dass Sie die XML-Datei von Anfang bis Ende erzeugen. Im Gegensatz zum Einsatz von XmlDocument haben Sie hierbei keine Möglichkeit, XML-Knoten an bestimmten Stellen einzufügen. Stattdessen müssen Sie für jede Änderung an einer bestehenden XML-Datei die kompletten Daten erneut schreiben.Soll in eine XML-Datei geschrieben werden, muss zunächst sicher gestellt werden, dass diese Datei existiert und zum Schreiben geöffnet werden kann. Prüfen Sie dies zunächst mittels
if(File.Exists(xmlFile)) { }
Bei der Variablen xmlFile handelt es sich um einen einfachen String. Dem Konstruktor der Klasse XmlTextWriter übergeben wir nun diesen String als ersten Parameter. Im zweiten Parameter übergeben wir eine Instanz der Klasse Encoding (Bei Standard-XML-Dateien Encoding.UTF8).
xmlTextWriter = new XmlTextWriter(xmlFile, Encoding.UTF8);
Die Initialisierung sollten Sie in einem try-catch-Block unterbringen, um eventuell auf einen Fehler beim Öffnen der Datei reagieren zu können. Bevor Sie nun schreibend auf unsere XmlTextWriter-Instanz zugreifen, sollten Sie zuvor noch einige Einstellungen vornehmen:
xmlTextWriter.Formatting = Formatting.Indented;xmlTextWriter.Indentation = 3;xmlTextWriter.IndentChar = ` `;
Zunächst setzen wir die Eigenschaft Formatting unseres XmlTextWriter-Objekts auf den Wert Formatting.Indented und signalisieren auf diese Weise, dass beim Schreiben der XML-Daten Zeilenumbrüche verwendet werden sollen. Über die Eigenschaft Indentation legen wir fest, um wie viele Zeichen jeweils eingerückt werden soll. Für unser Beispiel verwenden wir an dieser Stelle den Wert 3. Anschließend legen wir über IndentChar das Zeichen fest, welches zur Einrückung verwendet werden soll. Nun haben Sie alle Einstellungen getroffen und können mit dem eigentlichen Schreibvorgang beginnen.
Rufen Sie zunächst die Methode WriteStartDocument auf:
xmlTextWriter.WriteStartDocument(true);
Hierdurch wird zunächst die Startzeile
<?xml version=1.0 encoding=utf-8 standalone=yes?>
eingefügt. Über den Parameter vom Typ bool legen wir fest, dass das Attribut standalone auf den Wert yes gesetzt werden soll. Sie können jetzt das erste XML Tag über die Methode WriteStartElement(String elementName) anfügen. Geschlossen werden die geschrieben Startelemente über die Methode WriteEndElement(). Befinden Sie sich zwischen dem Aufruf der Methode WriteStartElement und WriteEndElement, beziehen sich die jeweiligen Methodenaufrufe jeweils auf dieses aktuelle XML Tag. Wird an dieser Stelle beispielsweise über WriteStartElement ein weiteres XML Tag erzeugt, entsteht eine entsprechende Verschachtelung. Soll dem aktuellen XML Tag ein Attribut hinzugefügt werden, setzen Sie hierzu die Methode WriteAttributeString(String attributeName, String attributeValue) ein. Um einen String innerhalb eines XML-Elementes unterzubringen, benötigen Sie keinen expliziten Aufruf der beiden Methoden WriteStartElement und WriteEndElement, sondern können direkt auf die Methode WriteElementString(String elementName, stringValue) zurückgreifen. Zusammengefasst könnte ein Schreibvorgang beispielsweise folgendermaßen aussehen:
xmlTextWriter.WriteStartDocument(true);xmlTextWriter.WriteComment( Beispiel für die Erzeugung einer XML-Datei );xmlTextWriter.WriteStartElement( buch );xmlTextWriter.WriteAttributeString("isbn , 3453061187 );xmlTextWriter.WriteElementString( titel , Die Jury );xmlTextWriter.WriteElementString( autor , John Grisham );xmlTextWriter.WriteElementString( preis , 9,95 );xmlTextWriter.WriteEndElement();xmlTextWriter.WriteEndDocument();
Wie Sie vielleicht bemerkt haben, haben wir neben den bereits besprochenen Methoden zusätzlich die Methode WriteComment eingesetzt, um einen XML-Kommentar zu erzeugen. Der gewünschte Kommentartext wird der Methode als String übergeben. Nach dem Ende des Schreibvorgangs sollten Sie außerdem daran denken, die Instanz der Klasse XmlTextWriter über die Methode Close zu schließen:
xmlTextWriter.Close();
Document Object Model
Nachdem wir in den letzten beiden Abschnitten Beispiele zum Arbeiten mit den Klassen XmlTextReader und XmlTextWriter besprochen haben, konnten Sie bereits erste Erfahrungen im Umgang mit XML-Dokumenten sammeln. Neben den bereits besprochenen Vorteilen bringen die bisher besprochenen Verfahren auch eine Reihe von Nachteilen mit sich. Der SAX-ähnliche (Simple API for XML) Zugriff mit XmlTextReader und XmlTextWriter arbeitet sequenziell. Dies bedeutet, dass das gesamte XML-Dokument sowohl beim Schreiben als auch beim Lesen von Anfang bis Ende der Reihe nach durchlaufen werden muss. Ein gezielter Zugriff auf einzelne XML-Knoten ist nicht möglich.Um dieser Beschränkung Abhilfe zu schaffen, existiert mit dem Document Object Model (DOM) eine weitere XML API, die den gezielten Zugriff auf einzelne Bestandteile der XML-Struktur erlaubt. Um Ihnen diese Flexibilität bieten zu können, ist es aber erforderlich, dass das gesamte XML-Dokument im Speicher gehalten wird. Dies führt natürlich zu einem erheblich höheren Speicherverbrauch und verlangsamt die Geschwindigkeit. Zentrale Klasse für den DOM-basierten XML-Zugriff ist XmlDocument, ebenfalls im Namespace System.Xml enthalten.
Der Umgang mit der Klasse XmlDocument ist nicht besonders schwierig, sodass wir direkt mit einem praktischen Beispiel einsteigen können. Zunächst benötigen wir eine Instanz der Klasse XmlDocument. Dem Konstruktor übergeben wir hierbei eine leere Parameterliste.
XmlDocument doc = new XmlDocument();
Der Dateiname des XML-Dokumentes, welches wir einlesen wollen, wird anschließend der Methode Load(String filename) als Parameter übergeben.
try{doc.Load(xmlFile);}catch (Exception ex){}
Nun haben Sie alle Vorbereitungen getroffen, um gezielt einzelne XML-Elemente einzulesen. Einzelne XML-Elemente werden über die XmlNode repräsentiert. Praktischerweise existiert ebenfalls die Klasse XmlNodeList, welche mehrere XmlNode aufnimmt und die Liste dynamisch verwaltet. Um eine Abfrage durchzuführen, setzen wir die Methode GetElementsByTagName ein. Als Resultat erhalten wir ein Objekt vom Typ XMLNodeList.
XmlNodeList buecherListe = doc.GetElementsByTagName( buch );
Als Parameter übergeben wir einen einfachen String mit dem Namen des XML-Elementes, welches wir auslesen möchten. Anschließend kann die Liste mit einer einfachen foreach-Schleife durchlaufen werden. Als Elemente werden jeweils Objekte vom Typ XmlNode zurückgegeben.
Auf Basis einer Instanz von XmlNode kann nun auf die einzelnen Bestandteile eines XML-Knotens zugegriffen werden. Mittels der Eigenschaft Attributes erhalten Sie direkten Zugriff auf Attribute. Hierzu übergeben Sie als Index einfach den Attributnamen:
XmlAttribute xmlAttr = buecherListe[0].Attributes[ isbn ];
Die Klasse XmlAttribute repräsentiert hierbei ein einzelnes Attribut. Um zu verhindern, dass Sie versuchen, auf Attribute zuzugreifen, welche nicht existieren, lässt sich anschließend durch eine if-Anweisung prüfen, ob das Objekt xmlAttr eine gültige Instanz enthält.
if (xmlAttr != null) { }
Um gezielt einen Unterknoten auszuwählen, setzen Sie die Methode SelectSingleNode ein. Als Parameter übergeben Sie dieser Methode einen String mit dem Namen des Unterelementes. Als Rückgabewert erhalten Sie ein Objekt vom Typ XMLNode.
XmlNode xmlNode = buecherListe[0].SelectSingleNode( titel );
Auch in diesem Fall sollten Sie nicht vergessen zu überprüfen, ob das zurückgegebene Objekt eine gültige Instanz der Klasse XmlNode enthält. Um auf den eingeschlossenen Text zwischen dem öffnenden und dem schließenden XML-Element zuzugreifen, nutzen Sie einfach die Eigenschaft InnerText der Klasse XmlNode. InnerText existiert ebenfalls für die Klasse XmlAttribute und ermöglicht es, auf einfache Weise auf den Text-String eines XML-Attributes zuzugreifen. Für unsere beiden Objekte xmlAttr und xmlNode resultiert folgender Code:
String attrText = xmlAttr.InnerText;String nodeText = xmlNode.InnerText;
Erzeugen und Ändern von XML-Dokumenten über das Document Object Model
Neben dem lesenden Zugriff auf XML-Dokumente erlaubt Ihnen das Document Object Model mit einfachen Mitteln das Erzeugen und Ändern von XML-Strukturen. An einem einfachen Beispiel sehen wir uns zunächst das Erzeugen eines XML-Dokumentes an:XmlDocument xmlDoc = new XmlDocument();XmlNode rootNode = xmlDoc.CreateElement("buecher");xmlDoc.AppendChild(rootNode);XmlDeclaration xmlDeclaration = xmlDoc.CreateXmlDeclaration("1.0", "utf-8", "yes");xmlDoc.InsertBefore(xmlDeclaration, rootNode);xmlDoc.Save("domtest.xml");
Zunächst wird eine Instanz der Klasse XmlDocument als Basis für das zu erzeugende XML-Dokument benötigt. Anschließend wird durch den Einsatz der Methode CreateElement der Klasse XmlDocument ein neues XML-Element erzeugt, welches wir durch die Methode AppendChild als Root-Element hinzufügen. Da jedes gültige XML-Dokument eine XML-Deklaration zu Beginn benötigt, erzeugen wir eine solche über die Methode CreateXmlDeclaration, welche ein Objekt vom Typ XmlDeclaration liefert. Die Methode CreateXmlDocument erwartet hierbei drei Parameter. Als ersten Parameter übergeben wir den String 1.0, da XML momentan nur in dieser Version verfügbar ist. Als zweiten Parameter übergeben wir utf-8 und legen hiermit die Codierung fest. Der dritte Parameter wird für den Wert des Attributes standalone innerhalb der XML-Deklaration verwendet. Das Objekt xmlDeclaration wird schließlich über die Methode InsertBefore vor dem root-Element angefügt. Um das XML-Dokument in eine Datei zu schreiben, wird anschließend die Methode Save aufgerufen, die als Parameter den Dateinamen enthält. Als Resultat erhalten wir folgende XML-Datei:
<?xml version="1.0" encoding="utf-8" standalone="yes"?><buecher />
Ebenso einfach lassen sich zu einem bestehenden XML-Dokument Knoten hinzufügen. Hierbei erzeigen wir über eine Instanz der Klasse XmlDocument zunächst ein Objekt vom Typ XmlNode nach folgendem Muster:
XmlNode newNode = xmlDoc.CraeteElement( buch );
Zu dem neuen Knoten lassen sich jetzt Attribute hinzufügen. Im ersten Schritt wird hierzu eine neue Instanz der Klasse XmlAttribute benötigt, die dann im zweiten Schritt dem newNode-Objekt zugeordnet werden kann.
XmlAttribute isbnAttr = xmlDoc.CreateAttribute( isbn );isbnAttr.Value = 34345645398 ;newNode.Attributes.Append(isbnAttr);
Analog zum vorherigen Beispiel setzen wir die Methode CreateAttribute zur Erzeugung eines XmlAttribute-Objektes ein. Als Parameter wird auch hier ein String übergeben, der den Namen des neuen Attributes erhält. Der Wert des soeben erstellten Attributes kann nun über die öffentliche Eigenschaft Value der Klasse XmlAttribute direkt zugewiesen werden. Im letzten Schritt kommt die Methode Append zum Einsatz, um die Zuordnung zwischen XML-Knoten und XML-Attribut herzustellen. Für den neuen XML-Knoten mit dem Namen buch müssen nun natürlich noch Unterelemente erzeugt werden - beispielweise ein Element, welches den Titel des Buches enthält. Die Vorgehensweise ist auch in diesem Fall nahezu identisch:
XmlNode titelNode = xmlDoc.CreateElement( titel );titelNode.InnerText = Der Titel des Buches ;newNode.AppendChild(titelNode);
Über die Eigenschaft InnerText lässt sich direkt der eingeschlossene Text-String des jeweiligen XML-Knotens editieren. Die Methode AppendChild fügt das neue Element anschließend automatisch als Subelement hinzu.
Sie haben nun gesehen, mit welchen Schritten sich zusätzliche Daten zu einem existierenden XML-Dokument hinzufügen lassen. An diese Beschreibung schließt sich sogleich die nächste Fragestellung an. Wie kann an einem bereits enthaltenen XML-Knoten eine gezielte Änderung durchgeführt werden? Um dieses Problem zu lösen, kommt erneut unsere Bücherliste aus Listing 1 zum Einsatz. Vorstellbar ist die Aufgabenstellung, dass an einem Datensatz zu einem eingetragenen Buch der Preis geändert werden soll. Als Ausgangspunkt für unsere Suche dient die zu jedem Buch eindeutige ISBN-Nummer, die wir als Attribut im XML-Knoten buch hinterlegt haben. Listing 3 zeigt den Programmtext, der eine Änderung des Preises zu einem bestimmten Buch innerhalb des XML-Dokumentes bewirkt.
Listing 3
XmlDocument xmlDoc = new XmlDocument();xmlDoc.Load("beispiel.xml");XmlNamespaceManager nsManager = new XmlNamespaceManager(xmlDoc.NameTable);string xpathquery = "/buecher/buch[attribute::isbn='3453061187']";XmlNode theNode = xmlDoc.SelectSingleNode(xpathquery, nsManager);theNode.ChildNodes[2].InnerText = "22.90";xmlDoc.Save("beispiel.xml");
Wie Sie erkennen können, sind auch hier nur wenige Schritte notwendig. Zunächst erstellen wir wieder eine Instanz der Klasse XmlDocument und öffnen über die Methode Load unsere Beispiel-XML-Datei. Anschließend benötigen wir eine Instanz der Klasse XmlNamespaceManager. Um nun einen XML-Knoten gezielt zu suchen, wird ein XPath-String erzeugt, welcher auf den Knoten mit dem ISBN-Attribut mit dem Wert 3453061187 verweist. Die Methode SelectSingleNode benötigt im nächsten Schritt als Parameter sowohl den XPath-Ausdruck als auch das Objekt vom Typ XmlNamespaceManager. Die Methode SelectSingleNode wählt jeweils nur einen einzigen XML-Knoten aus, weshalb der Rückgabewert vom Typ XmlNode ist. Wird als Ergebnis eine Liste von XML-Knoten erwartet, können Sie an dieser Stelle die Methode SelectNodes einsetzen. Hier entspricht der Rückgabewert dann einem Objekt vom Typ XmlNodeList. Die Parameterliste bleibt identisch. Anschließend kann über theNode.ChildNodes[2].InnerText auf die Preisinformation zu dem ausgewählten Buch zugegriffen und die gewünschte Änderung vorgenommen werden. Im letzten Schritt werden die geänderten Daten in die Datei übertragen.


