Ein einfaches Beispiel

(Auszug aus "Java und XSLT" von Eric M. Burke)

Lassen Sie uns mit dem vielleicht einfachsten Programm beginnen, das man überhaupt schreiben kann. Dazu erstellen wir ein einfaches Java-Programm, das eine statische XML-Datei mit einem XSLT-Stylesheet nach HTML transformiert. Mit einem einfachen Programm zu beginnen hat hauptsächlich den Vorteil, daß eventuelle Probleme mit Ihrer Entwicklungsumgebung, besonders CLASSPATH-bezogene Probleme, isoliert werden können, bevor Sie mit komplexeren Aufgaben weitermachen.

Wir werden zwei Versionen unseres Java-Programms schreiben, eine für Xalan und eine andere für SAXON. Eine darauffolgende JAXP-Implementierung wird dann zeigen, wie derselbe Code für viele verschiedene Prozessoren verwendet werden kann.

CLASSPATH-Probleme

Probleme mit dem CLASSPATH sind oft die Ursache, wenn Ihr Code nicht funktioniert. Das gilt besonders bei XML-bezogenen APIs. Da sehr viele Tools mittlerweile XML einsetzen, liegt es nahe, daß auf Ihrem System einige verschiedene DOM- und SAX-Implementierungen vorliegen. Bevor Sie irgendein Beispiel in diesem Kapitel ausprobieren, stellen Sie also am besten sicher, daß in Ihrem CLASSPATH keine älteren Parser aufgeführt sind.

Wesentlich subtilere Probleme können auftreten, wenn sich im Java 2 optional packages-Verzeichnis eine ältere Bibliothek befindet. Jede JAR-Datei, die im Verzeichnis jre/lib/ext gefunden wird, ist für die JVM automatisch verfügbar, ohne dem CLASSPATH hinzugefügt zu werden. Sie sollten nach Dateien wie jaxp.jar und parser.jar schauen, die ältere, inkompatible XML-APIs enthalten könnten. Wenn Probleme auftreten, entfernen Sie alle JAR-Dateien aus dem Verzeichnis mit den optionalen Paketen.

Leider müssen Sie ein wenig Detektivarbeit leisten, wenn Sie herausfinden wollen, woher die JAR-Dateien kamen. Obwohl Java 2 Version 1.3 verbesserte JAR-Funktionen, wie Versionsinformationen, eingeführt hat, werden die meisten JAR-Dateien, denen Sie begegnen, diese Funktionalität mit Sicherheit nicht bereitstellen.

Das Design

Das Design der Anwendung ist ziemlich einfach. Eine einzelne Klasse enthält eine main( )-Methode, die die Transformation durchführt. Die Anwendung erfordert zwei Parameter: den XML-Dateinamen, gefolgt vom XSLT-Dateinamen. Die Ergebnisse der Transformation werden dann schlicht auf System.out ausgegeben. Wir werden für unser Beispiel die folgenden XML-Daten verwenden:

<?xml version="1.0" encoding="ISO-8859-1"?>
<meldung>Jawohl, es hat geklappt!</meldung>

Das folgende XSLT-Stylesheet wird eingesetzt. Seine Ausgabemethode ist auf text gesetzt, und es gibt lediglich den Inhalt des <meldung>-Elements aus. In diesem Fall also den Text Jawohl, es hat geklappt!.

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text" encoding="ISO-8859-1"/>
    <!-- die Meldung wird einfach in den Ergebnisbaum kopiert -->
    <xsl:template match="/">
        <xsl:value-of select="meldung"/>
    </xsl:template>
</xsl:stylesheet>

Da die Dateinamen als Kommandozeilenparameter übergeben werden, kann die Anwendung auch mit anderen XML- und XSLT-Dateien genutzt werden. Vielleicht möchten Sie ja eins der Bundespräsidenten-Beispiele aus den vorherigen Seiten.

Xalan 1-Implementierung

Der komplette Code für die Xalan-Implementierung ist im folgenden Beispiel aufgeführt. Wie die Kommentare im Quelltext anzeigen, wurde er mit Xalan 1.2.2, dem gegenwärtig aktuellen XSLT-Prozessor von Apache, entwickelt und getestet. Für den Xalan-spezifischen Code werden voll-qualifizierte Klassennamen, wie org.apache.xalan.xslt.XSLTProcessor, verwendet.

Hinweis:
Ein Xalan 2-Beispiel wird hier nicht gezeigt, weil Xalan 2 zu Suns JAXP kompatibel ist. Die JAXP-Version dieses Programms läuft mit Xalan 2 genauso, wie mit jedem anderen JAXP-kompatiblen Prozessor.

Beispiel: BeispielXalan1.java

package kapitel5;

import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import org.xml.sax.SAXException;


/**
* Ein einfaches Beispiel für Xalan 1. Der Code wurde im Original mit
* Xalan 1.2.2 geschrieben und funktioniert nicht mit Xalan 2.
*/
public class BeispielXalan1 {
  
  /**
   * Nimmt zwei Kommandozeilenparameter an: den Namen einer XML-Datei und
   * den Namen eines XSLT-Stylesheets. Das Ergebnis der Transformation
   * wird auf stdout ausgegeben.
   */
  public static void main(String[] args)
      throws MalformedURLException, SAXException {
    if (args.length != 2) {
      System.err.println("Syntax:");
      System.err.println(" java " + BeispielXalan1.class.getName( )
        + " xmlDateiname xsltDateiname");
      System.exit(1);
    }
        
    String xmlDateiname = args[0];
    String xsltDateiname = args[1];
        
    String xmlSystemId = new File(xmlDateiname).toURL().toExternalForm( );
    String xsltSystemId = new File(xsltDateiname).toURL().toExternalForm( );
        
    org.apache.xalan.xslt.XSLTProcessor processor =
      org.apache.xalan.xslt.XSLTProcessorFactory.getProcessor( );
        
    org.apache.xalan.xslt.XSLTInputSource xmlInputSource =
      new org.apache.xalan.xslt.XSLTInputSource(xmlSystemId);
        
    org.apache.xalan.xslt.XSLTInputSource xsltInputSource =
      new org.apache.xalan.xslt.XSLTInputSource(xsltSystemId);
        
    org.apache.xalan.xslt.XSLTResultTarget ergebnisBaum =
      new org.apache.xalan.xslt.XSLTResultTarget(System.out);
        
    processor.process(xmlInputSource, xsltInputSource, ergebnisBaum);
  }
}

Der Code beginnt mit der üblichen Liste von Import-Anweisungen und der Klassendeklaration, gefolgt von einer einfachen Überprüfung, ob beide Kommandozeilenparameter übergeben wurden. Wenn alles in Ordnung ist, dann werden der XML- und XSLT-Dateiname in System-Identifikator-Werte konvertiert:

String xmlSystemId = new File(xmlDateiname).toURL().toExternalForm( );
String xsltSystemId = new File(xsltDateiname).toURL().toExternalForm( );

System-Identifier sind Teil der XML-Spezifikation und bedeuten exakt dasselbe wie ein URI (Uniform Resource Identifier). Eine URL (Uniform Resource Locator) ist eine spezielle Form des URI und kann für Methoden verwendet werden, die System-Identifier als Parameter erwarten. Aus Java-Programmierperspektive heißt das, ein plattformspezifischer Dateiname, wie C:\daten\simpel.xml, muß erst in file:///C:/daten/simpel.xml konvertiert werden, bevor er von den meisten XML-APIs genutzt werden kann. Der hier gezeigte Code macht diese Konvertierung und funktioniert auf Unix, Windows und anderen Plattformen, die Java unterstützen. Obwohl man versuchen könnte, dem Dateinamen manuell die Zeichenfolge file:/// voranzustellen, kann es sein, daß der Code dann nicht mehr portierbar ist. Die Dokumentation zu java.io.File besagt eindeutig, daß die toURL( )-Methode eine systemabhängige URL erzeugt, so daß die Ergebnisse sich unterscheiden, wenn derselbe Code auf einer Nicht-Windows-Plattform ausgeführt wird. Tatsächlich ist es so, daß der Code unter Windows eine nicht standardkonforme URL (mit einem einzelnen Schrägstrich) erzeugt, die in Java-Programmen aber trotzdem funktioniert: file:/C:/daten/simpel.xml.

Nun, da wir System-Identifier für unsere beiden Eingabedateien haben, wird eine Instanz des XSLT-Prozessors erzeugt:

org.apache.xalan.xslt.XSLTProcessor processor =
     org.apache.xalan.xslt.XSLTProcessorFactory.getProcessor( );

XSLTProcessor ist ein Interface und XSLTProcessorFactory dient der Erzeugung neuer Instanzen von Klassen, die das Interface implementieren. Da Xalan Open Source-Software ist, läßt sich leicht herausfinden, daß XSLTEngineImpl die Klasse ist, die das Interface XSLTProcessor implementiert, allerdings sollte man Code vermeiden, der von einer spezifischen Implementierung abhängt.

Die nächsten Codezeilen erzeugen XSLTInputSource-Objekte, eins für die XML-Datei und ein anderes für die XSLT-Datei:

org.apache.xalan.xslt.XSLTInputSource xmlInputSource =
     new org.apache.xalan.xslt.XSLTInputSource(xmlSystemId);

org.apache.xalan.xslt.XSLTInputSource xsltInputSource =
     new org.apache.xalan.xslt.XSLTInputSource(xsltSystemId);

XSLTInputSource ist eine Unterklasse von org.xml.sax.InputSource, die die Fähigkeit hinzufügt, direkt aus einem DOM-Knoten zu lesen. XSLTInputSource hat die Fähigkeit, XML- und XSLT-Daten neben einer System-ID aus java.io.InputStream, java.io.Reader, org.w3c.dom.Node oder einer existierenden InputSource zu lesen. Wie im Code gezeigt, wird die Datenquelle im Konstruktor angegeben. XSLTInputSource hat auch einen Konstruktor ohne Argumente und get/set-Methoden für jeden der unterstützten Datenquellen-Typen.

Als nächstes wird eine Instanz von XSLTResultTarget ezeugt, die das Ergebnis der Transformation an System.out sendet:

org.apache.xalan.xslt.XSLTResultTarget ergebnisBaum =
     new org.apache.xalan.xslt.XSLTResultTarget(System.out);

Ähnlich der XSLTInputSource, kann XSLTResultTarget ebenfalls als Wrapper für eine Instanz von org.w3c.dom.Node, einen OutputStream oder Writer, einen Dateinamen (keine System-ID!) oder eine Instanz von org.xml.sax.DocumentHandler dienen.

Die letzte Codezeile weist den Prozessor nur noch an, die Transformation auszuführen:

processor.process(xmlInputSource, xsltInputSource, ergebnisBaum);

SAXON-Implementierung

Zum Vergleich sehen Sie im folgenden Beispiel eine SAXON 5.5.1-Implementierung. Wenn Sie den Code durchgehen, werden Sie auf das Wort »trax« stoßen, das in den Java-Packages auftaucht. Das deutet an, daß die Version 5.5.1 von SAXON sich auf etwas hinentwickelte, das den Namen Transformation API for XML (TrAX) trägt. Mehr Informationen zu TrAX gibt es in der JAXP-Besprechung. Kurz zusammengefaßt, liefert TrAX eine einheitliche API, die mit jedem XSLT-Prozessor funktionieren soll.

Beispiel: BeispielSaxon.java

package kapitel5;

import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import org.xml.sax.SAXException;

/**
 * Ein einfaches SAXON-Beispiel. Der Code wurde ursprünglich mit SAXON 5.5.1
 * erstellt.
 */
public class BeispielSaxon {
  
    /**
     * Akzeptiert zwei Kommandozeilenparameter: den Namen der XML-Datei und
     * den Namen des XSLT-Stylesheets. Das Ergebnis der Transformation wird auf stdout
     * ausgegeben.
     */
    public static void main(String[] args)
           throws MalformedURLException, IOException, SAXException {
      if (args.length != 2) {
          System.err.println("Syntax:");
          System.err.println(" java " + BeispielSaxon.class.getName( )
                 + " xmlDateiame xsltDateiname");
          System.exit(1);
      }
             
      String xmlDateiname = args[0];
      String xsltDateiname = args[1];
             
      String xmlSystemId = new File(xmlDateiname).toURL().toExternalForm( );
      String xsltSystemId = new File(xsltDateiname).toURL().toExternalForm( );
             
      com.icl.saxon.trax.Processor processor =
               com.icl.saxon.trax.Processor.newInstance("xslt");
             
      // Anders als Xalan benutzt SAXON die InputSource von SAX. Xalan
      // dagegen verwendet seine eigene Klasse, XSLTInputSource
      org.xml.sax.InputSource xmlInputSource =
               new org.xml.sax.InputSource(xmlSystemId);
      org.xml.sax.InputSource xsltInputSource =
               new org.xml.sax.InputSource(xsltSystemId);
             
      com.icl.saxon.trax.Result ergebnis =
               new com.icl.saxon.trax.Result(System.out);
             
      // erzeuge neues kompiliertes stylesheet
      com.icl.saxon.trax.Templates templates =
               processor.process(xsltInputSource);
             
      // erzeuge einen Transformer für eine einzelne Transformation
      com.icl.saxon.trax.Transformer trans = templates.newTransformer( );
      trans.transform(xmlInputSource, ergebnis);
    }
}

Die SAXON-Implementierung beginnt exakt wie die Xalan-Implementierung. Auf die Klassendeklaration folgend werden die Kommandozeilenparameter überprüft und dann in System-IDs konvertiert. Die XML- und XSLT-System-IDs werden von org.xml.sax.InputSource wie folgt umhüllt:

org.xml.sax.InputSource xmlInputSource =
     new org.xml.sax.InputSource(xmlSystemId);
org.xml.sax.InputSource xsltInputSource =
     new org.xml.sax.InputSource(xsltSystemId);

Dieser Code ist praktisch nicht vom Xalan-Code zu unterscheiden, mit der Ausnahme, daß Xalan XSLTInputSource statt InputSource verwendet. Wie bereits erwähnt, ist XSLTInputSource lediglich eine Unterklasse von InputSource, die die Unterstützung zum Einlesen aus einem DOM-Knoten hinzufügt. SAXON hat auch die Fähigkeit, aus einem DOM-Knoten zu lesen, obwohl sein Ansatz sich geringfügig unterscheidet.

Bei der Erzeugung eines Result-Objekts wird das Ziel des XSLT-Ergebnisbaums festgelegt, das in diesem Beispiel System.out ist:

com.icl.saxon.trax.Result ergebnis =
    new com.icl.saxon.trax.Result(System.out);

Das XSLT-Stylesheet wird dann kompiliert, wodurch es als Objekt vorliegt und wiederholt für parallele Threads verwendet werden kann.

com.icl.saxon.trax.Templates templates =
    processor.process(xsltInputSource);

In einer typischen XML- und XSLT-Website werden zwar die XML-Daten dynamisch erzeugt, aber dieselben Stylesheets wiederholt verwendet. Stylesheets, die beispielsweise allgemeine Kopfzeilen, Fußzeilen und Navigationsleisten erzeugen, werden von vielen Seiten verwendet werden. Um die Performance zu maximieren, sollte man die Stylesheets einmal verarbeiten und die Instanzen für viele Clients zur selben Zeit wiederzuverwenden. Aus diesem Grund ist die Verwendbarkeit von Templates im Zusammenhang mit Threads ein wichtiger Punkt.

Eine Instanz der Transformer-Klasse führt die eigentliche Transformation durch. Anders als das Stylesheet selbst kann der Transformer nicht von vielen Clients gleichzeitig genutzt werden und somit nicht in Verbindung mit Threads eingesetzt werden. Wenn dies eine Servlet-Implementierung wäre, müßte die Transformer-Instanz also für jeden Aufruf von doGet oder doPost neu erzeugt werden. In unserem Beispiel lautet der Code wie folgt:

com.icl.saxon.trax.Transformer trans = templates.newTransformer( );
trans.transform(xmlInputSource, ergebnis);

SAXON, Xalan oder TrAX?

Wie die vorangegangenen Beispiele zeigen, haben SAXON und Xalan viele Gemeinsamkeiten. Während solche Gemeinsamkeiten es leicht machen, die verschiedenen APIs zu lernen, führen Sie doch nicht zu portierbarem Code. Wenn Sie Code unmittelbar gegen eines dieser Interfaces schreiben, ketten Sie sich selbst an diese bestimmte Implementierung, jedenfalls solange Sie Ihre Anwendung nicht neu schreiben möchten.

Die andere Option besteht darin, eine Fassade für die beiden Prozessoren zu schreiben, die ein konsistentes Interface darstellt, das mit jedem Prozessor hinter den Kulissen funktioniert. Das einzige Problem mit diesem Ansatz ist, daß Sie die Implementierung der Fassade anpassen müssen, wenn neue Prozessoren eingeführt werden. Für einen einzelnen oder eine Organisation würde es also schwer werden, mit den schnellen Änderungen in der Welt der XSLT-Prozessoren mitzuhalten.

Wäre die Fassade jedoch ein offener Standard und würde er von einer ausreichend großen Zahl an Nutzern unterstützt, würden die Leute und Firmen, die die XSLT-Prozessoren schreiben, unter dem Druck stehen, sich an die allgemeine API zu halten und nicht umgekehrt. TrAX wurde im Frühjahr 2000 als Versuch initiiert, eine konsistente API für jeden XSLT-Prozessor zu definieren.

Da einer der Leute hinter TrAX auch für die Implementierung einer der großen XSLT-Prozessoren verantwortlich war, wurde schnell akzeptiert, daß TrAX ein De-facto-Standard sein würde, ähnlich wie es mit SAX der Fall ist.

   

<< zurück vor >>

 

 

 

Tipp der data2type-Redaktion:
Zum Thema Java & XSLT bieten wir auch folgende Schulungen zur Vertiefung und professionellen Fortbildung an:

Copyright © 2002 O'Reilly Verlag GmbH & Co. KG
Für Ihren privaten Gebrauch dürfen Sie die Online-Version ausdrucken.
Ansonsten unterliegt dieses Kapitel aus dem Buch "Java und XSLT" denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

O’Reilly Verlag GmbH & Co. KG, Balthasarstraße 81, 50670 Köln, kommentar(at)oreilly.de