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 W3C -Empfehlung für die 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 XML Pointer Language
(XPointer) verallgemeinert dieses Prinzip.
Auf id
-Attribute kann weiter in der bekannten Form
verwiesen werden: 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 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 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
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 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 W3C an der Definition von so genennaten XML Schemas gearbeitet, die aus drei Teilen besteht: einem Primer und der Definition von Strukturen und 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 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) <= 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.