Websiteentwicklung: XSLT: Auswahl

XPath Bearbeiten

In den Vorlagen ist jeweils anzugeben, auf was im Quelldokument sich die jeweilige Vorlage bezieht, ferner selektieren auch einige andere Elemente Bestandteile aus dem Quelldokument. Dafür wird die Sprache XPath verwendet.

XML-Dokumente lassen sich in einer Baumstruktur darstellen. Einzelne Strukturen des Dokumentes werden Knoten genannt. Dies sind Elemente, Attribute, Textknoten, Kommentare, Verarbeitungsanweisungen. Ausgehend vom Dokumentknoten werden dann die Verzweigungen der Baumstruktur referenziert, wobei zur Bezeichnung Verwandtschaftsgrade verwendet werden. Etwa sind die XML-Deklaration, darauf folgende Kommentare und XML-Verarbeitungsanweisungen und das Wurzelelement Kinder des Dokumentknotens. Text und alle weiteren Elemente sind Nachfahren des Wurzelelementes. Ein Kind ist ein direkter Nachfahre, Enkel sind entsprechend die direkten Nachfahren eines Kindes von dessen Elternknoten aus gesehen. Hat ein Knoten mehrere Kinder, so sind dies relativ zueinander Geschwister. Entsprechend gibt es als Vorfahren dann gegebenenfalls Eltern, Großeltern, allgemein Vorfahren. Einmal abgesehen vom Dokumentknoten selbst hat jeder Knoten exakt einen Elternknoten.

Mit XPath kann man nun alle Knoten eines Dokumentes ansprechen, also für eine gewünschte Aktion auswählen. Neben einer vereinfachten Notation verwendet XPath dazu sogenannte Achsen. Für die Achsen gibt es bestimmte Schüsselwörter, dies sind gemäß XPath 1.0:

self
Der aktuelle Knoten wird selektiert; Kontextknoten
anchestor
Vorfahren bis hin zum Wurzelknoten
anchestor-or-self
Aktueller Knoten und Vorfahren
attribute
Attribute eines (Element-)Knotens
child
Kindelemente des Kontextknotens
descendant
Nachfahren
descendant-or-self
Aktueller Knoten und Nachfahren
following
Im Quelltext direkt nachfolgender Geschwisterknoten samt dessen Kindern
following-sibling
Im Quelltext direkt nachfolgender Geschwisterknoten
namespace
Namensraum des Kontextknotens
parent
Das Elternelement des Kontextknotens
preceding
Im Quelltext direkt vorhergehender Geschwisterknoten samt dessen Kindern
preceding-sibling
Im Quelltext direkt vorhergehender Geschwisterknoten

Achsen werden notiert, indem das Schlüsselwort notiert wird, gefolgt von zwei Doppelpunkten, worauf eine Knotenprüfung folgt, siehe unten.

Vereinfachte Notation, hinzu kommen die Namen selektierter Elemente oder Attribute, gegebenenfalls mit Präfix für die korrekte Namensraumzuordnung:

(kein Zeichen)
Nachkommen eines Knotens
@
Attribut des selektierten Elementes, hinter dem @ folgt dann der Name des Attributes
/
Wurzelknoten; folgt dahinter ein Name, so ist dies der Name des Wurzelelementes
//
Selektierter Knoten und dessen Nachfahren
.
Selektierter, aktueller Knoten
..
Elternknoten des selektierten, aktuellen Knotens
*
Beliebiger Knoten

Nach einer Achsenangabe folgt nach zwei Doppelpunkten eine Knotenprüfung, dies kann der Name eines Knotens (Element, Attribut) sein oder ein Schlüsselwort für einen bestimmten Knotentyp:

node()
Auswahl aller Knoten, welche über die Achse erreichbar sind
comment()
Selektion von Kommentarknoten
text()
Selektion von (nicht leeren) Textknoten
processing-instruction()
Selektion von Verarbeitungsanweisungen
processing-instruction("Zeichenfolge")
Selektion nur von Verarbeitungsanweisungen vom Typ 'Zeichenfolge', diese beginnen also mit '<?Zeichenfolge '

Die Knotenprüfung ist wahr, wenn es die selektierten Knoten gibt, sonst falsch.

Beispiele:

self::node()
Dies ist identisch mit der Kurznotation '.'
parent::node()
Dies ist identisch mit der Kurznotation '..'
descendant-or-self::node()
Dies ist identisch mit der Kurznotation '//'
/descendant::beispiel
Dies ist identisch mit der Kurznotation 'beispiel' und wählt alle Elemente beispiel aus
//beispiel/child::*
Alle Kinder von beispiel, auch in Kurznotation 'beispiel/*'
//beispiel/attribute::exempel
Attribut exempel vom Element beispiel, auch in Kurznotation 'beispiel/*'

Auf die Knotenprüfung können optional Prädikate folgen, diese stehen in eckigen Klammern '[]'. Dies sind Ausdrücke oder Funktionen, welche die Auswahl weiter filtern. Das Ergebnis der Auswertung eines solchen Prädikats ist entweder wahr oder falsch, bei wahr trifft die Auswahl zu. Man kann allerdings als Auswahl eines von mehreren Geschwisterknoten auch einfach eine Zahl notieren, welche der Position des Knotens entspricht, die Numerierung beginnt mit 1. Gibt man also eine Zahl z an, so ist die Auswahlwahl nur wahr für den Knoten mit der Nummer z.

Beispiel: 'beispiel/exempel[3]' selektiert jeweils das dritte Element exempel, welches beispiel als Elternelement hat.
Oder '/buch/kapitel[3]/abschnitt[2]' selektiert den zweiten abschnitt im dritten kapitel von buch.

Es stehen folgende logische Operatoren zur Verfügung:

or
entweder oder
and
und
!
nicht, Negation
<
kleiner
>
größer
<=
kleiner oder gleich
>=
größer oder gleich
=
gleich
!=
ungleich

Man beachte dabei, dass die Zeichen < und > innerhalb eines XML-Dokumentes eine besondere Bedeutung haben, also maskiert werden müssen, man schreibt also im XSLT-Dokument stattdessen &lt; und &gt;

Beispiel 'kapitel[titel="Szene Hannover bei Nacht"]' Damit wird ein kapitel selektiert, welches als Kindelement ein Element titel hat, welches als Textinhalt 'Szene Hannover bei Nacht' hat.
Oder 'svg:*[svg:title and svg:desc]' trifft auf alle Elemente zu, die als Kindelemente sowohl svg:title als auch svg:desc' haben. Das Präfix 'svg' muß dabei einem Namensraum zugeordnet sein, also sowohl im Quelldokument als auch in der XSLT-Datei, der Präfix in der XSLT-Datei ist dann folglich 'svg' und die zutreffenden Elemente im Quelldokument gehören zum selben Namenraum, je nachdem wie der festgelegt ist, können die Elemente dort ohne Präfix notiert sein oder auch mit einem anderen.

Ferner gibt es folgende arithmetische Operatoren:

+
Addition
-
Subtraktion
*
Multiplikation
div
Division
mod
Modulo, Rest einer Ganzzahldivision

Beispiel: möchte man selektieren, dass der Textknoten eines Elementes beispiel gleich 'XSLT' oder 'CSS' ist, so notiert man:
'beispiel[text()="XSLT" or text()="CSS"]'.

Weitere Beispiele:
'beispiel[@typ="XSLT"]' selektiert Elemente beispiel, die ein Attribut typ notiert haben, dessen Wert 'XSLT' ist.
'beispiel[@typ="XSLT"][3]' selektiert zu jedem geeigneten Elternelement nur das dritte Kindelement beispiel, welches ein Attribut typ notiert haben, dessen Wert 'XSLT' ist, bei der Zählung sind also nur die Kindelemente relevant, die sowohl das Attribut als auch den angegebenen Wert haben, nicht etwa alle Kindelemente beispiel.
'beispiel[3][@typ="XSLT"]' - hier hingegen trifft die Zählung auf alle Kindelemente beispiel zu, nur falls das dritte Kindelement auch zusätzlich noch das Attribut typ mit dem Wert 'XSLT' notiert hat, trifft die Auswahl zu.

Als Funktionen gibt es in XSLT 1.0 folgende:

Zur Knotenverarbeitung:

last()
Position des letzten Knotens und Anzahl der Kindknoten
position()
Position des aktuellen Knotens in der selektierten Ebene
count(Knotensatz)
Anzahl der Knoten von 'Knotensatz'
id('FID')
Seklektiert Elemente mit dem Fragmentidentifizierer entsprechend der Zeichenkette 'FID', dazu muß per DTD kenntlich gemacht werden, welches Attribut Fragmentidentifizierer beeinhaltet und die DTD muß vom XSLT-Prozessor auch gelesen werden oder diesem jedenfalls bekannt sein.
local-name(Knoten?)
Lokaler Name eines Knotens, wenn nicht angegeben der aktuelle Kontextknoten, also der Teil des Namens ohne Namensraumzuordnung
name(Knoten?)
Name eines Knotens, wenn nicht angegeben der aktuelle Kontextknoten, samt Namensraumzuordnung per Präfix
namespace-uri(Knoten?)
Namensraumadresse eines Knotens, wenn nicht angegeben der aktuelle Kontextknoten

Verarbeitung von Zeichenketten:

string(Objekt?)
Wandelt ein Objekt in eine Zeichenkette um, wenn nicht angegeben der aktuelle Kontextknoten
concat(Liste)
Vereint die Listenpunkte zu einer Zeichenkette. Die Listenpunkte sind dabei Zeichenketten, die je mit einem Komma und optionalen Leerzeichen voneinander separiert sind. Es gibt in der Liste mindestens zwei Einträge.
starts-width(Test, Muster)
Prüft, ob die Zeichenkette Test mit der Zeichenkette beginnt, die in Muster abgelegt ist. Rückgabe ist direkt ein Prüfergebnis wahr oder falsch
contains(Test, Muster)
Prüft, ob die Zeichenkette Test die Zeichenkette enthält, die in Muster abgelegt ist. Rückgabe ist direkt ein Prüfergebnis wahr oder falsch
substring-before(Test, Muster)
Gibt als Zeichenkette den Teil von Test zurück, welcher vor Muster liegt, sofern Muster nicht vorhanden ist, ist die zurückgegebene Zeichenkette leer
substring-after(Test, Muster)
Gibt als Zeichenkette den Teil von Test zurück, welcher hinter Muster liegt, sofern Muster nicht vorhanden ist, ist die zurückgegebene Zeichenkette leer
substring(Test, Anfang, Lang?)
Gibt als Zeichenkette den Teil von Test zurück, der mit dem Zeichen an Position Anfang beginnt und die Länge Lang hat, ist Länge nicht angegeben, den Rest von Test, die Zählung beginnt jeweils mit 1, Anfang und Lang sind also Zahlen
string-length(Test?)
Gibt als Zahl die Länge der Zeichenkette Test zurück, ist Test nicht angegeben, so wird der Textinhalt des Kontextknotens verwendet
normalize-space(Test?)
Rückgabe ist eine Zeichenkette, bei welcher von der Zeichenkette Test überschüssige Leerzeichen entfernt wurden.
translate(Test,Original,Ersatz)
Rückgabe ist eine Zeichenkette, bei der in Test die in der Zeichenkette Original notierten Zeichen durch jene ersetzt werden, welche in Ersatz stehen, jeweils in der Reihenfolge der Notation. Hat Original mehr Zeichen als Ersatz, so werden die letzten Zeichen ohne passenden Ersatz aus Test entfernt

Boolsche Funktionen:

boolean(Objekt)
konvertiert ein Objekt in einen Wahrheitswert, wahr sind dabei Zahlen als Objekt, die nicht 0 oder undefiniert (NaN) sind, ein Knoten ist wahr, wenn er nicht leer ist, ebenso ist eine Zeichenkette wahr, wenn sie nicht leer ist, bei anderen Objekten hängt der Wahrheitswert vom Typ ab.
not(Test)
Negation des Wahrheitswertes Test
true()
Liefert den Wahrheitswert wahr
false()
Liefert den Wahrheitswert falsch
lang(Test)
Testet die Sprache des Kontextknotens, Test ist dabei eine Zeichenkette gemäß dem Attribut xml:lang, gibt wahr zurück, wenn die Sprache zutrifft, dabei werden mit xml:lang notierte Unterdialekte nicht berücksichtigt; ist für Test also etwa nur 'en' notiert, liefern zum Beispiel folgende Sprachkennungen wahr zurück: 'en', 'EN', 'en-us'; ist hingegen für Test 'en-us' notiert, trifft nur 'en' für xml:lang nicht mehr zu, aber 'en-us' oder 'en-US-x-Hixie' tun es.

Zahlenfunktionen:

number(Objekt?)
Konvertiert Objekt in eine Zahl, sofern Objekt nicht angegeben ist, den aktuellen Kontextknoten. Bei Zeichenketten werden führende und angehängte Leerzeichen ignoriert und der Rest in eine Zahl konvertiert, sofern das nicht möglich ist, also das Objekt nicht nur eine Zahl repräsentieren, wird 'NaN' (keine Zahl) zurückgeliefert. Bei einem Wahrheitswert wird wahr als 1 zurückgeliefert, falsch als 0. Ein Knoten wird erst mit der Funktion string() in eine Zeichenkette verwandelt und dann zu einer Zahl konvertiert. Bei anderen Objekten ist das Ergebnis von der Art des Objektes abhängig.
sum(Knotensatz)
Aus den Objekten eines Knotensatzes werden per Funktion number() Zahlen gebildet, über welche summiert wird
floor(Zahl)
Rundet die Zahl auf die nächst kleinere oder gleichgroße ganze Zahl ab.
ceiling(Zahl)
Rundet die Zahl auf die nächst größere oder gleichgroße ganze Zahl auf.
round(Zahl)
Rundet die Zahl auf die nächstgelegene ganze Zahl auf. Ist der Nachkommateil 0.5, wird zur größeren Zahl gerundet, das kann also statistische Probleme mit sich bringen.

Ausführlichere Beispiele Bearbeiten

Gegeben sei eine Adressenliste wie die, die in einem der vorherigen Kapitel als Beispiel verwendet wurde. Es stellt sich die Frage, woher der XSLT-Prozessor weiß, welchen Wert er in die Tabellenzelle schreiben soll. Nehmen wir also den Quelltext etwas genauer unter die Lupe:

In der Ausgangsdatei:

 <person vorname="Anna" 
         nachname="Mueller" 
         strasse="Blumenweg 42" 
         plz="12345" 
         ort="Musterhausen" />

In der XSLT-Datei:

 <xsl:value-of select="@vorname" />

In der Ausgabe ist festzustellen, dass der Attributwert '@vorname' wohl für ein Attribut mit dem Namen vorname stehen muss. Woher weiß der Prozessor aber, wo dieses Attribut zu finden ist?

Dies ist ganz einfach: Wird die Vorlage auf die wesentlichsten Dinge reduziert, ergibt sich folgende Struktur:

<xsl:template match="/adressen">
   <xsl:for-each select="person">
    <xsl:sort select="@nachname" order="ascending" data-type="text" />
     <tr>
      <td><xsl:value-of select="@vorname" /></td>
     </tr>
    </xsl:for-each>
  </xsl:template>
 </xsl:stylesheet></nowiki>

Die Vorlage trifft auf das Wurzelelement adressen zu. Nacheinander werden darin die Nachfahren person selektiert, das ist dann an der Stelle jeweils der aktuelle Kontextknoten. Selektiert wird für diesen aktuellen Kontextknoten dann für eine Ausgabe das Attribut vorname.

Das bedeutet also, dass auf die Werte, die das select-Attribut annehmen kann, ähnliches zutrifft wie auf relative Pfadangaben. Jedenfalls bedeutet dies, dass wenn keine absolute Adresse angegeben wird, wie zum Beispiel '/adressen/person/@vorname', sondern man kann einfach vom aktuellen Kontextknoten ausgehen, wenn mit select etwas ausgewählt wird.

Nachfolgend ein Ausschnitt einer beispielhaften XML-Datei, sowie ein beispielhafter Ausschnitt aus einem Stilvorlage.

<root>
  <a blubb="abcdef" blob="ghijkl">Test</a>
  <a>
   <b asdf="qwertz">
    <c>Wikibooks ist gut!</c>
   </b>
  </a>
  <c>Dieser Text wird nicht aufgelistet, 
     da das Template für "c"-Elemente nur bei einem
     "b"-Element als Elternelement gilt.</c>
 </root>

Eine mögliche Stilvorlage zur Verarbeitung:

<!-- Vorlage für das Wurzelelement -->
<xsl:template match="/root">
   <!-- Wende alle zur Verfügung stehenden Vorlagen an. -->
   <xsl:apply-templates />
</xsl:template>
<!-- Ende der Vorlage für das Wurzelelement -->
 
<!-- Vorlage für alle Elemente a; 
  man könnte bei dem Beisspiel auch /root/a schreiben. -->
 <xsl:template match="a">
   Dies ist ein "a"-Element!
   Dies ist der Wert von blubb: <xsl:value-of select="@blubb" />
   <!-- Wert des Attributes "blubb" des aktuell verarbeiteten Elements.
        Auch hier wäre wieder /root/a/@blubb möglich. -->
   <xsl:apply-templates />
 </xsl:template>

<!-- Trifft auf alle Elemente b zu: -->
 <xsl:template match="b">
   Dies ist ein "b"-Element.
   asdf hat den Wert: <xsl:value-of select="@asdf" />
   <xsl:apply-templates />
 </xsl:template>

 <!-- Trifft auf alle Elemente c in einem Element b zu: -->
 <xsl:template match="b/c">
   Dies ist ein "c"-Element! Es enthält folgenden Text:
   <xsl:value-of select="." />
 </xsl:template>

 <!-- Trifft auf alle "c"-Elemente zu. 
      Keine Ausgabe, da leeres Element. -->
 <xsl:template match="c" />