Archiv für Kategorie XSLT und XPath
XSLT-SB: asin(), acos(), atan(), atan2(), dynamische Typung
Verfasst von Stf unter Allgemeines, XSLT-SB am 27. Juni 2011
Mit dem Sprung auf Version 0.2.37 kann die XSLT-SB, genauer math.xsl, jetzt auch die Arkusfunktionen und dynamische Typung. In Ergänzung der knappen Release-Notes hier ein paar Anmerkungen dazu.
Arkusfunktionen
Die Gruppe der Arkusfunktion lässt sich einfach auf der Grundlage von atan() implementieren – wenn man den richtigen Ansatz gefunden hat. Meine ersten Versuche, asin() und atan() über Taylor-Reihen zu implementierten scheiterten daran, dass diese Reihen zu langsam konvergieren. Zusammen mit den in der Reihenbildung verwendeten rekursiven Funktionen (Fakultät und Potenz) war schnell das Limit der Rekursionstiefe erreicht: Saxon bricht die Verarbeitung mit Hinweis auf das Überschreiten der maximalen Rekursionstiefe ab.
Die Lösung brachte ein alternativer Algorithmus zur Berechnung von atan(), den ich bei WolframMathWorld fand (Gleichungen Nr. 44 bis 48). Mittels des Arkustangens lässt sich einfach der Arkussinus und Arkuskosinus berechnen, so dass diese Implementierung ein Kinderspiel war (siehe Wikipedia).
dynamische Typung
Zweites großes Thema der Überarbeitung von math.xsl war die dynamische Typung. Die mathematischen Operatoren und Funktionen in XPath geben ihr Ergebnis i.d.R. mit dem Typ der Argumente zurück. Zum Beispiel ist das Ergebnis von fn:round-half-to-even() mit einem Argument vom Typ xs:decimal vom Typ xs:decimal, während mit einem xs:double-Argument das Ergebnis vom Typ xs:double ist.
Die Implementierung dieses Verhaltens barg einige Schwierigkeiten, weil manche Funktionen als Ergebnis NaN, -INF oder INF zurückgeben können. Diese speziellen Werte sind zwar mit den Typen xs:float und xs:double darstellbar, nicht aber mit xs:decimal (das aber genauere Ergebnisse als xs:double liefert) und xs:integer. Das gewünschte Verhalten ist wohl sehr vom Einsatzzweck der Funktionen abhängig. Der jetzige Kompromiss ist daher, dass bei diesen Werten der Cast von NaN, -INF oder INF auf ungeeignete Typen scheitert; wer Wert auf hohe Genauigkeit legt, kann vielleicht auch die kritischen Werte umschiffen.
Genauigkeit
Laut Standard müssen XPath-Implementierungen mit 18 signifikanten Stellen rechnen. Für die XSLT-SB habe ich mittels intern:round() die Genauigkeit auf 16 Stellen begrenzt, weil damit die meisten Tests erfolgreich absolviert werden. Trotzdem wird für manche Testwerte (etwa exp(100)) nicht das richtige Ergebnis ermittelt (bei vielen Berechnungsschritten summieren sich die Fehler halt). In diesen Fällen wird im Testprotokoll (hier z.B. für Saxon EE) eine Warnung (orange hinterlegt) ausgegeben. Diese sind zu unterscheiden von den gelb hinterlegten Fällen, bei denen ein Cast der Ergebnisse auf xs:decimal (den Typ der Tests) nicht möglich ist, siehe oben. In den Tests nicht berücksichtigt sind die Fälle, für die eine Funktion nicht definiert ist, etwa asin(3).
Intern (d.h. bei den Funktionen mit intern:-Prefix) wird mit einer höhren Stellenzahl gerechnet, diese müssen aber weder signifikant noch richtig sein.
XSLT-SB – eine Standard-Bibliothek für XSLT
Verfasst von Stf unter Beispiele, XSLT und XPath, XSLT-SB am 14. Mai 2011
Es ist vollbracht. Nach ein paar Wochenenden mit Feinschliff und letzten Test habe ich Version 0.2 von XSLT-SB – einer Standard-Bibliothek für XSLT – veröffentlicht.
Was ist XSLT-SB?
Die XSLT-Standard-Bibliothek (XSLT-SB) beinhaltet nützliche, immer wieder gebrauchte Funktionen und Templates. Gleichzeitig dient sie als beispielhafte Implementierung bestimmter Techniken. Sie wendet sich als Beispielsammlung vor allem an deutschsprachige Entwickler, um für diese die Einstiegshürden zu senken.
Die XSLT-SB hat zwei Quellen: einerseits habe ich zeitig angefangen, immer wieder gebrauchte Funktionen und Templates in produktive Bibliothek-Stylesheets auszulagern. Für die XSLT-SB habe ich einige davon übernommen. Beispiele dafür sind xsb:force-cast-to-integer() und xsb:parse-string-to-boolean() sowie die Grundlagen des Logging-Systems. Andererseits habe ich aus Spaß (oder so) mal die eine oder andere Funktion implementiert, bspw. ist files.xsl wesentlich umfangreicher ausgefallen, als es für die eigentliche Aufgabe notwendig gewesen wäre.
Templates und Funktionen der XSLT-SB entstanden also nicht systematisch, sondern nach Bedarf oder Interesse. Im besonderen habe ich nicht versucht, bestehende Bibliotheken wie EXSLT zu ersetzen. Deshalb kann die XSLT-SB mit Fug und Recht als lückenhaft bezeichnet werden.
Ich habe die XSLT-SB in einigen kleineren Projekten produktiv eingesetzt, aber der dauernde Härtetest steht noch aus. Außerdem sind die Stylesheets durch Dokumentation und Tests recht umfangreich; und ich habe sie auch nicht auf eine hohe Ausführungsgeschwindigkeit optimiert. Deshalb möchte ich heute von einem produktiven Einsatz abraten, aber selbstverständlich können einzelne Templates oder Funktionen gezielt in eigene Projekte übernommen werden. Die Veröffentlichung der Stylesheets macht einen breiteren Einsatz möglich, und ich freue mich auf das Feedback. Abhängig davon mag sie sich in die eine oder andere Richtung entwickeln – mehr Beispielsammlung oder mehr produktive Bibliothek.
Drei Besonderheiten der XSLT-SB möchte ich hervorheben: files.xsl, das Logging-System und die Testumgebung.
files.xsl
files.xsl bündelt Funktionen rund um URLs. Da xs:anyURI kaum geprüft wird, habe ich die Regeln von RFC 1808 (URL) in diverse String-Tests gegossen und darauf aufbauend Funktionen zum Ermitteln von Dateiname, Dateipfad, Dateierweiterung usw. entwickelt. Ergänzt wird das Stylesheet durch Funktionen wie xsb:file-exists() und xsb:mediatype-from-url().
Das Stylesheet demonstriert einige spezielle XML- und XSLT-Techniken, etwa benannte Entities für lesbare reguläre Ausdrücke, von Systemeigenschaften abhängige Funktionen (mit use-when) und die Verwendung von Java-Funktionen.
Logging-System
Die XSLT-SB implementiert ein konfigurierbares Logging-System, um Nachrichten des Stylesheets während der Verarbeitung einfach und flexibel auszugeben. Meldungen können per xsl:message oder (soweit der Rückgabetyp einer Funktion oder eines Templates das zulässt) als Kommentar, XML-Element oder HTML ausgegeben werden, unterschiedliche Dringlichkeitsstufen werden unterstützt. Die XSLT-SB nutzt das Logging-System intensiv für die Selbsttests der Funktionen, für den Einstieg lohnt ein Blick auf xsb:internals.Error bzw. xsb:internals.FunctionError in internals.xsl.
Testumgebung
Für Funktionstest habe ich eine Testumgebung entwickelt (siehe internals.testing.xsl). Tests werden in Templates zusammengefasst, die im Stylesheet selbst oder in externen Teststylesheets abgelegt werden können und per initialem Mode oder initialem Template aufgerufen werden. Einige Funktionen und Templates helfen beim Vergleich von erwarteten und berechneten Werten und kümmern sich um die Protokollierung. Interessanterweise haben mir die Test nicht nur beim nachträglichen Absichern der Stylesheets geholfen, sondern ich bin relativ schnell auf eine testgetriebene Entwicklung umgestiegen. Diesen Aspekt möchte ich in meiner täglichen Arbeit nicht mehr missen.
Die Testumgebung wird durch formale Tests der Stylesheets selbst ergänzt (internals.stylecheck.xsl, Template intern:internals.Stylecheck). Sie warnen bei fehlender Typung von Variablen, Parametern und Funktionen, fehlender Dokumentation u.a. und listen ToDos.
Ein Beispiel für absolvierte Tests und den Stylecheck, ausgegeben über das Logging-System als HTML, sind die Testergebnisse für files.xml unter Saxon-HE.
Wie kann die XSLT-SB benutzt werden?
Wie ich oben schrieb, kann ich die XSLT-SB im Moment nicht für den produktiven Einsatz empfehlen. Wer es trotzdem wagen möchte, kann sich die Stylesheets herunterladen und in eigene Projekte einbinden. Ein neues Projekt kann einfach auf der Grundlage von pattern+includes.xsl begonnen werden. Natürlich kann man die Stylesheets auch zum Nachschauen oder für Kopieren & Einfügen verwenden.
Die Verwendung der meisten Funktionen sollte selbsterklärend sein, für die Logging- und Testumgebung können die XSLT-SB-Stylesheets als Beispiel herangezogen werden. Die Stylesheets sind dokumentiert; eine HTML-Version der Dokumentation liegt im doc-Verzeichnis der Distribution und – meist aktueller – online.
Lizenz
Die Stylesheets und das Drumherum sind dual lizenziert: EXPAT (MIT) für den Einsatz als Software und CC-by 3.0, so dass einer Verwendung keine rechtlichen Hürden im Weg stehen sollten.
Was kommt als nächstes?
Das hängt vom Feedback ab – oder von überraschenden neuen Projekten. Auszug aus meiner ToDo-List:
auf Intel SOAE zum laufen bringen, im Moment stürzt dieser Prozessor einfach ab[Nachtrag: Ich habe das Problem eingegrenzt und im Intel-Forum dargestellt. In neueren Versionen des Prozessors tritt es wohl nicht mehr auf, allerdings plant Intel keine Veröffentlichung einer neuen Version, siehe hier. Damit sind mir hier wohl die Hände gebunden …]- Dokumentation verbessern, z.B. Liste der Funktionen mit Kurzbeschreibung erstellen. [Nachtrag: Im Projektwiki gibt es jetzt aus den Stylesheets heraus generierte Übersichten.]
- zusätzliche Funktionen implementieren, bspw. habe ich gerade wieder mal das
p:directory-listaus XProc vermisst - Kompakt-Distribution ohne geschwätzige Kommentare und Dokumentation erzeugen, um die Startgeschwindigkeit zu erhöhen
Links
Ich habe das Projekt bei Google-Code eingestellt, dort gibt es sowohl ein SVN-Repository als auch fertige Distributionen, die gelegentlich dem aktuellen Entwicklungsstand hinterherhinken können. Auf den Expedimentum-Seiten gibt es ein aktuelles Checkout aus dem Trunk, hier kann man auch online die Dokumentation einsehen. Kommentare und Fehlermeldungen sollten über die Google-Seiten laufen.
- Download: http://code.google.com/p/xslt-sb/downloads/list
- Projektseite bei Google Code: http://code.google.com/p/xslt-sb/
- aktuelles Checkout: http://www.expedimentum.org/example/xslt/xslt-sb/
Apache Ant: Initialen Mode und Initiales Template für XSLT 2.0 mit Saxon setzen
Verfasst von Stf unter Apache Ant, XSLT und XPath am 2. April 2011
Ein schönes Feature von XSLT 2.0 ist, dass man ein Stylesheet in einem initialen Mode oder mit einem initialen Template starten kann (siehe Standard). Ich benutze diese Möglichkeit gern, um Selbsttests direkt im Stylesheet unterzubringen und über einen initialen Mode auszuführen – ohne die eigentliche Logik des Stylesheets zu beeinflussen.
In OxygenXML kann man den initialen Mode oder das initiale Template prima im Transformationsszenario einstellen, der kleine Button mit dem rasenden Zahnrädchen neben der Auswahlliste für die Transformations-Engine öffnet den passenden Einstellungs-Dialog (siehe Dokumentation).
Schon vor geraumer Zeit wollte ich diese Feature auch mit Apache Ant nutzen, bin aber wegen fehlender Unterstützung durch Saxon und einen zwischenzeitlichen Ant-Bug in Version 1.8.1 nicht weitergekommen. Inzwischen wurden Saxon und Ant aktualisiert, so dass es Zeit für einen neuen Anlauf war. Die größte Herausforderung war, die jeweils passende URI für die beiden Features zu finden, letztendlich habe ich in die Saxon-Quelltexte geschaut. So geht’s:
<target name="test"> <xslt in="input.xml" out="output.xml" style="stylesheet.xsl"> <!-- Pfad zu Saxon an lokale Installation anpassen! --> <classpath location="saxon9he.jar" /> <factory name="net.sf.saxon.TransformerFactoryImpl"> <!-- hier ggfs. "http://saxon.sf.net/feature/initialTemplate" einsetzen --> <attribute name="http://saxon.sf.net/feature/initialMode" value="MyMode"/> </factory> </xslt> </target>
Über die Factory-Attribute können auch viele weitere Saxon-Optionen – die oft auch über die Kommandozeile zu erreichen sind – von Ant aus gesteuert werden, etwa der Umgang mit Whitespace (http://saxon.sf.net/feature/strip-whitespace) oder die Zeilennummerierung (http://saxon.sf.net/feature/linenumbering). Das habe ich allerdings nicht getestet.
Quellen:
- http://ant.apache.org/manual/Tasks/style.html (unter »factory (‘trax’ processors only)« und »Using factory settings«)
- http://saxonica.com/documentation/configuration/config-interfaces/jaxp-configuration.xml
- http://saxonica.com/documentation/javadoc/constant-values.html in Verbindung mit http://saxonica.com/documentation/javadoc/net/sf/saxon/TransformerFactoryImpl.html und http://saxonica.com/documentation/javadoc/net/sf/saxon/lib/FeatureKeys.html
XSLT/XPath: arithmetische Operationen mit einer Leersequenz
Verfasst von Stf unter XSLT und XPath am 17. Juli 2010
Neues aus der Serie »Man lernt nie aus…«: Ist bei arithmetischen Operationen einer der Operanden die empty sequence, so ist das Ergebnis bei XSLT/XPath 1.0 NaN (»not a number«), bei XSLT/XPath 2.0 aber die empty sequence. Dieses Verhalten ist im XPath-Standard unter 3.4 Arithmetic Expressions definiert.
Ich stand vor dem Problem, eine Variable, die entweder eine xs:decimal-Zahl oder aber auch die Leersequenz enthält, in eine gültige Instanz von xs:decimal umzuwandeln, d.h. statt der Leersequenz sollte 0 geliefert werden. Es ist gängige Programmiertechnik, eine Leersequenz durch Anhängen eines Leerstrings – z.B. xs:string( ( (), '') ) – in eine gültige Instanz von xs:string (d.h. einen Leerstring) umzuwandeln. Die analoge Technik – Addieren einer Null zu einer Leersequenz – funktioniert aber wegen des im Standard definierten Verhaltens nicht. Abhilfe schafft bei XSLT 2.0 eine spezielle Funktion:
<xsl:function name="xsb:force-cast-to-integer" as="xs:decimal"> <xsl:param name="input" as="xs:string?"/> <xsl:choose> <xsl:when test="$input castable as xs:decimal"> <xsl:sequence select="xs:decimal($input)"/> </xsl:when> <xsl:otherwise> <xsl:sequence select="0"/> </xsl:otherwise> </xsl:choose> </xsl:function>
oder kürzer:
<xsl:function name="xsb:force-cast-to-integer" as="xs:decimal"> <xsl:param name="input" as="xs:string?"/> <xsl:sequence select="if ($input castable as xs:decimal) then xs:decimal($input) else 0"/> </xsl:function>
Außer bei Leerstring und Leersequenz gibt diese Funktion auch bei nicht konvertierbaren Strings (wie z.B. römischen Zahlen) 0 zurück (was bei mir häufig das gewünschte Verhalten ist), aber die Beschränkung auf Leerstring und Leersequenz lässt sich einfach durch Ersetzen von $input castable as xs:decimal mit normalize-space($input) erzielen.
Nachtrag:
Thomas Meinicke merkte zum Rechnen mit Leersequenzen an, dass man zuerst mit exists() oder empty() prüfen kann, ob eine Sequenz leer ist, um dann den resultierenden Wahrheitswert in xs:decimal zu casten, etwa so: xs:decimal(exists( () ) ). Achtung: Dabei wird die Leersequenz zu 0, während der Leerstring zu 1 wird.
Nachtrag II:
Leersequenzen führen auch bei Vergleichs-Operationen zur Rückgabe einer Leersequenz, siehe im XPath-Standard unter 3.5.1 Value Comparisons. Da Leersequenzen zu false() evaluiert werden, ergibt beispielsweise () eq () immer false(). Um zu testen, ob eine Leersequenz vorliegt, muss deshalb empty() oder exist() verwendet werden.
Java in XSLT verwenden
Verfasst von Stf unter Java, XSLT und XPath am 9. Juli 2010
Es gibt immer wieder Programmieraufgaben, bei denen XSLT allein zur Lösung nicht ausreicht. In diesen Fällen kann – vorausgesetzt, dass der XSLT-Prozessor mitspielt – die Einbindung von Java helfen. XSLT bietet dafür zwei Mechanismen: Erweiterungsfunktionen (Extension Functions) und Erweiterungsbefehle (Extension Instructions, in XSLT 1.0 als Extension Elements bezeichnet). Im Folgenden werden nur Erweiterungsfunktionen betrachtet.
Nutzung von Standard-Java-Methoden
In einem früheren Post habe ich ein Verfahren beschrieben, um mit Saxon-Erweiterungsfunktionen die Existenz einer Datei zu testen. Natürlich lässt sich diese Aufgabe auch mit Java erledigen:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:test="something" xmlns:java-file="java:java.io.File" xmlns:java-uri="java:java.net.URI"> <xsl:function name="test:file-exists" as="xs:boolean"> <xsl:param name="fileURL" as="xs:string"/> <xsl:sequence select="java-file:exists(java-file:new(java-uri:new($fileURL)))"/> </xsl:function> </xsl:stylesheet>
Die Funktion test:file-exists nutzt die Methode exists() aus der Klasse java.io.File. Dazu wird in der xsl:stylesheet-Deklaration der Namespace java.io.File (der Klassenname) definiert und an das Präfix java-file gebunden. In der Zeile <xsl:sequence ... wird dann mit java-file:new(...) eine neue Instanz erzeugt und mit java-file:exists(...) die Methode aufgerufen. Die merkwürdige Syntax ist dem Umstand geschuldet, dass der Aufruf von Java wie eine XPath-Funktion aussehen soll, um problemlos mit XSLT verarbeitet werden zu können.
Die Klasse java.net.URI verwende ich zusätzlich, um den übergebenen String auf Gültigkeit zu überprüfen – bei einem ungültigen String schlägt schon die Erzeugung der URI fehl. Zudem erzwingt der Konstruktor von java.net.URI die Angabe einer absoluten URI, was in der Praxis hilft, Programmierfehler zu vermeiden. Relative Pfade lassen sich einfach mit der XPath-Funktion resolve-uri() – ggfs. in Verbindung mit base-uri() – in absolute Pfade umwandeln. Die Typung des Funktionsparameters fileURL als xs:anyURI würde hier übrigens nicht wirklich weiter helfen, da xs:anyURI per Standard nur recht schwache Gültigkeitskriterien hat. Wenn dieser zusätzliche Test nicht benötigt wird, kann das File-Objekt aber auch direkt aus dem Funktionsparameter erzeugt werden.
Das vollständige Stylesheet steht in der Beispielsammlung zum Download bereit.
Voraussetzungen und Konfiguration
Um Java in XSLT verwenden zu können, muss der jeweilige XSLT-Prozessor dies unterstützen. Nachdem Michael Kay neue Saxon-Pakete zusammengestellt hat, bietet sich folgendes Bild: Saxon-B und Saxon-SA bis einschließlich Version 9.1 unterstützen die Einbindung von Java-Funktionen, ab Version 9.2 funktioniert dies nur noch mit den kostenpflichtigen PE- und EE-Versionen. Bei AltovaXML hatte ich mit dem Build vom 23.10.2009 sowie der Version 2010 rel. 3 sp 1 Erfolg, allerdings musste ich nach der Fehlermeldung »JVM dll kann nicht geladen werden« bzw. »Can’t load JVM DLL« erst ein JDK installieren und die Path-Umgebungsvariable um das bin-Verzeichnis im JDK ergänzen.
Ein weiteres Beispiel
Im letzten Blog-Beitrag wurde die Ausgabe von Binärdaten in eine Datei angesprochen. Eine erste Java-Lösung sieht so aus:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:test="something" xmlns:java-file="java:java.io.File" xmlns:java-fos="java:java.io.FileOutputStream" xmlns:java-uri="java:java.net.URI" version="2.0"> <xsl:function name="test:write-hexBinary-to-file"> <xsl:param name="hexBin" as="xs:hexBinary?"/> <xsl:param name="OutputFileURI" as="xs:anyURI"/> <xsl:variable name="fostream" select="java-fos:new(java-file:new(java-uri:new($OutputFileURI) ) )"/> <xsl:value-of select="java-fos:write($fostream, test:tokenize-HexBinary($hexBin) )"/> </xsl:function> […] </xsl:stylesheet>
Zuerst wird ein FileOutputStream-Objekt erzeugt und einer Variablen zugewiesen. Danach wird die write()-Methode des Objektes aufgerufen. Interessant ist die Syntax dieses Methodenaufrufes: der scheinbaren Funktion java-fos:write() wird als erstes Argument das Objekt, dessen Methode aufgerufen werden soll, übergeben.
Die Umwandlung des HexBin-Strings in eine Sequenz von Bytes habe ich in die XSLT-Funktion test:tokenize-HexBinary samt zweier Hilfsfunktionen ausgelagert, die Details lassen sich im Beispiel-Stylesheet nachschlagen.
Einbindung externer Klassen
Das FileOutputStream-Beispiel funktioniert zwar, aber es ist eine ziemliche XSLT-Akrobatik für das Umwandeln von HexBinary in Bytes notwendig. Wenn man schon Java benutzt, kann man das Problem auch gleich ganz an Java delegieren. Unter Verwendung einer Lösung von Dave L. habe ich diese Klasse geschrieben:
package org.expedimentum.example.java; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; public class HexBinaryFileWriter { public void write (String hexbinary, File file) throws FileNotFoundException, IOException { FileOutputStream fos = new FileOutputStream(file); for (int i = 0; i < hexbinary.length(); i += 2) { fos.write( (Character.digit(hexbinary.charAt(i), 16) << 4) + Character.digit(hexbinary.charAt(i+1), 16)); } } }
Diese Klasse ist eine ganz normale Java-Klasse, sie beinhaltet keinen XSLT-bezogenen Code. Sie kann nun wie in den obigen Beispielen beschrieben in ein Stylesheet eingebunden werden:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:test="something" xmlns:java-hbfw="java:org.expedimentum.example.java.HexBinaryFileWriter" xmlns:java-file="java:java.io.File" xmlns:java-uri="java:java.net.URI" version="2.0"> <xsl:function name="test:write-hexBinary-to-file"> <xsl:param name="hexBin" as="xs:hexBinary?"/> <xsl:param name="OutputFileURI" as="xs:anyURI"/> <xsl:variable name="HexBinaryFileWriter" select="java-hbfw:new()"/> <xsl:value-of select="java-hbfw:write($HexBinaryFileWriter, string($hexBin), java-file:new(java-uri:new($OutputFileURI) ) )"/> </xsl:function> </xsl:stylesheet>
Unter <OxygenXML/> und Saxon muss das jar-File mit der HexBinaryFileWriter-Klasse im CLASSPATH liegen. Bei Saxon darf die Transformation nicht mit java -jar ... gestartet werden, vielmehr muss der Aufruf über java -cp ... net.sf.saxon.Transform erfolgen. AltovaXML wünscht die Übergabe des Pfades zum jar in der Deklaration des Namespace, was dann bspw. so aussehen könnte: xmlns:java-hbfw="java:org.expedimentum.example.java.HexBinaryFileWriter?path=jar:file:///C:/example/Java/dist/Beispiele.jar!/".
Der Quelltext der Klasse und ein jar liegen in der Beispielsammlung, ebenso das vollständige Stylesheet.
Noch ein Tipp zum Schluss: einfacher als das Schreiben, Einbinden und Warten von Java-Erweiterungen dürfte in vielen Fällen die Nutzung prozessorspezifischer Erweiterungsfunktionen – wie sie z.B. Saxon und AltovaXML anbieten – oder von XSLT-Bibliotheken wie EXSLT sein.
Weblinks
- XSL Transformations (XSLT) Version 2.0. 18 Extensibility and Fallback – http://www.w3.org/TR/xslt20/#extension (englisch)
- XSLT blooms with Java. Use Java in your stylesheets when XSLT won’t do the trick – http://www.javaworld.com/javaworld/jw-12-2001/jw-1221-xslt.html?page=1 (englisch)
- Extending XSLT with Java – http://cafeconleche.org/books/xmljava/chapters/ch17s03.html (englisch)
- Saxonica: XSLT and XQuery Processing: Writing reflexive extension functions in Java – http://www.saxonica.com/documentation/extensibility/functions.html (englisch)
- AltovaXML 2010: Java Extension Functions – http://manual.altova.com/AltovaXML/index.html?xextjava.htm (englisch)
Binärdateien mit Saxon schreiben
Verfasst von Stf unter Beispiele, Java, XSLT und XPath am 28. Juni 2010
XSLT bietet von Haus aus keine Möglichkeit zur Ausgabe von Binärdateien wie Bildern. Aber zumindest Saxon-PE/Saxon-EE ab Version 9.2 bietet eine Erweiterung, um diese Beschränkung zu umgehen. Nach einigen Tests ist es mir gelungen, sowohl jpeg als auch png zu schreiben. Das Beispiel-Stylesheet hat folgenden Abschnitt:
<xsl:variable name="image" as="xs:hexBinary">89504E470D0A1A0A0000000D494844520000000D0000000F0403000000CD0D5844000000017352474200AECE1CE900000018504C544562129D80418A8851829E7972B09266DCD946F4FF39FEFFFC482C1765000000564944415408D725CD2B12C0200C04D030133C0A5D55DD2354A1A3D069A17BFF237403316FF217AC70190ABCD905A5E36ED4CE2FB18EA14F0E51AEB6B484AD6E0F6ED0A95643AB53B1EF94EEC2842517363912F3EBDF0F99462041E4FA77630000000049454E44AE426082</xsl:variable> <xsl:result-document method="text" encoding="iso-8859-1" href="x.png" saxon:recognize-binary="yes"> <xsl:processing-instruction name="hex" select="$image"/> </xsl:result-document>
Ich hatte auf einem Mac mit <OxygenXML/> kein Glück bei der expliziten Angabe des Encodings, etwa hex.utf8, aber vielleicht habe ich nicht konsequent genug probiert.
Das vollständige Stylesheet steht unter http://www.expedimentum.org/example/xslt/binaerdaten-ausgabe.xsl zum Download bereit.
Nachtrag
Wenn man schon Erweiterungs-Funktionen bemüht, kann man die Arbeit auch gleich Java erledigen lassen. Damit ist man nicht mehr auf eine der »großen« Saxon-Versionen beschränkt; auch AltovaXML und Saxon-B 8.9 und 9.1 lassen sich zur Mitarbeit überreden. Ich habe zwei Varianten entwickelt: binaerdaten-ausgabe_java.xsl »borgt« sich nur den FileOutputStream von Java und wandelt den HexBinary-String per XSLT in eine Sequenz von Bytes um. binaerdaten-ausgabe_java_extern.xsl bedient sich der externen Klasse HexBinaryFileWriter, die die komplette Konvertierung und Ausgabe abwickelt. Die Erklärung folgt im nächsten Blog-Beitrag.
XSLT: import precedence und priority
Verfasst von Stf unter XSLT und XPath am 17. April 2010
Bei der Fehlersuche in einem Stylesheet bin ich heute auf eine überraschende Erkenntnis gestoßen: die import precedence bestimmt vor und unabhängig von der priority, welches matching template angewendet wird. Damit werden allgemeine Regeln im importierenden Stylesheets ohne Rücksicht auf spezielle Regeln in importierten Stylesheets ausgeführt. Ist das nicht gewünscht, lässt sich mit <xsl:next-match/> oder <xsl:apply-imports/> die Ausführung der importierten Templates ggfs. erzwingen. Alternativ lassen sich Stylesheets auch mit <xsl:include/> einbinden.
XSLT-Dokumentation mit XSLStyle™
Verfasst von Stf unter Beispiele, XSLT und XPath am 3. Mai 2009
Leider gibt es für XSLT keine standardisierten oder auch nur weitverbreiteten Regeln für die Dokumentation der Stylesheets. Ein Grund dafür könnte sein, dass einfach zu bedienende Werkzeuge nicht verfügbar sind. Für die Bespiele auf dieser Website habe ich mir noch einmal ein paar Ansätze angeschaut und mich dann für XSLStyle™ von G. Ken Holman entschieden, weil a) dieses Tool als XSL-Transformation direkt auf das Stylesheet selbst angewandt werden kann und b) XSLStyle™ auch noch ein paar Tests auf guten Programmierstil durchführt.
Die Installation ist recht einfach: zip-Datei herunterladen, in einem beliebigen Verzeichnis entpacken, fertig. XSLStyle™ kann über die Kommandozeile auf das zu dokumentierende Stylesheet angewandt werden, bequemer ist natürlich, in <OxygenXML/> ein Transformations-Szenario einzurichten.
Die Dokumentation erfolgt über Elemente im http://www.CraneSoftwrights.com/ns/xslstyle-Namensraum, die unmittelbar vor dem zu dokumentierenden XSL-Element stehen. XSLStyle™ kann bei fehlender Dokumentation warnen. Weil bei mir das von XSLStyle™ vorgegebene Präfix xs: immer für den http://www.w3.org/2001/XMLSchema-Namensraum steht, habe ich das Präfix doc: verwendet.
Innerhalb der XSLStyle™-Elemente kann die Dokumentation in beliebigem Markup erfolgen, G. Ken Holman gibt Stylesheets für DocBook und DITA mit. Ich habe mich pragmatisch für DocBook entschieden, weil ich damit aus einem früheren Projekt etwas Erfahrung habe. Es steht ein großer Teil des DocBook-Vokabulars zur Verfügung; nachdem meine Experimente mit einem recht elaboriertem Markup (wie refentry und funcsynopsis) primär aufgeblähten Code hervorgebracht haben, schien mir eine stark vereinfachte Version jedoch die bessere Variante. Der Code aus einem der letzten Posts zum Dateien-Testen sieht mit Dokumentation so aus:
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:misc="http://www.expedimentum.org/XSLT/Misc" xmlns:saxon="http://saxon.sf.net/" xmlns:doc="http://www.CraneSoftwrights.com/ns/xslstyle" xmlns:docv="http://www.CraneSoftwrights.com/ns/xslstyle/vocabulary" exclude-result-prefixes="doc docv" extension-element-prefixes="saxon" > <!-- --> <doc:doc filename="files.xsl" internal-ns="docv" global-ns="doc misc" vocabulary="DocBook" info="$Revision: 29 $, $Date: 2009-05-03 02:11:03 +0200 (So, 03 Mai 2009) $"> <doc:title>Dateien und Dateisystem</doc:title> <para>Dieses Stylesheet enthält Funktionen rund um das Dateisystem.</para> <para>Dieses Stylesheet unterliegt mangels Schöpungshöhe keinen urheberrechtlichen Beschränkungen.</para> <revhistory> <revision> <revnumber>0.28</revnumber> <date>2008-05-03</date> <authorinitials>Stf</authorinitials> <revremark>erste Version mit Dokumentation</revremark> </revision> </revhistory> </doc:doc> <!-- --> <!-- __________ misc:file-exist() __________ --> <doc:function> <doc:param name="href"><para>Pfad zur zu überprüfenden Datei (URI-encoded)</para></doc:param> <para>Diese Funktion überprüft, ob die angegebene Datei vorhanden ist. Das Ergebnis ist ein Wahrheitswert (<code>xs:boolean</code>, <code>true()</code> oder <code>false()</code>). <emphasis role="bold">Achtung!</emphasis> Diese Funktion erfordert bei Ausführung Saxon, da die Saxon-spezifische Erweiterung <code>saxon:file-last-modified()</code> verwendet wird.</para> </doc:function> <xsl:function name="misc:file-exists" as="xs:boolean"> <xsl:param name="href" as="xs:string?"/> <xsl:value-of select="boolean(normalize-space(string(saxon:file-last-modified($href))))"/> </xsl:function> <!-- --> </xsl:stylesheet>
Damit XSLStyle™ eine Dokumentation ausgibt, müssen der http://www.CraneSoftwrights.com/ns/xslstyle und ein weiter Namensraum für interne Zwecke (http://www.CraneSoftwrights.com/ns/xslstyle/vocabulary) im zu dokumentierenden Stylesheet deklariert werden. Außerdem erwartet XSLStyle™ am Anfang der Dokumentation ein <doc:doc/>-Element mit Basisinformationen, fehlt es (oder auch nur einzelne Attribute), gibt es bei mir merkwürdige Effekte.
Das vollständige Beispiel habe ich unter http://www.expedimentum.org/example/xslt/files.xsl abgelegt, die resultierende Dokumentation liegt unter http://www.expedimentum.org/example/xslt/doc/files.html. Hier bzw. hier gibt es ein etwas umfangreicheres Beispiel mit importierten Stylesheets.
Zum Schluss folgt noch eine Sammlung zufälliger Links zur XSL-Dokumentation, die bei der Recherche entstanden ist. Falls XSLStyle™ nicht passt, findet sich sicher eine geignetere Lösung. Mir hat XSLTdoc relativ gut gefallen, und mit einem Ant-Script lässt sich auch die umständliche Handhabung bändigen, leider lässt es sich nicht direkt als Transformation auf das zu dokumentierende Stylesheet anwenden.
- http://www.dpawson.co.uk/xsl/sect2/documentation.html: XSL-Dokumentation in den XSLT FAQ (englisch)
- http://www.bacman.net/apps/XSLdoc/: XSLdoc, ähnlich JavaDoc (englisch)
- http://xsltsl.sourceforge.net/docbook-extensions.html: DocBook Extensions For XSLT Stylesheet Documentation – Stylesheets in DocBook dokumentieren (englisch)
- http://www.linkwerk.com/pub/xslt/lib/xsltdoc/: xsltdoc — Literate XSLT Programming based on Namespaces (deutsch)
- http://www.pnp-software.com/XSLTdoc/: XSLTdoc – A Code Documentation Tool for XSLT (englisch)
- http://www.devx.com/getHelpOn/10MinuteSolution/20343/0/page/1: Document Your XSLT – eine ähnliche Lösung wie XSLTDoc (englisch)
- http://www.kanzaki.com/parts/xsltdoc.xsl: XSLT Stylesheet description stylesheet – ein weiteres Stylesheet, mit generischer Dokumentation
siehe auch: Florent Georges: XSLStyle and oXygen (englisch)
Encoding und Zeilenumbrüche ändern mit XSLT
Verfasst von Stf unter Beispiele, Grundlagen, XSLT und XPath am 26. April 2009
Beim Datenaustausch mit XML entsteht immer wieder das Problem, dass eine bestimmte Anwendung nur Daten in einem bestimmten Encoding »versteht«, es ist also gelegentlich eine Konvertierung des Encodings notwendig. Ich verwende dazu eine »Identity Transformation«, die bis auf das Encoding tatsächlich überhaupt keine Änderungen vornimmt:
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" > <!-- --> <xsl:output encoding="Windows-1252" indent="no" method="xml"/> <!--<xsl:output encoding="UTF-8" indent="no" method="xml"/>--> <!--<xsl:output encoding="UTF-16" indent="no" method="xml"/>--> <!--<xsl:output encoding="ISO-8859-1" indent="no" method="xml"/>--> <!-- --> <xsl:preserve-space elements="*"/> <!-- --> <xsl:template match="@* | node()"> <xsl:copy> <xsl:apply-templates select="@* | node()"/> </xsl:copy> </xsl:template> <!-- --> </xsl:stylesheet>
In XSLT kann man das Encoding des Output-Dokumentes mit <xsl:output encoding="xxx"/> festlegen. Im obigen Beispiel habe ich die gängigen Encodings vorgegeben, zur Anwendung müssen nur die jeweilige Zeile vom Kommentar befreit und die Zeile auskommentiert werden.
Die unterstützen Encodings sind vom jeweiligen XSLT-Prozessor und im Fall von Saxon zudem von der darunter liegenden Java-Installation abhängig. Obwohl laut XML-Standard nur UTF-8 und UTF-16 unterstützt werden müssen, kommen viele XSLT-Prozessoren auch mit ISO 8859-1, Windows-1252 und anderen zurecht.
Zeilenumbrüche
Im vorigen Post habe ich das Problem der falschen Zeilenumbrüche angesprochen, aber keine Lösung angeboten. Die folgt nun hier, allerdings muss ich dazu etwas weiter ausholen:
Zeilenumbrüche sind sogenannte Steuerzeichen, d.h. Zeichen, die normalerweise nicht dargestellt werden, sondern die Darstellung steuern. Historisch haben sich drei Varianten etabliert: Unix verwendet das Steuerzeichen LF (engl. line feed, hexadezimal &0A;), der Mac bis Mac OS 9 CR (engl. carriage return, hex &0D;) und Windows die Sequenz CRLF (&0A;&0D;). Unicode und alle Encodings kennen beide Zeichen, allerding müssen laut XML-Standard bei der Verarbeitung von XML-Dokumenten CR und CRLF zu LF normalisiert werden, so dass ausgegebene Dokumente normalerweise Unix-Zeilenumbrüche enthalten.
Um trotzdem Windows-Zeilenumbrüche in der Ausgabe zu erhalten, muss der XSLT-Prozessor bei der Ausgabe LF wieder in CRLF umwandeln. Soweit ich weiß, gibt es in Saxon keine Möglichkeit dazu, allerdings schreibt Microsofts msxsl.exe Windows-Zeilenumbrüche. <OxygenXML/> erlaubt in der Konfiguration von Transformations-Szenarien die Auswahl von MSXML4.0 als Transformator (sic!). Einzige Einschränkung: Microsoft unterstützt nur XSLT in der Version 1.0.
Ich habe das Stylesheet im Beispiel-Ordner hinterlegt und das Beispieldokument um Testfälle ergänzt. Auch die <OxygenXML/>-Projekt-Datei liegt in einer ergänzten Version vor.
Mit XSLT testen, ob eine Datei existiert
Verfasst von Stf unter Beispiele, XSLT und XPath am 19. April 2009
XSLT bietet recht wenig Möglichkeiten, mit der Außenwelt zu kommunizieren. Informationen über die Umwelt zur Laufzeit des Stylesheets lassen sich mit Bordmitteln nur in sehr geringem Umfang ermitteln, Wirkungen nur über die Dateiausgabe erzielen. Dies mag daher kommen, dass das funktionale Paradigma von XSLT Seiteneffekte verbietet, führt aber in der Praxis gelegentlich zu merkwürdigen Verrenkungen. Beispielsweise habe ich öfters das Problem, Stylesheets abhängig von der Existenz einer externen Datei aufzurufen. Bisher habe ich dazu in einer externen Anwendung ein XML mit Informationen zum Dateisystem erzeugt und dieses dann im XSLT ausgewertet. Das funktioniert ganz gut mit statischen Daten, bei häufigen Änderungen im Dateisystem ist dieses Verfahren aber zu umständlich.
XPath 2.0 bietet mit doc-available() zwar eine dedizierte Funktion, diese liefert aber nur bei der Existenz eines wohlgeformten XML-Dokumentes ein true(). Ebenso hängt ein logisches wahr bei unparsed-text-available() von der Existenz eines Textes ab. Was aber, wenn ich wissen will, ob eine Bild-Datei (nein, nicht SVG
) oder eine leere Datei existiert? Die wenigen Lösungsvorschläge verweisen auf xslt-externe Erweiterungsfunktionen. Diese setzen in der Regel die Existenz von Java und manchmal auch zusätzliche Java-Klassen voraus, was die Weiterverwendung vorhandener Stylesheets auf verschiedenen Rechnern erheblich erschwert.
Da in meiner Umgebung ohnehin meist Saxon eingesetzt wird, bietet es sich an, die Saxon-eigenen Erweiterungsfunktionen zu benutzen. Zwar bietet Saxon nicht den gesuchten Test, aber mit file-last-modified() eine Funktion, die einen Leerstring zurückgibt, wenn die Datei nicht vorhanden ist. Damit war die gesuchte Funktion schnell geschrieben:
<xsl:function name="misc:file-exists" as="xs:boolean" xmlns:saxon="http://saxon.sf.net/" extension-element-prefixes="saxon"> <xsl:param name="href" as="xs:string?"/> <xsl:value-of select="boolean(normalize-space(string(saxon:file-last-modified($href))))"/> </xsl:function>
Zu beachten ist, dass diese Funktion nur mit Saxon funktioniert und keine Fehlerbehandlung stattfindet. Außerdem muss der Parameter URI-codiert werden (z.B. Leerzeichen durch %20 ersetzen). Es gibt also noch genug Raum für Optimierungen.
Nachtrag: Diese Funktion ist nun in der Beispielsammlung abgelegt.
Nachtrag II: saxon:file-last-modified() ist spezifisch für Saxon 9.1. In Saxon 9.2 PE/EE wurde diese Funktion in saxon:last-modified() integriert, in Saxon HE stehen keine Erweiterungsfunktionen zur Verfügung (vgl. Post von Michael Kay auf der Saxon-Mailingliste). Ich habe das Stylesheet entsprechend aktualisiert.
Nachtrag III: Eine Lösung unter Verwendung von Java, die weniger prozessorabhängig ist, habe ich hier beschrieben.
Nachtrag IV: Ich habe die verschiedenen Lösungen in der XSLT-SB zusammengeführt. Der Code kann in der Beispielsammlung eingesehen werden.
