296 30 2MB
German Pages 144
XML mit .NET
.NET
Essentials
Armin Hanisch
XML mit .NET Programmierung und Basisklassen
An imprint of Pearson Education München • Boston • San Francisco • Harlow, England Don Mills, Ontario • Sydney • Mexico City Madrid • Amsterdam
Die Deutsche Bibliothek – CIP-Einheitsaufnahme Ein Titeldatensatz für diese Publikation ist bei Der Deutschen Bibliothek erhältlich. Die Informationen in diesem Produkt werden ohne Rücksicht auf einen eventuellen Patentschutz veröffentlicht. Warennamen werden ohne Gewährleistung der freien Verwendbarkeit benutzt. Bei der Zusammenstellung von Abbildungen und Texten wurde mit größter Sorgfalt vorgegangen. Trotzdem können Fehler nicht vollständig ausgeschlossen werden. Verlag, Herausgeber und Autoren können für fehlerhafte Angaben und deren Folgen weder eine juristische Verantwortung noch irgendeine Haftung übernehmen. Für Verbesserungsvorschläge und Hinweise auf Fehler sind Verlag und Herausgeber dankbar. Alle Rechte vorbehalten, auch die der fotomechanischen Wiedergabe und der Speicherung in elektronischen Medien. Die gewerbliche Nutzung der in diesem Produkt gezeigten Modelle und Arbeiten ist nicht zulässig. Fast alle Hardware- und Softwarebezeichnungen, die in diesem Buch erwähnt werden, sind gleichzeitig eingetragene Warenzeichen oder sollten als solche betrachtet werden. Umwelthinweis: Dieses Produkt wurde auf chlorfrei gebleichtem Papier gedruckt. Die Einschrumpffolie – zum Schutz vor Verschmutzung – ist aus umweltverträglichem und recyclingfähigem PE-Material.
5 05
4 04
3
2
1
03
02
ISBN 3-8273-1998-6 © 2002 by Addison-Wesley Verlag, ein Imprint der Pearson Education Deutschland GmbH, Martin-Kollar-Straße 10–12, D-81829 München/Germany Alle Rechte vorbehalten Einbandgestaltung: Barbara Thoben, Köln Lektorat: Christine Auf, [email protected], Tobias Draxler, [email protected] Korrektorat: Katja Treu, München Herstellung: Monika Weiher, [email protected] Satz: reemers publishing services gmbh, Krefeld, www.reemers.de Druck und Verarbeitung: Media-Print, Paderborn Printed in Germany
.NET Essentials
Inhalt Einführung
7
Vorwort Voraussetzungen Typografie Beispielcodes, Errata, Hinweise
7 7 8 8
1
XML-Basics
9
1.1 1.2 1.3 1.4
XML-Terminologie Namensräume Die .NET-Namensräume Unterstützte Standards
9 11 13 13
2
XmlReader und XmlWriter
15
2.1 2.2 2.3
Lesen von XML-Daten Schreiben von XML-Dateien Zusammenfassung
15 21 26
3
Serialisierung und XML
27
3.1 3.2
XML-Serialisierung im Detail Zusammenfassung
32 37
4
Document Object Model
39
4.1 4.2 4.3 4.4 4.5
Streaming oder DOM? XML-Dokumente laden und lesen Ändern von XML-Dokumenten per DOM Ereignisbehandlung Zusammenfassung
39 39 44 48 52
5
XPath-Abfragen
53
5.1 5.2 5.3 5.4 5.5
Warum XPath? Grundlagen SelectNodes und SelectSingleNode Nutzung des XPathNavigators Zusammenfassung
53 53 59 61 65
6
XML-Namensräume
67
6.1 6.2
Einführung Zusammenfassung
67 77
5
.NET
Inhalt ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
6
7
XSL-Transformationen
79
7.1 7.2 7.3 7.4 7.5
Grundlagen Stylesheets laden Transformation durchführen Transformationen verketten und optimieren Zusammenfassung
79 81 82 86 87
8
Validieren von XML
89
8.1 8.2 8.3
Warum ein Schema? XSD – der Standard Zusammenfassung
89 96 112
9
XML und Daten
113
9.1 9.2 9.3 9.4
Einführung DataSet und Co. XmlDataDocument Zusammenfassung
113 113 121 125
10
XML-Dokumentation
127
10.1 10.2
XML-Kommentare Zusammenfassung
127 134
11
Ausblick
135
Stichwortverzeichnis
137
.NET Essentials
Einführung Vorwort Thema dieses Bandes sind die XML-Basisklassen des .NET-Frameworks und ihre Verwendung sowie die Rolle von XML als Basisinfrastruktur innerhalb der .NET-Umgebung. XML ist bereits seit einigen Jahren dabei, sich zur neuen ‚lingua franca’ innerhalb der IT-Welt zu entwickeln und auch Microsoft setzt im Rahmen der .NET Initiative auf XML. Es gibt wohl kaum einen Bereich innerhalb des .NET-Frameworks, in dem XML keine Rolle spielt. Als universelles Austausch- und Speicherformat wird es für Konfigurationsdateien, ADO.NET, die Objektserialisierung, Schemas und XSLT bis hin zu SOAP und Web Services als sprachneutrales Format benutzt. Als Entwickler haben Sie kompletten Zugriff auf eine Reihe von hochoptimierten und vollständigen Klassen, die nahezu jede Anforderung abdecken und über den Funktionsumfang der alten XML Core Services der COM-Welt (MSXML) weit hinausgehen. Mit nur wenigen Zeilen an Code sind Sie in der Lage, Daten zu transformieren, XML-Strukturen aufzubauen oder zu validieren und mit ADO.NET sogar beliebige XML-Dateien entweder als relationales DataSet oder als XML-Baumstruktur zu behandeln.
Voraussetzungen Benötigte Software Zum Nachvollziehen der Beispiele in diesem Buch benötigen Sie einen Rechner mit Windows 2000 oder Windows XP, Visual Studio .NET oder das .NET-Framework SDK. Die Beispiele zu diesem Buch finden Sie zum Download auf der Website für diese Buchreihe, die unter dem URL http://www.dotnet-essentials.de erreichbar ist. Dieses Buch geht davon aus, dass Sie zumindest das .NET-Framework und die Kommandozeilen-Tools installiert haben. Sie benötigen nicht unbedingt Visual Studio .NET, obwohl Sie im Kapitel über XML-Validierung und XMLDokumentation sicher davon profitieren, da die grafische Oberfläche doch einige Aufgaben erleichtert. Aber auch diese Kapitel enthalten Hinweise, wie Sie ohne das Visual Studio .NET zurechtkommen.
7
.NET
Einführung ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
Vorkenntnisse Die Zielgruppe dieser Buchreihe sind Entwickler mit Programmiererfahrung, die sich bereits erfolgreich mit den Grundlagen von .NET und Visual Studio vertraut gemacht haben. Sie finden in diesen Bänden praktische Beispiele und kompakte Informationen ohne die endlose Wiederholung von Grundlagen. Der Autor geht davon aus, dass Sie C# beherrschen bzw. Code, der in C# geschrieben wurde, lesen können und mit der objektorientierten Programmierung vertraut sind. Aus Platzgründen sind viele Listings nicht komplett abgedruckt, sondern zeigen lediglich den Code einer (meist statischen) Methode ohne die Klassendefinition und die using-Anweisungen drumherum, da Sie sicher in der Lage sind, diese zu ergänzen. Wo es sinnvoll erschien oder bestimmte zusätzliche Namespaces eingebunden wurden, wurden komplette Listings abgedruckt. Die Quellcodes der Downloads sind natürlich alle vollständig.
Typografie Normaler Fließtext wird mit dieser Schriftart dargestellt. Falls innerhalb eines Textabsatzes einzelne Schlüsselwörter oder Klassen- bzw. Variablennamen vorkommen, werden diese als Klassenname hervorgehoben. Listings im Text besitzen diese Schriftart und wurden nach Möglichkeit so umbrochen, dass die Struktur trotz der geringen Spaltenbreite erhalten bleibt. Einzelne Listingszeilen erhalten diese Formatierung
Auswahlen in einem MENÜNAMEN oder als DIALOGOPTION werden ebenfalls besonders gekennzeichnet. Alle Dateinamen, URLs oder Pfadangaben sind wie hier gezeigt hervorgehoben.
Beispielcodes, Errata, Hinweise Die Beispielcodes zu diesem Band der Reihe .NET-Essentials finden Sie im Internet unter dem URL http://www.DotNet-Essentials.de. Hier finden Sie auch zusätzliche Hinweise, Ergänzungen und Korrekturen zu diesem und den anderen Bänden der Reihe. Alle Listings, Codeangaben und Verweise in diesem Buch wurden nach bestem Wissen und Gewissen auf Fehlerfreiheit geprüft. Sollten Sie dennoch einen Fehler finden oder Anregungen und Kommentare zu diesem Buch haben, freut sich der Autor auf Ihr Feedback.
8
.NET
Essentials
1
XML-Basics
1.1 XML-Terminologie Knoten, Elemente, Tags und Attribute Eine XML-Struktur (Struktur deshalb, da XML nicht notwendigerweise immer in einer Datei vorliegen muss, beispielsweise als MemoryStream) ist eine baumförmige Struktur. Dieser Baum besteht aus einzelnen Knoten oder Elementen, die in textueller Form durch entsprechende Tags dargestellt werden. Ein Element kann einen reinen Textinhalt oder Unterelemente (oder Kindknoten) besitzen, auch ein gemischter Inhalt aus Text und Unterelementen ist möglich. Je nach Programmiermodell (Streamingzugriff über XmlReader oder DOM-Zugriff über ein XmlDocument) wird diese XML-Struktur seriell gelesen und Element für Element an die Anwendung weitergegeben oder die komplette Datei wird gelesen und die Baumstruktur im Speicher aufgebaut (wobei hier der Ressourcenbedarf dementsprechend groß werden kann). Ein Element kann entweder ein XML-Element, ein Kommentar, eine Verarbeitungsanweisung an den XML-Parser oder Leerraum sein.
XML als Baum oder als Stream
Elemente können Attribute enthalten, die allerdings in keiner bestimmten Reihenfolge vorliegen müssen (der XML-Standard erlaubt ausdrücklich eine wechselnde Attributreihenfolge bei gleichen Elementen) und die daher in den XML-Klassen in einer ungeordneten Auflistung mitgeführt werden. Attribute besitzen einen Namen und einen Wert.
XML-Zeichen und XML-Einheiten Eine XML-Datei besteht aus verschiedenen Einheiten: Kommentaren, Leerraum, Verarbeitungsanweisungen oder Elementen. Auch hier gelten einige Regeln, ebenso wie für die Angabe von bestimmten Zeichen innerhalb einer XML-Datei. Leerraum wird innerhalb einer XML-Datei ignoriert oder beim Parsing mit beachtet, abhängig von der Einstellung der entsprechenden Klasse. Im .NET-Framework geschieht dies über eine Eigenschaft mit dem Namen PreserveWhitespace. Ist Leeraum innerhalb eines Elements von Bedeutung, beispielsweise für die Einrückung in einem Listing, dann sollten Sie das Attribut xml:space verwenden und einen Wert von "preserve" setzen. Der Defaultwert "default" legt fest, dass die Einstellungen des Parsers verwendet werden.
Leerraum
9
.NET
1 XML-Basics ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
Kommentare
Eín Kommentar beginnt auch in XML mit der Zeichenfolge
Document
Das Dokumentenobjekt, welches die Wurzel des XMLBaums darstellt.
DocumentFragment
Ein XML-Fragement beinhaltet einen Knoten oder einen Teilbaum, nicht notwendigerweise in einem eigenen Dokument.
DocumentType
Der Dokument-Typ, z.B.
Element
Ein einzelnes XML-Element, z.B.
EndElement
Ein schließendes Tag für ein Element, z.B.
Tabelle 2.1: Die von XmlReader gelieferten Knotentypen
16
Lesen von XML-Daten ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
Name
Beschreibung
EndEntity
Wird als Ergebnis einer Entity-Auflösung mit ResolveEntity zurückgeliefert und markiert das Ende eines Entity.
Entity
Die Deklaration eines XML-Entity, z.B.
EntityReference
Die Referenz auf ein Entity, z.B. ä
None
Wird geliefert, wenn noch nie die Methode Read aufgerufen wurde.
Notation
Ein Notationseintrag in der DTD, z.B.
ProcessingInstruction
Eine Verarbeitungsanweisung, z.B.
SignificantWhitespace
Leerraum, der beachtet werden soll, wenn z.B. die Option xml:space="preserve" verwendet wurde.
Text
Der Textinhalt eines XML-Knotens
Whitespace
Leerraum zwischen Auszeichnungsknoten
XmlDeclaration
Die XML-Deklaration selbst, z.B. ; diese Anweisung muss der erste Knoten im Dokument sein.
Tabelle 2.1: Die von XmlReader gelieferten Knotentypen (Fortsetzung)
Knotentypen unterscheiden Für die nachfolgende kleine XML-Datei, die in den nächsten Beispielen benutzt wird, soll nun jeweils der Knotentyp sowie der Name des Elements ausgegeben werden.
Algeirs overcast 11 93% E 5 1028 rising
Athens mostly cloudy 16 77% SW 2 1024
17
.NET
2 XmlReader und XmlWriter ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
rising
Listing 2.2: Die Beispieldatei mit XML-Inhalt
Da sich der Leerraum (white space) schlecht anzeigen lässt, erfolgt die Ausgabe hier über die Klasse Convert als Hexdump der Zeichencodes. // neue methode ReadXmlDoc mit Knotenbehandlung using System; using System.XML; using System.Text; namespace XmlReader { class clsReaderClass { XmlTextReader rdr; public clsReaderClass(string docurl) { rdr = new XmlTextReader(docurl); } public void ReadXmlDoc() { while (rdr.Read()) { // den typ des knotens als string ausgeben Console.Write(rdr.NodeType); // abhängig vom typ ausgabe bauen switch(rdr.NodeType) { // ein element: name ausgeben case XmlNodeType.Element: Console.Write(" "); break; // schliessendes tag: / + name case XmlNodeType.EndElement: Console.Write(" "); break; // textinhalt: achtung! value statt name! case XmlNodeType.Text: Console.Write(" " + rdr.Value); break;
18
Lesen von XML-Daten ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
// whitespace: zeichen als hexdump ausgeben case XmlNodeType.SignificantWhitespace: case XmlNodeType.Whitespace: for(int i = 0; i < rdr.Value.Length; i++) { int cv = Convert.ToUInt16(rdr.Value[i]); Console.Write(" {0:X2}", cv); } break; } Console.WriteLine(); } rdr.Close(); } // // Die Testklasse für clsReaderClass // class TestClass { static void Main(string[] args) { clsReaderClass rc; rc = new clsReaderClass("miniwetter.xml"); rc.ReadXmlDoc(); } } } Listing 2.3: Unterscheidung der einzelnen Knotentypen
Hier die Ausgabe des Programms bis zum Ende des ersten stationKnotens: XmlDeclaration Whitespace 0D 0A Element Whitespace 0D 0A 09 Element Whitespace 0D 0A 09 09 Element Text Algeirs EndElement Whitespace 0D 0A 09 09 Element Text overcast EndElement Whitespace 0D 0A 09 09 Element
19
.NET
2 XmlReader und XmlWriter ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
Text 11 EndElement Whitespace 0D 0A 09 09 Element Text 93% EndElement Whitespace 0D 0A 09 09 Element Text E EndElement Whitespace 0D 0A 09 09 Element Text 5 EndElement Whitespace 0D 0A 09 09 Element Text 1028 EndElement Whitespace 0D 0A 09 09 Element Text rising EndElement Whitespace 0D 0A 09 EndElement Listing 2.4: Die Ausgabe der einzelnen Knoten
Fehlerbehandlung beim Lesen Wird die Wohlgeformtheit des Dokuments durch eine ungültige XMLKonstruktion verletzt, wird eine XmlException ausgelöst und der Lesevorgang abgebrochen.
Lesen bestimmer Elemente und Datentypen Leere Elemente finden
Für viele Anwendungszwecke muss sichergestellt werden, dass der aktuelle Knoten ein bestimmtes Element enthält. Hierzu exisitieren weitere Methoden der XmlReader-Klasse, die ein bestimmtes Element erwarten. So kann mit der Methode IsStartElement festgestellt werden, ob der aktuelle Knoten ein Starttag ist, während beispielsweise IsEmptyElement dann true liefert, wenn es sich um ein »leeres« Element in der Form handelt. Die Methode ReadStartElement erlaubt das Lesen ganz bestimmter Elemente und ein manuelles Parsing der Datei, so dass eine Exception ausgelöst wird, wenn das Element nicht den erwarteten Namen besitzt.
20
Schreiben von XML-Dateien ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
2.2 Schreiben von XML-Dateien Das Schreiben von XML-Daten als Strom erfolgt mit Hilfe einer von der abstrakten Klasse XmlWriter abgeleiteten Klasse. In der aktuellen Version des .NET-Frameworks existiert eine Implementierung in der Klasse XmlTextWriter, die auch für die folgenden Beispiele benutzt wird.
Instantiierung der Klasse Der Konstruktor der Klasse XmlTextWriter ist dreifach überladen. Sie akzeptiert entweder einen vorhandenen TextWriter, einen Stream oder einen Dateinamen als Parameter, wobei für den Dateinamen und den IO-Stream als zweiter Parameter noch die Codierung der Zeichen angegeben werden kann (wird dieser auf null gesetzt, erfolgt die Ausgabe mit der Standardcodierung UTF-8). // instanz per dateinamen erzeugen // die datei wird bei existenz neu überschrieben XmlTextWriter xtw = new XmlTextWriter(@"c:\tmp\ausgabe.xml", null); Listing 2.5: Beispiel-Instantiierung per Dateinamen für XmlTextWriter
Schreiben von Elementen und Attributen Die Methoden zum Erstellen des Inhaltes sind leicht zu finden und anzuwenden, deren Namen sind ebenfalls weitestgehend selbsterklärend. Das folgende Beispiel zeigt eine Methode zum Schreiben einer einfachen XML-Datei. public static void DemoXmlTextWriter() { // instanz erzeugen XmlTextWriter xtw = new XmlTextWriter(@"c:\tmp\ausgabe.xml", null); // inhalte schreiben xtw.WriteStartElement("buecher"); xtw.WriteElementString("titel", "Von Windows verweht ..."); xtw.WriteStartElement("preis"); xtw.WriteAttributeString("waehrung","EUR"); xtw.WriteString("14.55"); xtw.WriteEndElement(); xtw.WriteEndElement(); // writer schliessen
21
.NET
2 XmlReader und XmlWriter ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
xtw.Close(); Console.WriteLine("Datei geschrieben."); } Listing 2.6: Schreiben von XML, die erste Version
WriteStartElement vs. WriteElement String
Wie Sie aus dem Listing erkennen, existiert zum Schreiben von Elementen eine Methode WriteElementString, die Starttag, Inhalt und Endtag in einem Aufruf schreibt. Soll das Element jedoch Unterelemente oder Attribute enthalten, dann benutzen Sie besser die Methoden WriteStartElement und WriteEndElement, mit denen Sie die einzelnen Elemente gezielt beginnen und beenden können. Die Ausgabe des Programms in der Datei c:\tmp\ausgabe.xml sieht dann so aus: Von Windows verweht ...14.55 Listing 2.7: Inhalt der erzeugten Datei
Hier sind noch einige Sachen zu verbessern. Zuerst ist dies keine gültige XML-Datei, noch nicht mal eine wohlgeformte, da die Verarbeitungsanweisung fehlt. Außerdem wäre eine für den (menschlichen) Entwickler etwas bessere Lesbarkeit recht schön. Diese beiden Punkte erreichen Sie durch ein paar Änderungen im Code. Zuerst werden die beiden Methoden WriteStartDocument und WriteEndDocument integriert, die für einen sauberen »Rahmen« um die XML-Struktur sorgen. Danach wird die Formatierung noch umgestellt. Hier die Änderungen im Code: public static void DemoXmlTextWriter() { // instanz erzeugen XmlTextWriter xtw = new XmlTextWriter(@"c:\tmp\ausgabe.xml", null); // formatierung einstellen // pro ebene 1 tab eingerückt xtw.Indentation = 1; xtw.IndentChar = '\t'; xtw.Formatting = Formatting.Indented; // inhalte schreiben xtw.WriteStartDocument(); xtw.WriteStartElement("buecher"); xtw.WriteElementString("titel", "Von Windows verweht ..."); xtw.WriteStartElement("preis"); xtw.WriteAttributeString("waehrung","EUR");
22
Schreiben von XML-Dateien ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
xtw.WriteString("14.55"); xtw.WriteEndElement(); xtw.WriteEndElement(); xtw.WriteEndDocument(); // writer schliessen xtw.Close(); Console.WriteLine("Datei geschrieben."); } Listing 2.8: Ausgabe mit XML-PI und Formatierung
Jetzt findet sich in der Ausgabe auch eine wohlgeformte XML-Struktur mit einer lesbaren Formatierung:
Von Windows verweht ... 14.55
Listing 2.9: Inhalte der Ausgabedatei
Nachdem es im Gegensatz zu beispielsweise HTML bei XML immer ein schließendes Tag geben muss, werden Elemente ohne Inhalt in der Notation geschrieben. Viele Programme kommen damit aber nicht zurecht (vor allem ältere Browser), daher kann anstelle der Methode WriteEndElement die Methode WriteFullEndElement benutzt werden, die eine Ausgabe in der Form erzeugt. Neben der Methode WriteString zum Schreiben von Textinhalten existieren noch Methoden zum Schreiben von Binärdaten in BinHex- oder Base64-Notation, als CDATA-Bereich oder als Entity-Referenz für Unicode-Zeichen. Alle diese Methoden folgenden aber dem gleichen Prinzip, so dass ein eigenständiges Beispiel für jede dieser Methoden hier nicht notwendig ist.
XML mit XmlWriter und XmlReader modifzieren Als Abschluss für dieses Kapitel ein kleines Beispiel, wie mit Hilfe dieser Streamingklassen auch große Dateien ressourcenschonend verarbeitet werden können. Nehmen wir an, in einer als XML-Datei vorliegenden Bücherliste soll die Umstellung von DEM auf EUR vorgenommen werden (gut, dass wir das schon alle hinter uns haben!). Mit einem XmlTextReader werden die einzelnen Knoten gelesen und über eine XmlTextWriterInstanz in die Ausgabedatei geschrieben. Wird ein Element mit dem Namen »Preis« angetroffen, wird dessen Textinhalt von Mark nach Euro
23
.NET
2 XmlReader und XmlWriter ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
umgerechnet und ein evtl. vorhandenes Attribut »Währung« ebenfalls korrekt umgestellt. // umwandeln von xml-dateien per streaming-zugriff public static void DemoModifyXml(string inURL, string outURL) { // die reader und writer XmlTextReader rdr = new XmlTextReader(inURL); XmlTextWriter wrt = new XmlTextWriter(outURL, null); // ausgabe einruecken wrt.Formatting = Formatting.Indented; wrt.Indentation = 4; wrt.IndentChar = (char)32; // marker fuer preis-element bool bPreistag = false; // dokumenten-start-pi schreiben wrt.WriteStartDocument(); // alle knoten lesen while (rdr.Read()) { // abhängig vom typ ausgabe bauen switch(rdr.NodeType) { // element, auf "preis" testen case XmlNodeType.Element: bPreistag = (rdr.Name == "preis"); if(bPreistag) Console.WriteLine("{0} gefunden!", rdr.Name); wrt.WriteStartElement(rdr.Name); // attribute mit kopieren if(rdr.HasAttributes) { for(int i = 0;i < rdr.AttributeCount;i++) { rdr.MoveToAttribute(i); // waehrung ist jetzt EUR // also eintragen if(rdr.Name == "waehrung") wrt.WriteAttributeString(rdr.Name,"EUR"); else wrt.WriteAttributeString(rdr.Name,rdr.Value); } } break;
24
Schreiben von XML-Dateien ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
// schliessendes tag: / + name case XmlNodeType.EndElement: if(rdr.Name == "preis" && bPreistag) { bPreistag = false; Console.WriteLine("/{0} gefunden!", rdr.Name); } wrt.WriteEndElement(); break; // textinhalt: achtung! value statt name! case XmlNodeType.Text: if(bPreistag) { decimal betrag = decimal.Parse(rdr.Value); betrag = betrag / 1.955830m; Console.WriteLine("Konvertiert!"); wrt.WriteString(betrag.ToString("F2")); } else wrt.WriteString(rdr.Value); break; } } rdr.Close(); wrt.WriteEndDocument(); wrt.Close(); } Listing 2.10: XML-Konvertierung manuell
Hier die beiden Dateien, zuerst die Eingabe-, dann die Ausgabedatei nach der Konvertierung:
Von Windows verweht ... 29,90
Alice im Windowsland 34,80
Listing 2.11: Die Eingabedatei
25
.NET
2 XmlReader und XmlWriter ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
Von Windows verweht ... 15,29
Alice im Windowsland 17,79
Listing 2.12: Die Ausgabedatei
2.3 Zusammenfassung Die Klassen XmlReader und XmlWriter sowie die davon abgeleiteten Klassen bieten einen ressourcenschonenden Zugriff auf XML-Dateien in Streaming-Form. Sie haben erfahren, wie sich feststellen lässt, welchen Knoten Sie gerade lesen, wie Elemente und Attribute geschrieben werden und wie mit Hilfe dieser Klassen auf einfache Art und Weise kleinere Änderungen in XML-Dateien durchgeführt werden können. Dieser rein lesend bzw. schreibende, nur vorwärtsgerichtete Zugriff reicht aber nicht aus, um alle Szenarien abzudecken. Für komplexere Änderungen oder die Navigation innerhalb der XML-Struktur reicht dieser Ansatz nicht mehr aus. Daher erfahren Sie im nächsten Kapitel, wie Sie den Zugriff über das Document Object Model realisieren. Mit dieser Schnittstelle können Sie jeden Knoten eines Dokuments einzeln nach Ihren Anforderungen anspringen und ändern.
26
.NET
Essentials
3
Serialisierung und XML
Einführung Serialisierung bedeutet, vereinfacht ausgedrückt, das »Überleben« der Objekte nach einem Programmende oder die Verpackung der Objekte für den Transport über Prozess- und Maschinengrenzen bei einem Remote-Aufruf. Für OOP-Systeme, die Mechanismen für solche Aktionen boten, wurde dies dadurch gewährleistet, dass aktuelle Daten und Variableninhalte in eine Datei geschrieben wurden. Der Code lag in der ausführbaren Datei vor und nach dem erneuten Start wurde die Datendatei geöffnet und der Entwickler hatte die Aufgabe, alle Daten wieder in die richtigen Strukturen zu laden. Innerhalb des .NET-Frameworks ist XML das Rückgrat für die Serialisierung von Klasseninstanzen. Die für die Nutzung dieser Funktionalität benötigten Namensräume des .NET-Frameworks sind System.XML und System.XML.Serialization.
Terminologie
Serialisierung mit XmlSerializer Die Klasse XmlSerializer ist für die Serialisierung einer Instanz in eine XML-Datei bzw. den umgekehrten Weg zurück zu einer Instanz zuständig. In der einfachsten Form erhält der Konstruktor einen Parameter mit dem Typ des Objekts, für das er zuständig sein soll. Hier eine sehr einfache Demoklasse, die in eine XML-Datei serialisiert werden soll: // eine einfache demo-klasse public class SimpleDemo { public string Kennzeichen; public int KmStand; public double Verbrauch; } Listing 3.1: Die erste Demoklasse
Als Ziel für die Serialisierung können Sie bei der Methode Serialize des XmlSerializer drei mögliche Ausgabetypen spezifizieren:
왘 Einen XmlWriter oder einen Nachfahren 왘 Einen Stream oder einen Nachfahren 왘 Einen TextWriter oder einen Nachfahren
27
.NET
3 Serialisierung und XML ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
Nachdem es hier nur um das Herauschreiben der Instanzdaten geht, verwenden wir im Listing einen XmlWriter, da dieser als reine vorwärtsgerichtete Nur-Schreiben-Klasse den geringsten Overhead besitzt. Hier der Code, mit dem die Instanz serialisiert wird: class TestClass { static void Main(string[] args) { XmlSerializer ser = new XmlSerializer(typeof(SimpleDemo)); XmlTextWriter tw = new XmlTextWriter("ausgabe.xml", null); SimpleDemo d = new SimpleDemo(); d.Kennzeichen = "LA-DY 2002"; d.KmStand = 45800; d.Verbrauch = 7.85; ser.Serialize(tw, d); tw.Close(); Console.WriteLine("Objekt serialisiert."); Console.Write("\nPress a key ...."); Console.ReadLine(); } } Listing 3.2: Serialisierung in eine XML-Datei
Nach dem erfolgreichen Durchlaufen des Codes liegt die Datei auf der Platte und Sie können sich deren Inhalt ansehen: Objekte als XML
LA-DY 2002 45800 7.85
Listing 3.3: Der Inhalt der XML-Ausgabedatei
Sie erkennen, dass das Wurzelelement den Namen der Klasse trägt und die einzelnen Elemente mit ihrer Bezeichnung den Namen der Felder entsprechen. Zusätzlich werden die beiden Deklarationen der Namens-
28
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
räume für XML-Schemas und XML-Schemainstanzen mit in das Wurzelelement aufgenommen (mehr zu Namensräumen erfahren Sie im Kapitel »XML-Namensräume«).
Deserialisierung mit XmlSerializer Um aus einer XML-Datei eine Instanz einer Klasse zu laden, gehen sie einfach den umgekehrten Weg. class TestClass { static void Main(string[] args) { XmlSerializer ser = new XmlSerializer(typeof(SimpleDemo)); // diesmal einen reader einsetzen XmlTextReader rdr = new XmlTextReader("ausgabe.xml"); SimpleDemo d; // klasseninstanz aus dem serializer d = ser.Deserialize(rdr) as SimpleDemo; rdr.Close(); // instanzdaten ausgeben Console.WriteLine("Objekt deserialisiert:"); Console.WriteLine(d.Kennzeichen); Console.WriteLine(d.KmStand); Console.WriteLine(d.Verbrauch); Console.Write("\nPress a key ...."); Console.ReadLine(); } } Listing 3.4: Deserialisierung aus einer XML-Datei
Abbildung 3.1: Die Ausgabe des Deserialisierungsbeispiels
29
.NET
3 Serialisierung und XML ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
Dieser Code produziert die in Abbildung 3.1 zu sehende Ausgabe und zeigt damit, dass so sehr leicht aus einer XML-Datei eine Objektinstanz erzeugt werden kann.
Ungleiche Dokumente und Klassen Falls ein Schema vorliegt, können Sie mit dem folgenden Aufruf aus dem Schema eine passende Klasse erstellen lassen, um dieses Problem zu vermeiden. Das Tool xsd.exe
xsd schema0.xsd /classes Anschliessend wird eine Datei schema0.cs erzeugt, die eine Klasse mit den aus dem Schema gewonnen Feldern enthält. //----------------------------------------------------// // This code was generated by a tool. // Runtime Version: 1.0.3705.0 // // Changes to this file may cause // incorrect behavior and will be lost if // the code is regenerated. // //----------------------------------------------------// // This source code was auto-generated by xsd, Version=1.0.3705.0. // using System.XML.Serialization;
/// [System.XML.Serialization.XmlRootAttribute(Namespace="", IsNullable=true)] public class SimpleDemo { /// public string Kennzeichen; /// public int KmStand; /// public System.Double Verbrauch; } Listing 3.5: Automatisch aus einem Schema erzeugte Klasse
30
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
Was aber passiert nun, wenn die Definition der Klasse nicht mit dem Aufbau der Datei übereinstimmt? Eine Möglichkeit wäre es, nur Dateien anzunehmen, deren Aufbau genau der Klasse entspricht, indem für die Klasse ein Schema erstellt und das Dokument damit validiert wird. Ein Beispiel dafür finden Sie im Kapitel über die Validierung von XML-Dokumenten. Für den anderen Fall wird die XML-Datei um ein zusätzliches Element ergänzt, das in der Klasse nicht vorhanden ist. Ebenso wird die Klasse um ein weiteres Feld ergänzt, das in der XML-Datei nicht vorhanden ist.
Zusätzlicher Inhalt in der XML-Datei
LA-DY 2002 45800 108 7.85
Listing 3.6: Die geänderte XML-Datei // eine einfache demo-klasse public class SimpleDemo { public string Kennzeichen; public int KmStand; public double Verbrauch; public string Hersteller; } Listing 3.7: Die geänderte Klasse mit dem neuen Feld
Bei der Ausführung des Programms erfolgt keine Fehlermeldung und die Ausgabe zeigt das Standardverhalten bei der Behandlung unbekannter Elemente in der XML-Datei:
Abbildung 3.2: Deserialisierung mit den geänderten Daten
31
.NET
3 Serialisierung und XML ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
Elemente ignorieren
Zusätzlich vorhandene Elemente werden also bei der Deserialisierung ignoriert, Felder in der Klasse ohne einen Inhalt in der XML-Datei werden von der Laufzeitumgebung bei der Erzeugung der Instanz initialisiert, bekommen dann aber keinen Wert aus der XML-Datei zugewiesen. Dieses Verhalten lässt sich, wie in den spätereren Abschnitten gezeigt, auch steuern und beinflussen.
3.1 XML-Serialisierung im Detail Steuerung der XML-Struktur Den Serialisierungsprozess, genauer die Erstellung der XML-Datei, können Sie über den Einsatz verschiedener Attribute steuern. So möchten Sie vielleicht einige Felder der Klasse nicht als eigene Unterelemente, sondern als Attribute des Klassenelementes rendern lassen oder für einige Elemente den Datentyp ändern. Serialisierungs-Attribute
Um das Wurzelelement der XML-Datei zu ändern, benutzen Sie vor der Klasse das Attribut [XmlRoot()]. Das Attribut [XmlElement] dient der Definition eines anderen Elementnamens, der vom Namen des Feldes abweicht. Mit Hilfe des Attributes [XmlAttribute] können Sie angeben, dass ein Feld nicht als eigenes Element, sondern als Attribut des umgebenden Elternelements gerendert wird. // eine einfache demo-klasse [XmlRoot(ElementName = "root")] public class SimpleDemo { public string Kennzeichen; public int KmStand; [XmlIgnore] public double Verbrauch; [XmlAttribute(AttributeName = "marke")] public string Hersteller; } Listing 3.8: Die Klasse mit XML-Steuerungsattributen
Wird die geänderte Klasse jetzt serialisiert, enthält die XML-Datei die neuen Daten, wobei das mit dem Attribut [XmlIgnore] deklarierte Feld aus der Datei ausgelassen wird.
32
XML-Serialisierung im Detail ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
LA-DY 2002 45800
Listing 3.9: Der neue Inhalt der XML-Datei
Serialisierung komplexerer Datentypen Für die Serialisierung von Datentypen wie Eigenschaften, Arrays, geschachtelten Objekten usw. gilt im Prinzip das bereits weiter oben gesagte. Für die folgenden Beispiele wird eine etwas erweiterte Klassendefinition benutzt, die über eine öffentliche Eigenschaft, ein Array und Konstruktoren verfügt. Hier der Teil der Klassendefinition mit den entsprechenden Feldern: // demo-klasse für die serialisierung public class DemoClass { // private felder der klasse private int fID; private int fIndentLevel = 0; // öffentliche felder public string StrField = "Nur ein String"; public System.UInt32[] liste = {1,3,5,7,11,13,17,19}; // konstruktoren public DemoClass() { } public DemoClass(int theID) { fID = theID; } // eine öffentliche eigenschaft public int IndentLevel { get { return fIndentLevel; } set { fIndentLevel = value; } } Listing 3.10: Der erste Teil der erweiterten Klassendefinition
Diese Klasse soll in der Lage sein, sich selbst zu serialisieren und über eine statische Methode aus einer anzugebenden XML-Datei die Deserialisierung durchzuführen. Dies wird in die beiden Methoden SaveToXml und LoadFromXml eingepackt.
33
.NET
3 Serialisierung und XML ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
// serialisieren in eine xml-datei public void SaveToXml(string filename) { XmlSerializer ser = new XmlSerializer(typeof(DemoClass)); // einen xmlwriter einsetzen XmlTextWriter xml = new XmlTextWriter(filename, null); xml.Formatting = Formatting.Indented; xml.Indentation = 4; xml.IndentChar = (char)32; // klasseninsanz serialisieren ser.Serialize(xml, this); xml.Close(); } // deserialisieren aus einer xml-datei public static object LoadFromXml(string filename) { XmlSerializer ser = new XmlSerializer(typeof(DemoClass)); XmlTextReader rdr = new XmlTextReader(filename); if(ser.CanDeserialize(rdr)) return ser.Deserialize(rdr); else return null; } } // ende der klassendefinition Listing 3.11: Die beiden Wrapper für die Serialisierung
Arrays serialisieren
Wird eine Instanz dieser Klasse über den Aufruf von SaveToXml in eine XML-Datei geschrieben, wird eine öffentliche Eigenschaft als XML-Element mit dem Namen dieser Eigenschaft geschrieben (also analog zu einem normalen öffentlichen Feld). Das Array wird als XML-Element mit dem Namen des Arrays erstellt und die einzelnen Elemente dieses Arrays werden als Unterelemente, jeweils mit dem Namen des Datentyps, in die Datei geschrieben. Hier die erstellte XML-Datei:
Hallo!!
34
XML-Serialisierung im Detail ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
1 3 5 7 11 13 17 19
4
Listing 3.12: Die serialisierte Instanz als XML-Datei
Für die Deserialisierung muss ein parameterloser Konstruktor in der Klasse vorhanden sein (Sie haben sich vielleicht schon darüber gewundert). Da der XmlSerializer aus den Daten der XML-Datei nicht erschließen kann, welcher Konstruktor mit welchen Parametern aufzurufen ist, muss ein Konstruktor ohne Parameter vorhanden sein, der dann aufgerufen wird, um die Instanz zu erstellen. Über das Einlesen der restlichen XML-Elemente werden anschließend die gleichlautenden öffentlichen Felder und Eigenschaften gesetzt.
Unbekannten XML-Inhalt behandeln Sollte in der XML-Datei zusätzlicher Inhalt vorhanden sein (in Form von Elementen, Attributen oder z.B. CDATA-Knoten), dann können Sie über Ereignisbehandlungsroutinen das Standardverhalten der Deserialisierung ändern. Sind keine solchen Eventhandler definiert, wird zusätzlicher Inhalt in der XML-Datei einfach ignoriert und für Felder der Klasse, für die kein Element in der Datei vorhanden ist, wird keine Zuweisung durchgeführt. Für die Steuerung dieses Verhaltens definiert die Klasse XmlSerializer drei Ereignisse: UnknownElement, UnknownAttribute und UnknownNode. Erstellen Sie eine Ereignisbehandlungsroutine für das gewünschte Ereignis, wird diese Methode dann aufgerufen. Für eine Demonstration erweitern wir die Datei aus Listing 3.3 um zusätzliche Elemente.
Unknown-Ereignisse
LA-DY 2002 Renault 45800 7.85
35
.NET
3 Serialisierung und XML ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
Listing 3.13: Der neue Inhalt der XML-Datei
Jetzt wird dafür eine Ereignisbehandlungsroutine definiert: public void XmlElementEventHandler(object sender, XmlElementEventArgs args) { Console.Write("Bei der Deserialisierung von {0} ", args.ObjectBeingDeserialized.ToString()); Console.Write("wurde ein Element gefunden.", args.Element.LocalName); Console.WriteLine(); } Listing 3.14: Der EventHandler für unbekannte Elemente
Diese Methode wird dem XmlSerializer nach der Instantiierung eingetragen. static void Main(string[] args) { XmlSerializer ser = new XmlSerializer(typeof(SimpleDemo)); XmlTextReader rdr = new XmlTextReader(@"c:\tmp\ser.xml"); ser.UnknownElement += new XmlElementEventHandler(DoUnknownElement); SimpleDemo sd = (SimpleDemo)ser.Deserialize(rdr); rdr.Close(); Console.WriteLine("KmStand: {0}", sd.KmStand); Console.Write("\nPress a key ...."); Console.ReadLine(); } Listing 3.15: Eintragen der Ereignisbehandlung
36
Zusammenfassung ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
Hier das Ergebnis des Programmablaufs:
Abbildung 3.3: Die unbekannten XML-Elemente werden gemeldet.
3.2 Zusammenfassung Serialisierung nach XML ist mit dem .NET-Framework auf einfache Art und Weise möglich, wobei Sie jederzeit in diesen Prozess eingreifen und die Serialisierung der Objektinstanzen genauer steuern können.
37
.NET
Essentials
4
Document Object Model
4.1 Streaming oder DOM? Während sich der Zugriff auf XML-Dateien per Streaming durch einen geringen Verbrauch an Ressourcen auszeichnet, besitzt diese Methode doch den einen oder anderen Nachteil in bestimmten Situationen. Wie Sie im Kapitel über XmlReader und XmlWriter (und den davon abgeleiteten Klassen) nachlesen können, ist nur ein vorwärtsgerichtetes Arbeiten möglich. Dies bedeutet, dass die Informationen in der XML-Datei in der Reihenfolge gelesen werden müssen, in der diese im Dokument auftauchen, während beim Schreiben bereits die endgültige Reihenfolge der Knoten feststehen muss. Besonders schwierig wird der Zugriff bei Zeigern (z.B. IDREF) innerhalb der Datei oder dem nachträglichen Einfügen von Knoten. Aus diesem Grund ist auch bei .NET die Bearbeitung von XML-Dateien über das Document Object Model möglich. Hier wird der komplette Dokumentbaum im Speicher aufgebaut, wobei jeder Knoten einer Instanz eines Nachfahren von XmlNode entspricht.
IDREF
Dieses Kapitel befasst sich mit den grundlegenden Möglichkeiten des .NET-Frameworks zur Bearbeitung von DOM-Strukturen. Die Methoden zur Selektion von Untermengen des Dokumentenbaumes mit Hilfe von XPath-Abfragen werden dann im nächsten Kapitel nach einer kurzen Einführung in das Thema XPath behandelt.
4.2 XML-Dokumente laden und lesen Laden bestehender Dokumente Das Laden bestehender Dokumente geschieht über eine Instanz der Klasse XmlDocument und deren Methode Load. Nach der Erzeugung der Instanz wird also nur die Methode Load benutzt, um dem Dokument gleich einen Inhalt zuzuweisen.
XmlDocument
// erzeugen eines neuen XML-Dokumentes XmlDocument thedoc = new XmlDocument(); // laden einer bestehenden datei thedoc.Load(@"h:\wetter.xml"); // hier erfolgt die bearbeitung der Datei Listing 4.1: Das Laden von bestehenden XML-Dokumenten
39
.NET
4 Document Object Model ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
Diese Methode ist mehrfach überladen. So können Sie anstelle des Dateinamens auch bei Bedarf einen URL oder eine Instanz eines XmlReaders angeben. LoadXml
Falls die XML-Daten bereits in Form eines Strings vorliegen, kann dieser benutzt werden, um den Inhalt des Dokumentes festzulegen. Dafür existiert die Methode LoadXml, die als Parameter einen String erwartet: // erzeugen eines neuen XML-Dokumentes XmlDocument thedoc = new XmlDocument(); // stringinhalt für XML, hier direkte zuweisung string s = "Bob"; // laden einer bestehenden Datei thedoc.LoadXml(s); // hier erfolgt die bearbeitung der datei Listing 4.2: Das Laden von bestehenden XML-Dokumenten
Somit sind wir in der Lage, Daten in XML in einem DOM-Objekt zu laden. In den nächsten Abschnitten dieses Kapitels wird es darum gehen, den Inhalt des Dokumentenbaums zu bearbeiten.
Knoten und Elemente XmlNode
Die einzelnen Elemente des Dokumentenbaums bestehen aus Knoten, die hier den einzelnen XML-Elementen entsprechen. Über die Eigenschaft NodeType eines XmlNode lässt sich feststellen, um welchen Knoten es sich handelt (Element, Kommentar, Verarbeitungsanweisung, etc.) und entsprechend kann Ihr Programmcode dann darauf reagieren. Zur Verdeutlichung des nächsten Listings hier nochmals ein Ausschnitt aus der Beispieldatei mit den Wetterdaten:
Algeirs overcast 11 93% E 5 1028 rising
Listing 4.3: Ausschnitt aus der Beispieldatei
40
XML-Dokumente laden und lesen ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
Um das -Element aus der ersten Stationsmeldung zu lesen, muss der zweite Kindknoten des Dokumentenknotens gelesen werden (der erste wäre die Verarbeitungsanweisung ), davon den ersten Kindknoten (das Element ) und davon wiederum den dritten Knoten. Hier der entsprechende Codeausschnitt dazu: static void SampleChildNodes() { XmlNode node; // erzeugen eines neuen XML-Dokumentes XmlDocument thedoc = new XmlDocument(); // laden einer bestehenden Datei thedoc.Load(@"h:\wetter.xml"); // das element holen node = thedoc.ChildNodes[1]; // das element holen node = node.ChildNodes[0].ChildNodes[2]; // inhalt ausgeben Console.WriteLine("Inhalt: {0}", node.OuterXml); } Listing 4.4: Zugriff auf einen bestimmten Elementknoten per DOM
Die Ausgabe erfolgt dann über die Eigenschaft OuterXml, so dass das Tag und die Attribute mit ausgegeben werden. Sind Sie nur am reinen Inhalt interessiert, können Sie statt dessen die Eigenschaft InnerText verwenden.
OuterXml
Abbildung 4.1: Die Ausgabe des Beispielprogramms
Sollte Ihnen der Programmablauf mehr wie ein Hüpfen von Knoten zu Knoten vorkommen, bei dem sich die Reihenfolge der Elemente auf keinen Fall ändern darf, liegen Sie richtig. Im nächsten Kapitel werden Sie mit XPath-Abfragen eine wesentlich bessere Möglichkeit kennenlernen, ein Set aus einzelnen Knoten aus einem Dokumentenbaum zu extrahieren.
41
.NET
4 Document Object Model ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
Auswählen einzelner Elemente XmlNodelist
Die Auswahl einzelner Knoten oder einer Liste von Knoten geschieht über verschiedene Methoden, die eine Instanz von XmlNode (bzw. einer Ableitung davon) oder eine Auflistung in einer XmlNodeList liefern. Damit lässt sich die Baumstruktur bequem durchsuchen und filtern. Das folgende Beispiel liefert alle location-Elemente der Datei und gibt diese Auflistung in einer Schleife aus, um aufzulisten, für welche Stationen Wetterdaten vorliegen. using System; using System.XML; namespace DOMSamples { class ReadXML { static void Main(string[] args) { // erzeugen eines neuen XML-Dokumentes XmlDocument thedoc = new XmlDocument(); // laden einer bestehenden Datei thedoc.Load(@"h:\wetter.xml"); // auslesen aller location-tags XmlNodeList nl = thedoc.GetElementsByTagName("location"); // auflistung in schleife ausgeben foreach(XmlNode node in nl) { Console.WriteLine("Station location: {0}", node.InnerText); } Console.WriteLine("Total # of stations: {0}", nl.Count); } } } Listing 4.5: Auslesen aller benannten Tags eines XML-Dokumentes
GetElementsByID
42
Zusätzlich existiert noch eine Methode GetElementsByID, mit der alle Elemente mit einem bestimmten ID-Attribut zurückgeliefert werden. Dafür muss allerdings ein Schema oder eine DTD zur Validierung vorhanden sein, da in .NET im Document Object Model keine ID-Attribute vorgesehen sind, wenn diese nicht explizit über eine DTD oder ein
XML-Dokumente laden und lesen ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
Schema definiert werden. Mehr dazu im Kapitel über die Validierung von XML-Dokumenten.
Zugriff auf Attribute Für den Zugriff auf eventuell vorhandene Attribute eines Elementes benutzen Sie zuerst die Eigenschaft Count der Auflistung Attributes des Knotens, um zu testen, ob diese einen Wert größer als 0 enthält.
Attributes und XmlAttribute Collection
Ist dies der Fall, liefert die Auflistung Attributes eine XmlAttribute Collection, deren Elemente aus XmlNodes bestehen. Damit lassen sich die einzelnen Unterelemente eines Knotens bequem nach vorhandenen Attributen durchsuchen, wie das nächste Beispiel zeigt. Hier wird für das erste station-Element in der Beispieldatei jeweils ausgegeben, ob Attribute vorhanden sind, und deren Namen und Wert. Auch hier wird aus Platzgründen wieder nur der Code für die Klasse aufgelistet, der Aufruf in einer Testklasse sollte aber kein Problem darstellen. // ausgabe von attributen aus xml-elementen static void SampleAttributes() { XmlNode node; // erzeugen eines neuen XML-dokumentes XmlDocument thedoc = new XmlDocument(); // laden einer bestehenden datei thedoc.Load(@"h:\wetter.xml"); // das erste element holen node = thedoc.ChildNodes[1].ChildNodes[0]; // durch alle kindknoten des elementes laufen foreach(XmlNode n in node.ChildNodes) { // haben wir attribute? if(n.Attributes.Count > 0) { // dann durch alle attribute laufen foreach(XmlNode a in n.Attributes) { Console.WriteLine("Element besitzt" + " Attribute:", n.Name); Console.WriteLine(" {0} = {1}", a.Name, a.Value); } } else // keine attribute, elementname ausgeben Console.WriteLine("Element hat keine" +
43
.NET
4 Document Object Model ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
" Attribute.", n.Name); } } Listing 4.6: Ausgabe der Attribute und ihrer Werte
4.3 Ändern von XML-Dokumenten per DOM Hinzufügen von neuen Elementen Das programmgesteuerte Hinzufügen neuer Elemente (Knoten) in die DOM-Struktur erlaubt den freien Auf- und Umbau eines XML-Dokumentes. Für jeden möglichen Elementtyp existiert in der XmlDocumentKlasse eine eigene Methode, da die Konstruktoren nicht direkt instantiierbar sind, da Elemente immer zu einem bestimmten Dokument gehören. Das Beispiel unten erzeugt zuerst ein neues XML-Dokument und füllt dies dann durch die entsprechenden Methoden-Aufrufe mit Inhalten. Zuletzt wird durch das Speichern nach Console.Out der Inhalt im Konsolenfenster ausgegeben. static void SampleNewContent() { XmlNode node1; XmlNode node2; // erzeugen eines neuen XML-Dokumentes XmlDocument thedoc = new XmlDocument(); // ein kommentarelement erzeugen thedoc.AppendChild(thedoc.CreateComment( "demo fuer .net-essentials")); // ein -element erzeugen XmlElement elem = thedoc.CreateElement("weatherdata"); thedoc.AppendChild(elem); // ein station-element mit attribut erzeugen node1 = elem.AppendChild(thedoc.CreateElement("station")); (node1 as XmlElement).SetAttribute("id", "Irgendwo"); // einfuegen von text funktioniert // ueber die Methode "CreateTextNode" node2 = node1.AppendChild(thedoc.CreateElement("weather")); node2.AppendChild(thedoc.CreateTextNode("sunny"));
44
Ändern von XML-Dokumenten per DOM ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
thedoc.Save(Console.Out); } Listing 4.7: Erzeugen von neuen Elementen per DOM
Hier die Ausgabe der Methode:
Abbildung 4.2: Die Ausgabe der neu erzeugten XML-Struktur
Löschen von Elementen Um ein Element aus der Struktur zu entfernen, existiert die Methode RemoveChild, die als Parameter den zu entfernenden Unterknoten erwartet. Als Erweiterung zum DOM durch Microsoft existiert noch eine Methode RemoveAll, mit der sich in einem Aufruf alle Unterelemente und Attribute eines Knotens entfernen lassen.
RemoveChild, RemoveAll
Der Aufruf der Methode SampleDeleteElements aus dem folgenden Beispiel entfernt aus allen -Elementen den Unterknoten . static void ProcessElementDelete(XmlNode theNode) { foreach(XmlNode n in theNode.ChildNodes) { if(n.HasChildNodes) ProcessElementDelete(n); } if(theNode.LocalName == "airtrend") { Console.WriteLine("Deleting a node ...."); theNode.ParentNode.RemoveChild(theNode); } } static void SampleDeleteElements() { // erzeugen eines neuen XML-Dokumentes
45
.NET
4 Document Object Model ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
XmlDocument thedoc = new XmlDocument(); // laden einer bestehenden Datei thedoc.Load(@"h:\miniwetter.xml"); // hier erfolgt die bearbeitung ProcessElementDelete(thedoc); // geänderte datei rausschreiben thedoc.Save("neuwetter.xml"); } Listing 4.8: Entfernen von Elementen aus der DOM-Struktur
Bearbeiten von Attributen CreateAttribute
Die Behandlung von Attributen erfolgt auf eine etwas abweichende Art und Weise, da die Methode CreateAttribute ein Mitglied der XmlDocument-Klasse ist. Um also ein neues Attribut zu erzeugen, während Sie nur auf den aktuellen Knoten Zugriff haben, gehen Sie folgendermaßen vor:
왘 Feststellen des Dokumentes, das diesen Knoten besitzt. Dazu hat jeder Knoten eine Eigenschaft OwnerDocument. 왘 Aufrufen der Methode CreateAttribute der Dokumentklasse und Übergeben von Name und eventuell Namensraumpräfixen. 왘 Setzen des Wertes für das Attribut. 왘 Hinzufügen des neuen Attributes an die Attribute-Auflistung des aktuellen Knoten. Die beiden nachfolgenden Methoden zeigen einen rekursiven Durchlauf durch ein XML-Dokument, bei dem für jeden Stationsknoten der Name der Station aus dem Unterknoten gelesen und bei als ID-Attribut eingetragen wird. Danach wird die neue Struktur unter einem anderen Namen gespeichert. Damit die Ausgabe leichter lesbar ist, wird hier eine Datei verwendet, die nur zwei Einträge für Wetterstationen besitzt und die den Namen miniwetter.xml trägt. // diese methode wird für jeden knoten aufgerufen static void ProcessElementChildren(XmlNode theNode) { Console.WriteLine("Processing ....", theNode.Name); // id bei station eintragen
46
Ändern von XML-Dokumenten per DOM ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
if(theNode.LocalName == "location") { // neues attribut ueber dokument erzeugen XmlAttribute attr = theNode.OwnerDocument.CreateAttribute("id"); // attributwert setzen auf den inhalt // des aktuellen knotens (location) attr.Value = theNode.InnerText; // an die auflistung der attribute anhängen theNode.ParentNode.Attributes.Append(attr); } // alle eventuellen unterknoten abarbeiten foreach(XmlNode n in theNode.ChildNodes) { if(n.HasChildNodes) ProcessElementChildren(n); } }
// hauptmethode static void SampleRecurseElements() { // erzeugen eines neuen XML-Dokumentes XmlDocument thedoc = new XmlDocument(); // laden einer bestehenden Datei thedoc.Load(@"h:\miniwetter.xml"); // hier erfolgt die bearbeitung ProcessElementChildren(thedoc); // geänderte datei rausschreiben thedoc.Save("neuwetter.xml"); } Listing 4.9: Ändern einer DOM-Struktur
Soll ein Attribut eines Elementes gelöscht werden, geschieht dies auf sehr einfache Weise durch den Aufruf der Methode RemoveAttribute der Klasse XmlElement, mit der ein benanntes Attribut aus einem XMLElement gelöscht werden kann. Da dieser Vorgang nicht sonderlich geheimnisvoll ist, sei an dieser Stelle auf ein weiteres Listing verzichtet.
RemoveAttribute
Eine andere Alternative ist die Nutzung der hilfreichen Methode SetAttribute der Klasse XmlElement. Bitte beachten Sie, dass aus Gründen
47
.NET
4 Document Object Model ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
des Zeilenumbruchs die beiden Zeilen 05 und 06 getrennt wurden, im Code ist dies eine einzige Zeile. 01 if(theNode.LocalName == "station") 02 { 03 XmlElement elem = ((XmlElement)theNode); 04 elem.SetAttribute("id", 05 elem.GetElementsByTagName("location") 06 .Item(0).InnerText); 07 } Listing 4.10: Alternative Erzeugung von Attributen
Mit dieser Methode lässt sich auch der Inhalt eines bereits bestehenden Attributes wieder ändern.
4.4 Ereignisbehandlung Die Klasse XmlDocument beinhaltet auch eine Reihe von Ereignissen, in die sich Ihr Code mit einer eigenen Ereignisbehandlungsroutine einhängen kann. Auf diese Weise kann nicht nur mitverfolgt werden, ob ein Knoten geändert, eingefügt oder gelöscht wurde. Über einen zweiten Satz von Ereignissen, der vor der eigentlichen Aktion ausgelöst wird, lässt sich die Durchführung der Änderung sogar bei Bedarf abbrechen.
t
Ereignis
Beschreibung
NodeChanged
Tritt auf, wenn ein Knoten in der DOM-Struktur geändert wurde
NodeChanging
Tritt auf, bevor ein Knoten in der DOM-Struktur geändert wird (Auslösen einer Exception bricht die Aktion ab)
NodeInserted
Tritt auf, wenn ein Knoten in der DOM-Struktur eingefügt wurde
NodeInserting
Tritt auf, bevor ein Knoten in der DOM-Struktur eingefügt wird (Auslösen einer Exception bricht die Aktion ab)
NodeRemoved
Tritt auf, wenn ein Knoten in der DOM-Struktur gelöscht wurde
NodeRemoving
Tritt auf, bevor ein Knoten in der DOM-Struktur gelöscht wird (Auslösen einer Exception bricht die Aktion ab)
Tabelle 4.1: Mögliche Ereignisse der XmlDocument-Klasse
Um auf eines dieser Ereignisse zu reagieren, muss in der Klasse ein EventHandler programmiert und dem Delegate für das Ereignis mitgeteilt werden. Das folgende Listing zeigt eine Klasse, die beim Löschen eines Knotens eine entsprechende Meldung ausgibt. // klasse zur ereignisbehandlung class EventDemo
48
Ereignisbehandlung ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
{ public void DemoActions() { XmlDocument thedoc = new XmlDocument(); thedoc.NodeRemoved += new XmlNodeChangedEventHandler(this.OnDelete); thedoc.Load(@"h:\miniwetter.xml"); try { thedoc.ChildNodes[1].ChildNodes[0].RemoveAll(); } catch (Exception e) { Console.WriteLine(e.Message); } thedoc.Save(Console.Out); Console.Write("Press a key ...."); Console.ReadLine(); } // knoten wurde gelöscht public void OnDelete(Object src, XmlNodeChangedEventArgs args) { Console.Write("Löschereginis: {1}", args.Node.Name, args.Action.ToString()); if (args.Node.Value != null) Console.WriteLine(" Wert = {0}", args.Node.Value); else Console.WriteLine(""); } } Listing 4.11: Abfangen von Löschereignissen im Dokument
Analog dazu funktioniert die Behandlung der NodeChanged- und Node Inserted-Ereignisse. Interessant ist aber die Möglichkeit, in diesen Prozess einzugreifen und bei Bedarf durch das Auslösen einer Exception die Aktion abzubrechen. Dazu dienen die Ereignisse, deren Namen jeweils auf »...ing« enden und die vielleicht besser als Anforderung bezeichnet werden.
NodeChanged, NodeInserted
49
.NET
4 Document Object Model ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
Im folgenden Beispiel soll verhindert werden, dass für eine Station der -Knoten gelöscht wird, alle anderen Unterknoten von sollen gelöscht werden können. Denken Sie daran, beim Löschen von Unterelementen in einer Schleife nicht mit foreach zu arbeiten, da hier das Set der zu durchlaufenden Elemente nur einmal berechnet wird und dann nicht mehr geändert werden darf. Zusätzlich muss die Schleife von »hinten nach vorne« durchlaufen werden, damit nach dem Löschen wegfallende Elemente die Reihenfolge nicht ducheinander bringen. Deshalb die auf den ersten Blick etwas merkwürdig aussehende Schleifenkonstruktion. Wollen Sie in einem Rutsch wirklich alle Unterknoten eines Knotens entfernen, benutzen Sie dazu am besten die Methode RemoveAll der Klasse XmlNode. Diese stellt eine nützliche Erweiterung des DOM-Standards durch Microsoft dar. // beispiel zum abfangen einer löschaktion im DOM class EventDemo { public void DemoActions() { XmlNode node; XmlDocument thedoc = new XmlDocument(); // eintragen der eventhandler thedoc.NodeRemoved += new XmlNodeChangedEventHandler(this.OnDelete); thedoc.NodeRemoving += new XmlNodeChangedEventHandler(this.OnDeleteReq); thedoc.Load(@"h:\miniwetter.xml"); // die erste station in der wetterdatei node = thedoc.ChildNodes[1].ChildNodes[0]; // alle unterelemente löschen for(int i = node.ChildNodes.Count-1; i >= 0; i--) { try { node.RemoveChild(node.ChildNodes[i]); } catch (Exception e) { Console.WriteLine(e.Message); } }
50
Ereignisbehandlung ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
// den inhalt der XML-Strukur zur // kontrolle ausgeben thedoc.Save(Console.Out); Console.Write("Press a key ...."); Console.ReadLine(); } // ab hier die ereignisbehandlungsroutinen // knoten wurde gelöscht public void OnDelete(Object src, XmlNodeChangedEventArgs args) { Console.Write("Löschereignis: {1}", args.Node.Name, args.Action.ToString()); if (args.Node.Value != null) Console.WriteLine(" Wert = {0}", args.Node.Value); else Console.WriteLine(""); } // knoten soll gelöscht werden, extracheck public void OnDeleteReq(Object src, XmlNodeChangedEventArgs args) { Console.Write("Löschanfrage: {1}", args.Node.Name, args.Action.ToString()); if (args.Node.Value != null) Console.WriteLine(" Wert = {0}", args.Node.Value); else Console.WriteLine(""); if(args.Node.LocalName == "location") { throw new XmlException("Location darf nicht " + "gelöscht werden!", null); } } } Listing 4.12: Abbrechen von Änderungen im DOM
Nach der Exception befindet sich die XML-Struktur in der XmlDocumentInstanz in einem stabilen Zustand wie vor der Aktion. Wäre im Code statt der Schleife durch die Unterelemente die Methode RemoveAll aufgerufen worden, hätte die Exception beim Element dafür gesorgt, dass gar keines der Unterelemente gelöscht worden wäre, da
51
.NET
4 Document Object Model ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
dann der komplette Aufruf von RemoveAll zurückgerollt worden wäre. Dies lässt sich bei Bedarf gezielt dafür einsetzen, komplette Knoten zu schützen, aber nur ein Unterelement zu prüfen.
4.5 Zusammenfassung In diesem Kapitel haben Sie eine Übersicht über die verschiedenen Klassen und Methoden des DOM im .NET-Framework erhalten. Vergleicht man die Anforderungen und Möglichkeiten mit denen im Kapitel über die Streaming-Klassen XmlReader und XmlWriter, dann lässt sich auch einordnen, wann am besten welche Methode zum Einsatz kommt. Sollten Sie eine große Anzahl von Dokumenten in einer Art Stapelbetrieb verarbeiten müssen oder benötigen Sie aus einem umfangreichen XML-Dokument nur eine kleine Anzahl von Elementen (z.B. um aus einer Liste von Teilen die Gewichte für den Versand zu berechnen), dann ist der Streaming-Ansatz wesentlich ressourcenschonender. Sollten Sie aber die volle Kontrolle über die XML-Struktur benötigen oder beim Aufbau im Speicher noch nicht die endgültige Reihenfolge der Elemente wissen (weil diese z.B. erst während der Laufzeit Ihres Programms erzeugt werden), dann ist es besser, mit dem DOM zu arbeiten, auch wenn dadurch der komplette DOM-Baum im Speicher aufgebaut wird.
52
.NET
Essentials
5
XPath-Abfragen
5.1 Warum XPath? Wie Sie in den vorherigen Kapiteln erfahren haben, bietet das .NETFramework eine ganze Reihe von Möglichkeiten, um XML-Strukturen zu lesen und zu schreiben. Allerdings ist der Weg zu einem bestimmten Knoten bzw. Element mehr oder weniger steinig. Das reine Abzählen von Unterknoten funktioniert beispielsweise nur dann, wenn Sie sich sicher sind, dass die Reihenfolge der Knoten nicht verändert wurde. Wie Sie dies sicherstellen und auch bestimmte Elemente zwingend erforderlich machen, erfahren Sie im Kapitel über die Validierung von XML-Dateien. Hier soll es aber zuerst darum gehen, eine sehr mächtige und flexible Möglichkeit zur Suche nach einem oder mehreren Knoten in einer DOMStruktur vorzustellen, nämlich den XPath-Standard. XPath ist eine Entwicklung des World Wide Web Consortiums (W3C) zur Lokalisierung von Knoten in XML-Strukturen, die normalerweise nicht allein genutzt wird, sondern dazu dient, zusammen mit anderen Standards benutzt zu werden. So bestehen zum Beispiel die Suchausdrücke der Schablonen für die Transformation von XML aus XPath-Ausdrücken. XPath operiert auf der abstrakten, logischen Struktur eines XML-Dokuments, nicht auf seiner äußerlichen Syntax. Seinen Namen erhält XPath durch die Verwendung einer auch in URLs genutzten Pfad-Notation (path), mit der sich durch die hierarchische Struktur eines XML-Dokuments navigieren lässt. Für eine erfolgreiche Arbeit mit XML ist daher eine eingehende Beschäftigung mit XPath unabdingbar. Dieses Kapitel kann aus Platzgründen nur eine kurze Einführung bieten, für die detailliertere Beschäftigung sei auf die Homepage für XPath unter http://www.w3.org/TR/ xpath.html verwiesen.
Das Prinzip von XPath
5.2 Grundlagen XPath-Ausdrücke In der einfachsten Form beschreibt eine XPath-Abfrage den durch Schrägstriche getrennten Weg in der hierarchischen XML-Struktur bis zum Zielknoten. So liefert der XPath-Ausdruck weatherdata/station/temp alle Knoten unterhalb von , wobei dieses Element ein Unterknoten von sein muss.
53
.NET
5 XPath-Abfragen ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
Die folgende Abbildung zeigt die Ausgabe, wenn für jeden Knoten die Eigenschaft OuterXml ausgegeben wird.
Abbildung 5.1: Ausgabe aller -Knoten im XML-Baum
Der |-Operator
XPath-Ausdrücke lassen sich mit Hilfe des Operators "|" (in der Bedeutung von »oder«) verbinden. So liefert der Ausdruck /weatherdata/station/location | /weatherdata/station/temp beispielsweise einen Match für entweder das - oder das -Element. Da der XPath-Parser zuerst die Geschwisterknoten der gleichen Ebene durchsucht, erhalten Sie so auf einfachste Art eine Auflistung der Standorte mit der jeweiligen Temperatur. Hier das Listing und die Ausgabe des Programms dazu: // kombinieren von XPath-Abfragen static void SampleSelectOr() { // erzeugen eines neuen XML-Dokumentes XmlDocument thedoc = new XmlDocument(); // laden der xml-datei thedoc.Load(@"h:\miniwetter.xml"); // abfragen per XPath XmlNodeList nl = thedoc.SelectNodes( "/weatherdata/station/location | " + /weatherdata/station/temp"); foreach(XmlNode n in nl) { Console.WriteLine(n.OuterXml); } } Listing 5.1: Kombination zweiter XPath-Ausdrücke
54
Grundlagen ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
Abbildung 5.2: Ausgabe der kombinierten XPath-Abfrage
Der Kontextknoten Wird eine XPath-Abfrage beispielsweise nach dem Knoten durchgeführt (name), hängt es davon ab, von wo die Suche gestartet wird. Ein Beginn an der Wurzel des Dokumentes liefert unter Umständen ein komplett anderes Ergebnis als der Beginn an einem Unterknoten innerhalb der XML-Struktur. Daher verwendet XPath das Konzept eines sogenannten »Kontextknotens«. Dieser Kontextknoten ist der aktuelle Knoten, an dem die Suche gestartet wird (bei der Verwendung der Methode SelectNodes beispielsweise der XmlNode, für den die Methode aufgerufen wird).
Die Abkürzung für den Kontextknoten ist der . (punkt)
Achsen Für die Durchführung einer XPath-Abfrage kann eine bestimmte Richtung definiert werden, die sog. Achse. Auch in einem hierarchischen Dateisystem beispielsweise kann über die Notation .. eine Ebene höher gewechselt werden, um »nach oben« zu suchen. Bei der normalen Angabe wie /weatherdata/station/location ist die Achse immer child, also jeweils ein Unterknoten. Dies ist die Default-Achse, muss daher auch nicht angegeben werden. Wird hingegen eine Achse angegen, trennt man diese vom Rest der Abfrage durch einen doppelten Doppelpunkt (::). Einige Achsen werden (wie child) sehr häufig benötigt und besitzen eine eigene Abkürzung. Die wichtigsten Achsen zeigt die nachfolgende Tabelle. Startpunkt ist immer der Kontextknoten. Achse
Bedeutung
child
Default, ein Nachkomme des Kontextknotens
self
Der Kontextknoten (Abkürzung .)
descendant
Irgendein Nachkomme des Kontextknotens, also z.B. auch ein Nachkomme über zwei oder drei Ebenen
descedant-or-self
Wie oben, allerdings unter Einbeziehung des Kontextknotens (Abkürzung //)
parent
Elternknoten des Kontextknotens (Abkürzung ..)
t
Tabelle 5.1: Die wichtigsten Achsen für XPath-Ausdrücke
55
.NET
5 XPath-Abfragen ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
Achse
Bedeutung
following
Alle nach dem Kontextknoten folgenden Knoten
preceding
Alle vor dem Kontextknoten vorhandenen Knoten
attribute
Attribut des angegebenen Knotens (Abkürzung @)
Tabelle 5.1: Die wichtigsten Achsen für XPath-Ausdrücke (Fortsetzung)
Eine vollständige Liste der möglichen Achsen finden Sie auf der XPathHomepage im Web unter dem URL www.w3.org/TR/xpath.html . Verweis auf Elternknoten mit ..
Um beispielsweise alle -Elemente zu selektieren, egal zu welchem Elternknoten diese gehören, kann der Ausdruck //temp benutzt werden. Der Ausdruck //location/.. liefert alle Elternknoten (in diesem Falle ), die ein -Element als Kindknoten besitzen. Somit sind sehr leicht Abfragen in verschiedenen »Richtungen« möglich, die durch die Auswahl des Kontextknotens noch zusätzlich beeinflusst werden können. Für eine noch leichtere Abfrage fehlen jetzt nur noch Möglichkeiten, einen Vergleich durchzuführen, um nur ein bestimmtes Subset von passenden Knoten auszuwählen. Dies geschieht bei XPath mit Hilfe von so genannten Prädikaten.
Prädikate Prädikate entsprechen in etwa der WHERE-Klausel bei einer SQL-Abfrage, da sie das zurückgelieferte Set von Knoten durch Bedingungen noch weiter filtern können. Diese Prädikate werden an der gewünschten Stelle hinter das jeweilige Hierarchieelement in eckigen Klammern angefügt. Klingt kompliziert, ist es aber nicht. Hier ein Beispiel: //station[location = "Athens"] liefert das -Element, welches ein Unterlement besitzt, das den Inhalt Athens besitzt. Prüfung auf vorhandene Attribute
Der Prädikatwert muss nicht unbedingt einen Vergleich enthalten. Wird nur ein Unterelement oder ein Attribut angegeben, dann wird auf das Vorhandensein dieses Elementes geprüft. Der nachfolgende Ausdruck liefert in der XmlNodeList alle -Knoten, die als Elternknoten ein -Element besitzen, das wiederum ein Attribut "status" enthält. /weatherdata/station[@status]/location Wird mehr als ein Prädikat angegeben, was durchaus zulässig ist, besitzt die Reihenfolge dieser Prädikate eine entscheidende Bedeutung. Betrachten wir dazu die beiden folgenden XPath-Ausdrücke: 1. /weatherdata/station[2][@status] 2. /weatherdata/station[@status][2]
56
Grundlagen ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
Für den Ausdruck Nr. 1 ist das Ergebnis der zweite -Knoten unterhalb von , falls dieser über ein Attribut (wie Sie an der Abkürzung @ für die Attribut-Achse sehen) mit dem Namen status verfügt. Die Knotenliste wäre hier leer, da der zweite Knoten in unserer Beispieldatei wetter.xml (für die Station in Amsterdam) nicht über das entsprechende Attribut verfügt. Der Ausdruck Nr. 2 liefert dagegen zuerst die Liste der Stationen mit einem Attribut status und wählt anschließend davon den zweiten Knoten aus. Im Falle der Beispieldatei enthält die Ergebnismenge eine Ausgabe:
Bukarest light rain 10 49% W 3 1013 raising
Listing 5.2: Die Ergebnismenge der zweiten Abfrage
Zur Kombination von Ausdrücken innerhalb der Prädikate können Sie mit den drei booleschen Operatoren and, or und not arbeiten (case-sensitive) bzw. Teilausdrücke in Klammern gruppieren. Denken Sie bei den relationalen Operatoren daran, das Zeichen "" entsprechend durch ein ">" zu quoten, schließlich befinden wir uns innerhalb der XML-Grenzen. Mehr dazu und zu den weiteren Möglichkeiten der Prädikate finden Sie in der Online-Dokumentation von .NET oder auf den Webseiten des W3C.
Prädikat-Operatoren
Als letzten Bestandteil der Einführung in die XPath-Sprache fehlen nun nur noch die Funktionen, mit deren Hilfe noch flexiblere Abfragen möglich sind.
Funktionen Funktionen innerhalb von XPath-Ausdrücken können einen der vier folgenden Datentypen zurückliefern: Boolsche Werte (true oder false), Zahlenwerte, Zeichenketten oder eine Knotenmenge. Wenn eine Funktion einen anderen Datentyp als eine Knotenmenge liefert, können natürlich die nachfolgend besprochenen Methoden SelectNodes und SelectSingleNode nicht benutzt werden. Verwenden Sie dafür die Methoden des XPathNavigators, der weiter unten vorgestellt wird.
Funktionstypen
57
.NET
5 XPath-Abfragen ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
Mit Hilfe dieser Funktionen ist es sehr einfach möglich, Code zu vermeiden und die Arbeit einfach in einen XPath-Ausdruck auszulagern. Damit Sie die folgenden Beispiele nachvollziehen können, sollten Sie zuerst die Abschnitte über SelectNodes und den XPathNavigator lesen und dann hierher zurückblättern. Aus der Sicht des Autors erschien es an dieser Stelle sinnvoller, die XPath-Einführung nicht zu zerreißen, sondern linear aufzubauen. Sobald eine Funktion kein Set von Knoten mehr liefert, sind die üblichen Selektierungsmethoden (wie SelectNodes oder Select) nicht mehr anwendbar. Benutzen Sie statt dessen die Methode Evaluate der Klasse XPathNavigator, welche als Rückgabetyp object besitzt und dementsprechend per Unboxing oder Konvertierung gewandelt werden kann. Evaluate-Funktion
Eine der einfachsten Funktionen ist count(), die einfach die Anzahl der auf den Ausdruck passenden Knoten liefert. Damit lässt sich die Anzahl der in der Beispieldatei enthaltenenen Stationsmeldungen mit einer einzigen Zeile Code bestimmen. int nCount = (int)xnav.Evaluate("count(//station)"); Listing 5.3: Zählen von Knoten mit XPath-Funktionen
Es existiert eine recht beachtliche Liste von nützlichen Funktionen, wobei sich Microsoft hier an den XPath-Standard hält. Anstatt diese Liste einfach zu wiederholen, sei auf die Online-Hilfe von Visual Studio.NET verwiesen, in der Sie unter dem Indexbegriff XPath, Funktionen die entsprechende Liste finden. Nachfolgend noch einige Beispiele, die ein paar der unterstützten Funktionen demonstrieren:
t
Ausgabe aller Temperaturknoten, aber nur der Textinhalt des Elements: /weatherdata/station/temp/text() Ausgabe aller Stationen mit "P": /weatherdata/station[ starts-with(location,"P")]/location Ausgabe der Luftfeuchtigkeit für alle Stationen, die eine Temperatur von mehr als 10° melden: /weatherdata/station/temp[text() > 10]/../humidity Alle Stationsdaten mit steigendem Luftdruck und mehr als 10°: /weatherdata/station[(temp > 10) and (airtrend = "rising")]
58
SelectNodes und SelectSingleNode ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
5.3 SelectNodes und SelectSingleNode Mit der Methode SelectNodes lässt sich eine geladene DOM-Struktur mit Hilfe einer XPath-Abfrage durchsuchen und als XmlNodelist-Auflistung zurückgeben.
SelectNodes
Das folgende Beispiel liefert alle -Elemente unterhalb von /weatherdata/station und berechnet anschließend die Durchschnittstemperatur der Wettermeldungen. static void SampleSelectNodes() { double sum = 0.0; int count = 0; // erzeugen eines neuen XML-Dokumentes XmlDocument thedoc = new XmlDocument(); // laden der xml-datei thedoc.Load(@"h:\wetter.xml"); // abfragen per XPath XmlNodeList nl = thedoc.SelectNodes("/weatherdata/station/temp"); // durchlaufen der nodelist foreach(XmlNode n in nl) { sum += double.Parse(n.InnerText); count++; } Console.WriteLine("{0} Werte. Schnitt = {1:F2} Grad", count, sum / count); } Listing 5.4: Auslesen von Knoten mit XPath-Abfragen
Damit ist auf einfache Art und Weise nahezu ohne Programmieraufwand das Durchsuchen von XML-Strukturen nach bestimmten Kritierien möglich. Soll ein ganz bestimmter Knoten (z.B. anhand einer ID) gefunden werden, kann dazu die Methode SelectSingleNode benutzt werden. So lassen sich mit der folgende Klasse die kompletten XML-Elemente für einen Standort (also das -Element) abrufen, in dem der Name des Standortes eingegeben wird. Wird kein passender Knoten gefunden, liefert die Methode eine Null-Referenz, auf die gestestet werden kann.
SelectSingleNode
59
.NET
5 XPath-Abfragen ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
Im Beispiel wird über ein XPath-Prädikat (s.o. bei den Grundlagen) der Name des Standortes gesucht, aber das -Element zurückgeliefert. // suchen eines einzelnen xml-nodes // per SelectSingleNode static void SampleSelectSingleNode() { string location; // der gesuchte name string xpath; // der XPath-Ausdruck // erzeugen eines neuen XML-Dokumentes XmlDocument thedoc = new XmlDocument(); // laden der xml-datei thedoc.Load(@"h:\wetter.xml"); // abfrageschleife do { Console.Write("Standort eingeben : "); location = Console.ReadLine(); // bei leerer eingabe schleife verlassen if(location.Length == 0) break; // abfrage durchführen xpath = "/weatherdata/station[location = \"" + location + "\"]"; XmlNode node = thedoc.SelectSingleNode(xpath); // wenn objektreferenz, dann ergebnis ausgeben if( node != null) Console.WriteLine(node.OuterXml); else Console.WriteLine( "Ort nicht in Daten enthalten!"); } while(true); } Listing 5.5: Suchen eines einzelnen Knotens per XPath
60
Nutzung des XPathNavigators ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
5.4 Nutzung des XPathNavigators Anstelle der beiden Methoden der XmlDocument-Klasse kann man für eine bessere Leistung bei der Verarbeitung von XPath-Abfragen auch eine Klasse nutzen, die eine Implementierung der Schnittstelle IXPathNavigable bereitstellt. Diese Klasse ist für die Nutzung innerhalb der Transformation mit XSLT optimiert, kann aber auch als schnelle und optimierte Methode zum Suchen mit XPath in normalen Dokumenten verwendet werden. Die Klasse XPathNavigator selbst ist eine abstrakte Klasse, für eigene Zwecke (z.B. das Bereitstellen von XPath-Abfragen für ein hierarchisches Dateisystem) lässt sich eine entsprechende Ableitung von XPathNavigator implementieren. In der derzeitigen Version des .NET-Frameworks sind das die beiden Klassen XPathDocument und XmlNode. Hier wird der Navigator über einen Aufruf der Methode Create Navigator erzeugt, wie die nachfolgenden Beispiele noch im Detail zeigen werden.
XPathDocument
Hier der Code zur Erzeugung einer XPathNavigator-Instanz, in dem in ein XPathDocument eine bestehende XML-Datei geladen wird.
XPathNavigator
// erzeugen des XML-Dokumentes XPathDocument thedoc = new XPathDocument(@"h:\wetter.xml"); // erstellen der XPathNavigator-Instanz XPathNavigator xnav = thedoc.CreateNavigator();
Der Konstruktor von XPathDocument ist mehrfach überladen, so dass Sie natürlich auch auf andere Art und Weise XML-Daten übergeben können. Sie können eine Instanz des Dokumentes entweder als Stream, als UNC-Pfad, TextReader oder XmlReader erzeugen.
Navigation mit dem XPathNavigator Für die Navigation mit dem XPathNavigator lassen sich die folgenden Methoden einsetzen, die eine direkte Navigation im Vergleich zu den nachfolgend beschriebenen Auswahlmethoden vornehmen. Methode
Beschreibung
Matches
Prüft, ob der aktuelle Knoten dem angegebenen XPath-Ausdruck entspricht. Kann zum Testen einer bestimmten Position verwendet werden.
MoveTo
Springt an die Knotenposition, die den Kontext für den angegebenen XPathNavigator bildet.
MoveToAttribute
Bewegt den Navigator zum angegebenen Attribut, wobei ein Namensraum angegeben werden kann.
t
Tabelle 5.2: Die Navigationsmethoden für den XPathNavigator
61
.NET
5 XPath-Abfragen ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
Methode
Beschreibung
MoveToFirst
Geht zum ersten Geschwisterknoten des aktuellen Knotens (der erste Knoten auf der gleichen Ebene).
MoveToFirstAttribute
Springt zum ersten Attribut.
MoveToFirstChild
Springt zum ersten Unterknoten des aktuellen Knotens.
MoveToFirstNamespace
Bewegt den Navigator zum ersten Namensraumknoten des aktuellen Knotens.
MoveToId
Springt zu dem Knoten, der ein ID-Attribut mit dem angegebenen Zeichenfolgenwert besitzt.
MoveToNamespace
Bewegt den Navigator zum Namensraumknoten mit dem angegebenen lokalen Namen.
MoveToNext
Geht zum nächsten Geschwisterknoten des aktuellen Knotens (nächster Knoten der aktuellen Ebene).
MoveToNextAttribute
Springt zum nächsten Attribut.
MoveToNextNamespace
Springt zum nächsten Namensraumknoten.
MoveToParent
Bewegt den Navigator zum Elternelement des aktuellen Knotens.
MoveToPrevious
Springt zum vorherigen Geschwisterknoten des aktuellen Knotens.
MoveToRoot
Springt zum Wurzelknoten, zu dem der aktuelle Knoten gehört.
Tabelle 5.2: Die Navigationsmethoden für den XPathNavigator (Fortsetzung)
Auswählen von Knotenmengen mit Select XPathNodeIterator
Mit Hilfe der Methode Select lassen sich XPath-Ausdrücke zur Filterung einer XML-Struktur benutzen, für deren Treffer die Methode dann einen XPathNodeIterator zurückliefert. Die Methode Select liefert eine Iterator-Instanz, die zuerst auf dem Knoten positioniert ist, der als Kontext (und damit Startknoten) für die XPath-Abfrage dient. Daher muss zuerst einmal MoveNext aufgerufen werden, um auf den ersten Knoten des ResultSets zu treffen. Das folgende Listing liefert einen kurzen »Wetterbericht«, in dem über einen XPath-Ausdruck jeweils das - und das Element gesucht und dann mit Hilfe des Iterators in einer Schleife ausgegeben werden. // nutzung von XPathNodeIterator static void SampleXPathNodeIterator() { // erzeugen des XML-Dokumentes XPathDocument thedoc = new XPathDocument(@"h:\wetter.xml");
62
Nutzung des XPathNavigators ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
// erstellen der XPathNavigator-Instanz XPathNavigator xnav = thedoc.CreateNavigator(); XPathNodeIterator iter = xnav.Select("//location | //weather"); while(iter.MoveNext()) { Console.Write("Wetter in {0}: ", iter.Current.Value); iter.MoveNext(); Console.Write("{0}", iter.Current.Value); Console.WriteLine(); } } Listing 5.6: Durchlaufen eines Knotensets mit dem Iterator
Sie können für den Iterator die gleichen Methoden wie für den Navigator verwenden, da die Eigenschaft Current einen auf den aktuellen Knoten positionierten XPathNavigator zurückliefert. Allerdings können Sie diesen Navigator nicht aus dem gelieferten Trefferset herausbewegen. Sie können statt dessen diesen Navigator klonen (über die Methode XPathNavigator.Clone, die einen auf den gleichen Knoten positionierten Klon des für den Aufruf verwendeten Navgators liefert) und dann mit dem Klon aus dem Trefferset herausnavigieren, falls Sie dies benötigen.
Die Eigenschaft Current
Auswerten von Ausdrücken Die Methode Evaluate des XPathNavigators bietet eine sehr flexible Möglichkeit, auch XPath-Ausdrücke auszuwerten, die einen anderen Datentyp als ein Knotenset liefern. In Kombination mit XPath-Funktionen und den Operatoren für XPath-Ausdrücke lassen sich manche Aufgaben fast ohne Code erledigen, indem der XPath-Engine die Arbeit zugeschoben wird. Als Beispiel soll hier die Berechnung der Durchschnittstemperatur aller Wettermeldungen dienen.
Nochmal Evaluate
Im Kapitel über die Nutzung der Streamingklassen XmlReader und XmlWriter finden Sie ein Listing, das die gleiche Aufgabe löst. Hier war allerdings wesentlich mehr Arbeit nötig. Das Abfragen der passenden Tagnamen, die Addition der Werte usw. wurde alles im Code erledigt. Mit Hilfe der Evaluate-Methode verkürzt sich der Code radikal: // berechnungen als xpath-ausdruck static void SampleXPathDocument() { // erzeugen des XML-Dokumentes
63
.NET
5 XPath-Abfragen ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
XPathDocument thedoc = new XPathDocument(@"h:\wetter.xml"); // erstellen der XPathNavigator-Instanz XPathNavigator xnav = thedoc.CreateNavigator(); string xpr = "sum(//temp/text()) div count(//temp)"; double avg = (double)xnav.Evaluate(xpr); Console.WriteLine("{0:F2}", avg); } Listing 5.7: Durchschnittsberechnung mit XPath und Evaluate
Liefert eine Auswertung eines XPath-Ausdrucks ein Set von Knoten, dann wird eine XPathNodeIterator-Instanz geliefert (s. voriger Abschnitt), mit der Sie dann die einzelnen Knoten der Ergebnismenge durchlaufen können.
Optimieren durch Kompilieren von Ausdrücken Bei jeder Auswahl von Knoten mit Hilfe eines XPath-Ausdrucks muss der Parser jedesmal wieder die Analyse und Optimierung der Abfrage durchführen, was bei wiederverwendeten Abfragen zu einer Leistungseinbuße führt. Daher besitzen alle entsprechenden Methoden, die einen XPath-Ausdruck akzeptieren, auch eine Überladung für einen kompilierten Ausdruck. Dieser wird durch eine Instsanz der Klasse XPathExpression gebildet. Die Nutzung dieser Funktionalität erfordert nur minimal mehr Code, bietet aber gerade bei komplexeren Ausdrücken eine deutliche Steigerung der Abfrageleistung. XPathExpressions bringen Performance
Hier das Listing aus dem letzten Abschnitt, nur umgewandelt in eine Form, bei der der XPath-Ausdruck zuerst kompiliert und dann verwendet wird. Wie Sie sehen, kein Mehraufwand, aber eine gesteigerte Performance bei komplexen Abfragen (das Konzept ist ähnlich SQL mit ad-hoc queries und stored procedures). // berechnungen als xpath-ausdruck static void SampleXPathDocument() { // erzeugen des XML-Dokumentes XPathDocument thedoc = new XPathDocument(@"h:\wetter.xml"); // erstellen der XPathNavigator-Instanz XPathNavigator xnav = thedoc.CreateNavigator(); // jetzt zuerst kompilieren des ausdrucks
64
Zusammenfassung ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
XPathExpression xpr = xnav.Compile( "sum(//temp/text()) " + "div count(//temp)"); double avg = (double)xnav.Evaluate(xpr); Console.WriteLine("{0:F2}", avg); } Listing 5.8: Kompilieren von Ausdrücken steigert die Leistung.
5.5 Zusammenfassung Die Möglichkeit, innerhalb des DOM-Modells mit der XPath-Unterstützung von .NET zu arbeiten, erleichtert die Arbeit erheblich. Viele vorher manuell zu codierende Arbeiten können so an die XPath-Engine abgegeben werden und das Durchsuchen von komplexen XML-Strukturen wird durch die mächtigen XPath-Ausdrücke sehr erleichtert. Im übernächsten Kapitel werden Sie erfahren, wie mit Hilfe dieser Ausdrücke die Transformation von XML-Daten gesteuert werden kann.
65
.NET
Essentials
6
XML-Namensräume
6.1 Einführung Wozu überhaupt Namensräume, wird sich mancher Einsteiger in die XML-Programmierung fragen. Die Antwort ist einfach: wie in jedem Namenssystem zur Vermeidung von Namenskonflikten. Ebenso wie es in einem Dateisystem eine Datei text.txt nur dann zweimal geben kann, wenn sich diese in zwei Verzeichnissen befinden (und damit im Pfadnamen einen anderen "Präfix" besitzen), können Elementnamen ohne Probleme mehrfach verwendet werden, solange sie zwei verschiedenen Namensräumen angehören. Zur Kennzeichnung eines Namensraumes dient ein uniform resource identifier (Details dazu finden Sie im RFC2396, der unter dem URL http://www.ietf.org/rfc/rfc2396.txt zu finden ist), der eine eindeutige Identifikation des Namensraumes darstellt. Obwohl viele NamensraumURIs einem Web-URL ähneln, handelt es sich dabei nicht um einen Link (trotzdem aktivieren viele Firmen unter einem URL-URI oft eine Hinweisseite in HTML, z.B. auch das W3C). Wie der Name schon ausdrückt, geht es hier nur um eine eindeutige Kennzeichnung und da ein Domainname nur einmal vergeben wird, ist eine URL-URI eben einfach eindeutig zu erstellen.
URI und URL
Nachdem die Angabe des URI bei jedem Element dieses Namensraums zu viel Aufwand wäre, existiert jeweils zugeordnet zu einem Namensraum ein Präfix, das durch einen Doppelpunkt vom Elementnamen (dem local name) getrennt wird. Innerhalb eines Elementes ist auch die mehrfache Angabe von Namensräumen möglich.
Präfixe und Namensraum-URIs
Wie das XML-Fragment unten zeigt, lässt sich mit Hilfe von XMLNamensräumen ein Konflikt zwischen den beiden Elementen mit dem lokalen Namen titel auflösen. Ohne die entsprechenden Namensräume wären die beiden Elemente für eine verarbeitende Anwendung auch nicht voneinander zu unterscheiden gewesen.
Alice im Windowslandtitel>
Prof. Dr. Franz Testhuber
Listing 6.1: Ein XML-Fragment mit Namensräumen
67
.NET
6 XML-Namensräume ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
NamensraumDeklaration
Auch bei der Definition eines XML-Schemas sind Namensräume wichtig, da für ein Schema definiert wird, für welchen Namensraum-URI das im Schema definierte Inhaltsmodell gelten soll. Hierfür dient das Attribut targetNamespace des -Elementes. Sehen wir uns dazu im Schema meteo.xsd die Definition des -Elements an:
Listing 6.2: Kopf des meteo-Schemas
Hier wird als Zielnamensraum der URI "urn:meteo-demo" verwendet. Gleichzeitig wird ein Präfix wx für den gleichen Namensraum vereinbart, so dass alle Elemente ohne Präfix und solche mit dem Präfix wx als zu diesem Namensraum gehörig angesehen werden. Zusätzlich wird der Namensraum mit dem URI http://www.w3.org/2001/XMLSchema und dem Präfix xsd deklariert. Dieser enthält das Schema für die XSD-Schemata. Auch hier sollten Sie sich an die beiden Konventionen xsd oder xs für das Schema-Präfix halten, um Ihren programmierenden Kollegen Verwirrung zu ersparen. Für die Feststellung der Eindeutigkeit eines URI sei noch erwähnt, dass der RFC2396 festlegt, dass ein URI dann mit einem anderen URI identisch ist, wenn diese bei einem Zeichenvergleich keinerlei Unterscheide aufweisen. Achten Sie deshalb bei der Übernahme bekannter URIs immer auf die richtige Schreibweise.
t
Für die Beispiele in diesem Kapitel wird neben den Beispielen mit den Wettermeldungen die Beispieldatei nsdemo.xml verwendet, die den unten gezeigten Inhalt besitzt.
Wannsee-Wahnsinns-Welle
98.35 104.15 92.10
Nightly Blues 22:00 23:30
68
Einführung ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
Jonny Setup
MO MI FR SA
Office Charts 10:00 11:00 Gabi Parser
MO DI MI DO
Radio Benchmark - die schnelle Welle
89.90 96.50
Rush Hour 15:30 17:00 Steve Q. Log
MO DI MI DO FR
Listing 6.3: Die Namensraum-Beispieldatei
Namespaces bei XmlReader und XmlWriter Beim Lesen von XML mit Hilfe des XmlReaders oder einer abgeleiteten Klassen können Sie auf verschiedene Eigenschaften zurückgreifen, um Informationen über die Namensräume der einzelnen Elemente zu erhalten. Das folgende Beispiel zeigt eine Methodendefinition, mit der die Datei nsdemo.xml gelesen wird. Der Code listet die Informationen zur Schachtelungstiefe, dem Elementnamen, einem evtl. vorhandenen Präfix und dem dazugehörigen Namensraum-URI auf. public static void DemoElementList() { XmlTextReader rdr = new XmlTextReader(@"c:\tmp\nsdemo.xml"); Console.WriteLine("{0}\t{1,-16}\t{2}\t{3}",
69
.NET
6 XML-Namensräume ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
"Tiefe", "Tag", "Präfix", "Namespace"); while(rdr.Read()) { if(rdr.NodeType == XmlNodeType.Element) Console.WriteLine("{0}\t{1,-16}\t{2}\t{3}", rdr.Depth, rdr.LocalName, rdr.Prefix, rdr.NamespaceURI); } rdr.Close(); } Listing 6.4: Auslesen von Namensrauminformationen bei XmlReader
Hier die Ausgabe des Programms:
Abbildung 6.1: Die Ausgabe von Listing 6.4
XmlWriter und Namespaces
Beim Schreiben von Elementen mit einer Instanz von XmlTextWriter oder einer von XmlWriter abgeleiteten Klasse können Sie für Elemente und Attribute ebenfalls eine Namensraumdefinition und ein Präfix angeben. Wird die Methode WriteStartElement in der Überladung mit einem lokalen Namen und dem Namensraum (also ohne Präfix) aufgerufen, hängt das Ergebnis davon ab, ob dieser Namensraum bereits gültig und bekannt ist oder nicht. Falls ja, dann schreibt der XmlTextWriter automatisch das passende Präfix in die Ausgabedatei. Ist der Namensraum bisher unbekannt, wird für das Element ein Verweis auf diesen Namensraum ohne Präfix erstellt (also der Default-Namensraum für das Element auf den angegebene URI gesetzt). Die Methode im Beispiel unten zeigt das programmatische Erstellen einer XML-Datei mit der Angabe von Namensräumen.
70
Einführung ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
// schreiben von xml mit namensäumen für elemente public static void DemoNamespaceWriter() { string filename = @"c:\tmp\ausgabe.xml"; XmlTextWriter xw = new XmlTextWriter(filename, null); xw.Formatting = Formatting.Indented; xw.Indentation = 4; xw.IndentChar = (char)32; xw.WriteStartDocument(); xw.WriteStartElement("root"); // schreiben eines elements mit namensraum xw.WriteStartElement("aw","book", "urn:bookdemo-uri"); // attribut mit namensraumdaten schreiben xw.WriteAttributeString("aw", "isbn", "urn:bookdemo-uri", "0-0000-00000-0"); // element fuer bekannten namespace xw.WriteStartElement("titel", "urn:bookdemo-uri"); xw.WriteString("Demo-Titel"); xw.WriteEndElement(); // element fuer unbekannten namespace xw.WriteStartElement("autor", "urn:book-authors-uri"); xw.WriteString("An O. Nymous"); xw.WriteEndElement(); xw.WriteEndElement(); xw.Close(); Console.WriteLine("Datei {0} geschrieben", filename); } Listing 6.5: XmlTextWriter und Namensräume
Hier das Ergebnis des Methodenaufrufs:
Demo-Titel
71
.NET
6 XML-Namensräume ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
An O. Nymous
Listing 6.6: Die Ausgabedatei mit Namensraumdeklarationen
XmlNamespaceManager Die Klasse XmlNamespaceManager dient der Verwaltung und der Auflösung von Namensrauminformationen und wird immer dann verwendet, wenn eine XML-Datei über deklarierte Namensräume verfügt. Alle anderen XML-relevanten Klassen besitzen Überladungen für die bisher vorgestellten Methoden, bei denen eine XmlNamespaceManagerInstanz als Parameter angegeben werden kann. Namenstabellen verwalten Präfix- und Elementnamen
Bei der Instantiierung eines XmlNamespaceManager wird eine Tabelle als Parameter angegeben, der sogenannte »name table«. Hierbei handelt es sich um eine Tabelle mit atomisierten Zeichenfolgen für die Verwaltung der Elementnamen in der XML-Datei. Atomisiert deshalb, da jeder Name in dieser Tabelle nur einmal gespeichert wird, auch wenn es mehr als ein Element mit diesem Namen gibt. Nachdem Strings in der .NET-Umgebung nicht mehr verändert werden können (dies ist nur über die StringBuilder-Klasse möglich), kann somit auch ein Vergleich auf Gleichheit über die Objektreferenz erfolgen, der wesentlich schneller als ein Zeichenkettenvergleich durchgeführt werden kann. Mehr zur Stringverarbeitung inklusive Beispielen und Performancevergleichen zwischen Stringallozierung und StringBuilder in .NET finden Sie in meinem Buch "Goto C#", ebenfalls bei Addison-Wesley. Das folgende Code-Fragment erzeugt eine Instanz von XmlNamespace Manager und fügt eine Definition für einen Namensraum hinzu. XmlDocument thedoc = new XmlDocument(); thedoc.Load(@"c:\tmp\nsdemo.xml"); XmlNamespaceManager nsmgr = new XmlNamespaceManager(thedoc.NameTable); nsmgr.AddNamespace("radio", "urn:radio-uri"); Listing 6.7: Erzeugen einer XmlNamespaceManager-Instanz
t
Hier wird für die Instantiierung die vorhandene Namenstabelle des XmlDocuments benutzt. Nach der Erzeugung trägt der NamespaceManager automatisch drei Namensräume ein: Namespace:http://www.w3.org/XML/1998/namespace, Prefix:xml Namespace:http://www.w3.org/2000/xmlns/, Prefix:xmlns
72
Einführung ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
Außerdem wird ein Leerstring (String.Empty) für ein leeres Präfix eingetragen, also der Standardnamespace sozusagen gelöscht. Elemente ohne Präfix sind damit keinem Namensraum zugeordnet. Mit Hilfe der Methode AddNamespace fügen Sie eine zusätzliche Namensraumdeklaration hinzu. Dabei geht der Namespace-Manager davon aus, dass die Gültigkeit des Präfixes und des Namensraum-URI bereits geprüft wurde. Es wird hier keine weitere Überprüfung durchgeführt.
AddNameSpace
Die Klasse XmlNamespaceManager unerstützt Auflistungen über eine Implementierung der IEnumerable-Schnittstelle. Sie können also mit einer foreach-Schleife die Liste der eingetragenen Präfixe durchlaufen: Console.WriteLine("Folgende Präfixe sind eingetragen:"); foreach(string s in nsmgr) Console.WriteLine("Präfix \"{0}\" ", s); Listing 6.8: Auflisten aller eingetragenen Namensräume
Für die einzelnen Aufgaben der Verwaltung von Namensräumen sind in der Klasse eine ganze Reihe von Methoden und Eigenschaften definiert, mit der z.B. eine Zuordnung von Namensraum-URI zu Präfix in einer Zeile erledigt ist. Aufgabe
Methode oder Eigenschaft
Den Namespace-URI finden, der als Standardnamespace definiert wurde.
DefaultNamespace-Eigenschaft
Den Namespace-URI finden, der für ein Präfix deklariert wurde und im Gültigkeitsbereich liegt.
LookupNamespace-Methode
Das Präfix finden, das für einen NamespaceURI deklariert wurde und im Gültigkeitsbereich liegt.
LookupPrefix-Methode
Dem XmlNamespaceManager weitere Namespaces hinzufügen.
AddNamespace-Methode
Namespaces aus dem XmlNamespaceManager entfernen.
RemoveNamespace-Methode
Einen Gültigkeitsbereich für einen Namespace festlegen.
PushScope-Methode, PopScope-Methode
Überprüfen, ob im aktuellen Gültigkeitsbereich ein Präfix definiert ist.
HasNamespace-Methode
Den Namen der NameTable finden, die gegenwärtig in der Lookup- und in der Add-Methode verwendet wird.
NameTable-Eigenschaft
t
Tabelle 6.1: Aufgaben im Bereich Namensräume und die passenden Methoden dazu
73
.NET
6 XML-Namensräume ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
XPath und Namensräume Für die Selektierung von Elementen mit Hilfe einer XPath-Abfrage muss bei Namensräumen ebenfalls ein Namespace-Manager angegeben werden, der mit den entsprechenden Namensraumdeklarationen versorgt wurde. Anschließend erfolgt die Abfrage wie bereits bekannt per XPath-Ausdruck. SetContext
Es besteht allerdings ein Unterschied: es kann kein String als XPathAusdruck verwendet werden, sondern der Ausdruck muss vorher in eine Instanz einer XPathExpression kompiliert werden, da diese Klasse über eine Methode SetContext verfügt, mit der XPath-Ausdruck und Namespace-Manager verbunden werden. Beim XPath-Ausdruck selbst, der der Methode XPathNavigator.Compile übergeben wird, müssen die entsprechenden Präfixe für den Namensraum angegeben werden. Das Beispiel zeigt eine Methode mit diesen Änderungen im Code, um auch bei XPath-Abfragen auf Namensräume zugreifen zu können. public static void DemoNamespaceXPath() { XPathDocument thedoc = new XPathDocument(@"c:\tmp\nsdemo.xml"); XPathNavigator xnav = thedoc.CreateNavigator(); try { XmlNamespaceManager nsmgr = new XmlNamespaceManager(new NameTable()); nsmgr.AddNamespace("radio", "urn:radio-uri"); nsmgr.AddNamespace("leute", "urn:leute-uri"); XPathExpression xpr = xnav.Compile("//leute:moderator/@email"); xpr.SetContext(nsmgr); XPathNodeIterator iter = xnav.Select(xpr); while(iter.MoveNext()) { Console.WriteLine(iter.Current.Value); } } catch(Exception e) { Console.WriteLine("Error: ", e.Message); } } Listing 6.9: XPath-Auswahl von Knoten mit Namensräumen
74
Einführung ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
Validierung und Namensräume Bei der Validierung von XML-Daten, die Deklarationen für einen Namensraum enthalten, ändert sich kaum etwas, nur dass die Elemente entsprechend qualifiziert angegeben weren. Ein Sonderfall ist eine XML-Datei mit Elementen ohne Präfix (unqualifiziert), die über einen Standardnamensraum verfügen. Die Definition des Namensraums ist hier nochmals für die Beispieldatei meteo.xml aufgeführt:
Listing 6.10: Definition des Default-Namensraums (ohne Präfix)
Sie sehen die Definition eines Standardnamensraums für Elemente ohne Präfix in der vorletzten Zeile und in der letzten Zeile der Verweis, wo für diesen Namensraum das entsprechende Schema zu finden ist. Im Schema selbst erfolgt zuerst die Definition für den xsd-Namensraum, den Schemadefinitionsnamensraum. Anschließend wird das Namensraum-Präfix wx mit dem gleichen URI wie in der XML-Datei deklariert und danach der targetNamespace für das Schema ebenfalls auf diesen URI gesetzt, so dass das Dokument gegen dieses Schema geprüft werden kann. Bei beiden Angaben für elementFormDefault und attributeFormDefault entsprechen der Standardeinstellung und sind hier nur der Vollständigkeit halber aufgeführt. Deren Inhalt legt fest, dass Angaben unqualifiziert, also ohne Angabe des Präfix in der Datei erfolgen.
elementForm Default und attributeForm Default
Listing 6.11: Das -Element mit Namensräumen
Für die Validierung besteht der einzige Unterschied zur Validierung mit einem XSD-Schema darin, dass bei der Methode XmlSchemaCollection.Add nun die Angabe des Präfix zu einem Namensraum-URI erfolgt (ohne einen Namensraum war dies jeweils ein leerer String). Der Rest entspricht dem normalen Ablauf. Hier nochmal die komplette Klasse, damit Sie nicht zurückblättern müssen:
XmlSchema Collection
75
.NET
6 XML-Namensräume ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
class XSDSchemaDemo { private bool bValidated = true;
// annahme
// die ereignisbehandlungsroutine public void ValidationSink(object sender, ValidationEventArgs args) { // nur fehler ausgeben, warnungen ignorieren if(args.Severity == XmlSeverityType.Error) { bValidated = false; Console.WriteLine("Fehler: {0}", args.Message); } } // prüfen mit dem angeforderten schematyp public bool Validate(string url, ValidationType vt) { // schema collection mit schema fuellen XmlSchemaCollection xsc = new XmlSchemaCollection(); try { xsc.Add("urn:meteo-demo", @"c:\tmp\meteo.xsd"); //XSD schema } catch(Exception e) { Console.WriteLine("Exception thrown! {0}", e.Message); } // textreader erzeugen XmlTextReader txtrdr = new XmlTextReader(url); // den validatingreader erzeugen XmlValidatingReader vrdr = new XmlValidatingReader(txtrdr); vrdr.ValidationType = vt; // schemas zum validatingreader hinzufügen vrdr.Schemas.Add(xsc); // eintragen eventhandler vrdr.ValidationEventHandler += new ValidationEventHandler(this.ValidationSink); // jetzt keine exception mehr bei fehler
76
Zusammenfassung ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
while(vrdr.Read()) { } Console.WriteLine(vrdr.Schemas.Count); // ergebnis zurückliefern return bValidated; } } Listing 6.12: Überprüfung mit XSD-Schema und Namensräumen
6.2 Zusammenfassung Namensräume sind für XML mittlerweile unverzichtbar, um die Vielfalt der gleichlautendenden Elementnamen auseinander halten zu können. Mit Hilfe eines URI sind sie leicht eindeutig zu spezifizieren. Über ein Präfix, der durch einen Doppelpunkt vom lokalen Namensteil des Elements oder Attributes getrennt wird, ist die Verwendung kein Problem. Einsteiger sollten bei der manuellen Erstellung eines Schemas auf die richtige Deklaration der Namensraum-URIs achten und sich nach Möglichkeit anfangs der Hilfe einer XML-Entwicklungsumgebung bedienen. Nach Möglichkeit sollten Sie auf die Deklaration eines Default-Namensraums (ohne Präfix) verzichten, wenn Sie mehr als einen Namensraum verwenden, da die Lesbarkeit und die Zuordnung der Elemente durch das Präfix erleichtert wird.
77
.NET
Essentials
7
XSL-Transformationen
7.1 Grundlagen XSLT ist wohl für viele Entwickler der komplizierteste Teil der XMLTechnologien und wird daher noch nicht überall in dem Maß eingesetzt, das der Bedeutung von XSL-Transformation gerecht würde. Eine komplette Einführung in die Thematik würde an dieser Stelle zu weit führen, aus Platzgründen werde ich mich auf die wichtigsten Punkte beschränken, so dass Sie schnell zu Ergebnissen kommen. Details und Weiterentwicklungen (wie die XSL-Formatting Objects) finden Sie bei den diversen Titeln zum Thema XSLT. Wie der Name bereits ausdrückt, handelt es sich hier um eine »Stylesheet-Language«, d.h. um das Anwenden von Formatierungsregeln auf alle in einem Stylesheet definierten Elemente des geparsten XML-Baumes. Das Zusammenwirken der einzelnen Elemente zeigt die Abbildung eines Transformationsvorgangs.
Statt XSLT wird oft auch XSL als Abkürzung verwendet
Abbildung 7.1: Ablauf einer XSL-Transformation
Eine Eingabestruktur in XML wird mit Hilfe eines Stylesheets, das die Formatierungsregeln enthält, zu einer Ausgabestruktur umgewandelt. Das Ergebnis dieser Transformation muss übrigens nicht unbedingt wieder eine XML-Datei sein, sondern kann durchaus HTML oder sogar Flatfile sein. Nur nebenbei: Sie benötigen nicht unbedingt .NET für die Nutzung von XSLT, die XML Core Services von Microsoft (der MSXML) sind mittlerweile in der Version 4 erhältlich und bieten auch in der alten COM-Welt die Unterstützung der W3C-Empfehlungen.
79
.NET
7 XSL-Transformationen ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
Aufbau eines Stylesheets Ein XSL-Stylesheet (oder XSLT-Transformationsdatei, wie die deutsche Version des .NET-Frameworks diese Dateien nennt) ist selbst ebenfalls eine XML-Datei. Die für XSLT nötigen Elemente werden im XSLNamensraum definiert, der durch den URI http://www.w3.org/1999/XSL/ Transform identifiziert wird und (per Konvention) das Präfix xsl trägt. Auch wenn theoretisch ein anderes Präfix ebenso gut möglich wäre, sollten Sie sich doch an xsl halten. Wer weiß schon, in wie vielen Anwendungen dieses Präfix fest eincodiert wurde … XSL-Templates
Innerhalb des Stylesheet-Rumpfes werden so genannte Schablonen (templates) definiert, die als Wert für das Attribut match jeweils einen XPath-Ausdruck enthalten. Matcht dieser (wird ein übereinstimmendes Element gefunden), so wird dieses Template abgearbeitet und die darin enthaltenen Elemente in den Ausgabestrom geschrieben.
The weather in
:
83
.NET
7 XSL-Transformationen ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
The weather in
: