KAPITEL 4 Qualitätskontrolle mit Schemas

KAPITEL 4 Qualitätskontrolle mit Schemas
KAPITEL 4
Qualitätskontrolle mit Schemas
Bis jetzt haben wir über die Dinge gesprochen, die alle XML-Dokumente gemeinsam
haben. Die Wohlgeformtheitsregeln sind universal und sichern eine perfekte Kompatibilität mit allen allgemeinen Werkzeugen und APIs. Diese Homogenität der Syntax ist ein
großer Vorteil von XML. Aber gleichermaßen bedeutsam ist die Notwendigkeit, über
Möglichkeiten zu verfügen, verschiedene XML-Sprachen voneinander zu unterscheiden.
Ein Dokument soll normalerweise einer bestimmten Sprache entsprechen, und wir benötigen Möglichkeiten zur Prüfung, inwieweit es dieser Sprache entspricht.
Schemas, das Thema dieses Kapitels, sind die Schafhirten der Markup-Sprachen. Sie verhindern, dass Dokumente aus der Herde ausscheren und Ärger verursachen. Beispielsweise kann der Administrator einer Website ein Schema einsetzen, um festzustellen,
welche Webseiten gültiges XHTML darstellen und welche dieses nur vorgeben. Ein
Schema kann auch verwendet werden, um auf stimmige und eindeutige Weise die Spezifikation einer Sprache zu publizieren.
Grundkonzepte
Generell ist ein Schema eine allgemeine Repräsentation einer Klasse von Gegenständen.
Das Schema für eine Speisekarte im Restaurant könnte beispielsweise folgendermaßen
lauten: »Eine Liste der Speisen, die in einer bestimmten Einrichtung, in der man Nahrung
zu sich nehmen soll, angeboten werden.« Ein Schema kann dem Gegenstand, den es
beschreibt, ähneln, so wie ein Smiley einem wirklichen menschlichen Gesicht gleicht. Die
in einem Schema enthaltenen Informationen ermöglichen Ihnen festzustellen, ob ein
Dokument eine repräsentative Instanz eines Konzepts ist oder nicht.
Im XML-Kontext ist ein Schema ein Test für Dokumente, den sie bestehen können oder
nicht.1 Von einem Dokument, das den Test besteht, sagt man, dass es dem Schema entpricht oder dass es gültig bzw. valid ist. Das Testen des Dokuments mit einem Schema
1 In technischer Hinsicht prüfen Schemas die Dokumente Element für Element und Attribut für Attribut. Es ist
auch möglich, nur einen Teilbaum auf Gültigkeit zu prüfen und festzustellen, dass Teile gültig sind und
andere nicht. Dieser Prozess ist ziemlich komplex und geht über den Horizont dieses Buchs hinaus.
116 |
Kapitel 4: Qualitätskontrolle mit Schemas
nennt man Gültigkeitsprüfung bzw. Validierung. Ein Schema sichert, dass ein Dokument
eine Mindestmenge an Anforderungen erfüllt, und findet Mängel, die zu einer fehlerhaften Verarbeitung führen könnten. Es kann auch als eine Möglichkeit dienen, eine
Anwendung zu formalisieren, und kann veröffentlicht werden, um eine Sprache mit eindeutigen Regeln zu beschreiben.
Validierung
Ein XML-Schema ist wie ein Programm, das einem Prozessor sagt, wie er ein Dokument
lesen soll. Dieses ähnelt einem Thema, das wir später besprechen werden, den so
genannten Transformationen. Der Prozessor liest die Regeln und Deklarationen im
Schema und nutzt sie, um einen bestimmten Typ von Parser aufzubauen, einen so
genannten validierenden Parser. Ein validierender Parser nimmt als Eingabe eine XMLInstanz und liefert einen Validierungsbericht als Ausgabe. In der kleinsten Ausprägung
ist dieser Bericht nicht mehr als ein einfacher Rückgabewert, der wahr ist, wenn das
Dokument gültig ist, und anderenfalls falsch. Der Parser kann optional ein PSVI (Post
Schema Validation Infoset) erzeugen, das Informationen über Datentypen und Struktur
enthält, die für die Weiterverarbeitung genutzt werden können.
Die Validierung vollzieht sich auf mindestens vier Stufen:
Struktur
Die Verwendung und Platzierung von Markup-Elementen und -Attributen.
Datentypen
Muster für Zeichendaten (z.B. Zahlen, Datumswerte, Text).
Vollständigkeit
Der Status von Verknüpfungen zwischen Knoten und Ressourcen.
Verfahrensregeln
Diverse Tests wie Rechtschreibprüfung, Prüfsummenergebnisse usw.
Die Validierung der Struktur ist der wichtigste Schritt, und Schemas sind am besten dazu
geeignet, diese Stufe umzusetzen. Die Prüfung der Datentypen ist oft hilfreich, insbesondere bei »datenbankartigen« Dokumenten, wird aber nicht so weitreichend unterstützt.
Die Prüfung auf Vollständigkeit ist weniger verbreitet und etwas schwer zu definieren.
Verfahrensregeln werden oft von Anwendungen geprüft.
Ein Geschichte der Schema-Sprachen
Es gibt viele verschiedene Arten von XML-Schemas, und alle haben ihre Stärken und
Schwächen.
DTD
Die älteste und am umfassendsten unterstützte Schema-Sprache ist die DTD (Document
Type Definition). DTDs wurden SGML entlehnt und in einer vereinfachten Form in die
Grundkonzepte |
117
XML-Kern-Empfehlung eingeschlossen. Obwohl eine DTD zum Lesen und zur Verarbeitung eines XML-Dokuments nicht erforderlich ist, kann sie eine nützliche Komponente
für ein Dokument sein, die die Möglichkeit bietet, makroartige Einheiten (Entities) und
andere Annehmlichkeiten zu definieren. DTDs waren die erste umfassend eingesetzte
Methode zur formalen Definition von Sprachen wie beispielsweise HTML.
W3C XML Schema
Sobald XML populär wurde, begannen Entwickler eine Alternative zu DTDs zu fordern.
DTDs unterstützen keine Namensräume, die erst nach der XML 1-0-Spezifikation auftauchten. Außerdem kennen sie nur eine schwache Datentypisierung, weil sie sich hauptsächlich auf das Markup konzentrieren. Das W3C bildete eine Arbeitsgruppe für XML
Schema und begann, Vorschläge entgegenzunehmen, aus denen später die W3C-Empfehlung zu XML Schema wurden.
Im Folgenden finden Sie einige der Vorschläge, die von verschiedenen Gruppen gemacht
wurden.
XML-Data
Dieser Vorschlag wurde von Arbortext, DataChannel, Inso Corporation, Microsoft
und der University of Edinburgh im Januar 1998 eingereicht. Diese technische
Denkschrift stellte viele der Funktionen vor, die in W3C Schema eingebaut wurden,
und viele andere, die nicht eingebaut wurden, wie beispielsweise einen Mechanismus zur Deklaration von Entities und die Unterstützung für objektorientierte Programmierung. Microsoft hat eine Version davon implementiert, die XDR (XMLData Reduced) genannt wird.
Document Content Description (DCD)
IBM, Microsoft und Textuality reichten diesen Vorschlag im Juli 1998 ein. Es war
ein Versuch, XML-Data in RDF (Resource Description Framework) zu integrieren.
Er führte den Gedanken ein, Elemente und Attribute austauschbar zu gestalten.
Schema for Object-Oriented XML (SOX)
Wie der Name schon andeutet, wurde diese technische Denkschrift von den Anforderungen der Programmierung beeinflusst. Sie umfasste Konzepte wie Vererbung
und Parameter. Sie wurde im Juli 1998 von Veo Systems/Commerce One eingereicht, die eine Implementierung geschaffen haben, die sie heute einsetzen.
Document Definition Markup Language (DDML)
Dieser Vorschlag erwuchs aus den Diskussionen der XML-Dev-Mailing-Liste. Man
nahm die in DTDs ausgedrückten Informationen und formatierte sie als XML.
Unterstützung für Datentypen sollte mit anderen Spezifikationen geschaffen werden.
Auf Basis dieser Vorschläge schuf die W3C XML Schema-Arbeitsguppe im Mai 2001 eine
Empfehlung, die aus drei Teilen bestand (XMLS0, XMLS1 und XMLS2), die Primer
(Grundlagen), Structures (Strukturen) und Datatypes (Datentypen) genannt wurden.
Obwohl einige der Vorgänger immer noch verwendet werden, kamen die beteiligten Par-
118 |
Kapitel 4: Qualitätskontrolle mit Schemas
teien überein, dass alle in den Ruhestand geschickt und stattdessen das eine, wahre W3C
XML Schema verwendet werden sollte.
RELAX NG
Ein unabhängiges Unternehmen einiger kreativer Geister schuf eine andere Schema-Sprache namens RELAX NG (was »relaxing« ausgesprochen wird). Darin wurden RELAX
(Regular Language Description for XML) und TREX (Tree Regular Expressions for XML)
verschmolzen. Wie W3C Schema unterstützt es Namensräume und Datentypen. Es enthält aber auch einige einzigartige Neuerungen wie die Austauschbarkeit von Elementen
und Attributen in Inhaltsbeschreibungen und flexiblere Inhaltsmodelle.
RELAX, ein Produkt der von Murata Makoto geleiteten INSTAC XML-Arbeitsgruppe
der Japanese Standard Association, sollte eine einfache Alternative zu XML Schema sein.
»Sie sind komplexe Spezifikationen leid?«, fragt die Homepage. »Jetzt können Sie relaxen!« Im Unterschied zu W3C Schema, mit seinem umfassenden Anwendungsbereich
und dem erheblichen Lernaufwand, ist RELAX einfach zu implementieren und zu verwenden.
Sie können sich RELAX als (in XML formatierte) DTDs vorstellen, die um Datentypen
erweitert wurden, die aus dem Satz an Datentypen von W3C Schema geerbt wurden. Die
Folge ist, dass es fast keine Mühe macht, von DTDs zu RELAX zu migrieren, und dass es
auch ziemliche einfach ist, von RELAX zu W3C Schema zu migrieren, falls Sie das später
wollen. Es bietet zwei Stufen der Entsprechung. »Classic« ist wie eine um eine Prüfung
der Datentypen erweiterte Validierung mit einer DTD, »Fully relaxed« fügt noch weitere
Features hinzu.
Die theoretische Grundlage von RELAX ist eine Baum-Verarbeitung über Hedge Automata. Obwohl sie nichts über Hedge Automata wissen müssen, um RELAX oder RELAX
NG einzusetzen, machen diese mathematischen Grundlagen es einfacher, mit RELAX
NG effizienten Code zu schreiben. Murata Makoto hat eine RELAX NG-Implementierung vorgestellt, die 27 KByte auf einem Handy benötigt und dabei sowohl Schema als
auch Parser umfasst.
Zu ungefähr der gleichen Zeit, zu der auch RELAX Gestalt annahm, entwickelte James
Clark von der Thai Opensource Software TREX. Es erwuchs der Arbeit an XDuce, einer
typisierten Programmiersprache für die Bearbeitung von XML-Markup und -Daten.
XDuce (ein Zusammenziehung von »XML« und »transduce«, umwandeln) ist eine Transformationssprache, die als Eingabe ein XML-Dokument nimmt, die Daten herauszieht
und ein anderes Dokument in XML oder einem anderen Format ausgibt. TREX nutzt das
Typ-System von XDuce, fügt noch andere Funktionen hinzu und vereint sie in einer
XML-basierten Programmiersprache. XDuce erschien im März 2000. TREX folgte im
Januar 2001.
Wie RELAX nutzt TREX eine sehr klare und flexible Sprache, die einfach zu erlernen, zu
lesen und zu implementieren ist. Die Definitionen von Elementen und Attributen sind
austauschbar, was die Syntax erheblich vereinfacht. Es bietet vollständige Unterstützung
Grundkonzepte |
119
von Namensräumen, gemischten Inhalten und ungeordneten Inhalten – alles Dinge, die
DTDs fehlen oder mit DTDs nur schwer zu bewirken sind. Wie RELAX nutzt es den
Datentyp-Satz von W3C XML Schema und verringert den Lernaufwand noch weiter.
RELAX NG (NG steht für »neue Generation«) kombiniert die besten Eigenschaften von
RELAX und TREX in einer XML-basierten Schema-Sprache. Es wurde im Mai 2001 von
einem von James Clark geleiteten technischen Komitee von OASIS angekündigt, und
Murata Makoto überwacht seine Entwicklung. Es wurde als Vorschlag für einen internationalen Standard von der ISO/IEC angenommen.
Schematron
Man sollte auch Schematron kennen. Es wurde 1999 zuerst von Rick Jelliffe vom Academia Sinicia Computing Centre vorgestellt. Es nutzt XPath-Ausdrücke, um Gültigkeitsregeln zu definieren, und ist eine der flexibelsten Schema-Sprachen, die es zurzeit gibt.
Brauchen Sie Schemas?
Es scheint so, als seien Schemas eine Menge Arbeit. Das ist richtig. Wenn Sie ein Schema
entwerfen, müssen Sie scharf darüber nachdenken, wie Ihre Sprache strukturiert ist.
Wenn Ihre Sprache sich weiterenwickelt, müssen Sie Ihr Schema aktualisieren, so wie Sie
jedes andere Programm warten müssen. Es wird zu Fehlern kommen, Versionen müssen
nachgehalten werden, und gelegentlich muss man sogar einen vollständigen Neuentwurf
in Betracht ziehen. Ist ein Schema also wirklich all diese zusätzliche Arbeit wert?
Lassen Sie uns zunächst die Vorteile betrachten:
• Ein Schema kann als eine Spezifikation fungieren, die publiziert wird. Es gibt einfach keine bessere Möglichkeit, eine Sprache zu beschreiben, als mit einem Schema.
Ein Schema ist schließlich ein »Ja-oder-Nein-Test« für die Gültigkeit von Dokumenten. Es soll für Menschen und Maschinen gleichermaßen lesbar sein. DTDs erinnern sehr stark an die BNF-Grammatiken (Backus-Naur-Form-Grammatiken), die
verwendet werden, um Programmiersprachen zu beschreiben. Andere Schemas wie
RELAX NG sind intuitiv und leicht zu lesen. Wenn Sie also Informationen darüber
verbreiten müssen, wie eine Markup-Sprache verwendet werden muss, ist ein
Schema dafür kein schlechtes Mittel.
• Ein Schema sorgt dafür, dass gröbere Fehler abgefangen werden. Sicher, es gibt
bereits die Wohlgeformtheitsregeln, die Ihre Programme vor grundlegenden Syntaxfehlern schützen. Aber gehen diese weit genug? Was ist, wenn ein erforderliches
Feld mit Informationen fehlt oder wenn jemand einen Elementnamen immer in der
gleichen Form falsch schreibt? Das sind Dinge, die nur ein validierender Parser entdecken kann.
• Ein Schema ist portabel und effizient. Eine Möglichkeit ist, für die Prüfung von
Dokumenten ein Programm zu schreiben. Aber das ist nicht unbedingt die beste
Wahl. Programme können plattformabhängig sein, sind schwer zu installieren und
120 |
Kapitel 4: Qualitätskontrolle mit Schemas
zu umfangreich für eine Übertragung. Ein Schema aber ist kompakt und nur für
einen Zweck optimiert: die Validierung von Dokumenten. Es ist einfach, jemandem
ein Schema zu übergeben. Sie können dabei sicher sein, dass es bei ihm funktioniert, weil die Syntax durch eine Standard-Spezifikation gesichert ist. Und weil viele
Schemas auf XML basieren, können sie in XML-Editoren bearbeitet und von Wohlgeformtheitsprüfern geprüft werden.
• Ein Schema ist erweiterbar. Schemas wurden so entworfen, dass sie Modularität
unterstützen. Wenn Sie eine Gruppe ähnlicher Sprachen warten wollen oder verschiedene Versionen von ihnen, können Sie gemeinsame Komponenten teilen. Beispielsweise können Sie mit DTDs allgemeine Entities für Sonderzeichen oder häufig
genutzte Textbausteine deklarieren. Diese können so hilfreich sein, dass Sie sie in
andere Sprachen exportieren wollen.
Der Einsatz von Schemas hat aber auch ein paar Nachteile:
• Ein Schema verringert die Flexibilität. Die Ausdrucksfähigkeit von Schemas variiert
erheblich, und jeder Standard neigt dazu, seine Mängel zu haben. DTDs beispielsweise sind berüchtigt für ihre Inkompatibilität mit Namensräumen. Sie sind auch
nicht dazu in der Lage, ein Inhaltsmodell zu definieren, in dem bestimmte Kindelemente erforderlich sind, aber in einer beliebigen Reihenfolge erscheinen können.
Obwohl andere Schema-Sprachen einen Fortschritt gegenüber DTDs darstellen,
haben jedoch auch sie immer die ein oder andere Grenze.
• Schemas können Hindernisse für Autoren sein. Trotz der Fortschritte bei den XMLEditoren mit ausgefeilten grafischen Benutzerschnittstellen wird das Schreiben von
XML niemals so sein wie das Schreiben in einem traditionellen Textverarbeitungsprogramm. Die Zeit, die man damit verbringt, darüber nachzudenken, welches Element man in einem bestimmten Kontext verwenden soll, ist Zeit, die man nicht
mehr damit verbringen kann, über den Inhalt selbst nachzudenken. Der aber ist
eigentlich der Grund dafür, dass man überhaupt schreibt. Einige Editoren bieten ein
kontextabhängiges Menü, in dem man die Elemente auswählen kann. Für einen
normalen Autor kann das je nach Sprache und Werkzeug trotzdem noch verwirrend und frustrierend sein.
• Sie müssen es warten. Mit einem Schema besitzen Sie ein weiteres Werkzeug, das
Sie debuggen und warten müssen. Wie Programme hat es Fehler, Versionen und
sogar seine eigene Dokumentation. Man hat ein Schema unglaublich schnell beschädigt, indem man eine importierte Komponente löscht oder einen Syntaxfehler einbaut. Ältere Dokumente können auf einmal ihre Gültigkeit verlieren, wenn Sie ein
Schema aktualisieren. Das zwingt Sie dazu, im Nachhinein Veränderungen an ihnen
vorzunehmen. Ein Lichtblick ist, dass außer DTDs die meisten Schema-Sprachen
XML-basiert sind und Sie so einen XML-Editor nutzen können, um Änderungen
vorzunehmen.
• Der Entwurf eines Schemas ist eine schwierige Aufgabe. Schemas sind Dokumente,
deren Entwicklung eine verzwickte Angelegenheit sein kann. Sie müssen genau darüber nachdenken, wie die einzelnen Elemente zusammenspielen, welche Daten einGrundkonzepte |
121
gegeben werden und ob es Sonderfälle gibt, die berücksichtigt werden müssen.
Wenn Sie gerade mit dem Entwurf einer Sprache begonnen haben, wird es noch
viele Anforderungen geben, die Ihnen erst bewusst werden, wenn Sie beginnen, die
Sprache einzusetzen. Eine Art Henne-Ei-Problem sozusagen.
Um sich die Entscheidung leichter zu machen, sollten Sie das Problem auf folgende
Weise angehen. Ein Schema ist wesentlich ein Werkzeug für die Qualitätskontrolle.
Wenn Sie hinreichend sicher sein können, dass Ihre Dokumente so gut sind, dass sie problemlos verarbeitet werden können, benötigen Sie keine Schemas. Wenn Sie jedoch die
zusätzliche Absicherung benötigen, dass Ihre Dokumente vollständig und richtig strukturiert sind, und die Arbeit, die Sie sparen, weil Sie sie nicht beim Beheben von Fehlern verschwenden, die zusätzliche Arbeit aufwiegt, die Sie in die Wartung eines Schemas
stecken müssen, sollten Sie eins in Erwägung ziehen.
Bedenken sollten Sie, ob an der Erzeugung der Dokumente Menschen beteiligt sind.
Egal, wie sorgsam wir sind, neigen wir Menschen dazu, Fehler zu machen. Aber Dokumente, die von Programmen erzeugt werden, sind in der Regel sehr vorhersagbar und
müssen wahrscheinlich nie validiert werden.
Das Schwierigste ist aber nicht die Antwort auf die Frage, ob Sie ein Schema brauchen,
sondern die Antwort auf die Frage, welchen Standard Sie verwenden sollen. Es gibt ein
paar wertvolle Auswahlmöglichkeiten, und diese werde ich Ihnen im verbleibenden
Kapitel vorstellen. Ich hoffe, dass ich Ihnen genug Informationen biete, damit Sie entscheiden können, welches die richtige Wahl für Ihre Anwendung ist.
DTDs
Das ursprüngliche XML-Dokumentmodell ist die DTD (Document Type Definition).
DTDs gab es eigentlich schon vor XML. Sie sind eine reduzierte Hinterlassenschaft von
SGML, deren Kern-Syntax aber fast vollständig intakt geblieben ist. Die folgenden
Punkte beschreiben, wie eine DTD einen Dokumenttyp beschreibt.
• Eine DTD deklariert eine Menge von erlaubten Elementen. Sie können keine anderen Elementnamen benutzen als die aus diesem Satz. Stellen Sie sich dies als das
»Vokabular« der Sprache vor.
• Eine DTD definiert ein Inhaltsmodell für jedes Element. Das Inhaltsmodell ist ein
Muster, das besagt, welche Elemente oder Daten sich in welcher Reihenfolge und in
welcher Anzahl innerhalb eines Elements befinden dürfen und ob das Pflicht oder
optional sind. Stellen Sie sich dies als »Grammatik« der Sprache vor.
• Sie deklariert für jedes Element eine Menge von zulässigen Attributen. Jede AttributDeklaration definiert den Namen, den Datentyp, die Standardwerte (soweit vorhanden) und das Verhalten des Attributs (z.B. ob es Pflicht oder optional ist).
• Sie stellt eine Vielzahl von Mechanismen zur Verfügung, um die Verwaltung des
Modells zu erleichtern, beispielsweise die Benutzung von Parameter-Entities sowie
die Fähigkeit, Teile des Modells aus einer externen Datei zu importieren.
122 |
Kapitel 4: Qualitätskontrolle mit Schemas
Der Dokumentprolog
Gemäß der XML-Empfehlung sollen alle externen geparsten Entities (einschließlich
DTDs) mit einer Text-Deklaration beginnen. Diese sieht aus wie eine XML-Deklaration,
schließt aber explizit das standalone-Property aus. Wenn Sie eine andere Zeichenkodierung als das standardmäßige UTF-8 oder oder eine andere XML-Versionsnummer als das
standardmäßige 1.0 angeben müssen, würden Sie das hier tun. Im umlautgeplagten deutschen Sprach- und Kulturraum würde man das nutzen, um die auch in der DTD wahrscheinlich erforderliche ISO-8859-1-Zeichenkodierung anzugeben.
Wenn Sie in der DTD einen Zeichensatz angeben, hat dies nicht automatisch Auswirkungen auf die XML-Dokumente, die diese DTD nutzen.
XML-Dokumente müssen ihre Kodierung in ihrem jeweiligen Dokumentprolog angeben.
Nach der Text-Deklaration endet jedoch die Ähnlichkeit mit gewöhnlichen Dokumentprologen. Externe geparste Entities, DTDs eingeschlossen, dürfen keine DokumenttypDeklaration enthalten.
Deklarationen
Eine DTD ist ein Satz von Regeln oder Deklarationen. Jede Deklaration fügt der Sprache,
die Sie beschreiben, ein neues Element, einen Satz von Attributen, ein Entity oder eine
Notation hinzu. DTDs können mit Parameter-Entities kombiniert werden – eine Technik, die als Modularisierung bezeichnet wird. Sie können auch innerhalb der internen
Teilmenge des Dokuments Deklarationen hinzufügen.
Die Reihenfolge der Deklarationen ist in zwei Situationen von Bedeutung: Erstens, wenn
es redundante Entitiy-Deklarationen gibt, ist die zuerst aufgeführte vorrangig, und alle
anderen werden ignoriert.2 Dies zu wissen ist wichtig, wenn Sie entweder in der internen
Teilmenge oder durch Staffelung von DTDs Deklarationen überschreiben. Als Zweites ist
die Reihenfolge von Bedeutung, wenn in Deklarationen Parameter-Entities verwendet
werden; diese müssen deklariert werden, bevor sie als Referenzen benutzt werden können.
In Bezug auf Whitespace ist die Syntax von Deklarationen flexibel. Mit Ausnahme der
Zeichenfolge am Anfang, die den Deklarationstyp kennzeichnet, können Sie überall
zusätzlichen Whitespace einfügen.
2 Nur Entity-Deklarationen können redundant sein, ohne dass das zu einem Gültigkeitsfehler führt. Wird ein
Elementtyp mehrfach deklariert, führt das dazu, dass die DTD (und alle Dokumente, die sie nutzen) ungültig
werden.
DTDs |
123
Beispielsweise sind alle folgenden Formen zulässig:
<!ELEMENT
dingchen
<!ELEMENT
dingchen
ALL>
<!ELEMENT dingchen (
ALL>
foo
bar
zap
|
|
)*>
Ein Beispiel
Stellen Sie sich folgendes Szenario vor: Sie sammeln Informationen über eine Gruppe von
Leuten. Die Daten, die Sie erhalten, werden einem Programm übergeben, das sie verarbeitet und in einer Datenbank speichert. Sie brauchen ein schnelles Mittel, um festzustellen, ob alle erforderlichen Informationen vorhanden sind, bevor sie die Einsendungen
akzeptieren können. Dazu werden wir eine DTD nutzen.
Die Informationen in diesem Beispiel werden Volkszählungsdaten sein. Ihr Personal
streift durch die Nachbarschaft, befragt Familien und gibt die Daten auf Laptops ein. Es
verwendet einen XML-Editor, der mit einer DTD konfiguriert ist, die Sie geschrieben
haben, um Ihre Sprache, CensusML, zu modellieren. Später übertragen die Mitarbeiter
alle CensusML-Dokumente an eine zentrale Sammelstelle, wo sie über Nacht verarbeitet
werden.
Beispiel 4-1 zeigt, wie ein typisches gültiges CensusML-Dokument ohne Dokument-Prolog aussehen sollte. Ein Dokument repräsentiert die Befragung einer Familie. Es enthält
ein Datum, eine Adresse und eine Liste der Personen, die dort leben. Von den Personen
wollen wir den vollständigen Namen sowie Alter, Beschäftigungsverhältnis und
Geschlecht wissen. Zusätzlich nutzen wir für die Personen Identifikationsnummern, um
sicherzustellen, dass wir nicht aus Versehen jemanden mehrfach eintragen.
Beispiel 4-1: Ein typisches CensusML-Dokument
<?xml version="1.0" encoding="ISO-8859-1"?>
<census-datensatz erhoben-durch="3163">
<datum><jahr>2003</jahr><monat>10</monat><tag>11</tag></datum>
<adresse>
<straße>Stolpersteinweg</straße>
<ort>Smaragdstadt</ort>
<bezirk>Grünau</bezirk>
<staat>Oz</staat>
<postleitzahl>885JKL</postleitzahl>
</adresse>
<person beschäftigt="vollzeit" pid="P270405">
<name>
<vorname>Manni</vorname>
<nachname>Vip</nachname>
<junior/>
</name>
<alter>39</alter>
124 |
Kapitel 4: Qualitätskontrolle mit Schemas
Beispiel 4-1: Ein typisches CensusML-Dokument (Fortsetzung)
<geschlecht>männlich</geschlecht>
</person>
<person beschäftigt="teilzeit" pid="P273882">
<name>
<vorname>Mammi</vorname>
<nachname>Vip</nachname>
</name>
<alter>36</alter>
<geschlecht>weiblich</geschlecht>
</person>
<person pid="P472891">
<name>
<vorname>Minni</vorname>
<nachname>Vip</nachname>
</name>
<alter>11</alter>
<geschlecht>weiblich</geschlecht>
</person>
</census-datensatz>
Lassen Sie uns also anfangen, die DTD zusammenzusetzen. Die erste Deklaration ist die
für das Dokumentelement:
<!ELEMENT census-datensatz
(datum, adresse, person+)>
Das erzeugt die ersten Regeln für die CensusML-Sprache: (1) Es gibt ein Element namens
census-datensatz, und (2) es muss ein datum-Element, ein adresse-Element und mindestens ein person-Element enthalten. Lassen Sie irgendeines dieser Elemente aus oder
schreiben sie in einer anderen Reihenfolge, wird das Dokument ungültig.
Beachten Sie, dass die Deklaration nicht wirklich festlegt, dass census-datensatz als
Dokumentelement verwendet werden muss. Eigentlich kann eine DTD nicht festlegen,
welches Element die Wurzel eines Dokuments ist. Möglicherweise gehen Sie dieses als
Mangel an, da Sie so nicht verhindern können, dass jemand ein unvollständiges Dokument einreicht, das nur ein person-Element enthält und sonst nichts. Andererseits kann
man es aber auch als eine bestimmte Eigenschaft von DTDs betrachten, die es ermöglicht, dass eine DTD das Modell für mehr als einen Dokumenttyp enthält. DocBook beispielsweise stützt sich auf diese Eigenschaft, um verschiedene Modelle von Dokumenten
zu unterstützen. Bei einem Buch wird das book-Element als Wurzel verwendet, bei einem
Artikel hingegen das article-Element. Sie sollten dieses Schlupfloch aber auf alle Fälle
im Auge behalten.
Jetzt sollten wir die Attribute für dieses Element deklarieren. Es gibt nur eins, erhobendurch, das angibt, welcher Volkszähler dieses Dokument aufgenommen hat. Sein Typ ist
CDATA (Zeichendaten, character data). Wir geben an, dass es erforderlich ist (required),
weil es wichtig ist zu wissen, wer die Daten einreicht, damit man verhindern kann, dass
irgendjemand in böser Absicht gefälschte Datensätze einreicht. Hier sehen Sie die Attributliste für census-datensatz:
DTDs |
125
<!ATTLIST census-datensatz
erhoben-durch
CDATA
#REQUIRED>
Als Nächstes deklarieren wir das datum-Element. Die Reihenfolge der Element-Deklarationen spielt keine Rolle. Alle Deklarationen werden in den Speicher des Parsers eingelesen, bevor die eigentliche Validierung beginnt. Deswegen ist es nur erforderlich, dass
tatsächlich für jedes im Dokument auftauchende Element eine Deklaration vorhanden
ist. Ich aber liebe es, wenn die Dinge geordnet sind und alles in einer ähnlichen Reihenfolge angegeben wird. Deswegen gibt es hier die nächste Gruppe mit Deklarationen:
<!ELEMENT
<!ELEMENT
<!ELEMENT
<!ELEMENT
datum (jahr, monat, tag)>
jahr #PCDATA>
monat #PCDATA>
tag #PCDATA>
Das Literal #PCDATA steht für Zeichendaten und passt auf null oder mehr Zeichen. Jedes
Element mit dem Inhaltsmodell #PCDATA kann Zeichendaten enthalten, aber keine Elemente. Man könnte die Elemente jahr, monat und tag also als Datenfelder bezeichnen.
Das Element datum hingegen muss Elemente enthalten und darf keine Zeichendaten enthalten.3
Kümmern wir uns jetzt um die Adresse. adresse ist ebenso wie datum ein Container für
Elemente. Die meisten seiner Unterelemente sind einfache Datenfelder (die nur Zeichendaten als Inhalt haben), aber ein Element, straße, hat gemischten Inhalt. Hier sind die
Deklarationen:
<!ELEMENT adresse
(straße, ort, bezirk, staat, postleitzahl)>
<!ELEMENT straße (#PCDATA | gebäude)*>
<!ELEMENT ort #PCDATA>
<!ELEMENT bezirk #PCDATA>
<!ELEMENT staat #PCDATA>
<!ELEMENT postleitzahl #PCDATA>
<!ELEMENT gebäude #PCDATA>
Die Deklaration für straße folgt dem Muster, das bei allen Elementen mit gemischtem
Inhalt verwendet wird. #PCDATA muss an erster Stellen stehen. Erst danach dürfen die
zulässigen Unterelemente angeführt werden, die voneinander durch senkrechte Striche
(|) getrennt werden. Das Sternchen (*) ist hier erforderlich. Es bedeutet, dass das, was
davor steht, null Mal oder mehrfach vorkommen kann. Folglich sind die Zeichendaten
ebenso optional wie alle Elemente, mit denen sie durchsetzt sein können.
Leider gibt es keine Möglichkeit zu fordern, dass eine Element mit gemischtem Inhalt
Zeichendaten enthalten muss. Der Volkszähler könnte das straße-Element auch leer lassen, und der validierende Parser wäre dennoch zufrieden. Es ist nicht erlaubt, das Sternchen (*) in ein Plus (+) zu ändern, um zu fordern, dass irgendwelche Zeichendaten
3 Es können Whitespace-Zeichen verwendet werden, um das Markup besser lesbar zu machen. Diese werden
bei der Validierung ignoriert.
126 |
Kapitel 4: Qualitätskontrolle mit Schemas
vorkommen müssen. Weil sie die Validierung einfach und schnell machen sollen, kümmern sich DTDs bei Zeichendaten nie um die tatsächlichen Details.
Unsere letzte Aufgabe ist, die Elemente und Attribute zu deklarieren, aus denen ein person-Element zusammengesetzt ist. Hier ist ein Versuch für die Element- und AttributDeklarationen:
<!ELEMENT person (name, alter, geschlecht)>
<!ELEMENT name (vorname, nachname, (junior | senior)?)>
<!ELEMENT alter #PCDATA>
<!ELEMENT geschlecht #PCDATA>
<!ELEMENT vorname #PCDATA>
<!ELEMENT nachname #PCDATA>
<!ELEMENT junior EMPTY>
<!ELEMENT senior EMPTY>
<!ATTLIST person
pid
ID
#REQUIRED
beschäftigt (vollzeit|teilzeit) #IMPLIED>
Das Inhaltsmodell für diesen Container ist etwas komplexer. Vor- und Nachname sind
erforderlich, aber es gibt die Option, nach ihnen eine zusätzliche Information (»Junior«
oder »Senior«) anzugeben. Diese Zusatzangaben werden hier mit dem Schlüsselwort
EMPTY als leere Elemente deklariert. Das Fragezeichen gibt an, dass sie optional sind,
schließlich ist nicht jeder Junior oder Senior. Vielleicht wäre es genauso einfach, daraus
ein Attribut namens zusatzangabe mit den Werten junior oder senior zu machen. Aber
ich entschloss mich, es auf diese Weise zu machen, weil ich Ihnen zeigen wollte, wie man
ein leeres Element deklariert. Außerdem ist das Markup nicht so vollgestopft, wenn man
hier ein Element verwendet. Unser Container-Element besitzt ja bereits zwei Attribute.
Das Attribut, das zuerst deklariert wird, ist ein erforderlicher Person-Identifier, pid. Sein
Typ ist ID. Das sagt validierenden Parsern, dass es ein im Geltungsbereich des Dokuments eindeutiger Identifier ist. Kein anderes Element darf ein ID-Attribut mit diesem
Wert haben. Das heißt, dass der Parser den Fehler bemerkt, wenn ein Volkszähler eine
Person zufälligerweise zweimal aufnimmt. Der Parser meldet dann, dass das Dokument
nicht gültig ist. Das kann der Parser aber nur innerhalb des Geltungsbereichs dieses
Dokument tun. Es verschafft uns also noch nicht die Möglichkeit zu verhindern, dass ein
Volkszähler dieselbe Person in einem anderen Dokument aufnimmt.
Attribute vom Typ ID haben noch andere Grenzen. Es gibt nur einen Geltungsbereich für
alle ID-Attribute. Das bedeutet, dass Sie, auch wenn Sie sie auf verschiedene Weise verwenden wollen (beispielsweise ein ID-Attribut für die Adresse und eins für die Person),
nicht denselben String in beiden Elementtypen verwenden können. Eine Lösung dafür
kann sein, dem jeweiligen Identifier einen Code voranzustellen, indem Sie beispielsweise
bei Adressen »WOHNORT-38225« und bei Personen »PID-489284« verwenden und so
im Prinzip selbst getrennte Geltungsbereiche für unterschiedliche ID-Attribute schaffen.
Beachten Sie, dass ID-Attribute wie XML-Elemente und Attributnamen immer mit einem
Buchstaben oder einem Unterstrich anfangen müssen.
DTDs |
127
Das andere Attribut, beschäftigt, ist ebenfalls optional. Das wird mit dem Schlüsselwort
#IMPLIED angezeigt. Es ist gleichzeitig ein Aufzählungstyp. Das heißt, dass es eine Menge
von zulässigen Werten gibt (vollzeit und teilzeit). Hat ein Attribut einen anderen
Wert, führt das zu einem Validierungsfehler.
Beispiel 4-2 zeigt die vollständige DTD.
Beispiel 4-2: Die CensusML-DTD
<!-Census Markup Language
(Verwenden Sie <census-datensatz> als Dokumentelement.)
-->
<!ELEMENT census-datensatz (datum, adresse, person+)>
<!ATTLIST census-datensatz
erhoben-durch
CDATA
#REQUIRED>
<!-- Das Datum, an dem die Informationen aufgenommen wurden. -->
<!ELEMENT datum (jahr, monat, tag)>
<!ELEMENT jahr #PCDATA>
<!ELEMENT monat #PCDATA>
<!ELEMENT tag #PCDATA>
<!-- Adressinformationen -->
<!ELEMENT adresse
(straße, ort, bezirk, staat, postleitzahl)>
<!ELEMENT straße (#PCDATA | gebäude)*>
<!ELEMENT ort #PCDATA>
<!ELEMENT bezirk #PCDATA>
<!ELEMENT staat #PCDATA>
<!ELEMENT postleitzahl #PCDATA>
<!-- Informationen zur Person -->
<!ELEMENT person (name, alter, geschlecht)>
<!ELEMENT name (vorname, nachname, (junior | senior)?)>
<!ELEMENT alter #PCDATA>
<!ELEMENT geschlecht #PCDATA>
<!ELEMENT vorname #PCDATA>
<!ELEMENT nachname #PCDATA>
<!ELEMENT junior EMPTY>
<!ELEMENT senior EMPTY>
<!ATTLIST person
pid
ID
#REQUIRED
beschäftigt (vollzeit|teilzeit) #IMPLIED>
Tipps für den Entwurf und die Anpassung von DTDs
Der Entwurf und der Aufbau einer DTD ist zu einem eine Wissenschaft zum anderen
eine Kunst. Die Grundkonzepte sind eigentlich einfach, aber die Arbeit mit einer großen
DTD – Hunderte von Element- und Attribut-Deklarationen zu warten und sie dabei lesbar und fehlerfrei zu halten – kann eine Herausforderung sein. Dieser Abschnitt bietet
128 |
Kapitel 4: Qualitätskontrolle mit Schemas
eine Sammlung von Hinweisen und praktischen Erfahrungsschätzen, die Ihnen vielleicht
helfen. Der darauf folgende Abschnitt zeigt ein konkretes Beispiel, das diese Erfahrungen
zur Anwendung bringt.
Halten Sie die DTD geordnet
DTDs sind wegen ihrer schlechten Lesbarkeit berüchtigt, aber eine gute Organisation ist
immer hilfreich. Ein paar zusätzliche Minuten damit verbracht, aufzuräumen und Kommentare zu schreiben, können Ihnen später Stunden mit Kopfzerbrechen ersparen. Häufig ist eine DTD ihre eigene Dokumentation. Wenn Sie also erwarten, dass sie von
anderen genutzt wird, ist sauberer Code doppelt wichtig.
Ordnen Sie Deklarationen nach ihrer Funktion an
Halten Sie die Deklarationen in nach Zweck getrennten Abschnitten. Bei kleinen
DTDs erleichtert das den Überblick über die Datei. Bei großen DTDs sollten Sie die
Deklarationen vielleicht sogar in separate Module aufteilen. Einige Kategorien,
anhand deren man die Deklarationen gruppieren kann, sind Block-, Inline- und Hierarchie-Elemente, Teile von Tabellen, Listen usw. In Beispiel 4-4 werden die Deklarationen nach ihrer Funktion gruppiert (Block-, Inline- und Hierarchie-Elemente).
Whitespace
Polstern Sie Ihre Deklarationen mit reichlich Leerzeichen und Tabulatoren aus.
Inhaltsmodelle und Attributlisten leiden unter einer sehr gedrängten Syntax. Wenn
Sie zwischen den einzelnen Teilen Zwischenräume lassen oder für diese sogar
jeweils eine eigene Zeile verwenden, tragen Sie mit dazu bei, dass diese verständlicher werden. Rücken Sie die Zeilen innerhalb von Deklarationen ein, damit die
Begrenzungen klarer hervortreten. Fügen Sie zwischen logische Abschnitte zusätzliche Leerzeilen ein, und vielleicht benutzen Sie auch einen Kommentar mit einer
Reihe auffälliger Zeichen, um eine Trennlinie hinzuzufügen. Wenn Sie schnell einmal die Datei durchgehen wollen, werden Sie feststellen, dass das Navigieren so
wesentlich einfacher ist.
Kommentare
Verwenden Sie Kommentare großzügig – sie sind wie Wegweiser in einer Textwüste. Stellen Sie zunächst an den Anfang jeder Datei einen Kommentar, der den
Zweck der DTD oder des Moduls erläutert, die Versionsnummer angibt und Kontaktinformationen anbietet. Wenn es sich um ein individuell angepasstes Frontend
zu einer öffentlichen DTD handelt, vergessen Sie nicht, das Original zu erwähnen,
auf dem es beruht; würdigen Sie die Autoren und erläutern Sie die von Ihnen vorgenommenen Änderungen. Machen Sie anschließend jeden Abschnitt und Unterabschnitt der DTD kenntlich.
Fügen Sie überall da einen Kommentar hinzu, wo es hilfreich sein könnte, die
Benutzung der DTD zu erklären oder Ihre Entscheidung zu erläutern. Wenn Sie die
DTD abändern, fügen Sie neue Kommentare hinzu, die Ihre Änderungen beschreiben. Kommentare stellen einen Teil der Dokumentation dar, und unklare oder überholte Dokumentationen können schlimmer sein als überhaupt keine.
DTDs |
129
Versionierung
Wie Software wird auch Ihre DTD wahrscheinlich aktualisiert werden, wenn sich Ihre
Anforderungen ändern. Sie sollten die Versionen nachhalten, indem Sie sie nummerieren. Wollen Sie Verwirrung vermeiden, ist es wichtig, dass Sie die Versionsnummer
ändern, wenn Sie das Dokument ändern. Üblicherweise erhält die erste offizielle Veröffentlichung die Nummer 1.0. Kleinere Veränderungen werden mit der Erhöhung der
Dezimalstelle dokumentiert: 1.1, 1.2 usw. Bei größeren Veränderungen erhöhen Sie
die ganze Zahl um einen Zähler: 2.0, 3.0 usw. Dokumentieren Sie die Veränderungen
von Version zu Version. Es stehen Versionskontrollsysteme zur Verfügung, die diesen
Vorgang automatisieren. Auf Unix-Systemen sind sowohl das RCS- als auch das CVSPaket die getreuen Freunde der Entwickler.
Parameter-Entities
Parameter-Entities können wiederkehrende Teile von Deklarationen aufnehmen
und ermöglichen Ihnen, diese an einer Stelle zu bearbeiten. In der externen Teilmenge können sie in Elementtyp-Deklarationen benutzt werden, um Elementgruppen und Inhaltsmodelle aufzunehmen, oder in Attribut-Deklarationslisten, um
Attribut-Definitionen aufzunehmen. Die interne Teilmenge ist ein wenig strenger.
Parameter-Entities können nur vollständige Deklarationen enthalten, jedoch keine
Fragmente.
Nehmen wir beispielsweise an, Sie wollen, dass jedes Element ein optionales IDAttribut, über das Verknüpfungen hergestellt werden können, und ein klasse-Attribut hat, mit dem spezifische Rolleninformationen festgehalten werden können.
Parameter-Entities, die nur in DTDs zulässig sind, ähneln den gewöhnlichen allgemeinen Entities. Bei ihrer Deklaration wird nur ein zusätzliches % eingefügt. Ein
Parameter-Entity, das allgemeine Attribute aufnimmt, können Sie auf folgende
Weise definieren:
<!ENTITY % allgemeine.atts "
id
ID
#IMPLIED
klasse
CDATA
#IMPLIED"
>
Dieses Entity kann in Deklarationen von Attributlisten genutzt werden:
<!ATTLIST foo %allgemeine.atts;>
<!ATTLIST bar %allgemeine.atts;
extra
CDATA
#FIXED "blah"
>
Beachten Sie, dass Parameter-Entity-Referenzen statt mit einem & mit einem % beginnen.
Attribute und Elemente
Eine DTD von Grund auf zu erstellen ist nicht so einfach. Sie müssen Ihre Informationen
in ihre konzeptionellen Bestandteile aufspalten und sie als hierarchische Struktur verpacken, aber es ist nicht immer ganz klar, wie die Informationen aufgeteilt werden können.
Das Buch-Modell macht in dieser Hinsicht weniger Probleme, denn dessen Unterteilung
130 |
Kapitel 4: Qualitätskontrolle mit Schemas
in hierarchische Container wie Kapitel, Abschnitte und Absätze liegt auf der Hand. Die
Modelle für Gleichungen, Moleküle und Datenbanken sind nicht ganz so offensichtlich.
Bei solchen Anwendungen ist ein beweglicher Verstand gefordert, um die Dokumente in
die optimale Mischung aus Elementen und Attributen aufzuspalten. Die folgenden Tipps
sind Grundsätze, die Ihnen beim Entwurf von DTDs helfen können:
• Wählen Sie sinnvolle Namen. Wenn Ihr Dokument ausschließlich aus Elementen
wie ding, objekt und teil besteht, ist es fast unmöglich herauszufinden, was was ist.
Namen sollten in engem Zusammenhang mit dem logischen Zweck eines Elements
stehen. Es ist besser, für unterschiedliche Aufgaben spezielle Elemente anzulegen,
als einige wenige Elemente damit zu überfordern, mit vielen verschiedenen Situationen fertig werden zu müssen. Beispielsweise sind die HTML-Elemente div und span
nicht ideal, weil sie viele verschiedene Rollen übernehmen können.
• Hierarchie liefert zusätzliche Informationen. Eine Zeitung hat Artikel, die Absätze
und Überschriften enthalten. Container schaffen Begrenzungen, die das Schreiben
von Stylesheets und Anwendungen zur Verarbeitung erleichtern. Und sie schaffen
implizite Eigentumsverhältnisse, die Prozessoren praktische Anknüpfpunkte und
Navigationshilfen zur Verfügung stellen. Container bieten zusätzliche Tiefe – eine
weitere Dimension, die die Struktur verstärkt.
Bemühen Sie sich um eine Baum-Struktur, die einem breiten, buschigen Strauch
ähnelt. Wenn Sie zu tief gehen, beginnt das Markup, den Inhalt unter sich zu begraben, und es wird schwerer, ein Dokument zu bearbeiten. Zu flache Strukturen hingegen gehen zu Lasten des Informationsgehalts. Als gute Analogie können Sie sich
Dokumente und ihre Komponenten als ineinander geschachtelte Kästen vorstellen.
Mit einem großen Kasten, der mit einer Million winziger Kästchen angefüllt ist, lässt
es sich wesentlich schwerer arbeiten als mit einem Kasten, der einige wenige mittlere Kästen enthält, in deren Inneren sich wiederum kleinere Kästen befinden usw.
• Sie sollten wissen, wann Sie Elemente, wann Attribute verwenden. Ein Element nimmt
Inhalte auf, die Teil Ihres Dokuments sind. Ein Attribut modifiziert das Verhalten
eines Elements. Die Kunst besteht darin, ein ausgewogenes Verhältnis zwischen allgemeinen Elementen mit den einen bestimmten Zweck festlegenden Attributen und
einzelnen Elementen für jeden einzelnen Anwendungsfall zu finden.
Modularisierung
Es hat verschiedene Vorteile, wenn man eine riesige DTD in kleinere Komponenten oder
Module aufspaltet. Der erste Vorteil ist, dass eine modularisierte DTD leichter zu warten
ist, und zwar aus Gründen der zuvor erwähnten Organisation und weil die einzelnen
Teile separat bearbeitet oder zur Fehlersuche »ausgeschaltet« werden können. Außerdem
wird die DTD dadurch konfigurierbar. Module in getrennten Dateien können gegen
andere ausgetauscht werden, indem man einfach nur ein einziges Parameter-Entity neu
definiert. Selbst innerhalb einer einzelnen Datei kann angegeben werden, ob die Module
einbezogen oder ausgeschlossen werden sollen.
DTDs |
131
XML bietet zwei Möglichkeiten, Ihre DTD zu modularisieren. Die erste besteht darin, die
einzelnen Teile in gesonderten Dateien abzuspeichern und sie dann über externe Parameter-Entities zu importieren. Die zweite besteht darin, ein syntaktisches Mittel einzusetzen, das bedingter Abschnitt genannt wird. Beides sind mächtige Methoden, eine DTD
flexibler zu machen.
Module aus externen Quellen importieren
Eine DTD muss nicht in einer einzigen Datei gespeichert sein. Tatsächlich ist es häufig
sinnvoll, sie in mehreren Dateien abzuspeichern. Vielleicht möchten Sie etwas von
jemand anderem entlehnen und seine DTD als Teilmenge in Ihre eigene importieren.
Oder Sie wollen die DTD möglicherweise nur ein bisschen übersichtlicher machen,
indem Sie die einzelnen Teile auf verschiedene Dateien aufteilen.
Um ganze DTDs oder Teile von DTDs zu importieren, benutzen Sie ein externes Parameter-Entity. Hier sehen Sie ein Beispiel für eine komplette DTD, die Teile aus verschiedenen Modulen importiert:
<!ELEMENT katalog (titel, metadaten, einleitung, einträge+)>
<!ENTITY % grundlegendes.zeug
SYSTEM "grundlegendes.mod">
%grundlegendes.zeug;
<!ENTITY % einleitungs.material SYSTEM "einleitendes.mod">
%einleitungs.material;
<!ENTITY % metadaten
PUBLIC "-//Standard-Zeug//DTD Metadaten
v3.2//EN" "http://www.standard-zeug.org/dtds/metadaten.dtd">
%metadaten;
Diese DTD hat zwei lokale Komponenten, die durch System-Identifier angegeben werden. Jede Komponente hat die Dateinamenserweiterung .mod. So gibt man üblicherweise
an, dass eine Datei Deklarationen enthält, aber nicht als eigenständige DTD genutzt werden soll. Die letzte Komponente ist eine eigenständige DTD. In diesem Beispiel ist es eine
öffentliche Ressource.
Beim Importieren von DTD-Text kann ein Problem auftauchen. Ein externes ParameterEntity importiert den gesamten Text in eine Datei, nicht nur einen Teil davon. Sie erhalten alle Deklarationen und nicht nur ein paar ausgewählte. Schlimmer noch, es gibt keinen lokalen Geltungsbereich, innerhalb dessen Deklarationen in der lokalen DTD
automatisch diejenigen aus der importierten Datei überschreiben. Die Deklarationen
werden zu einer einzigen logischen Einheit zusammengebaut, und alle Informationen
darüber, was von wo importiert wurde, gehen verloren, bevor die DTD geparst wird.
Es gibt ein paar Wege, dieses Problem zu umgehen. Sie können die Entity-Deklarationen
überschreiben, indem Sie sie neu deklarieren, oder, um genau zu sein, vordeklarieren.
Mit anderen Worten: Wenn ein Element mehrfach deklariert wird, hat die erste Deklaration Vorrang. Sie können eine Entity-Deklaration also mit einer Deklaration in der internen Teilmenge überschreiben, weil die interne Teilmenge vor der externen Teilmenge
gelesen wird.
132 |
Kapitel 4: Qualitätskontrolle mit Schemas
Das Überschreiben einer Element-Deklaration ist schwieriger. Wird ein Element mehrfach deklariert, ist das Dokument nicht mehr gültig. (Sie können aber mehrere ATTLISTDeklarationen für dasselbe Element angeben. Auch dann wird die erste als die richtige
akzeptiert.) Die Frage ist also, wie man eine Deklaration wie diese
<!ELEMENT vieleck (seite+, winkel+)>
mit einer eigenen Deklaration wie der folgenden überschreiben kann:
<!ELEMENT vieleck (seite, seite, seite+, winkel, winkel, winkel+)>
Mit dem, was Sie bisher kennen, ist es nicht möglich, Element-Deklarationen zu überschreiben. Ich muss Ihnen erst ein weiteres Feature der DTD-Syntax vorstellen, das man
als bedingten Abschnitt bezeichnet.
Bedingte Abschnitte
Ein bedingter Abschnitt ist eine spezielle Form von Markup, die in DTDs benutzt wird,
um einen Textbereich für die Einbeziehung in die DTD bzw. den Ausschluss aus der
DTD zu markieren.4 Wenn Sie ahnen, dass ein Teil Ihrer DTD eines Tages vielleicht
unerwünscht ist, können Sie ihn zu einem bedingten Abschnitt machen und dem Endbenutzer die Entscheidung darüber überlassen, ob er diesen behalten will oder nicht.
Beachten Sie, dass bedingte Abschnitte nur in externen Teilmengen, nicht in internen
Teilmengen benutzt werden können.
Bedingte Abschnitte ähneln CDATA-Abschnitten. Sie verwenden eckige Klammern als
Begrenzer, das Schlüsselwort CDATA wird jedoch entweder durch INCLUDE oder durch
IGNORE ersetzt. Die Syntax sieht folgendermaßen aus:
<![schalter[DTD-Text]]>
schalter fungiert dabei wie ein Ein-/Aus-Schalter, der DTD-Text aktiviert, wenn sein Wert
INCLUDE ist, oder ihn als inaktiv markiert, wenn er auf IGNORE gesetzt ist. Ein Beispiel:
<![INCLUDE[
<!-- Diese Deklarationen werden eingebunden. -->
<!ELEMENT foo (bar, caz, bub?)>
<!ATTLIST foo crud CDATA #IMPLIED)>
]]>
<![IGNORE[
<!-- Diese Deklarationen werden ignoriert. -->
<!ELEMENT blah #PCDATA>
<!ELEMENT glop (flub|zuc) 'zuc')>
]]>
Die Verwendung der fest kodierten Literale INCLUDE und IGNORE ist nicht besonders günstig, denn Sie müssen jeden bedingten Abschnitt von Hand bearbeiten, um den Schalter
ein- oder auszuknipsen. Für gewöhnlich ist der Schalter ein Parameter-Entity, das an
einem beliebigen Ort definiert werden kann:
4 In SGML können Sie bedingte Abschnitte sowohl in Dokumenten als auch in DTDs benutzen. XML
beschränkt ihren Gebrauch auf DTDs. Mir persönlich fehlen sie, weil ich sie für eine starke Möglichkeit halte,
Dokumente auf Basis bestimmter Bedingungen zu ändern.
DTDs |
133
<!ENTITY % optionales.zeug "INCLUDE">
<![%optionales.zeug;[
<!-- Diese Deklarationen können eingebunden werden oder nicht. -->
<!ELEMENT foo (bar, caz, bub?)>
<!ATTLIST foo crud CDATA #IMPLIED)>
]]>
Da das Parameter-Entity optionales.zeug mit dem Schlüsselwort INCLUDE definiert ist,
werden die Deklarationen aus dem markierten Abschnitt eingebunden. Wenn optionales.zeug als IGNORE definiert worden wäre, würden die Deklarationen im Dokument ignoriert.
Diese Technik ist besonders leistungsfähig, wenn Sie das Entity innerhalb einer Dokument-Teilmenge deklarieren. Im nächsten Beispiel deklariert unsere DTD ein allgemeines
Entity, das die Bezeichnung warnung hat. Der tatsächliche Wert des Entity hängt davon
ab, ob verwende-warnung auf INCLUDE gesetzt wurde:
<![%verwende-warnung;[
<!ENTITY warnung "<p>Dies ist Beta-Software. Wir können nicht garantieren,
dass sie frei von Fehlern ist.</p>">
]]>
<!ENTITY warnung "">
In Dokumenten, in die Sie eine Warnung einfügen wollen, brauchen Sie das SchalterEntity einfach nur in der internen Teilmenge zu deklarieren:
<?xml version="1.0"?>
<!DOCTYPE handbuch SYSTEM "handbuch.dtd" [
<!ENTITY % verwende-warnung "IGNORE">
]>
<handbuch>
<titel>Benutzerhandbuch für Techno-Wuzzy</titel>
&warnung;
...
In diesem Beispiel ist das Entity verwende-warnung auf IGNORE gestellt. Die warnung ist also
als leerer String deklariert, und der Text des Dokuments wird keine Warnung enthalten.
Dies ist ein einfaches Beispiel für die Anpassung einer DTD mit Hilfe von bedingten
Abschnitten und Parameter-Entities.
Lassen Sie uns nun zu unserem ursprünglichen Problem zurückkehren und uns ansehen,
wie man Element- und Attribut-Deklarationen überschreibt. Ich zeige Ihnen hier, wie
man das mit bedingten Abschnitten macht. Zuerst muss die DTD so geschrieben werden,
dass sie das Umschalten mit Parameter-Entities ermöglicht:
<!ENTITY % standard.vieleck "INCLUDE">
<![%standard.vieleck;[
<!ELEMENT vieleck (seite+, winkel+)>
]]>
134 |
Kapitel 4: Qualitätskontrolle mit Schemas
Jetzt können Sie in Ihrem Dokument diese DTD als externe Teilmenge deklarieren. Dann
deklarieren Sie das Parameter-Entity standard.vieleck in der internen Teilmenge neu:
<!DOCTYPE bild SYSTEM "figuren.dtd" [
<!ENTITY % standard.vieleck "IGNORE">
<!ELEMENT vieleck (seite, seite, seite+, winkel, winkel, winkel+)>
]>
Da die interne Teilmenge vor der externen Teilmenge gelesen wird, hat die Deklaration
des Parameter-Entity an dieser Stelle Vorrang vor der in der DTD. Der bedingte
Abschnitt in der DTD erhält den Wert IGNORE, und dadurch wird die externe ElementDeklaration für vieleck verborgen. Die Element-Deklaration in der internen Teilmenge
ist gültig und wird vom Parser verwendet.
Bedingte Abschnitte können geschachtelt werden, doch die äußeren Abschnitte überschreiben die inneren. Wenn der äußere Abschnitt auf IGNORE gesetzt ist, werden seine
Inhalte (einschließlich aller bedingten Abschnitte in seinem Inneren) unabhängig von
ihrem Wert vollständig abgeschaltet, zum Beispiel:
<![INCLUDE[
<!-- Der Text hier wird eingebunden. -->
<![IGNORE[
<!-- Der Text hier wird ignoriert. -->
]]>
]]>
<![IGNORE[
<!-- Der Text hier wird ignoriert. -->
<![INCLUDE[
<!-- Warnung: Was hier steht, wird ebenfalls ignoriert! -->
]]>
]]>
Öffentliche DTDs machen häufig umfassend Gebrauch von bedingten Abschnitten, um
ein Höchstmaß an Anpassung zu ermöglichen. Die DocBook-XML-DTD Version 1.0 enthält zum Beispiel Folgendes:
<!ENTITY % screenshot.content.module "INCLUDE">
<![%screenshot.content.module;[
<!ENTITY % screenshot.module "INCLUDE">
<![%screenshot.module;[
<!ENTITY % local.screenshot.attrib "">
<!ENTITY % screenshot.role.attrib "%role.attrib;">
<!ELEMENT screenshot (screeninfo?, (graphic|graphicco))>
<!ATTLIST screenshot
%common.attrib;
%screenshot.role.attrib;
%local.screenshot.attrib;
>
<!--end of screenshot.module-->]]>
<!ENTITY % screeninfo.module "INCLUDE">
<![%screeninfo.module;[
<!ENTITY % local.screeninfo.attrib "">
DTDs |
135
<!ENTITY % screeninfo.role.attrib "%role.attrib;">
<!ELEMENT screeninfo (%para.char.mix;)*>
<!ATTLIST screeninfo
%common.attrib;
%screeninfo.role.attrib;
%local.screeninfo.attrib;
>
<!--end of screeninfo.module-->]]>
<!--end of screenshot.content.module-->]]>
Der äußere bedingte Abschnitt umgibt Deklarationen für screenshot und auch screeninfo, das in screenshot vorkommt. Sie können sowohl screenshot als auch screeninfo
vollständig eliminieren, indem Sie in Ihrer lokalen DTD screenshot.content.module auf
IGNORE stellen, bevor die Datei geladen wird. Alternativ können Sie nur den Abschnitt um
die screeninfo-Deklarationen abschalten – vielleicht um Ihre eigene Version von screeninfo zu deklarieren. (Durch Abschalten der Deklarationen für ein Element in der importierten Datei vermeiden Sie Warnhinweise von Ihrem Parser wegen redundanter
Deklarationen.) Beachten Sie die Parameter-Entities, die für die Zuweisung verschiedener
Arten von Inhalts- und Attribut-Definitionen verwendet werden, wie %common.attrib;. Es
gibt auch Anker, über die Sie Ihre eigenen Attribute einbringen können, wie
%local.screenshot.attrib;.
Mit dem geschickten Einsatz von bedingten Abschnitten lässt sich eine DTD extrem flexibel machen, obwohl sie dann vielleicht schwerer zu lesen ist. In Ihren persönlichen
DTDs sollten Sie bedingte Abschnitte sparsam einsetzen und versuchen, sie so zu entwerfen, dass sie von Anfang an Ihrem Bedarf entsprechen. Später, wenn die DTD zu einer
öffentlichen Ressource wird, ist es sinnvoll, bedingte Abschnitte hinzuzufügen, um
Anpassungen durch den Endbenutzer zu ermöglichen.
Die interne Teilmenge verwenden
Rufen Sie sich aus dem Abschnitt »Deklarationen« weiter oben in diesem Kapitel ins
Gedächnis, dass die interne Teilmenge der Teil eines XML-Dokuments ist, der EntityDeklarationen enthalten kann. Tatsächlich ist sie jedoch weit mächtiger, denn Sie können in der internen Teilmenge beliebige Deklarationen ablegen, die in einer DTD stehen
würden. Beschränkungen unterliegen lediglich bedingte Abschnitte (können nicht
benutzt werden) und Parameter-Entities (können nur vollständige Deklarationen, jedoch
keine Fragmente enthalten). Dies ist nützlich für das Überschreiben oder das Ein- und
Ausschalten von Teilen der DTD. Hier sehen Sie die allgemeine Form:
<!DOCTYPE wurzelelement URI [ deklarationen ]>
Wenn ein Parser die DTD liest, liest er zuerst die interne, dann die externe Teilmenge.
Das ist wichtig, weil die erste Deklaration eines Entity Vorrang vor allen anderen Deklarationen dieses Entity hat. Auf diese Weise können Sie die Entity-Deklarationen aus der
DTD überschreiben, indem Sie sie in der internen Teilmenge deklarieren. In der internen
Teilmenge können auch neue Elemente und Attribute deklariert werden, aber es dürfen
keine Deklarationen überschrieben werden, die in der DTD bereits vorhanden sind. Sie
136 |
Kapitel 4: Qualitätskontrolle mit Schemas
erinnern sich sicher daran, dass man Elemente und Attribute neu definiert, indem man
Parameter-Entities einsetzt, um einen bedingten Abschnitt abzuschalten, der die Deklarationen der DTD enthält.
Dieses Beispiel zeigt einige richtige Anwendungen einer internen Teilmenge:
<!DOCTYPE inventar SYSTEM "inventar-bericht.dtd" [
<!-- Füge dem posten-Element das neue Attribut "kategorie" hinzu. -->
<!ATTLIST posten kategorie (schraube | bolzen | mutter) #REQUIRED>
<!-- Definiere das allgemeine Entity firmenname neu. -->
<!ENTITY firmenname "Knackige Kekse KGaA">
<!-- Definiere das preis-Element neu, indem das Parameter-Entity
preis.modul neu definiert wird. -->
<!ELEMENT preis (währung, betrag)>
<!ENTITY % preis.modul "IGNORE">
<!-- Verwende eine anderes Modul für die Abbildungen als das,
das die DTD nutzt. -->
<!ENTITY % abbildungen SYSTEM "meine-abbildungen.mod">
]>
Die Deklaration der Attributliste in dieser internen Teilmenge fügt der Attributmenge für
posten das Attribut kategorie hinzu. Wenn die DTD bereits ein kategorie-Attribut für
posten deklariert, wird ihre Deklaration durch diese überschrieben. Ansonsten wird das
Attribut kategorie einfach der in der DTD bereits deklarierten Attributmenge für posten
hinzugefügt.
Die Element-Deklaration kollidiert mit einer in der DTD bereits vorhandenen Deklaration. In der nächsten Zeile wird jedoch ein bedingter Abschnitt abgeschaltet, indem das
Parameter-Entity preis.modul auf IGNORE gesetzt wird. So wird die Deklaration in der
DTD vor dem Parser verborgen.
Sie dürfen ein Element in einer DTD nur einmal deklarieren. Während Sie
die Deklarationen für Attribute überschreiben können, müssen Sie vermeiden, ein Element in der internen Teilmenge zu deklarieren, das Sie bereits
an anderer Stelle deklariert haben.
SimpleDoku: Ein narratives Beispiel
Im Abschnitt »Ein Beispiel« haben wir eine einfache DTD für eine Markup-Sprache zur
Beschreibung von Daten entwickelt. Narrative Anwendungen sind oft etwas komplexer,
weil die menschliche Sprache komplexer ist als einfache Datenstrukturen. Lassen Sie uns
nun mit einer DTD für eine komplexere, narrative Anwendung experimentieren.
Von DocBook inspiriert, habe ich eine kleine narrative Anwendung geschaffen, die ich
SimpleDoku nenne. Sie ist viel kleiner und versucht nicht einmal, einen Bruchteil von
dem zu tun, was DocBook kann. Aber sie streift alle wichtigeren Konzepte und ist deswe-
DTDs |
137
gen für Lehrzwecke geeignet. Das eigentliche Ziel von SimpleDoku ist, eine MarkupSprache für kleine, einfache Dokumente wie das in Beispiel 4-3 zur Verfügung zu stellen.
Beispiel 4-3: Ein Beispiel-SimpleDoku-Dokument
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE doc SYSTEM "simpledoku.dtd">
<dok>
<titel>Organismus oder Maschine?</titel>
<abschnitt id="essen">
<titel>Sams Dinerschuppen</titel>
<absatz>Ein gewaltiger LKW fuhr vorüber. Er war so breit, dass er vier ganze
Fahrspuren verschlang. Der ganze hintere Abschnitt ein blendendes Personenabteil in
Chrom und Neon. Auf dem Dach spross ein gigantischer Plastik-Hamburger,
an dem Lichter aufblitzten. "Sams Schräge Seelen Snax Scheune" stand darauf
geschrieben. Als er mit atemberaubender Geschwindigkeit vorbeiraste,
sah ich am Ende eine Traube angebundener Autos hin- und herschwingen.</absatz>
<absatz>Darunter waren:</absatz>
<liste>
<listenposten><absatz>ein dieselbetriebenes Einrad,</absatz></listenposten>
<listenposten><absatz>eine Stretch-Limousine, locker 50 Meter lang,
</absatz></listenposten>
<listenposten><absatz>die süßesten kleinen Nussschalen-Wägelchen, rund
wie Kugeln, die unten ein Raupenfahrwerk hatten.</absatz></listenposten>
</liste>
<absatz>Ich machte mich auf, den LKW abzufangen, mein Fahrzeug
anzuhängen und an Bord zu klettern.</absatz>
<hinweis>
<absatz>Wenn Sie Ihren Wagen an einen fahrenden LKW anhängen wollen,
sollten Sie besser wissen, was Sie tun.</absatz>
</hinweis>
</abschnitt>
</dok>
Beispiel 4-4 ist die SimpleDoku-DTD.
Beispiel 4-4: Die SimpleDoku-DTD
<?xml version="1.0" encoding="ISO-8859-1"?>
<!-SimpleDoku-DTD
-->
<!-- ===========================================================================
Parameter-Entities
=========================================================================== -->
<!-- In allen Elementen verwendete Attribute. -->
<!ENTITY % allgemeine.atts "
id
ID
#IMPLIED
klasse
CDATA
#IMPLIED
xml:space (default | preserve) 'default'
">
138 |
Kapitel 4: Qualitätskontrolle mit Schemas
Beispiel 4-4: Die SimpleDoku-DTD (Fortsetzung)
<!-- Block-Elemente und komplexe Elemente -->
<!ENTITY % block.gruppe "
autor
| blockzitat
| programmbeispiel
| beispiel
| abbildung
| grafik
| liste
| hinweis
| absatz
| bemerkung
">
<!-- Inline-Elemente -->
<!ENTITY % inline.gruppe "
akronym
| textauszug
| befehl
| datum
| betonung
| dateiname
| begriffseinführung
| literal
| zitat
| ulink
| xref
">
<!-- ===========================================================================
Hierarchie-Elemente
=========================================================================== -->
<!-- Das Dokumentelement -->
<!ELEMENT dok (titel, (%block.gruppe)*, abschnitt+)>
<!ATTLIST dok %allgemeine.atts;>
<!-- Abschnitte zur Einteilung des Dokuments -->
<!ELEMENT abschnitt (titel, (%block.gruppe)*, abschnitt*)>
<!ATTLIST abschnitt %allgemeine.atts;>
<!-- ===========================================================================
Block-Elemente
=========================================================================== -->
<!-- Ein Ort, um den Namen des Autors unterzubringen. -->
<!ELEMENT autor #PCDATA>
<!ATTLIST autor %allgemeine.atts;>
<!-- Bereich mit zitiertem Text -->
<!ELEMENT blockzitat (absatz+)>
<!ATTLIST blockzitat %allgemeine.atts;>
DTDs |
139
Beispiel 4-4: Die SimpleDoku-DTD (Fortsetzung)
<!-- Formelles Programmbeispiel (mit Titel) -->
<!ELEMENT beispiel (titel, programmbeispiel)>
<!ATTLIST beispiel %allgemeine.atts;>
<!-- Formelle Abbildung (mit Titel) -->
<!ELEMENT abbildung (titel, grafik)>
<!ATTLIST abbildung %allgemeine.atts;>
<!-- Fußnote außerhalb des Textflusses -->
<!ELEMENT fußnote (absatz+)>
<!ATTLIST fußnote %allgemeine.atts;>
<!-- Abbildung -->
<!ELEMENT grafik EMPTY>
<!ATTLIST grafik
dateiref
CDATA
%allgemeine.atts;
>
#REQUIRED
<!-- Folge von Listenposten -->
<!ELEMENT liste (begriff?, listenposten)+>
<!ATTLIST liste
typ
(nummeriert|punkte|definition)
%allgemeine.atts;
>
"nummeriert"
<!-- Bestandteile einer Liste -->
<!ELEMENT listenposten (%block.gruppe;)+>
<!ATTLIST listenposten %allgemeine.atts;>
<!-- Hinweis im Textfluss -->
<!ELEMENT hinweis (absatz+)>
<!ATTLIST hinweis %allgemeine.atts;>
<!-- Einfacher Absatz -->
<!ELEMENT absatz (#PCDATA | %inline.gruppe; | fußnote)*>
<!ATTLIST absatz %allgemeine.atts;>
<!-- Programmlisting -->
<!ELEMENT programmbeispiel (#PCDATA | %inline.gruppe;)*>
<!ATTLIST programmbeispiel
xml:space (preserve) #FIXED 'preserve'
%allgemeine.atts;
>
<!-- Sichtbarer Kommentar -->
<!ELEMENT bemerkung (#PCDATA | %inline.gruppe;)*>
<!ATTLIST bemerkung %allgemeine.atts;>
<!-- Dokument- oder Abschnittsüberschrift -->
<!ELEMENT titel (#PCDATA | %inline.gruppe;)*>
<!ATTLIST titel %allgemeine.atts;>
140 |
Kapitel 4: Qualitätskontrolle mit Schemas
Beispiel 4-4: Die SimpleDoku-DTD (Fortsetzung)
<!-- Begriff in einer Definitionsliste -->
<!ELEMENT begriff (#PCDATA | %inline.gruppe;)*>
<!ATTLIST begriff %allgemeine.atts;>
<!-- ===========================================================================
Inline-Elemente
=========================================================================== -->
<!ENTITY % inline.inhalt "#PCDATA">
<!ELEMENT akronym %inline.inhalt;>
<!ATTLIST akronym %allgemeine.atts;>
<!ELEMENT zitat %inline.inhalt;>
<!ATTLIST zitat %allgemeine.atts;>
<!ELEMENT befehl %inline.inhalt;>
<!ATTLIST befehl %allgemeine.atts;>
<!ELEMENT datum %inline.inhalt;>
<!ATTLIST datum %allgemeine.atts;>
<!ELEMENT betonung %inline.inhalt;>
<!ATTLIST betonung %allgemeine.atts;>
<!ELEMENT dateiname %inline.inhalt;>
<!ATTLIST dateiname %allgemeine.atts;>
<!ELEMENT begriffseinführung %inline.inhalt;>
<!ATTLIST begriffseinführung %allgemeine.atts;>
<!ELEMENT literal %inline.inhalt;>
<!ATTLIST literal %allgemeine.atts;>
<!ELEMENT zitat %inline.inhalt;>
<!ATTLIST zitat %allgemeine.atts;>
<!ELEMENT ulink %inline.inhalt;>
<!ATTLIST ulink
href
CDATA
#REQUIRED
%allgemeine.atts;
>
<!ELEMENT xref EMPTY>
<!ATTLIST xref
verknüpft
ID
#REQUIRED
%allgemeine.atts;
>
<!-- ===========================================================================
Nützliche Entities
=========================================================================== -->
DTDs |
141
Beispiel 4-4: Die SimpleDoku-DTD (Fortsetzung)
<!ENTITY % isolat1
PUBLIC "ISO 8879:1986//ENTITIES
"isolat1.ent"
>
%isolat1;
<!ENTITY % isolat2
PUBLIC "ISO 8879:1986//ENTITIES
"isolat2.ent"
>
%isolat2;
<!ENTITY % isomath
PUBLIC "ISO 8879:1986//ENTITIES
"isoamso.ent"
>
%isomath;
<!ENTITY % isodia
PUBLIC "ISO 8879:1986//ENTITIES
"isodia.ent"
>
%isodia;
<!ENTITY % isogriechisch
PUBLIC "ISO 8879:1986//ENTITIES
"isogrk3.ent"
>
%isogriechisch;
Added Latin 1//EN//XML"
Added Latin 2//EN//XML"
Added Math Symbols: Ordinary//EN//XML"
Diacritical Marks//EN//XML"
Greek Symbols//EN//XML"
W3C XML Schema
DTDs sind hauptsächlich darauf ausgerichtet zu beschreiben, wie die Elemente in einem
Dokument angeordnet sind. Sie sagen über die Inhalte in einem Dokument wenig mehr,
als dass ein Element Zeichendaten enthalten kann oder nicht. Obwohl Attributen ein
paar Typen zugeordnet werden können (z.B. ID, IDREF oder Wertaufzählungen), gibt es
keine Möglichkeit, die Datentypen einzugrenzen, die ein Element enthalten kann.
Kehren wir zum Abschnitt »Ein Beispiel« und dem darin enthaltenen Beispiel zurück,
können wir sehen, wie diese Einschränkung zu einem ernsthaften Problem werden kann.
Nehmen wir an, ein Volkszähler hätte das Dokument aus Beispiel 4-5 eingereicht.
Beispiel 4-5: Ein schlechtes CensusML-Dokument
<?xml version="1.0" encoding="ISO-8859-1"?>
<census-datensatz erhoben-durch="9170">
<datum><monat>?</monat><tag>110</tag><jahr>03</jahr></datum>
<adresse>
<ort>Munchkinland</ort>
<straße></straße>
<bezirk></bezirk>
<staat>Hier Dummkopf</staat>
<postleitzahl></postleitzahl>
</adresse>
142 |
Kapitel 4: Qualitätskontrolle mit Schemas
Beispiel 4-5: Ein schlechtes CensusML-Dokument (Fortsetzung)
<person beschäftigt="vollzeit" pid="?">
<name>
<nachname>Bürgli</nachname>
<vorname>Bert</vorname>
</name>
<alter>2131234</alter>
<geschlecht>ja</geschlecht>
</person>
</census-datensatz>
Mit diesem Element ist einiges nicht in Ordnung. Das Datum hat ein falsches Format.
Einige wichtige Felder wurden nicht ausgefüllt. Das angegebene Alter ist unmöglich
hoch. geschlecht, das »männlich« oder »weiblich« sein müsste, enthält etwas anderes.
Der Identifier für die Person hat einen nicht zulässigen Wert. Trotzdem würde ein validierender Parser anhand der DTD zu unserer Verzweifelung keins dieser Probleme
bemerken.
Man könnte leicht ein Programm schreiben, um die Datentypen zu überprüfen. Aber das
ist eine Low-Level-Operation, die fehleranfällig ist und gewisse technische Fähigkeiten
verlangt. Außerdem verliert man damit den eigentlichen Zweck einer DTD aus den
Augen, die ja angelegt wurde, um ein Metadokument zu schaffen, das eine formale
Beschreibung einer Markup-Sprache bietet. Programmiersprachen sind nicht so portabel
und eignen sich nicht gut, um syntaktische und semantische Details zu transportieren.
Wir müssen also feststellen, dass DTDs bei der Beschreibung einer Markup-Sprache
nicht weit genug gehen.
Noch schlimmer ist, dass Markup von einer DTD oft aus trivialen Gründen abgelehnt
wird. Beispielsweise steht der Inhalt von datum und name nicht in der bestimmten Reihenfolge, die von den Element-Deklarationen gefordert wird. Das scheint unnötig kleinlich,
aber es ist tatsächlich sehr schwierig, ein Inhaltsmodell zu schreiben, in dem die Kindelemente in einer beliebigen Ordnung vorkommen dürfen.
Lassen Sie uns zur Erläuterung dieses Problems versuchen, datum flexibler zu machen,
damit es die Kindelemente in beliebiger Reihenfolge akzeptiert. Mir fällt nichts Besseres
ein, als die Deklaration so zu schreiben:
<!ELEMENT datum
(jahr,
| (monat,
| (tag,
)>
(
((monat, tag) | (tag, monat)))
((jahr, tag)
| (tag, jahr)))
((monat, jahr) | (jahr, monat)))
Ziemlich scheußlich, oder? Und das, obwohl Datum nur drei Kindelemente hat. Das ist
ein anderer ernsthafter Nachteil von DTDs.
Die Beschränkung der DTDs, die wahrscheinlich den meisten Schaden anrichtet, ist das
Aussperren von Namensräumen. Jedem Element in einem Dokument muss eine Deklaration in der DTD entsprechen. Es gibt keine Ausnahmen. Das steht in grundlegendem
Widerspruch zu XML-Namensräumen, die es Ihnen ermöglichen, beliebige Vokabulare
W3C XML Schema
|
143
zu importieren. Sicher, es gibt gute Gründe dafür, die Arten von Elementen einzuschränken, die in einem Dokument vorkommen dürfen: Die Validierung ist effektiver, und es
kann verhindert werden, dass illegale Argumente auftauchen. Aber es gibt keine Möglichkeit, dieses Feature abzuschalten, wenn Sie es nicht brauchen können.5
Um Probleme wie dieses zu lösen, wurde ein neues Validierungssystem erfunden, das
Schema genannt wird. Wie DTDs enthalten Schemas Regeln, denen ein Dokument entsprechen muss, damit es als gültig betrachtet wird. Im Unterschied zu DTDs sind Schemas aber nicht Teil der XML-Spezifikation. Sie sind eine Zusatztechnologie, die Sie
einsetzen können, wenn Sie Parser zur Verfügung haben, die sie unterstützen.
Es gibt verschiedene konkurrierende Schema-Arten. Das eine, das vom W3C sanktioniert
ist, heißt XML Schema. Ein anderer Vorschlag namens RELAX NG fügt Fähigkeiten
hinzu, die XML Schema nicht bietet, beispielsweise die Unterstützung von regulären
Ausdrücken für die Prüfung von Zeichendaten. Eine andere beliebte Alternative ist Schematron. In diesem Abschnitt befassen wir uns mit der W3C-Variante, mit den Alternativen in späteren Abschnitten.
XML Schemas sind selbst XML-Dokumente. Das ist sehr praktisch, weil Sie so Wohlgeformtheit und Gültigkeit prüfen können, wenn Sie an einem Schema Änderungen vornehmen. Sie sind ausführlicher als eine DTD, aber immer noch ganz gut lesbar und dabei
erheblich flexibler.
Das bezirk-Element aus dem Volkszählungsbeispiel würden wir folgendermaßen deklarieren:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="bezirk" type="xs:string"/>
</xs:schema>
Das xs:element-Element hat die gleiche Rolle wie eine !ELEMENT-Deklaration in einer
DTD. Das name-Attribut deklariert einen Namen (»bezirk«), und das type-Attribut definiert ein Inhaltsmodell, indem es auf einen Datentyp verweist. Statt das Inhaltsmodell
mit einer kompakten Folge von Symbolen innerhalb der Element-Deklaration zu definieren, definieren Schemas Inhaltsmodelle an einem eigenen Ort, auf den dann in der Element-Deklaration verwiesen wird. Das ist ähnlich der Verwendung von ParameterEntities, jedoch, wie wir noch sehen werden, bei Schemas flexibler.
xs:string verweist auf einen einfachen Typ für Elemente, einen, der Bestandteil der
Schema-Spezifikation ist. In diesem Fall ist es nur ein String mit Zeichendaten, so ungefähr das einfachste, was man haben kann. Eine Alternative zu xs:string ist xs:token.
Auch er enthält einen String, normalisiert aber für Sie die Leerzeichen (das heißt, führende und folgende Leerzeichen werden entfernt, mehrere zu einem zusammengezogen).
5 Es gibt einen komplexen, auf Parameter-Entities basierenden Hack, eine DTD anzulegen, die mit Namensräumen klarkommt. Obwohl das W3C ihn für XHTML und SVG eingesetzt hat, ist er zerbrechlich und stellt
außerdem ein großes Lesbarkeitsproblem dar.
144 |
Kapitel 4: Qualitätskontrolle mit Schemas
Tabelle 4-1 stellt die anderen einfachen Typen vor, die in Schemas üblicherweise zum
Einsatz kommen. Es gibt noch viel mehr Typen in W3C XML Schema Part 2: Datentypen, aber dieser Kern befriedigt die häufigsten Anforderungen und ermöglicht Ihnen
einen Einstieg.
Tabelle 4-1: Einfache Typen, die in Schemas üblicherweise verwendet werden
Typ
Verwendung
xs:string
Enthält beliebigen Text.
xs:token
Enthält beliebige, mit Whitespace getrennte Text-Token.
xs:QName
Enthält einen mit einem Namensraum qualifizierten Namen.
xs:decimal
Enthält eine Dezimalzahl beliebiger Genauigkeit. (Prozessoren müssen mindestens 18 Stellen unterstützen.) Beispielsweise wären »3,252333«, »–1,01«
und »+20« zulässige Werte.
xs:integer
Enthält einen ganzzahligen Wert wie »0«, »35« oder »–1433322«.
xs:float
Enthält eine 32-Bit-IEEE 754-Fließkommazahl.
xs:ID, xs:IDREF,
xs:IDREF
Ist dasselbe wie ID, IDREF, IDREFS in DTDs.
xs:boolean
Enthält einen Wert, der wahr oder falsch sein und als »true« bzw. »false« oder
»1« bzw. »0« ausgedrückt werden kann.
xs:time
Enthält eine Zeitangabe im ISO 8601-Format (HH:MM:SS–Zeitzone) wie
21:55:00–06:00.
xs:date
Enthält eine Datumsangabe im ISO 8601-Format (JJJJ-MM-DD) wie 2004-1230.
xs:dateTime
Enthält eine kombinierte Datums-/Zeitangabe im ISO 8601-Format (JJJJMM-DDTHH:MM:SS–Zeitzone) wie 2004-12-30T21:55:00–06:00.
Die meisten Elemente sind jedoch nicht einfach. Sie können Elemente, Attribute und Zeichendaten mit bestimmten Formaten enthalten. Deswegen enthalten Schemas auch komplexe Element-Definitionen. Das datum-Element könnte man folgendermaßen definieren:
<xs:element name="datum">
<xs:complexType>
<xs:all>
<xs:element ref="jahr"/>
<xs:element ref="monat"/>
<xs:element ref="tag"/>
</xs:all>
</xs:complexType>
</xs:element>
<xs:element name="jahr" type="xs:integer"/>
<xs:element name="monat" type="xs:integer"/>
<xs:element name="tag" type="xs:integer"/>
Das Element datum ist ein komplexer Typ, weil er besondere Anforderungen hat, die Sie
explizit definieren müssen. In diesem Fall verlangt der Typ eine Gruppe von drei Elementen (die in beliebiger Reihenfolge vorkommen können), auf die mit dem ref-Attribut
über den Namen verwiesen wird. Diese Elemente, auf die verwiesen wird, werden am
Ende als Elemente vom Typ integer definiert.
W3C XML Schema
|
145
Es ist möglich, unsere Datumsangabe noch weiter zu verbessern. Obwohl das Schema
sichert, dass jedes der Unterfelder jahr, monat und tag einen Integer-Wert enthält, lässt es
immer noch Werte zu, die wir nicht brauchen können. –125724 beispielsweise ist ein
gültiger Integer-Wert, der uns als Wert für monat aber nicht gefallen würde.
Den Bereich für einen Datentyp steuert man mit so genannten Facetten. Eine Facette ist
ein zusätzlicher Parameter, der einer Typ-Definition hinzugefügt wird. Sie können für das
monat-Element folgendermaßen einen neuen Datentyp definieren:
<xs:simpleType name="monatsNum">
<xs:restriction base="xs:integer">
<xs:minInclusive value="1"/>
<xs:maxInclusive value="12"/>
</xs:restriction>
</xs:simpleType>
<xs:element name="monat" type="monatsNum"/>
Hier haben wir einen Typ angelegt und ihn monatsNum genannt. Benannte Typen sind
nicht an ein bestimmtes Element gebunden, deswegen sind sie nützlich, wenn Sie denselben Typ immer wieder verwenden. In dieser Typ-Definition befindet sich ein xs:restriction-Element, mit dem wir von dem losen xs:integer einen spezifischeren Typ ableiten.
Darin befinden sich zwei Facetten, minInclusive und maxInclusive, die die Unter- bzw.
Obergrenze setzen. Jedes Element, dem der Typ monatsNum zugewiesen wird, wird daraufhin geprüft, ob sein Wert eine ganze Zahl ist, die in diesen Bereich fällt.
Mit Facetten kann man, außer Bereiche festzulegen, auch feste Werte erzeugen, die
Länge von Strings beschränken und Muster mit regulären Ausdrücken angeben. Beispielsweise könnten Sie angeben, dass eine Postleitzahl wie die in unserem Volkszählungsbeispiel ein String sein muss, der drei Ziffern enthält, auf die drei Buchstaben
folgen:
<postleitzahl>885JKL</postleitzahl>
Ein mögliches Muster wäre [0-9][0-9][0-9][A-Z][A-Z][A-Z]. Besser noch wäre die
Schreibweise [0-9]{3}[A-Z]{3}. Hier sehen Sie, wie das in einem Schema aussehen
könnte:
<xs:element name="postleitzahl" type="plzTyp"/>
<xs:simpleType name="plztyp">
<xs:restriction base="xs:token">
<xs:pattern value="[0-9]{3}[A-Z]{3}"/>
</xs:restriction>
</xs:simpleType>
Eine andere Möglichkeit, einen Typ zu definieren, stellt eine Aufzählung dar, die eine
Menge mit zulässigen Werten definiert. Das geschlecht-Element beispielsweise darf nur
zwei Werte enthalten: weiblich oder männlich. Hier sehen Sie einen Typ für geschlecht:
<xs:simpleType name="geschlechtTyp">
<xs:restriction base="xs:token">
<xs:enumeration value="weiblich"/>
<xs:enumeration value="männlich"/>
146 |
Kapitel 4: Qualitätskontrolle mit Schemas
</xs:restriction>
</xs:simpleType>
Lassen Sie mich jetzt zeigen, wie ich ein Schema für den Dokumenttyp CensusML schreiben würde. Beispiel 4-6 zeigt meinen Versuch.
Beispiel 4-6: Ein Schema für CensusML
<?xml version="1.0" encoding="ISO-8859-1"?>eingefügt
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- Dokumentelement -->
<xs:element name="census-datensatz">
<xs:complexType>
<xs:sequence>
<xs:element ref="datum"/>
<xs:element ref="adresse"/>
<xs:element ref="person" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute ref="erhoben-durch"/>
</xs:complexType>
</xs:element>
<!-- Die Zahl, die den Volkszähler identifiziert (1-9999) -->
<xs:attribute name="erhoben-durch">
<xs:simpleType>
<xs:restriction base="integer">
<xs:minInclusive value="1"/>
<xs:maxInclusive value="9999"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<!-- Eine Struktur, die Datumsinformationen enthält. -->
<!-- Das ist eine Vereinfachung gegenüber der vorangehenden -->
<!-- Definition, die drei Unterelemente verwendete. -->
<xs:element name="datum" type="date"/>
<!-- Eine Struktur, die Adressinformationen enthält. -->
<xs:element name="adresse">
<xs:complexType>
<xs:all>
<xs:element ref="straße"/>
<xs:element ref="ort"/>
<xs:element ref="bezirk"/>
<xs:element ref="staat"/>
<xs:element ref="postleitzahl"/>
</xs:all>
</xs:complexType>
</xs:element>
<xs:element
<xs:element
<xs:element
<xs:element
name="straße" type="string"/>
name="ort" type="string"/>
name="bezirk" type="string"/>
name="staat" type="string"/>
W3C XML Schema
|
147
Beispiel 4-6: Ein Schema für CensusML (Fortsetzung)
<!-- Das postleitzahl-Element nutzt das Format 123ABC. -->
<xs:element name="postleitzahl"/>
<xs:simpleType>
<xs:restriction base="string">
<xs:pattern value="[0-9]{3}[A-Z]{3}"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<!-- Eine Struktur, die Daten zu einem Bewohner eines Haushalts enthält. -->
<xs:element name="person">
<xs:complexType>
<xs:all>
<xs:element ref="name"/>
<xs:element ref="alter"/>
<xs:element ref="geschlecht"/>
</xs:all>
<xs:attribute ref="beschäftigt"/>
<xs:attribute ref="pid"/>
</xs:complexType>
</xs:element>
<!-- Beschäftigungsstatus: vollzeit, teilzeit oder keins -->
<xs:attribute name="beschäftigt">
<xs:simpleType>
<xs:restriction base="string">
<xs:enumeration value="vollzeit"/>
<xs:enumeration value="teilzeit"/>
<xs:enumeration value="keins"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<!-- Zahl, die Person identifiziert (1-999999) -->
<xs:attribute name="pid">
<xs:simpleType>
<xs:restriction base="integer">
<xs:minInclusive value="1"/>
<xs:maxInclusive value="999999"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<!-- Alter (0-200) -->
<xs:element name="alter">
<xs:simpleType>
<xs:restriction base="integer">
<xs:minInclusive value="0"/>
<xs:maxInclusive value="200"/>
</xs:restriction>
</xs:complexType>
148 |
Kapitel 4: Qualitätskontrolle mit Schemas
Beispiel 4-6: Ein Schema für CensusML (Fortsetzung)
</xs:element>
<!-- Geschlechtstyp: männlich oder weiblich -->
<xs:element name="geschlecht">
<xs:simpleType>
<xs:restriction base="string">
<xs:enumeration value="weiblich"/>
<xs:enumeration value="männlich"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<!-- Ein Struktur, die den Namen enthält. Beachten Sie das choice-Element,
das ein optionales junior- ODER senior-Element erlaubt. -->
<xs:element name="name">
<xs:complextype>
<xs:all>
<xs:element ref="vorname"/>
<xs:element ref="nachname"/>
</xs:all>
<xs:choice minOccurs="0">
<xs:element ref="junior"/>
<xs:element ref="senior"/>
</xs:choice>
</xs:complextype>
</xs:element>
<xs:element name="junior" type="leeresElem"/>
<xs:element name="senior" type="leeresElem"/>
<!-- Einen Typ für eine leeres Element definieren. -->
<xs:complexType name="leeresElem"/>
</xs:schema>
Einige Bemerkungen:
• Da XML Schema eine Vielzahl von Datumsformaten unterstützt, ist es sinnvoll, den
umständlichen datum-Container und seine drei Kindelemente durch ein Element zu
ersetzen, das nur Textinhalt aufnimmt. Das vereinfacht das Schema und die Unterstützungsprogramme für die Census-Anwendung.
• Ich habe das Attribut maxOccurs verwendet, um eine unbegrenzte Anzahl von personElementen zuzulassen. Ohne dieses würde das Schema nur ein solches Element
erlauben.
• Ein choice-Element ist das Gegenstück zu all. Statt zu fordern, dass alle Elemente
vorkommen, erlaubt es nur, dass lediglich eins der möglichen Elemente vorkommt.
In diesem Fall wollte ich, dass höchstens eins der beiden Elemente junior oder
senior erscheint.
W3C XML Schema
|
149
• Das Attribut minOccurs des choice-Elements habe ich auf null gesetzt, um es optional zu machen. Sie können junior oder senior wählen, müssen es aber nicht.
• Seltsamerweise gibt es keinen Typ für leere Elemente. Darum musste ich einen definieren, leeresElem, der für die Elemente junior und senior genutzt wird.
Damit haben wir XML Schema nur oberflächlich berührt. Es bietet eine
unglaubliche Vielzahl von Features, darunter Typerweiterung und -einschränkung, Listen, Unions, Namensraum-Features und vieles mehr.
Mehr Informationen zu XML Schema finden Sie in Eric van der Vlists
XML Schema (O’Reilly Verlag 2003).
RELAX NG
RELAX NG ist eine mächtige Schema-Validierungssprache, die auf früheren Arbeiten wie
RELAX und TREX aufbaut. Wie W3C Schema verwendet RELAX NG eine XML-Syntax
und unterstützt Namensräume und Datentypen. Darüber hinaus integriert es Attribute in
Inhaltsmodelle. Das vereinfacht die Struktur des Schemas erheblich. Es bietet außerdem
einen überlegenen Mechanismus für die Handhabung von ungeordnetem Inhalt und
unterstützt kontextabhängige Inhaltsmodelle.
Im Allgemeinen scheint es einfacher, Schemas in RELAX NG zu schreiben als in W3C
Schema. Die Syntax ist sehr klar. Dafür sorgen Elemente wie zeroOrMore für optionalen
wiederholten Inhalt. Deklarationen können andere Deklarationen enthalten, und auch
das führt zu einer natürlicheren Wiedergabe der Struktur eines Dokuments.
Betrachten Sie das einfache Schema in Beispiel 4-7, das einen Dokumenttyp für die Protokollierung von Arbeitsaktivitäten modelliert. Das Schema lässt sich gut lesen, und die
Struktur eines typischen Dokuments kann gut antizipiert werden.
Beispiel 4-7: Ein einfaches RELAX NG-Schema
<?xml version="1.0" encoding="ISO-8859-1"?>
<element name="arbeitslog"
xmlns="http://relaxng.org/ns/structure/1.0"
xmlns:bem="http://relaxng.org/ns/kompatibilitaet/bemerkungen/1.0">
<bem:dokumentation>Ein Dokument zur Protokollierung von Arbeitsaktivitäten,
die in Tage und weiter in Aufgaben eingeteilt werden.</bem:dokumentation>
<zeroOrMore>
<element name="tag">
<attribute name="datum">
<text/>
</attribute>
<zeroOrMore>
<element name="aufgabe">
<element name="beschreibung">
<text/>
</element>
<element name="anfangszeit">
<text/>
150 |
Kapitel 4: Qualitätskontrolle mit Schemas
Beispiel 4-7: Ein einfaches RELAX NG-Schema (Fortsetzung)
</element>
<element name="endzeit">
<text/>
</element>
</element>
</zeroOrMore>
</element>
</zeroOrMore>
</element>
Als DTD würde das so aussehen:
<!ELEMENT
<!ELEMENT
<!ELEMENT
<!ELEMENT
<!ELEMENT
<!ELEMENT
<!ATTLIST
arbeitslog (tag*)>
tag (aufgabe*)>
aufgabe (beschreibung, anfangszeit, endzeit)>
beschreibung #PCDATA>
anfangszeit #PCDATA>
endzeit #PCDATA>
tag datum CDATA #REQUIRED>
Die DTD ist kompakter, stützt sich aber auf eine Syntax, die entschieden kein XML ist.
RELAX NG bewirkt das Gleiche mit größerer Lesbarkeit.
RELAX NG bietet auch eine kompakte Syntax, die der von DTDs ähnelt,
aber alle Features von RELAX NG enthält. Eine kurze Einführung finden
Sie unter http://www.xml.com/pub/a/2002/06/19/rng-compact.html. James
Clarks Trang-Programm, das Sie unter http://www.thaiopensource.com/
relaxng/trang.html bekommen können, macht es leicht, zwischen RELAX
NG, RELAX NG Compact Syntax und DTDs ebenso wie W3C XML Schema
hin und her zu konvertieren.
Die grundlegende Komponente eines RELAX NG-Schemas ist ein Muster. Ein Muster
zeigt jedes beliebige Konstrukt an, das die Ordnung sowie die Typen von Struktur und
Daten beschreibt. Es kann eine Element-Deklaration, eine Attribut-Deklaration, Zeichendaten oder jede beliebige Kombination davon sein. Die Elemente in dem Schema
werden verwendet, um diese Muster zu gruppieren, zu ordnen und zu parametrisieren.
Beachten Sie, dass jedes Element oder Attribut, das in einem anderen Namensraum als
dem RELAX NG-Namensraum (http://relaxng.org/ns/structure/1.0) steht, vom Parser einfach ignoriert wird. Das verschafft uns einen Mechanismus zum Einbetten von Kommentaren und Bemerkungen. Deswegen habe ich im vorangehenden Beispiel den bemNamensraum geschaffen.
Elemente
Das element-Konstrukt wird verwendet, um ein Element zu deklarieren und um festzulegen, an welchem Ort es auftauchen darf (wenn es in einer anderen element-Deklaration
steht). Das folgende Schema deklariert beispielsweise drei Elemente, bericht, titel und
RELAX NG |
151
rumpf, und gibt an, dass das erste Element die anderen beiden Elemente genau in der Reihenfolge und Häufigkeit enthält, in der sie hier auftauchen:
<element name="bericht"
xmlns="http://relaxng.org/ns/structure/1.0">
<element name="titel">
<text/>
</element>
<element name="rumpf">
<text/>
</element>
</element>
Zwischen diesen Elementen ist wie bei DTDs Whitespace zulässig. Das text-Element,
das immer leer ist, schränkt den Inhalt der inneren Elemente auf Zeichendaten ein.
Wiederholung
RELAX NG bietet zwei Modifikator-Elemente, zeroOrMore und oneOrMore, die wiederholt
vorkommende Kindelemente möglich machen. Sie wirken wie die Sternchen- und PlusOperatoren (* bzw. +) in DTDs. In diesem Beispiel wurde das rumpf-Element so verändert, dass es eine beliebige Zahl von absatz-Elementen zulässt:
<element name="bericht"
xmlns="http://relaxng.org/ns/structure/1.0">
<element name="titel">
<text/>
</element>
<element name="rumpf">
<zeroOrMore>
<element name="absatz">
<text/>
</element>
</zeroOrMore>
</element>
</element>
Auswahl
Der Fragezeichen-Operator (?) in DTDs bedeutet, dass ein Element optional ist (null Mal
oder mehrfach vorkommen kann). In RELAX NG können Sie das mit dem optionalModifikator bewirken. Dieses Schema erlaubt Ihnen beispielsweise, nach titel ein
optionales autorenname-Element einzufügen:
<element name="bericht"
xmlns="http://relaxng.org/ns/structure/1.0">
<element name="titel">
<text/>
</element>
<optional>
<element name="autorenname">
<text/>
</element>
152 |
Kapitel 4: Qualitätskontrolle mit Schemas
</optional>
<element name="rumpf">
<text/>
</element>
</element>
Es kann auch verwendet werden, um eine Auswahl von Elementen anzubieten. Dem
Senkrechter-Strich-Operator (|) von DTDs entspricht der Modifikator choice. Hier fordern wir, dass nach titel entweder ein autorenname- oder ein quelle-Element erscheint:
<element name="bericht"
xmlns="http://relaxng.org/ns/structure/1.0">
<element name="titel">
<text/>
</element>
<choice>
<element name="autorenname">
<text/>
</element>
<element name="quelle">
<text/>
</element>
</choice>
<element name="rumpf">
<text/>
</element>
</element>
Diese Deklaration kombiniert choice und zeroOrMore, um einen Container zu erzeugen,
der gemischten Inhalt (Text und Elemente in beliebiger Reihenfolge) haben kann:
<element name="absatz"
xmlns="http://relaxng.org/ns/structure/1.0">
<zeroOrMore>
<choice>
<text/>
<element name="betonung">
<text/>
</element>
</choice>
</zeroOrMore>
</element>
Gruppierung
Sie können den Modifikator group einsetzen, um eine erforderliche Folge von Kindelementen anzugeben. Er funktioniert ähnlich wie die Klammern in DTDs. Hier beispielsweise kann der (jetzt erforderliche) autorenname entweder Klartext oder eine Folge von
Elementen sein:
<element name="bericht"
xmlns="http://relaxng.org/ns/structure/1.0">
<element name="titel">
<text/>
RELAX NG |
153
</element>
<element name="autorenname">
<choice>
<text/>
<group>
<element name="vorname"><text/></element>
<element name="nachname"><text/></element>
</group>
</choice>
</element>
<element name="rumpf">
<text/>
</element>
</element>
Der Container group ist notwendig, weil ohne ihn die Elemente vorname und nachname Teil
der Auswahl wären und einander dann gegenseitig ausschließen würden.
DTDs bieten keine Möglichkeit, eine Elementgruppe anzugeben, bei der die Reihenfolge
der Elemente nicht bedeutsam, der Inhalt selbst aber erforderlich ist. RELAX NG bietet
den Container interleave, der genau das ermöglicht. Er erzwingt, dass alle Elemente vorhanden sind, erlaubt aber, dass sie in beliebiger Reihenfolge erscheinen. Im Folgenden
Beispiel kann titel vor autorenname kommen oder danach:
<element name="bericht"
xmlns="http://relaxng.org/ns/structure/1.0">
<interleave>
<element name="titel">
<text/>
</element>
<element name="autorenname">
<text/>
</element>
</interleave>
<element name="rumpf">
<text/>
</element>
</element>
Nicht-Element-Inhalts-Deskriptoren
Der Inhalts-Deskriptor text ist nur eine aus einer Reihe von Optionen zur Beschreibung
von Nicht-Element-Inhalt. Hier ist die vollständige Sammlung:
Name
Inhalt
empty
Gar kein Inhalt.
text
Jeder beliebige String.
value
Ein festgelegter Wert.
data
Text, der einem bestimmten Muster (Datentyp) entspricht.
list
Eine Folge von Werten.
154 |
Kapitel 4: Qualitätskontrolle mit Schemas
Der Marker empty schließt jeden Inhalt aus. Da das Element lesezeichen diese Deklaration enthält, darf es nur als leeres Element erscheinen und in keiner anderen Form:
<element name="lesezeichen">
<empty/>
</element>
RELAX NG bietet den Deskriptor value, um eine bestimmte Folge von Zeichen anzugeben. Hier sehen Sie beispielsweise eine Aufzählung mit Werten für ein Element größe:
<element name="größe">
<choice>
<value>klein</value>
<value>mittel</value>
<value>groß</value>
</choice>
</element>
Standardmäßig normalisiert value den String, entfernt überzählige Leerzeichen. Das folgende Beispielelement würde von der vorangehenden Deklaration akzeptiert:
<größe>
klein
</größe>
Wenn Sie die Normalisierung ausschalten und nur genau übereinstimmende Strings
zulassen wollen, müssen Sie das Attribut type="string" hinzufügen. Bei der folgenden
Deklaration würde der Inhalt des vorausgehenden Elements verworfen, weil zusätzliche
Leerzeichen darin enthalten sind:
<element name="größe">
<choice>
<value type="string">klein</value>
<value type="string">mittel</value>
<value type="string">groß</value>
</choice>
</element>
Der interessanteste Inhalts-Deskriptor ist data. Das ist in RELAX NG das Fundament für
die Verwendung von Datentypen. Sein type-Attribut enthält den Namen eines Typs, der
in einer Datentyp-Bibliothek definiert wird. (Zerbrechen Sie sich hier noch nicht den
Kopf darüber, was das bedeutet, darum kümmern wir uns bald.) Der Inhalt des hier
deklarierten Elements soll ein Integer-Wert sein:
<element name="font-größe">
<data type="integer"/>
</element>
Ein Nachteil von data ist, dass es im Unterschied zu text in Inhalten nicht mit Elementen
vermischt werden darf.
Der Deskriptor list enthält eine Folge von Tokens, die mit Leerzeichen voneinander
getrennt werden. Token-Listen sind ein praktisches Mittel, um Mengen mit diskreten Daten
zu repräsentieren. Hier wird eine verwendet, um eine Menge mit Zahlen zu kodieren:
<element name="vektor">
<list>
RELAX NG |
155
<oneOrMore>
<data type="float"/>
</oneOrMore>
</list>
</element>
So sieht ein zulässiger vektor aus:
<vektor>44.034 19.0 -65.33333</vektor>
Beachten Sie, dass der oneOrMore-Deskriptor genauso gut mit Text funktioniert wie mit Elementen. Das ist noch ein weiteres Beispiel dafür, wie stimmig und flexibel RELAX NG ist.
Datentypen
Obwohl RELAX NG Datentypen unterstützt, enthält die Spezifikation nur zwei eingebaute Typen: string und token. Wenn Sie andere Arten von Datentypen verwenden wollen, müssen Sie sie aus einer anderen Spezifikation importieren, indem Sie ein
datatypeLibrary-Attribut setzen:
<element name="font-größe">
<data type="integer"
datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"/>
</element>
Damit werden die Datentyp-Definitionen in der W3C Schema-Spezifikation mit Ihrem
Schema verknüpft. Welche Datentypen Sie verwenden können, ist von der Implementierung Ihres RELAX NG validierenden Parsers abhängig.
Es ist nicht so praktisch, das datatypeLibrary-Attribut in jedem data-Element anzugeben.
Die gute Nachricht ist, dass es von jedem Vorfahr im Schema geerbt werden kann. Hier
deklarieren wir es einmal in einer Element-Deklaration, und alle data-Deskriptoren darin
verwenden diese Bibliothek:
<element name="rechteck"
xmlns="http://relaxng.org/ns/structure/1.0"
datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
<element name="breite">
<data type="double"/>
</element>
<element name="höhe">
<data type="double"/>
</element>
</element>
String und token
string und token passen beide auf beliebige Folgen zulässiger XML-Zeichendaten. Der
Unterschied ist, dass token Whitespace normalisiert und string Whitespace lässt, wie er
sind. Sie entsprechen den Datentypen value bzw. fixed.
156 |
Kapitel 4: Qualitätskontrolle mit Schemas
Parameter
Einige Datentypen erlauben Ihnen, einen Parameter anzugeben, der das Muster noch
weiter einschränkt. Das wird mit einem param-Element als Kindelement des data-Elements ausgedrückt. Das folgende Element gibt beispielsweise an, dass sein Inhalt nicht
mehr als acht Zeichen umfassen darf:
<element name="benutzername"
xmlns="http://relaxng.org/ns/structure/1.0">
<data type="string">
<param name="maxLength">8</param>
</data>
</element>
Attribute
Attribute werden auf fast genau die gleiche Weise deklariert wie Elemente. In diesem Beispiel fügen wir dem bericht-Element ein datum-Attribut hinzu:
<element name="bericht"
xmlns="http://relaxng.org/ns/structure/1.0">
<attribute name="datum">
<text/>
</attribute>
<element name="titel">
<text/>
</element>
<element name="rumpf">
<text/>
</element>
</element>
Anders als bei Elementen spielt die Reihenfolge der Attribute keine Rolle. Die Attribute
des alarm-Elements im nächsten Beispiel können in beliebiger Reihenfolge auftauchen,
obwohl wir kein choice-Element verwendet haben:
<element name="alarm"
xmlns="http://relaxng.org/ns/structure/1.0">
<attribute name="priorität">
<value>notfall</value>
<value>wichtig</value>
<value>warnung</value>
<value>benachrichtigung</value>
</attribute>
<attribute name="symbol">
<value>bombe</value>
<value>ausrufezeichen</value>
<value>trauriger-smiley</value>
</attribute>
<element name="rumpf">
<text/>
</element>
</element>
RELAX NG |
157
Element- und Attribut-Deklarationen sind austauschbar. Hier nutzen wir ein choice-Element, um zwei Möglichkeiten anzubieten: Bei der einen ist symbol ein Attribut, bei der
anderen ein Element. Die zwei schließen einander aus:
<element name="alarm"
xmlns="http://relaxng.org/ns/structure/1.0">
<choice>
<attribute name="symbol">
<value>bombe</value>
<value>ausrufezeichen</value>
<value>trauriger-smiley</value>
</attribute>
<element name="symbol">
<value>bombe</value>
<value>ausrufezeichen</value>
<value>trauriger-smiley</value>
</element>
</choice>
<element name="rumpf">
<text/>
</element>
</element>
Ein anderer Unterschied zu Element-Deklarationen ist, dass es eine Kurzform gibt, um
anzuzeigen, dass, wenn keine Inhaltsinformationen angeboten werden, standardmäßig
Text angenommen werden soll. So z.B. bei dieser Deklaration:
<element name="betonung">
<attribute name="style"/>
</element>
Das ist äquivalent zu:
<element name="betonung">
<attribute name="style">
<text/>
</attribute>
</element>
Diese Austauschbarkeit von Element- und Attribut-Deklarationen macht diese SchemaSprache einfacher und eleganter.
Namensräume
RELAX NG ist vollständig namensraumkompatibel. Sie können Namensräume in jedes
name-Attribut einschließen. Dazu verwendet man das xmlns-Attribut:
<element name="gedicht"
xmlns="http://relaxng.org/ns/structure/1.0">
xmlns:foo="http://www.meinzeug.com/kommentar">
<optional>
<attribute name="xml:space">
<choice>
<value>default</value>
158 |
Kapitel 4: Qualitätskontrolle mit Schemas
<value>preserve</value>
</choice>
</attribute>
</optional>
<zeroOrMore>
<choice>
<text/>
<element name="foo:kommentar"><text/></element>
</choice>
</zeroOrMore>
</element>
Sie können jeder Attribut- oder Element-Deklaration das Attribut ns hinzufügen, um
einen impliziten Namensraum-Kontext zu erzeugen. Folgende Deklaration beispielsweise:
<element name="gemüse" ns="http://www.broccoli.net"
xmlns="http://relaxng.org/ns/structure/1.0">
<empty/>
</element>
würde auf die beiden folgenden Elemente passen:
<nahrung:gemüse xmlns:nahrung="http://www.broccoli.net"/>
<gemüse xmlns="http://www.broccoli.net"/>
aber nicht auf diese:
<gemüse/>
<nahrung:gemüse xmlns:nahrung="http://www.hässliches-obst.org"/>
Die Namensraum-Einstellung wird vererbt. Sie müssen sie also nur einmal weit oben in
der Element-Hierarchie setzen. Hier fordern die beiden inneren Element-Deklarationen
für titel und rumpf implizit den Namensraum http://schreibenleichtgemacht.info:
<element name="bericht" ns="http://schreibenleichtgemacht.info"
xmlns="http://relaxng.org/ns/structure/1.0">
<element name="titel"><text/></element>
<element name="rumpf"><text/></element>
</element>
Namensklassen
Eine Namensklasse ist jedes beliebige Muster, das für eine Gruppe von Element- oder Attributtypen stehen kann. Wir haben bereits eine kennen gelernt, choice, die auf eine aufgezählte Menge von Elementen und Attributen passt. Noch offener ist die Klasse anyName, die
erlaubt, dass jeder Elemente- oder Attributtyp das beschriebene Inhaltsmodell hat.
Dieses Muster beispielsweise passt auf jedes wohlgeformte Dokument:
<grammar
xmlns="http://relaxng.org/ns/structure/1.0">
<start>
<ref name="alle-elemente"/>
</start>
RELAX NG |
159
<define name="alle-elemente">
<element>
<anyName/>
<!-- Wird anstelle des "name"-Attributs verwendet -->
<zeroOrMore>
<choice>
<ref name="anyElement"/>
<text/>
<attribute><anyName/></attribute>
</choice>
</zeroOrMore>
</element>
</define>
</grammar>
anyName erscheint innerhalb von element anstelle des name-Attributs. zeroOrMore ist hier
erforderlich, weil jede Namensklasse jeweils nur auf ein einziges Objekt passt.
Die Klasse nsName passt auf jedes Element oder Attribut in einem Namensraum, der über
ein ns-Attribut angegeben wird. Beispielsweise:
<element
xmlns="http://relaxng.org/ns/structure/1.0">
<nsName ns="http://erfundene-site.org" />
<empty/>
</element>
So wird eingestellt, dass jedes Element im Namensraum http://erfundene-site.org ein
leeres Element ist. Lassen Sie das ns-Attribut weg, erbt nsName den Namensraum vom
nächsten Vorfahr, der einen definiert. Das also funktioniert auch:
<element ns="http://erfundene-site.org"
xmlns="http://relaxng.org/ns/structure/1.0">
<nsName />
<empty />
</element>
Wenn Sie nicht alles durchlassen wollen, können Sie die Menge mit except beschneiden.
Verwenden Sie es als Kindelement von anyName oder nsName, um Klassen von Elementen
oder Attributen aufzuführen, die Sie nicht zulassen wollen. Hier werden nur die Elemente, die sich nicht im aktuellen Namensraum befinden, als leer deklariert:
<element ns="http://erfundene-site.org"
xmlns="http://relaxng.org/ns/structure/1.0">
<anyName>
<except>
<nsName />
</except>
</anyName>
<empty />
</element>
Sie dürfen Namensklassen nur nicht als Kindelemente von define-Elementen verwenden.
Folgendes ist falsch:
160 |
Kapitel 4: Qualitätskontrolle mit Schemas
<define name="zu-mehrdeutig">
<anyName/>
</define>
define-Elemente werden wir im nächsten Abschnitt betrachten.
Während dieses Buch in den Druck ging, kündigte James Clark eine
Namespace Routing Language (NRL) an, in der man mit großer Flexibilität beschreiben kann, wie Inhalte aus unterschiedlichen Namensräumen
validiert und verarbeitet werden sollen. Weitere Informationen und eine
Implementierung finden Sie unter http://www.thaiopensource.com/relaxng/
nrl.html.
Benannte Muster
Die Muster, die uns bisher begegnet sind, sind monolithisch. Alle Deklarationen sind in
eine große Deklaration eingebettet. Für einfache Dokumente ist das in Ordnung, aber
wenn die Komplexität zunimmt, können solche Deklarationen unübersichtlich werden.
Benannte Muster ermöglichen Ihnen, Deklarationen aus dem Hauptmuster auszulagern
und das Schema so in getrennte Teile zu zerlegen, mit denen man leichter arbeiten kann.
Es bietet auch eine Möglichkeit, Muster wiederzuverwenden, die an vielen Stellen vorkommen.
Ein Schema, das benannte Muster nutzt, hat folgendes Layout:
<grammar>
<start>
Hauptmuster
</start>
<define name="Mustername">
Muster
</define>
weitere Muster-Definitionen
</grammar>
Das umgebende grammar-Element umschließt sowohl das Hauptmuster als auch eine
Gruppe von Definitionen benannter Muster. Es enthält genau ein start-Element mit dem
primären Muster und eine beliebige Zahl von define-Elementen, die jeweils ein benanntes Muster definieren. Benannte Muster werden mit dem ref-Element in ein Muster
importiert. Ein Beispiel:
<grammar
xmlns="http://relaxng.org/ns/structure/1.0">
<start>
<element name="bericht">
<ref name="kopf"/>
<ref name="rumpf"/>
</element>
</start>
<define name="kopf">
RELAX NG |
161
<element name="titel">
<text/>
</element>
<element name="autorenname">
<text/>
</element>
</define>
<define name="rumpf">
<zeroOrMore>
<element name="absatz">
<text/>
</element>
</zeroOrMore>
</define>
</grammar>
Das start-Element muss genau ein Muster enthalten. Ein define-Element jedoch kann
eine beliebige Zahl von Kindelementen enthalten, weil sein Inhalt in ein anderes Muster
kopiert wird.
Sie können eine Grammatik auch im DTD-Stil schreiben, die für jedes Element eine Definition enthält:6
<grammar
xmlns="http://relaxng.org/ns/structure/1.0">
<start>
<element name="bericht">
<ref name="titel"/>
<ref name="autorenname"/>
<zeroOrMore>
<ref name="absatz"/>
</zeroOrMore>
</element>
</start>
<define name="titel">
<element name="titel">
<text/>
</element>
</define>
<define name="autorenname">
<element name="autorenname">
<text/>
</element>
</define>
6 Auf diese Weise können DTDs unmittelbar auf ein RELAX NG-Schema abgebildet werden. Diese Art von
Rückwärtskompatibilität ist wichtig, weil die meisten Leute immer noch DTDs verwenden. Das ist also eine
gute Möglichkeit, zu RELAX NG zu migrieren.
162 |
Kapitel 4: Qualitätskontrolle mit Schemas
<define name="absatz">
<element name="absatz">
<text/>
</element>
</define>
</grammar>
Rekursive Definitionen
Rekursive Definitionen sind zulässig, solange das ref-Element innerhalb eines elementElements steht. Dieses Muster beschreibt ein Abschnitt-Element, das bis in eine beliebige
Tiefe Unterabschnitte haben kann:
<grammar
xmlns="http://relaxng.org/ns/structure/1.0">
<start>
<element name="bericht">
<element name="titel"><text/></element>
<zeroOrMore>
<ref name="absatz">
</zeroOrMore>
<zeroOrMore>
<ref name="abschnitt"/>
</zeroOrMore>
</element>
</start>
<define name="absatz">
<element name="absatz">
<text/>
</element>
</define>
<define name="abschnitt">
<element name="abschnitt">
<zeroOrMore>
<ref name="absatz"/>
</zeroOrMore>
<zeroOrMore>
<ref name="abschnitt"/>
</zeroOrMore>
</element>
</define>
</grammar>
Steht bei einer rekursiven Definition ref nicht innerhalb eines element, führt das zu einer
logischen Endlosschleife. Deswegen ist Folgendes illegal:
<define name="foo">
<choice>
<ref name="bar"/>
RELAX NG |
163
<ref name="foo"/>
</choice>
</define>
Die Reihenfolge der Definitionen für benannte Muster spielt keine Rolle. Solange jedem
referenzierten Muster eine Definition innerhalb derselben grammar entspricht, ist alles in
Ordnung.
Aggregat-Definitionen
Mehrere Muster-Definitionen mit dem gleichen Namen sind illegal, es sei denn, Sie nutzen das combine-Attribut. Dieses sagt dem Prozessor, dass er die Definitionen zu einer
zusammenfügen soll, in der sie entweder mit dem choice- oder dem interleave-Container gruppiert werden. Der Wert des Attributs gibt an, wie die Teile verbunden werden
sollen. Ein Beispiel:
<define name="block.klasse" combine="choice">
<element name="titel">
<text/>
</element>
</define>
<define name="block.klasse" combine="choice">
<element name="absatz">
<text/>
</element>
</define>
Das ist äquivalent zu:
<define name="block.klasse"
xmlns="http://relaxng.org/ns/structure/1.0">
<choice>
<element name="titel">
<text/>
</element>
<element name="absatz">
<text/>
</element>
</choice>
</define>
Der Nutzen von Aggregat-Definitionen wird klarer, wenn sie für Muster in anderen
Dateien verwendet werden.
Modularität
Bei Schemas verlangt gutes Haushalten häufig, dass man Teile in verschiedene Dateien
steckt. Das macht nicht nur die Teile kleiner und leichter handhabbar, sondern ermöglicht auch, dass sie von verschiedenen Schemas genutzt werden können.
164 |
Kapitel 4: Qualitätskontrolle mit Schemas
Externe Referenzen
Das Muster externalRef funktioniert wie ref und nutzt das Attribut href, um die Datei
anzugeben, die eine Grammatik enthält. externalRef verweist auf die ganze Grammatik,
nicht auf ein einzelnes benanntes Muster darin.
Angenommen, Sie haben eine Datei namens section.rng, die dieses Muster enthält:
<grammar
xmlns="http://relaxng.org/ns/structure/1.0">
<start>
<ref name="abschnitt"/>
</start>
<define name="abschnitt">
<element name="abschnitt">
<zeroOrMore>
<ref name="absatz"/>
</zeroOrMore>
<zeroOrMore>
<ref name="abschnitt"/>
</zeroOrMore>
</element>
</define>
<define name="absatz">
<text/>
</define>
</grammar>
Das können Sie folgendermaßen in ein Muster in einer anderen Datei einbinden:
<element name="bericht"
xmlns="http://relaxng.org/ns/structure/1.0">
<element name="titel"><text/></element>
<oneOrMore>
<externalRef href="section.rng"/>
</oneOrMore>
</element>
Eingebette Grammatiken
Ein Folge externer Referenzen ist, dass Grammatiken tatsächliche andere Grammatiken
enthalten können. Um Namenskonflikte zu verhindern, besitzt jede grammar ihren eigenen Geltungsbereich für benannte Muster. Die benannten Muster in einem Vorfahr stehen Kindgrammatiken nicht automatisch zur Verfügung. ref kann nur auf Definitionen
aus der aktuellen grammar verweisen.
Wenn Sie diese Einschränkung umgehen wollen, können Sie parentRef verwenden. Es
funktioniert wie ref, sucht aber nach Definitionen in der Grammatik eine Stufe höher.
Betrachten Sie folgendes Beispiel, in dem zwei Grammatiken aufeinander verweisen. Ich
definiere ein Element, absatz, als einen Absatz, der Fußnoten enthalten kann. Das Element fußnote selbst kann eine Anzahl von absatz.inhalt-Elementen enthalten. Sie wer-
RELAX NG |
165
den in den Dateien absatz.rng bzw. fussnote.rng gespeichert. Beispiel 4-8 und 4-9 zeigen,
wie sie aussehen.
Beispiel 4-8: absatz.rng
<?xml version="1.0" encoding="ISO-8859-1"?>
<grammar
xmlns="http://relaxng.org/ns/structure/1.0">
<start>
<element name="absatz">
<zeroOrMore>
<choice>
<ref name="absatz.inhalt"/>
<externalRef name="fussnote.rng"/>
</choice>
</zeroOrMore>
</element>
</start>
<define name="absatz.inhalt">
<text/>
</define>
</grammar>
Beispiel 4-9: fussnote.rng
<?xml version="1.0" encoding="ISO-8859-1"?>
<grammar
xmlns="http://relaxng.org/ns/structure/1.0">
<start>
<element name="fußnote">
<oneOrMore>
<parentRef name="absatz.inhalt"/>
</oneOrMore>
</element>
</start>
</grammar>
Das Fußnoten-Muster stützt sich darauf, dass seine Elterngrammatik ein Muster für
absatz.inhalt definiert.
Grammatiken zusammenführen
Sie können Grammatiken aus externen Quellen zusammenfügen, indem Sie include als
Kind von grammar verwenden. Wie externalRef nutzt include ein href-Attribut, um die
Definitionen einzulesen. Es integriert sie aber tatsächlich in denselben Kontext. externalRef hingegen hält die Geltungsbereiche für benannte Muster getrennt.
Man kann include beispielsweise nutzen, um eine bestehende Definition mit weiteren
Mustern zu erweitern. Angenommen, dieses Muster befindet sich in block.rng:
<grammar
xmlns="http://relaxng.org/ns/structure/1.0">
<start>
166 |
Kapitel 4: Qualitätskontrolle mit Schemas
<ref name="block.klasse"/>
</start>
<define name="block.klasse">
<choice>
<element name="titel">
<text/>
</element>
<element name="absatz">
<text/>
</element>
</choice>
</define>
</grammar>
Dieser Klasse können Sie weitere Elemente hinzufügen, indem Sie sie mit include folgendermaßen einbinden:
<grammar
xmlns="http://relaxng.org/ns/structure/1.0">
<include href="block.rng">
<start>
<oneOrMore>
<element name="section">
<ref name="block.klasse"/>
</element>
</oneOrMore>
</start>
<define name="block.klasse" combine="choice">
<element name="gedicht">
<text/>
</element>
</define>
</grammar>
Das Attribut combine ist notwendig, weil dem Prozessor mitgeteilt werden muss, wie die
neue Definition in die vorangehende Definition, die aus block.rng importiert wird, eingegliedert werden soll. Sie sehen, dass von mehreren define-Mustern für denselben Namen
eines das combine-Attribut weglassen darf, wie es hier bei block.rng der Fall ist.
Importierte Definitionen überschreiben
Sie können einige der Definitionen überschreiben, die Sie importiert haben, indem Sie
neue Definitionen in das include-Element einschließen. Angenommen, Sie haben die folgende Datei bericht.rng:
<grammar
xmlns="http://relaxng.org/ns/structure/1.0">
<start>
<element name="bericht">
<ref name="kopf"/>
<ref name="rumpf"/>
</element>
</start>
RELAX NG |
167
<define name="kopf">
<element name="titel"><text/></element>
</define>
<define name="rumpf">
<element name="abschnitt">
<oneOrMore>
<element name="absatz"><text/></element>
</oneOrMore>
</element>
</define>
</grammar>
Sie möchten diese Grammatik importieren, aber leicht anpassen. Es soll nicht nur ein
titel, sondern auch ein untertitel erlaubt sein. Statt die ganze Grammatik neu zu
schreiben, müssen Sie nur kopf neu definieren:
<grammar
xmlns="http://relaxng.org/ns/structure/1.0">
<include href="report.rng">
<define name="kopf">
<element name="titel"><text/></element>
<optional>
<element name="untertitel"><text/></element>
</optional>
</define>
</include>
<start>
<ref name="bericht">
</start>
</grammar>
Das ist eine gute Möglichkeit, ein Schema so anzupassen, dass es Ihren Anforderungen
entspricht.
CensusML-Beispiel
Für den Fall, dass Sie neugierig geworden sind, will ich noch einmal zu unserem CensusML-Beispiel aus dem Abschnitt »W3C XML Schemas« zurückkehren und versuchen,
es als RELAX NG-Schema zu formulieren. Das Ergebnis ist Beispiel 4-10.
Beispiel 4-10: Ein RELAX NG-Schema für CensusML
<?xml version="1.0" encoding="ISO-8859-1"?>
<element name="census-datensatz">
xmlns="http://relaxng.org/ns/structure/1.0"
datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
<attribute name="erhoben-durch">
<data type="integer">
<param name="minInclusive">1</param>
<param name="maxInclusive">9999</param>
</data>
</attribute>
168 |
Kapitel 4: Qualitätskontrolle mit Schemas
Beispiel 4-10: Ein RELAX NG-Schema für CensusML (Fortsetzung)
<element name="datum">
<data type="date"/>
</element>
<element name="adresse">
<interleave>
<element name="straße"><text/></element>
<element name="ort"><text/></element>
<element name="bezirk"><text/></element>
<element name="postleitzahl">
<data type="string">
<param name="pattern">[0-9][0-9][0-9][A-Z][A-Z][A-Z]</param>
</data>
</element>
</interleave>
</element>
<oneOrMore>
<element name="person">
<interleave>
<attribute name="beschäftigt">
<choice>
<value>vollzeit</value>
<value>teilzeit</value>
<value>none</value>
</choice>
</attribute>
<attribute name="pid">
<data type="integer">
<param name="minInclusive">1</param>
<param name="maxInclusive">999999</param>
</data>
</attribute>
<element name="alter">
<data type="integer">
<param name="minInclusive">0</param>
<param name="maxInclusive">200</param>
</data>
</element>
<element name="geschlecht">
<choice>
<value>männlich</value>
<value>geschlecht</value>
</choice>
</element>
<element name="name">
<interleave>
<element name="vorname"><text/></element>
<element name="nachname"><text/></element>
<optional>
<choice>
<element name="junior"><empty/></element>
<element name="senior"><empty/></element>
</choice>
</optional>
RELAX NG |
169
Beispiel 4-10: Ein RELAX NG-Schema für CensusML (Fortsetzung)
</interleave>
</element>
</interleave>
</element>
</oneOrMore>
</element>
Dieses Schema sieht auf alle Fälle viel ordentlicher aus als die W3C Schema-Version.
Aufzählungen und komplexe Typen sind wesentlich klarer. Die Gruppierungsstrukturen
lassen sich sehr gut lesen. Ich persönlich denke, dass RELAX NG im Großen und Ganzen
intuitiver ist.
Schematron
Schematron wählt einen anderen Ansatz als die Schema-Sprachen, die wir bisher gesehen
haben. Statt Vorschriften der Art »Dieses Element hat das folgende Inhaltsmodell:« aufzustellen, basiert es auf einer Folge Boolescher Tests. Vom Ergebnis des Tests ist dann
abhängig, welche der vorher festgelegten Meldungen ausgegeben wird.
Diese Tests basieren auf XPath. XPath ist eine sehr feinkörnige und umfassende Sammlung von Werkzeugen zur Knotenuntersuchung. XPath als Fundamtent zu nehmen ist
clever und hält einiges an Komplexität aus der Schema-Sprache heraus. XPath, das bei
solchen Dingen wie XSLT und einigen DOM-Implementierungen genutzt wird, kann in
Winkel reichen, in die stumpfere Werkzeuge wie DTDs nicht kommen können. »Es ist«,
wie der Schöpfer von Schematron, Rick Jelliffe, sagt, »ein Staubwedel für die hintersten
Ecken eines Zimmers, die der Staubsauger (DTD) nicht erreichen kann.«
Überblick
Ein Schematron-Schema hat die folgende Grundstruktur:
<schema xmlns="http://www.ascc.net/xml/schematron">
<pattern>
<rule context="XPath-Ausdruck">
<assert test="XPath-Ausdruck">
Meldung
</assert>
<report test="XPath-Ausdruck">
Meldung
</report>
... weitere Tests ...
</rule>
... weitere Regeln ...
</pattern>
... weitere Muster ...
</schema>
In Schematron hat pattern (Muster) nicht die gleiche Bedeutung wie in RELAX NG. Hier
ist es nur eine logische Gruppierung von Regeln. Wenn Ihr Schema Bücher testet, kann
170 |
Kapitel 4: Qualitätskontrolle mit Schemas
ein Muster Regeln für Kapitel enthalten, während ein anderes Regeln für Anhänge gruppiert. Betrachten Sie es also als ein hochstufigeres, konzeptionelles Prüfmuster, nicht als
ein spezifisches knotenprüfendes Muster.
Der Kontext für jeden Test wird durch eine Regel, rule, bestimmt. Sein context-Attribut
enthält ein XPath-Muster, das Knoten findet. Jeder gefundene Knoten wird zum Kontextknoten, auf den alle Tests innerhalb der Regel angewandt werden.
Die Kinder einer Regel, report und assert, wenden jeweils einen Test auf den Kontextknoten an. Dieser Test ist ein anderer XPath-Ausdruck, der im test-Attribut gespeichert
wird. Der Inhalt eines report-Elements wird ausgegeben, wenn ihr XPath-Ausdruck mit
»true« ausgewertet wird. assert ist genau das Gegenteil. Sein Inhalt wird ausgegeben,
wenn sein Test »false« ergibt.
XPath-Ausdrücke eignen sich ausgezeichnet für die Beschreibung von XML-Knoten und
hinreichend gut für die Prüfung von Textmustern. Folgendermaßen könnten Sie eine
E-Mail-Adresse prüfen:
<rule context="email">
<p>E-Mail-Adresse gefunden ...</p>
<assert test="contains(.,'@')">Fehler: Kein @ in der Adresse</assert>
<assert test="contains(.,'.')">Fehler: Kein Punkt in der Adresse</assert>
<report test="length(.)>20">Warnung: E-Mail ungewöhnlich lang</report>
</rule>
Das, was passiert, wenn Sie ein Dokument durch einen Schematron-Validierer laufen lassen, lässt sich folgendermaßen zusammenfassen. Zunächst wird das Dokument geparst
und ein Dokumentbaum im Speicher aufgebaut. Dann wird für jede Regel ein Kontextknoten gesucht. Dazu wird der XPath-Lokalisierungsausdruck verwendet. Schließlich
wird für jedes assert oder report in der Regel der XPath-Ausdruck ausgewertet und
abhängig vom ermittelten Booleschen Wert Text ausgegeben. Der Grundgedanke ist,
dass der Schematron-Prozessor jedes Mal, wenn etwas gefunden wird, das nicht in Ordnung ist, eine entsprechende Nachricht ausgibt. Sie können sich Schemtron als eine Sprache zur Erzeugung von Validierungsberichten vorstellen.
Ein interessantes Feature von Schematron ist, dass bei ihm die Dokumentation Teil der
Sprache selbst ist. Statt sich auf Kommentare oder den Namensraum-Hack aus RELAX NG
zu stützen, definiert diese Sprache explizit Elemente und Attribute, die Kommentare enthalten sollen. Das Wurzelelement, schema, kann ein optionales title-Element enthalten,
mit dem das Schema benannt wird, und pattern-Elemente haben name-Attribute, über die
Regelgruppen identifiziert werden können. Ein Schematron-Validierer verwendet dieses
Attribut, um jedes geprüfte Muster in der Ausgabe zu kennzeichnen. Es gibt auch eine Satz
von Tags zur Textformatierung, die aus HTML entlehnt wurde, wie p und span.
Lassen Sie uns ein Beispiel anschauen. Unten sehen Sie ein Schema, mit dem ein BerichtDokument geprüft wird. Wir lassen zwei Arten von Berichten zu. Ein Bericht muss entweder einen Rumpf oder mindestens drei Abschnitte enthalten.
<schema xmlns="http://www.ascc.net/xml/schematron">
Schematron |
171
<title>Test: Prüfung der Gültigkeit des Dokuments</title>
<pattern name="Typ 1">
<p>Typ 1-Berichte müssen einen Titel und einen Rumpf haben.</p>
<rule context="/">
<assert test="bericht">Falsches Element. Das ist kein Bericht.</assert>
</rule>
<rule context="bericht">
<assert test="titel">Mist! Der Titel fehlt.</assert>
<report test="titel">Gut, Titel gefunden.</report>
<assert test="rumpf">Ach nee! Der Rumpf fehlt.</assert>
<report test="rumpf">Gut, Rumpf gefunden.</report>
</rule>
</pattern>
<pattern name="Type 2">
<p>Type 2-Berichte müssen einen Titel und <em>mindestens
drei</em> Abschnitte haben.</p>
<rule context="/">
<assert test="bericht">Falsches Element. Das ist kein Bericht.</assert>
</rule>
<rule context="bericht">
<assert test="titel">Mist! Der Titel fehlt.</assert>
<report test="titel">Gut, Titel gefunden.</report>
<assert test="count(abschnitt)&gt;2">Dieser Bericht enthält
nicht genug Abschnitte.</assert>
<report test="count(abschnitt)&gt;2">Abschnitte in Massen, also
bin ich zufrieden.</report>
</rule>
</pattern>
</schema>
Lassen Sie jetzt den Schematron-Validierer dieses Dokument prüfen:
<bericht>
<titel>Ein lächerlicher Bericht</titel>
<rumpf>
<absatz>Hier ist ein Absatz.</absatz>
<absatz>Hier ist ein Absatz.</absatz>
</rumpf>
</bericht>
Ich habe eine Schematron-Version benutzt, die ihren Bericht in HTML-Form ausgibt.
Abbildung 4-1 zeigt, wie es in meinem Browser aussieht.
Abstrakte Regeln
Eine abstrakte Regel ermöglicht Ihnen, Regeln wieder zu verwenden, wenn es wahrscheinlich ist, dass sie in einem Schema öfter auftauchen. Die Syntax bleibt die gleiche, es
werden nur die zusätzlichen Attribute abstract und id eingefügt, die auf yes bzw. einen
eindeutigen Wert gesetzt werden. Eine andere Regel kann auf id über das rule-Attribut
eines extends-Kindelements verweisen. Betrachten Sie folgendes Beispiel.
172 |
Kapitel 4: Qualitätskontrolle mit Schemas
Abbildung 4-1: Ein Schematron-Bericht
<rule id="inline" abstract="yes">
<report test="*">Fehler! Element im Inline-Element.</report>
<assert test="text">Komisch, da ist kein Text in diesem Inline-Element.</assert>
</rule>
<rule context="fett">
<extends rule="inline"/>
</rule>
<rule context="betonung">
<extends rule="inline"/>
</rule>
<rule context="zitat">
<extends rule="inline"/>
</rule>
Schemas im Vergleich
Jedes der Schemas, die wir uns angesehen haben, besitzt zugleich überzeugende Eigenschaften und grundlegende Mängel. Einige der wichtigsten Punkte werden in Tabelle 4-2
aufgeführt.
Tabelle 4-2: Ein Schema-Vergleich
Feature
DTD
W3C Schema
RELAX NG
Schematron
XML-Syntax
Nein
Ja
Ja
Ja
Namensraum-Kompatibilität
Nein
Ja
Ja
Ja
Deklararion von Entities
Ja
Nein
Nein
Nein
Prüft Datentypen
Nein
Ja
Ja
Ja
Standardwerte für Attribute
Ja
Ja
Neina
Nein
Notationen
Ja
Ja
Nein
Nein
Ungeordneter Inhalt
Nein
Ja
Ja
Ja
Modularität
Ja
Ja
Ja
Nein
Elemente/Attribute austauschbar
Nein
Ja
Ja
Ja
Schemas im Vergleich |
173
Tabelle 4-2: Ein Schema-Vergleich (Fortsetzung)
Feature
DTD
W3C Schema
RELAX NG
Schematron
Gibt an, wie die Verknüpfung mit einem Dokument hergestellt werden soll
Ja
Ja
Nein
Nein
a Wurde später in der RELAX NG DTD-Kompatibilitätsspezifikation hinzugefügt.
DTDs gibt es bereits am längsten. Deswegen werden sie, wie man auch erwarten würde,
in Literatur und Software am umfassendsten unterstützt. Außerdem haben Sie den Vorteil, dass zurzeit nur sie die Möglichkeit bieten, Entities zu deklarieren. Die Syntax für
DTDs ist leicht erlernbar. Ihre Lesbarkeit lässt allerdings gelegentlich einiges zu wünschen übrig. Versuchen Sie mal, die DocBook-XML-DTD zu lesen, dann werden Sie verstehen, was ich meine. Sie ist mehr schlecht als recht modular, aber mir scheinen
Parameter-Entities häufig eine Qual, insbesondere wenn man importierte Deklarationen
überschreiben will.
W3C XML Schema hat den Vorteil, vom W3C abgesegnet zu sein. Sie können also davon
ausgehen, dass es viele Konvertiten gewinnen wird. Die Software-Unterstützung nimmt
schnell zu. Ich denke, dass es ganz annehmbar ist, aber es hat eine Klobigkeit, die den
Entwurf eines Schemas zu einer harten Aufgabe macht. Die Datentypen werden sicher zu
einem De-facto-Standard. Sie werden ja schon jetzt von RELAX NG und anderen übernommen. Im Großen und Ganzen ist das ein guter Schritt nach vorn. Seien Sie sich aber
dessen bewusst, dass da immer welche sein werden, die ihm die Krone streitig machen
wollen.
RELAX NG hat mit seiner Eleganz und Einfachheit meine Bewunderung gewonnen. Das
Schreiben von Schemas ist leicht und das Lesen noch viel leichter. Es kann (mit Trang)
einfach in andere Schemasprachen wie W3C Schema und DTDs umgewandelt werden
und wird dadurch ein idealer Ausgangspunkt für die Entwicklung von Schemas. Zuckerstückchen wie interleave und eingebettete Grammatiken sollte man nicht übersehen. Es
wäre schön, wenn in der Spezifikation mehr eingebaute Typen definiert würden. Aber es
ist wahrscheinlich, dass die meisten Implementierer einfach nur die Typen der W3C
Schema-Empfehlung importieren werden. Sie müssen sich darüber also keine Sorgen
machen.
Auf sich allein gestellt, ist Schematron nicht annähernd so hilfreich wie die anderen Validierungswerkzeuge, die wir in diesem Kapitel kennen gelernt haben. Man könnte mit
XPath einige einfache Tests durchführen. Aber es bietet keine Unterstützung für reguläre
Ausdrücke, um Elementnamen oder Zeichendaten zu prüfen. Außerdem führt seine
Abhängigkeit von einer flachen Regelmenge dazu, dass Schemas klobig und schwer zu
entwickeln sind. Aber wenn es gemeinsam mit anderen Werkzeugen eingesetzt wird,
beginnt Schematron richtig zu glänzen. »Der wirkliche Vorteil mit Schematron ist,«
schreibt XML-Expertin Jeni Tennisson, »wenn man es in einem Gespann mit einer anderen Schemasprache, insbesondere mit W3C XML Schema, einsetzt. Man kann es nicht
als eigenständiges Schema einsetzen, aber es ist ein Geschenk des Himmels, wenn man
174 |
Kapitel 4: Qualitätskontrolle mit Schemas
Co-Occurrance-Bedingungen testen will, nachdem die eigentliche Validierung mit W3C
XML Schema oder RELAX NG durchgeführt worden ist.«
Es gab eine Zeit, zu der wir alle mit DTDs arbeiten mussten. Über die Jahre wurden mehr
und mehr XML-Tools entwickelt, und wir alle sollten dankbar sein für die unermüdlichen Anstrengungen vieler Entwickler. Es lohnt sich, mit unterschiedlichen Schematypen
vertraut zu werden, damit man eins finden kann, das den eigenen Bedürfnissen am besten entspricht.
Schemas im Vergleich |
175
Was this manual useful for you? yes no
Thank you for your participation!

* Your assessment is very important for improving the work of artificial intelligence, which forms the content of this project

Download PDF

advertisement