Erweiterbare Schemas

(Auszug aus "XML Schema" von Eric van der Vlist)

Die Erweiterbarkeit eines Schemas wird wesentlich beeinflußt durch seinen Stil, durch die Auswahl, welche Komponenten (Elemente und Attribute, Element- und Attributgruppen sowie einfache und komplexe Typen) global sein sollen, durch die Verwendung der Attribute final und fixed und durch die optionale Verteilung dieser Komponenten über mehrere Schema-Dokumente. Diese drei Faktoren sollten wir uns ansehen.

Globale Komponenten

Ein einfaches Beispiel ist oft besser als eine lange Erklärung. Um die Unterschiede zwischen verschiedenen Schema-Stilen zu veranschaulichen, nehmen wir deswegen einige Beispiele aus unserer Bibliothek und untersuchen Elemente komplexen und einfachen Typs sowie Attribute.

Elemente

Betrachten wir die Definition des book-Elements am Beispiel unserer Bibliothek. Wir haben vier verschiedene elementare Arten, dieses Element zu definieren, und sie validieren alle die gleiche Menge von Instanzelementen, jedoch nicht die gleiche Menge von Instanzdokumenten, denn wenn ein Element global sichtbar ist, kann es als Dokumentenelement verwendet werden. Wir können einen Puppe-in-der-Puppe-Entwurf verwenden und das book-Element und seinen Typ lokal innerhalb des library-Elements definieren (ich habe denselben Puppe-in-der-Puppe-Entwurf für die Kindelemente von book gewählt, um das Schema kurz zu halten, da wir uns für dieses Beispiel auf die Definition von book konzentrieren wollen):

<xs:element name="library">
   <xs:complexType>
      <xs:sequence>
         <xs:element name="book" maxOccurs="unbounded">
            <xs:complexType>
               <xs:sequence>
                  <xs:element ref="isbn"/>
                  <xs:element ref="title"/>
                  <xs:element ref="author" minOccurs="0" maxOccurs="unbounded"/>
                  <xs:element ref="character" minOccurs="0" maxOccurs="unbounded"/>
               </xs:sequence>
               <xs:attribute ref="id"/>
               <xs:attribute ref="available"/>
            </xs:complexType>
         </xs:element>
      </xs:sequence>
   </xs:complexType>
</xs:element>

Wir können auch ein globales book-Element definieren und dieses im Inhaltsmodell unserer Bibliothek referenzieren:

<xs:element name="book">
   <xs:complexType>
      <xs:sequence>
         <xs:element ref="isbn"/>
         <xs:element ref="title"/>
         <xs:element ref="author" minOccurs="0" maxOccurs="unbounded"/>
         <xs:element ref="character" minOccurs="0" maxOccurs="unbounded"/>
      </xs:sequence>
      <xs:attribute ref="id"/>
      <xs:attribute ref="available"/>
   </xs:complexType>
</xs:element>
<xs:element name="library">
   <xs:complexType>
      <xs:sequence>
         <xs:element ref="book" maxOccurs="unbounded"/>
      </xs:sequence>
   </xs:complexType>
</xs:element>

Der dritte klassische Weg besteht darin, einen komplexen Typ bookType für das Inhaltsmodell unseres book-Elements zu definieren (ich hätte den Typ auch book nennen können, finde bookType aber weniger verwirrend):

<xs:complexType name="bookType">
   <xs:sequence>
      <xs:element ref="isbn"/>
      <xs:element ref="title"/>
      <xs:element ref="author" minOccurs="0" maxOccurs="unbounded"/>
      <xs:element ref="character" minOccurs="0" maxOccurs="unbounded"/>
   </xs:sequence>
   <xs:attribute ref="id"/>
   <xs:attribute ref="available"/>
</xs:complexType>
<xs:element name="library">
   <xs:complexType>
      <xs:sequence>
         <xs:element name="book" type="bookType" maxOccurs="unbounded"/>
      </xs:sequence>
   </xs:complexType>
</xs:element>

Schließlich können wir eine Gruppe definieren, die unser book-Element enthält:

<xs:group name="bookGroup">
   <xs:sequence>
      <xs:element name="book">
         <xs:complexType>
            <xs:sequence>
               <xs:element ref="isbn"/>
               <xs:element ref="title"/>
               <xs:element ref="author" minOccurs="0" maxOccurs="unbounded"/>
               <xs:element ref="character" minOccurs="0" maxOccurs="unbounded"/>
            </xs:sequence>
            <xs:attribute ref="id"/>
            <xs:attribute ref="available"/>
         </xs:complexType>
      </xs:element>
   </xs:sequence>
</xs:group>
<xs:element name="library">
   <xs:complexType>
      <xs:sequence>
         <xs:group ref="bookGroup" maxOccurs="unbounded"/>
      </xs:sequence>
   </xs:complexType>
</xs:element>

Diese vier grundlegenden Stile können natürlich miteinander kombiniert werden. Ein extremes Beispiel sähe so aus:

<xs:complexType name="bookType">
   <xs:sequence>
      <xs:element ref="isbn"/>
      <xs:element ref="title"/>
      <xs:element ref="author" minOccurs="0" maxOccurs="unbounded"/>
      <xs:element ref="character" minOccurs="0" maxOccurs="unbounded"/>
   </xs:sequence>
   <xs:attribute ref="id"/>
   <xs:attribute ref="available"/>
</xs:complexType>
<xs:element name="book" type="bookType"/>
<xs:group name="bookGroup">
   <xs:sequence>
      <xs:element ref="book"/>
   </xs:sequence>
</xs:group>
<xs:element name="library">
   <xs:complexType>
      <xs:sequence>
         <xs:group ref="bookGroup" maxOccurs="unbounded"/>
      </xs:sequence>
   </xs:complexType>
</xs:element>

Auch wenn dieses Beispiel exzessiv aussehen mag, müssen wir anerkennen, dass es auch das am besten erweiterbare ist, denn hier kann man sämtliche Methoden zum Wiederverwenden und Ableiten der drei Kompositoren benutzen. Nachdem wir nun diese vier grundlegenden Stile kennengelernt haben, wollen wir sie in Bezug auf Wiederverwendbarkeit und Ableitbarkeit vergleichen.

Die Puppe in der Puppe ist offensichtlich der Stil, der am schlechtesten erweiterbar ist: Sowohl die Definition des book-Elements als auch die seines Inhaltsmodells sind lokal. Man kann nicht auf sie verweisen, um sie an anderer Stelle in einem Schema weiterzuverwenden, sie können nicht durch Ableitung, durch xs:redefine oder durch Ersetzungsgruppen modifiziert werden. Die Verwendung des Puppe-in-der-Puppe-Stils erweist sich hier als wirksamere »Blockiermöglichkeit«, als es ein block-Attribut je sein könnte. Das book-Element oder sein Inhaltsmodell zu ändern oder wiederzuverwenden macht es erforderlich, ein ganz anderes Schema mit dem Instanzdokument zu verbinden oder aber dort ein xsi:type-Attribut zu verwenden.

Das flache Modell, das globale Elementdefinitionen verwendet, gewährt einen Grundumfang an Flexibilität, da das Element nun an jeder Stelle in einem beliebigen Schema wiederverwendet, als Dokumentenelement in einem Instanzdokument benutzt und als Kopf einer Ersetzungsgruppe eingesetzt werden kann. Wenn das flache Modell wie in unserem Beispiel mit einer lokalen Definition eines komplexen Typs benutzt wird, erlaubt es keine Änderung des jeweiligen Inhaltsmodells. Unter den drei Einsatzmöglichkeiten des flachen Modells ist diejenige als Kopf einer Ersetzungsgruppe die einzige, die (mit Hilfe eines block-Attributs) blockiert werden kann. Das flache Modell kann ohne Einschränkung als Dokumentenelement in einem Instanzdokument oder an beliebigen Stellen in einem Schema verwendet werden. Wir müssen auch anmerken, daß Elemente nicht redefiniert werden können und daß das Inhaltsmodell unseres book-Elements nicht geändert werden kann, außer durch eine Ersetzung mit Hilfe von xsi:type im Instanzdokument.

Die Definition eines globalen komplexen Typs zur Beschreibung des Inhaltsmodells für das book-Element öffnet zwei verschiedene Türen. Das Inhaltsmodell des book-Elements kann nun wiederverwendet werden, um erweiterte oder eingeschränkte Inhaltsmodelle abzuleiten, die an anderer Stelle benutzt werden können, und der komplexe Typ kann durch xs:redefine redefiniert werden. Wie wir unter Objektorientierte Konstruktion weiterer Bausteine gesehen haben, kann die Ableitung durch das Attribut final blockiert werden, aber die Redefinition kann nicht gesteuert werden.

Zu guter Letzt ermöglicht die Einbettung der Definition des book-Elements in eine Gruppe, diese Gruppe andernorts wiederzuverwenden – zum Beispiel in unserem flachen Modell –, kann aber gleichzeitig die Definition des book-Elements bei Bedarf verstecken, so daß es nicht als Dokumentenelement in Instanzdokumenten verwendet werden kann. (Übrigens wird dadurch auch die Verwendung als Kopf einer Ersetzungsgruppe blockiert.) Eine Gruppe zu definieren macht es auch möglich, diese durch xs:redefine umzudefinieren und so die Auftretenshäufigkeit des Elements zu ändern, neue Elemente hinzuzufügen oder gar das Inhaltsmodell zu ändern, wenn ein globaler komplexer Typ verwendet wurde. Eine Elementgruppe so zu verwenden ist dem Ansatz von RELAX NG sehr ähnlich und verleiht ein wenig von dessen Flexibilität. Wir müssen jedoch anmerken, daß Elementgruppen nicht rekursiv sein können; dies kann eine Begrenzung für die Verwendung von Elementgruppen sein, wenn man rekursive Inhaltsmodelle mit Elementgruppen definieren will, da immer noch ein globales Element definiert werden muß, das in Referenzen verwendet werden kann. Dies kann ein Problem sein, wenn wir ein globales Element nicht verwenden können oder wollen – zum Beispiel wenn wir zwei verschiedene rekursive Inhaltsmodelle haben, die denselben Elementnamen mit unterschiedlichen Inhalten verwenden.

Welcher Ansatz ist der richtige? Es gibt keine einzelne endgültige Antwort auf diese Frage, aber wir wissen, daß jeder dieser Stile seinen eigenen Strauß an Erweiterungsmöglichkeiten bietet. Die Wahl zwischen diesen Stilen oder einer Stilkombination hat gravierende Auswirkungen auf die Wiederverwendbarkeit und Ableitbarkeit der Definitionen, die in einem Schema vorliegen. Die folgende Tabelle kann dabei helfen, sich die Unterschiede zwischen diesen Stilen vorzustellen, aber denken Sie daran, daß man alle miteinander kombinieren kann.

Tabelle: Stilarten für komplexe Typen

StilElement- ReferenzInhaltsmodell- ReferenzAbleitungErsetzungsgruppeDokumentenelementRedefinition
Puppe in der PuppeNeinNeinNeinNeinNeinNein
FlachJaNeinNeinJaJaNein
Komplexer TypNeinJaJaNeinNeinJa
GruppeJaNeinNeinNeinNeinJa

Elemente einfachen Typs verhalten sich weitgehend wie komplexe Typen, außer daß natürlich die Definitionen komplexer Typen durch Definitionen einfacher Typen ähnlich denen für Attribute, die im nächsten Abschnitt besprochen werden, ersetzt werden.

Attribute

Wie wir unter Kontrolle über Namensräume erörtert haben, verhalten sich Attribute insofern anders als Elemente, als sie meistens unqualifiziert sind. Das bedeutet also, daß sie nicht global definiert werden können. Ansonsten haben wir bei Attributen, einfachen Typen und Attributgruppen eine ähnliche Situation, wie wir sie bei Elementen und komplexen Typen haben. (Die andere Ausnahme besteht darin, daß es im Land der Attribute nichts Entsprechendes für Ersetzungsgruppen oder xsi:type gibt). Wenn wir die Definition eines Attributs lang im title-Element nehmen, das auf en oder de beschränkt ist, können wir einen Puppe-in-der-Puppe-Entwurf wählen, bei dem das Attribut und sein Typ lokal definiert werden:

<xs:element name="title">
   <xs:complexType>
      <xs:simpleContent>
         <xs:extension base="xs:token">
            <xs:attribute name="lang">
               <xs:simpleType>
                  <xs:restriction base="xs:language">
                     <xs:enumeration value="en"/>
                     <xs:enumeration value="de"/>
                  </xs:restriction>
               </xs:simpleType>
            </xs:attribute>
         </xs:extension>
      </xs:simpleContent>
   </xs:complexType>
</xs:element>

Wir können auch ein flaches Design nehmen, bei dem das Attribut global definiert ist:

<xs:attribute name="lang">
   <xs:simpleType>
      <xs:restriction base="xs:language">
         <xs:enumeration value="en"/>
         <xs:enumeration value="de"/>
      </xs:restriction>
   </xs:simpleType>
</xs:attribute>
<xs:element name="title">
   <xs:complexType>
      <xs:simpleContent>
         <xs:extension base="xs:token">
            <xs:attribute ref="lang"/>
         </xs:extension>
      </xs:simpleContent>
   </xs:complexType>
</xs:element>

Ein globaler einfacher Typ kann ebenfalls definiert werden:

<xs:simpleType name="langType">
   <xs:restriction base="xs:language">
      <xs:enumeration value="en"/>
      <xs:enumeration value="de"/>
   </xs:restriction>
</xs:simpleType>
<xs:element name="title">
   <xs:complexType>
      <xs:simpleContent>
         <xs:extension base="xs:token">
            <xs:attribute name="lang" type="langType"/>
         </xs:extension>
      </xs:simpleContent>
   </xs:complexType>
</xs:element>

Das Attribut kann in einer Attributgruppe »versteckt« werden:

<xs:attributeGroup name="langGroup">
   <xs:attribute name="lang">
      <xs:simpleType>
         <xs:restriction base="xs:language">
            <xs:enumeration value="en"/>
            <xs:enumeration value="de"/>
         </xs:restriction>
      </xs:simpleType>
   </xs:attribute>
</xs:attributeGroup>
<xs:element name="title">
   <xs:complexType>
      <xs:simpleContent>
         <xs:extension base="xs:token">
            <xs:attributeGroup ref="langGroup"/>
         </xs:extension>
      </xs:simpleContent>
   </xs:complexType>
</xs:element>

All dies kann gemeinsam verwendet werden:

<xs:simpleType name="langType">
   <xs:restriction base="xs:language">
      <xs:enumeration value="en"/>
      <xs:enumeration value="de"/>
   </xs:restriction>
</xs:simpleType>
<xs:attribute name="lang" type="langType"/>
<xs:attributeGroup name="langGroup">
   <xs:attribute ref="lang"/>
</xs:attributeGroup>
<xs:element name="title">
   <xs:complexType>
      <xs:simpleContent>
         <xs:extension base="xs:token">
            <xs:attributeGroup ref="langGroup"/>
         </xs:extension>
      </xs:simpleContent>
   </xs:complexType>
</xs:element>

Die Auswirkungen dieser Entwurfsentscheidungen sind ziemlich genau die gleichen wie die, die wir bei den Elementen komplexen Typs gesehen haben, außer natürlich in Bezug auf Ersetzungsgruppen und die Verwendbarkeit als Dokumentenelement. Die folgende Tabelle erklärt die Optionen, die diese unterschiedlichen Ansätze bieten.

Tabelle: Stilarten für Attribute

StilAttribut- ReferenzDatentyp- ReferenzAbleitungRedefinition
Puppe in der PuppeNeinNeinNeinNein
FlachJaNeinNeinNein
Einfacher TypNeinJaJaJa
AttributgruppeJaNeinNeinJa

Die Attribute final und fixed

Diese Attribute sind bereits unter Objektorientierte Konstruktion weiterer Bausteine behandelt worden, und sie haben offensichtliche Auswirkungen auf die Wiederverwendbarkeit der Definitionen von einfachen und komplexen Typen, da sie einige oder alle weiteren Ableitungen blockieren können. Diese Klasse von Merkmalen betrifft die Flexibilität des Schemas selbst. Ihre Freunde block und abstract sind Merkmale, die sich auf die Offenheit des Schemas auswirken, nicht jedoch auf die Menge der Instanzdokumente.

Aufteilung von Schema-Komponenten

Der letzte Faktor, der die Flexibilität und Wiederverwendbarkeit unseres Schemas (und unserer Schema-Bibliotheken) betrifft, ist die Aufteilung der Komponenten auf verschiedene Dokumente. Einige Schema-Designer sind in dieser Richtung so weit gegangen zu empfehlen, jede Klasse oder Komponente in einem eigenen Schema-Dokument unterzubringen und dann diejenigen Komponenten, die man zum Aufbau eines vollständigen Schemas benötigt, einzubinden oder zu importieren. Dies mag exzessiv erscheinen, bietet jedoch sehr feine Abstufungen und erlaubt es, die Beschränkungen von xs:redefine zu umgehen. (Wenn eine Komponente redefiniert werden muß, lassen Sie einfach die alte Definition weg und schreiben eine neue.)

Die größten Probleme bei einem solchen Entwurf sind wahrscheinlich die Verwaltung einer Anzahl verschiedener Dokumente, die schnell anwachsen kann, und die zahlreichen Abhängigkeiten zwischen diesen Dokumenten. Die Abhängigkeiten müssen beim Entwurf von Schema-Bibliotheken bedacht werden. Sie sind mitunter schwer nachzuvollziehen, weil die Verbindungen zwischen den einbindenden und den eingebundenen Dokumenten multidirektional sind. Eine Komponente innerhalb eines eingebundenen Schemas kann auf Komponenten verweisen, die in einem beliebigen anderen Schema definiert sind, das der Schema-Prozessor verarbeitet.

Wir müssen uns erneut ansehen, wie ein Schemaprozessor ein globales Schema unter Verwendung all der importierten, eingebundenen und redefinierten Anweisungen, die er findet, aufbaut. Der Schema-Prozessor baut zu Anfang ein großes konsolidiertes Schema aus all den Komponenten auf, die in den Schema-Dokumenten, die er verarbeitet hat, enthalten sind. Nach dem Aufbau des konsolidierten Schemas löst er dann die Verweise zwischen Komponenten auf. Auch wenn dieser einfache und mächtige Mechanismus auf Einbindungen ohne Einschränkung zutrifft, werden wir sehen, daß die Sache bei Importen und Redefinitionen häßlicher wird. Fangen wir mit dem einfachsten Fall an und betrachten wir die Verarbeitung von xs:include.

Die Semantik von xs:include unterscheidet sich leicht von der Semantik von Include-Anweisungen, wie sie in Sprachen wie C verwendet werden. Der Vorgang sollte als konditionale Einbindung verstanden werden. Ein xs:include ist in Wirklichkeit die Aufforderung, ein Schema zu lesen, falls es nicht bereits gelesen worden ist, alle in diesem Schema angetroffenen Komponenten-Deklarationen dem konsolidierten Schema hinzuzufügen, falls sie noch nicht definiert waren, die in dem neuen Schema angetroffenen und in dem globalen Schema bereits definierten Komponenten zu ignorieren, sofern sie identisch sind, und eine Fehlerbedingung auszulösen, wenn sie unterschiedlich sind. Das bedeutet, daß es vollkommen legitim ist, Schleifen und mehrmalige Einbindungen zu erzeugen, sei es direkt (Schema A bindet Schema B ein, das Schema A einbindet), sei es indirekt (Schema A bindet Schema B und Schema C ein, das seinerseits Schema B einbindet). Wir können Einbindungspfade so komplex konstruieren, wie wir wollen.

Die Bedeutung von xs:redefine ist ähnlich, außer daß einige Komponenten redefiniert werden können. Wenn wir diese Konstruktion verwenden, reicht der Unterschied aus, um Schleifen unmöglich zu machen, bei denen ein Schema A Komponenten eines Schemas B redefiniert, das seinerseits Schema A redfiniert oder einbindet. Diese Einschränkung bedeutet letztlich, daß wir zwar von Einbindungsgraphen sprechen können, daß die Redefinitionen jedoch einen Baum aufbauen. Der Vorgang des Einbindens oder Redefinierens ist jedoch rekursiv, und wenn wir ein Schema einbinden (oder redefinieren), binden wir das aus dem eingebundenen Dokument resultierende konsolidierte Schema ein, nicht das Dokument selbst. Wir können immer noch Einbindungsschleifen innerhalb der Zweige des Redefinitionsbaums erzeugen (Schema A kann Schema B redefinieren, das Schema C einbindet, das seinerseits Schema B einbindet).

Einige Designer verlassen sich darauf, daß ein Schema ohne Ziel-Namensräume, das in ein Schema mit einem Ziel-Namensraum eingebunden (oder dort redefiniert) wird, den Ziel-Namensraum seines Einbinders »borgt«. Diese Eigenschaft kann dazu verwendet werden, »neutrale« Komponenten ohne Namensräume zu konstruieren, die als Bausteine eingebunden und verwendet werden können. Da diese Komponenten den Namensraum des einbindenden Schemas annehmen, wie ein Chamäleon die Farbe seiner Umgebung annimmt, werden solche Schemas »Chamäleon-Schemas« genannt. Obwohl diese Technik einfach und in einigen Fällen möglicherweise praktisch ist, kann es verwirrend sein, ähnliche Komponenten (und daher ähnliche Typen und Inhaltsmodelle) in verschiedenen Namensräumen zu definieren, statt einen gemeinsamen Namensraum für sie zu definieren, wodurch diese Typen und Inhaltsmodelle unmittelbar als identisch erkennbar wären.

xs:import verhält sich ein wenig wie xs:include: Es werden keine Redefinitionen vorgenommen, was bedeutet, daß Schleifen erzeugt werden können, bei denen Schema A (für Namensraum A) Schema B (für Namensraum B) importiert, das seinerseits Schema A importiert. Wichtig ist, daß xs:import zwei verschiedenen Zwecken dient: Es ist eine Anweisung, ein Schema zu importieren, und eine Deklaration, daß Komponenten aus einem bestimmten Namensraum referenziert werden können. Wenn Schema A für Namensraum A Schema B für Namensraum B importiert und wenn Schema B Komponenten aus dem Namensraum A referenzieren muß, muß eine xs:import-Anweisung in Schema B stehen, um zu deklarieren, daß Namensraum A verwendet werden kann. (Das Attribut schemaLocation ist optional und kann in solchen Fällen weggelassen werden.)

Nachdem wir die drei Mechanismen (Einbinden, Redefinieren und Importieren) durchgearbeitet haben, können wir sie alle miteinander mischen und anmerken, daß Chamäleon-Schemas gemeinsam mit Importen verwendet werden können. In diesem Fall kann dasselbe importierte Chamäleon mehrere Male unter verschiedenen Namensräumen zu einem globalen Schema beitragen. Wenn Schema A für Namensraum A das Schema B ohne Namensraum einbindet und außerdem Schema C für Namensraum C importiert, das seinerseits Schema B einbindet, gehören die beiden Einbindungen des Schemas B zwei verschiedenen Namensräumen an und werden als verschieden betrachtet.

Wir haben nun alle Elemente, um innovative Mischungen aus Einbindungs- und Importgraphen mit Redefinitionsbäumen zu finden. Bedenken Sie, daß das Einfache seinen Reiz hat, und wenn wir uns nicht selbst Beschränkungen auferlegen, verirren wir Menschen uns möglicherweise eher als unser Lieblingsschemaprozessor.

   

zum Seitenanfang

<< zurück vor >>

 

 

 

Tipp der data2type-Redaktion:
Zum Thema XML Schema bieten wir auch folgende Schulungen zur Vertiefung und professionellen Fortbildung an:

Copyright © 2003 O'Reilly Verlag GmbH & Co. KG
Für Ihren privaten Gebrauch dürfen Sie die Online-Version ausdrucken.
Ansonsten unterliegt dieses Kapitel aus dem Buch "XML Schema" denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

O’Reilly Verlag GmbH & Co. KG, Balthasarstraße 81, 50670 Köln, kommentar(at)oreilly.de