XSLT als Code-Generator

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

Aus Performance-Gründen liefern EJB-Komponenten typischerweise dependent objects und keine einzelnen Variablen zurück. Diese Klassen sind nur lesbar und enthalten eine Gruppe ähnlicher Variablen. Ein typisches abhängiges Objekt ist im folgenden Beispiel zu sehen. Es handelt sich um ein Beispiel aus dem Buch Enterprise JavaBeans von Richard Monson-Haefel (O’Reilly).

Beispiel: Address.java

public class Address implements java.io.Serializable {
  
  private String street;
  private String city;
  private String state;
  private String zip;
  
  /**
   * Erzeuge eine Instanz eines abhängigen Objekts.
   */
  public Address(String street, String city, String state, String zip) {
    this.street = street;
    this.city = city;
    this.state = state;
    this.zip = zip;
  }
  
  public String getStreet( ) {
    return this.street;
  }
  
  public String getCity( ) {
    return this.city;
  }
  
  public String getState( ) {
    return this.state;
  }
  
  public String getZip( ) {
    return this.zip;
  }
} 

Anstelle vieler einzelner Methoden kann ein Entity-Bean eine einzige Methode bereitstellen, um eine Instanz von Address zu holen. Das reduziert die Netzwerklast und die Zugriffe auf die Datenbank und macht den Code leichter verständlich. Wie Sie sehen, ist die Klasse Address ziemlich unkompliziert. Sie besitzt einen Konstruktor, der alle Instanzvariablen initialisiert, und eine Menge von get-Methoden.

Im Gegensatz zu Address können andere abhängige Objekte sehr viele Variablen besitzen. Diese einzugeben kann sehr aufwendig sein und ist wohl eher eine Tippübung als kreative Programmierung. XSLT kann hier als einfacher Code-Generator helfen, diese aufwendige Programmierarbeit zu erledigen. AddressDO.xml aus dem folgenden Beispiel enthält die Daten, die als Eingabe für unseren Code-Generator dienen sollen.

Beispiel: AddressDO.xml

<?xml version="1.0" encoding="ISO-8859-1"?>
<dependentObject class="Address">
    <property name="street" type="String" getter="getStreet"/>
    <property name="city" type="String" getter="getCity"/>
    <property name="state" type="String" getter="getState"/>
    <property name="zip" type="String" getter="getZip"/>
</dependentObject>

Die XML-Daten sind offensichtlich viel kürzer als der erzeugte Code, und dieser Unterschied wächst für größere abhängige Objekte mit vielen Membervariablen. Das Element <dependentObject> enthält eine Liste von <property>-Elementen, von denen jedes einen Variablennamen, einen Datentyp und den Namen einer get-Methode definiert. Nun, da die Daten in einem wohldefinierten XML-Format vorliegen, kann mit einer DTD oder einem Schema eine Validierung durchgeführt werden. Ein ambitionierter Programmierer könnte ein einfaches graphisches Interface zum Editieren der Struktur des Elements <dependentObject> erstellen.

Die eigentliche Code-Erzeugung wird von einem XSLT-Stylesheet durchgeführt. Als Ausgabemethode sollte text eingestellt werden. Besondere Aufmerksamkeit liegt hier auf dem Whitespace. Für HTML- oder XHTML-Ausgaben spielt Whitespace keine Rolle. Da Browser mehrere Leerzeichen und Zeilenvorschübe in ein Leerzeichen zusammenfassen, kann das XSLT-Stylesheet beliebig formatiert werden. Bei einem Code-Generator spielt die Formatierung dagegen eine wichtige Rolle. Das kann dazu führen, daß die Stylesheets wesentlich schwerer zu lesen sind, was der größte Nachteil von XSLT beim Einsatz als Code-Generator ist. Das folgende Beispiel zeigt das Code-Generator-Stylesheet für abhängige Objekte.

Beispiel: dependentObject.xslt

<?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"/>
    <xsl:variable name="className" select="/dependentObject/@class"/>
    <!--
        ********************************************************************
        ** Erzeuge den Rumpf der Klasse. Die anderen Templates erzeugen
        ** jeweils Teile der Klasse.
        ***************************************************************** -->
    <xsl:template match="/dependentObject">public class <xsl:value-of select="$className"/>
        <xsl:text> implements java.io.Serializable {
  </xsl:text>
        <xsl:apply-templates select="property" mode="generateField"/>
        <xsl:text>
        /** 
         *Erzeuge eine neue Instanz eines abhängigen Objektes.
         */
        public</xsl:text>
        <xsl:value-of select="$className"/>(<xsl:apply-templates select="property" mode="generateConstructorParam"/>
        <xsl:text>) {</xsl:text>
        <xsl:apply-templates select="property" mode="generateInitializers"/>
        }
        <xsl:apply-templates select="property" mode="generateGetter"/>
        }
    </xsl:template>
    <!--
        *****************************************************************
        ** Erzeuge die Deklaration der privaten Instanzvariablen.
        ************************************************************** -->
    <xsl:template match="property" mode="generateField">private <xsl:value-of select="@type"/>
        <xsl:text> </xsl:text>
        <xsl:value-of select="@name"/>;</xsl:template>
    <!--
        *****************************************************************
        ** Erzeuge eine "get"-Methode für jede Eigenschaft.
        ************************************************************** -->
    <xsl:template match="property" mode="generateGetter">public <xsl:value-of select="@type"/>
        <xsl:text> </xsl:text>
        <xsl:value-of select="@getter"/>( ) {return this.<xsl:value-of select="@name"/>;}
    </xsl:template>
    <!--
        *****************************************************************
        ** Erzeuge einen Konstruktor-Parameter.
        ************************************************************** -->
    <xsl:template match="property" mode="generateConstructorParam">
        <xsl:text xml:space="preserve"/>
        <xsl:value-of select="@type"/>
        <xsl:text> </xsl:text>
        <xsl:value-of select="@name"/>
        <xsl:if test="position() != last( )">, </xsl:if>
    </xsl:template>
    <!--
        *****************************************************************
        ** Erzeuge den Code für die Initialisierung im Konstruktor.
        ************************************************************** -->
    <xsl:template match="property" mode="generateInitializers">
        <xsl:text xml:space="preserve">    this.</xsl:text>
        <xsl:value-of select="@name"/>
        <xsl:text> = </xsl:text>
        <xsl:value-of select="@name"/>;
    </xsl:template>
</xsl:stylesheet>

Dieses Stylesheet erzeugt den Code für Address.java. Zuerst wird die Ausgabemethode auf text gesetzt und eine Variable für den Klassennamen angelegt. Durch die Variable muß nicht jedesmal <xsl:value-of select="/dependentObject/@class"/> verwendet werden, wenn der Klassenname benötigt wird.

In Stylesheets, die als Code-Generator dienen, wird häufig das Element <xsl:text> verwendet, da es eine bessere Kontrolle über Whitespace erlaubt. An mehreren Stellen wird dieses Element verwendet, um Zeilenvorschübe in der Ausgabe zu erzeugen. Zum Beispiel:

    <xsl:text> implements java.io.Serializable {
</xsl:text>

Da das schließende Tag auf der nächsten Zeile liegt, wird der Zeilenvorschub in die Ausgabe übernommen. Mit <xsl:text> werden auch einzelne Leerzeichen erzeugt:

private <xsl:value-of select="@type"/>
<xsl:text> </xsl:text>
<xsl:value-of select="@name"/>;</xsl:template>

XSLT-Prozessoren ignorieren Whitespace zwischen zwei XSLT-Elementen, solange nicht auch andere Zeichen vorhanden sind. Der Text private direkt vor <xsl:value-of select="@type"/> enthält z.B. Zeichen gefolgt von einem Leerzeichen. In diesem Fall bleibt das Leerzeichen nach private erhalten. Aber das Leerzeichen zwischen den beiden <xsl:value-of>-Elementen wird unterdrückt, wenn es nicht explizit mit <xsl:text> </xsl:text> erhalten wird.

Die korrekte Formatierung der Ausgabe stellt eine Herausforderung, aber kein unüberwindbares Problem dar. Es erfordert gewöhnlich einige XSLT-Optimierungen, bis alles gut aussieht. Ein Code-Formatierer stellt hier eine Alternative dar. Produkte wie JIndent können Java-Code automatisch säubern, indem lange Zeilen umgebrochen und Leerzeichen und Klammern an den richtigen Stellen eingefügt werden. Wenn Ihnen ein solches Werkzeug zur Verfügung steht, können Sie die Formatierung im XSLT-Stylesheet ignorieren und hierfür nach der Transformation JIndent verwenden.

   

<< 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