1. Vorwort
RELAX NG ist eine Schemasprache für XML, mit der man den Aufbau eines XML-Dokuments beschreiben kann. Hat man ein solches Schema erstellt, dann kann man leicht überprüfen, ob ein XML-Dokument den Anforderungen entspricht. Beispielsweise kann man in einem Schema festlegen, dass ein Adressbucheintrag genau einen Vornamen und einen Nachnamen enthalten muss. Das hat zwei Vorteile:
-
Der Anwender kann prüfen, ob das XML-Dokument den Anforderungen entspricht (Validierung).
-
Mit einem passenden Editor kann sich per Autovervollständigung Vorschläge für die Eingabe geben lassen und spart Tipparbeit und vermeidet Fehler.
Die Schemasprache RELAX NG eignet sich sehr gut für diese Aufgabe und wird von vielen XML-Editoren unterstützt. RELAX NG kommt in zwei Arten daher, die funktional identisch sind. Einmal als »kompakte Syntax« und einmal als »XML-Syntax«. In diesem Dokument wird nur die XML-Syntax beschrieben.
Dieses Tutorial lehnt sich an das »offizielle« Tutorial an, das auf der RELAX NG-Webseite zur Verfügung steht. Es ist keine direkte Übersetzung. Hier und da wurden Änderungen vorgenommen, um den Inhalt leichter zu vermitteln. Hinweise auf nicht mehr übliche Techniken wurden entfernt (TREX, DTD, …)
Neu eingeführte Elemente werden im linken Rand dargestellt. Damit kannst du das Tutorial auch »überfliegen«, wenn du nach einem bestimmten Befehl suchst. Das funktioniert leider nur, wenn der Bildschirm groß genug ist… |
Dieses Dokument unterliegt der CC-By-SA Lizenz. Der Quelltext (Asciidoctor) des Dokuments ist auf Github zu finden. Dieses Dokument kann man auch als EPUB herunterladen.
2. Los geht’s
Als Beispiel für das Tutorial (bzw. die ersten Schritte) soll folgende XML-Datei dienen, für die ein Schema erstellt werden soll (speichern z.B. unter source.xml
):
<addressBook>
<card>
<name>John Smith</name>
<email>js@example.com</email>
</card>
<card>
<name>Fred Bloggs</name>
<email>fb@example.net</email>
</card>
</addressBook>
Das dazugehörige Schema könnte wie folgt aussehen (speichern z.B. unter schema.rng
):
element
zeroOrMore
text
<element name="addressBook" xmlns="http://relaxng.org/ns/structure/1.0">
<zeroOrMore>
<element name="card">
<element name="name">
<text/>
</element>
<element name="email">
<text/>
</element>
</element>
</zeroOrMore>
</element>
Die hier aufgezeigte Form des Schemas (mit dem Wurzelelement element ) ist in der Praxis eher unüblich. RELAX NG Schemadateien haben als Wurzelelement oft grammar , das wesentlich flexibler ist. Das wird in Kapitel
Benannte Muster
eingeführt.
|
Die Validierung des Dokuments wird meist in einem XML-Editor durchgeführt (z.B. oXygen XML), in der Regel auch während der Eingabe. Man kann mit dem Programm Jing auch auf der Kommandozeile validieren, das ist natürlich etwas umständlicher, für automatisierte Workflows jedoch extrem praktisch:
java -jar jing.jar schema.rng source.xml
Passt die XML-Datei zum Schema, so gibt es keine Ausgabe und der Rückgabewert des Programms ist 0. Andernfalls wird eine Fehlermeldung ausgegeben.
Das oben gezeigte Schema erwartet als Wurzelelement ein Element mit dem Namen addressBook
mit keinem oder mehreren Kindelementen card
, die wiederum zwei Elemente (name
und email
) in dieser Reihenfolge enthalten. Beide Kindelemente müssen vorhanden sein und enthalten beliebigen Text, der auch leer sein kann. Mit etwas Geduld kann man das ganz gut aus dem Schema ablesen.
oneOrMore
Möchte man z.B. dass addressBook
mindestens ein Element card
enthält, dann ersetzt man zeroOrMore
durch oneOrMore
:
<element name="addressBook" xmlns="http://relaxng.org/ns/structure/1.0">
<oneOrMore>
<element name="card">
<element name="name">
<text/>
</element>
<element name="email">
<text/>
</element>
</element>
</oneOrMore>
</element>
2.1. Attribute
Falls anstelle von Elementen Attribute gewünscht sind, kann man das einfach erreichen. Dazu passt man das Schema an und ersetzt quasi die <element ..>
durch <attribute ..>
:
attribute
<element name="addressBook" xmlns="http://relaxng.org/ns/structure/1.0">
<zeroOrMore>
<element name="card">
<attribute name="name" />
<attribute name="email" />
</element>
</zeroOrMore>
</element>
Da die Reihenfolge der Attribute in XML nicht wichtig ist, können sie bei RELAX NG auch in beliebiger Reihenfolge festgelegt werden. Bei Elementen hingegen ist die Reihenfolge wesentlich. |
Es gibt einen Unterschied zwischen den Mustern für Attribute und Elemente. Die Voreinstellung für den Inhalt von Attributen ist <text />
. Daraus folgt, dass
<attribute name="email"/>
die Kurzform für
<attribute name="email">
<text/>
</attribute>
ist. Für Elemente hingegen gibt es keine Voreinstellung. Leere Elemente müssen explizit als solche gekennzeichnet sein.
<!-- nicht erlaubt! -->
<element name="email"/>
<!-- aber das hier ist erlaubt -->
<element name="email">
<empty />
</element>
Ist hingegen ein Attribut definiert, ist das <empty />
nicht notwendig und wird implizit angenommen:
<element name="card">
<attribute name="email" />
</element>
ist dasselbe wie
<element name="card">
<attribute name="email" />
<empty/>
</element>
Namensraum des Schemas
Der Namensraum von RELAX NG ist http://relaxng.org/ns/structure/1.0
. Mit dem »Attribut«
xmlns="http://relaxng.org/ns/structure/1.0"
wird dieser Namensraum als Voreinstellung für alle (Kind-)Elemente genommen. Man kann jedoch genau so gut einen Präfix nutzen:
xmlns:rng="http://relaxng.org/ns/structure/1.0"
dann müssen alle Elemente dieses Präfix benutzen:
<rng:element name="addressBook" xmlns:rng="http://relaxng.org/ns/structure/1.0">
<rng:zeroOrMore>
<rng:element name="card">
<rng:element name="name">
<rng:text/>
</rng:element>
<rng:element name="email">
<rng:text/>
</rng:element>
</rng:element>
</rng:zeroOrMore>
</rng:element>
3. Optionale Elemente und Attribute
Möchte man ein Element als optional markieren, macht man das wie folgt:
optional
<element name="addressBook" xmlns="http://relaxng.org/ns/structure/1.0">
<zeroOrMore>
<element name="card">
<element name="name">
<text/>
</element>
<element name="email">
<text/>
</element>
<optional>
<element name="note">
<text/>
</element>
</optional>
</element>
</zeroOrMore>
</element>
Das Element note
ist nun als Kind von card
erlaubt, muss aber nicht angegeben werden.
Genauso funktioniert dies mit Attributen:
<element name="addressBook" xmlns="http://relaxng.org/ns/structure/1.0">
<zeroOrMore>
<element name="card">
<attribute name="name"/>
<attribute name="email"/>
<optional>
<attribute name="note" />
</optional>
</element>
</zeroOrMore>
</element>
Das Attribut note
kann angegeben werden.
4. Auswahl (Choice)
Mit dem Element choice
kann man ein »Entweder/Oder«-Konstrukt erzeugen.
Wenn das Adressbuch beispielsweise entweder das Element name
oder die beiden Elemente givenName
und familyName
haben soll, nutzt man choice
. Die gewünschte XML-Datei ist folgende:
<addressBook>
<card>
<givenName>John</givenName>
<familyName>Smith</familyName>
<email>js@example.com</email>
</card>
<card>
<name>Fred Bloggs</name>
<email>fb@example.net</email>
</card>
</addressBook>
Dazu passt diese Schemadatei.
choice
group
<element name="addressBook" xmlns="http://relaxng.org/ns/structure/1.0">
<zeroOrMore>
<element name="card">
<choice>
<element name="name">
<text/>
</element>
<group>
<element name="givenName">
<text/>
</element>
<element name="familyName">
<text/>
</element>
</group>
</choice>
<element name="email">
<text/>
</element>
</element>
</zeroOrMore>
</element>
Das Element group
hier ist notwendig, sonst wäre die Aussage »entweder name
oder givenName
oder familyName
«. Dasselbe funktioniert auch für Attribute:
<element name="addressBook" xmlns="http://relaxng.org/ns/structure/1.0">
<zeroOrMore>
<element name="card">
<choice>
<attribute name="name" />
<group>
<attribute name="givenName" />
<attribute name="familyName" />
</group>
</choice>
<attribute name="email">
<text/>
</attribute>
</element>
</zeroOrMore>
</element>
Choice kann man auch für Kombinationen von Elementen und Attribute nutzen, wenn man z.B. »entweder Element oder Attribut« beschreiben möchte.
Im nächsten Beispiel kann man entweder das Attribut oder das Element benutzen, und zwar für jedes der beiden Felder name
und email
separat.
<element name="addressBook" xmlns="http://relaxng.org/ns/structure/1.0">
<zeroOrMore>
<element name="card">
<choice>
<element name="name">
<text/>
</element>
<attribute name="name">
<text/>
</attribute>
</choice>
<choice>
<element name="email">
<text/>
</element>
<attribute name="email">
<text/>
</attribute>
</choice>
</element>
</zeroOrMore>
</element>
Dieses Schema passt auf folgende XML-Dokumente:
<card name="John Smith" email="js@example.com"/>
<card email="js@example.com" name="John Smith"/>
<card email="js@example.com"><name>John Smith</name></card>
<card name="John Smith"><email>js@example.com</email></card>
<card><name>John Smith</name><email>js@example.com</email></card>
Aber nicht auf:
<card><email>js@example.com</email><name>John Smith</name></card>
weil das Element email
nicht vor dem Element name
auftreten darf.
Erinnerung: das zweite Beispiel oben ist erlaubt, weil ja bei den Attributen die Reihenfolge unwichtig ist.
5. Benannte Muster
Für alle größeren Schemadateien ist es vorteilhaft, wenn man das Schema in einzelne Teile gliedert. Mit define
kann man einen einzelnen Abschnitt definieren und einen Namen zuweisen, auf dem man später mit ref
verweisen kann.
Eine solche Definition sieht so aus:
define
<define name="cardContent">
<element name="name">
<text/>
</element>
<element name="email">
<text/>
</element>
</define>
da nun aber bei mehreren solchen Definitionen unklar ist, wo das Schema »anfängt«, muss eine andere Struktur her. Das Grundgerüst ist nun folgendes:
grammar
start
<grammar xmlns="http://relaxng.org/ns/structure/1.0">
<start>
<element name="wurzelelement">
...
</element>
</start>
</grammar>
Jetzt kann das bekannte Schema in Teile zerlegt werden:
ref
<grammar xmlns="http://relaxng.org/ns/structure/1.0">
<start>
<element name="addressBook">
<zeroOrMore>
<element name="card">
<ref name="cardContent"/>
</element>
</zeroOrMore>
</element>
</start>
<define name="cardContent">
<element name="name"><text /></element>
<element name="email"><text /></element>
</define>
</grammar>
Man kann das auch noch weiter treiben, in dem man die beiden Elemente aus card
auch noch aufteilt:
<!-- der Rahmen wie oben -->
<define name="cardContent">
<ref name="namecontent"/>
<ref name="emailcontent"/>
</define>
<define name="namecontent">
<element name="name">
<text />
</element>
</define>
<define name="emailcontent">
<element name="email">
<text />
</element>
</define>
Die Reihenfolge der Definitionen ist beliebig. Die Referenzen dürfen sich auch selbst aufrufen:
<define name="inline">
<choice>
<text/>
<element name="bold">
<ref name="inline"/>
</element>
<element name="italic">
<ref name="inline"/>
</element>
<element name="span">
<optional>
<attribute name="style"/>
</optional>
<ref name="inline"/>
</element>
</choice>
</define>
Die Elemente bold
, italic
und span
dürfen entweder Text enthalten oder eines der Elemente bold
, italic
oder span
, und das mit beliebiger Schachtelungstiefe.
6. Datentypen
In den letzten Beispielen gab es als einzigen Datentyp »text«, der alles beinhaltet. Manchmal ist es jedoch wünschenswert, nur bestimmte Werte zuzulassen, beispielsweise für das Geburtsjahr. Der Anwender soll genau vier Ziffern in das Feld schreiben dürfen, die ggf. noch innerhalb eines Wertebereiches liegen.
RELAX NG selber kennt keine Datentypen, kann aber »externe« Datentypen einbinden.
Die wohl am häufigsten benutzte Sammlung an Datentypen hat den URI http://www.w3.org/2001/XMLSchema-datatypes
. Dieser Identifier wird im Attribut datatypeLibrary
angegeben, das man am einfachsten am Wurzelelement mit angibt:
datatypeLibrary
<grammar
xmlns="http://relaxng.org/ns/structure/1.0"
datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
<start>
...
</start>
</grammar>
bzw.
<element xmlns="http://relaxng.org/ns/structure/1.0"
datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"
name="addressBook">
...
</element>
Möchte man ein Element einschränken auf einen bestimmten Wert, kann man das mit dem Muster data
erreichen:
data
<element name="year">
<data type="gYear"/>
</element>
Das erlaubt nur Eingaben, die dem Muster gYear
entsprechen.
Die Beschreibung der eingebauten Typen ist unter https://www.w3.org/TR/xmlschema-2/#built-in-datatypes
zu finden.
Nicht erlaubt ist es, verschiedene Datentypen in einem Element zu benutzen.
<!-- nicht erlaubt! -->
<element name="year">
<data type="gYear"/>
<element name="note">
<text/>
</element>
</element>
Datentypen können Parameter (Einschränkungen) haben. So sind für den Datentyp string
die folgenden Einschränkungen möglich:
-
length
-
minLength
-
maxLength
-
pattern
Die beiden Parameter enumeration
und whiteSpace
sind laut den Richtlinien für Datentypen in RELAX NG nicht erlaubt.
Diese Parameter werden als Kindelement von data
angegeben
param
<element name="email">
<data type="string">
<param name="minLength">6</param>
<param name="maxLength">127</param>
</data>
</element>
7. Vorbelegte Werte
Manchmal möchte man Werte von Elementen oder Attributen auf eine begrenzte Anzahl von Auswahlmöglichkeiten einschränken.
Dafür gibt es das Muster value
:
value
<element name="card">
<attribute name="name"/>
<attribute name="email"/>
<attribute name="preferredFormat">
<choice>
<value>html</value>
<value>text</value>
</choice>
</attribute>
</element>
Analog funktioniert das auch mit Elementinhalten.
<element name="preferredFormat">
<choice>
<value>html</value>
<value>text</value>
</choice>
</element>
Vorsicht: Dieses Muster schränkt zwar die Werte auf html
und text
ein, erlaubt aber Leerzeichen (Whitespace) am Anfang und am Ende des Wertes. So ist
<preferredFormat> html </preferredFormat>
ein gültiger Wert für das value
-Muster oben. Um das zu verhindern kann man dem Element value
mit dem Attribut type
einschränken:
<element name="preferredFormat">
<choice>
<value type="string">html</value>
<value type="string">text</value>
</choice>
</element>
Ist keine datatypeLibrary festgelegt (siehe das vorherige Kapitel), dann werden die internen Typen token oder string genommen, die dem ersten (mit Whitespace) bzw. dem zweiten genannten Verhalten (ohne Whitespace) entsprechen.
|
8. Listen
Dieses Muster passt auf eine durch Leerzeichen (Whitespace) separierte Liste von Werten. Ein einfaches Beispiel erklärt das wohl am besten:
list
<element name="vector">
<list>
<data type="float"/>
<data type="float"/>
</list>
</element>
Auf dieses Schema passt z.B. folgendes XML-Dokument:
<vector>
1.2
3.2
</vector>
Wichtig ist nur, dass die Werte durch Leerzeichen oder ähnliches getrennt werden.
Die folgenden beiden Definitionen zeigen die Anwendungsmöglichkeiten:
<element name="vector">
<list>
<oneOrMore>
<data type="double"/>
</oneOrMore>
</list>
</element>
<element name="path">
<list>
<oneOrMore>
<data type="double"/>
<data type="double"/>
</oneOrMore>
</list>
</element>
Im ersten Fall sind ein oder mehrere Werte vom Typ double
erlaubt, im zweiten Fall sind eine gerade Anzahl der Werte erlaubt.
In der Praxis spielen diese Listen vermutlich nicht so eine große Rolle.
9. Verschachteln von Elementen (interleave)
Das Interleave-Muster erlaubt es, Kindelemente in beliebiger Reihenfolge auftreten zu lassen.
Im folgenden Beispiel können die Kindelemente von card
in der Reihenfolge email
und dann name
oder andersrum auftreten.
interleave
<element name="addressBook">
<zeroOrMore>
<element name="card">
<interleave>
<element name="name">
<text/>
</element>
<element name="email">
<text/>
</element>
</interleave>
</element>
</zeroOrMore>
</element>
Mit Interleave sind aber weitaus mächtigere Konstrukte möglich.
Als Beispiel dient der Kopf einer HTML-Seite (Elementname head
).
In dem Kopf sind verschiedene Regeln zu beachten:
-
Es ist ein Vorkommen der Elemente
title
undbase
erlaubt, letzteres ist jedoch optional. -
Die Elemente
style
,script
,link
undmeta
dürfen mehrfach vorkommen, sind aber auch optional. -
Die Reihenfolge der Elemente ist beliebig.
Unter der Annahme, dass es für die <ref name="…">
passende Definitionen gibt, kann man die Regel für den HTML-Kopf mit dem folgenden Muster beschreiben:
<define name="head">
<element name="head">
<interleave>
<ref name="title"/>
<optional>
<ref name="base"/>
</optional>
<zeroOrMore>
<ref name="style"/>
</zeroOrMore>
<zeroOrMore>
<ref name="script"/>
</zeroOrMore>
<zeroOrMore>
<ref name="link"/>
</zeroOrMore>
<zeroOrMore>
<ref name="meta"/>
</zeroOrMore>
</interleave>
</element>
</define>
Hier ist eine Eigenschaft zu erwähnen, die vielleicht nicht unbedingt intuitiv ist. Das Muster oben erlaubt die folgende XML-Datei:
<head>
<meta ... />
<title ... />
<meta ... />
</head>
Man könnte vermuten, dass die meta
-Elemente hintereinander stehen müssen, weil sie durch zeroOrMore
zusammenhängen.
Gemischter Inhalt (mixed content)
Für einen Sonderfall des Interleave-Musters gibt es ein extra Element. Wenn ein Muster p auch Text beinhalten darf (mixed content), dann kann man
<interleave> <text/> p </interleave>
durch
mixed
<mixed> p </mixed>
abkürzen.
10. Mehrfachdefinitionen (combine )
Ein Schema kann mehrere Definitionen (define
) mit demselben Namen enthalten.
Dann muss allerdings beschrieben werden, was passiert, wenn mehrere Definitionen zu einer zusammengeführt werden.
Warum sollte man mehrere Definitionen mit demselben Namen haben? Das spielt dann eine wichtige Rolle, wenn man mehrere Schemadateien hat und diese kombinieren will. Mehr dazu im nächsten Abschnitt. |
combine
<define name="inline" combine="choice">
<element name="bold">
<ref name="inline"/>
</element>
</define>
<define name="inline" combine="choice">
<element name="italic">
<ref name="inline"/>
</element>
</define>
ist dasselbe wie
<define name="inline">
<choice>
<element name="bold">
<ref name="inline"/>
</element>
<element name="italic">
<ref name="inline"/>
</element>
</choice>
</define>
Für Attribute wird in der Regel combine="interleave"
benutzt:
<grammar xmlns="http://relaxng.org/ns/structure/1.0">
<start>
<element name="addressBook">
<zeroOrMore>
<element name="card">
<ref name="card.attlist"/>
</element>
</zeroOrMore>
</element>
</start>
<define name="card.attlist" combine="interleave">
<attribute name="name">
<text/>
</attribute>
</define>
<define name="card.attlist" combine="interleave">
<attribute name="email">
<text/>
</attribute>
</define>
</grammar>
ist dasselbe wie
<grammar xmlns="http://relaxng.org/ns/structure/1.0">
<start>
<element name="addressBook">
<zeroOrMore>
<element name="card">
<ref name="card.attlist"/>
</element>
</zeroOrMore>
</element>
</start>
<define name="card.attlist">
<interleave>
<attribute name="name">
<text/>
</attribute>
<attribute name="email">
<text/>
</attribute>
</interleave>
</define>
</grammar>
was wiederum dasselbe ist wie
<grammar xmlns="http://relaxng.org/ns/structure/1.0">
<start>
<element name="addressBook">
<zeroOrMore>
<element name="card">
<ref name="card.attlist"/>
</element>
</zeroOrMore>
</element>
</start>
<define name="card.attlist">
<group>
<attribute name="name">
<text/>
</attribute>
<attribute name="email">
<text/>
</attribute>
</group>
</define>
</grammar>
weil die Kombination von Attributen per Interleave denselben Effekt hat wie die Kombination in einer Gruppe.
Es gibt noch ein paar beachtenswerte Regeln bei Mehrfachdefinitionen:
-
Alle Vorkommen einer Definition müssen denselben Wert bei
combine
haben. -
Bei einer Definition darf das Attribut
combine
weggelassen werden. Es wird dann der Wert der anderen Definitionen benutzt. -
Die Reihenfolge der Definitionen ist irrelevant.
-
Mehrere Startelemente (
start
) werden wie Definitionen behandelt und können kombiniert werden.
11. Mehrere Schemadateien
Man kann anstelle einer Schemadatei mehrere verwenden. DocBook sei hier als Beispiel erwähnt: DocBook bietet eine große Schemadatei zum herunterladen an. Möchte man eigene Erweiterungen zu DocBook hinzufügen, ändert man nicht diese Hauptdatei, sondern schreibt die Erweiterungen in eine eigene Schemadatei und verwendet die eigene als Schema für das XML-Dokument.
Es gibt zwei Arten der Referenzierung: externalRef
und include
.
Die erste Variante bietet sich an, wenn man kleinere Definitionen hat, die man aus verschiedenen Hauptdateien einbindet und die zweite Variante bietet sich in Situationen an wie im DocBook-Beispiel beschrieben.
11.1. Referenz auf externe Muster
Wenn wir eine einfache Datei haben (card.rng
):
<grammar xmlns="http://relaxng.org/ns/structure/1.0">
<start>
<ref name="card"/>
</start>
<define name="card">
<element name="card"><empty/></element>
</define>
</grammar>
kann man auf das Startelement per externalRef
zugreifen:
externalRef
<grammar xmlns="http://relaxng.org/ns/structure/1.0">
<start>
<element name="addressBook">
<zeroOrMore>
<externalRef href="card.rng"/>
</zeroOrMore>
</element>
</start>
</grammar>
Man kann dieses Muster auch bei choice
benutzen:
<choice>
<externalRef href="pattern1.rng"/>
<externalRef href="pattern2.rng"/>
</choice>
Der Fantasie sind hier kaum Grenzen gesetzt. In der Praxis findet sich jedoch etwas häufiger der folgende Mechanismus:
11.2. Zusammenführen von Schemadateien
Mit dem include
Befehl lassen sich Schemadateien zusammenführen.
Das include -Element hat zwei verschiedene Semantiken, je nach dem wie man es benutzt. In diesem Abschnitt wird include benutzt, um Definitionen zu ergänzen (combine ), im nächsten Abschnitt wird include benutzt, um Definitionen zu ersetzen. Ein feiner, aber sehr wichtiger Unterschied!
|
Es sei eine Datei gegeben, die ein Adressbuch definiert (addressbook.rng
):
<grammar xmlns="http://relaxng.org/ns/structure/1.0">
<start>
<element name="addressBook">
<zeroOrMore>
<ref name="card" />
</zeroOrMore>
</element>
</start>
<define name="card">
<element name="card">
<ref name="card.attributes" />
</element>
</define>
<define name="card.attributes">
<attribute name="name"/>
<attribute name="email"/>
</define>
</grammar>
Dieses Schema sollte nach dem Studium der letzten Abschnitte leicht zu lesen sein. Es erlaubt eine Datei der folgenden Form:
<addressBook>
<card name="..." email="..." />
<card name="..." email="..." />
...
</addressBook>
Möchten wir die Definitionen oben erweitern, fangen wir am besten mit der trivialen Datei an (schema.rng
):
include
<grammar xmlns="http://relaxng.org/ns/structure/1.0">
<include href="addressbook.rng"/>
</grammar>
In diesem Zustand ist es egal, ob gegen schema.rng
oder gegen addressbook.rng
validiert wird, weil der Inhalt identisch ist.
Erweitern wir nun die Definitionen aus addressbook.rng
in der neuen Datei, dann muss ausschließlich gegen die neue Datei validiert werden, sonst werden die Erweiterungen nicht beachtet.
Die erste Erweiterung besteht darin, dass ein optionales Attribut note
in einem Eintrag benutzt werden darf:
<grammar xmlns="http://relaxng.org/ns/structure/1.0">
<include href="addressbook.rng"/>
<define name="card.attributes" combine="interleave">
<optional>
<attribute name="note"/>
</optional>
</define>
</grammar>
Im letzten Abschnitt wurde erwähnt, dass Attribute mit choice="interleave"
verbunden werden sollten.
Damit ist nun ein optionales Attribut note
erlaubt:
<addressBook>
<card name="..." email="..." />
<card name="..." email="..." note="..." />
...
</addressBook>
11.3. Ersetzen von Definitionen
Schreibt man die neuen Definitionen in das include
-Element, so werden die alten Definitionen überschrieben. Das ist ein Unterschied zu der vorherigen Benutzung von include
.
Es sei wieder die Datei gegeben, die ein Adressbuch definiert (addressbook.rng
). Unsere neue schema.rng
ist nun wie folgt:
<grammar xmlns="http://relaxng.org/ns/structure/1.0">
<include href="addressbook.rng">
<define name="card.attributes">
<attribute name="familyName"/>
<attribute name="givenName"/>
</define>
</include>
</grammar>
Hier steht die Definition innerhalb des include
-Elements. Daher wird die Definition der erlaubten Attribute überschrieben.
Eine gültige XML-Datei hat nun die Form:
<addressBook>
<card familyName="..." givenName="..."/>
<card familyName="..." givenName="..."/>
</addressBook>
Tipp: Die include
-Anweisung darf auch das start
-Element enthalten, das dann das start
-Element der eingebundenen Datei überschreibt.
11.4. Platzhalter
Es gibt ein Element notAllowed
, das als Platzhalter für Erweiterungen dienen kann.
Im einfachsten Fall wird es so benutzt:
notAllowed
<element name="einElement">
<notAllowed/>
</element>
Wie der Name schon sagt, ist ein Element, das notAllowed
enthält, in einer XML-Datei verboten.
Es bietet sich aber folgendes Muster an, Erweiterungen einer Auswahlliste zu ermöglichen:
<grammar xmlns="http://relaxng.org/ns/structure/1.0">
<start>
<element name="auswahlliste">
<zeroOrMore>
<ref name="auswahl"/>
</zeroOrMore>
</element>
</start>
<define name="auswahl">
<element name="auswahl">
<choice>
<element name="eins"><empty /></element>
<element name="zwei"><empty /></element>
<ref name="auswahl.extras"/>
</choice>
</element>
</define>
<define name="auswahl.extras">
<notAllowed/>
</define>
</grammar>
Ein notAllowed
-Element als Teil einer Auswahl (choice
) wird einfach ignoriert.
Nun kann man die Auswahlliste in einer eigenen Datei erweitern:
<grammar xmlns="http://relaxng.org/ns/structure/1.0">
<include href="auswahlliste.rng"/>
<define name="auswahl.extras" combine="choice">
<element name="drei">
<empty />
</element>
</define>
</grammar>
11.5. parentRef
Hat man zwei Schemadateien, kann man aus der »inneren« Datei auf Definitionen in der äußeren mit parentRef
zugreifen.
Dies hier ist die äußere Datei addressbook.rng
<grammar xmlns="http://relaxng.org/ns/structure/1.0">
<start>
<ref name="addressBook" />
</start>
<define name="addressBook">
<element name="addressBook">
<externalRef href="card.rng"/>
</element>
</define>
<define name="card.attributes">
<attribute name="givenName"/>
<attribute name="familyName"/>
</define>
</grammar>
die bindet eine Datei card.rng
ein:
parentRef
<grammar xmlns="http://relaxng.org/ns/structure/1.0">
<start>
<ref name="card" />
</start>
<define name="card">
<element name="card">
<parentRef name="card.attributes"/>
<empty/>
</element>
</define>
</grammar>
In der Datei card.rng
wird Bezug auf ein Muster genommen, das in der Datei addressbook.rng
definiert sein muss.
Ohne dieses Muster ist das Schema ungültig.
12. Namensräume (Namespaces)
RELAX NG kann mit XML-Namensräumen umgehen. Damit ist nicht der Namensraum der Schemadatei gemeint, sondern die Namensräume der zu validierenden XML-Datei. Diese kann keinen oder mehrere Namensräume enthalten. Namensräume sind fester Bestandteil einer XML-Datei und daher muss das Schema diese Namensräume prüfen können.
Für diejenigen, die sich nicht so sicher sind, wie die XML-Namensräume funktionieren, gibt es hier eine Auffrischung:
12.1. Auffrischung: XML-Namensräume
Ein Elementname oder ein Attributname besteht immer aus einem lokalen Namen und einem ggf. leerem Namensraum. Der Namensraum hat zur Kennung einen (fast beliebigen) Präfix und ist ein sogenannter »uniform resource identifier« (URI).
In XML werden Namensräume zweistufig angewendet. Die erste Stufe ist es, einem Präfix einen URI zuzuweisen:
xmlns:ex="http://example.com"
Anschließend kann das Präfix wie folgt benutzt werden:
<ex:elementname />
In diesem Fall hat das Element den lokalen Namen elementname
und den Namensraum http://example.com
.
Das benutzte Präfix ist egal (in manchen Fällen spielt er dennoch eine Rolle, aber das sei hier vernachlässigt).
Der Namensraum sieht häufig aus wie eine URL, ist es aber nicht! Es ist nur ein beliebiger, möglichst eindeutiger String.
Das Präfix ist optional. Wenn es weggelassen wird, spricht man von einem Default-Namespace. Die erste Stufe hat dann die Form ohne :ex
:
xmlns="http://example.com"
die zweite Stufe dann
<elementname />
Der Namensraum (bzw. der Name) des Elements elementanme
ist in beiden Beispielen identisch.
Es sollte keine technische Unterscheidung geben zwischen den beiden Varianten mit explizitem Namensraum und mit Default-Namensraum.
Noch ein wichtiger Hinweis. Die erste Stufe (die Zuordnung eines Präfix zu dem Namensraum) erfolgt am benutzten Element selbst oder an einem der Elternelemente. Das Beispiel oben wäre vollständig also wie folgt:
<ex:elementname xmlns:ex="http://example.com" />
oder
<elternelement xmlns:ex="http://example.com">
<ex:elementname />
<elternelement>
Im letzten Fall hat das Elternelement nicht den Namensraum http://example.com
, das Kindelement schon.
Mit diesem Exkurs sollte die Anwendung von RELAX NG-Namensräumen kein Problem darstellen.
12.2. Benutzung des Attributs ns
In dem Attribut ns
kann der erwartete Namensraum einfach angegeben werden:
ns
<element name="foo" ns="http://example.com">
<empty/>
</element>
Dieses Schema passt auf folgende Elemente:
<foo xmlns="http://example.com"/>
<e:foo xmlns:e="http://example.com"/>
<example:foo xmlns:example="http://example.com"/>
aber nicht auf diese hier:
<foo/>
<e:foo xmlns:e="http://EXAMPLE.COM"/>
<example:foo xmlns:example="http://example.net"/>
Ist ds Attribut ns
leer, so wird ein leerer Namensraum erwartet.
<element name="foo" ns="">
<empty/>
</element>
passt auf diese Elemente
<foo xmlns=""/>
<foo/>
aber nicht auf folgende:
<foo xmlns="http://example.com"/>
<e:foo xmlns:e="http://example.com"/>
Das Attribut ns
muss nicht an jedem Element angegeben werden. Das Schema sucht nach dem nächsten Elternelement, das ein solches Attribut hat. So kann man einen Namensraum, der für die ganze XML-Datei gilt, auch am Wurzelelement grammar
angeben:
<grammar
xmlns="http://relaxng.org/ns/structure/1.0"
ns="http://example.com">
<start>
<element name="addressBook">
<empty/>
</element>
</start>
</grammar>
Das Element addressBook
wird im Namensraum http://example.com
erwartet.
Nochmals der Hinweis: der Eintrag xmlns="http://relaxng.org/ns/structure/1.0"
ist nur dafür da, die Schemadatei als RELAX NG Datei zu kennzeichnen und hat nichts mit dem zu überprüfenden XML-Dokument zu tun.
12.3. Namensräume in Attributen
Eine Besonderheit bei Namensräumen in Attributen ist meiner Meinung nach eine Schwachstelle der Definition von XML-Namensräumen. Die Attribute eines Elements befinden sich nicht in demselben Namensraum des umschließenden Elements. D.h. im Beispiel von oben
<elementname attrib="wert" xmlns="http://example.com" />
ist das Attribut attrib
nicht im selben Namensraum wie das Element.
Daher ist die Voreinstellung für Attribute der leere Namensraum:
<element name="addressBook" ns="http://example.com">
<zeroOrMore>
<element name="card">
<attribute name="name"/>
<attribute name="email"/>
</element>
</zeroOrMore>
</element>
ist identisch zu:
<element name="addressBook" ns="http://example.com">
<zeroOrMore>
<element name="card" ns="http://example.com">
<attribute name="name" ns=""/>
<attribute name="email" ns=""/>
</element>
</zeroOrMore>
</element>
12.4. Namensräume mit qualifizierten Namen
Anstelle des Attributs ns
wie oben beschrieben können die Namensräume auch direkt angegeben werden:
<grammar
xmlns="http://relaxng.org/ns/structure/1.0">
<start xmlns:ex="http://example.com">
<element name="ex:addressBook">
...
</element>
</start>
</grammar>
passt auf folgende XML-Datei:
<ex:addressBook xmlns:ex="http://example.com" >
...
</ex:addressBook>
Das ist hilfreich, wenn mehrere Namensräume in der zu validierenden XML-Datei vorkommen und man sich die mehrfache Angabe des Attributs ns
ersparen möchte. Die direkte Angabe mit qualifizierten Namen hat Vorrang vor der Angabe des Attributs ns
.
13. Namensklassen
Ein etwas esoterisches Kapitel. Zwar durchaus praxisrelevant, aber man muss manchmal lange auf die Konstrukte schauen, um sie zu verstehen.
anyName
<grammar
xmlns="http://relaxng.org/ns/structure/1.0">
<start>
<element name="wurzel">
<ref name="beliebig"/>
</element>
</start>
<define name="beliebig">
<element>
<anyName />
<empty/>
</element>
</define>
</grammar>
Der Name der Definition sagt schon, was hier passiert. Es ist als Kind vom Wurzelelement ein (leeres) Element mit einem beliebigen Namen erlaubt.
Das Element anyName
ist eine Namensklasse.
Es gibt noch weitere Klassen, die hier als Beispiele aufgeführt werden sollen:
except
name
<grammar xmlns="http://relaxng.org/ns/structure/1.0"> <start> <element name="wurzel"> <ref name="fastbeliebig"/> </element> </start> <define name="fastbeliebig"> <element> <anyName> <except> <name>hallo</name> </except> </anyName> <empty/> </element> </define> </grammar>
Folgendes XML-Dokument ist nicht durch das Schema gedeckt:
<wurzel>
<hallo></hallo>
</wurzel>
Weil der »verbotene« Name benutzt wurde.
Die Klasse nsName
hat ein Attribut ns
, das wie unter Namensräume beschrieben funktioniert:
nsName
<element name="card" ns="http://www.example.com">
<zeroOrMore>
<attribute>
<anyName>
<except>
<nsName/>
<nsName ns=""/>
</except>
</anyName>
</attribute>
</zeroOrMore>
<text/>
</element>
Das Element card
darf Attribute enthalten, die nicht
-
im Namensraum
http://www.example.com
sind (1. Regel, das Attributns
wird auselement
genommen) -
im leeren Namensraum sind (2. Regel)
Machmal möchte man beliebige Attribute in einem Element zulassen, bei manchen Attributen aber nur bestimmte Werte erlauben.
Ein Beispiel ist xml:space
, das die Werte default
und preserve
enthalten darf.
Das Muster, beliebige Attribute zuzulassen, ist klar:
<element name="example">
<zeroOrMore>
<attribute>
<anyName/>
</attribute>
</zeroOrMore>
</element>
Wenn man die Werte für das Attribut xml:space
nutzen will, dann muss man die Namensklasse anyName
oben einschränken:
<element name="example">
<zeroOrMore>
<attribute>
<anyName>
<except>
<name>xml:space</name>
</except>
</anyName>
</attribute>
</zeroOrMore>
<attribute name="xml:space">
<choice>
<value>default</value>
<value>preserve</value>
</choice>
</attribute>
</element>
Wird die Einschränkung weggelassen, so würde xml:space
sowohl auf das obere als auch auf das untere Muster passen.
Das ist mehrdeutig und daher nicht erlaubt.
14. Anmerkungen (Annotations)
RELAX NG hat die praktische Eigenschaft, Elemente und Attribute aus »fremden« Namensräumen einfach zu ignorieren. Das kann man benutzen, um eigene Elemente hinzuzufügen, z.B. zur Dokumentation.
<element name="addressBook"
xmlns="http://relaxng.org/ns/structure/1.0"
xmlns:a="http://relaxng.org/ns/compatibility/annotations/1.0">
<zeroOrMore>
<element name="card">
<a:documentation>Information about a single email address.</a:documentation>
<element name="name">
<text/>
</element>
<element name="email">
<text/>
</element>
</element>
</zeroOrMore>
</element>
Wird der oben genannte Namensraum für die Dokumentation benutzt, so wird dies in manchen Editoren als Tooltip gezeigt. Hier ist ein Beispiel für das Schema im oXygen XML-Editor
RELAX NG hat auch ein div
-Element, das keinerlei Bedeutung hat.
Es dient lediglich zur logischen Trennung für den Anwender und hat auf die Ausführung der Regeln keine Auswirkung.
Man könnte mit einem Namensraum selber eine Trennung vornehmen um ein Schema zu modularisieren.
div
<grammar xmlns:m="http://www.example.com/module">
<div m:name="inline">
<define name="code"> pattern </define>
<define name="em"> pattern </define>
<define name="var"> pattern </define>
</div>
<div m:name="block">
<define name="p"> pattern </define>
<define name="ul"> pattern </define>
<define name="ol"> pattern </define>
</div>
</grammar>