Sandini Bib
XML mit .NET
Sandini Bib
.NET
Essentials
Die .NET Essentials greifen die wichtigsten Themen des .NET Frameworks auf und behandeln auf knappem Raum, was Sie wissen müssen, um sofort eigene .NET Anwendungen auf höchstem Niveau zu schreiben. Ohne umständliche Einführung kommen Sie umgehend zum Kern des jeweiligen Themas und finden Antworten auf Fragen, mit denen Sie sich beim Codieren immer wieder konfrontiert sehen. Die Autoren zeigen Lösungen auf, die Sie übernehmen oder variieren und in eigene Projekte integrieren können: GDI+
Web Forms
Ellen Diehl, Thomas Ehrenberg 192 Seiten € 16,95 [D]/sFr 27,50 ISBN 3-8273-1993-5
Holger Schwichtenberg 144 Seiten EUR 16,95 [D]/sFr 27,50 ISBN 3-8273-2010-0
Grafikprogrammierung mit System.Drawing – Vektorgrafiken, Bildbearbeitung, Texteffekte – inkl. Farbteil
Web Forms – ASP.NET-Programmierung mit System.Web.UI: Webcontrols, Ereignisse, State Management, Datenbindung, User Controls, Debugging
Windows Forms
ADO.NET
Michael Kofler 176 Seiten € 16,95 [D]/sFr 27,50 ISBN 3-8273-1994-3
Ralf Westphal 144 Seiten € 16,95 [D]/sFr 27,50 ISBN 3-8273-1997-8
Grafische Benutzerschnittstellen mit System.Windows.Forms – Formularinterna, mehrere Fenster, Multithreading, Zwischenablage, Drag&Drop
Datenbankprogrammierung mit System.Data: relationale Datenbanken für ADO.NET, komplexe hierarchische Daten, Fehlerbehandlung
Weitere Informationen finden Sie unter www.dotnet-essentials.de.
Unser Online-Tipp für noch mehr Wissen
... aktuelles Fachwissen rund um die Uhr — zum Probelesen, Downloaden oder auch auf Papier.
www.InformIT.de
Sandini Bib
.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
Sandini Bib
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
Sandini Bib
.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
Sandini Bib
.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
Sandini Bib
.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
Sandini Bib
.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
Sandini Bib
.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
Sandini Bib
.NET
1 XML-Basics
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
Kommentare
Ein Kommentar beginnt auch in XML mit der Zeichenfolge . Innerhalb eines Kommentars dürfen keine doppelten Bindestriche vorkommen. Kommentare sind nicht innerhalb eines Element-Tags erlaubt und dürfen auch nicht vor der ersten Verarbeitungsanweisung vorkommen, die eine XML-Datei als solche kennzeichnet (, Details s.u.).
PIs
Eine Verarbeitungsanweisung (processing instruction) ist kein Element, sondern eine Anweisung an den XML-Parser bzw. die Anwendung, eine bestimmte Aktion auszuführen oder eine Kennzeichnung für bestimmte Eigenschaften des Dokumentes. Diese beginnen immer mit der Zeichenkette .
CDATA
Soll ein Abschnitt oder Elementinhalt als regulärer Text aufgefasst werden, ohne dass der XML-Parser versucht, darin Markup zu erkennen, kann dieser Abschnitt als CDATA-Sektion (CDATA steht für character data) geschrieben werden. Dazu wird folgendes Konstrukt verwendet: , ohne dass der Parser über diese Zeichen stolpert ]]> Listing 1.1: Ein CDATA-Abschnitt
Entities
t
Sonderzeichen bzw. Entitäten beginnen in XML immer mit einem kaufmännischen Und-Zeichen (&) und enden mit einem Semikolon (;). Folgende Entities sind bereits vordefiniert: Entity
Bedeutung
&
Das &-Zeichen
<
Das -Zeichen
"
Das "-Zeichen
'
Das '-Zeichen
Das Zeichen mit dem Code 00
Das Zeichen mit dem hexadezimalen Code 00
Tabelle 1.1: Vordefinierte Entitätszeichen in XML
Elemente
10
Elemente sind der ganze Rest, also ein öffnendes und ein schließendes Tag, dazwischen entweder weitere Elemente oder ein Textinhalt. Auch ein gemischter Inhalt aus Textzeichen, Unterelementen oder z.B. CDATA ist möglich. Alternativ zu der Konstruktion kann ein Element ohne Inhalt auch als abgekürzt werden.
Sandini Bib Namensräume
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
Gültig oder wohlgeformt Im Gegensatz zum butterweichen HTML-Standard müssen XMLStrukturen wesentlich strikteren Regeln folgen. Dabei wird zwischen einem gültigen und einem wohlgeformten Dokument unterschieden. Wir kommen gleich auf diesen Unterschied zurück. Generell gelten für XML-Strukturen die folgenden Regeln:
왘 Es wird strikt zwischen Groß- und Kleinschreibung unterschieden, d.h. ein Tag ist ein anderes Tag als . 왘 Jedes geöffnete Tag muss auch wieder geschlossen werden.
t
왘 Attributwerte müssen grundsätzlich in Anführungszeichen eingeschlossen werden, die Reihenfolge der Attribute in einem Element ist nicht definiert. Ohne ein Schema sind nur CDATA-Attributwerte erlaubt. 왘 Es sind keine isolierten Auszeichnungszeichen (wie ) erlaubt, diese müssen als Entitäten angegeben werden, z.B. > als >. 왘 Tags müssen korrekt geschachtelt werden, d.h. es sind keine verschränkten Schachtelungen erlaubt. 왘 Zu Beginn einer XML-Datei steht die Verarbeitungsanweisung mit einer Versionsangabe (obligatorisch) und einer Zeichencodierung (optional), z.B. . Eine XML-Struktur oder -datei, die diese Bedingungen erfüllt, wird als wohlgeformte XML-Datei bezeichnet. Eine gültige XML-Datei liegt dann vor, wenn der Inhalt dieser Datei anhand einer Grammatik validiert werden kann (beispielsweise über eine DTD oder ein XSD-Schema).
1.2 Namensräume Das Problem der Namenskonflikte ist so alt wie die Softwareentwicklung selbst. Stellen Sie sich vor, Sie entwickeln ein Kalenderprogramm mit Datumsberechnungen. Natürlich nennen Sie diese Klasse Datum. In einem Nachbarteam arbeitet ein Kollege ebenfalls an einer Klasse für Datumsberechnung, allerdings für den Kalender der Inkas. Auch er nennt seine Klasse Datum. In einem System ohne Namespaces hätten Sie nun ein Problem. Mit dem .NET-Framework und den Namensräumen erhalten Sie zwei Vorteile: Zum einen besitzen Sie damit eine weitere Möglichkeit, logisch zusammengehörige Klassen in einem Namensraum zu sammeln. Zum anderen vermeiden Sie damit die Konflikte bei der Namensgebung von Klassen und müssen sich nicht irgendwelche obskuren Präfixe aus Vorname und Blutgruppe des Entwicklers für die
11
Sandini Bib
.NET
1 XML-Basics
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
Benennung von Klassen ausdenken. Wenn Sie sich jetzt fragen, wo denn dabei das Problem liegt, arbeiten Sie einfach mal ein Jahr lang in einem Programmierteam mit 20 Entwicklern. Namensräume gibt es auch woanders
Ein anderes Beispiel sind Internet-Domains. Ein Domainname wie TollesProdukt ist nur einmal vorhanden. Glücklicherweise existiert für das Domain Name System eine auf Namensräumen basierende Lösung, die sogenannten Toplevel-Domains. Unterhalb von z.B. .com kann es die Domain geben, genauso aber unterhalb von .net und beispielsweise .de, damit haben dann immerhin drei Firmen die Möglichkeit, sich um diesen Namen zu streiten. Bei DNS wird der »Namensraumkennzeichner« als Postfix an den Domainnamen angehängt, da Domainnamen von rechts nach links gelesen werden, um in der Hierarchie abzusteigen.
XML-Namensräume Auch bei XML-Namensräumen existiert ein Präfix-Kennzeichner, der vor dem eigentlich Elementnamen geschrieben wird und durch einen Doppelpunkt vom Elementnamen getrennt wird. Der eigentliche Namensraum selbst wird durch einen URI angegeben und bei der Definition mit diesem Präfix verknüft. URI/URN: siehe RFC2396
Die obige Zeile zeigt die Definition eines Namensraumes und die Verwendung des entsprechenden Präfix für das Element. Weitere Details, auch die Verwendung von XML-Namensräumen im Programmcode, finden Sie im Kapitel über die Unterstützung von Namensräumen.
.NET-Namensräume Auch ein komplexes Produkt wie das .NET-Framework kommt nicht ohne einen Mechanismus zur Vermeidung von Namenskonflikten aus. So ist es ohne weiteres möglich, zwei Klassen mit dem Name Error zu definieren, solange sich diese beiden Klassen in verschiedenen Namensräumen befinden. Auch in .NET wird der Namensraum (im Gegensatz beispielsweise zum Internet Domain Name System) vor dem Namen der Klasse angegeben, die einzelnen Elemente werden hier durch einen Dezimalpunkt getrennt und Namensräume sind im Gegensatz zu XML hierarchisch über mehrere Ebenen schachtelbar. Achten Sie also darauf, XML-Namensräume und .NET-Namensräume für Klassen nicht zu verwechseln.
12
Sandini Bib Die .NET-Namensräume
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
1.3 Die .NET-Namensräume Die gesamte Funktionalität der XML-Basisklassen ist in dem öffentlichen Assembly System.Xml.dll enthalten, das im globalen Assemblycache liegt. Aus Gründen der Übersichtlichkeit wurden die einzelnen Funktionsblöcke in verschiedene Namenspaces unterteilt. System.Xml Hier finden sich die allgemeinen Klassen und Typen wie XmlReader und XmlWriter. System.Xml.XPath Dieser Namensraum beinhaltet alle Typen, die für die Abfrage von Xml-Strukturen mit Hilfe der XPath-Sprache benötigt werden. System.Xml.Xsl Die Transformation von Xml-Strukturen in andere Formate oder einen anderen Aufbau der Xml-Struktur wird durch die Datentypen in diesem Namensraum ermöglicht. In der Literatur (und auch im .NET-Framework) finden Sie manchmal anstelle der Abkürzung XSL auch die Ablürzung XSLT. System.Xml.Schema In diesen Namensraum befinden sich die Datentypen zur Validierung von Xml-Strukturen auf Gültigkeit und die Klassen für das Schema Object Model, das, ähnlich wie das Document Object Model für Dokumente, für die Baumdarstellung von Schemas vorhanden ist. System.Xml.Serialization Hier finden Sie die Klassen für die Serialisierung von Instanzen in eine XML-Datei. Auf eine Auflistung aller in den jeweiligen Namensräumen enthaltenen Klassen wird hier aus Platzgründen verzichtet. Erstens macht es keinen Sinn, hier einfach Informationen zu wiederholen, die Sie genauso gut in der Online-Hilfe zum .NET-Framework finden, zum anderen ist auf Grund der Konzeption dieses Buches auch gar kein Platz für ellenlange Tabellen.
1.4 Unterstützte Standards Microsoft hat sich bei der Planung der .NET-Initiative bemüht, bestehende und in Planung befindliche offene Standards so gut als möglich einzuhalten. Für den Bereich XML finden Sie hier eine Liste der unterstützten Standards mit dem URL für weitere Informationen:
13
Sandini Bib
.NET
1 XML-Basics
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
Die wichtigsten Standards
XML 1.0 http://www.w3.org/TR/1998/REC-xml-19980210 (einschließlich DTD-Unterstützung) XML-Namespaces http://www.w3.org/TR/REC-xml-names/ (sowohl Streamebene als auch DOM) XSD-Schemas http://www.w3.org/2001/XMLSchema XPath-Ausdrücke http://www.w3.org/TR/xpath XSLT-Transformationen http://www.w3.org/TR/xslt DOM Level 1 Core http://www.w3.org/TR/REC-DOM-Level-1/ DOM Level 2 Core http://www.w3.org/TR/DOM-Level-2/
14
Sandini Bib
.NET
Essentials
2
XmlReader und XmlWriter
Die Basis für die XML-Infrastruktur in .NET bilden unter anderem die beiden abstrakten Klassen XmlReader und XmlWriter. Diese beiden Klassen definieren den grundlegenden Leistungsumfang für alle abgeleiteten Klassen.
2.1 Lesen von XML-Daten Die .NET-Laufzeitumgebung unterstützt für den Zugriff auf XMLDateien sowohl den DOM-Ansatz als auch einen Streaming-Zugriff ähnlich SAX. Müssen Sie innerhalb des Dokumentes navigieren oder wahlfrei schreiben, bleibt nur das Document Object Model, welches eine Baumstruktur aller Knoten im Speicher aufbaut. Sicherlich nicht gerade ressourcenschonend, aber mit allen Möglichkeiten der Manipulation der einzelnen XML-Elemente und ihrer Inhalte.
Die Arbeitsweise des XmlReaders Der andere Ansatz ist eine Art Streaming der XML-Dokumente, eine Methode, die bereits seit längerem von beipielsweise dem SAX-Protokoll verfolgt wird. Hier sind die Anforderungen an die Ressourcen wesentlich geringer. Im Gegensatz zu SAX implementiert XmlReader aber ein Pull-Modell, bei dem die Applikation die Daten abholt und daher auch uninteressante Teile der Datei überlesen lassen kann. Diese Art von Zugriff wird (beipielsweise in der Datenbankwelt) auch oft als Firehose Cursor bezeichnet, da nur vorwärts lesend durch die Datei gegangen wird und dies (analog dem Feuerwehrschlauch) die Methode mit dem besten Durchsatz darstellt.
Firehose = Feuerwehrschlauch, Fachausdruck für schnellsten Zugriff
Von der abstrakten Klasse XmlReader abgeleitet existieren im .NETFramework drei Klassen: XmlTextReader, XmlNodeReader und die Klasse XmlValidatingReader, die eine Möglichkeit der Überprüfung auf Gültigkeit des Dokumentes erlaubt. Die Klasse XmlReader und die davon abgeleiteten Klassen arbeiten mit einem depth first-Ansatz, d.h. es wird zuerst innerhalb eines Knotens in die Tiefe gesucht und danach werden die Geschwisterknoten des aktuellen Knotens abgearbeitet.
15
Sandini Bib
.NET
2 XmlReader und XmlWriter
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
XmlReader / XmlTextReader Der einfachste Ansatz ist das Einlesen eines XML-Dokuments von einem URL oder einem Dateinamen . // einlesen einer XML-Datei public void ReadXmlDoc(string docurl) { XmlTextReader rdr = new XmlTextReader(docurl); while (rdr.Read()) { // behandlung des aktuellen knotens } rdr.Close(); } Listing 2.1: Die einfachste Art, eine XML-Datei zu lesen
Innerhalb der while-Schleife kann nun der aktuell gelesene Knoten der Datei untersucht und entsprechend verarbeitet werden.
Das Konzept des »aktuellen Knotens« Die .NET-Laufzeitumgebung implementiert das Konzept eines aktuellen Knotens. Beim Lesen mit einer XmlReader-Instanz wird dieser aktuelle Knoten über die verschiedenen Methoden immer weiter nach vorne bewegt, bis das Ende einer Datei erreicht wird. Dieser aktuelle Knoten ist nicht notwendigerweise immer ein XML-Element. Die Eigenschaft NodeType des XmlReaders enthält einen der folgenden Werte, die im Namensraum System.XML in der Aufzählung XmlNodeType definiert werden:
t
Name
Beschreibung
Attribute
Ein Attribut, z.B. units="Celsius"
CDATA
Ein CDATA-Bereich, der Text enthält, der sonst als XML-Auszeichnung erkannt würde, z.B. ]]>
Comment
Ein Kommentar, z.B.
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
Sandini Bib 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
Sandini Bib
.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
Sandini Bib 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
Sandini Bib
.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
Sandini Bib 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
Sandini Bib
.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
Sandini Bib 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
Sandini Bib
.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
Sandini Bib 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
Sandini Bib
.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
Sandini Bib
.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
Sandini Bib
.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
Sandini Bib ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.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
Sandini Bib
.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
Sandini Bib ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.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
Sandini Bib
.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
Sandini Bib 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
Sandini Bib
.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
Sandini Bib 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
Sandini Bib
.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
Sandini Bib 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
Sandini Bib
Sandini Bib
.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
Sandini Bib
.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
Sandini Bib 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
Sandini Bib
.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
Sandini Bib 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
Sandini Bib
.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
Sandini Bib Ä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
Sandini Bib
.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
Sandini Bib Ä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
Sandini Bib
.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
Sandini Bib 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
Sandini Bib
.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
Sandini Bib 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
Sandini Bib
.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
Sandini Bib
.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
Sandini Bib
.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
Sandini Bib 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
Sandini Bib
.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
Sandini Bib 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
Sandini Bib
.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
Sandini Bib 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
Sandini Bib
.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
Sandini Bib 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
Sandini Bib
.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
Sandini Bib 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
Sandini Bib
.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
Sandini Bib 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
Sandini Bib
Sandini Bib
.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
Sandini Bib
.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
Sandini Bib 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
Sandini Bib
.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
Sandini Bib 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
Sandini Bib
.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
Sandini Bib 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
Sandini Bib
.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
Sandini Bib 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
Sandini Bib
.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
Sandini Bib 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
Sandini Bib
Sandini Bib
.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
Sandini Bib
.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 :
Listing 7.1: Eine sehr einfache XSLT-Datei
Das obige Stylesheet enthält ein Template mit dem Wurzelelement als Übereinstimmungsausdruck und eines, das für alle -Elemente als Kontextknoten oder dessen Nachfahren eine Übereinstimmung liefert. Für das Wurzelelement erfolgt nur die Ausgabe eines Absatz-Tags in HTML, gefolgt von einer rekursiven Anwendung aller anderen definierten Templates im Stylesheet (), danach wieder ein schließendes Tag für einen Absatz.
80
Sandini Bib Stylesheets laden
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
Die Schablone für alle -Elemente zeigt die Ausgabe von Textinformation, die unverändert in den Ausgabestrom geschrieben werden sollen (per ) und das Einsetzen von Elementinhalten über die Anweisung , die ebenfalls einen XPath-Ausdruck verwendet. Beispiele für komplexere Stylesheets finden Sie noch weiter unten in diesem Kapitel oder in einem der vielen Einführungen in das Thema XSLT.
7.2 Stylesheets laden Zum Laden von Tranformations-Stylesheets benutzen Sie die Methode Load der Klasse XslTranform, die mehrfach überladen ist. So kann das Stylesheet nicht nur aus einer Datei, sondern auch von einem bereits geparsten XPathDocument oder einem XmlDocument oder einem XmlReader stammen. Damit können auch programmatisch erzeugte Stylesheets leicht geladen und für die Transformation verwendet werden. Das folgende Beispiel lädt ein Stylesheet von einem URL:
XslTransform, Methode Load
XslTransform xslt = new XslTransform(); //Load the stylesheet. xslt.Load("http://localhost/demo/xsl/wetter1.xsl"); Listing 7.2: Laden eines bestehenden Stylesheets
Laden über einen XmlUrlResolver Wird ein Stylesheet (oder eine andere externe Ressource für XML wie z.B. eine DTD, ein Schema u.a.) über einen URL geladen, spielt die Berechtigung für diesen Zugriff eine Rolle. Besteht für das angegebene Vezeichnis auf dem IIS ein anonymer Zugriff, reicht die Angabe des URL aus. Für alle anderen Fälle muss ein sogenannter Resolver angegeben werden, der sich dann um die Weiterreichung der Benutzerinformationen kümmert. Die Klasse NetworkCredentials findet sich übrigens im Namensraum System.Net, den Sie importieren müssen. Das Listing zeigt das Laden eines Stylesheets mit Hilfe eines Resolvers und der Angabe von Benutzername und Kennwort.
Authentifizierung über Network Credentials aus System.Net
static void SampleResolverTransform() { XslTransform xslt = new XslTransform(); // neue resolver-instanz erzeugen XmlUrlResolver resolver = new XmlUrlResolver(); // benutzderdaten festlegen NetworkCredential nc =
81
Sandini Bib
.NET
7 XSL-Transformationen
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
new NetworkCredential("usr","pwd","domain"); resolver.Credentials = nc; // stylesheet laden. xslt.Load("http://kiste/xsl/wetter1.xsl", resolver); // hier dann transformation durchführen } Listing 7.3: Laden von Ressourcen über einen Resolver
Diese Art des Zugriffs benötigen Sie insbesondere dann, wenn innerhalb des Stylesheets (oder des XML-Dokumentes) auf eine externe Datei verwiesen wird, da dann der Resolver die einzige Möglichkeit darstellt, diese Verweise unter Sicherheitsaspekten aufzulösen. Der XmlUrlResolver ist die Standardklasse (abgeleitet von XmlResolver) für diesen Zweck, Sie können allerdings auch eigene, spezielle Resolver programmieren. Weitere Hinweise dazu finden Sie in der Online-Hilfe von Visual Studio .NET.
7.3 Transformation durchführen Methode Transform
Die eigentliche Hauptarbeit führt die Methode Transform der Klasse XslTransform durch, durch deren Aufruf das Stylesheet und die XML-Ein-
gangsdaten verbunden werden. Auch diese Methode ist wieder mehrfach überladen, so dass für jeden Zweck die passende Liste an Parametern zur Verfügung steht. So wird als erster Parameter die XMLStruktur übergeben, wobei Sie die Wahl zwischen einer Klasse haben, die die Schnittstelle XPathNavigable implementiert (XmlNode, XmlDocument oder XPathDocument) oder einer Instanz eines XPathNavigators. Der zweite Parameter ist eine Referenz auf eine Liste von Parameterersetzungen für das Stylesheet (siehe weiter unten) und der dritte Parameter gibt das Ziel der Transformation an, wobei hier ein Stream-Nachfolger, ein TextWriter oder ein XmlWriter benutzt werden können. Das folgende Listing ist der komplette Code für die Transformation einer XML-Datei mit Hilfe eines XSL-Stylesheets. Das Ergebnis der Transformation wird dann in die Standardausgabe geschrieben. Nach dem Listing folgt das Listing des verwendeten Stylesheets und in der Abbildung die Ausgabe des Programms. // XSL-Transformationen in .NET using using using using
82
System; System.XML; System.XML.XPath; System.XML.XSL;
Sandini Bib Transformation durchführen
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
namespace XSLDemo { class XSLDemos { static void SampleSimpleTransform() { XslTransform xslt = new XslTransform(); // stylesheet laden xslt.Load(@"h:\wetter1.xsl"); // XPathDocument erzeugen und die // xml-daten laden XPathDocument thedoc = new XPathDocument(@"h:\miniwetter.xml"); // XmlTextWriter fuer die ausgabe // erzeugen XmlTextWriter writer = new XmlTextWriter(Console.Out); // transformation durchführen // ausgabe geht nach Console.Out, wie // durch XmlTextWriter-Erzeugung festgelegt xslt.Transform(thedoc,null,writer); } static void Main(string[] args) { SampleSimpleTransform(); Console.WriteLine(); Console.Write("Press a key ...."); Console.ReadLine(); } } } Listing 7.4: Transformation eines XML-Dokumentes per XSL
Hier das verwendete Stylesheet:
83
Sandini Bib
.NET
7 XSL-Transformationen
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
The weather in :
Listing 7.5: Demo-Stylesheet wetter1.xsl
Im Konsolenfenster wird die folgende Ausgabe erzeugt:
Abbildung 7.2: Die Ausgabe im Konsolenfenster
XML-Strukturen mit XSLT umformen Xml-Editoren als sinnvolles Werkzeug
Mit Hilfe von XSL-Stylesheet und der Klasse XslTransform lassen sich nicht nur XML-Daten in ein anderes Format überführen. Eines der Haupteinsatzgebiete ist die Umformung einer XML-Struktur in eine andere Struktur, um Daten für verschiedenene Anwendungen anzupassen. So ist die Beispieldatei meteo.xml mit Hilfe einer XSL-Transformation aus der Datei wetter.xml entstanden. Produkte wie z.B. der BiztalkServer haben diese Funktionaliät als Grundlage. Visual Studio .NET bietet zwar eine Möglichkeit zur Erstellung von XSLT-Dateien, aber für diese Stylesheets existiert in der aktuellen Version keine Unterstützung durch Autovervollständigung, IntelliSense und all die anderen guten Dinge aus dem Code-Editor. Für die Erstellung von komplexeren Stylesheets sollten Sie sich daher eine XML-Entwicklungsumgebung wie XMLSpy oder Stylus Studio ansehen. Das nachfolgende Stylesheet erzeugte aus der Datei wetter.xml die Dateo meteo.xml für die weiteren Beispiele. Mit Hilfe der XSL-Funktionen lassen sich dabei auch Teile eines Elementinhaltes verwenden, wie die Funktion substring-before für das Abschneiden des Prozentzeichens bei der Luftfeuchtigkeit zeigt.
84
Sandini Bib Transformation durchführen
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
percent knots hpa
85
Sandini Bib
.NET
7 XSL-Transformationen
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
Listing 7.6: Komplexeres Beispiel für eine XSL-Transformation
7.4 Transformationen verketten und optimieren Transformationen verketten Vermeiden Sie unnötiges Parsing
Nicht immer reicht eine einzelne Transformation aus. So sind durchaus Szenarien denkbar, bei denen zuerst eine XML-Datei mit Hilfe eines Stylesheets in ein Stylesheet transformiert wird und dann dieses Stylesheet benutzt wird, um eine XML-Datendatei zu transformieren. Oder eine Datei wird nacheinander von drei verschiedenen Stylesheets umgeformt. Dazu sollte nach Möglichkeit vermieden werden, einen neuen Parse-Vorgang zu starten. Dies ist beispielsweise dann der Fall, wenn das Ergebnis der ersten Transformation (das Zwischenergebnis) in einen Stream (z.B. eine Instanz von MemoryStream) geschrieben wird. Sinnvoller ist die Nutzung eines XmlReader, aus dem sich dann eine neue Instanz eines XPathDocument erzeugen lässt, die gleich die durch den Reader geparsen Inhalt übernimmt. Je nach Szenario und Dateistruktur lassen sich dadurch deutliche Performancesteigerungen erzielen. Hier ein Beispiel für eine zweifache Transformation. Einsatzmöglichkeiten in der Praxis bieten sich beispielsweise für Redaktionssysteme, die XML-Content von XML nach XHTML rendern und danach in einem zweiten Durchlauf das »nackte« XHTML durch Formatierungen wie Inline-Styles oder andere Markup-Attribute ergänzen. // mehrfache transformationen mit // koppelung über einen XmlReader static void SampleChainedTranform() { XslTransform xslt1 = new XslTransform(); XslTransform xslt2 = new XslTransform(); // stylesheets laden xslt1.Load(@"h:\wetter1.xsl"); xslt2.Load(@"h:\wetter2.xsl"); // XPathDocument erzeugen und datei laden XPathDocument thedoc = new XPathDocument(@"h:\miniwetter.xml"); // XmlTextWriter für den output erzeugen
86
Sandini Bib Zusammenfassung
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
XmlTextWriter writer = new XmlTextWriter(Console.Out); // Zwischenergebnis in einen XmlReader stecken XmlReader temprdr = xslt1.Transform(thedoc, null); // XPathDocument aus diesem Reader erzeugen XPathDocument doc2 = new XPathDocument(temprdr); // nochmal transformieren und an die konsole xslt2.Transform(doc2,null,writer); } Listing 7.7: Mehrfache Transformationen per XSL
XPathNavigable-Implementierungen nutzen Aus Gründen der Performance sollte für die Transformation nach Möglichkeit die XML-Struktur zuerst in eine XPathDocument-Instanz geladen werden, da diese die Schnittstelle XPathNavigable implementiert und für die Verarbeitung von Stylesheets optimiert wurde. Das obige Listing zeigt diesen Ansatz. Sie können natürlich auch einfach der URL für die Eingabe- und die Ausgabedatei angeben, wobei für kleinere Dateien der Performanceunterschied kaum ins Gewicht fällt. Ganz anders sieht die Sache dagegen aus, wenn Sie eine 15 Mbyte große XML-Struktur mit zwei Stylesheet-Transformationen in eine andere XML- oder Editfact-Datei überführen.
Mehrfaches Laden von Stylesheets vermeiden Vermeiden Sie nach Möglichkeit das unnötige Laden von Stylesheets, da hierbei jedesmal von neuem das Dokument geparst und eventuelle externe Referenzen aufgelöst werden müssen. Werden mehrere Dokumente mit dem gleichen Stylesheet behandelt, sollte das Laden per Load außerhalb der Schleife erfolgen und nicht jedesmal neu in der Schleife.
7.5 Zusammenfassung Mit nur wenigen Methoden (hautpsächlich Load und Transform) bietet die XSLT-Unterstützung von .NET eine mächtige Möglichkeit, beliebige XML-Strukturen programmgesteuert zu transformieren und sogar die Stylesheets selbst als XML-Dokumente zuerst programmatisch zu erzeugen. Die sich bietenden Möglichkeiten werden nur durch die Fantasie des Entwicklers begrenzt. Entwerfen Sie doch einfach eine XMLNotation für Klassen oder benutzen Sie eine der verfügbaren und schreiben sich dann dazu ein XSL-Stylesheet, das je nach Auswahl entweder C#- oder Visual Basic .NET-Code erzeugt!
87
Sandini Bib
Sandini Bib
.NET
Essentials
8
Validieren von XML
8.1 Warum ein Schema? In der Regel werden XML-Strukturen von Anwendungen weiterverarbeitet. Daher besteht ein Interesse daran, dass eine zur Bearbeitung vorliegende Datei in ihrem Aufbau vorhersagbar ist bzw. sich an einen vereinbarten Standard hält. Ist dies nicht der Falls, kann die Datei unter Umständen nicht verarbeitet werden (wenn z.B. bei einer Bestelldatei in XML die Angabe der Teilenummer fehlt). Daher wurden bereits zu Beginn der XML-Entwicklung Möglichkeiten von SGML übernommen, die Struktur der Datei zu beschreiben und so einen Test auf Einhaltung dieser Struktur zu ermöglichen. Diese erste Variante eines Schemas war die Document Type Definition (DTD). Aus verschiedenen Gründen (die in den folgenden Abschnitten noch erläutert werden) ist eine DTD aber nicht das ideale Mittel zur Beschreibung der Grammatik einer XML-Datei. Standard ist derzeit die vom World Wide Web Consortium (W3C) als Empfehlung verabschiedete XMLSchema Definition (XSD), die das zweite hier erläuterte Mittel zur Defintion einer XML-Grammatik darstellt.
DTDs sind noch relativ häufig
Aufbau einer DTD Aus Gründen des zur Verfügung stehenden Platzes und da DTD meist nur noch in Form von Portierungsquellen für XSD-Schemata auftauchen, sind die nächsten Abschnitte nur eine kurze Einführung. Weitere Details zum Erstellen von DTD-Schemata finden Sie in den allgemeinen XML-Büchern zum Thema bzw. dem Internet. Anhand einer Document Type Definition lassen sich aber schön der Weg von der Struktur zum Dokument und auch die Nachteile eines Textansatzes zeigen, der schließlich zu den XSD-Schemata führte. Eine DTD beschreibt, welche Elemente und Attribute in welcher Reihenfolge auftreten, ob ein Attribut erforderlich ist und diverse weitere Details der XML-Struktur. Diese Beschreibung ist eine Textdatei (kein XML, was einen der Hauptnachteile der DTDs darstellt) und besitzt für die Beispieldatei Meteo.xml folgenden Aufbau: sky (#PCDATA)*> temp (#PCDATA)*> temp units (Celsius|Fahrenheit|Kelvin) #REQUIRED> humidity (#PCDATA)*> humidity units CDATA #REQUIRED> wind (#PCDATA)*>
89
Sandini Bib
.NET
8 Validieren von XML
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
airpressure (#PCDATA)*> airpressure CDATA #REQUIRED CDATA #REQUIRED> station ((sky|clouds),temp,humidity,wind,airpressure)> station id CDATA #REQUIRED> weatherdata (station)+>
Listing 8.1: Eine Document Type Definition
Definition von Elementen Wie sich relativ leicht erkennen lässt, werden mögliche Elemente mit dem Ausruck definiert, der als Parameter den Namen des Elements und in runden Klammern die Liste der Unterelemente erhält. Werden als Elementinhalt beliebige Zeichenketten an Stelle von weiteren Unterelementen akzeptiert, wird dies durch #PCDATA angegeben. Diese Abkürzung steht für »parsed character data« und erlaubt beliebige Zeichenketten. Ein Beispiel für Unterlemente finden Sie bei der Definition , hier werden die notwendigen Unterlemente aufge-
listet. Da in der Datei entweder das Tag oder die Bewölkung mit vorkommen darf, wird diese Auswahl in der DTD über den choice operator "|" festgelegt. Dieser arbeitet wie ein logisches Oder und kann in einer längeren Liste auch mehrfach wiederholt werden, wie Sie bei den Attributdefinitionen noch sehen werden. Für das Auftreten von Elementen kann angegeben werden, ob und wie oft ein Element als Unterelement auftreten darf. In der Beispieldatei muss innerhalb des -Elementes mindestens ein Element vorhanden sein, was in der DTD durch das nachgestellte "+" definiert wird. Wer bereits mit regulären Ausdrücken gearbeitet hat, kennt die möglichen Modifikatoren bereits:
t
Zeichen
Bedeutung
?
Optionales Element, muss nicht vorhanden sein
*
Dieses Element kann 0-mal oder öfter auftauchen
+
Dieses Element muss mindestens einmal vorhanden sein
|
Choice operator, eines der Elemente aus der Liste
Tabelle 8.1: Die möglichen Modifikatoren für Elemente und Attribute
90
Sandini Bib Warum ein Schema?
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
Definition von Attributen Für die Definition von Attributen dient das -Statement, mit dem für ein als Parameter angegebenes Element die Liste der möglichen Attribute definiert wird. Nach dem Namen des Elements folgt der Attributname und anschließend der mögliche Inhalt, entweder CDATA für Zeichenfolgendaten oder die direkte Angabe eines oder einer Reihe möglicher Werte mit dem choice-Modifikator. Für Attribute existieren vier mögliche Instanztypen: Fixed
Das Attribut muss dem angegebenen Wert entsprechen und muss immer explizit angegeben werden
Implied
Das Attribut ist optional, es existiert kein Defaultwert
Optional
Das Attribut ist optional, wird kein Attribut angegeben, setzt der Parser den Default-Wert ein
Required
Es muss immer explizit ein Attributwert angegeben werden
t
Tabelle 8.2: Die möglichen Belegungstypen für Attributinhalte
Interne und externe DTDs Die obige DTD ist eine interne DTD, da dieser Text in der XML-Datei selbst enthalten ist: Listing 8.2: Einbetten einer DTD in das XML-Dokument
Das -Element gibt nach dem Namen des Wurzelelementes (hier ) in eckige Klammern eingeschlossen die Document Type Definition an. Dies ist nicht immer wünschenswert. Soll beispielsweise mehr als eine Dokumenteninstanz die gleiche DTD benutzen, wäre dies erstens Platzverschwendung und zweitens wären Änderungen wesentlich aufwändiger. Daher ist auch die Angabe einer externen DTD möglich. Hier wird im DOCTYPE-Element anstelle der direkten Angabe der DTD einfach ein Verweis auf eine Betriebssystemdatei aufgeführt. Um unsere interne in eine externe DTD umzuwandeln, markieren Sie den komplet-
Eine externe DTD verwenden
91
Sandini Bib
.NET
8 Validieren von XML
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
ten Text zwischen den beiden eckigen Klammern und speichern diesen in eine Textdatei (per Konvention mit der Erweiterung .dtd) ab. Anschließend ändern Sie das DOCTYPE-Element wie folgt ab:
Eventuell benötigen Sie einen UrlResolver
Auf diese Weise beziehen Sie sich bei der Validierung auf eine externe Datei. Erfolgt der Zugriff auf die XML-Datei über einen URL, benötigen Sie für das Laden der XML-Struktur einen Resolver (siehe Kapitel 5, "Resolver"), der sich um die Sicherheitsinformationen für den Zugriff auf externe Verweise (Benutzer, Password, Domain) kümmert.
Validierung mit Hilfe einer DTD Der grundlegende Ablauf ist für eine DTD, ein XDR-Schema oder ein XSD-Schema der gleiche, da der zu verwendende Schematyp für einen XmlValidatingReader einfach über eine Eigenschaft eingestellt wird. XmlValidating Reader
Für die Prüfung eines XML-Dokumentes auf Gültigkeit wird eine Instanz der Klasse XmlValidatingReader verwendet. Deren Konstruktor verfügt über eine Überladung, die entweder ein XML-Fragment als String oder Stream akzeptiert oder aber eine XmlReader-Instanz, was in den meisten Fällen sinnvoller sein dürfte. Der XmlReader (genauer, eine Nachfolgeklasse wie XmlTextReader, da es sich bei XmlReader um eine abstrakte Klasse handelt) wird dem ValidatingReader-Konstruktor übergeben und in einer Schleife wird per Read solange gelesen, bis die gesamte XML-Struktur überprüft wurde. Im einfachsten Fall müssen Sie wirklich nicht mehr tun, da ohne eine definierte Ereignisbehandlung der XmlValidatingReader eine Exception auslöst, die Sie nur abzufangen brauchen, um festzustellen, ob in der XML-Struktur ein Fehler enthalten ist oder nicht. Das nachfolgende Listing zeigt diesen Ansatz, bei dem die Beispieldatei meteo.xml mit Hilfe einer internen DTD validiert wird. // validierung mit hilfe einer internen dtd // die namensräume using System; using System.XML; using System.XML.XPath; using System.XML.Schema; namespace SchemaSamples { // die validierungsklasse class DTDSchemaDemo {
92
Sandini Bib Warum ein Schema?
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
private string fFilename; private XmlTextReader txtrdr = null; // der konstruktor public DTDSchemaDemo(string filename) { fFilename = filename; txtrdr = new XmlTextReader(fFilename); } // prüfen mit dem angeforderten schematyp public bool Validate(ValidationType vt) { // den validatingreader erzeugen XmlValidatingReader vrdr = new XmlValidatingReader(txtrdr); vrdr.ValidationType = vt; // durchlaufen, wenn keine exception, // dann ist das dokument gültig try { while(vrdr.Read()) { } return true; } // verloren, doch ein fehler catch(Exception e) { Console.WriteLine(e.Message); return false; } } static void Main() { // instanz der demo-klasse erzeugen DTDSchemaDemo sd = new DTDSchemaDemo(@"c:\tmp\meteo.xml"); // validieren und ergebnis ausgeben if(sd.Validate(ValidationType.DTD)) Console.WriteLine("Keine Fehler, " + " Dokument gültig."); else Console.WriteLine("Dokument nicht " + "gültig."); // den benutzer das // konsolenfenster schliessen lassen
93
Sandini Bib
.NET
8 Validieren von XML
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
Console.WriteLine(); Console.Write("Press a key ...."); Console.ReadLine(); } } } Listing 8.3: Prüfen eines Dokumentes mit Hilfe einer DTD
Validierung mit EventSink Alternativ kann für den ValidatingReader auch eine Ereignisbehandlungsroutine angegeben werden, die dann aufgerufen wird, wenn bei der Validierung ein Warnungs- oder Fehlerereignis auftritt. Dazu muss der obige Code nur um die entsprechende Routine ergänzt und dieser Delegate beim Handler eingetragen werden. Das folgende Listing zeigt die Unterschiede zur Version ohne Ereignisbehandlung: // validierung mit hilfe einer internen dtd // die namensräume using System; using System.XML; using System.XML.XPath; using System.XML.Schema; namespace SchemaSamples { // die validierungsklasse class DTDSchemaDemo { private string fFilename; private XmlTextReader txtrdr = null; private bool bValidated = true; // annahme // der konstruktor public DTDSchemaDemo(string filename) { fFilename = filename; txtrdr = new XmlTextReader(fFilename); } // die ereignisbehandlungsroutine public void ValidationSink(object sender, ValidationEventArgs args) { // nur fehler ausgeben, warnungen ignorieren if(args.Severity == XmlSeverityType.Error) {
94
Sandini Bib Warum ein Schema?
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
bValidated = false; Console.WriteLine("Fehler: {0}", args.Message); } } // prüfen mit dem angeforderten schematyp public bool Validate(ValidationType vt) { // den validatingreader erzeugen XmlValidatingReader vrdr = new XmlValidatingReader(txtrdr); vrdr.ValidationType = vt; // eintragen eventhandler vrdr.ValidationEventHandler += new ValidationEventHandler(this.ValidationSink); // jetzt keine exception mehr bei fehler while(vrdr.Read()) { } // ergebnis zurückliefern return bValidated; } static void Main() { // instanz der demo-klasse erzeugen DTDSchemaDemo sd = new DTDSchemaDemo(@"c:\tmp\meteo.xml"); // validieren und ergebnis ausgeben if(sd.Validate(ValidationType.DTD)) Console.WriteLine("Keine fehler, " + "Dokument gültig."); else Console.WriteLine("Dokument nicht " + "gültig."); // den benutzer das // konsolenfenster schliessen lassen Console.WriteLine(); Console.Write("Press a key ...."); Console.ReadLine(); } } } Listing 8.4: Validierung mit einer Ereignisbehandlungsroutine
95
Sandini Bib
.NET
8 Validieren von XML
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
Solange Sie einfach nur wissen möchten, ob das Dokument gültig ist oder nicht, reicht auch die einfache Variante, bei der nur geprüft wird, ob eine XmlException aufgetreten ist. Die Definition einer Ereignisbehandlungsroutine (event sink) bietet dagegen mehr Flexibilität bei der Reaktion auf das Ereignis.
DTDs und die Alternativen (XDR und andere) Einer der Hauptnachteile einer DTD ist die Tatsache, dass es sich hierbei nicht selbst um eine XML-Datei handelt, so dass all die vorhandenen Werkzeuge und Möglichkeiten nicht auf eine DTD angewendet werden können. Darüber hinaus existieren aber noch andere Nachteile, die Hersteller und Entwickler von XML-Lösungen bereits frühzeitig nach Alternativen suchen ließen. Hier eine (nicht vollständige) Liste der Gründe, warum eine DTD nicht optimal für die Erstellung eines Schemas geeignet ist:
왘 Keine Einschränkung für Inhalte (CDATA heißt wirklich: alle Zeichen) 왘 Keine Defaults für Elemente, nur für Attribute 왘 Definition der Attributwerte erlaubt nur Aufzählungen als komplexere Typen 왘 Keine Namensraumunterstützung 왘 Aufwändige Definition bei komplexeren Inhaltsstrukturen 왘 Nicht modularisierbar (nur über entities und das nur begrenzt) 왘 Keine Wildcards für Elemente oder Attribute 왘 Keine Vererbung oder Erweiterung von DTDs Jeder Hersteller von XML-Produkten hatte bis zur Verabschiedung der XS-Empfehlung durch das W3C eigene Vorschläge entwickelt. Bei Microsoft war dies das "XML Data Reduced-Schema" (XDR), das ebenso wie DTD heute eigentlich nur noch historischen Wert hat, da .NET die XSD-Schemata unterstützt und damit den optimalen Weg für die Definition der Struktur von XML-Dateien bietet.
8.2 XSD – der Standard Einführung Die Wünsche und Anforderungen an das W3C für einen DTD-Nachfolger waren umfangreich und nicht leicht zu erfüllen:
96
Sandini Bib XSD – der Standard
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
왘 Definition im XML-Format 왘 Selbstbeschreibend und für Menschen lesbar 왘 Klar, einfach und modular strukturiert 왘 Von möglichst vielen XML-Anwendungen nutzbar 왘 Zusammenarbeit mit anderen Standards (z.B. XPath) 왘 Möglichst schnell verfügbar 왘 Ausführlich beschrieben mit vielen Beispielen Das 2001 verabschiedete Ergebnis erfüllt viele, aber nicht alle der Wünsche der XML-Gemeinde. Insbesondere des Punkt der Einfachheit, Konsistenz und hohen Verständlichkeit wurde klassisch verfehlt. Die XML-Schemadefinition ist wohl eines der komplexesten Dokumente aus der Feder des W3C (manche behaupten, eindeutig zu komplex), so dass vor das eigentliche Dokument (mehrere hundert Seiten in zwei Teilen) eine umfangreiche, mit Beispielen versehene Einführung gestellt werden musste.
XSD ist nicht leicht verständlich
Auch wenn Sie hier einige Beispiele für das Erstellen von Schemas mit den Werkzeugen von Visual Studio .NET finden, ist die Erstellung eines Schemas für den produktiven Einsatz keine leichte Aufgabe. In der Regel werden Sie mit einem bereits vorhandenen Schema arbeiten, für das Instanzdokumente validiert werden müssen. Dennoch sollten Ihnen die Grundlagen der Schema-Erstellung vertraut sein. Die Startseite für die Dokumente, Tools und Verweise auf den Seiten des W3C findet sich unter dem URL http://www.w3.org/XML/Schema. Der eigentliche Standard besteht aus drei Teilen (Einführung, Strukturen, Datentypen): XML Schema Part 0: Primer (http://www.w3.org/TR/xmlschema-0/) XML Schema Part 1: Structures (http://www.w3.org/TR/xmlschema-1/)
t
XML Schema Part 2: Datatypes (http://www.w3.org/TR/xmlschema-2/) An dieser Stelle seien alle XML-Profis gleich vorsorglich um Verzeihung für eine ganze Reihe von Vereinfachungen und Detailunterlassungen gebeten, aber für eine wirklich fundierte Einführung hätte der gesamte Umfang des Buches diesem einen Thema gewidmet werden müssen.
97
Sandini Bib
.NET
8 Validieren von XML
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
Erstellen eines XSD-Schemas Angenommen, für eine Datei mit Wettermeldungen wird ein Schema benötigt, um das Dokument validieren zu können. Aus Gründen der Übersichtlichkeit beschränken wir uns hier auf ein einzelnes, etwas vereinfachtes -Element (die späteren Beispiele verwenden dann wieder die vollständige Datei meteo.xml) Dieses Element besitzt folgenden Aufbau: Athens mostly cloudy 16° Celsius 77% SW, 2 knoten 1024 Listing 8.5: Ein vereinfachtes XML-Fragment
Zur Erstellung eines Schemas für diese XML-Struktur stehen mehrere Möglichkeiten zur Verfügung:
왘 Manuelle Erstellung eines Schemas 왘 Verwenden einer XML-Entwicklungsumgebung 왘 Verwenden des Tools xsd.exe 왘 Benutzen des Schema-Editors von Visual Studio .NET Der rein manuelle Weg ist ideal für Leute, die heute ihren Quellcode immer noch mit vi oder notepad schreiben. Alle anderen (einschließlich des Autors) dürften einen etwas beqeuemeren Weg vorziehen. Die Verwendung einer der auf dem Markt erhältlichen XML-Entwicklungsumgebungen ist zwar recht komfortabel, die Bedienung unterscheidet sich aber deutlich von Produkt zu Produkt und eine Vorstellung der verschiedenen Werkzeuge ist auch nicht Thema des Buches.
Schema-Erstellung mit xsd.exe xsd.exe ist ein wichtiges Tool
98
Wenden wir und also zuerst der Verwendung eines KommandozeilenTools aus dem .NET-Framework zu. Das Programm xsd.exe ist ein Werkzeug zum Erzeuen von Schemata, Datenbeschreibungen und Datendefinitionen per XML-Schema. Die möglichen Parameter entnehmen Sie bitte dem folgenden Kasten, der den Hilfetext wiedergibt, den xsd.exe bei einem Aufruf ohne Parameter ausgibt.
Sandini Bib XSD – der Standard
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
xsd.exe - Utility to generate schema or class files from given source. xsd.exe xsd.exe xsd.exe xsd.exe
.xsd /classes|dataset [/e:] [/l:] [/n:] [/o:] [/uri:] .dll|.exe [/outputdir:] [/type: [...]] .xml [/outputdir:] .xdr [/outputdir:]
- OPTIONS /classes Generate classes for this schema. Short From is '/c'. /dataset Generate sub-classed DataSet for this schema. Short From is '/d'. /element: Element from schema to process. Short From is '/e:'. /language: The language to use for the generated code. Choose from 'CS', 'VB', 'JS' or provide a fully-qualified name for a class implementing System.CodeDom.Compiler.CodeDomProvider. The default is 'CS' (CSharp). Short form is '/l:'. /namespace: The namespace for generated class files. The default namespace is the global namespace. Short form is '/n:'. /nologo Suppresses the banner. /out: The output directory to create files in. The default is the current directory. Short form is '/o:'. /type: Type from assembly to generate schema for. Multiple types may be provided. If no types are provided, then schemas for all types in an assembly are generated. Short form is '/t:'. /uri: Uri of elements from schema to process. Short From is '/u:'. - ARGUMENTS .xsd Name of a schema containing elements to import .dll|exe Name of an assembly containing types to generate schema for. .xml Name of an xml file to infer xsd schema from. .xdr Name of an xdr schema to convert to xsd. Multiple file arguments of the same type may be provided.
99
Sandini Bib
.NET
8 Validieren von XML
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
Für die Erstellung eines XSD-Schemas aus dem XML-Fragment ergänzen wir dieses um die XML-Verarbeitungsanweisung und speichern die Datei unter dem Namen eingabe.xml ab. Anschließend wird xsd.exe mit der folgenden Befehlszeile aufgerufen: xsd.exe eingabe.xml
Das Programm schreibt dann eine Datei eingabe.xsd im gleichen Verzeichnis mit folgendem Inhalt: 01 02 06 07 08 09 12 15 18 21 24 27 28 29 30 33 34 35
100
Sandini Bib XSD – der Standard
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
36 37 38 39 Listing 8.6: Das automatisch erstellte Schema
Nach der XML-Verarbeitungsanweisung in Zeile 1 erfolgt in den Zeilen 2 bis 5 die Definition des -Elementes und der entsprechenden Namensräume (mehr zu Namensräumen erfahren Sie im Kapitel 6). Die Zeilen 6 bis 29 definieren das Element und seine darin enthaltenen Unterelemente, während die Zeilen 30 bis 38 die Verwendung dieses Elementes regeln. Die meisten der Elemente und Attribute dürften selbsterklärend sein. So wird mit Hilfe des Attributes type festgelegt, um welchen Datentyp es sich beim Inhalt dieses Elementes handelt. Sollten Sie sich jetzt fragen, warum dann hier überall der Wert xs:string enthalten ist, dann deshalb, weil aus der XML-Datei selbst keine weiteren Informationen aus den Elementinhalten über deren Datentyp gewonnen werden können. Hier wäre in einem Produktivschema also bereits manuelle Nacharbeit angesagt. Das Attribut minOccurs (zu dem auch ein Pendant maxOccurs existiert) gibt an, wie oft das Element mindestens vorkommen muss (auch hier wäre ein Wert von "1" die richtige Einstellung). Über das Element wird definiert, dass es sich bei diesem Typ um eine »Sequenz« handelt, also alle angegebenen Unterelemente in genau dieser Reihenfolge auftreten müssen (natürlich unter Beachtung von minOccurs und maxOccurs).
Wie oft darf ein Element vorkommen?
Der Schemadesigner von Visual Studio Laden wir dieses Schema nun in den Schema-Editor von Visual Studio .NET. Dazu öffnen Sie die entsprechende XSD-Datei oder fügen Sie diese einem Projekt hinzu. Sollten Sie nicht mit Visual Studio .NET arbeiten, dann ist die Beschaffung eines XML-Editors mit einem Schemadesigner eine sinnvolle Investition. Andernfalls stehen Sie vor der doch etwas mühsamen Aufgabe, eine Schemadatei von Hand zu pflegen. Hier können Sie nun Änderungen anbringen oder die Eigenschaften des Schemas betrachten. Über die Reiter am unteren Rand des Fensters können Sie zwischen dieser Designeransicht und der Darstellung des XML-Codes für das Schema umschalten.
101
Sandini Bib
.NET
8 Validieren von XML
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
Abbildung 8.1: Der XML-Schemadesigner von Visual Studio .NET
Schemas mit Referenzen oder im BabuschkaDesign
Für die Datei meteo.xml soll nun mit Hilfe des Schema-Designers ein XSD-Schema erstellt werden. Visual Studio .NET bietet dafür eine automatisch arbeitende Funktion an, welche ein Schema im so genannten »russian doll design« erzeugt. Laden Sie dazu die Datei meteo.xml in Visual Studio .NET und wählen Sie anschließend aus dem Menü XML die Option SCHEMA ERZEUGEN aus. Wenn Sie nun im automatisch erzeugten Schema auf die XML-Ansicht umschalten, erkennen Sie sofort den Bezug zu den ineinander geschachtelten russischen Babuschka-Puppen. Sie können mit diesem Designer auch komplett neue Schemata manuell erstellen. Die nötigen ersten Schritte dazu sollen am Beispiel eines neu erstellten Schemas für die Datei meteo.xml gezeigt werden. Dazu wird über das DATEI-Menü eine neue Datei vom Typ XML-SCHEMA angelegt, die über die unten gezeigte Toolbox aus der Entwicklungsumgebung mit Inhalt gefüllt wird.
Definition des WurzelElements
Zuerst wird ein Root-Element benötigt. Ziehen Sie dazu ein Element auf die Arbeitsfläche. Geben Sie im linken Eingabefeld den Inhalt weatherdata ein (dies wird der Elementname). Im rechten Eingabefeld (dem Typ des Elements) können wir keinen der vordefinierten einfachen Datentype wie string oder positiveInteger benutzen, sondern geben den Text weatherType ein (diesen Datentypen werden wir gleich definieren). Der Datentyp für den Inhalt des -Wurzelelements besteht aus einer Abfolge von mindestens einem -Element mit den entsprechenden Unterelementen | , , , und .
102
Sandini Bib XSD – der Standard
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
Abbildung 8.2: XSD-Schema im Babuschka-Design
Abbildung 8.3: Die Toolbox für XSD-Schemata
Erstellen Sie dazu über die Toolbox einen complexType mit dem Namen weatherType und einem Element station sowie einen complexType mit dem Namen stationType und den Elementen , , , , und .
103
Sandini Bib
.NET
8 Validieren von XML
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
Der Designerbildschirm sollte (nach dem Speichern, sicherheitshalber) so aussehen:
Abbildung 8.4: Der erste Teil des Schemas ist angelegt.
Erstellen einer choice-Gruppe
Bis jetzt sind alle Typen für die Elementinhalte noch vom Datentyp string und darüber hinaus muss für die Auswahl zwischen und noch Vorkehrung getroffen werden. Dies geschieht durch das Austauschen des Elementes durch eine Gruppe. Löschen Sie das Element , fügen Sie (über die Toolbox oder die rechte Maustaste) eine choice-Group ein (über Ausschneiden und Einfügen bei markierten Elementzeilen können Sie die Reihenfolge ändern, ebenso natürlich über die XML-Ansicht direkt im Code) und geben Sie dort die beiden Elemente und an. Jetzt sollte Ihr Designerbildschirm der Abbildung 8.5 gleichen. Für einzelne Elemente der Datei existieren auch Attribute (beispielsweise das Attribut units für das -Element). Um diese Attribute in das Schema aufzunehmen, muss aus dem einfachen Typ string, der für die Elementinhalte eingetragen war, ein sogenannter komplexer Typ gemacht werden. Das XML-Schema kennt zwei grundlegende Inhaltstypen für ein Schema: simpleType und complexType. Während ein simpleType nur einen der vordefinierten Datentypen aus dem SchemaNamensraum und keine Unterelemente oder Attribute beinhalten darf, kann ein complexType sowohl gemischten Inhalt, Unterlemente oder auch Attribute beinhalten. Für die Definition von Attributen zu einem Element muss also ein complexType her. Dazu stellen Sie den Typ für ein Element, das Attribute haben soll (z.B. ) auf die Einstellung UNNAMED COMPLEXTYPE.
104
Sandini Bib XSD – der Standard
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
Abbildung 8.5: Einfügen der choice-Gruppe für sky und clouds
Daraufhin wird ein neuer complexType unterhalb des -Elements im Schema angelegt, den Sie mit den beiden Attributen units und speed versehen können. Wie Sie sehen, ist die komplett neue Erstellung eines etwas komplexeren Schemas keine leichte Aufgabe. Besser ist meist die Verwendung eines Generators wie xsd.exe und das anschließende Abändern. Wollen Sie einen definierten Typ wiederverwenden, können Sie durch Anklicken mit der rechten Maustaste und die Auswahl TYP GLOBAL DEKLARIEREN eine Deklaration als Nachfolger des -Elements erzeugen und so diesen Typ per Verweis an mehr als einer Stelle wiederverwenden.
Schema-Generierung aus Datentabellen Über den Serverexplorer lässt sich z.B. für ADO.NET-Validierungen ein Schema für eine Datentabelle erzeugen. Wählen Sie dazu aus dem Serverexplorer von Visual Studio .NET eine Tabelle aus und ziehen Sie diese auf ein neues XML-Schema (siehe Abbildung 8.6). Nach dem Ablegen auf der leeren Schemadesigner-Seite wird automatisch ein Schema für die XML-Darstellung der Tabelle erzeugt (siehe Abbildung 8.7).
105
Sandini Bib
.NET
8 Validieren von XML
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
Abbildung 8.6: Der Serverexplorer in Aktion
Abbildung 8.7: Das automatisch erzeugte Schema
106
Sandini Bib XSD – der Standard
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
Hier der Inhalt der erzeugten Schema-Datei: Listing 8.7: Das automatisch erzeugte Tabellenschema
Visual Studio .NET geht dabei davon aus, dass für jede Spalte ein entsprechendes Element in der Datei vorhanden ist und die Tabelle so heißt wie das Wurzelelement. Ist dies nicht der Fall, können Sie mit Hilfe des Schema-Designers dieses Schema jederzeit umbauen, um einzelne Werte beispielsweise in Attribute zu verlagern.
107
Sandini Bib
.NET
8 Validieren von XML
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
Schema aus Klassen erstellen Für die Deserialisierung von Instanzen aus XML-Daten ist es wünschenswert, sicherzustellen, dass der Aufbau der Datei auch alle nötigen Informationen enthält. Dazu wird mit Hilfe des xsd-Tools aus der Klasse ein Schema erstellt, mit dem dann eine Datei validiert werden kann. Dazu verwenden wir die einfache Beispielklasse aus dem Kapitel über die XML-Serialisierung, SimpleDemo. Als Parameter für xsd.exe wird dabei der Name des Assemblies angegeben, in dem der übersetzte Klassentyp enthalten ist, zusammen mit der Option /type: (oder /t:) und dem Namen des Typs, für den das Schema erstellt werden soll. Wird kein Typ angegeben, werden Schemas für alle Typen im Assembly erstellt. xsd assemblyname.exe /T:SimpleDemo
Anschließend wird eine Datei schema0.xsd im Verzeichnis abgelegt, in der ein Schema für den entsprechenden Typ vorhanden ist. xsd generiert Schemadateien
Listing 8.8: Das generierte Schema
Übrigens lässt sich aus einem vorliegenden Schema mit Hilfe von xsd.exe auch eine Klasse erzeugen, so dass Sie einen anderen Weg gehen und für ein erhaltenenes Schema eine C#-Klasse erzeugen lassen können.
108
Sandini Bib XSD – der Standard
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
Verweisen auf ein Schema Im Dokument wird in der Regel noch der Verweis auf dieses Schema eingetragen werden:
t
Listing 8.9: Verweis auf das Schema in der Datendatei
Mit diesen Einträgen wird deklariert, dass das Schema für alle Elemente, die zu keinem Namensraum gehören, in der Datei zu finden ist, auf die das Attribut noNamespaceSchemaLocation verweist. Soll das Schema für Elemente in einem Namensraum angegeben werden, dann wird statt dessen das Attribut SchemaLocation benutzt. Diese Angabe bedeutet noch nicht, dass das Dokument notwendigerweise gültig ist, sondern dass das Dokument eine Validierung gegen dieses Schema überstehen sollte. Die Gültigkeit steht also genau wie bei einer DTD erst nach einem erfolgreichen Durchlauf mit der XmlValidatingReader-Instanz fest.
noNamespaceSchemaLocation
Unterbleibt diese Eintragung des Schemaverweises, wird bei der Validierung mit allen passenden Präfixen für die geladenen Namensräume geprüft.
XSD-Validierung Die Validierung mit Hilfe eines Schemas unterscheidet sich (mit Ausnahme der unterschiedlichen Attribute noNamespaceSchemaLocation und SchemaLocation kaum. Weitere Details zu Namensräumen und ihrer Rolle finden Sie im Kapitel über die Namensräume. Hier ein komplettes Listing mit einer Klasse für die Validierung eines Dokumentes mit Hilfe eines XSD-Schemas. Nachdem hier kein Namensraum definiert wurde, wird beim Aufruf der Methode XmlSchema Collection.Add als erster Parameter ein leerer String (oder String. Empty) übergeben, so dass das als zweiter Parameter angegebene Schema zur Validierung aller Elemente ohne einen Namensraumpräfix benutzt wird. Der restliche Code ist fast der gleiche wie bei einer Validierung mit Hilfe einer DTD.
XmlSchema Collection
// validierung mit hilfe eines xsd-schemas // die namensräume using System; using System.XML; using System.XML.XPath; using System.XML.Schema;
109
Sandini Bib
.NET
8 Validieren von XML
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
namespace SchemaSamples { // die validierungsklasse 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 erzeugen und // mit schema und namensraum-URI fuellen XmlSchemaCollection xsc = new XmlSchemaCollection(); try { // "" bezeichnet standard-namensraum // fuer alle elemente ohne präfix xsc.Add("", @"c:\tmp\meteo.xsd"); } 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);
110
Sandini Bib XSD – der Standard
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
// eintragen eventhandler vrdr.ValidationEventHandler += new ValidationEventHandler(this.ValidationSink); // jetzt keine exception mehr bei fehler while(vrdr.Read()) { } Console.WriteLine(vrdr.Schemas.Count); // ergebnis zurückliefern return bValidated; } } // die testklasse class TestClass { static void Main() { // instanz der demo-klasse erzeugen XSDSchemaDemo sd = new XSDSchemaDemo(); // validieren und ergebnis ausgeben if(sd.Validate(@"c:\tmp\meteo.xml", ValidationType.Schema)) Console.WriteLine("Keine fehler, " + "Dokument gültig."); else Console.WriteLine("Dokument ist nicht gültig."); // den benutzer das konsolenfenster // schliessen lassen Console.WriteLine(); Console.Write("Press a key ...."); Console.ReadLine(); } } } Listing 8.10: Prüfen von XML-Dateien mit einem XSD-Schema
Über die Auflistung Schemas der XmlValidatingReader-Klasse haben Sie Zugriff auf die einzelnen geladenen Schemata. Jeder Eintrag ist eine Instanz einer XmlSchema-Klasse und kann über ein eigenes Obektmodell (das schema object model, SOM) verwaltet werden. Dieses Objektmodell erlaubt den programmgesteuerten Aufbau eines Schemas und ähnelt sehr dem Document Object Model für Instanzdokumente.
111
Sandini Bib
.NET
8 Validieren von XML
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
8.3 Zusammenfassung Validierung ist ein wichtiges Thema für XML-Dokumente, um sicherzustellen, dass für eine Weiterverarbeitung alle benötigten Elemente und Attribute in der definierten Folge und mit den korrekten Datentypen vorhanden sind. Obwohl das .NET-Framework nach wie vor DTDs und XDR-Schemas unterstützt, sollten Sie Ihre Schemas nach Möglichkeit als XSD-Dateien erstellen, um alle Vorteile der XML-Werkzeuge nutzen zu können. Im Web finden Sie eine ganze Reihe von bereits fertigen Schemas, mit denen sich Industriebranchen oder Institutionen auf ein gemeinsames XML-Vokabular geeinigt haben (ein guter Ansatzpunkt ist beispielsweise die XML-Registry unter http://www.xml.org).
112
Sandini Bib
.NET
Essentials
9
XML und Daten
9.1 Einführung Thema dieses Kapitels ist die Behandlung von XML als Datentabellen. Zusammen mit der DataSet-Klasse aus System.Data bietet das XmlData Document aus dem Namensraum System.XML das Beste aus beiden Welten, relationalen Zugriff und XML-Flexibilität. Da es zum umfangreichen Thema ADO.NET einen eigenen Band dieser Reihe geben wird, beschränkt sich dieses Kapitel auf die XML-Seite von ADO.NET und die Kopplung des Zugriffs als relationale Sicht über das DataSet und die DOM-Sicht über ein XmlDataDocument.
Das Beste aus zwei Welten
9.2 DataSet und Co. Das Programmiermodell von ADO.NET bietet zwei grundlegende Möglichkeiten zum Zugriff auf Daten. Einmal die Klasse DataSet als relationale Darstellung der Daten, frei navigierbar, ohne die Notwendigkeit einer Verbindung zur Datenbank und mit allen Möglichkeiten der Datenmanipulation.
DataSet
Diese Mächtigkeit kann allerdings auch zum Problem werden. Bei ADO war der relativ große Overhead des RecordSet-Objektes immer ein Problem, wenn es nur darum ging, einfach eine Abfrage an den Server zu schicken und dann das Resultset über eine ASP-Seite an den Client zurückzupumpen. Nur lesen, nichts ändern, immer vorwärts und wieder raus aus der Datenbank – mit ADO.NET ist das ressourcenschonend möglich. Dazu dient der DataReader, der ähnlich einem so genannten »firehose cursor« die schnellste Möglichkeit für den Datenzugriff bietet.
Schwergewicht RecordSet in ADO
Nachdem Microsoft selbst eine der derzeit leistungsfähigsten Datenbanken anbietet, existiert dafür auch eine eigene Unterstützung über die im Namensraum System.Data.OleDb vorhandene OLE DB-Anbindung hinaus. Der Namensraum System.Data.SqlClient bietet eigene Objekte, die direkt die TDS-Schnittstelle des SQL-Servers nutzen und so den geringstmöglichen Overhead besitzen. Der Nachteil des DataReaders (und damit der Grund, warum er hier nicht weiter besprochen wird) ist die Tatsache, dass der Abruf der Daten nicht in XML erfolgt.
Besonderheit SQLServer
113
Sandini Bib
.NET
9 XML und Daten
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
DataSet erstellen Auch hier wird für die Beispiele wieder eine XML-Datei verwendet, die eine XML-Sicht zweier verknüpfter Tabellen darstellt. Da der Autor seit Jahren Sammler von Single Malt Whiskies ist, fiel die Wahl auf eine kleine Liste von Destillerien und Abfüllungen. Eine DataSet-Instanz ist unabhängig von der für die Füllung verwendeten Technologie immer eine im Speicher gehaltene, gecachte relationale Darstellung der verwendeten Tabellen. In seiner Funktionalität noch am ehesten mit dem alten RecordSet von ADO verwandt, bietet es in Wirklichkeit eine wesentlich höhere Funktionalität und vor allem die Möglichkeit der Beibehaltung von Relationen zwischen Datentabellen auch nach dem Schließen der Datenbankverbindung (was beim »flachgeklopften« RecordSet immer ein Problem war). Anlegen eines DataSets
Bevor wir also XML-Daten speichern, muss zuerst ein DataSet angelegt und mit Inhalt gefüllt werden. Dies geschieht entweder über manuelle Aufrufe zum Hinzufügen von Datenzeilen oder beispielsweise über den Zugriff auf einen SQL-Server. Für alle Leser, die über keinen installierten SQL-Server verfügen, hier der Code für die manuelle Erzeugung des DataSets, der Datentabellen und der Spalten pro Tabelle. Aus dem Code ist so leicht ersichtlich, wie Sie sich ein DataSet erstellen. Weitere Details finden Sie im Band zu ADO.NET. static DataSet BuildDataSet() { // neue leere dataset-instanz DataSet ds = new DataSet("DataSet"); // die tabellen des datasets DataTable tblDist = new DataTable("Distillery"); DataTable tblBott = new DataTable("Bottling"); // aufbauen der ersten tabelle tblDist.Columns.Add("DistilleryID", typeof(System.Int32)); tblDist.Columns.Add("Name", typeof(System.String)); tblDist.Columns.Add("Region", typeof(System.String)); tblDist.Columns.Add("Status", typeof(System.String)); tblDist.PrimaryKey = new DataColumn[] { tblDist.Columns["DistilleryID"] }; // aufbauen der zweiten tabelle tblBott.Columns.Add("Name",
114
Sandini Bib DataSet und Co.
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
typeof(System.String)); tblBott.Columns.Add("Age", typeof(System.Int32)); tblBott.Columns.Add("Distillery", typeof(System.Int32)); tblBott.Columns.Add("Notes", typeof(System.String)); // tabellen zum dataset hinzufügen ds.Tables.Add(tblDist); ds.Tables.Add(tblBott); // relation zwischen den spalten aufbauen // und erzwingen (parameter "true") ds.Relations.Add("BottlingToDistillery", tblDist.Columns["DistilleryID"], tblBott.Columns["Distillery"], true); // das fertige dataset zurückgeben return ds; } Listing 9.1: Programmgesteuerter Aufbau eines DataSets
DataSet aus XSD-Schema Natürlich muss dieses manuelle Aufbauen des DataSets nicht sein, wenn Sie Zugriff auf ein XML-Schema haben, das diese Daten bereits enthält. Dies kann entweder mit Hilfe der Methode WriteXmlSchema durch ein anderes DataSet erstellt, von Hand gebaut oder durch eine XSLT-Transformation entstanden sein. Das passende XSD-Schema für die beiden Tabellen aus dem letzten Beispiel hat den folgenden Inhalt:
WriteXmlSchema
115
Sandini Bib
.NET
9 XML und Daten
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
Listing 9.2: Das XSD-Schema für das Beispiel-DataSet
116
Sandini Bib DataSet und Co.
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
Soll das DataSet mit Hilfe dieses Schemas erstellt werden, reicht ein Methodenaufruf aus:
DataSet ganz einfach
static DataSet DataSetFromSchema(string filename) { DataSet ds = new DataSet(); ds.ReadXmlSchema(filename); return ds; }
Das Schema kann allerdings auch beim Lesen der Daten automatisch hergeleitet oder aus einem in der XML-Datei vorhandenen InlineSchema gelesen werden, wie der nächste Abschnitt und das nächste Listing zeigen werden.
XML-Daten laden Jetzt kann das DataSet mit Daten gefüllt werden. Nachdem das Thema dieses Bandes XML ist, werden wir das DataSet nicht per Datenbankabfrage füllen, sondern aus einer XML-Datei laden, deren Aufbau dem Schema entspricht. Dazu wird die Methode ReadXml benutzt. Anschließend wird eine Liste der Tabellen mit der Anzahl der enthaltenen Zeilen ausgegeben static void Main() { // dataset erstellen aus einem xsd-schema DataSet dsdemo = DataSetFromSchema("datasetdemo.xsd"); // die daten aus der xml-datei einlesen dsdemo.ReadXml("datasetdemo.xml", XmlReadMode.IgnoreSchema); // ausgabe der tabellen Console.WriteLine("Liste der Tabellen"); foreach(DataTable tbl in dsdemo.Tables) { Console.WriteLine("{0,-32} => {1} Zeilen.", tbl.TableName, tbl.Rows.Count); } Console.Write("\nPress a key ...."); Console.ReadLine(); } Listing 9.3: Füllen des DataSets aus einer XML-Datei
117
Sandini Bib
.NET
9 XML und Daten
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
Die Ausgabe der Methode sollte dann wie folgt aussehen:
Abbildung 9.1: Die Ausgabe der Tabllen mit der Zeilenzahl
Der letzte Parameter der Methode ReadXml verdient eine etwas genauere Erläuterung. Mit diesem Parameter XmlReadMode legen Sie fest, wie die Behandlung der Schemadaten erfolgen soll. Folgende Werte sind laut Online-Dokumentation möglich:
t
Membername
Beschreibung
Auto
Standardeinstellung. Führt die am besten geeignete dieser Aktionen aus: Legt XmlReadMode auf DiffGram fest, wenn es sich bei den Daten um ein DiffGram handelt. Legt XmlReadMode auf ReadSchema fest, wenn das Dataset bereits über ein Schema verfügt oder das Dokument ein Inlineschema enthält. Legt XmlReadMode auf InferSchema fest, wenn das Dataset noch kein Schema aufweist und das Dokument kein Inlineschema enthält.
DiffGram
Liest ein DiffGram, wobei die Änderungen aus dem DiffGram auf das DataSet angewendet werden. Die Semantik ist identisch mit der einer Merge-Operation. Wie bei der Merge-Operation werden die RowState-Werte beibehalten. Die Eingabe in ReadXml mit DiffGrams sollte nur unter Verwendung der Ausgabe von WriteXml als DiffGram abgerufen werden.
Fragment
Liest XML-Dokumente, z.B. solche, die durch Ausführen von FOR XML-Abfragen generiert werden, für eine Instanz von SQL Server. Wenn XmlReadMode auf Fragment festgelegt ist, wird der Standardnamespace als Inlineschema gelesen.
IgnoreSchema
Ignoriert alle Inlineschemas und liest Daten in das vorhandene DataSet-Schema. Wenn Daten dem vorhandenen Schema nicht entsprechen, werden sie verworfen (auch die Daten aus anderen für das DataSet definierten Namespaces). Wenn es sich bei den Daten um ein DiffGram handelt, hat IgnoreSchema dieselbe Funktion wie DiffGram.
Tabelle 9.1: Die möglichen Werte für den XmlReadMode
118
Sandini Bib DataSet und Co.
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
Membername
Beschreibung
InferSchema
Ignoriert alle Inlineschemas, leitet das Schema von den Daten her und lädt die Daten. Wenn das DataSet bereits ein Schema enthält, wird das aktuelle Schema durch Hinzufügen neuer Tabellen oder neuer Spalten zu vorhandenen Tabellen erweitert. Eine Ausnahme wird ausgelöst, wenn die hergeleitete Tabelle bereits mit einem anderen Namespace vorhanden ist, oder wenn bei hergeleiteten Spalten ein Konflikt mit vorhandenen Spalten auftritt.
ReadSchema
Liest jedes Inlineschema und lädt die Daten. Wenn das DataSet bereits ein Schema enthält, können diesem evtl. neue Tabellen hinzugefügt werden. Es wird jedoch eine Ausnahme ausgelöst, wenn Tabellen im Inlineschema bereits im DataSet vorhanden sind.
Tabelle 9.1: Die möglichen Werte für den XmlReadMode (Fortsetzung)
XML-Daten schreiben Genauso einfach wie das Laden einer bestehenden Datei ist das Schreiben von XML-Daten möglich. Dazu wird die Methode WriteXml benutzt, bei der über einen Parameter angegeben werden kann, ob nur die Daten oder auch das Schema als Inline-Schema geschrieben werden soll. Möchten Sie nur das Schema allein schreiben, benutzen Sie dazu die Methode WriteXmlSchema.
WriteXml und WriteXmlSchema
static void Main() { // dataset erstellen aus einem xsd-schema DataSet dsdemo = DataSetFromSchema("datasetdemo.xsd"); // die daten aus der xml-datei einlesen dsdemo.ReadXml("datasetdemo.xml", XmlReadMode.IgnoreSchema); // ausgabe der tabellen Console.WriteLine("Liste der Tabellen"); foreach(DataTable tbl in dsdemo.Tables) { Console.WriteLine("{0,-32} => {1} Zeilen.", tbl.TableName, tbl.Rows.Count); } // jetzt xml in eine andere datei schreiben dsdemo.WriteXmlSchema("ausgabe.xsd"); dsdemo.WriteXml("ausgabe.xml", XmlWriteMode.IgnoreSchema);
119
Sandini Bib
.NET
9 XML und Daten
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
Console.Write("\nPress a key ...."); Console.ReadLine(); } Listing 9.4: Schreiben des DataSet-Inhalts in eine Datei
Soweit die kurze Betrachung der DataSet-Klasse unter dem Gesichtspunkt XML und Schema. Als nächstes folgt die XmlDataDocument-Klasse, mit der Sie eine Kombination aus relationalem Zugriff und DOMModell erhalten.
Typisierte DataSets Eine weitere Möglichkeit für den Zugriff auf XML-Daten und relationale Daten ist ein streng typorientiertes DataSet, bei dem der Zugriff nicht über die oben beschriebenen Auflistungen wie Tables erfolgt, sondern durch streng typisierte Namen. Dazu wird mit den Daten aus dem Schema eine eigene Klasse von DataSet abgeleitet und verwendet. Neben dem Vorteil der Fehlervermeidung ist der Code auch lesbarer und schneller, da auf den Zugriff über einen Stringindex verzichtet werden kann. xsd erstellt auch DataSets
Um ein solches DataSet zu erstellen, benötigen Sie zuerst das Schema. Hier wird das in Listing 9.2 gezeigte Schema benutzt. Mit Hilfe des Hilfsprogramms xsd.exe wird daraus eine Dataetklasse erzeugt: xsd /d /l:cs /n:Datasamples datasetdemo.xsd Das Tool erstellt (standardmäßig im gleichen Verzeichnis) eine Datei mit dem Namen datasetdemo.cs. Diese Datei ist mehrere hundert Zeilen lang und enthält den kompletten Code für eine abgeleitete DataSetKlasse mit dem Namen, der im Schema angegeben wurde (hier DemoData).
t
Abhängig davon, ob Sie mit Visual Studio .NET arbeiten oder nicht, ergeben sich nun zwei Wege: In Visual Studio .NET nehmen Sie diese Datei einfach in das Projekt auf und beim nächsten Build wird diese Datei mitübersetzt (achten Sie darauf, den korrekten C#-Namensraum anzugeben, wie oben Datasamples, der C#-Namensraum der Beispieldateien). Ohne Visual Studio .NET lassen Sie den C#-Compiler einfach ein Assembly als Bibliotheks-DLL erzeugen und referenzieren dieses dann: csc /target:library /r:system.dll,system.data.dll datasetdemo.cs Nun können Sie einfach eine Instanz der Klasse erzeugen und damit arbeiten. Das folgende Beispiel (diesmal nicht nur die Methode, sondern der komplette Code) lädt zuerst die Datendatei in diese Klasse, suchst dann darin nach einer bestimmten Datenzeile und fügt in der verbundenen Tabelle eine neue Zeile ein. Anschließend wird die gesamte so erstellte XML-Struktur auf die Konsole ausgegeben.
120
Sandini Bib XmlDataDocument
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
// typisiertes dataset nutzen using System; using System.Data; using System.Data.SqlClient; namespace Datasamples { class DataSetDemo { static void Main(string[] args) { DemoData dd = new DemoData(); try { dd.ReadXml("datasetdemo.xml", XmlReadMode.Auto); } catch(Exception e) { Console.WriteLine("Exception: {0}", e.Message); } DemoData.DistilleryRow row = dd.Distillery.Select("name = 'Bowmore'")[0] as DemoData.DistilleryRow; dd.Bottling.AddBottlingRow("Bowmore Darkest", 17, row , ""); Console.WriteLine(dd.GetXml()); Console.Write("\nPress a key ...."); Console.ReadLine(); } } } Listing 9.5: Streng typisierte DataSets sind leichter lesbar.
9.3 XmlDataDocument Die Klasse XmlDataDocument bietet eine Erweiterung von XmlDocument, um beim Zugriff auf Daten sowohl die Möglichkeiten der DataSetKlasse nutzen zu können als auch den Zugriff über das Document Object Model der XML-Klassen.
121
Sandini Bib
.NET
9 XML und Daten
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
Grundlagen XmlData Document, Methode Load
Nach dem Erstellen einer Instanz kann über die Eigenschaft DataSet auf das integrierte DataSet zugegriffen werden, um beispielsweise ein Schema einzulesen, mit dem das DataSet dann seine Tabellen-Auflistung anlegt. Das folgende Fragment zeigt das Aufbereiten des DataSets über das Einlesen des Schemas, das anschließende Laden der Daten erfolgt allerdings über die Load-Methode der XmlDataDocument-Instanz. XmlDataDocument datadoc = new XmlDataDocument(); datadoc.DataSet.ReadXmlSchema("datasetdemo.xsd"); datadoc.Load("datasetdemo.xml");
Die Klasse hält die Daten in beiden Zugriffspfaden synchron, so dass Sie je nach Bedarf die entsprechende Methode benutzen können, ohne sich Gedanken um den Datenabgleich zwischen relationalem und DOM-Zugriff machen zu müssen.
Spezielle XmlDataDocument-Methoden GetElement FromRow GetRowFrom Element
Über die beiden Methoden GetElementFromRow und GetRowFromElement lassen sich die Daten jeweils einander zuordnen. So können Sie für ein bestimmtes Element die entsprechende Datenzeile anzeigen lassen und umgekehrt. Die folgende Methode arbeitet zuerst mit den DataSet-Methoden und legt nach dem Laden der Daten aus einer XML-Datei einen Primärschlüssel für die Tabelle Distillery fest, um dann über diesen Schlüssel auf eine Datenzeile zuzugreifen. Anschließend wird mit der Methode Get ElementFromRow auf die gleichen Daten, nur über das DOM, zugegriffen. // demo fuer xmldatadocument static void DataDocumentDemo() { XmlDataDocument datadoc = new XmlDataDocument(); datadoc.DataSet.ReadXmlSchema(@"c:\tmp\datasetdemo.xsd"); datadoc.Load(@"c:\tmp\datasetdemo-dataonly.xml"); // zugriff über dataset-methoden DataTable tbl = datadoc.DataSet.Tables["Distillery"]; // primary key fuer tabelle setzen tbl.PrimaryKey = new DataColumn[] { tbl.Columns["DistilleryID"] };
122
Sandini Bib XmlDataDocument
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
// zugriff auf eine zeile über key DataRow row = tbl.Rows.Find(4); // ausgeben mit dataset-methoden Console.WriteLine("\nDatenzeile"); foreach(DataColumn c in tbl.Columns) Console.Write("{0} ", row[c]); Console.WriteLine(); // element zu dieser zeile holen Console.WriteLine("\nXml-Inhalt"); XmlElement elem = datadoc.GetElementFromRow(row); Console.WriteLine("Knotentyp: {0}", elem.NodeType.ToString()); Console.WriteLine(elem.OuterXml); Console.WriteLine(); } Listing 9.6: GetElementFromRow im Einsatz
Wird diese Methode ausgeführt, zeigt die Ausgabe zweimal die gleichen Daten, nur in unterschiedlicher Darstellung (aus Gründen der besseren Lesbarkeit wurde die XMl-Darstellung umbrochen): Datenzeile 4 Ardbeg Kildalton
Active
XML-Inhalt Knotentyp: Element 4 Ardbeg Kildalton Active Listing 9.7: Die Ausgabe der Methode aus Listing 9.6
XML speichern mit XmlDataDocument Auch mit einer XmlDataDocument-Instanz können Sie die Daten wieder in eine XML-Datei schreiben. Hierbei existieren aber zwischen der Methode Save des XmlDataDocuments und der Methode WriteXml des DataSets einige Unterschiede in der Behandlung des XML-Stroms. Dazu eine kleine Methode, mit der zuerst eine XML-Datei geladen und dann mit den beiden Methoden jeweils in eine eigene Datei gespeichert wird.
123
Sandini Bib
.NET
9 XML und Daten
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
static void XmlFormatDemo() { XmlDataDocument datadoc = new XmlDataDocument(); datadoc.DataSet.ReadXmlSchema("datasetdemo.xsd"); datadoc.PreserveWhitespace = true; datadoc.Load("datasetdemo.xml"); datadoc.Save("ausgabe-save.xml"); datadoc.DataSet.WriteXml("ausgabe-writexml.xml"); } Listing 9.8: Schreiben der XML-Daten auf verschiedene Weise
Preserve WhiteSpace
Je nach Einstellung der Eigenschaft PreserveWhitespace für die Instanz von XmlDataDocument (beim Laden des Dokuments) wird nicht signifikanter Leerraum beibehalten oder nicht (die Standardeinstellung ist false). Die Methode WriteXml schreibt dagegen immer nur die signifikanten Daten ohne Leerraum. Hier ein Ausschnitt aus der mit Save geschriebenen Datei, der den in der Originaldatei vorhandenen Leerraum und Nichtdaten-Elemente beibehält: 2 Laphroaig Kildalton Active 3 Lagavulin Kildalton Active
Im Gegensatz dazu enthält die mit der Methode WriteXml geschriebene Datei nur die Elemente, die für den Aufbau des DataSets relevant sind. Auch beim Lesen per ReadXML wird übrigens nur der Dateninhalt gelesen und ebenso Nichtdaten-Elemente und Leerraum entfernt.
124
Sandini Bib Zusammenfassung
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
2 Laphroaig Kildalton Active 3 Lagavulin Kildalton Active
9.4 Zusammenfassung Die zentrale Rolle von XML im .NET-Framework kommt gerade bei ADO.NET deutlich zum Tragen. Als internes Format für die Speicherung von nicht verbundenen DataSets und den Transport von Daten zwischen Maschinen und Prozessen ist XML das zentrale Format. Somit können Sie jede beliebige gültige XML-Datei als Tabelle laden und in Tabellenform bearbeiten oder Datentabellen als hierarchische XML-Daten über eine Firewall hinweg per http versenden. Mit Hilfe der XmlDataDocument-Klasse sind Sie in der Lage, gleichzeitig relational mit ADO.NET-Methoden und über das XML-Datenmodell auf ein und denselben Datenbestand zuzugreifen.
125
Sandini Bib
Sandini Bib
.NET
Essentials
10 XML-Dokumentation C# ist derzeit die einzige Programmiersprache von Microsoft, die eine Möglichkeit zur Erstellung von XML-Kommentaren bietet. Ähnlich wie javadoc bei Java lassen sich somit bei Bedarf automatisch XML-Dateien erzeugen, die anschließend mit Hilfe eines XSLT-Stylesheets in ein beliebiges Format (z.B. HTML) transformiert werden können.
C# als Vorreiter
Zur Erstellung der XML-Datei mit den Dokumentationstags dient die Option /doc: des Compilers bzw. die Menüoption ERSTELLEN VON KOMMENTARWEBSEITEN im Menü EXTRAS, falls Sie Visual Studio .NET verwenden. In den Projekteinstellungen können Sie ebenfalls einen Namen eintragen, unter dem diese XML-Datei automatisch erstellt werden soll.
10.1 XML-Kommentare Erstellen von Kommentaren Ein XML-Kommentar wird durch drei Schrägstriche (also den C#-Kommentar um einen weiteren Schrägstrich ergänzen) eingeleitet. Visual Studio .NET hilft auch hier wieder mit einem Automatismus: falls Sie den Kommantar beispielsweise über einer Klasse oder Methode beginnen, werden automatisch Parameter ausgelesen und ein -Tag erzeugt. Sie können solche XML-Kommentare vor einer Klasse oder ein Mitglied der Klasse (Exception, Delegate, Methode, Eigenschaft) erstellen. XMLKommentare im Code eines Klassenmitglieds oder vor einem Namensraum werden nicht akzeptiert und der Compiler gibt eine Warnung aus:
/// startet einen XML-
Kommentar
t
C:\tmp>csc /doc:Class1.xml Class1.cs Microsoft (R) C#-Compilerversion 7.00.9466 für Microsoft (R) .NET Framework Version 1.0.3705 Copyright (C) Microsoft Corporation 2001. Alle Rechte vorbehalten. Class1.cs(61,4): warning CS1587: Der XML-Kommentar ist auf keinem gültigen Sprachelement abgelegt Listing 10.1: XML-Kommentare dürfen nicht überall stehen.
Damit fehlt leider eine Möglichkeit, eigene XML-Tags an geeigneter Stelle im laufenden Code in die Datei einzubringen. Vielleicht integriert Microsoft diese Funktionalität ja in die nächste Version des Frameworks.
127
Sandini Bib
.NET
10 XML-Dokumentation
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
Zur Verdeutlichung der Einsatzmöglichkeiten zeigt das nachfolgende Listing einen Ausschnitt aus einer Klasse mit XML-Kommentaren: /// /// Diese Methode führt die eigentliche /// Validierung durch. Für jeden Namensraum, /// der benutzt wird, muss mit der Methode /// xsc.Add("urn:meteo-demo", "meteo.xsd") /// das entsprechende Schema geladen und der /// Präfix des Namensraums /// definiert werden. /// /// URL bzw. Dateiname der zu prüfenden Datei /// /// Validierungstyp, entweder Auto, DTD, None, XDR /// oder Schema /// true = Dokument wurde validiert, /// false = Fehler bei Validierung public bool Validate(string url, ValidationType vt) Listing 10.2: XML-Kommentare in Listings
Die Kommentarelemente Folgende Kommentar-Tags sind im Code verwendbar
Kennzeichnet als Code zu formatierenden Text in einem Kommentar. Dieses Element ist für Code im Fließtext des Kommentars gedacht.
Kennzeichnet als Code zu formatierenden Text in einem Kommentar. Dieses Element ist für mehrzeiligen Code und Listingsabschnitte im Kommentar gedacht.
Definiert ein Beispiel in einem Kommentar und wird meist in Kombination mit dem -Element verwendet.
Kommentiert die Exceptions, die eine Klasse auslösen kann und enthält ein Attribut cref mit dem Inhalt einer im Kontext verfügbaren Exception-Klasse. Beispiel: Thrown when...
(Syntax wird vom Compiler überprüft) Beispiel zu include weiter unten
128
Erlaubt den Verweis auf eine andere Datei und die Einbindung von mit einem XPath-Ausdruck übereinstimmenden Elementen aus dieser Datei. Im nächsten Abschnitt finden Sie ein Beispiel zu diesem Tag (Syntax wird vom Compiler überprüft).
Sandini Bib XML-Kommentare
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
Dient der Erstellung von Listen oder Definitionslisten im Kommentar.
Definiert einen Absatz. Damit lassen sich längere Texte innerhalb des - , - oder -Elements besser strukturieren.
Bescheibt einen Parameter, der im Attribut name angegeben wird und in einfachen Anführungszeichen angegeben werden muss. Der Text des Elements ist die Beschreibung des Parameters (Syntax wird vom Compiler überprüft).
Verweist auf einen Parameter im Code. Der Name des Parameters wird als Werts für das Attribut name in doppelten Anführungszeichen angegeben (Syntax wird vom Compiler überprüft).
Dient der Dokumentation des Zugriffs auf ein Mitglied der Klasse und enthält ein Attribut cref, dessen Wert in doppelten Anführungszeichen ein im Kontext ereichbares PermissionSet darstellt (Syntax wird vom Compiler überprüft).
Dient der Beschreibung einer Klasse oder eines Mitglieds der Klasse und kann zusätzlichen, längeren Text enthalten, während eher für eine kurze zusammenfassende Beschreibung ohne Beispiele oder Code gedacht ist.
Beschreibt den Rückgabewert einer Methode.
Definiert einen Link auf ein Mitglied der Klasse, das als Wert für das Attribut cref in doppelten Anführungszeichen angegeben wird (Syntax wird vom Compiler überprüft).
Definiert einen Link auf ein Mitglied der Klasse, das als Wert für das Attribut cref in doppelten Anführungszeichen angegeben wird. dient dazu, diesen Link unter dem Abschnitt "Siehe auch" auszugeben. Wenn Sie die XML-Datei selbst verarbeiten, können Sie und nach eigenen Wünschen aufteilen (Syntax wird vom Compiler überprüft).
Übersichtsartige Beschreibung eines Klassenmitglieds, die von Visual Studio .NET auch für die Anzeige von Quicktipps bei IntelliSense verwendet wird. Für eine längere Beschreibung des Datentyps selbst sollten Sie zusätzlich verwenden.
129
Sandini Bib
.NET
10 XML-Dokumentation
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
Dokumentiert eine Eigenschaft der Klasse. Geben Sie hier Informationen über den Typ und die Verwendung der Eigenschaft an. Darüber hinaus können Sie bei Bedarf eigene XML-Elemente benutzen. Solange es sich bei den verwendeten Konstrukten um wohlgeformtes XML handelt, sind Sie völlig frei in der Verwendung eigener Elemente wie z.B. , oder anderen Elementen, die natürlich auch Attribute beinhalten dürfen.
Einbinden von externen Dateien Mit Hilfe des Elements ist es möglich, den XML-Kommentar von der Codedatei zu trennen und in der Klasse nur einen Verweis auf die Datei mit den Kommentaren anzugeben. Das Element besitzt zwei Attribute, deren Werte in einfachen Anführungszeichen eingeschlossen werden müssen. Das erste Attribut file enthält den Pfadnamen der XML-Datei mit den Kommentaren. Das Attribut path enthält einen XPath-Ausdruck, mit dem innerhalb der Datei die Elemente selektiert werden, die an dieser Stelle eingefügt werden sollen. Der Kommentar in der Klasse DemoClass.cs lautet beispielsweise wie folgt: class Test { /// public static void Main() { } /// public void DoIt() { } } Listing 10.3: Verweis auf Includedateien in XML-Kommentaren
Hier die dazugehörige Datei Democlass.inc mit den Kommentaren The summary for this type.
130
Sandini Bib XML-Kommentare
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
The summary for this other type. Listing 10.4: Eine reine Kommentardatei zur Einbindung per
Damit ist unter Umständen zwar wieder eine Trennung von Code und Dokumentation verbunden, was aus softwaretechnischer Sicht nicht optimal ist. Als Vorteil bietet sich hier aber die Möglichkeit, diesen Teil automatisiert zu generieren oder per XSLT aus einem XML-fähigen Case-Tool herzuleiten. Auch eine Weiterverarbeitung dieser Datei mit anderen Tools wie zur Erstellung von Hilfedateien (HTML Help Workshop) ist denkbar.
Externe Dateien besitzen Vor- und Nachteile
Generell liegt der Vorteil der XML-Kommentare weniger in der Verwendung als im Code vorhandener Dokumentation für den Endanwender der Applikation, sondern in einer Hilfestellung für die Benutzer von Bibliotheken, die mit C# erstellt werden. Hier ist ein Entwickler, der Klassenbibliotheken benutzen möchte, auf eine gute und aktuelle Dokumentation der Schnittstellen angewiesen. Durch die Integration dieser Informationen in die Quellcodedatei wird es dem Programmierer leicht gemacht, diese aktuell und mit der Funktionalität des Codes synchron zu halten. Auch wenn damit ein etwas höherer Aufwand bei der Erstellung des Codes verbunden ist, sollten Sie daran denken, dass Quellcode Dutzende Male öfter gelesen als geschrieben wird und jede Zeile, die das Verständnis beim Lesen erleichtert, effektiv Zeit einspart.
Kommentardatei erstellen Mit Hilfe der Option /doc: wird die XML-Datei mit den Informationen generiert. Alternativ könenn Sie diese Option auch in Visual Studio .NET festlegen, indem Sie in den Eigenschaften des Projektes den Ordner KONFIGURATIONSEIGENSCHAFTEN anklicken und dort in der Seite ERSTELLEN das Feld XML-DOKUMENTATIONSDATEI eintragen. Die generierte XML-Datei löst bei den Elementen, für die der Compiler die Syntax überprüft, die Referenzen auf die Mitglieder der Klasse oder zu anderen Typen auf und generiert dazu eine eindeutige Bezeichnung des Typs, die Sie in einer weiterverarbeitenden Anwendung benutzen könenn.
Datentypen erhalten eine ID
131
Sandini Bib
.NET
10 XML-Dokumentation
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
t
Diese ID ist wie folgt aufgebaut: Der erste Teil der ID-Zeichenfolge kennzeichnet die Art des zu identifizierenden Members durch ein einzelnes Zeichen, gefolgt von einem Doppelpunkt. Die folgenden Membertypen werden verwendet: Zeichen
Beschreibung
N
Namespace
T
Typ: Klasse, Schnittstelle, Struktur, Auflistung, Delegat
F
Feld
P
Eigenschaft, einschließlich Indexer
M
Methode, Konstruktoren, Operatoren usw.
E
Ereignis
!
Fehlerzeichenfolge; der Rest der Zeichenfolge stellt Informationen über den Fehler bereit. Vom C#-Compiler werden Fehlerinformationen für Links, die nicht aufgelöst werden können, erstellt.
Tabelle 10.1: Typkennzeichner für ID-Zeichenfolgen in Kommentaren
Beim zweiten Teil der Zeichenfolge handelt es sich um den voll gekennzeichneten Namen eines Elements, beginnend mit dem NamespaceStammverzeichnis. Der Name des Elements und die umschließenden Typen sowie der Namensraum sind durch Punkte getrennt. Wenn der Name des Elements selbst Punkte enthält, werden sie durch ein Nummernzeichen (#) ersetzt. Es wird vorausgesetzt, dass kein Element ein Nummernzeichen direkt im Namen aufweist. Der voll gekennzeichnete Name des String-Konstruktors würde beispielsweise "System.String.#ctor" lauten. Wenn es sich bei Eigenschaften und Methoden um Argumente der Methode handelt, folgt die in Klammern eingeschlossene Argumentliste. Wenn keine Argumente vorhanden sind, werden keine Klammern verwendet. Die Argumente werden durch Kommas voneinander getrennt. Die Codierung jedes Arguments erfolgt genauso wie die Codierung in einer .NET Framework-Signatur. Hier ein Beispiel für eine generierte Datei mit ID-Zeichenfolgen: Class1 Die Demo-Klasse für die XSD-Validierung. Wird von Main() aufgerufen.
132
Sandini Bib XML-Kommentare
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
privates Feld, gibt an, ob die validierung geglückt ist. Ereignisbehandlungsroutine für die validierung der datei das ereignisauslösende objekt ValidationEventArgs, enthalten die parameter fuer das ereignis Diese Methode führt die eigentliche Validierung durch. Für jeden Namensraum, der benutzt wird, muss mit der Methode xsc.Add("urn:meteo-demo", "meteo.xsd") das entsprechende Schema geladen und der Präfix des Namensraums definiert werden. URL bzw. Dateiname der zu prüfenden Datei Validierungstyp, entweder Auto, DTD, None, XDR oder Schema true = Dokument wurde validiert, false = Fehler bei Validierung Testklasse für die Validierung mit XSD-Schema Main-Methode für die Testklasse, hier geht's los. Der Code
133
Sandini Bib
.NET
10 XML-Dokumentation
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
Console.WriteLine(); Console.Write("Press a key ...."); Console.ReadLine(); dient nur dazu, das konsolenfenster solange offen zu halten, bis der benutzer mit ENTER das fenster schliesst. Listing 10.5: Die erstelle XML-Dokumentationsdatei
Ohne Visual Studio müssen Sie die XSL-Stylesheets selbst entwerfen.
Diese Datei kann nun mit Hilfe eines XSL-Stylesheets und einer Instanz der XslTransform-Klasse in einer nur wenige Zeilen langen Konsolenanwendung in eine optisch ansprechendere Form gebracht oder in ein Updategram zur Speicherung in einer Datenbank konvertiert werden. Auch hier automatisiert Visual Studio .NET mit dem Menüpunkt ERSTELLEN VON KOMMENTARWEBSEITEN im Menü EXTRAS den größten Teil der Arbeit.
10.2 Zusammenfassung Obwohl noch einige Wünsche offen bleiben (wie eigene Tags oder die Einbeziehung von Kommentaren innerhalb des Codes eines Klassenmitglieds) ist die Möglichkeit zur Dokumentation des Codes mit Hilfe von XML ein guter Ansatz und erleichert das automatisierte Erstellen von Dokumentationen im XML-Format deutlich. Über XSL-Stylesheets ist zudem die Transformation der so gewonnen Daten in ein anderes Format sehr leicht möglich.
134
Sandini Bib
.NET
Essentials
11 Ausblick Dieser Band sollte Ihnen als Entwickler einen kompakten Schnellstart in die produktive Arbeit ermöglichen, wie der Titel »Essentials« nahelegt. Für eine komplette und ausführliche Behandlung aller Aspekte von XML in .NET wäre wohl der zehnfache Umfang notwendig. Natürlich fehlt noch eine ganze Menge: das Thema »Web Services und SOAP« nutzt XML als integralen Bestandteil, ADO.NET konnte nur kurz gestreift werden und die XML-Konfigurationsdateien und Sicherheitseinstellungen der Applikationen wären ebenso einen eigenen Band in dieser Reihe wert. XPointer und XQuery sind dem Schwerpunkt .NET-XML zum Opfer gefallen. Auch bei ASP.NET ist nicht alles HTML, sondern auch hier ist XML ein wichtiger Bestandteil. Für ASP.NET Services und ADO.NET möchte ich an dieser Stelle an die entsprechenden Bände meiner Autorenkollegen und -kolleginnen in der .NET-Essentials-Reihe verweisen. Für die anderen Themen sind bereits weitere Ausgaben zu dieser Reihe in Planung. Da das Thema XML in all seinen Facetten in jedem Bereich von .NET erscheint, sei es nun die XML-Abfrage beim SQL-Server 2000 oder eine Sicherheitsrichtlinie für .NET-Applikationen, hat auch Microsoft die immense Bedeutung erkannt und bietet in der Online-Dokumentation zum .NET-Framework und auch in der MSDN-Bibiothek (http:// msdn.microsoft.com) eine ganze Reihe von Grundlagenartikeln und weiterführenden Whitepapers an. Während der Arbeit an einem solchen Buch entwickeln sich auch die Standards im XML-Bereich weiter. XPath 2.0, XML 2.0 und die XQueryLanguage, die XSL Formatting Objects und andere neue Entwicklungen sind teilweise noch nicht einmal als Empfehlung des W3C verabschiedet worden. Im Grafikbereich existieren mit VML und SVG zwei Standards, die miteinander um den Platz als Vektorgrafikstandard für das Web konkurrieren (wobei die persönliche Vorliebe des Autors SVG gilt). Das Thema XML-Schemas (XSD) wird von vielen Stellen als zu komplex und nicht die Vorgaben erfüllend (leichtverständlich, knapp) beurteilt und auch hier sind (beispielsweise mit Relax NG) alternative Entwicklungen im Gange. Dennoch wird sich der XSD-Standard nach Meinung des Autors durchsetzen, da mit Hilfe von »annotated schemas« und den Möglichkeiten von XSD, Vererbungen auszudrücken, eine größere Flexibilität als bei reinen Mustergrammatiken oder DTDs besteht. In den Folgebänden der .NET-Essentials werden diese und weitere Themenkomplexe aufgegriffen. Die Autoren freuen sich darauf, Sie mit weiteren Informationen aus der Praxis in kompakter Form zu versorgen.
135
Sandini Bib
Sandini Bib
.NET
Essentials
Stichwortverzeichnis ! .NET-Namensräume 12 /doc 127
XmlDocument 48 XmlValidatingReader 94 Evaluate 63
A AddNamespace 73 ADO.NET 113 Grundlagen 113 Programmiermodell 113 Attribute, XmlAttributeCollection 43 Attributes, Auflistung 43
G GetElementFromRow 122 GetElementsByID 42 GetRowFromElement 122 Gültigkeit, XML-Dokument 11
C CDATA 10 CreateAttribute 46 CreateNavigator 61 D DataSet 113 erstellen 114 Tables-Auflistung 114, 120 typisiert 120 XML laden 117 XML schreiben 119 Deserialisierung 29 DOCTYPE 91 Document Object Model siehe DOM Dokumentationstags 127 Dokumentbaum 39 DOM 39 Inhalte ändern 44 DTD 89 Alternativen 96 Aufbau 89 intern, extern 91 validieren 92 E Elemente, DOM 40 Entitäten siehe Entities Entities 10 Ereignisbehandlung, XmlDocument 48 Ereignisse Serialisierung 35
I IEnumerable 73 InnerText 41 IXPathNavigable 61 K Knoten, aktueller 16 Knotenmengen, auswählen 62 Knotentypen 17 Kommentar, XML 10, 127 Kommentardatei 131 Kompilieren, XPath-Ausdrücke 64 Kontextknoten, XPath 55 L Leerraum, XML 9 Load, XmlDocument 39 LoadFromXml, komplexe Datentypen 33 LoadXml 40 N Namensräume 11 NodeChanged, XmlDocument 49 NodeInserted, XmlDocument 49 NodeType 40 noNamespaceSchemaLocation 109 O OuterXml 41 P Präfix, Namensräume 67
137
Sandini Bib
.NET
Stichwortverzeichnis
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
Essentials
R ReadXML 124 ReadXml 118 RemoveAll 45, 52 RemoveAttribute 47 RemoveChild 45 S SaveToXml, komplexe Datentypen 33 SchemaLocation 109 SelectNodes, Funktionen 59 SelectSingleNode, Funktionen 59 Serialisierung 27 komplexe Datentypen 33 Steuerung der 32 XML 27 XML-Inhalte 29 SetContext 74 Streaming, XML lesen und schreiben 15 Stylesheet 79 System.Data.OleDb 113 System.Data.SqlClient 113 System.Xml 13 System.Xml.Schema 13 System.Xml.Serialization 13 System.Xml.XPath 13 System.Xml.Xsl 13 T targetNamespace 68 Transform 82 Transformationen, optimieren 86 Typisierte DataSets 120 U Unbekanntes XML, Serialisierung 35 UnknownAttribute, Serialisierung 35 UnknownElement, Serialisierung 35 UnknownNode, Serialisierung 35 URI 67 V Verarbeitungsanweisung, XML 10 W Wohlgeformtheit, XML-Dokument 11 WriteElementString 22 WriteEndDocument 22 WriteEndElement 22 WriteFullEndElement 23
138
WriteStartDocument 22 WriteStartElement 22 WriteStartelement, Namensräume 70 WriteXml 119, 123 WriteXmlSchema 115
X XDR 96 XML Baumstruktur 9 Dateien schreiben 21 Entity 10 Leerraum 9 Lesen von Dateien 15 und Daten 113 XmlAttributeCollection 43 XmlDataDocument 121 Load 122 Methoden 122 XmlDocument 39 XML-Dokumentation 127 XML-Dokumentationsdatei 131 XML-Dokumente 39 Streaming oder DOM 39 validieren 89 XmlElement, Attributbehandlung 47 XML-Kommentare 127 Datei erstellen 131 Dateien einbinden 130 Elemente 128 XML-Namensräume 12, 67 Grundlagen 67 URI 67 Validierung 75 XmlNamespaceManager 72 Namenstabellen 72 XmlNode 40 XmlNodeList 42 XPath 59 XmlReader 15 XmlReadMode 118 XmlResolver 82 XML-Schema 68, 96 attributeFormDefault 75 aus Tabellen 105 elementFormDefault 75 erstellen 98 Grundlagen 89 Namensräume 68 Visual Studio-Designer 101
Sandini Bib ●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
.NET
Essentials
XmlSchemaCollection 75 Add 109 XmlSerializer 36 XML 27 XmlTextReader 16, 23 XmlTextWriter, Namensräume 70 XmlUrlResolver 81 XmlValidatingReader 92 Schemas-Auflistung 111 XmlWriter 15 Namensräume 70 XPath 53 Achsen 55 Ausdrücke 53 Evaluate 63 Funktionen 57 Grundlagen 53 Kontextknoten 55 Namensräume 74 Prädikate 56 Select-Methode 62 XPathNavigator 61 XPath-Abfrage, SelectNodes 59 XPath-Ausdrücke, kompilieren 64 XPathDocument 61 XPathExpression 64 SetContext 74 XPathNavigable 87 XPathNavigator 61 Clone 63 CreateNavigator 61 Matches 61 MoveTo 61 MoveToAttribute 61 MoveToFirst 62
MoveToFirstAttribute 62 MoveToFirstChild 62 MoveToFirstNamespace 62 MoveToId 62 MoveToNamespace 62 MoveToNext 62 MoveToNextAttribute 62 MoveToNextNamespace 62 MoveToParent 62 MoveToPrevious 62 MoveToRoot 62 Navigationsmethoden 61 XPathNodeIterator 62 Current 63 XSD 96 Grundlagen 96 xsd, Klassen erzeugen 30 xsd.exe Datenschema 105 Klassenschema 108 Schema erstellen 98 XSD-Schema 109 DataSet erstellen 115 XSD-Validierung 109 XSL 79 Stylesheet 79 Templates 80 XML-Strukturtransformation 84 XPath-Ausdrücke 80 XSL-Stylesheets 87 Aufbau 80 laden 81 XSLT 79 XslTransform 82 XSL-Transformationen 79 verketten 86
139
Sandini Bib
... aktuelles Fachwissen rund, um die Uhr – zum Probelesen, Downloaden oder auch auf Papier. www.InformIT.de
InformIT.de, Partner von Addison-Wesley, ist unsere Antwort auf alle Fragen der IT-Branche. In Zusammenarbeit mit den Top-Autoren von Addison-Wesley, absoluten Spezialisten ihres Fachgebiets, bieten wir Ihnen ständig hochinteressante, brandaktuelle Informationen und kompetente Lösungen zu nahezu allen IT-Themen.
wenn Sie mehr wissen wollen ...
www.InformIT.de
Sandini Bib
Copyright Daten, Texte, Design und Grafiken dieses eBooks, sowie die eventuell angebotenen eBook-Zusatzdaten sind urheberrechtlich geschützt. Dieses eBook stellen wir lediglich als persönliche Einzelplatz-Lizenz zur Verfügung! Jede andere Verwendung dieses eBooks oder zugehöriger Materialien und Informationen, einschliesslich •
der Reproduktion,
•
der Weitergabe,
•
des Weitervertriebs,
•
der Platzierung im Internet, in Intranets, in Extranets,
•
der Veränderung,
•
des Weiterverkaufs
•
und der Veröffentlichung
bedarf der schriftlichen Genehmigung des Verlags. Insbesondere ist die Entfernung oder Änderung des vom Verlag vergebenen Passwortschutzes ausdrücklich untersagt! Bei Fragen zu diesem Thema wenden Sie sich bitte an:
[email protected] Zusatzdaten Möglicherweise liegt dem gedruckten Buch eine CD-ROM mit Zusatzdaten bei. Die Zurverfügungstellung dieser Daten auf unseren Websites ist eine freiwillige Leistung des Verlags. Der Rechtsweg ist ausgeschlossen. Hinweis Dieses und viele weitere eBooks können Sie rund um die Uhr und legal auf unserer Website
http://www.informit.de herunterladen