Gültiges XML

Mit einer Document Type Definition (DTD) können formale Anforderungen an die Struktur eines wohlgeformten XML-Dokuments definiert werden. Eine DTD beschreibt eine Gammatik, die unter anderem fest legt, welchen Inhalt und welche Attribute ein Element haben darf. Die Syntax ähnelt dabei der von regulären Ausdrücken (oder, was das angeht, mehr noch der von SNOBOL4). Ein Dokument, dessen Struktur einer DTD entsprich, heißt ein gültiges XML-Dokument.

Dieses ursprüngliche Konzept der Gültigkeit als Übereinstimmung mit einer DTD hat inzwischen einiges an Bedeutung verloren. Mit [Ext. Link]XML Schema (2004) und [Ext. Link]Relax NG (2001) gibt es inwischen leistungsfähigere, wenn auch teilweise deutlich komplexere Ansätze der XML-Validierung.

Damit ein Dokument als gültig erkannt werden kann, muss es eine Deklaration des Dokumententyps enthalten, die zwischen der XML-Deklaration und dem öffnenden Tag des obersten Elements stehen muss. Bei dem Beispiel auf der letzten Seite muss dazu als zweite Zeile zum Beispiel

<!DOCTYPE sect SYSTEM "http://www.hars.de/2000/xml/sect.dtd">

eingefügt werden. Diese Deklaration sagt, dass das oberste Element des Dokuments vom Typ sect ist und die DTD den System Identifier http://www.hars.de/2000/xml/sect.dtd hat. Ein Programm, das das Dokument auf seine Gültigkeit überprüfen (im Jargon "validieren") will, muss die DTD von der als System Identifier angegebenen Stelle laden (es geht auch anders, siehe Public Identifier und Kataloge, aber das ist noch nicht richtig standardisiert).

Deklaration von Elementen

Eine DTD muss für alle in einem Dokument zulässigen Elemente eine <ELEMENT>-Deklaration enthalten. Diese gibt den Namen und das Inhaltsmodell für das Element an, für das Element sect zum Beispiel:

<!ELEMENT sect  (title?, para+)>

Das Inhaltsmodell besagt, dass in einem sect-Element zuerst null oder ein title Element und dann ein oder mehr para-Elemente vorhanden sein müssen. Um die Häufigkeit, mit der ein Element auftreten kann, festzulegen, gibt es drei Wiederholungsanzeiger: ? wenn ein Element null oder ein Mal auftreten kann, + wenn es wenigstens ein Mal auftreten muss und * wenn es gar nicht oder beliebig oft auftreten darf. Bei der Verbindung zwischen Elementen in einem Inhaltsmodell steht das Komma für ein Sequenz, ein vertikaler Strich | für eine Alternative. Mit Klammern können in einem Inhaltsmodell Gruppen gebildet werden. Ein kompexeres Inhaltsmodell für sect könnte so aussehen:

(title?, (intro | motto)?, para, (para | figure)*)

Diesen Modell erlaubt nach einem optionalen Titel entweder eine Einleitung oder ein Motto (möglicherweise keines von beiden, aber nie beide), dann einen Absatz und schließlich eine beliebige, möglicherweise leere Folge von weiteren Absätzen und Abbildungen. Das Beispieldokument würde auch diesem Modell entsprechen.

Wenn ein Element keine anderen Elemente, sondern nur Text enthalten soll, ist sein Inhaltsmodell "Parsed Character Data". Bei dem Beispiel trifft das etwa auf die Elemente term und em zu:

<!ELEMENT term  (#PCDATA)>
<!ELEMENT em    (#PCDATA)>

Interessanter sind Elemente wie para, die sowohl Text als auch andere Elemente enthalten können, so genannten gemischten Inhalt. Die Deklaration eines Elements mit gemischtem Inhalt (mixed content) muss die folgende Form haben:

<!ELEMENT para  (#PCDATA | term | em)*>

Das heißt, #PCDATA muss als erstes kommen, als Verbindung zwischen den zulässigen Elementen ist nur die Alternative | zulässig, es darf in dem Inhaltsmodell keine durch Klammern gebildete Gruppen geben, und der Wiederholungsanzeiger muss der Stern sein. Man kann also die Art der zulässigen Elemente einschränken, aber weder ihre Reihenfolge noch ihre Häufigkeit.

Attribute

Eine DTD legt auch für alle Elemente die zulässigen Attribute mit deren Typen und Default-Werten fest. In dem Beispiel kommen nur zwei Attribute vor, und zumindest diese müssen in der DTD deklariert werden:

<!ATTLIST title id    ID      #IMPLIED>
<!ATTLIST term  class NMTOKEN #IMPLIED>

title hat ein Attribut id, dessen Typ als ID angegeben ist. Das heißt, der Wert muss ein gülitger Name sein und darf im ganzen Dokument nur ein einziges mal als Wert eines Attributs vom Typ ID vorkommen. Das Attribut class soll ein NMTOKEN sein, muss also aus den gleichen Zeichen wie ein Name bestehen, ist aber sonst nicht eingeschränkt. ID und NMTOKEN gehören zu den tokenarigen Werttypen, wozu auch IDREF als Referenz auf Elemente mit einen Attribut von Typ ID gehört, oder IDREFS und NMTOKENS für Listen von IDREF oder NMTOKEN Token. Beliebige Zeichenketten (mit der Einschränkung, dass das Zeichen < in Attributen immer als &lt; eingegeben werden muss) kann ein Attribut mit dem Typ CDATA aufnehmen, und für Attribute, die nur wenige bestimmte Werte annehmen können, steht ein Aufzählungstyp zur Verfügung.

In den minimalen Beispiel haben beide Attribute den Default-Wert #IMPLIED, was so viel heißt dass sie keinen Default haben und auch nicht unbedingt einen Wert haben müssen. Der Default könnte auch #REQUIRED sein, dann muss das Attribut immer angegeben werden. Wenn ein anderer Wert angegenben wird, ist dies der Wert des Attributs, sofern er im Dokument nicht geändert wird. Schließlich kann ein solcher Default durch die Angabe #FIXED als unveränderlich gekennzeichnet werden. Eine Deklaration für die Attribute von term, die mehrere dieser Möglichkeiten nutzt, könnte so aussehen:

<!ATTLIST term
        id       ID      #IMPLIED
        class    NMTOKEN #IMPLIED
        descr    CDATA   #IMPLIED
        area     (sci | soc | tech) "tech"
        form     NMTOKEN #FIXED     "name"
>

Die Definition von Elementen und Attributen sind die beiden wichtigsten Konzepte, wer jetzt (oder nach einem der nächsten Abschnitte) hinreichend verwirrt ist, kann jederzeit zu den Ergänzungen und Erweiterungen von XML weitergehen, ohne zu viel zu verpassen.

Parameterentitäten

In realen DTDs würde man para vermutlich anders als oben definieren. Das Inhaltsmodell von para ist praktisch das gleiche wie das von Bildunterschriften, Tabellenzellen, Kapiteleinleitungen oder auch Überschriften: fortlaufender Text mit einigen Hervorhebungen. Um diese Gemeinsamkeiten auszudrücken, bietet sich das [Ext. Link]DTD-Entwurfsmuster "fortlaufender Text" an. Es bedient sich des Mittels einer Parameterentität, einer Art von Makros für DTDs:

<!ENTITY % running.text "#PCDATA | term | em">
<!ELEMENT title (%running.text;)*>
<!ELEMENT para  (%running.text; | fn)*>
<!ELEMENT fn    (%running.text;)*>

Durch diese Konstruktion wird sicher gestellt, dass Änderungen am Inhaltsmodell für den eigentlichen Text eines Dokuments (zum Beispiel die Addition eines Elements name, um automatisch Personenverzeichnisse erstellen zu können) an allen Stellen wirksam werden, an denen fortlaufender Text zulässig ist.

Ein ähnliches Verfahren wendet man auch an, wenn mehrere Elemente die gleichen Attribute haben können. Wenn zum Beispiel alle ein id- und ein class-Attribut haben können, würde man die Parameterentität

<!ENTITY % coreattrs "
        id       ID      #IMPLIED
        class    NMTOKEN #IMPLIED
">
definieren, und dann diese in den ATTLIST-Deklarationen verwenden.

Allgemeine Entitäten

Parameterentitäten sind Abkürzungen, die man in der DTD selbst verwenden kann. Man kann in einer DTD aber auch Entitäten definieren, die im XML-Dokument benutzt werden können, so genannte allgemeine Entitäten. Die einfachste Form einer allgemeinen Entität, eine interne Entität, wird ohne das für Parameterentitäten typische Prozentzeichen definiert:

<!ENTITY hhlug "Hamburger Linux User Gruppe">

Wenn jetzt in einem XML-Dokument die Entität als &hhlug; referenziert wird, erscheint in dem Ergebnis der Verarbeitung des Dokuments der Ersetzungstext "Hamburger Linux User Gruppe" (natürlich ohne die Anführungszeichen), sofern die Anwendung Entitäten auflöst, was sie nicht immer tun muss.

Eine andere Form von allgemeinen Entitäten sind die externen Entitäten:

<ENTITY hhlug-hp SYSTEM "http://www.hhlug.de/index.xhtml">

Wenn diese in einem Dokument mit &hhlug-hp; referenziert wird, wird der Inhalt der XHTML-Version der Hompage der &hhlug; an dieser Stelle eingefügt (wenn es sie denn gibt, sonst gibt es eine Fehlermeldung).

Die interne Teilmenge der DTD

Die in der DOCTYPE-Deklaration mit einem SYSTEM Identifier (oder auch einem PUBLIC Identifier referenzierte DTD, bildet die so genannte externe Teilmenge der DTD. Ihre Benutzung hat den Vorteil, dass man die gleiche DTD für viele verschiedene Dokumente benutzen kann. Es gibt aber auch die Möglichkeit, statt oder zusätzlich zu dieser externen Teilmenge auch eine interne Teilmenge direkt in der DOCTYPE-Deklaration zu definieren. Im Prinzip kann man (mit einigen Einschränkungen bei der Benutzung von Parameterentitäten) die ganze DTD in der internen Teilmenge deklarieren, typischerweise benutzt man diese Möglichkeit aber, um einzelne Entitäten zu definieren, die nur in einem Dokument benutzt werden:

<!DOCTYPE sect SYSTEM "http://www.hars.de/2000/xml/sect.dtd" [
  <ENTITY hhlug "Hamburger Linux User Gruppe">
]>

Public Identifier und Kataloge

Es gibt ein Problem bei der Benutzung weit verbreiteter und standardisierter DTDs, deren System Identifier eine http-URL ist: man muss prinzipiell in der Lage sein, auf die URL zuzugreifen, wenn man ein Dokument validieren will. Das bedeutet, dass man ohne eine stehende Verbindung zum Internet eigentliche kein gültiges XML verarbeiten kann!

Eine Lösung für dieses Problem, die auch schon die Definition von XML vorsieht, ist die Benutzung von Public Identifiers, die ein Programm auf eine lokal bekannte DTD abbilden kann. Dazu muss es auf einen lokal vorhandenen Katalog zurückgreifen, der Public Identifier lokalen System Identifiern zuordnet, so dass nur bei einen nicht bekannten Public Identifier tatsächlich über das Netz auf die DTD zugegriffen werden muss. Um einen solchen Mechanismus zu nutzen, müsste die DOCTYPE-Deklaration für das Beispieldokument zum Beispiel so aussehen (einmal davon abgesehen, dass das zuständige Normierungsgremium mir den benutzten Namensraum mit meinem Namen nicht offiziell zugeteilt hat):

<!DOCTYPE sect PUBLIC "-//Florian Hars//Beispiel DTD sect V 1.0//EN"
               "http://www.hars.de/2000/xml/sect.dtd">

Dies ist in der SGML-Welt eine fest etablierte und standardisierte Praxis. Die XML-Definition sieht zwar Public Identifier vor, sagt aber nicht, wie mit ihnen umzugehen ist. Falls man seinen XML-Dateien mit SGML-Werkzeugen bearbeitet, kann man die Katalog-Funktionen dieser Werkzeuge benutzen, ansonsten muss man auf die Ergebnisse der entsprechenden Diskussionen in den XML-Arbeitsgruppen des [Ext. Link]W3C warten.

Weiter im Text: Ergänzungen und Erweiterungen von XML.

Florian Hars <florian@hars.de>, 2007-10-15 (orig: 2000-06-14)