Die Fülle an neuen Elementen, Attributen und Funktionen ist überwältigend, nicht zuletzt weil XSLT 2.0 auf XPath 2.0, dem Nachfolger von XPath, aufbaut und alleine dieser bereits eine ganze Reihe von Erneuerungen und Verbesserungen mit sich bringt. Dennoch, XSLT 2.0 ist abwärtskompatibel zu XSLT 1.0, sodass sich bestehende XSLT-Stylesheets auch mit einem XSLT 2.0-Prozessor verarbeiten lassen.
Play the Saxon
Das Angebot an XSLT 2.0-Prozessoren für .NET-Anwendungen ist zurzeit noch eher bescheiden. Das mag daran liegen, dass die Spezifikation „XSL Transformations (XSLT) Version 2.0“ erst als Candidate Recommendation vorliegt. Das Gröbste ist also geklärt, jetzt erfolgt die Feinjustierung bis zu einer endgültigen Empfehlung durch das W3C. Dann könnte XSLT 2.0 auch Eingang in das .NET Framework halten.
Bis dahin muss man sich in .NET mit der Portierung einer populären Java-Bibliothek vertrösten oder auf AltovaXML ausweichen – dieses ist im Werkzeugkasten beschrieben. Die .NET-Portierung nennt sich Saxon.NET, verfügt über einige interessante Erweiterungen und stammt vom XSLT-2.0-Editor Michael Kay beziehungsweise von seiner Firma Saxonica.Saxonica vertreibt ein einziges Produkt: Saxon. Das gibt es in zwei Varianten. Als kostenlose, quelloffene B-Variante (Basic Edition) und als kostenpflichtige SA-Variante (Schema Aware). Die unterstützt zusätzlich zu XPath 2.0, XSLT 2.0 und XQuery 1.0 (in den aktuellen W3C-Empfehlungen) auch XML Schema 1.0.
Beide Varianten stehen sowohl für Java als auch für .NET zur Verfügung. Die Open-Source-Variante wird über die zugehörige Projektseite bei Sourceforge bereitgestellt. Für .NET-Anwendungen ist die ZIP-Datei Saxonb8-8n.zip vorgesehen. Weitere Dateien sind nicht erforderlich. Zu empfehlen ist noch der Download der Datei Saxon-resources8-8.zip, welche eine Reihe von Beispielanwendungen, darunter auch für C# (im Unterordner Samples\cs), enthält.
Improvisation
Ein Setup für Saxon.NET gibt es nicht. Stattdessen enthält der Unterordner bin aus der ZIP-Datei Saxonb8-8n.zip neben den erforderlichen DLL-Dateien und den drei Kommandozeilenprogrammen Query, Transform und Validate eine Stapelverarbeitungsdatei Install-gac.bat, welche die Assemblies in den GAC installiert. Leider funktioniert diese nicht auf einem deutschsprachigen Windows, da der Pfad zum .NET Framework SDK 1.1, welches die Stapelverarbeitungsdatei voraussetzt, fest codiert wurde. Das Problem lässt beheben, indem die erste Zeile wie folgt abgeändert wird:
set NET="%PROGRAMFILES%\Microsoft.NET\SDK\v1.1\Bin"
Die Stapelverarbeitungsdatei installiert vier Assemblies in den GAC. Die DLL-Dateien mit dem Präfix „IKVM“ entstammen dem Open-Source-Projekt IKVM.NET. Dieses Projekt implementiert eine Java-Virtual-Machine für .NET. Mit IKVM.NET lassen sich unter anderem Java-Bibliotheken in .NET-Assemblies kompilieren. Ein Beispiel für ein solches Java-Assembly ist die Datei Saxon8.dll, welche der Java-Bibliothek saxon8.jar entspricht.
Saxon.NET ist folglich keine Neuimplementierung von Saxon auf der .NET-Plattform, sondern eine direkte Portierung, was einige unangenehme Folgen mit sich bringt. So kennt die Saxon-API keine XmlReader-Klasse. Damit scheidet beispielsweise der SQL Server 2005 als (unmittelbare) Datenquelle für eine XSLT-Transformierung aus.
Um die Integration der Saxon-API in das .NET Framework kümmert sich daher eine gesonderte Assembly namens Saxon8api.dll. Diese Assembly (bzw. der darin enthaltene Namespace Saxon.Api) stellt ein vereinfachtes API für .NET-Anwendungen bereit. Kernstück dieser Bibliothek ist das sog. „XSLT/XQuery/XPath data model“, kurz XDM. Es abstrahiert von den tatsächlichen XML-Datenquellen, indem es eine Reihe von Klassen wie XdmDestination bereitstellt. Die Klasse XdmDestination ist eine abstrakte Klasse und repräsentiert das Ziel einer XML-Ausgabe.
Kompilation
Listing 1 verwendet eine Subklasse mit dem Namen DomDestination, um das Ergebnis einer XSLT-2.0-Transformation in ein XmlDocument-Objekt zu schreiben. Des Weiteren ist ein Processor-Objekt erforderlich. Das Processor-Objekt dient dazu, das Stylesheet und das Eingabedokument zu laden. Außerdem stellt es einen gemeinsamen Kontext für verschiedene XSLT-Stylesheets und XQuery-Abfragen zur Verfügung (zum Beispiel in der SA-Variante eine gemeinsame Schema-Auflistung).
Um ein XML-Dokument zu laden, wird ein DocumentBuilder-Objekt benötigt, welches die NewDocumentBuilder-Methode der Processor-Klasse erzeugt. Anschließend erstellt die Build-Methode des DocumentBuilder-Objekts ein XdmNode-Objekt, welches die Eingabe für die XSLT-Transformierung darstellt. Als Quelle beziehungsweise als Parameter für die Build-Methode kann ein XmlReader-Objekt oder eine Pfadangabe eingesetzt werden. Pfadangaben sind als Uri-Objekt zu liefern und das Uri-Objekt muss einen absoluten Pfad enthalten.
Für das Laden des Stylesheets muss zuerst ein XsltCompiler-Objekt erzeugt werden (über die NewXsltCompiler-Methode des Processor-Objekts). Dessen Compile-Methode erstellt aus einem XSLT-Dokument, dessen Ursprung zum Beispiel als Uri-Objekt angegeben ist, ein XsltExecutable-Objekt. Wie die XslCompiledTransform-Klasse des .NET Frameworks 2.0 kompiliert Saxon.NET das Stylesheet in eine interne und effizientere Repräsentation, um eine bessere Ausführungsgeschwindigkeit bei der anschließenden Transformation zu erreichen.
Die XsltExecutable-Klasse ermöglicht noch keine Transformation. Sie bietet nur eine einzige Methode an: Die Load-Methode erzeugt schließlich ein XsltTransformer-Objekt. Dieses Objekt verfügt wiederum über eine ganze Reihe von Eigenschaften, mit denen sich die Transformation steuern lässt. Die wichtigste Eigenschaft lautet InitialContextNode. Der Kontextknoten bestimmt die Eingabe für das XSLT-Stylesheet und wird mit dem zuvor erzeugten XdmNode-Objekt besetzt. Die eigentliche Transformation erfolgt über den Aufruf der Run-Methode unter Angabe eines XdmDestination-Objektes.
Listing 1------------------------------------------------XSLT-Transformierung mit Saxon.NETDim proc As New Processor()Dim build As DocumentBuilder = proc.NewDocumentBuilder()Dim base As New Uri(Environment.CurrentDirectory)Dim doc As XdmNode = build.Build(New Uri(base, "..\Data.xml"))Dim comp As XsltCompiler = proc.NewXsltCompiler()Dim exec As XsltExecutable = comp.Compile(New Uri(base, "..\Transform.xslt"))Dim xslt As XsltTransformer = exec.Load()Dim dest As New DomDestinationxslt.InitialContextNode = docxslt.Run(dest)dest.XmlDocument.Save("Report.html")
Komposition
Das in Listing 1 verwendete Stylesheet Transform.xslt demonstriert eine Reihe von Besonderheiten von XSLT 2.0. Eine bedeutende Änderung in XSLT 2.0 gegenüber der Version 1.0 ist die Abschaffung der Result-Tree-Fragmente. Ein Result-Tree-Fragment entsteht in XSLT 1.0 beispielsweise durch eine Variablendeklaration, wie sie in Listing 2 gezeigt ist. Die Variable items enthält demnach eine Reihe von item-Elementen. Diese sind in XSLT 1.0 jedoch nicht ohne Weiteres zugänglich. Erst die Einführung einer (proprietären) Erweiterungsfunktion, meist node-set genannt, ermöglicht den Zugriff auf die temporäre Datenstruktur.
XSLT 2.0 macht Schluss mit den Result-Tree-Fragmenten und führt stattdessen das Konzept der Sequenzen ein. So ist die Variable items eine Sequenz von Knoten, genauer von item-Elementen, und lässt sich dementsprechend innerhalb eines XPath-Ausdruckes (/item) abfragen. Tatsächlich geht das Konzept der Sequenzen auf XPath 2.0 zurück. So erzeugt der XPath-Ausdruck (1,2,3,4) die Sequenz der natürlichen Zahlen eins bis vier. Um solche Sequenzen komfortabel zu bearbeiten, stellt XPath in seiner zweiten Version auch ein for-Konstrukt sowie eine if-Anweisung zur Verfügung (siehe Listing 2). Sequenzen von Sequenzen gibt es übrigens in XPath 2.0 nicht. So ist (1,2),(3,4) identisch zu 1,2,3,4. Des Weiteren ist ein einzelnes Element gleichbedeutend mit einer Sequenz der Länge eins. Schließlich repräsentiert die leere Sequenz () eine Art NULL-Wert.
Listing 2------------------------------------------------Anwendung von Sequenzen<xsl:variable name="items"><xsl:for-each select ="for $n in (1,2,3,4) return if ($n mod 2 eq 0) then $n else ()"><item><xsl:value-of select="." /></item></xsl:for-each></xsl:variable>
Transkription
Eine Frage wurde im Zusammenhang mit XSLT 1.0 besonders oft gestellt: Wie funktioniert die Gruppierung nach ein oder mehreren Attributen beziehungsweise Elementen in XSLT 1.0. Die (etwas umständliche) Lösung wurde (nach ihrem Erfinder) Muenchian-Methode genannt. Einfacher geht es in XSLT 2.0 mit der for-each-group-Anweisung und dem group-by-Attribut. Listing 3 nutzt diese Anweisung, um eine Reihe von item-Elementen gemäß dem Wert ihres group-Attributs zu gruppieren. Das group-by-Attribut bestimmt dabei das Attribut oder Element, nach dem gruppiert werden soll. Die for-each-group-Schleife durchläuft die Menge an möglichen Gruppen, das heißt die Menge an distinkten Werten für das group-Attribut. Die Mitglieder der momentanen Gruppe sind über die Funktion current-group zugänglich, die in diesem Fall eine Sequenz von item-Elementen liefert. Anstatt diese in das reguläre Ausgabedokument zu schreiben, nutzt Listing 3 die result-document-Anweisung, um für jede Gruppe ein separates Ausgabedokument zu erzeugen. Der Inhalt dieser sekundären Ausgabedokumente wird von dem Inhalt des result-document-Elements bestimmt.
Listing 3------------------------------------------------Mehrere Ausgabedokumente und Gruppierung<xsl:for-each-group select="item" group-by="@group"><li><a href="{@group}.html"><xsl:value-of select="@group"/></a></li><xsl:result-document href="{@group}.html"><html><body><h1><xsl:value-of select="@group" /></h1><ul><xsl:for-each select="current-group()"><li><xsl:value-of select="text()" /></li></xsl:for-each></ul></body></html></xsl:result-document></xsl:for-each-group>
Interpretation
Eigene Funktionen in XSLT zu implementieren, war auch in XSLT 1.0 möglich, aber nur weil sich viele Hersteller über die Spezifikation hinweg setzten und eigene Erweiterungen wie eben das script-Element anboten. XSLT 2.0 bietet hierfür das function-Element an. Listing 4 zeigt die Anwendung dieses Elements anhand der rekursiven Fibonacci-Funktion. Als Eingabe erwartet die Funktion einen Integer-Wert. Dies muss entsprechend durch ein parameter-Element und das Attribut as definiert werden. Als Attributwert ist ein benutzerdefinierter Typ (an dieser Stelle kommt die Schema-Awareness ins Spiel) oder ein vordefinierter XML Schema-Typ zu spezifizieren. Aus diesem Grund deklariert das Stylesheet auch den XML-Schema-Namespace im stylesheet-Element. Zudem definiert das Stylesheet einen Namespace für die eigenen Funktionen und verknüpft ihn mit dem Präfix dnm.
Ein sequence-Element beziehungsweise dessen select-Attribut bestimmt den Rückgabewert der Funktion. Das Ergebnis ist folglich eine Folge, die in diesem Fall allerdings die Länge eins besitzt, da es jeweils nur eine Fibonacci-Zahl für einen bestimmten Index gibt. Wie die Funktionsdefinition jedoch auch zeigt, benötigt die Berechnung der n-ten Fibonacci-Zahl die Fibonacci-Zahlen mit den Indizes n - 2 und n - 1. Insbesondere verlangt die Berechnung der (n - 1)-ten Fibonacci-Zahl ebenfalls die (n - 2)-te Fibonacci-Zahl, so dass mindestens eine Berechnung mehr als einmal ausgeführt wird. Gerade bei größeren Fibonacci-Zahlen dauert die Berechnung aus diesem Grund verhältnismäßig lange. Schneller ginge es, wenn sich die Funktion die bereits berechneten Zahlen merken würde. Genau dies leistet eine Saxon-spezifische Erweiterung: das Attribut memo-function. Um es zu verwenden, ist zusätzlich der Saxon-Namespace einzubinden und mit einem Präfix zu versehen.
Saxon stellt eine Reihe weiterer, nützlicher XSLT-Erweiterungen zur Verfügung, darunter eine eigene Version der Anweisung call-template, die es erlaubt, den Namen eines aufzurufenden Templates erst zur Laufzeit zu bestimmen, sowie das assignable-Attribut und die assign-Anweisung, mit derer sich der Wert einer Variablen auch nach ihrer Initialisierung ändern lässt. Schließlich unterstützt Saxon auch ein Großteil der Erweiterungen, die das EXSLT-Projekt definiert.
Die Kritiken
Gegenüber den früheren Versionen [XQuery: SQL für XML, Martin Szugat, dot.net magazin 5.2005] zeigt Saxon.NET eine wesentlich konsistentere Verzahnung mit dem .NET-Framework. Dies erleichtert die Anwendung von Saxon.NET und XSLT 2.0 ungemein. Da sich XSLT 2.0 jedoch noch in Entwicklung befindet, ist auch Vorsicht geboten. Man darf gespannt sein, was sich bis zur endgültigen Empfehlung noch ändern wird.
Auch Altova, bekannt für seinen XML-Editor XMLSpy, hat einen kostenlosen XSLT 2.0-Prozessor namens AltovaXML im Angebot, der nebst einer .NET-, eine Java- und eine COM-Schnittstelle anbietet und zusätzlich zu XSLT 1.0 und 2.0 (aktuelle Candidate Recommendation) ebenso XQuery 1.0 unterstützt.
Beide Engines, für XQuery 1.0 und XSLT 2.0, sind Schema-aware. Die Schnittstelle der AltovaXML-Bibliothek ist sehr einfach gehalten – Listing 5 zeigt ein ebenso einfaches Beispiel. Die zugehörige XSLT-Datei liest den Text „Test“ aus dem XML-Eingabedokument und schreibt ihn als Listenelement in die HTML-Datei. Wie das Beispiel offenbart, unterstützt die Bibliothek keine relativen Pfade – ein leicht lösbares Problem. Im Vergleich zu der Version von 2005 wurde auf das lästige Konsolenfenster verzichtet.
oXygen und Stylus Studio
Zwar gibt es eine ganze Reihe von XML-Editoren, die bereits XSLT 2.0 unterstützen und hierfür unter anderem einen Debugger anbieten, doch wer die zahlreichen Erweiterungen von Saxon verwendet, braucht auch einen XSLT-Debugger, der diese versteht. Zwei kommerzielle XML-Editoren kommen hier in Frage: oXygen und Stylus Studio. Den Editor oXygen gibt es nur in einer Variante, dafür aber für verschiedene Plattformen: Windows, Mac OS X, Linux beziehungsweise Unix und als Add-In für Eclipse 3.1 und 3.2. Die Unterstützung für Saxon (in der Java-Variante) fällt in Stylus Studio umfangreicher aus, ist aber abhängig von der jeweiligen Edition: Home Edition, Professional Suite und Enterprise Suite. Sowohl oXygen als auch die drei genannten Stylus Studio-Editionen sind als Trialversionen verfügbar.
Listing 4------------------------------------------------Eigene Funktionen und Saxon-Erweiterungen<xsl:stylesheet version="2.0"xmlns:xsl="http://www.w3.org/1999/XSL/Transform"xmlns:saxon="http://saxon.sf.net/"xmlns:xs="http://www.w3.org/2001/XMLSchema"extension-element-prefixes="saxon"xmlns:dnm="urn:dotnet-magazin:saxon"><xsl:function name="dnm:fibonacci" saxon:memo-function="yes"><xsl:param name="n" as="xs:integer" /><xsl:sequence select="if ($n = 1) then 1else if ($n = 2) then 1else dnm:fibonacci($n - 2) + dnm:fibonacci($n - 1)" /></xsl:function>
Listing 5------------------------------------------------XSLT-Transformierung mit AltovaXMLDim altova As New ApplicationDim xslt2 As IXSLT2 = altova.XSLT2xslt2.InputXMLFileName = Path.Combine(Environment.CurrentDirectory, "..\..\Data.xml")xslt2.XSLFileName = Path.Combine(Environment.CurrentDirectory, "..\..\Transform.xslt")xslt2.Execute(Path.Combine(Environment.CurrentDirectory, "..\..\Report.html"))




