AltovaXML in OxygenXML einbinden

Um AltovaXML in OxygenXML nutzen zu können, muss man diesen XSLT-Prozessor als »Custom Engine« einrichten. So geht’s:

Unter Einstellungen/XML/XSLT-FO-XQuery/Custom Engines einen neuen Prozessor anlegen:

OxygenXML: einen neuen XSLT-Prozessor anlegen, Seite »Custom Engines«

OxygenXML: einen neuen XSLT-Prozessor anlegen

Als Prozessortyp ist »XSLT« vorausgewählt und richtig. Name und Beschreibung eingeben und dann in das Feld Kommandozeile »"C:\Program Files (x86)\Altova\AltovaXML2011\AltovaXML.exe" /xslt2 ${xsl} /in ${xml} /out ${out}« eintragen (Pfad zu AltovaXML bitte an lokale Gegebenheiten anpassen).

OxygenXML: einen neuen XSLT-Prozessor anlegen: Standardeinstellung für AltovaXML

OxygenXML: Standardeinstellung für AltovaXML

Speichern, fertig. In den Transformationsszenarien kann jetzt der neu angelegte Prozessor ausgewählt werden:

OxygenXML: Auswahl der XSLT-Prozessors im Transformationsszenario

Leider kann OxygenXML keine Parameter oder initiale Modes bzw. initiale Templates an den so erzeugten Prozessor übergeben. Ein Workaround ist, für definierte Fälle eigene Prozessoren anzulegen. Für den ad-hoc-Selbsttest der XSL-SB sieht das so aus:

OxygenXML: einen neuen XSLT-Prozessor anlegen: AltovaXML mit erweiterten Einstellungen für initialen Mode und Parameter

OxygenXML: AltovaXML mit erweiterten Einstellungen für initialen Mode und Parameter

Der erste Teil der Kommandozeile »"C:\Program Files (x86)\Altova\AltovaXML2011\AltovaXML.exe" /xslt2 ${xsl} /in ${xml} /out ${out} /m internals.self-test -param internals.logging-level="'DEBUG'" -param internals.errors.die-on-critical-errors="'no'"« ist identisch zu oben, initialer Mode und Parameter werden zusätzlich hart codiert übergeben. Hier sollten die Entwickler von OxygenXML gelegentlich einmal nachbessern, es kann ja nicht so schwer sein, einen benannten Parameter als benanntes Makro anzubieten.

Keine Kommentare

XSLT-SB – eine Standard-Bibliothek für XSLT

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-list aus 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.

Keine Kommentare

Apache Ant: Initialen Mode und Initiales Template für XSLT 2.0 mit Saxon setzen

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:

Keine Kommentare

Apache Ant 1.8.2 unter Mac OS X einrichten

Weil Ant 1.8.1 im XSLT-Task vergisst, den Classpath auszulesen, funktionieren mit dieser Version keine XSLT-2.0-Stylesheets (näheres siehe Bugzilla). Und weil Apple auch zwei Monate nach Erscheinen von Version 1.8.2 (mit dem Bugfix) noch kein Update durchgeführt hat, musste ich von Hand für Abhilfe sorgen. So geht’s:

  1. Ant 1.8.2 herunterladen und entpacken
  2. Ich habe den entpackten Ordner unter ~/Applications abgelegt, gleich neben der alten 1.8.0-Version. Bei mir sieht das dann so aus:
    localhost:Applications stf$ ls -F -1
    Crane-xslstyle-20100817-0240z/
    apache-ant-1.8.0/
    apache-ant-1.8.2/
    calabash-0.9.24/
  3. Erzeugen eines SymLinks auf den Ant-Ordner. Dazu ein Terminalfenster öffnen und
    ln -s ~/Applications/apache-ant-1.8.2 ~/Applications/ant

    eingeben. Durch Umbiegen des SymLinks auf ein anderes Verzeichnis kann man einfach auf andere Ant-Versionen umschalten: auf eine ältere oder später auch auf eine neuere.

  4. Jetzt muss man bash – dem Programm hinter dem Terminalfenster – noch sagen, dass es die neue Ant-Version benutzen soll. Dazu fügt man der PATH-Variablen dem Pfad zum neuen Ant-Verzeichnis hinzu. Ich habe dazu im Benutzerverzeichnis eine Datei ./bash_profile angelegt und folgendes hineingeschrieben:
    echo running ~/.bash_profile
    export PATH=~/Applications/ant/bin:$PATH
    export ANT_HOME=~/Applications/ant

    Die erste Zeile gibt bei jedem Konsolenstart eine Statusmeldung aus, die zweite Zeile setzt die PATH-Umgebungsvariable, die dritte Zeile sorgt dafür, das Ant seine Dateien findet.
    Da verborgene Dateien (mit Punkt am Anfang des Dateinamens) im Finder standardmäßig ausgeblendet werden, habe ich ganz UNIX-like auf der Konsole gearbeitet. Mit

    pico ~/.bash_profile

    wird der Editor geöffnet, Speichern funktioniert über CTRL-o, Verlassen mit CTRL-x.

  5. Eine neues Terminalfenster starten. In der zweiten Zeile sollte jetzt soetwas wie running /Users/stf/.bash_profile stehen. Mit dem Terminal-Kommando
    set

    lässt sich eine Liste der Umgebunsvariablen ausgeben, hier sollten jetzt für PATH und ANT_HOME die neuen Werte angezeigt werden. Schließlich bringt die Versionsangabe mit

    ant -v

    Gewissheit, ob alles geklappt hat.

Meine Helferlein auf diesem Weg waren die beiden unten angegebenen Artikel sowie man ln und man ls.

Hoffen wir, dass Apple bald auf 1.8.2 aktualisiert und zukünftig solche Workarounds nicht mehr notwendig sind …

Quellen:

Keine Kommentare

EPUB-Beispiel aus Wikipedia

Die Beispiele aus dem Wikipedia-Artikel EPUB ergeben ein vollständiges E-Book. Leider kann man bei Commons noch keine EPUBs hochladen, siehe https://bugzilla.wikimedia.org/show_bug.cgi?id=17858. Ich habe deshalb die dazugehörigen Dateien und das EPUB in der Beispielsammlung abgelegt.

Keine Kommentare

XSLT/XPath: arithmetische Operationen mit einer Leersequenz

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.

Keine Kommentare

Java in XSLT verwenden

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

2 Kommentare

Binärdateien mit Saxon schreiben

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.

1 Kommentar

Include mehrerer Pattern in Schematron

Schematron bringt einen recht simplen Mechanismus zum Einbinden externen Schemata mit: das Element <sch:include/> bindet ein externes XML-Dokument ein, und zwar genau an der Stelle, wo <sch:include/> im Code steht. Das partielles Überschreiben oder gezielte Einbinden einzelner Pattern und Rules ist nicht möglich. Dies hat zwei Folgen:

  1. Da unterhalb von <sch:schema/> kein weiteres <sch:schema/> eingebunden werden darf, kann die eingebundene Datei nicht alleinstehend zum Validieren benutzt werden.
  2. Da <sch:pattern/> direkt unterhalb von <sch:schema/> stehen muss (es gibt in Schematron kein klammerndes Element wie etwa <define/> in RelaxNG), können mehrere Pattern nur eingebunden werden, wenn sie in der eingebundenen Datei als Wurzelelemente direkt nebeneinander stehen. Eine solche Datei ist aber kein wohlgeformtes XML, folgerichtig verweigert Schematron den Include.

Ein Thread auf der DSDL-Mailingliste und eine Anfrage auf der XML-DEV-Mailingliste ergaben zwar, dass das Problem bekannt ist, die vorgeschlagenen Lösungen sollen aber erst in zukünftigen Versionen von Schematron festgezurrt werden, und sie funktionieren jetzt noch nicht mit <OxygenXML/>. Deshalb entschied ich mich, auf die im grundlegenden XML-Standard definierte Einbindung externer Entities zurückzugreifen. Dieser Workaround sieht so aus:

Basis-Schematron:

<!DOCTYPE schema [
	<!ENTITY basic-pattern SYSTEM "basic-pattern.ent">
]>
<schema xmlns="http://purl.oclc.org/dsdl/schematron" queryBinding="xslt2">
	&basic-pattern;
</schema>

eingebundene Entity-Datei (basic-pattern.ent):

<pattern xmlns="http://purl.oclc.org/dsdl/schematron">
	<!-- some rule -->
</pattern>
<pattern xmlns="http://purl.oclc.org/dsdl/schematron">
	<!-- some rule -->
</pattern>
<!-- add some more pattern here -->

Diese Datei ist kein wohlgeformtes XML, weil sie mehrere Wurzelelemente hat, sie wird aber nur eingebunden in andere Dateien verwendet – die dann wohlgeformt sind. In der Praxis wird man wohl einfach ein Schematron entwickeln und die Includes dann per Cut&Paste in die Entity-Datei verschieben, so dass die fehlende Wohlgeformtheit kein Nachteil ist.

erweitertes Schematron:

<!DOCTYPE schema [
	<!ENTITY basic-pattern SYSTEM "basic-pattern.ent">
]>
<schema xmlns="http://purl.oclc.org/dsdl/schematron" queryBinding="xslt2">
	&basic-pattern;
	<!-- add some more pattern here -->
</schema>

Dieser Workaround hat verschiedene Vorteile:

  1. Externe Entity-Dateien sind konform zum XML- und Schematron-Standard.
  2. Die einbindenden Dateien lassen sich zusammen mit den eingebundenen Dateien validieren.
  3. Es sind keine Änderungen an etablierten Werkzeugen und Workflows notwendig (so ist es mir mit flüchtiger Recherche nicht gelungen, <OxygenXML/> andere Schematron-Stylesheets unterzuschieben).

Ich habe die drei Beispieldateien unter http://www.expedimentum.org/example/Schematron/ abgelegt.

»crosslinked«

Keine Kommentare

XSLT: import precedence und priority

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.

Quelle: http://www.w3.org/TR/xslt20/#conflict

Keine Kommentare