XSLT aus XSLT generieren

(Auszug aus "XSLT Kochbuch" von Sal Mangano)

Problem

Sie möchten XSLT aus einer anderen XML-Darstellung generieren. Oder Sie möchten alternativ dazu XSLT oder Pseudo-XSLT in echtes XSLT transformeiren.

Lösung

Es gibt zwei Dinge an der Kontrollstruktur von XSLT, die mich manchmal stören: erstens das Fehlen einer if-then-elsif-else-Konstruktion und zweitens die Abwesenheit einer echten Schleifenkonstruktion. Natürlich kenne ich xsl:choose und xsl:for-each, aber beiden fehlt ein bisschen etwas. An xsl:choose stört mich, dass das choose-Element praktisch keine Funktion hat, außer dass es eine weitere Verschachtelungsebene erzwingt. Und xsl:for-each ist keine echte Schleifen-, sondern eine Iterationskonstruktion. Um Schleifen mit Laufvariablen zu emulieren, müssen Sie eine Rekursion bzw. die Piez-Methode (siehe das Rezept Einen String n-mal duplizieren) verwenden, was sehr schwierig ist.

Dieses Beispiel illustriert eine Transformation von XSLT nach XSLT, die so tut, als gäbe es in XSLT die Elemente xslx:elsif, xslx:else und xslx:loop. Weil das nicht der Fall ist, werden Sie ein Stylesheet erzeugen, das echtes XSLT aus dem folgenden Pseudo-XSLT generiert. Ein xsl:if und ein xslx:if nebeneinander zu haben ist zwar heikel, aber es wäre falsch, den XSLT-Standardnamensraum für Ihre Elementerweiterungen zu benutzen. Vielleicht werden diese Elemente eines Tages im XSLT-Standard definiert:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xslx="http://www.ora.com/XSLTCookbook/ExtendedXSLT" >
  <xsl:output method="text"/>
  <xsl:template match="foo">
    <xslx:if test="bar">
      <xsl:text>In der Nähe von foo werden Sie oft eine bar finden!</xsl:text>
    </xslx:if>
    <xslx:elsif test="baz">
      <xsl:text>Ein baz ist ein sicheres Zeichen von Besessenheit.</xsl:text>
    </xslx:elsif>
    <xslx:else>
      <xslx:loop param="i" init="0" test="$i &lt; 10" incr="1">
        <xsl:text>Hmm, ich habe nichts zu sagen, aber das 10-mal.</xsl:text>
      </xslx:loop>
    </xslx:else>
    <xslx:loop param="i" init="10" test="$i &gt;= 0" incr="-1">
      <xslx:loop param="j" init="10" test="$j &gt;= 0" incr="-1">
        <xsl:text>&#xa;</xsl:text>
        <xsl:value-of select="$i * $j"/>
      </xslx:loop>
    </xslx:loop>
    <xslx:if test="foo">
      <xsl:text>foo foo! Niemand sagt foo foo!</xsl:text>
    </xslx:if>
    <xslx:else>
      <xsl:text>Na ja, also gut!</xsl:text>
    </xslx:else>
  </xsl:template>
</xsl:stylesheet>

Es folgt eine Transformation, die echtes XSLT aus Pseudo-XSLT generiert. Um die Sache einfach zu halten, werden in diesem Beispiel keine semantischen Überprüfungen vorgenommen, z.B. auf mehrere xsl:else-Klauseln bei einem einzigen xsl:if oder auf mehrfaches Vorkommen von Parametern in verschachtelten Schleifen:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xslx="http://www.ora.com/XSLTCookbook/ExtendedXSLT" xmlns:xso="dummy" >
  <!-- Verwende die Identitätstransformation wieder, um reguläres XSLT aus einer Quelle zu einem Ziel zu kopieren. -->
  <xsl:import href="../util/copy.xslt"/>
  <!-- Lassen Sie den Prozessor KEINE Formatierung vornehmen, indem Sie indent = yes benutzen, weil das die xsl:text-Knoten vermasselt -->
  <xsl:output method="xml" version="1.0" encoding="UTF-8" />
  <!-- Wir benutzen außerdem xso als Alias, wenn wir xslt-Elemente wörtlich ausgeben müssen -->
  <xsl:namespace-alias stylesheet-prefix="xso" result-prefix="xsl"/>
  <xsl:template match="xsl:stylesheet | xsl:transform">
    <xso:stylesheet>
      <!-- Der erste Durchgang behandelt die if-elsif-else-Übersetzung und die Konvertierung von xslx:loop in benannte Template-Aufrufe -->
      <xsl:apply-templates select="@* | node( )"/>
      <!-- Der zweite Durchgang behandelt die Konvertierung von xslx:loop in rekusive benannte Templates -->
      <xsl:apply-templates mode="loop-body" select="//xslx:loop"/>
    </xso:stylesheet>
  </xsl:template>
  <!-- Wir suchen nach xslx:if mit entsprechendem xslx:elsif oder xslx:else -->
  <xsl:template match="xslx:if[following-sibling::xslx:else or following-sibling::xslx:elsif]">
    <xsl:variable name="idIf" select="generate-id( )"/>
    <xso:choose>
      <xso:when test="{@test}">
        <xsl:apply-templates select="@* | node( )"/>
      </xso:when>
      <!-- Wir bearbeiten das xsl:elsif und xslx:else in einem speziellen Modus als Teil von xsl:choose. Wir müssen sicherstellen, nur die zu nehmen, deren vorausgehendes xslx:if dieses xslx:if ist -->
      <xsl:apply-templates select="following-sibling::xslx:else[generate-id(preceding-sibling::xslx:if[1]) = $idIf] | following-sibling::xslx:elsif[generate-id(preceding-sibling::xslx:if[1]) = $idIf]" mode="choose"/>
    </xso:choose>
  </xsl:template>
  <!-- Ignoriere xslx:elsif und xslx:else im normalen Modus -->
  <xsl:template match="xslx:elsif | xslx:else"/>
  <!-- Ein xslx:elsif wird zu xsl:when -->
  <xsl:template match="xslx:elsif"  mode="choose">
    <xso:when test="{@test}">
      <xsl:apply-templates select="@* | node( )"/>
    </xso:when>
  </xsl:template>
  <!-- Ein xslx:else wird zu xsl:otherwise -->
  <xsl:template match="xslx:else" mode="choose">
    <xso:otherwise>
      <xsl:apply-templates/>
    </xso:otherwise>
  </xsl:template>
  <!-- Ein xslx:loop wird zum Aufruf eines benannten Templates -->
  <xsl:template match="xslx:loop">
    <!-- Jedes Template erhält den Namen loop-N, wobei N die Position dieser Schleife relativ zu vorherigen Schleifen beliebiger Tiefe ist. -->
    <xsl:variable name="name">
      <xsl:text>loop-</xsl:text>
      <xsl:number count="xslx:loop" level="any"/>
    </xsl:variable>
    <xso:call-template name="{$name}">
      <xsl:for-each select="ancestor::xslx:loop">
        <xso:with-param name="{@param}" select="${@param}"/>
      </xsl:for-each>
      <xso:with-param name="{@param}" select="{@init}"/>
    </xso:call-template>
  </xsl:template>
  <!-- Der Modus loop-body wird im zweiten Durchgang benutzt. -->
  <!-- Hier werden rekursive Templates für die Schleifen generiert. -->
  <xsl:template match="xslx:loop" mode="loop-body">
    <xsl:variable name="name">
      <xsl:text>loop-</xsl:text>
      <xsl:value-of select="position( )"/>
    </xsl:variable>
    <xso:template name="{$name}">
      <!-- Falls diese Schleife in einer anderen verschachtelt ist, muss sie die äußeren Schleifenparameter "sehen", daher generieren wir diese hier. -->
      <xsl:for-each select="ancestor::xslx:loop">
        <xso:param name="{@param}"/>
      </xsl:for-each>
      <!-- Der lokale Schleifenparameter -->
      <xso:param name="{@param}"/>
      <!-- Generiere den Rekursionssteuerungstest -->
      <xso:if test="{@test}">
        <!-- Wende Template im normalen Modus an, um Aufrufe von verschachtelten Schleifen zu behandeln, während alles andere kopiert wird. -->
        <xsl:apply-templates/>
        <!-- Das ist der rekursive Aufruf, der das incr auf den Schleifenparameter anwendet -->
        <xso:call-template name="{$name}">
          <xsl:for-each select="ancestor::xslx:loop">
            <xso:with-param name="{@param}" select="${@param}"/>
          </xsl:for-each>
          <xso:with-param name="{@param}" select="${@param} + {@incr}"/>
        </xso:call-template>
      </xso:if>
    </xso:template>
  </xsl:template>
</xsl:stylesheet>

Hier ist das generierte Ergebnis:

<xso:stylesheet xmlns:xso="http://www.w3.org/1999/XSL/Transform" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xslx="http://www.ora.com/XSLTCookbook/ExtendedXSLT" version="1.0">
  <xsl:output method="text"/>
  <xsl:template match="foo">
    <xso:choose>
      <xso:when test="bar">
        <xsl:text>In der Nähe von foo werden Sie oft eine bar finden!</xsl:text>
      </xso:when>
      <xso:when test="baz">
        <xsl:text>Ein baz ist ein sicheres Zeichen von Besessenheit.</xsl:text>
      </xso:when>
      <xso:otherwise>
        <xso:call-template name="loop-1">
          <xso:with-param name="i" select="0"/>
        </xso:call-template>
      </xso:otherwise>
    </xso:choose>
    <xso:call-template name="loop-2">
      <xso:with-param name="i" select="10"/>
    </xso:call-template>
    <xso:choose>
      <xso:when test="foo">
        <xsl:text>foo foo! Niemand sagt foo foo!</xsl:text>
      </xso:when>
      <xso:otherwise>
        <xsl:text>Na ja, also gut!</xsl:text>
      </xso:otherwise>
    </xso:choose>
  </xsl:template>
  <xso:template name="loop-1">
    <xso:param name="i"/>
    <xso:if test="$i &lt; 10">
      <xsl:text>Hmm, ich habe nichts zu sagen, aber das 10-mal.</xsl:text>
      <xso:call-template name="loop-1">
        <xso:with-param name="i" select="$i + 1"/>
      </xso:call-template>
    </xso:if>
  </xso:template>
  <xso:template name="loop-2">
    <xso:param name="i"/>
    <xso:if test="$i &gt;= 0">
      <xso:call-template name="loop-3">
        <xso:with-param name="i" select="$i"/>
        <xso:with-param name="j" select="10"/>
      </xso:call-template>
      <xso:call-template name="loop-2">
        <xso:with-param name="i" select="$i + −1"/>
      </xso:call-template>
    </xso:if>
  </xso:template>
  <xso:template name="loop-3">
    <xso:param name="i"/>
    <xso:param name="j"/>
    <xso:if test="$j &gt;= 0">
      <xsl:text></xsl:text>
      <xsl:value-of select="$i * $j"/>
      <xso:call-template name="loop-3">
        <xso:with-param name="i" select="$i"/>
        <xso:with-param name="j" select="$j + −1"/>
      </xso:call-template>
    </xso:if>
  </xso:template>
</xso:stylesheet>

Diskussion

Der Schlüssel bei der Generierung von XSLT mit XSLT ist das Element xsl:namespace-alias. Ohne dieses Element könnte der Prozessor den eigentlichen XSLT-Inhalt nicht von jenem unterscheiden, der wortwörtlich als Ergebniselemente ausgegeben werden soll. Die Generierung von XSLT mit XSLT ist in weit mehr Situationen nützlich, als dieses Beispiel allein abdeckt. Einige Beispiele dafür sind:

Erleichterung von Literate Programming

Beim Literate Programming (auf Deutsch manchmal »literarisches Programmieren« genannt) werden Codefragmente in von Menschen lesbare Dokumentation eingebettet (statt umgekehrt, wie es üblicher ist). Das heißt, die Information kommt hierbei in der Reihenfolge vor, die für Menschen am besten geeignet ist, und nicht in der, die für Compiler am besten geeignet ist.

Bereitstellung von bedingten Includes/Imports

Wenn es diese Eigenschaft in XSLT gäbe, dann wären dafür vermutlich schwierige Erweiterungen am Verarbeitungsmodell nötig, ähnlich zum Präprozessor bei einem C-Programm.

Ermöglichung dynamischer Auswertung von XPaths

Diese Kategorie bezieht sich auf ein Stylesheet, das XPaths aus einer importierten Quelle generiert und sie in einem anderen Stylesheet einbettet, in dem sie statisch ausgewertet werden. Durch diesen zusätzlichen Umweg wird also ein dynamisches Verhalten emuliert. Oftmals werden diese XPaths in einem Dokument eingebettet. Zum Beispiel könnten Sie eine Tabellenspezifikation ähnlich zu folgender haben:

<table of="person">
  <column label="Firstname" content="name/firstname" />
  <column label="Surname" content="name/surname" />
  <column label="Age" content="@age" type="number" />
  <sort select="Surname, Firstname, Age" />
</table>

Es ist einfacher, die von diesem XML beschriebene Tabelle zu generieren (indem man XSLT aus der Tabellenspezifikation generiert und dieses XSLT dann über die Daten laufen lässt), als die Tabellenspezifikation im gleichen Stylesheet zu interpretieren, in dem Sie die Daten verarbeiten.

Siehe auch

Ein ähnliches Beispiel, das außerdem auch validiert, finden Sie in Oliver Beckers XSLT-Loop Compiler.

  

<< zurück vor >>

 

 

 

Tipp der data2type-Redaktion:
Zum Thema XSLT bieten wir auch folgende Schulungen zur Vertiefung und professionellen Fortbildung an:

Copyright © 2006 O'Reilly Verlag GmbH & Co. KG
Für Ihren privaten Gebrauch dürfen Sie die Online-Version ausdrucken.
Ansonsten unterliegt dieses Kapitel aus dem Buch "XSLT Kochbuch" denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

O'Reilly Verlag GmbH & Co. KG, Balthasarstraße 81, 50670 Köln, kommentar(at)oreilly.de