Zusatzfunktionen und Erweiterungen des XSLT-Prozessors Xalan von Apache

Xalan++

Michael Seemann

Warnung! Der XSLT-Standard beschreibt nur in groben Zügen, wie ein XSLT-Prozessor um zusätzliche Funktionen und Elemente erweitert werden kann. Aus diesem Grund ist die Verwendung dieser Möglichkeiten immer etwas problematisch. Andererseits lassen sich so sehr einfache und schnell umzusetzende Problemlösungen finden, wie der folgende Beitrag zeigen soll.

Die Erweiterungsmöglichkeiten des XSLT-Prozessors lassen sich grob in drei Gruppen
einteilen. Die erste Gruppe dient der Steuerung der Ausgaben. In einer Zweiten
lassen sich die bereits in Xalan enthaltenen Erweiterungselemente und -funktionen
zusammenfassen. Ein mächtiges Programmierwerkzeug pictureet die Möglichkeit, eigene
Erweiterungen zu implementieren. Unabhängig von Xalan – aber dennoch zum Projekt
gehörend – ist der XSLT-Compiler.

Ausgabesteuerung

In der XSLT-Empfehlung sind
insgesamt zehn Attribute für das Element xsl:output vorgesehen. Offensichtlich
reichen diese Steuerungsmöglichkeiten für die Ausgabe aber nicht aus. Es besteht
immer der Bedarf nach weiteren. Um diese zu nutzen, muss zunächst der entsprechende
Namespace im xsl:stylesheet Element gesetzt werden: xmlns:xalan = „http://xml.apache.org/xslt.
Die Bezeichnung xalan ist nicht entscheidend für das Funktionieren, ein
spartanisches x wäre völlig ausreichend. Das Stylesheet wird auf diese
Weise aber übersichtlicher, da sofort sichtbar wird, welcher URL und damit welche
Erweiterungen referenziert werden. Je nach verwendeter Ausgabemethode (xml,
html, text) können jetzt weitere Attribute zum Element xsl:output
hinzugefügt werden. Bei der Verwendung von xml steht xalan:indent-amount
zur Verfügung. Ursprünglich bestand nur die Möglichkeit, festzulegen, ob eine
Einrückung der Ausgabe erfolgen soll. Über diese Erweiterung lässt sich festlegen,
um wie viele Leerzeichen die Kindelemente jeweils eingerückt werden sollen. In
den Quellen zum Artikel befindet sich dazu ein Beispiel. Das Ergebnis lässt sich
über den Kommandozeilenaufruf java org.apache.xalan.xslt.Process -in foo1.xml
-xsl foo1.xsl -out foo_out1.xml
betrachten. Sind die Quelldateien nicht im
aktuellen Verzeichnis, muss die URL-Schreibweise verwendet werden ( file:///).
Standardmäßig ist der Wert für indent-amount auf 0 gesetzt, so dass seine
interne Verwendung gar nicht bemerkt wird. Auch bei der HTML-Ausgabe besteht die
Möglichkeit, das Ausmaß der Einrückung festzulegen. Eine weitere Möglichkeit ist
das Unterdrücken des standardmäßig eingefügten Meta-Tags über xalan:omit-meta-tags=“yes“.
Über xalan:use-url-escaping wird gesteuert, ob Attributwerte (z.B. von
href, action, codebase,) so umgewandelt werden, dass ein
korrekter URL entsteht (URL escaping). Diese Umwandlung ist standardmäßig eingeschaltet,
ist aber nicht immer sinnvoll (siehe Beispiel foo2.xsl). Andere Prozessoren führen
diese Umwandlung nicht automatisch durch, sodass eine generelle Abschaltung dieses
Features durchaus sinnvoll sein kann. Für XSLT-Einsteiger ist nicht immer nachvollziehbar,
warum innerhalb eines Stylesheets die Angabe von zu einem Fehler führt.
Die Angabe von aber zu einer korrekten Ausgabe von im HTML-Text
führt. Die Ursache liegt darin, dass Stylesheets zunächst einmal XML-Dokumente
sind. In diesen sind nur die vier Entitäten & und zulässig.
Wenn der XSLT-Prozessor die HTML-Ausgabedatei erzeugt, werden die Zahlenangaben
in entsprechende HTML-Entität umgewandelt. Xalan verwendet dazu eine Liste (src/org/apache/xalan/serialize/HTMLEntities.res),
die als Default-Wert dem Attribut xalan:entities zugewiesen ist. Sollte
für spezielle Anwendungen eine Entität fehlen, kann sie entweder zu dieser Liste
hinzugefügt werden oder es wird eine eigene Liste erstellt und diese dem Attribut
xalan:entities zugewiesen. Mit diesen Möglichkeiten lässt sich schon einiges
an Chaos stiften. Wem das nicht reicht, der kann auch noch einen Schritt weiter
gehen und die gesamte Ausgabe durch eine eigene Klasse realisieren lassen. Dazu
muss dem Attribut xalan:content-handler diese Methode als Wert übergeben
werden. Standardmäßig verwendet Xalan die Klasse org.apache.xalan.serialize.SerializerToXML
für die Ausgabe von XML-Dokumenten. Für die anderen Ausgabemöglichkeiten existieren
jeweils andere Klassen, die von dieser abgeleitet sind. Mit einem Blick in den
Quelltext ist es sicher kein Problem eine völlig neue Ausgabemethode zu schreiben,
die sämtliche Wünsche umsetzt.

Erweiterungen in XSLT

Bevor wir einen Blick auf die
Erweiterung werfen, soll dargestellt werden, wie Stylesheets trotz der Nutzung
propriäterer Erweiterungen Standardkonform entwickelt werden können. Dazu dienen
die Funktionen element-available, function-available sowie das Element
xsl:fallback. Mit Hilfe der Funktion element-available kann vor
der Verwendung eines bestimmten Elements überprüft werden, ob der Prozessor ein
solches Element unterstützt bzw. ob ein solches Element über einen Erweiterungsmechanismus
verfügbar gemacht wurde. Die Funktion liefert für das Argument xsl:template
mit Sicherheit bei jedem XSLT-Prozessor true zurück. Für ein imaginäres
Element xsl:foo, aber sicher false. Ähnlich arbeitet die Funktion
function-available. Nur, dass diese die Verfügbarkeit von Funktionen, wie
z.B. format-number, überprüft. Folgendes Codebeispiel demonstriert die
Verwendung:


Das Element test:elem1 ist nicht verfügbar!

Die Funktion xsl:anyFunction ist nicht verfügbar!

Die Funktion number() ist verfügbar!

Eine weitere Möglichkeit, auf nicht verfügbare Elemente
zu reagieren, ist das Element xsl:fallback. Dieses wird als Kindelement
eines eventuell nicht verfügbaren Elements notiert. Trifft der Prozessor auf dieses
Element, führt er die Transformation innerhalb des fallback-Elements aus:


eigentliche Transformation

Alternative Transformation

Die Ausgabe der obigen Transformation wäre also „Alternative Transformation“.
Das vollständige Beispiel befindet sich auf der Heft-CD.

Erweiterungselemente

Normalerweise
fügt der XSLT-Prozessor alle Element, die nicht zum Namensraum xsl gehören
in das Ausgabedokument ein. Werden Erweiterungselemente verwendet, führt der Prozessor
eine bestimmte Aktion aus. Liefert die Aktion einen Ergebniswert zurück, wird
dieser an Stelle des Elements in die Ausgabe übernommen. Dabei ist es durchaus
zulässig, dass kein Ergebnis zurückgegeben wird. Dem Prozessor muss als Erstes
mitgeteilt werden, welche Elemente er ausführen soll. Dazu sieht der Standard
das Attribut extension-element-prefixes vor. Der innerhalb dieses Attributs
angegebene Wert enthält eine durch Leerzeichen getrennte Liste aller Namensräume,
die als Erweiterungselemente behandelt werden sollen. Daraus folgt natürlich,
dass ein solcher Namensraum zunächst definiert worden sein muss. Um z.B. die Erweiterungselemente
zur Ausgabeumleitung nutzen zu können, muss also zuerst ein Namespace deklariert
werden: xmlns:redirect = „org.apache.xalan.xslt.extensions.Redirect“. Und
anschließend dieser als Namespace von Erweiterungselementen gekennzeichnet werden:
extension-element-prefixes=“redirect“. Jetzt stehen drei weitere Elemente
zur Verfügung (open, write, close), mit denen die Ausgabe
einer Transformation in Dateien umgeleitet werden kann. Jedes Element kann über
die Attribute file und select gesteuert werden. Mit file
wird ein statischer Ausdruck angegeben, der den Dateinamen angibt. Über select
ist die Auswertung eines XPath-Ausdrucks möglich, so dass die Dateinamen zur Laufzeit
ermittelt werden können. Im einfachsten Fall ist das Element write ausreichend.
Trifft der Prozessor auf dieses Element, öffnet er die angegebene Datei, schreibt
das Ergebnis der Transformation in diese und schließt die Datei wieder. Wenn die
vollständige Kontrolle über diesen Mechanismus gewünscht wird, muss die Datei
explizit mit open geöffnet und mit close wieder geschlossen werden.
Die redirect-Erweiterung lässt sich sehr gut einsetzen, um große XML-Dokumente
(z.B. eine Dokumentation) für die HTML-Ausgabe in kleinere Dokumente zu zerlegen
und bei einer PDF-Ausgabe das gesamte Dokument in einem Stück zu belassen. Eine
interessante Erweiterung stellt das Element pipeDocument dar. Das Element
ist über den Namespace xmlns:pipe=“xalan://PipeDocument“ erreichbar. Diese
Abkürzung kann verwendet werden, da Xalan die entsprechenden Klassen u.a. im Package
org.apache.xalan.lib sucht. Die vollständige Bezeichnung der Klasse lautet
demzufolge org.apache.xalan.lib.PipeDocument. Wie der Name vermuten lässt,
können mit Hilfe dieses Elements mehrere Stylesheets nacheinander für eine Transformation
benutzt werden. Dazu wird den Attributen source und target jeweils
ein Dateiname übergeben und die zu prozessierenden Stylesheets als Kindelement
notiert (

). Interessanter als die Nutzung existierender
Erweiterungen dürfte aber die Entwicklung eigener Erweiterungen sein. Ein ausführliches
Beispiel dazu befindet sich im Kasten. Das Beispiel verwendet Java als Erweiterungssprache
– das ist aber nicht zwingend notwendig. Xalan unterstützt das Bean Scripting
Framework, um Skriptspachen, wie z.B. JavaScript, PerlScript und wenn es sein
muss auch VBScript zu nutzen. Um scriptbasierte Erweiterungen zu schreiben, müssen
zunächst die Klassenbibliothek bsf.jar – sozusagen der Rahmen und eine
Klassenbibliothek, welche die Scriptsprache implementiert (z.B. rhino.jar
für JavaScript), in den Klassenpfad eingefügt werden. Anschließend muss der Namespace
xmlns:lxslt=“http://xml.apache.org/xslt definiert werden, der die entsprechende
Funktionalität zum Aufruf von Scripten zur Verfügung stellt. Für jeden Scriptbereich
muss ein eigener Namespace deklariert werden. Dieser wird zur Liste der extension-element-prefixes
hinzugefügt, wenn der Namensraum auch für Erweiterungselemente verwendet werden
soll. Falls es nicht gewünscht wird, dass Namespacedeklarationen in die Ausgabedokumente
übernommen werden, kann dieses Verhalten unterdrückt werden. Dazu müssen die Deklarationen
als Liste im Attribut exclude-result-prefixes angegeben werden. Die Definition
der eigentlichen Scriptimplementierung erfolgt durch das Element lxslt:component.
Das Attribut prefix stellt die Verknüpfung zum selbst definierten Namensraum
dar. Die Werte der Elemente functions und elements enthalten jeweils
eine Liste der Scriptfunktionen, die als Erweiterungsfunktion bzw. Erweiterungselement
verwendet werden sollen. Einziges Kindelement von lxslt:component ist lxslt:script.
Durch Angabe des Attributs lang wird ähnlich wie in HTML, die zu verwendende
Scriptsprache festgelegt. Um die Verwirrung komplett zu machen, kann auch javaclass
als Sprache eingetragen werden. In diesem Fall muss dann das Attribut src
hinzugefügt werden, um dem Prozessor mitzuteilen, welche Klasse verwendet werden
soll (Beispiel input.xsl). Der Klassenname muss aber nicht vollständig sein. Es
ist ausreichend einen Package-Namen anzugeben und bei der Verwendung der Klasse
sozusagen den fehlenden Teil zu ergänzen. Ein allgemeiner Rahmen für eine Scripterweiterung
würde wie folgt aussehen:

 

An dieser Stelle wird
auch die unterschiedliche Signatur von Erweiterungselementen und -funktionen deutlich.
Einem Element wird immer eine Zugriffsmöglichkeit auf den aktuellen Prozessorkontext
und eine Referenz auf das Element selbst übergeben. Der Prozessorkontext erlaubt
den Zugriff auf den XSLT-Prozessor, das Quelldokument, das Stylesheet und den
aktuellen Kontextknoten. Über das Element kann z.B. auf die Werte der Attribute
des Erweiterungselements zugegriffen werden. Einer Funktion können beliebig viele
Parameter übergeben werden. Die XSLT-Datentypen Node-Set, String
Boolean, Number und Result Tree Fragment werden in ihre entsprechenden
Repräsentationen in der jeweiligen Scriptsprache umgewandelt. Alle anderen Typen
werden ohne Konvertierung an die Funktion übergeben. Für den Rückgabewert gilt
das Gleiche. So ist es z.B. möglich, einem Parameter bzw. einer Variablen im Stylesheet
einen Objekt-Typ zuzuweisen, der nicht zu den XSLT-Datentypen gehört.

Erweiterungsfunktionen

In der Klasse org.apache.xalan.lib.Extensions sind eine Reihe von Erweiterungsfunktionen
zusammengefasst, die über den Namespace http://xml.apache.org/xalan erreichbar
sind. Mit Hilfe der Funktion evaluate können XPath-Ausdrücke zur Laufzeit
berechnet werden, obwohl an der entsprechenden Stelle vom XSLT-Standard keine
Auswertung vorgesehen ist. Ein Beispiel (Verzeichnis ExtensionLib ) macht das
Problem deutlich. Normalerweise besteht keine Möglichkeit, für das Element xsl:sort
zur Laufzeit festzulegen, nach welchem Element/Attribut sortiert werden soll.
Dabei wäre es natürlich ideal, einen Parameter an das Stylesheet zu übergeben,
der festlegt, wonach sortiert werden soll. An dieser Stelle hilft die Funktion
evaluate. Ihr kann der Parameter übergeben werden. Sie berechnet den XPath-Ausdruck
und fügt das Ergebnis an der entsprechenden Stelle ein. Der XSLT-Standard definiert
eine Möglichkeit, einer Variablen einen Teilbaum zuzuweisen. Das funktioniert
aber nur über einen entsprechenden select-Ausdruck. Wenn der Variablen
reiner Text übergeben wird, auch wenn er ein wohlgeformtes Teildokument darstellt,
besteht keine Möglichkeit, über XPath-Ausdrücke auf bestimmte Knoten zuzugreifen.
Dazu muss zuerst eine Umwandlung in eine Knotenmenge erfolgen. Diese Aufgabe übernimmt
die Erweiterungsfunktion nodeset. Funktionen, die ebenfalls im Zusammenhang
mit Knotenmengen stehen, sind intersection (Schnittmenge), difference
(Durchschnitt), distinct (entfernt mehrfach auftretende Knoten) und hasSameNodes
(überprüft, ob zwei NodeSets die gleichen Koten enthalten). Interessant dürfte
auch die Funktion tokenize sein, die eine Zeichenkette zerlegt und ein
NodeSet zurück liefert. Das Begrenzungszeichen kann festgelegt werden.
Als Standard werden die Whitespace-Zeichen verwendet – Tabulator, Zeilenumbruch,
Wagenrücklauf und Leerzeichen.

SQL-Unterstützung

Inzwischen unterstützen fast alle
großen Datenbankhersteller in irgend einer Form die Generierung von XML auf der
Grundlage von Datenbankabfragen. Für einfache Anwendungen ist es aber nicht immer
erforderlich auf diese Möglichkeit zurückzugreifen. Es kann natürlich auch sein,
dass die bevorzugte Datenbank eine solche Funktion (noch) nicht zur Verfügung
stellt. In solchen Fällen bietet die in Xalan eingebaute SQL- Bibliothek trotzdem
eine gute Möglichkeit, mit relativ wenig Aufwand, aus SQL-Datenbanken XML-Dokumente
zu generieren (Verzeichnis SQL). JDBC leistet an dieser Stelle gute Dienste. Am
Deutlichsten wird die Verwendung der SQL-Erweiterung mit Sicherheit an einem Beispiel,
das das generierte XML-Dokument ausgibt (siehe Listing 2).

Als Erstes muss wie gehabt der entsprechende Namespace deklariert werden. Um eine
Verbindung zur Datenbank herzustellen wird die Funktion new aufgerufen,
der als Parameter der Datenbanktreiber und die Datenquelle übergeben wird. Intern
setzt Xalan den Aufruf new() in die entsprechenden Konstruktor-Aufrufe
der Klasse um. Ein Blick in die API-Dokumentation macht deutlich, welche überladenen
Konstruktormethoden zur Verfügung stehen, um auch Nutzername und Passwort mit
zu übergeben. Das Ergebnis – ein XConnection Objekt wird in der XSLT-Variablen
connection zwischengespeichert. Im nächsten Schritt wird die SQL-Abfrage
ausgeführt und in Form eines Teildokuments in der Variablen table gesichert.
Dieses wird dann in das Ausgabedokument eingefügt. Abschließend wird die Verbindung
zur Datenbank wieder geschlossen – sql:close($connection). Das Ergebnisdokument
der Abfrage hat folgendes Format (gekürzt):

Projektverträge in der Praxis
recht

...

Über XPath-Ausdrücke lassen
sich dann bestimmte Knoten transformieren. Um alle Tabellenzeilen auszugeben,
würde der Aufruf eines Templates
lauten. Um den Ressourcenverbrauch möglichst gering zu halten, kann auf jedes
row-Element nur einmal zugegriffen werden. Das liegt daran, dass intern
für jede Ergebniszeile immer das gleiche row-Element verwendet wird. Dieses
Verhalten kann geändert werden, indem die Funktion disableStreamingMode
genutzt wird. Mit enableStreamingMode wird dieser Modus wieder eingeschaltet.
Falls während der Abarbeitung der Erweiterung etwas schief geht, kann über getError
ein Fehlertext ausgegeben werden. Das ist daran zu erkennen, dass die Abfrage
not($connection) true liefert. Eine weitere Verbesserung der Funktionalität
ist die Verwendung von parametrisierbaren Abfragen.

XSLTC

Dass die
Verarbeitung von Stylesheets nicht gerade zu einem Geschwindigkeitsrausch führt,
ist schon durch das Prinzip der Interpretation zur Laufzeit bedingt. Wenn die
Implementierungen eines XSLT-Prozessors dann auch noch eine Performance-Optimierung
vermissen lässt, kann es schon zu Zweifeln am Sinn einer Transformationssprache
kommen. Ursprünglich stammt die Idee von SUN, ein XSLT-Stylesheet in Javabyte-Code
zu übersetzen und diesen, anstelle der eigentlichen XSLT-Dokumente, für die Transformation
zu verwenden. Um ein Stylesheet zu kompilieren, müssen sich die Klassenbibliotheken
xsltc.jar, runtime.jar und BCEL.jar im Klassensuchpfad befinden.
Der Kommandozeilenaufruf java org.apache.xalan.xsltc.compiler.XSLTC stylesheet.xsl
übersetzt dann das Stylesheet und erzeugt eine stylesheet.class-Datei.
Im Sprachgebrauch von XSLTC ist diese Klasse ein Translet. Leider können die oben
beschriebenen Erweiterungen nicht benutzt werden, da der XSLT-Compiler völlig
unabhängig vom eigentlichen Xalan arbeitet. Zur Verwendung des Translets muss
sich die Klassenbibliothek xsltc.jar und das Translet selbst im Klassenpfad
befinden. Über den Kommandozeilenaufruf java org.apache.xalan.xsltc.runtime.DefaultRun
foo.xml stylesheet
erfolgt dann die Transformation des XML-Dokumentes foo.xml.
Parameter werden übergeben, in dem der Name, gefolgt von einem Gleichheitszeichen
und dem Parameterwert, angehängt wird. Mehrere Parameter werden durch Leerzeichen
getrennt. Diese Aufrufvariante ist natürlich etwas umständlich. Sie kann aber
auch über JAXP erfolgen. Es muss nur die gewünschte Implementierung für die TransformerFactory
eingestellt werden. Das erfolgt über System.setProperty. Als key
wird javax.xml.transform.TransformerFactory und als value org.apache.xalan.xsltc.trax.TranformerFactoryImpl
übergeben. Die Verwendung ist dann völlig identisch mit jedem normalen Aufruf
einer Transformation. Entscheidend ist aber, dass der Aufruf von Templates
translet = tFactory.newTemplates(new StreamSource(xslInUrI))
dazu führt, dass
das Stylesheet kompiliert wird und so für die spätere Verwendung zur Verfügung
steht. Der Vorteil ergibt sich natürlich erst, wenn mehrere Transformationen nacheinander
mit dem gleichen Translet durchgeführt werden. Es ist aber auch problemlos möglich,
ein bereits kompiliertes Stylesheet zu benutzen. Die Geschwindigkeitsvorteile
können erheblich sein. Insbesondere der erste Aufruf kann die Zeit für die Transformation
eines Dokuments gut um die Hälfte verkürzen. Das ist natürlich von einigen Nebenbedingungen
abhängig. So ist sicher nachvollziehbar, dass das Größenverhältnis und die Komplexität
von Stylesheet und XML-Dokument einen relativ großen Einfluss auf die Transformationsgeschwindigkeit
haben. Problematisch bei der Verwendung von Translets ist die noch nicht vollständige
Umsetzung des XSLT-Standard.

Warum nicht?

Die Verwendung propriäterer Erweiterungen
ist immer etwas problematisch. Da eine durchgehende Standardisierung fehlt, können
Stylesheets, die auf diese Art von Erweiterungen bauen, im Allgemeinen. nicht
mit anderen Prozessoren verwendet werden. Trotzdem bieten sie sehr gute Möglichkeiten,
den Umgang mit Stylesheets zu vereinfachen, für etwas mehr Geschwindigkeit zu
sorgen, die XSLT-Dokumente übersichtlicher zu gestalten und fehlende Funktionen
sehr einfach hinzuzufügen. Wenn die Entscheidung für Xalan aber gefallen ist,
gibt es keinen Grund, auf diese Vorteile zu verzichten.

Entwicklung eigener Erweiterungen

Wenn
XML-Dokumente in HTML-Formulare transformiert werden, sollen auch Auswahlfelder
(≶select name=“select“>) nicht fehlen. Die zur Verfügung stehenden Optionen sollen natürlich nicht statisch im Stylesheet angegeben, sondern u.U. aus einem anderen XML-Dokument ermittelt werden. Eventuell muss sogar ein bestimmter Wert vorselektiert werden.
In einem ersten Lösungsansatz könnte dieses Problem durch ein Template gelöst werden, dass mit entsprechenden Parametern aufgerufen wird (SpeedTest1.xsl). Diese Variante wäre insofern von Vorteil, da sie mit hoher Wahrscheinlichkeit von allen XSLT-Prozessoren unterstützt wird. Es geht aber eleganter.
Ein weiterer Lösungsansatz (Methode optionList der Klasse HTMLForms) könnte eine eigene Funktion entwickeln, welche den aktuellen Wert als String und die Auswahlliste als NodeList übergeben bekommt. Das Ergebnis der Funktion ist ein String, den der Prozessor in das Ausgabedokument einfügt. Die Verwendung der Funktion demonstriert das Stylesheet input.xsl:

Das Problem ist, dass bestimmte Zeichen bei der Ausgabe in ihre Entitäten umgewandelt werden. Deshalb muss man dem Element xsl:value-of das Attribut disable-output-escaping=“yes“ hinzufügen. Dann erfolgt die Ausgabe von und nicht im Ausgabedokument.
Das Problem lässt sich dadurch umgehen, dass die Funktion keine Zeichenkette, sondern einen Knoten in das Ausgabedokument einfügt. In diesem Fall muss allerdings xsl:copy-of select=““ verwendet werden und nicht xsl:value-of select=““. Die Methode zur Umsetzung sieht folgendermaßen aus:

public Node optionList2(String currOption, NodeList nl){
Document doc= new DocumentImpl();
Element option = null;
Node currNode = null;
Element select = doc.createElement( "select" );
for (int i=0; i

Es wird zunächst ein leeres XML-Dokument benötigt, um die entsprechenden Knoten zu erzeugen. Anschließend werden zum select-Tag alle Kinder der NodeList als option-Elemente an dieses Tag gehängt. Die Vorauswahl eines bestimmten Wertes wird dadurch erreicht, dass an der gewünschten Position das Attribut selected hinzugefügt wird. Abschließend wird das Element select an den aufrufenden XSLT-Prozessor zurückgegeben, sodass dieser den Knoten in das Ausgabedokument einfügen kann.
Von Vorteil bei der Nutzung von Funktionserweiterungen ist die Tatsache, dass die Funktionsparameter vor der Übergabe ausgewertet werden und gegebenenfalls eine Typumwandlung erfolgt. Bei Erweiterungselementen ist das nicht der Fall, dort werden die Attributwerte immer als Zeichenketten übergeben. Der Entwickler muss sich also selbst darum kümmern, die richtigen Werte auszulesen.
Ein Geschwindigkeitstest zeigt, dass die erste Variante schneller ist. Das liegt daran, dass im ersten Fall eine Zeichenkette und im zweiten zunächst ein Teilbaum erzeugt wird, der dann in das Ausgabeelement eingefügt wird. Auch bei der Umwandlung des Ausgabebaums in eine Textausgabe ist die erste Variante natürlich schneller.
Die Verwendung von Erweiterungsfunktionen löst aber noch nicht alle Probleme. So ist die Anzahl der Parameter immer festgelegt. Es können z.B. keine weiteren Attribute zum select-Tag hinzugefügt werden, die z.B. die Anzahl der sichtbaren Auswahlmöglichkeiten (size-Attribut) festlegen. Eventuell besteht auch der Bedarf, ein style-Attribut hinzuzufügen oder Ähnliches. Deshalb soll die HTML-Combobox durch ein Erweiterungselement realisiert werden (Methode comboBox der Klasse HTMLForms). Die gesamte Methode ist für einen Abdruck an dieser Stelle etwas umfangreich – deshalb werden hier nur die interessantesten Teile dargestellt:

public String comboBox(org.apache.xalan.extensions.XSLProcessorContext context,
ElemExtensionCall extElem)
throws javax.xml.transform.TransformerException,org.xml.sax.SAXException{

org.apache.xpath.XPathContext xctxt = context.getTransformer().getXPathContext();
ResultTreeHandler rth = context.getTransformer().getResultTreeHandler();
rth.processingInstruction(javax.xml.transform.Result.PI_DISABLE_OUTPUT_ESCAPING, "");

String size = extElem.getAttribute( "size" );

String currOption = "";
...
NodeList boxItems = null;
String boxItemPath = extElem.getAttribute ( "select" );
if(null != boxItemPath){
XPath myxpath = new XPath(boxItemPath, extElem, xctxt.getNamespaceContext(), XPath.SELECT);
XObject xobj = myxpath.execute(xctxt, context.getContextNode(), extElem);
boxItems = xobj.nodelist();
}

StringBuffer result =  new StringBuffer().append("");

return result.toString();
}

Innerhalb des Stylesheets erfolgt die Nutzung des Elements folgendermaßen:
. Damit ist auch schon ersichtlich, dass der XSLT-Prozessor die Attributwerte nicht auswerten kann. Trifft der Prozessor auf dieses Element, ruft er die Methode comboBox auf und übergibt ein Objekt namens XSLProcessorContext und das Element selbst.
Um die bessere Performance bei der Generierung einer Zeichenkette zu nutzen, arbeitet auch das Erweiterungselement nach diesem Prinzip. Dazu muss dem Prozessor mitgeteilt werden, dass die Ausgabe nicht umgewandelt werden soll. Für das Element xsl:value-of steht dazu das Attribut disable-output-escaping=“yes“ zur Verfügung. Diese Möglichkeit besteht an dieser Stelle nicht. Deshalb muss diese Aufgabe von der Methode comboBox realisiert werden. Dazu wird auf das Transformer-Objekt zugegriffen und für den ResultTreeHandler die nötige ProcessingInstruction gesetzt:

rth.processingInstruction( javax.xml.transform.Result.PI_DISABLE_OUTPUT_ESCAPING , "" )

Als Nächstes erfolgt der Zugriff auf die Attributwerte des Elements. Sind die Attributwerte als Zeichenketten zu interpretieren, ist das Problem in einer Codezeile erschlagen – es ist nur die Methode getAttribute() des Erweiterungselementes aufzurufen. Problematischer ist die Ermittlung von Parametern, die sich durch die Auswertung von Ausdrücken erst zur Laufzeit ergeben. Im Beispiel ist das der aktuelle Wert des Elements currOption und die NodeList, welche die darzustellenden Optionen enthält. Dazu wird über das Objekt XSLProcessorContext Zugriff auf den aktuellen XPathContext genommen und der gewünschte Ausdruck ausgewertet. Das Ergebnis liegt dann als XObject vor, das in den gewünschten Typ umgewandelt werden kann (xobj.nodelist()). Der restliche Code ist aus den anderen Methoden schon bekannt. Es wird wieder ein select-Tag erzeugt und dieses in das Ausgabedokument eingefügt. Eine weitere Verbesserung ist die Verwendung eines StringBuffers anstelle des Datentyps String.
Zusätzliche Attribute können problemlos hinzugefügt werden und trotzdem hinterlässt das Stylesheet einen aufgeräumten Eindruck. Insbesondere ist der Zweck intuitiv ersichtlich, wie ein Blick in das Stylesheet input2.xsl hoffentlich zeigt. Eine höhere Geschwindigkeit ist ebenfalls feststellbar. Allerdings nicht unbedingt beim ersten Aufruf, da hier die Zeit für das Instanzieren der Erweiterungsklasse hinzukommt.

Geschrieben von
Michael Seemann
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.