Ergänzungen und Erweiterungen von XML

Auf der Grundlage der XML-Spezifikation bauen verschiedene Empfehlungen auf, die XML um weitergehende Funktionalitäten erweitern sollen, wie die Adressierung beliebiger Teile eines Dokuments, die Kombination von Markup-Definitionen aus verschiedenen Gebieten und andere Konzeptionen von "Gültigkeit" eines Dokuments.

XPath

Das Datenmodell, dass jeden wohlgeformten XML-Dokument zu Grunde liegt, ist ein Baum, der manche Ähnlichkeiten mit einem Unix-Dateisystem hat. Daher lag es nahe, für die Adressierung eines Teils dieses Baums eine Syntax zu wählen, die der von Dateisytempfaden ähnelt, allerding um einiges leistungfähiger ist. Diese Syntax wird in der [Ext. Link]W3C -Empfehlung für die [Ext. Link]XML Path Language (XPath) beschreiben.

Wie in im Unix-Dateisystem wird die Wurzel des Baums mit einem Schrägstrich / bezeichnet, alle Elemente, Attribute und einige andere Teile eines XML-Dokuments bilden die Knoten dieses Baums. Elemente werden durch ihren Namen bezeichnet, in dem schon öfter benutzten Beispiel bezeichnet also /sect das sect-Element, dass sich direkt unter der Wurzel des Dokuments befindet. Zwei Schrägstriche bezeichnen eine beliebige Verschachtelungsfolge: //term bezeichnet alle term-Elemente, die sich irgendwo unterhalb der Wurzel des Dokuments befinden, //term[1] nur das erste (in der Reihenfolge, wie sie im Dokument stehen). Das // kann an beliebiger Stelle im Pfad stehen: //title//term bezeichnet alle term-Elemente, die in beliebiger Tiefe in irgendeinem title-Element stehen.

Im Allgemeinen wird ein XPath-Ausdruck, der ein Pfad oder auch ein boolescher oder arithmetischer Ausdruck sein kann, relativ zu einem Kontextknoten ausgewertet. Ein relativer Pfad beginnte nicht mit einem Schrägstrich. Auch bei relativen Pfaden ähnelt die Syntax der von Dateisystemen: . bezeichnet den Kontextknoten selbst, .. den Elternknoten, * bezeichnet alle Elemente, die Kinder des Kontextknoten sind, para bezeichnet alle para-Elemente, die Kinder des Kontextknotens sind.

Auf Attribute wird mit einem dem Namen vorangestellten @ Bezug genommen. @class ist das class-Arrtibut des Kontextknotens (der ein Element sein muss), ../@class ist das class-Attribut des Elternkontens des Kontextknotens.

Zusätzlich kann man in einem XPath-Pfad Prädikate benutzen. Ein Prädikat ist selbst ein XPath-Ausdruck, der als Filter für den Pfad dient: */term bezeichnet alle term-Elemente, die Enkelkinder des Kontextknotens sind, unabhängig davon, in welchem Element sie direkt enthalten sind. */term[@class="def"] ist die Teilmenge davon,, die ein class-Attribut mit dem Wert def haben, und */term[count(../term)> 2] sind die term-Elemente, die Enkel des Kontextknotens sind und deren direkte Elternknoten wenigstens drei Kinder haben, die term-Elemente sind. Das letzte Beispiel benutzte eine der in der XPath-Spezifikation definierten Funktionen, nämlich count(), und einen arithmetischen Vergleich.

XPointer

Eine Anwendung von XPath ist es, im href-Attribut eines Links auf einen genauen Punkt in einem Dokuments zu verweisen. Von HTML kennt man den Fragment-Identizizierer, der mit einem Doppelkreuz an eine URL angehängt wird und im Prinzip auf die id-Attribute von allen Elementen des Dokuments und die name-Attribute der <A>-Elemente verweist (auch wenn tatsächlich nur das letztere von hinreichend vielen Browsern unterstützt wird). Die [Ext. Link]XML Pointer Language (XPointer) verallgemeinert dieses Prinzip. Auf id-Attribute kann weiter in der bekannten Form verwiesen werden: sect1.xml#start verweist (bei einem Browser, der das kann) auf das title-Element des Beispieldokuments. Man kann mit XPointer aber auch auf beliebige XPath-Pfade verweisen: sect1.xml#xpointer(//term) verweist als Fragment-Identifizierer auf alle term-Elemente in der Beispieldatei. Das ist in diesem Fall ein einzelnes Element, das muss aber nicht so sein, und es ist dann die Sache des Browsers, ob er es für einen Fehler hält, wenn ein Link auf mehrere gleichmäßig über ein Dokument verteilte Stellen verweist.

Eine andere Möglichkeit von XPointer, die über XPath hinausgeht, ist die, auf einen Bereich zwischen zwei Punkten in einem Dokument zu verweisen, die zu völlig verschiedenen Teilen des Baums gehören können: sect1.xml#xpointer(string-range(//title,"Markup")[1]/range-to(string-range(//para,",")[1])) bezeichnet den ganzen Bereich vom ersten Auftreten des Strings "Markup" in einem title-Element bis zum ersten Komma in einem para-Element, in den Beispiel also gerade:

Markup</title>
  <para><term class="def">Generisches Markup</term> ist wichtig,

Die Idee dahinter ist es, etwa vom Benutzer eines XML-Editors mit der Maus ausgewählte Textbereiche standardisiert durch XPointer beschrieben zu können. Es erlaubt auch, das Problem überlappender Strukturen anzugehen.

XLink

Eine wichtige Anwendung von Xpointer ist [Ext. Link]XLink, eine Spezifikation, die Hyperlinks zwischen XML-Dokumenten regelt. In HTML gibt es nur eine sehr geringe Auswahl an Links: Man kann Bilder und einige Arten von Objekten direkt in ein Dokument einbinden, und mit dem Anker-Element <a> kann man Hyperlinks setzen, bei deren Aktivierung das aktuell Dokument ersetzt oder das Zieldokument in einem anderen Fenster oder Frame geöffnet wird.

Alle diese Links sind Beispiele für das, was bei XLink als einfacher Link bezeichnet wird, Links mit genau zwei Endpunkten, deren Start die Stelle des Dokuments ist, an der sie definiert werden, und deren Ziel eine durch einen URI spezifizierte Ressource ist.

Ein Bild mit einer Bildunterschrift könnte mit XLink so realisiert werden:

<bild xlink:type="simple"
      xlink:show="embed"
      xlink:actuate="onLoad"
      xlink:href="meinbild.jpg"
>Dies ist mein Bild</bild>

Dies beschreibt einen einfachen Link, dessen Ziel in das aktuelle Dokument eingebettet werden soll und der automatisch beim Laden des aktuellen Dokuments geladen wird: gerade das normale Verhalten des img-Tags in HTML in der Standardeinstellung vieler Browser. Das Verhalten des <a>-Tags von HTML kann man dagegen so reproduzieren:

<a xlink:type="simple"
   xlink:show="replace"
   xlink:actuate="onRequest"
   xlink:href="andereseite.xml"
>Zur anderen Seite</a>

Erweiterte Links können mehr als zwei Endpunkte haben und komplizierte Verbindungen zwischen beliebig vielen Ressourcen herstellen. Keiner der Endpunkte eines erweiterten Links muss Teil des Dokuments sein, in dem der Link definiert wird. Zum Beispiel definiert der folgende erweiterte Link (unter der Annahme, dass die Hompage der HHLUG eine XML-Datei ist):

<xlink:extended>
  <xlink:locator
    xlink:href='http://www.hhlug.de/#xpointer(string-range(//body,"XML")[1])'
    xlink:role="start" />
  <xlink:locator
    xlink:href="http://www.hars.de/2000/xml/"
    xlink:role="ziel" />
  <xlink:arc
    xlink:show="replace"
    xlink:actuate="onRequest"
    xlink:from="start"
    xlink:to="ziel" />
</xlink:extended>

einen Link vom ersten Auftreten des Strings "XML" im sichtbaren Text der Homepage der HHLUG zu dieser Website, die aus Anlass eines Vortrags auf einem Treffen der [Ext. Link]HHLUG entstanden ist. Es ist allerdings noch eine ungeklärte Frage, wie irgendein Browser von der Existenz dieses Links erfahren kann.

In den Beispielen dieses Abschnitts wurden die zu XLink gehörenden Tags und Attribute alle mit dem Präfix xlink: geschrieben. Dies setzt voraus, dass bei den Elementen (oder bei weiter oben im Baum stehenden) der Namensraum von XLink entsprechend deklariert worden ist.

Namensräume

Eine der Hoffnungen hinter der Definition von XML war, dass mit der Zeit standardisierte, allgemein verbreitete in XML definierte Auszeichnungssprachen für verschiedene Anwendungsbereiche entstehen würden. Um den größten möglichen Nutzen aus dieser Vielfalt von Sprachen zu ziehen, muss es aber möglich sein, sie in einem Dokument zu kombinieren. Dazu muss man aber Tags und Attributen aus den verschiedenen Sprachen, die zufälliger Weise den gleichen Namen tragen -- etwa div als Gliederungseinheit eines Artikels und div als mathematische Division, die Teil einer in dem Artikel vorkommenden Gleichung ist. Die Lösung dafür ist, dass man jedem Element in einem Dokument einen [Ext. Link]Namensraum zuordnet, ähnlich, wie es auch mit Variablennamen in Perl oder C++ gemacht wird. Ein Beispiel ist der XLink-Namensraum, der im vorigen Abschnitt schon benutzt worden ist. Indem ein Attribut wie href oder type explizit dem Namensraum von Xlink zugeordnet wird, kann man jedem Element in einem XML-Dokument die Funktion eines Hyperlinks verleihen, und es besteht keine Gefahr, das xlink:type-Attribut mit einem type-Attribut zu verwechseln, das ein Element unabhängig von XLink haben kann.

In diesem Beispiel wurde als Präfix für den XLink-Namensraum die Zeichenfolge xlink gewählt, was natürlich nahe liegt, aber nicht zwingend notwendig ist. Der eigentliche Namensraum ist nicht dieses Präfix, sondern ein eindeutiger String, der im Fall von XLink http://www.w3.org/1999/xlink/namespace/ lautet. Das einem Namensraum entsprechende Präfix kann beliebig deklariert werden, und in einem Dokument kann der gleiche Namensraum auch mit verschiedenen Präfixen bezeichnet werden. Die Deklaration eines Namensraums geschieht über ein xmlns-Attribut, und der Namensraum ist dann für das Element, das dieses Attribut trägt, und alle darin enthaltenen Elemente definiert. Wenn das Dokument nur einen einzigen Link enthält, kann die Deklaration also in dem Element stehen, das als Link funktionieren soll:

<a xmlns:xlink="http://www.w3.org/1999/xlink/namespace/"
   xlink:type="simple"
   xlink:show="replace"
   xlink:actuate="onRequest"
   xlink:href="andereseite.xml"
>Zur anderen Seite</a>

Wenn aber Links über das ganze Dokument verteilt vorkommen, kann es sinnvoll sein, den Namensraum nur ein einziges Mal beim obersten Element zu deklarieren.

Neben der Möglichkeit, ein Präfix für einen Namensraum zu deklarieren, kann man auch einen Default-Namensraum deklarieren, der ohne Präfix benutzt wird. Dazu lässt man einfach nach dem xmlns den Doppelpunkt und die Angabe des Präfix weg:

<extended xmlns="http://www.w3.org/1999/xlink/namespace/">
  <locator
    href='http://www.hhlug.de/#xpointer(string-range(//body,"XML")[1])'
    role="start" />
  <locator
    href="http://www.hars.de/2000/xml/"
    role="ziel" />
  <arc
    show="replace"
    actuate="onRequest"
    from="start"
    to="ziel" />
</extended>

Von einem Tag ohne explizites Präfix wird dann angenommen, dass es in den Default-Namensraum gehört.

Zwei Dinge sind bei Namensräumen zu beachten:

  • Während von einem Tag ohne Präfix angenommen wird, dass es zum Default-Namensraum gehört, ist ein Attribut ohne Präfix ein Attribut ohne Präfix, bei dem keinerlei Annahmen über Namensräume gemacht werden. Wenn Attribute aus dem Default-Namensraum für Elemente aus einem anderen Namensraum benutzt werden müssen, muss der Namensraum explizit angegeben werden. Hierfür ist es dann nötig, den Default-Namensraum noch ein zweites mal mit einem Präfix zu deklarieren.
  • Namensräume werden durch URIs identifiziert. Es wird aber nicht gefordert, dass diese URI auf irgendein tatsächlich existierendes Dokument verweist, das entscheidende ist, dass diese URI ein eindeutiger String ist. Die Wahl für die Benennung von Namensräumen ist deshalb auf URIs gefallen, weil jeder Mensch mit Internetzugang sich billig oder umsonst über freie Webspace-Provider einen URI-Raum zur eigenen Verfügung besorgen kann, und diesen nicht wie bei den Formal Public Identifiers von SGML bei einer internationalen Organisation beantragen muss. Ob diesen URIs über die Funktion als weltweit eindeutiger String hinaus noch irgendeine Bedeutung zukommen soll (und ob es zum beispiel sinnvoll ist, relative URIs für Namensräume zu benutzen) ist zu dem Zeitpunkt, wo diese Seiten entstehen, gegenstand heftigster Diskussionen im [Ext. Link]W3C Am besten man benutzt immer nur absolute URIs und macht keine Annahmen darüber, dass sie irgendetwas bedeuten, dann ist man auf der sicheren Seite.

Eine letzte Bemerkung zu Namensräumen: Die Möglichkeit, für ein Element einen Namensraum zu deklarieren und dann in dem Element beliebig Elemente aus diesem Namensraum zu benutzen, verträgt sich nicht wirklich gut mit dem Konzept einer DTD. Aus einem Dokument, das in größerem Umfang Namensräume benutzt, ein gültiges XML-Dokument im Sinn der XML-Spezifikation zu machen, erfordert zumindest erhebliche Gymnastik bei der Definition der DTD. Diese Schwierigkeit ist eine der Motivationen, andere Konzeptionen von formaler Gültigkeit eines XML-Dokuments zu entwickeln.

XML Schema und Schematron

Das in der XML-Spezifikation eingeführte, auf DTDs aufbauende Konzept von gültigem XML hat einige Schwächen. Abgesehen davon, dass es sich nur schlecht mit den später eingeführten Namensräumen verträgt, kann man viele an sich sinnvolle Bedingungen an ein Dokument mit einer DTD nicht ausdrücken. Um hier Abhilfe zu schaffen, wird vom [Ext. Link]W3C an der Definition von so genennaten XML Schemas gearbeitet, die aus drei Teilen besteht: einem [Ext. Link]Primer und der Definition von [Ext. Link]Strukturen und [Ext. Link]Datentypen.

Es gibt zum Beispiel keine elegente Möglichkeit mit einer DTD festzulegen, dass ein Element b drei bis fünf Mal im Element a enthalten sein kann. Das könnte man naiv so formulieren:

<!ELEMENT a ((b, b, b) | (b, b, b, b) | (b, b, b, b, b))>

und hätte damit ein nichtdeterministisches Inhaltsmodell (wenn ein Parser in einem a-Element das erste b-Element sieht, kann er nicht feststellen, zu welchem der drei möglichen Inhaltsmodelle es gehört), was aus Gründen der Kompatibilität mit SGML nicht zulässig ist. Die Alternative

<!ELEMENT a (b, b, b, b?, b?))>

ist zwar kürzer, aber auch nicht besser verständlich und genauso nichtdeterministisch. In einem Schema (mit dem an das Präfix xsd gebundenen Namensraum http://www.w3.org/1999/XMLSchema) kann man einen Datentyp mit dem entsprechenden Modell definieren und diesen dann Elementen als Typ zuweisen:

<xsd:complexType name="a-typ">
  <xsd:element name="b" minOccurs="3" maxOccurs="5" />
</xsd:complexType>
<xsd:element name="a" type="a-typ" />

Schemas erlauben es auch, Datentypen für den Wert von Attributen und den Inhalt von Elementen zu definieren, die spezifischer sind als CDATA bzw. #PCDATA. Letztere sind wenig hilfreich, wenn man ausdrücken will, dass ein Attribut nur Werte annehmen kann, die von einem einzelnen Verlag vergebene ISB-Nummern sind. In einem Schema kann man für solche Zwecke einen ISBN-Typ nur für diesen Verlag definieren, der von dem allgemeinen Typ xsd:string abgeleitet ist und die zusätzliche Bedingung durch einen regulären Ausdruck beschreibt:

<xsd:simpleType name="myisbn" base="xsd:string">
  <xsd:pattern value="3-928186-\d{2}-[0-9X]" />
</xsd:simpleType>

Das letzte Beispiel zeigt auch, wie ein Typ von einem anderen Abgeleitet wird, in diesem Fall von dem primitiven Typ xsd:string. Dies ist für alle Typen möglich, auch für komplexe Typen wie den a-typ aus den Beispiel davor, so dass ein Schema eine ganze Hierarchie von Datentypen definieren kann. Durch die Definition und Ableitung von Typen kann man ähnliche Effekte erzielen wie mit Parameterentitäten in einer DTD, hat allerdings eine sehr viel ausdrucksstärkere Sprache zur Verfügung.

Wie eine DTD beschreibt auch ein XML Schema eine abstrakte Grammatik, gegen die ein Dokument validiert werden kann. Die genauen Details sind aber zur Zeit noch im Fluss, es ist bisher nicht einmal klar, ob diese Grammatik kontextfrei sein soll oder nicht.

Einen ganz anderen Ansatz bei der Validierung verfolgt das [Ext. Link]Schematron von Rick Jelliffe. Es beruht nicht auf einer formalen Grammatik, sondern erlaubt Test auf bestimmte Muster in der durch XPath-Ausdrücke beschriebenen Baumstruktur des Dokuments. Das Beispiel des a-Elements, das drei bis fünf b-Elemente enthalten soll, sieht im Schematron dann so aus:

<rule context="a">
  <assert test="count(b)>= 3 and count(b) &lt;= 5">Ein a muss
  drei bis fünf b enthalten</assert>
</rule>

Einen davon inspirierten Ansatz erläutere ich detaillierter in einem kleine Tutorium über das Validieren mit Stylesheets.


Mit diesen Ausführungen ist der Überblick über die technischen Grundlagen von XML abgeschlossen. Weiter geht es mit einigen Reflexionen über rechten Gebrauch von XML.

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