XSLT mit JavaScript erweitern

(Auszug aus "XSLT Kochbuch" von Sal Mangano)

Problem

Sie möchten JavaScript verwenden, um eine in XSLT fehlende Funktionalität zu implementieren.

Lösung

Die folgenden Beispiele machen von der Eigenschaft von Xalan-Java 2 Gebrauch, Scripting-Sprachen wie JavaScript ausführen zu können. Eine typische Anwendung einer JavaScript-basierten Erweiterung ruft eine Funktion auf, die weder in XSLT noch in XPath eingebaut ist. Ein häufiges Beispiel dafür sind trigonometrische Funktionen:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xalan="http://xml.apache.org/xslt" xmlns:trig="http://www.ora.com/XSLTCookbook/extend/trig">
  <xsl:output method="text"/>
  <xalan:component prefix="trig" functions="sin">
    <xalan:script lang="javascript">function sin (arg){ return Math.sin(arg);}</xalan:script>
  </xalan:component>
  <xsl:template match="/">The sin of 45 degrees is <xsl:text/><xsl:value-of select="trig:sin(3.14159265 div 4)"/></xsl:template>
</xsl:stylesheet>

Mit JavaScript können Sie Funktionen implementieren, die Seiteneffekte haben, sowie Objekte, die einen internen Zustand besitzen: (Ich sollte mich schämen, so etwas vorzuschlagen! Aber ganz im Ernst, wenn Sie die Grenzen von XSLT überschreiten, dann müssen Sie nicht mehr nach seinen Regeln spielen, aber die Folgen müssen Sie akzeptieren.)

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xalan="http://xml.apache.org/xslt" xmlns:count="http://www.ora.com/XSLTCookbook/extend/counter">
  <xsl:output method="text"/>
  <xalan:component prefix="count" functions="counter nextCount resetCount makeCounter">
    <xalan:script lang="javascript">
      function counter(initValue)
      {
        this.value = initValue ;
      }

      function nextCount(ctr)
      {
        return ctr.value++ ;
      }
      function resetCount(ctr, value)
      {
        ctr.value = value  ;
        return "" ;
      }

      function makeCounter(initValue)
      {
        return new counter(initValue) ;
      }
    </xalan:script>
  </xalan:component>
  <xsl:template match="/">
    <xsl:variable name="aCounter" select="count:makeCounter(0)"/>
    Count: <xsl:value-of select="count:nextCount($aCounter)"/>
    Count: <xsl:value-of select="count:nextCount($aCounter)"/>
    Count: <xsl:value-of select="count:nextCount($aCounter)"/>
    Count: <xsl:value-of select="count:nextCount($aCounter)"/>
    <xsl:value-of select="count:resetCount($aCounter,0)"/>
    Count: <xsl:value-of select="count:nextCount($aCounter)"/>
  </xsl:template>
</xsl:stylesheet>

In den meisten Implementierungen ergibt dieser Code Folgendes:

   Count: 0
   Count: 1
   Count: 2
   Count: 3
   Count: 0

Ein Prozessor, der keine Seiteneffekte erwartet, kann potenziell die Auswertungsreihenfolge ändern und die erwarteten Ergebnisse untergraben.

Im folgenden Beispiel können Sie auf die JavaScript-Bibliothek für reguläre Ausdrücke zugreifen:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xalan="http://xml.apache.org/xslt" xmlns:regex="http://www.ora.com/XSLTCookbook/extend/regex">
  <xsl:output method="text"/>
  <xalan:component prefix="regex" functions="match leftContext rightContext getParenMatch makeRegExp">
    <xalan:script lang="javascript">
      function Matcher(pattern)
      {
        this.re = new RegExp(pattern) ;
        this.re.compile(pattern) ;
        this.result="" ;
        this.left="" ;
        this.right="" ;
      }

      function match(matcher, input)
      {
        matcher.result = matcher.re.exec(input) ;
        matcher.left = RegExp.leftContext ;
        matcher.right = RegExp.rightContext ;
        return matcher.result[0] ;
      }

      function leftContext(matcher)
      {
        return matcher.left ;
      }

      function rightContext(matcher)
      {
        return matcher.right ;
      }

      function getParenMatch(matcher, which)
      {
        return matcher.result[which] ;
      }

      function makeRegExp(pattern)
      {
        return new Matcher(pattern) ;
      }
    </xalan:script>
  </xalan:component>
  <xsl:template match="/">
    <xsl:variable name="dateParser" select="regex:makeRegExp('(\d\d?)[/-](\d\d?)[/-](\d{4}|\d{2})')"/>
    Match: <xsl:value-of select="regex:match($dateParser,'I was born on 05/03/1964 in New York City.')"/>
    Left: <xsl:value-of select="regex:leftContext($dateParser)"/>
    Right: <xsl:value-of select="regex:rightContext($dateParser)"/>
    Month: <xsl:value-of select="regex:getParenMatch($dateParser, 1)"/>
    Day: <xsl:value-of select="regex:getParenMatch($dateParser,2)"/>
    Year: <xsl:value-of select="regex:getParenMatch($dateParser,3)"/>
  </xsl:template>
</xsl:stylesheet>

Das Ergebnis dieses Beispiels lautet:

   Match: 05/03/1964
   Left: I was born on
   Right: in New York City.
   Month: 05
   Day: 03
   Year: 1964

Außerdem können Sie mit Xalan JavaScript-basierte Erweiterungselemente erzeugen. Es folgt ein solches Erweiterungselement, das die Ausführung seines Inhalts n-mal wiederholt. Das ist nützlich beim Duplizieren von Strings, Strukturen oder als einfaches Schleifenkonstrukt:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xalan="http://xml.apache.org/xslt" xmlns:rep="http://www.ora.com/XSLTCookbook/extend/repeat" extension-element-prefixes="rep">
  <xsl:output method="xml"/>
  <xalan:component prefix="rep" elements="repeat">
    <xalan:script lang="javascript">
      <![CDATA[
        function repeat(ctx, elem)
        {
          // Hole Attributwert n als Integer
          n = parseInt(elem.getAttribute("n")) ;
          // Hole Transformer, der für die Ausführung von Knoten benötigt wird
          xformer = ctx.getTransformer( ) ;
          // Führe Inhalt des repeat-Elements n-mal aus
          for(var ii=0; ii < n; ++ii)
        {
          node = elem.getFirstChild( ) ;
          while(node)
          {
            node.execute(xformer) ;
            node = node.getNextSibling( ) ;
          }
        }
        // Der Rückgabewert wird in die Ausgabe eingefügt, gib also null zurück, um das zu verhindern.
        return null ;
      }
    ]]>
    </xalan:script>
  </xalan:component>
  <xsl:template match="/">
    <tests>
      <!-- Dupliziere Text -->
      <test1><rep:repeat n="10">a</rep:repeat></test1>
      <!-- Dupliziere Struktur -->
      <test2>
        <rep:repeat n="10">
          <Malady>
            <FirstPart>Shim's</FirstPart>
            <SecondPart>Syndrome</SecondPart>
          </Malady>
        </rep:repeat>
      </test2>
      <!-- Wiederhole die Ausführung des XSLT-Codes (genau das, was wir in test1 und test2 gemacht haben). -->
      <test3>
        <rep:repeat n="10">
          <xsl:for-each select="*">
            <xsl:copy/>
          </xsl:for-each>
        </rep:repeat>
      </test3>
    </tests>
  </xsl:template>
</xsl:stylesheet>

Diskussion

Es ist verführerisch, Erweiterungen in JavaScript (oder einer anderen eingebetteten Skriptsprache) zu implementieren, weil Sie trotz Umdenkens bei der Sprache keine andere Entwicklungsumgebung benötigen oder einen separaten Compiler aufrufen müssen. Der vielleicht größte Vorteil ist allerdings der, dass Sprachen wie JavaScript oder VBScript sehr leicht zu erlernen sind. (Tatsächlich hätte ich die Zeilen, die ich jemals in JavaScript geschrieben hatte, vor diesem Buch, an zwei Händen und einigen Zehen abzählen können.)

Die Herausforderung beim Einsatz von skriptbasierten Erweiterungen besteht in der normalerweise recht dünnen Dokumentation dazu, wie man XSLT mit den Skripts koppeln kann. Daher sind an dieser Stelle ein paar Hinweise dazu angebracht. Der größte Teil dieser Informationen ist in den Dokumenten zu Xalan-Erweiteungen verfügbar, aber wenn man es eilig hat, etwas zum Laufen zu bringen, kann man sie leicht übersehen.

Erstens: Skriptbasierte Erweiterungen sind nur in Xalan-Java möglich, nicht in Xalan C++.

Zweitens: Vergewissern Sie sich, dass Sie bsf.jar und js.jar (für JavaScript) zu Ihrem Klassenpfad hinzufügen, entweder auf der Kommandozeile, wenn Sie Java in einer Unix-Shell aufrufen,

java -cp /xalan/bin/xalan.jar:/xalan/bin/xercesImpl.jar:/xalan/bin/bsf.jar: /xalan/bin/js.jar org.apache.xalan.xslt.Process -in input.xml -xsl trans.xslt

oder in der Umgebungsvariablen CLASSPATH:

export CLASSPATH=/xalan/bin/xalan.jar:/xalan/bin/xercesImpl.jar:/xalan/bin/bsf.jar:/xalan/bin/js.jar

Unter Windows ersetzen Sie die Doppelpunkte als Pfadtrennzeichen durch Semikola und verwenden set statt export.

Drittens: Beachten Sie, dass js.jar kein Teil der Xalan-Distribution ist. Sie müssen es sich separat von Mozilla.org besorgen.

Nachdem Sie Ihre Umgebung korrekt konfiguriert haben, müssen Sie Ihr Stylesheet angeben, um den Anforderungen von Xalan für skriptbasierte Erweiterungen zu genügen. Die schmutzigen Details finden Sie in der Einleitung dieses Kapitels.

Die Implementierung von Erweiterungsfunktionen ist wesentlich einfacher als die von Erweiterungselementen, und dazu sollten die Beispiele im Abschnitt »Lösung« (zusammen mit der Xalan-Dokumentation) ausreichend sein. Der Rest dieses Abschnitts konzentriert sich auf Erweiterungselemente.

Wenn die zu einem Erweiterungselement gehörende Funktion aufgerufen wird, werden automatisch zwei Objekte an sie übergeben. Das erste ist ein Kontext vom Typ org.apache.xalan.extensions.XSLProcessorContext. Dieses Objekt ist ein Handle, um an mehrere andere nützliche Objekte zu gelangen, z.B. an den Kontextknoten, das Stylesheet-Objekt und den Transformer. Außerdem implementiert es die Funktion outputToResultTree(Stylesheet stylesheetTree, java.lang.Object obj), die Daten auf den Ergebnisbaum ausgeben kann. Die Tatsache, dass all diese Objekte Java-basiert sind, aber aus JavaScript zugänglich sind, ist eine Eigenschaft des Bean Scripting Frameworks, das in bsf.jar enthalten ist.

Das zweite Objekt ist eine Instanz von org.apache.xalan.templates.ElemExtensionCall. Dieses Objekt stellt das Erweiterungselement selbst dar. Aus diesem Element können Sie Attribute und Kindelemente extrahieren, die Ihr Skript benötigt, um die Funktionalität der Erweiterung zu implementieren. Das geschieht mit Hilfe von standardisierten DOM-Funktionsaufrufen wie z.B. getAttribute( ), getFirstChild( ), getLastChild( ) usw.

Es gibt kaum eine Beschränkung bei dem, was Sie mit einem skriptbasierten Erweiterungselement machen können. Sie müssen einfach nur fähig und willens sein, im Quellcode von Xalan-Java und der Dokumentation herumzuwühlen, um herauszufinden, wie Sie es dazu bringen, das zu machen, was Sie wollen. Allerdings sollten Sie skriptbasierte Erweiterungen nur für einfache Aufgaben verwenden, da sie erheblich langsamer sind als native Java-Erweiterungen.

  

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