1 / 23

Transformationen

mit XSLT

Teil 4


2 / 23

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. &auml;, &nbsp;). 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" />

3 / 23

XPath-Ausdrücke und -Datentypen

Lokalisierungspfade sind spezielle XPath-Ausdrücke, die Knotenmengen auswählen.

Außerdem gibt es:


4 / 23

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.

5 / 23

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.

6 / 23

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.


7 / 23

Vergleiche innerhalb von Attributwerten

kommen bei xsl:if, xsl:when und innerhalb von Prädikaten (z.B. in select-Attributen) vor.

<xsl:if test="position() &lt; 10"> ... </xsl:if>
<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.

8 / 23

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.

9 / 23

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.

10 / 23

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.


11 / 23

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:


12 / 23

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() &lt; 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.

13 / 23

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:


14 / 23

Funktionen (II)

weitere Funktionen auf Knotenmengen:

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>

15 / 23

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

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.

16 / 23

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.

17 / 23

Funktionen (V)

Zugriff auf Namen von Elementen / Attributen:

<?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.

18 / 23

Funktionen (VI)

Eindeutige Bezeichner - IDs

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.

19 / 23

Funktionen (VII)

Eindeutige Bezeichner lassen sich auch generieren:

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>

20 / 23

Funktionen (VIII) - für Zahlen

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).

21 / 23

Funktionen (IX) - für Zeichenketten



22 / 23

Funktionen (X) - für Zeichenketten

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>

23 / 23

Funktionen (XI) - für Zeichenketten


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)