Überprüfen von XML-Dokumenten in .NET

Veröffentlicht: 06. Sep 2002 | Aktualisiert: 10. Nov 2004

Von Dan Wahlin

XML ist in der Lage, Daten zu beschreiben und die Datenstruktur während eines Austauschs zu erhalten. Damit wird XML zu einem exzellenten Mechanismus für den Datenaustausch zwischen verteilten Systemen. Aufgrund der erweiterbaren Architektur von XML ist es jedoch sehr wichtig, dass die in den Austausch involvierten unterschiedlichen Systeme und Anwendungen einen gemeinsamen Standard für die Struktur des XML-Dokuments unterstützen, so dass die Daten ordnungsgemäß extrahiert und ihrem Zweck entsprechend genutzt werden können.

 

Zur Homepage von Wrox

Diesen Artikel können Sie dank freundlicher Unterstützung von Wrox auf MSDN Online lesen. Wrox ist ein Partner von MSDN Online.

 

Zur Partnerübersichtsseite von MSDN Online

Verwandte Quellen (in englischer Sprache)

Dieser Artikel ist Teil von Dan Wahlins Profibeiträgen zu "XML Support in ASP.NET"

XML ist in der Lage, Daten zu beschreiben und die Datenstruktur während eines Austauschs zu erhalten. Damit wird XML zu einem exzellenten Mechanismus für den Datenaustausch zwischen verteilten Systemen. Aufgrund der erweiterbaren Architektur von XML ist es jedoch sehr wichtig, dass die in den Austausch involvierten unterschiedlichen Systeme und Anwendungen einen gemeinsamen Standard für die Struktur des XML-Dokuments unterstützen, so dass die Daten ordnungsgemäß extrahiert und ihrem Zweck entsprechend genutzt werden können.

Es gibt ein Verfahren, das sicherstellt, dass bei einem XML-Dokument spezielle Richtlinien beachtet wurden: die "Überprüfung". Dieser zweite Artikel aus der Schriftenreihe XML-Unterstützung in .NET befasst sich mit den unterschiedlichen Alternativen zum Überprüfen von XML-Dokumenten und zeigt im Detail, wie XML-Dokumente im .NET Framework programmgesteuert überprüft werden. Bevor wir jedoch in die Diskussion über die Unterstützung der XML-Überprüfung in .NET einsteigen, wollen wir uns zunächst einmal mit der Entstehung der Dokumente befassen, anhand derer XML überprüft werden kann.

Wie können XML-Dokumente überprüft werden?
Bei der Überprüfung von XML-Dokumenten gibt es zwei grundlegende Alternativen, nämlich DTDs (Dokumenttypdefinitionen) und XML-Schemas. Zwar können sowohl DTDs als auch XML-Schemas zur Überprüfung von XML herangezogen werden, jedoch bergen beide Ansätze auch bestimmte Vor- und Nachteile, wie Sie in den nachfolgenden Abschnitten sehen werden.

Dokumenttypdefinitionen
DTDs gibt es schon seit einigen Jahren. Sie haben sich aus der Basissprache von XML, der so genannten Standard Generalized Markup Language (SGML) entwickelt. Zwar sind DTDs im Vergleich zu vielen anderen Webtechnologien schon relativ alt, jedoch erfüllen sie ihre Aufgabe - das Beschreiben der Struktur, der ein XML-Dokument zu entsprechen hat - ganz hervorragend. DTDs ermöglichen die Definition von wichtigen Teilbereichen eines XML-Dokuments wie beispielsweise von Elementen, Attributen und Entitäten. Ich möchte zwar in diesem Artikel nicht im Detail auf das Erzeugen von DTDs eingehen, jedoch zeigt Listing Listing1 eine einfache DTD, die zum Überprüfen des in Listing Listing2 aufgeführten XML-Dokuments verwendet werden kann.

<?xml version="1.0" encoding="UTF-8"?>
<!ELEMENT Customers (Customer*)>
<!ELEMENT Customer (CompanyName, ContactName, 
  ContactTitle, Address, City, Zip, Phone, Fax)>
<!ATTLIST Customer
      CustomerID CDATA #REQUIRED
>
<!ELEMENT CompanyName (#PCDATA)>
<!ELEMENT Address (#PCDATA)>
<!ELEMENT City (#PCDATA)>
<!ELEMENT ContactName (#PCDATA)>
<!ELEMENT ContactTitle (#PCDATA)>
<!ELEMENT Fax (#PCDATA)>
<!ELEMENT Phone (#PCDATA)>
<!ELEMENT Zip (#PCDATA)>

ListingListing 1. Eine DTD kann zum Überprüfen der Struktur eines XML-Dokuments verwendet werden.

ListingListing 2. Ein XML-Dokument enthält Metadaten, mit denen die im Dokument enthaltenen Daten beschrieben werden. In diesem Beispiel werden Kundendaten mithilfe von unterschiedlichen Elementen und Attributen beschrieben. Das XML-Dokument kann anhand der in Listing Listing1 dargestellten DTD überprüft werden.

<?xml version="1.0"?>
<!--
     The following statement references the DTD 
     in Listing 1
 -->
<!DOCTYPE Customers SYSTEM "Customers.dtd">
<Customers>
       <Customer CustomerID="32">
              <CompanyName>ACME Corp</CompanyName>
              <ContactName>John Doe</ContactName>
              <ContactTitle>Sales Representative</ContactTitle>
              <Address>1234 Anywhere St.</Address>
              <City>Phoenix</City>
              <Zip>85244</Zip>
              <Phone>123-123-1234</Phone>
              <Fax>123-123-1235</Fax>
       </Customer>
</Customers>

Blicken Sie jetzt noch einmal auf Listing Listing1, dann sehen Sie, dass dieses Dokument eine Reihe von Elementdefinitionen enthält und beschreibt, wie jedes der Elemente geschachtelt werden kann. Beispielsweise gibt die DTD an, dass das Stammelement Customers über 0 oder mehr untergeordnete Elemente mit Namen Customer verfügen kann (mit dem Zeichen * festgelegt).

<!ELEMENT Customers (Customer*)>

Die Daten in den runden Klammern werden als das Inhaltsmodell des Elements bezeichnet und legen fest, was sich zwischen den Start- und Endtags des Elements befinden darf.
Das Customer-Element fungiert als übergeordnetes Element der neun unterschiedlichen untergeordneten Elemente, wie in der folgenden DTD-Elementdefinition gezeigt:

<!ELEMENT Customer (CompanyName, ContactName, 
          ContactTitle, Address, City, PostalCode, 
          Country, Phone, Fax)>

Die Reihenfolge, in der diese untergeordneten Elemente im Customer-Element auftreten, ist hierbei von großer Bedeutung. Das Element CompanyName muss als erstes, das Element Fax muss als letztes zusammen mit den anderen Elementen (ContactName, ContactTitle usw.) in der angegebenen Reihenfolge zwischen diesen beiden Elementen auftreten. Das Attribut des Customer-Elements mit Namen CustomerID wird ebenfalls in der DTD definiert, und zwar unter Verwendung des Schlüsselwortes ATTLIST:

<!ATTLIST Customer
      CustomerID CDATA #REQUIRED
>

Diese Definition legt fest, dass das Attribut alphanumerische Zeichen (CDATA = Zeichendaten) umfassen darf und dass es im XML-Dokument enthalten sein muss.

Wenn Sie einmal die Element- und Attributdefinitionen in der DTD in Listing 1 näher betrachten, werden Sie feststellen, dass die Definitionen, obwohl sie für die Festlegung der eigentlichen XML-Dokumentstruktur hervorragend geeignet sind, bei der Beschreibung der unterschiedlichen Datentypen, die in den Elementen und dem einzelnen Attribut enthalten sein dürfen, kläglich versagen.

DTDs unterstützen keine Datentypen wie Ganzzahlen, Gleitkommazahlen, Datumsangaben usw. Tatsächlich können Elemente in DTDs lediglich untergeordnete Elemente, verarbeitete Zeichendaten (PCDATA) oder eine Kombination daraus enthalten. PCDATA sind mit dem primitiven Zeichenfolgendatentyp vergleichbar, der in vielen Programmiersprachen verwendet wird.

Diese mangelnde Unterstützung für Datentypen kann beim Austausch von XML-Dokumenten zwischen verteilten Systemen ein ernstes Problem darstellen, da es keine Möglichkeit gibt, mit DTDs sicherzustellen, dass die empfangenen Daten gültig sind. Da XML-Dokumente verarbeitet und in Datenspeicher wie z. B. relationale Datenbanken importiert werden, die wiederum zwingend bestimmte Datentypen voraussetzen, schlagen Prozesse häufig fehl, wenn unrichtige Daten einfügt oder aktualisiert werden.

Neben der mangelnden Unterstützung für Datentypen weisen DTDs auch noch eine Reihe weiterer Nachteile auf, wie fehlende Unterstützung für XML-Namespaces und Nichtbeachtung der Syntaxregeln, die mit der XML-Spezifikation aufgestellt werden. Trotz dieser Nachteile kommen DTDs aber weiterhin häufig für die Prüfung der Struktur von XML-Dokumenten zum Einsatz, und viele der hierbei verwendeten XML-Parser können ausschließlich anhand von DTDs prüfen.

XML-Schemas
Das W3C hat mit der jüngst veröffentlichten XML-Schemaspezifikation eine neue und wesentlich leistungsfähigere Methode zum Überprüfen von XML-Dokumenten zugänglich gemacht. Die .NET-Plattform bietet eine herausragende Unterstützung für Schemas und legt diese nicht nur bei der Überprüfung von XML-Dokumenten zugrunde, sondern setzt sie auch für die Arbeit mit relationalen Daten ein.

XML-Schemas bieten gegenüber DTDs zahlreiche Vorteile wie die robuste Unterstützung einer Reihe von Datentypen, von XML-Namespaces und XML-Regeln. Zudem können XML-Schemas recht weit reichend angepasst werden, so dass sogar benutzerdefinierte Datentypen generiert werden können. Allerdings sind auch XML-Schemas nicht ohne Nachteile: Sie sind - in einem allerdings noch vertretbaren Rahmen - komplizierter zu entwickeln als DTDs, und sie sind im Allgemeinen umfangreicher, da die XML-Regeln befolgt werden müssen.

Aufgrund der immanenten Komplexität kann sich der vorliegende Artikel nicht im Detail mit dieser Thematik auseinander setzen. In Listing 3 finden Sie jedoch ein Beispiel für ein XML-Schema, das verwendet werden kann, um das XML-Dokument in Listing 4 zu überprüfen.

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" 
    elementFormDefault="qualified">
      <xs:element name="Customers">
            <xs:complexType>
                  <xs:sequence>
                        <xs:element ref="Customer"/>
                  </xs:sequence>
            </xs:complexType>
      </xs:element>      
      <xs:element name="Customer">
            <xs:complexType>
                  <xs:sequence>
                        <xs:element name="CompanyName" 
                            type="xs:string"/>
                        <xs:element name="ContactName" 
                            type="xs:string"/>
                        <xs:element name="ContactTitle" 
                            type="xs:string"/>
                        <xs:element name="Address" 
                            type="xs:string"/>
                        <xs:element name="City" 
                            type="xs:string"/>
                        <xs:element name="Zip" 
                            type="xs:int"/>
                        <xs:element name="Phone" 
                            type="xs:string"/>
                        <xs:element name="Fax" 
                            type="xs:string"/>
                  </xs:sequence>
                  <xs:attribute name="CustomerID" 
                      type="xs:int" use="required"/>
            </xs:complexType>
      </xs:element>
</xs:schema>

Listing 3. XML-Schemas bieten gegenüber DTDs eine Reihe von Vorteilen wie Unterstützung von Datentypen, Namespaces und der XML-Syntaxregeln.

<?xml version="1.0"?>
<Customers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="Customers.xsd">
      <Customer CustomerID="32">
            <CompanyName>ACME Corp</CompanyName>
            <ContactName>John Doe</ContactName>
            <ContactTitle>Sales Representative</ContactTitle>
            <Address>1234 Anywhere St.</Address>
            <City>Phoenix</City>
            <Zip>85244</Zip>
            <Phone>123-123-1234</Phone>
            <Fax>123-123-1235</Fax>
      </Customer>
</Customers>

Listing 4. XML-Dokumente können mithilfe der Attribute noNamespaceSchemaLocation oder SchemaLocation auf ein vorhandenes Schema verweisen. In diesem Beispiel wird das Attribut noNamespaceSchemaLocation verwendet, da das Dokument nicht mit Namespaces arbeitet.

Im Schema in Listing 3 sehen Sie, dass ein bestimmtes Namespacepräfix (xs) verwendet wird, um Elemente und Datentypen zu bezeichnen, die in der Schemadefinition festgelegt sind. Elemente werden mithilfe des element-Tags und Attribute mithilfe des attribute-Tags definiert, wie die beiden nachstehenden Definitionen zeigen:

<xs:element name="CompanyName" type="xs:string"/>
<xs:attribute name="CustomerID" type="xs:int" 
    use="required"/>

Innerhalb eines Elements kann auch das complexType-Element verwendet werden:

<xs:element name="Customers">
    <xs:complexType>
        <xs:sequence>
            <xs:element ref="Customer"/>
        </xs:sequence>
    </xs:complexType>
</xs:element>

Das complexType-Element könnte grob mit den runden Klammern gleichgesetzt werden, die zur Definition des Inhaltsmodells eines Elements in DTDs (siehe Listing 1) verwendet werden. Hierbei entspricht das Starttag von complexType der öffnenden und das Endtag der schließenden Klammer. Innerhalb des complexType-Tags befinden sich mehrere verschiedene untergeordnete Elemente. Im vorstehenden Beispiel wird das sequence-Tag verwendet, um die Reihenfolge der untergeordneten Elemente im übergeordneten Customer-Element festzulegen. Das sequence-Tag ersetzt das in DTDs verwendete Komma.

Um dies zu verdeutlichen, definiert die folgende DTD ein Element mit Namen person, das untergeordnete Elemente mit Namen firstName, lastName und birthDate enthalten kann. Die Reihenfolge, in der die untergeordneten Elemente auftreten müssen, wird festgelegt, indem der Inhalt der Klammer von links nach rechts gelesen wird, wobei das Komma als Trennzeichen fungiert:

<!ELEMENT person (firstName,lastName,birthDate)>
<!ELEMENT firstName (#PCDATA)>
<!ELEMENT lastName (#PCDATA)>
<!ELEMENT birthDate (#PCDATA)>

Diese Definition kann mithilfe der XML-Schemasprache wie folgt umgeschrieben werden:

<xs:element name="person">
    <!--
        The complexType element is similar to the 
        "(" and ")" characters in DTDs 
    -->
    <xs:complexType>
        <!--
           The sequence element is similar to the 
           comma in DTDs as it determines the order
           in which the child elements must appear
        -->
        <xs:sequence>
            <xs:element name="firstName" type="xs:string" />
            <xs:element name="lastName" type="xs:string" />
            <xs:element name="birthDate" type="xs:date" />
        </xs:sequence>
    </xs:complexType>
</xs:element>

Obwohl die Schemaversion wesentlich ausführlicher ist als die DTD-Version, haben Schemas den großen Vorteil, dass zahlreiche Datentypen verwendet werden können, wie in Abbildung 1 gezeigt.

Bild01

Abbildung 1. Die XML-Schemaspezifikation definiert viele unterschiedliche Datentypen, die zum Überprüfen der in einem XML-Dokument enthaltenen Daten verwendet werden können.

Die in Abbildung 1 aufgeführten Datentypen stellen gegenüber der extrem begrenzten Anzahl möglicher Datentypen in DTDs eine enorme Verbesserung dar. Anstatt das Zip-Element einfach als vom Datentyp PCDATA zu definieren, können Sie nun sicherstellen, dass die Daten innerhalb dieses Elements ganzzahlig sind:

<xs:element name="Zip" type="xs:int" />

Datumsangaben können zudem darauf überprüft werden, ob ein gültiges Datum verwendet wurde. Beispielsweise muss das im vorigen Beispiel gezeigte BirthDate-Element ein korrektes Datum enthalten, damit es als gültig betrachtet wird, da das type-Attribut den Wert xs:date enthält. Der folgende Wert wäre nach der Elementdefinition im Schema nicht korrekt (beachten Sie, dass das Format für gültige Daten auf Jahr-Monat-Tag festgelegt wurde):

<BirthDate>2002-02-30</BirthDate>

Möglicherweise müssen Sie die in Abbildung 1 gezeigten Schematypen in bestimmten Situationen einschränken oder erweitern. Beispielsweise könnte das Zip-Element aus Listing 4 Daten enthalten, deren Format fünf Ziffern gefolgt von einem Bindestrich (-) und weiteren vier Ziffern (zip+4) beinhaltet. Diesen benutzerdefinierten Datentyp können Sie mithilfe des simpleType-Elements generieren:

<xs:simpleType name="stZipCode">
      <xs:restriction base="xs:string">
            <xs:pattern value="\d{5}-\d{4}" />
      </xs:restriction>
</xs:simpleType>

Beachten Sie, dass im value-Attribut des xs:pattern-Elements ein regulärer Ausdruck verwendet wird. Dadurch werden Flexibilität und Steuerungsmöglichkeiten für die in einem Element oder Attribut enthaltenen Daten erheblich verbessert. Um dem Zip-Element das Format simpleType zuzuweisen, kann die folgende Syntax verwendet werden:

<xs:element name="Zip" type="stZipCode" />

Mit XML-Schemas können Struktur und Daten eines XML-Dokuments recht sorgfältig geprüft werden, um festzustellen, ob das Dokument für den Einsatz in einer Anwendung geeignet ist. Der Themenkreis XML-Schemas ist zwar noch wesentlich umfangreicher, als dieser Artikel wiedergeben kann, doch haben Sie hiermit schon einige der wesentlichen Features kennen gelernt, die für das Überprüfen von XML bereitstehen. Im Folgenden wollen wir uns daher mit der programmgesteuerten Überprüfung von XML-Dokumenten in .NET befassen.

Überprüfen von XML auf der .NET-Plattform
Nach diesem Überblick über die beiden wichtigsten Methoden zum Überprüfen von XML wollen wir uns nun mit der programmgesteuerten Überprüfung anhand einer DTD oder eines Schemas auf der .NET-Plattform befassen. Wenn Sie einen Blick in den Namespace System.xml werfen, sehen Sie zwei Klassen, die für diesen Zweck herangezogen werden können, nämlich XmlTextReader und XmlValidatingReader. Beide Klassen repräsentieren ein "forward-only, cursor-style model" und können einen Strom von XML-Token erzeugen und lesen.

Die XmlTextReader-Klasse kann zwar zum schnellen und effizienten Lesen von XML verwendet werden, kann XML jedoch nur dann anhand einer DTD oder eines Schemas überprüfen, wenn sie zusammen mit der XmlValidatingReader-Klasse eingesetzt wird. Der vorliegende Artikel enthält einen Überblick über die Grundlagen der Arbeit mit der XmlTextReader-Klasse; weitere Einzelheiten über deren Einsatz werden in späteren Artikeln folgen.

Bevor Sie sich mit einem Beispiel zur Überprüfung von XML befassen, müssen Sie wissen, wie die Klassen XmlTextReader und XmlValidatingReader zusammenarbeiten. Zunächst muss der Konstruktor von XmlTextReader aufgerufen werden, und ein Pfad zu dem zu prüfenden XML-Dokument muss übergeben werden. Die Klasse verfügt über unterschiedliche Konstruktorversionen, für unsere Zwecke kann jedoch nur der Folgende verwendet werden:

[C#]
public XmlTextReader(
   string url
);

Nachdem das XmlTextReader-Objekt instantiert und das zu verarbeitende und zu prüfende XML-Dokument geladen wurde, kann das XmlTextReader-Objekt in den Konstruktor von XmlValidatingReader geladen werden:

XmlValidatingReader vReader = new XmlValidatingReader(reader);

Bevor die Read()-Methode von XmlValidatingReader aufgerufen und das XML-Dokument verarbeitet wird, muss dessen ValidationEventHandler-Ereignis mit einem ValidationEventHandler-Delegaten verknüpft werden, der sich im System.Xml.Schema-Namespace befindet.

Falls Sie noch nicht mit Delegaten vertraut sind: Sie ermöglichen das Verbinden von Ereignissen mit Ereignishandlermethoden. Auf diese Weise können sämtliche Probleme, die während des Überprüfungsprozesses auftreten, an einen zentralen Ereignishandler übergeben werden, der eine Fehlermeldung erzeugt, die Protokollierung oder auch andere Aufgaben übernimmt. Der nachstehende Code zeigt, wie XmlValidatingReader in C# mit einem Ereignishandler verbunden wird:

vReader.ValidationEventHandler += 
    new ValidationEventHandler(ValidationCallBack);

Die ValidationType-Eigenschaft von XmlValidatingReader sollte zudem auf einen gültigen Enumerationswert festgelegt werden. Zu den gültigen Werten gehören ValidationType.Auto, ValidationType.DTD, ValidationType.Schema, ValidationType.None und ValidationType.XDR. Wenn Sie bereits wissen, dass die Überprüfung anhand eines XML-Schemas erfolgen soll, können Sie XmlValidatingReader mit dem folgenden Code angeben, dass ein Schema verwendet wird:

vReader.ValidationType = ValidationType.Schema;

Listing 5 enthält ein vollständiges Beispiel aller erforderlichen Schritte zum Überprüfen eines XML-Dokuments anhand eines Schemas. Dieser Beispielcode steht auch in der Datei ValidationSampleSchema.aspx.cs zum Download bereit.

//Assume document is valid to start
protected bool valid = true;
private void Page_Load(object sender, 
  System.EventArgs e) {
    string xmlPath = 
        Server.MapPath("XML/Customers(Schema).xml");
    XmlTextReader reader = null;
    XmlValidatingReader vReader = null;
    try {
        //Load XML into XmlTextReader
        reader = new XmlTextReader(xmlPath);
        //Load XmlTextReader into XmlValidatingReader
        vReader = new XmlValidatingReader(reader);
        //Set the validation type
        vReader.ValidationType = ValidationType.Schema;
        //Hook up the XmlValidatingReader's 
        //ValidationEventHandler to a call
        //back method named DTDCallBack
        vReader.ValidationEventHandler += 
            new ValidationEventHandler(
                this.ValidationCallBack);
        //Read through the XML document by calling 
        //the Read() method
        while (vReader.Read()) {}
        //Check boolean field named valid (located at top of code)
        if (this.valid) 
            this.lblOutput.Text = 
                "Validation was Successful!";
    }
    catch {}
    finally {
        //Close readers
        vReader.Close();
        reader.Close();
    }
}
public void ValidationCallBack(object sender, 
  ValidationEventArgs args ) {
    this.lblOutput.Text = "Validation failed!  " +
        "Error is: " + args.Message;
    this.valid = false;
}

Listing 5. Mit der Kombination der Klassen XmlTextReader und XmlValidatingReader können XML-Dokumente anhand von DTDs und XML-Schemas überprüft werden. Das Beispiel zeigt die Überprüfung anhand eines XML-Schemas.

Wenn bei der Überprüfung des XML-Dokuments ein Fehler auftritt, wird die ValidationCallBack-Methode aufgerufen und ein ValidationEventArgs-Objekt als Parameter übergeben. Dieses Objekt bietet mehrere wichtige Eigenschaften, die für den Zugriff auf Fehlerinformationen wie Exception, Message und Severity verwendet werden können. Die Exception-Eigenschaft gibt ein XmlSchemaException-Objekt zurück, das (über die Eigenschaften LineNumber und LinePosition) für den Zugriff auf die Zeilen- und Spaltenposition verwendet werden kann, an der der Fehler im XML-Dokument aufgetreten ist. Dies erweist sich insbesondere bei der Fehlersuche als nützlich und kann auch bei der Protokollierung verwendet werden.

Dynamisches Zuweisen von Schemas zu XML-Dokumenten
Listing 5 zeigt, wie ein XML-Dokument überprüft wird, das sich unter Verwendung des noNamespaceSchemaLocation-Attributs auf ein bestimmtes Schema bezieht (siehe auch Listing 4.) Wie müssten Sie jedoch vorgehen, um ein XML-Dokument anhand eines Schemas zu überprüfen, auf das im XML-Dokument nicht verwiesen wird? Dies kann mithilfe der Schemas-Eigenschaft von XmlValidatingReader erfolgen. Diese Eigenschaft kann eine Listing von XmlSchema-Objekten enthalten, die mithilfe der Auflistungsmethode Add() hinzugefügt werden können. Die Add()-Methode ist überladen und kann eine Vielzahl von Eingabeparametern von Zeichenfolgen bis XmlSchema-Objekten aufnehmen (weitere Einzelheiten dazu finden Sie im SDK). Der folgende Code zeigt, wie das in Listing 3 gezeigte Schema der Listing hinzugefügt wird, so dass das XML-Dokument dynamisch anhand des Schemas überprüft werden kann.

vReader.Schemas.Add(null,Server.MapPath("Schemas/Customers.xsd"));

Dieser Code gibt an, dass im Schema kein targetNamespace verwendet wird (targetNamespace ist ein Attribut, das im XML-Dokument für den Verweis auf einen Namespace verwendet werden kann) und legt den physischen Pfad zum Schema fest. Der vollständige Code dieses Beispiels kann als Datei (ValidationExampleDynamicSchema.aspx.cs) gedownloadet werden. Obwohl im Beispiel ein XML-Schema verwendet wird, können ebenso gut auch XDR-Schemas (XML-Data Reduced) verwendet werden.

Erweitern von Entitäten mit "XmlValidatingReader"
Vor dem Abschluss unseres Exkurses zum Thema Überprüfen von XML-Dokumenten wollen wir noch einige weitere nützliche Einsatzmöglichkeit von XmlValidatingReader betrachten, die nicht mit der Überprüfung zusammenhängen. Obwohl DTDs in der Regel zum Überprüfen von XML-Dokumenten verwendet werden, können sie auch Entitätendefinitionen enthalten. Eine Entität ist einfach ein Platzhalter für häufig verwendete Daten und kann mit dem Einschließen von Dateien, Makros, Variablen usw. verglichen werden. Entitäten können innerhalb der DTD mithilfe des Schlüsselworts ENTITY wie nachstehend gezeigt definiert werden:

<!ENTITY address "1234 Anywhere St.">

Nach der Definition kann ein Entitätenverweis an mehreren Stellen innerhalb des XML-Dokuments hinzugefügt werden, indem dem Namen der Entität ein kaufmännisches Und-Zeichen (&) voran- und ein Semikolon (;) nachgestellt wird:

<Address>&address;</Address>

Nach der Verarbeitung wird die Entität im XML-Dokument "erweitert", so dass die korrekten Daten (1234 Anywhere St. in diesem Beispiel) wie nachstehend gezeigt in der XML-Datei positioniert werden:

<Address>1234 Anywhere St.</Address>

Diese Erweiterung der Entität setzt die Verwendung von XmlValidatingReader voraus, obwohl die DTD möglicherweise nur zur Definition von Entitäten und nicht zur Definition der Elemente und Attribute verwendet wird, die das XML-Dokument enthalten kann. Listing 7 zeigt den erforderlichen Code zum Erweitern der in einer DTD definierten Entitäten, auf die in einem XML-Dokument verwiesen wird. Der Code umfasst eine Reihe von Kommentaren, die erläutern, was im jeweiligen Schritt passiert.

private void Page_Load(object sender,System.EventArgs e) {
    Response.ContentType = "text/xml";
    XmlTextReader reader = null;
    XmlValidatingReader vReader = null;
    try  {
        reader = 
        new XmlTextReader(
            Server.MapPath("XML/Customers(Entities).xml"));
        vReader = new XmlValidatingReader(reader);
        //Set ValidationType to none since we don't
        //want to validate but do want to expand entities
        vReader.ValidationType = ValidationType.None;
        //Set EntityHandling property to ExpandCharEntities
        //so that &address; gets expanded
        vReader.EntityHandling = 
            EntityHandling.ExpandCharEntities;
        //Load XmlValidatingReader into XmlDocument and 
        //then write out the contents to the web page
        //to show that the entities were indeed expanded
        XmlDocument doc = new XmlDocument();
        doc.Load(vReader);
        doc.Save(Response.Output);
    }
    catch {}
    finally {
        //Close readers
        reader.Close();
        vReader.Close();
    }
}

Listing 7. Obwohl XmlValidatingReader normalerweise zum Überprüfen von XML-Dokumenten verwendet wird, kann die Klasse auch nur zum Erweitern von Entitäten herangezogen werden. Den vollständigen Code dieses Listings finden Sie in der Datei ExpandEntities.aspx.cs.

Entwickeln eines wiederverwendbaren Überprüfungsobjekts
Da sich das Verfahren zum Überprüfen von XML-Dokumenten im Großen und Ganzen wiederholt, kann ein wiederverwendbares Objekt entwickelt werden, das viele der zum Überprüfen von XML anhand von DTDs oder Schemas benötigten Funktionen einschließt. Listing 8 fasst viele der unterschiedlichen Konzepte zusammen, die in diesem Artikel bereits vorgestellt wurden, um eine Klasse mit Namen Validator zu erzeugen. Eine funktionierende Version der Validator-Klasse finden Sie unter http://www.xmlforasp.net/content.aspx?content=SchemaValidator.

Zwar kann sich dieser Artikel nicht im Detail mit der Validator-Klasse befassen; aber ich möchte Ihnen dennoch die fortschrittlichen Leistungsmerkmale der Klasse zur XML-Überprüfung, z. B. die Verwendung der XmlParserContext-Klasse zur dynamischen Zuweisung von DTDs, nicht vorenthalten. Die Klasse bietet zudem auch Protokollierungsfunktionen, so dass Überprüfungsfehler einfach nachverfolgt werden können.

public class Validator { 
    bool _valid;      //Track if XML is valid
    bool _logError;   //Track if we log any errors
    string _logFile;  //Track logfile location
    string _validationErrors = String.Empty;
    XmlTextReader xmlReader = null;
    XmlValidatingReader vReader = null;
    //The Validation() method accepts the XML to validate, a schema
    //collection (if needed), an array containing info needed to
    //dynamically assign a DTD, a Boolean indicating if any errors
    //should be logged, and the path to the log file.  It returns
    //a custom ValidationStatus object
    public ValidationStatus Validate(object xml,
       XmlSchemaCollection schemaCol, string[] dtdInfo,
       bool logError,string logFile) {
        _logError = logError;
        _logFile = logFile;
        _valid = true;
        try {
            //Determine how XML document to validate was passed
            if (xml is StringReader) xmlReader = 
                new XmlTextReader((StringReader)xml);
            if (xml is String)  xmlReader = 
                new XmlTextReader((String)xml);
            //Handle dynamically adding DTD reference
            if (dtdInfo != null && dtdInfo.Length > 0) {
                //Use XmlParserContext to assign DTD root name plus
                //DTD definitions
                XmlParserContext context = 
                    new XmlParserContext(null,null,dtdInfo[0],
                        "",dtdInfo[1],"",dtdInfo[1],"",
                        XmlSpace.Default);
                xmlReader.MoveToContent();
                vReader = 
                    new XmlValidatingReader(xmlReader.ReadOuterXml(),
                        XmlNodeType.Element,context);
                vReader.ValidationType = ValidationType.DTD;
            } else { //Handle other cases
                vReader = new XmlValidatingReader(xmlReader);
                vReader.ValidationType = ValidationType.Auto;
                if (schemaCol != null) {
                    vReader.Schemas.Add(schemaCol);
                }
            }
            vReader.ValidationEventHandler  += 
               new ValidationEventHandler(this.ValidationCallBack);
            // Parse through XML
            while (vReader.Read()){}
        } catch  {
            _valid = false;
        } finally {  //Close our readers
            if (xmlReader != null) {
                xmlReader.Close();
            }
            if (vReader != null) {
                vReader.Close();
            }
        }
        ValidationStatus status = new ValidationStatus();
        status.Status = _valid;
        status.ErrorMessages = _validationErrors;
        return status;
    }
    private void ValidationCallBack(object sender, 
        ValidationEventArgs args){
        _valid = false;  //hit callback so document has a problem
        DateTime today = DateTime.Now;
        StreamWriter writer = null;
        try {
            if (_logError) {
                writer = 
                    new StreamWriter(_logFile,true,Encoding.ASCII);
                writer.WriteLine("Validation error in XML: ");
                writer.WriteLine();
                writer.WriteLine(args.Message + " " + 
                    today.ToString());
                    writer.WriteLine();
                if (xmlReader.LineNumber > 0) {
                    writer.WriteLine("Line: "+ xmlReader.LineNumber + 
                                     " Position: " + 
                                     xmlReader.LinePosition);
                }
                writer.WriteLine();
                writer.Flush();
            } else {
                _validationErrors = args.Message + " Line: " +
                                    xmlReader.LineNumber + " Column:" +
                                    xmlReader.LinePosition + "\n\n";
            }
        }
        catch {}
        finally {
            if (writer != null) {
                writer.Close();
            }
        }
    }
} 
public struct ValidationStatus {
    public bool Status;
    public string ErrorMessages;
}

Listing 8. Die Validation-Klasse umfasst mehrere unterschiedliche Leistungsmerkmale der XML-Überprüfung auf der .NET?Plattform (Validator.cs).

Listing 9 zeigt, wie die Validator-Klasse zum Generieren eines Onlineüberprüfungstools genutzt werden kann, das es dem Endbenutzer ermöglicht, je ein XML-Dokument und ein Schema in zwei Textfelder einzugeben. Alle Fehler, die bei der Überprüfung des XML-Dokuments erkannt werden, werden zurückgemeldet.

private void btnSubmit_Click(object sender, System.EventArgs e) {
    try {
        XmlDocument doc = new XmlDocument();
        //Load XML document
        doc.LoadXml(this.txtXml.Text);
        //Load XML Schema
        XmlSchema schema = XmlSchema.Read(
            new StringReader(this.txtSchema.Text),null);
        XmlSchemaCollection schemaCol = new XmlSchemaCollection();
        schemaCol.Add(schema);
        Validator validator = new Validator();
        ValidationStatus status = validator.Validate(
            new StringReader(doc.OuterXml),schemaCol,null,false,null);
        if (status.Status) {
            this.lblStatus.ForeColor = Color.Navy;
            this.lblStatus.Text = "Validation of XML was SUCCESSFUL!";
        } else {
            this.lblStatus.ForeColor = Color.Red;
            this.lblStatus.Text = "Validation of the XML Document " +
                 failed! Error message(s):<p /> " + 
                 status.ErrorMessages;
        }
    }
    catch (Exception exp) {
        this.lblStatus.ForeColor = Color.Red;
        this.lblStatus.Text = exp.Message;
    }
}

Listing 9. Mithilfe der in Listing 8 dargestellten Validator-Klasse können XML-Dokumente einfach anhand von DTDs oder Schemas überprüft werden (SchemaValidator.aspx).
Abbildung 2 zeigt die Eingabeschnittstelle des Tools:

Bild02

Abbildung 2. Das Validator-Tool ist ein Onlinetool, das es Benutzern ermöglicht, XML-Dokumente anhand von Schemas zu prüfen.

Zusammenfassung
Das Überprüfen von XML-Dokumenten ist ein wichtiger Bestandteil des Datenaustauschs. Zwar können XML-Dokumente sowohl mit DTDs als auch mit XML-Schemas überprüft werden, dennoch bieten Schemas im Vergleich mehr Vorteile wie beispielsweise Unterstützung für Namespaces und Datentypen.

Die .NET-Plattform enthält mehrere unterschiedliche Klassen, mit denen sich XML-Dokumente anhand von DTDs oder XML-Schemas überprüfen lassen. Wenn Sie diese Klassen und ihre Vorteile gezielt verwenden, ist sichergestellt, dass XML-Daten gültig sind und potenzielle Fehler vermieden werden, bevor die Daten mit anderen Teilen einer Anwendung in Kontakt kommen.