
Transformationen
mit XSLT
Teil 4
Festlegung von Ausgabe-Eigenschaften
erfolgt über das folgende Top-Level-Element:
"Top-Level" bedeutet, dass dieses Element direktes Kind von
xsl:stylesheet sein muss (ähnlich wie
xsl:template). xsl:output ist ein
leeres Element.
<xsl:output method = "xml" | "html" | "text"
encoding = String
omit-xml-declaration = "yes" | "no"
indent = "yes" | "no"
doctype-public = String
doctype-system = String
cdata-section-elements = QNamen />
Diese Liste der Attribute ist nicht vollständig. Außerdem gibt
es noch version, standalone und
media-type.
Für method wird standardmäßig xml
angenommen, es sei denn, das erzeugte Wurzelelement heißt
html. Die html-Ausgabemethode sorgt dafür,
dass alle Elemente gemäß den HTML-Regeln ausgegeben werden. So
werden Elemente mit leerem Inhaltsmodell immer ohne End-Tag
ausgegeben (z.B. <br> oder
<hr>). Zeichen, für die in HTML Entities definiert
sind, können durch die entsprechenden Entityreferenzen ausgegeben
werden (z.B. ä, ).
In der Ausgabemethode text werden keine Tags ausgegeben,
sondern nur Textinhalt (d.h. für alle generierten Elemente wird nur
ihr Zeichenkettenwert ausgegeben).
encoding gibt die gewünschte Kodierung der erzeugten
Ausgabe an. Standardmäßig wird UTF-8 angenommen. Über
omit-xml-declaration kann gesteuert werden, ob eine
XML-Deklaration erzeugt werden soll. Bei der Ausgabemethode
xml passiert das standardmäßig, bei html
und text nicht. Verwendet man eine andere Kodierung als
UTF-8 oder UTF-16, darf die XML-Deklaration bei der
xml-Ausgabemethode nicht weggelassen werden, da
sonst die Ausgabe nicht mehr wohlgeformt wäre.
Bei Angabe von indent="yes" darf der XSLT-Prozessor
erzeugte Elemente nach eigenem Ermessen einrücken ("pretty print").
Da Textknoten im Stylesheet, die ausschließlich aus Leerraum bestehen,
ignoriert werden, stehen Tags, die durch das Stylesheet erzeugt
wurden, ohne automatische Einrückung in der Regel direkt
hintereinander in einer Zeile. Allerdings kann insbesondere bei
gemischtem Inhalt die automatische Einrückung zuweilen zu
ungewollten Ergebnissen führen.
Mittels doctype-public und doctype-system
kann eine Dokumenttyp-Deklaration erzeugt werden. Es ist nicht
möglich (bzw. nur mit "Hacks"), ein XML-Dokument mit interner
DTD-Teilmenge zu generieren. Ebensowenig kann die Dokumenttyp-Deklaration
der Eingabedatei kopiert werden, da es in der Baumrepräsentation
hierfür keinen Knoten gibt.
Über das Attribut cdata-sections kann schließlich
angegeben werden, welche Elemente ihren Textinhalt in der Ausgabe
in CDATA-Abschnitte einschließen sollen.
Beispiele:
<xsl:output method="html"
doctype-public="-//W3C//DTD HTML 4.01 Transitional//EN"
version="4.01" /> |
<xsl:output indent="yes" encoding="iso-8859-1"
doctype-system="kvv.dtd" /> |
XPath-Ausdrücke und -Datentypen
Lokalisierungspfade sind spezielle XPath-Ausdrücke, die
Knotenmengen auswählen.
Außerdem gibt es:
- boolesche Werte ("wahr" oder "falsch")
Es gibt keine Literale für diese beiden Werte.
Hilfsweise kann man die Funktionen true() und
false() benutzen.
- Zahlen
(Dezimalzahlen mit Dezimalpunkt ohne Exponentendarstellung)
z.B. 1,
-12,
3.141592654
- Zeichenketten
(begrenzt durch Anführungszeichen)
z.B. "eine Zeichenkette",
'und noch eine'
Boolesche Werte
entstehen als Ergebnis eines Vergleichs.
Folgende Vergleichsoperatoren gibt es:
| für alle Typen: |
= |
Gleichheit |
!= |
Ungleichheit |
| für Zahlen: |
< |
kleiner |
<= |
kleiner oder gleich |
> |
größer |
>= |
größer oder gleich |
<xsl:if test="@name='Fritz Meier'"> ... |
Hier wird der Wert des Attributs name mit der
Zeichenkette 'Fritz Meier' verglichen.
Vergleiche
Ein Größenvergleich mit etwas anderem als einer Zahl ergibt immer
falsch.
@name > 'Schmidt'
@name <= 'Schmidt'
|
Beide Vergleiche ergeben den Wahrheitswert falsch. Bei
Vergleichen in XPath (und auch beim Rechnen) können keine
Fehler (im Sinne von Fehlermeldungen) auftreten.
Zeichenketten können nicht untereinander der Größe nach
(lexikographisch) verglichen werden.
Bei Knotenmengen gilt: wenn sich ein Knoten findet,
für den der Vergleich wahr ist, dann ist der gesamte Vergleich
wahr.
zutat = "Rotwein"
zutat != "Rotwein"
|
Der erste Vergleich ist wahr, wenn Rotwein
irgendwo als Zutat vorkommt. Der zweite Vergleich ist wahr,
wenn es (auch) eine andere Zutat als Rotwein
gibt, bzw. dann falsch, wenn sich keine solche andere
Zutat finden lässt.
Es wird immer mit dem Zeichenkettenwert verglichen.
Das bedeutet, es wird nicht die Identität zweier Knoten
getestet. Beispiel (Kontextknoten sei irgendein
rezept-Element): der Test . = //rezept[1]
testet nicht, ob das aktuelle Rezept das erste ist,
sondern, ob das erste Rezept den gleichen Wert wie das aktuelle
hat. Das könnte möglich sein, obwohl das aktuelle Rezept nicht
das erste ist (wenngleich das bei Rezepten eher unwahrscheinlich
ist).
Vergleiche mit leeren Knotenmengen ergeben immer falsch.
Nebeneffekt - sogar soetwas ergibt falsch:
@fehlt = @fehlt
d.h. obwohl rechts und links scheinbar das gleiche steht,
schlägt der Vergleich fehl, wenn es kein Attribut namens
fehlt gibt.
Logische Ausdrücke
Die Funktion not kehrt einen logischen Wert um.
zutat != "Rotwein"
not(zutat = "Rotwein")
|
Die beiden Ausdrücke sind nicht äquivalent! Der erste
Vergleich war erfüllt, wenn es eine Zutat gibt, die
kein Rotwein ist. Der zweite Vergleich ist hingegen erfüllt,
wenn unter den Zutaten überhaupt kein Rotwein vorkommt.
Über die logischen Operatoren or und and
können logische Werte miteinander verknüpft werden.
idee/@name="Kalle" and zubereitung/@dauer="1 min"
@rolle='Vater' or @rolle='Sohn'
|
Der Operator and bindet stärker als
or.
Vergleiche innerhalb von Attributwerten
kommen bei xsl:if, xsl:when und
innerhalb von Prädikaten (z.B. in select-Attributen)
vor.
- Das kleiner-Zeichen muss dann durch die entsprechende
Entity-Referenz dargestellt werden:
<xsl:if test="position() < 10"> ... </xsl:if> |
- Zeichenketten müssen durch das jeweils andere Anführungszeichen
begrenzt werden:
<xsl:when test="@rolle='Sohn'"> ... </xsl:when>
<xsl:when test='@rolle="Vater"'> ...</xsl:when>
|
Achtung Fehlerquelle:
<xsl:when test="@rolle=Vater">
testet etwas anderes, nämlich ob der Wert des Attributs
rolle mit dem Wert eines Kindelements
namens Vater übereinstimmt. Wenn es solche
Elemente nicht gibt, ist das kein Fehler, sondern liefert
nur als Ergebnis des Vergleichs den Wert falsch.
Zahlen
In XSLT kann man rechnen:
+ |
Addition |
- |
Subtraktion |
* |
Multiplikation |
div |
(reelle) Division |
mod |
Rest bei der ganzzahligen Division |
Der Schrägstrich / trennt einzelne Schritte
innerhalb eines Lokalisierungspfades und wird daher nicht als
Divisionsoperator verwendet.
Der Stern wird zwar auch als Lokalisierungsschritt verwendet,
allerdings ist seine jeweilige Bedeutung abhängig von seiner
Stellung innerhalb des Ausdrucks eindeutig.
Es gilt wie üblich Punkt- vor Strichrechnung.
Achtung: Das Minuszeichen ist innerhalb von XML-Namen zugelassen.
Damit würde x-y als Name, jedoch x - y
als Subtraktion interpretiert werden.
Außerdem gibt es als spezielle Werte
- positiv und negativ null,
- positiv und negativ unendlich sowie
- NaN (Not a Number).
Für die letzten drei gibt es jedoch keine Literale.
Wenn man sowas braucht, kann man diese Werte "ausrechnen":
1 div 0 ergibt positiv unendlich
(Infinity)
-1 div 0 ergibt negativ unendlich
(-Infinity) und
0 div 0 ergibt NaN.
Zahlen (II)
Beispiel
eine lange Bestell-Liste:
<bestellungen>
<bestellung anzahl="2" preis="14.95">Kalender</bestellung>
<bestellung anzahl="1" preis="29.95">Kochbuch</bestellung>
<bestellung anzahl="10" preis="0.20">Lesezeichen</bestellung>
...
</bestellungen> |
In einer Tabelle soll unter anderem der Preis pro Bestellung
inklusive Bestellpauschale i.H.v. 50 Cents ausgegeben werden.
<xsl:template match="bestellung">
<td><xsl:value-of select="." /></td>
<td><xsl:value-of select="@anzahl" /></td>
<td><xsl:value-of select="@preis" /></td>
<td><xsl:value-of select="@anzahl * @preis + 0.5" /></td>
</xsl:template> |
Wichtig ist hier, dass im preis-Attribut nicht etwa
ein Komma als Dezimaltrenner verwendet wird. In diesem Fall
würde das Ergebnis jeweils NaN sein.
Wie man die gesamte Summe über alle Bestellungen ausrechnet,
sehen wir später. Das ist nämlich überraschenderweise nicht so
einfach. Ebenso kümmern wir uns hier nicht um eine
schöne Formatierung der Zahlen.
Beispiel: Ausgabe einer mehrspaltigen Tabelle
Als Eingabe sei nur eine flache Liste vorhanden:
<liste>
<eintrag>123123</eintrag>
<eintrag>123239</eintrag>
<eintrag>123240</eintrag>
<eintrag>123772</eintrag>
<eintrag>124006</eintrag>
<eintrag>124010</eintrag>
...
<eintrag>138901</eintrag>
</liste> |
Zum Beispiel die Matrikelnummern aller Studenten, die erfolgreich am
Praktikum teilgenommen haben.
Gesucht ist jetzt eine tabellarische Formatierung der Art:
| 123123 |
123239 |
123240 |
| 123772 |
124006 |
124010 |
| ... |
| 138901 |
|
Diese Tabelle ist zeilenweise zu lesen.
Es soll 3 Spalten geben. Das bedeutet:
- In der ersten Spalte stehen der 1., der 4., der 7., usw.
Eintrag
(Alle Zahlen, die bei Teilung durch 3 den Rest 1 lassen)
- Daneben stehen jeweils die nächsten beiden Nachfolger
(d.h. die nächsten (3-1) Nachfolger)
Die folgenden Zeilen leisten genau dies:
<xsl:template match="liste">
<table border="1">
<!-- Bearbeite jeden dritten Eintrag -->
<xsl:for-each select="eintrag[position() mod 3 = 1]">
<!-- Erzeuge jeweils eine Tabellenzeile -->
<tr>
<!-- Bearbeite den jeweiligen Eintrag und seine beiden
(position() < 3) Nachfolger -->
<xsl:for-each
select=".|following-sibling::eintrag[position() < 3]">
<!-- Erzeuge jeweils ein Tabellenfeld -->
<td>
<xsl:value-of select="." />
</td>
</xsl:for-each>
</tr>
</xsl:for-each>
</table>
</xsl:template> |
Der senkrechte Strich | vereinigt Knotenmengen.
Es handelt sich hier tatsächlich um eine echte Mengenvereinigung.
Das Ergebnis enthält also keine mehrfachen Knoten; die Knoten sind
nicht sortiert. Die xsl:for-each-Anweisung verarbeit
dann die Knoten der Verarbeitungsmenge in Dokumentordnung, d.h. in
der Reihenfolge, in der sie in der XML-Quelle vorliegen.
Es gibt keinen Operator für Mengendurchschnitt oder -differenz.
Beides kann allerdings über etwas kompliziertere Ausdrücke
bestimmt werden.
Funktionen
XSLT und XPath stellen einen Satz von Funktionen zur Verfügung,
die innerhalb von Ausdrücken aufgerufen werden können.
Einige haben wir schon kennen gelernt:
position()
liefert die Kontextposition, d.h. die Nummer des Kontextknotens
in der aktuellen Knotenliste
last()
liefert die Kontextgröße, d.h. die Nummer des letzten Knotens in
der aktuellen Knotenliste
Funktionen (II)
weitere Funktionen auf Knotenmengen:
count(Knotenmenge)
liefert die Anzahl der Knoten in der als Argument übergebenen
Menge
Beispiel:
<xsl:template match="rezepte-sammlung">
<xsl:text>Dieses Buch enthält </xsl:text>
<xsl:value-of select="count(rezept)" />
<xsl:text> einmalige Rezepte.</xsl:text>
</xsl:template> |
Funktionen (III)
Häufiges Problem:
Man möchte die Position eines anderen Knoten bestimmen,
z.B. des Elternknotens.
Die Funktion position() hilft hier nicht, da
- diese nur die Position des Kontextknotens liefert
- diese Position immer in Bezug auf eine Kontext-Knotenmenge
definiert ist
Wir hatten bereits gesehen, dass das Ergebnis von
position() entscheidend von der im Schritt zuvor
durch xsl:apply-templates ausgewählten Knotenmenge
abhängt.
Lösung:
Man zählt die entsprechenden Vorgänger-Knoten, etwa:
count(../preceding-sibling::*)+1 |
Hier wird 1 addiert, damit der erste Knoten (der ja keine
vorangehenden Geschwister besitzt) tatsächlich die Nummer 1
erhält.
Funktionen (IV)
Identität von Knoten
Die Operatoren = und != vergleichen
immer den Zeichenkettenwert (den Inhalt) der beiden
Knoten.
Wie bestimmt man, ob zwei Knoten identisch sind?
Man zählt die Knoten der Vereinigungsmenge.
Beispiele:
<xsl:if test="count(. | /*/rezept[3]) = 1">
<!-- wir sind beim dritten Rezept --> |
Dieses Beispiel ist etwas künstlich, da man entweder direkt
position() oder aber
count(preceding-sibling::rezept)+1 verwenden könnte.
Sinnvoll ist diese Variante im Zusammenhang mit Funktionen, die
Knoten liefern (id oder key), oder mit
Variablen. Zum Beispiel:
test="count(key(...)[1] | .) = 1"
Durchschnitt zweier Knotenmengen $a und
$b:
$a[count(.|$b) = count($b)] |
Also alle die Knoten aus $a, die auch in
$b enthalten sind, d.h. die Vereinigung von
$b und diesem Knoten aus $a macht
$b nicht größer.
Hier wird bereits auf Variablen vorgegriffen, die wir später
noch kennen lernen werden.
Funktionen (V)
Zugriff auf Namen von Elementen / Attributen:
name(Knoten)
liefert den Namen des Argumentknotens
local-name(Knoten)
liefert den lokalen Teil des Namens des Argumentknotens
namespace-uri(Knoten)
liefert den URI des Namensraums, in dem sich der Argumentknoten
befindet
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> ...
|
name(/*) |
= |
xs:schema |
local-name(/*) |
= |
schema |
namespace-uri(/*) |
= |
http://www.w3.org/2001/XMLSchema |
Eigentlich wird als Argument immer eine Knotenmenge übergeben.
Jede der Funktionen liefert dann Namen bzw. URI des ersten Knotens
dieser Menge.
Für Elemente und Attribute aus dem leeren bzw. dem voreingestellten
Namensraum liefern name und local-name
dasselbe.
Für Wurzel-, Text- und Kommentarknoten liefert jede der Funktionen
die leere Zeichenkette. namespace-uri liefert außerdem
für Namensraum- und Processing-Instruction-Knoten die leere
Zeichenkette. name und local-name liefern
für Namensraumknoten das dazugehörige Präfix und für
Processing-Instruction-Knoten das Ziel der Verarbeitungsanweisung.
Funktionen (VI)
Eindeutige Bezeichner - IDs
id(Bezeichnerliste)
liefert die Elementknoten, für die die Namen in
Bezeichnerliste als ID-wertige
Attribute vergeben wurden
Beispiel:
<zutaten>
<zutat menge="1" id="ei">Ei</zutat>
...
</zutaten> |
id('ei') liefert dann die erste Zutat aus obiger
Liste.
id('ei')/@menge liefert das entsprechende
menge-Attribut.
Das zweite Beispiel zeigt, dass (technisch gesehen) die Funktion
id eine Knotenmenge liefert, an die sich ein
beliebiger Lokalisierungspfad anschließen kann.
Der Name id des Attributs spielt keine Rolle.
Entscheidend ist, dass in der dazugehörigen DTD oder im XML-Schema
dieses Attribut als vom Typ ID deklariert wurde.
Besitzt der XSLT-Prozessor kein Wissen über den Dokumenttyp,
kann er auch keine ID-wertigen Attribute erkennen. Die Funktion
id liefert dann eine leere Knotenmenge.
Funktionen (VII)
Eindeutige Bezeichner lassen sich auch generieren:
generate-id(Knoten)
erzeugt einen eindeutigen Namen für den übergebenen Knoten.
Es gibt keine Möglichkeit, das Format das erzeugten Namens
in irgendeiner Art zu beeinflussen. Ein XSLT-Prozessor muss lediglich
(in einem Lauf) für denselben Knoten denselben Namen erzeugen.
Man kann sich nicht darauf verlassen, dass bei verschiedenen Aufrufen
für die gleiche Quelldatei gleiche Namen erzeugt werden.
Beispiel: Generierung eines verlinkten
Inhaltsverzeichnisses
<xsl:template match="rezepte-sammlung">
<xsl:for-each select="rezept">
<a href="#{generate-id(.)}">
<xsl:value-of select="titel" />
</a><br />
</xsl:for-each>
<xsl:apply-templates />
</xsl:template>
<xsl:template match="rezept/titel">
<h2><a name="{generate-id(..)}">
<xsl:value-of select="." />
</a></h2>
</xsl:template> |
Funktionen (VIII) - für Zahlen
floor(Zahl)
rundet die übergebene Zahl ab
ceiling(Zahl)
rundet die übergebene Zahl auf
round(Zahl)
rundet auf die nächste ganze Zahl
sum(Knotenmenge)
liefert die Summe der als Zahlen interpretierten Werte der
Knoten aus der übergebenen Knotenmenge
Angenommen, die XML-Quelle enthält
<zubereitung dauer="3"> <!-- ohne "min" --> |
Gesamtkochzeit: sum(/*/rezept/zubereitung/@dauer)
Man kann mittels sum nur Zahlen aufsummieren, die
direkt in der XML-Quelle vorkommen. Es ist nicht möglich, die
Summe von Zwischenergebnissen in einem einzigen Ausdruck zu
berechnen (z.B. die Gesamtsumme beim Bestellungsbeispiel auf Seite
9, d.h. die Summe aus allen Produkten
von @anzahl und @preis).
Funktionen (IX) - für Zeichenketten
Funktionen (X) - für Zeichenketten
substring-before(String,
Teilstring)
liefert die Zeichenkette, die sich in String
vor dem ersten Auftreten von Teilstring
befindet.
substring-after(String,
Teilstring)
liefert die Zeichenkette, die sich in String
nach dem ersten Auftreten von Teilstring
befindet.
Ist Teilstring nicht in
String enthalten, wird die leere
Zeichenkette zurückgegeben.
Beispiel: Vor- und Nachname der Rezept-Autoren
<xsl:template match="idee">
Vorname: <xsl:value-of select="substring-before(@name, ' ')" />
Nachname: <xsl:value-of select="substring-after(@name, ' ')" />
</xsl:template> |
Funktionen (XI) - für Zeichenketten
substring(String, Beginn,
Länge)
liefert die Teilzeichenkette aus String,
die an Position Beginn beginnt und die
Länge Länge besitzt.
Wird Länge weggelassen, wird die
Teilzeichenkette bis zum Ende von String
zurückgegeben.
Das erste Zeichen ist an der Position 1.
Beispiel: eine vollständige Zeitangabe
<zeit>2002-01-03T10:43:52+01:00</zeit> |
| Monat: |
substring(zeit, 6, 2) |
| Uhrzeit: |
substring(zeit, 12, 8) |
| mit Zeitzone: |
substring(zeit, 12) |