![]() |
|
URL dieses Artikels:
zu Ausgabe:
02.2002
Xalan++
Zusatzfunktionen und Erweiterungen des XSLT-Prozessors Xalan von Apache
von 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 bildet 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: <xsl:if test="not(element-available('test:elem1'))">
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: <test:elem2>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 ( <xsl:stylesheetAn 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). Listing 2 <?xml version="1.0" encoding="ISO-8859-1" ?>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): <sql>Über XPath-Ausdrücke lassen sich dann bestimmte Knoten transformieren. Um alle Tabellenzeilen auszugeben, würde der Aufruf eines Templates 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: <xsl:value-of select="HTMLF:optionList(currOption, document('options.xml')/Options/item)" disable-output-escaping="yes"/>
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){
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: Listing 3public String comboBox(org.apache.xalan.extensions.XSLProcessorContext context,Innerhalb des Stylesheets erfolgt die Nutzung des Elements folgendermaßen: 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. |
||
|