Java, XSLT und WML

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

   

   

Abgesehen von Webanwendungen für firmeninterne Intranets müssen Webanwendungen für eine große Vielzahl von Gerätetypen entwickelt werden. Wie die folgenden Beispiele zeigen werden, unterscheiden sich mobile Geräte viel stärker untereinander als normale Webbrowser. Das verstärkt den Bedarf einer klaren Trennung zwischen Daten und Präsentation, die mit XML und XSLT möglich wird. Es werden viele verschiedene Präsentationsformen benötigt, die für je ein bestimmtes Gerät optimiert sind. Java-Servlets stellen das Bindeglied dar, erkennen den Gerätetyp des Clients und steuern die XSLT-Transformation.

Ein Beispiel mit WML

WML ist eine relativ neue XML-basierte Markup-Sprache, die speziell für mobile Geräte entworfen wurde. Als solche ist sie kompakt, einfach zu parsen und für kleine Displays optimiert. WML ist das Produkt des WAP-Forums, einer Vereinigung von über 500 Mitgliedsfimen, die Spezifikationen für mobile Geräte definiert (Anmerkung der data2type-Redaktion: heutzutage Teil der Open Mobile Alliance). Weitere Informationen zu WML bietet das Buch Learning WML and WMLScript von Martin Frost (O’Reilly). (Dieses Buch ist nicht mehr lieferbar, Sie finden es aber bei oreilly.com im Rahmen von Safari Books Online.)

Zunächst ist WML eine XML-basierte Markup-Sprache. (WML-Dokumente sind XML-Dokumente gemäß einer WML-DTD.) Das bedeutet, daß alle WML-Dokumente im Unterschied zu HTML wohlgeformt und gültig sein müssen. Alle Tags müssen z.B. klein geschrieben und korrekt verschachtelt werden, und die Werte von Attributen müssen in Anführungsstrichen stehen. Das folgende Beispiel zeigt ein WML-Dokument.

Beispiel: Eine einfache WML-Seite

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN" "http://www.wapforum.org/DTD/wml_1.1.xml">
<wml>
    <card id="home" title="Namenseintrag">
        <p>Vorname: <input name="vorname"/></p>
        <p>Alter: <input name="alter" format="*N"/></p>
        <do type="accept">
            <go href="#hallo"/>
        </do>
    </card>
    <card id="hallo" title="Hallo">
        <p>Hallo $(vorname:e)!</p>
        <p>Sie behaupten <em>$(alter:e)</em> Jahre alt zu sein. Kann das stimmen?</p>
        <p>Klicken Sie <a href="#home">hier</a> um Ihre Antwort zu ändern.</p>
    </card>
</wml>

Dieses spezielle WML-Dokument gehorcht Version 1.1 der WML-Spezifikation. Im Gegensatz zu HTML heißt das Wurzelelement <wml> und ist allgemein auch als deck oder Stapel bekannt. Ein WML-Stapel enthält ein oder mehrere <card>-Elemente, also »Karten«, von denen jedes einen Bildschirm auf dem Gerät darstellt. Die Gruppierung von Karten in Stapeln steigert die Performance, da das mobile Gerät weniger Serveranfragen absetzen muß, wenn der Nutzer von einer Karte zur anderen navigiert.

Die erste Karte des Beispiels veranlaßt den Nutzer, seinen Vornamen und sein Alter einzugeben. Anders als bei HTML-Formularen werden die Werte in den WML-Variablen vorname und alter gespeichert. Das ist aufgrund der begrenzten Übertragungskapazitäten von mobilen Geräten besser, als ein Formular zum Server zu senden. Das Feld Alter demonstriert eine weitere Eigenschaft von WML, die HTML nicht aufweist:

<input name="alter" format="*N"/>

Das hier gezeigte Attribut format besagt, daß der Nutzer jede beliebige Anzahl von Ziffern oder Dezimalpunkten eingeben kann. (Nicht alle Geräte unterstützen diese Fähigkeit.) Formate für Datumseinträge, Telefonnummern und andere Muster können durch einfache Format-Strings aus der WML-Spezifikation dargestellt werden. Das ist ein großer Vorteil gegenüber traditionellen Webtechniken, bei denen für eine clientseitige Validierung Skriptsprachen eingesetzt werden müssen. Die folgende Tabelle zeigt alle Formatvorgaben von WML 1.1.

Tabelle: Format-Strings in WML

Format Anzeige
A Großbuchstaben, Symbole und Interpunktionszeichen; keine Ziffern.
a Kleinbuchstaben, Symbole und Interpunktionszeichen; keine Ziffern.
N Jede beliebige Ziffer.
n Ziffern, Symbole und Interpunktionszeichen.
X Wie A, aber mit Ziffern.
x Wie a, aber mit Ziffern.
M Alle Zeichen, aber das Gerät soll versuchen, Großbuchstaben zu verwenden.
m Alle Zeichen, aber das Gerät soll versuchen, Kleinbuchstaben zu verwenden.
*f Beliebige Anzahl der angegebenen Zeichen; f ist ein Format-Code aus dieser Tabelle; muß am Ende des Format-Strings stehen.
nf n ist eine Zahl von 1 bis 9, die die maximale Anzahl von Zeichen angibt; f ist ein Format-Code aus dieser Tabelle; muß am Ende des Format-Strings stehen.
\c Zeigt ein bestimmtes Zeichen an; NNN\-NN\-NNNN spezifiziert z.B. das Datenformat für Sozialversicherungsnummern der USA, der Nutzer kann also eine Zahl wie 333-22-4444 eingeben.

Die erste Karte aus dem obigen Beispiel Eine einfache WML-Seite endet mit dem Element <do>:

<do type="accept">
  <go href="#hallo"/>
</do>

Es erzeugt einen Button, der einen Hyperlink zur URL #hallo und somit eine Referenz auf die zweite Karte im Stapel darstellt. Deshalb wird beim Betätigen dieses Buttons nicht der Server kontaktiert. Die folgenden Abbildungen zeigen, wie ein bestimmtes Gerät die erste und zweite Karte darstellt.

Ein Simulator für ein Mobiltelephon - erste Karte

Ein Simulator für ein Mobiltelephon - zweite Karte

Abbildungen: Ein Simulator für ein Mobiltelephon

Wie Sie sehen, stellt die Karte die Werte dar, die in der ersten Karte eingegeben wurden. (Beachten Sie, daß dieses Gerät das Tag <em> beim Darstellen des Alters nicht berücksichtigt.) Hier ist ein Code-Ausschnitt:

Hallo $(vorname:e)!

Er zeigt, wie WML-Variablen eingesetzt werden. Das :e am Ende des Variablennamens ist optional und veranlaßt das Gerät vor der Darstellung des Textes, ein URL-Escaping durchzuführen. Dies ist nützlich, falls der Nutzer Leerzeichen oder Zeichen wie < eingibt, die zu Problemen mit WML führen können.

Hyperlinks in WML sehen wie in HTML aus:

<p>Klicken Sie <a href="#home">hier</a>, um

Das größte Problem beim Einsatz von WML ist die große Vielfalt an Geräten. Die folgende Abbildung zeigt dieselben zwei Karten auf einem Mobiltelefon mit kleinerem Display.

Ein anderer Simulator für ein Mobiltelefon

Abbildung: Ein anderer Simulator für ein Mobiltelefon

Wie man sieht, paßt die erste Karte nicht auf das Display, so daß der Nutzer scrollen muß. Bei der zweiten Karte wird das Tag für die Hervorhebung (<em>) bei der Darstellung des Alters, im Gegensatz zum ersten Browser aus der Abbildung Ein Simulator für ein Mobiltelephon, berücksichtigt. Obwohl solche Unterschiede im Laufe der Zeit verschwinden sollten, weil die Hersteller die vollständige WML-Spezifikation umsetzen, gibt es keine Garantie für die Darstellung von Buttons und <input>-Feldern. Oft werden <do>-Elemente auf physische Tasten des Gerätes abgebildet und nicht auf dem Display dargestellt.

WMLScript und WBMP

Wegen der geringen Übertragungsraten ist die Unterstützung von Skriptsprachen für mobile Geräte unerläßlich. WAP definiert die Skriptsprache WMLScript, die eng mit WML verkoppelt und deren Syntax stark an JavaScript angelehnt ist. Mit WMLScript können Validierungen und einfache Verarbeitungen auf dem Client durchgeführt werden, so daß die Anzahl von Serveranfragen stark reduziert wird.

Wireless Bitmap (WBMP) ist ein sehr einfaches Bilddatenformat, das für kleine Geräte optimiert ist. WBMP-Bilder sind schwarz-weiß und können mit einer Anzahl frei erhältlicher Werkzeuge erstellt werden.

In Learning WML & WMLScript finden Sie eine umfassende Einführung in diese Technologien.

Servlets und WML

Servlets sind für Entwickler mobiler Dienste wichtig, da sie den Typ des Clients erkennen können. Für normale Webbrowser, komplexe PDAs und einfache Mobiltelephone können dann verschiedene XSLT-Stylesheets ausgewählt werden.

Identifizierung des Clients

Die Erkennung des Clienttyps ist die wichtigste Aufgabe des Servlets. Für diesen Zweck werden zwei bestimmte Werte des HTTP-Headers verwendet: User-Agent und Accept. Der Text im folgenden Beispiel zeigt den HTTP-Header für den Simulator des Ericsson R520m.

Beispiel: Beispiel eines HTTP-Headers

GET / HTTP/1.1
Host: 25.12.44.22
Accept: application/vnd.wap.wmlc, application/vnd.wap.wbxml,
application/vnd.wap.wmlscriptc, */*, text/vnd.wap.wml, application/xml, text/xml, text/vnd.wap.wmlscript
User-Agent: EricssonR520/R1A
Accept-Charset: *

Es handelt sich um einen Text, bei dem jede außer der ersten Zeile aus einem Name:Wert-Paar besteht. Der Header-Eintrag Accept listet die vom Gerät darstellbaren MIME-Typen auf, so daß nur nach dem String text/vnd.wap.wml gesucht werden muß, um herauszufinden, ob das Gerät WML unterstützt. Im gezeigten Beispiel könnte es sich also um ein mobiles Gerät handeln.

Warnung:
Einige Webbrowser können text/vnd.wap.wml auch darstellen. Der Header-Eintrag Accept ist also für das Bestimmen des Typs des Clients nicht hinreichend.

Der Header-Eintrag User-Agent definiert das Gerät eindeutig. Allerdings befolgen die Hersteller die Standards nicht konsistent. Die folgende Tabelle zeigt die User-Agents, die von verschiedenen Handy-Simulatoren geliefert werden.

Tabelle: Beispiele für User-Agents

Simulatortyp User-Agent
Ericsson R320s EricssonR320/R1A
Ericsson R380s R380 2.1 WAP1.1
Ericsson R520m EricssonR520/R1A
Motorola Motorola VoxGateway/2.0
Nokia Nokia-WAP-Toolkit/2.1
Openwave OWG1 UP/4.1.20a UP.Browser/4.1.20a-XXXX UP.Link/4.1.HTTP-DIRECT

Im allgemeinen folgt auf den Herstellernamen eine Modellbezeichnung, nur das Ericsson R380s hält sich nicht an diese Vereinbarung. Unter Browsererkennung wurde bereits erwähnt, daß der User-Agent fast jedes Webbrowsers mit dem String »Mozilla« beginnt, so daß sich hier eine Möglichkeit zur Unterscheidung von Webbrowsern und mobilen Geräten bietet.

Es ist sehr einfach, im Servlet auf die genannten Einträge des HTTP-Headers zuzugreifen:

protected void doGet(HttpServletRequest req, HttpServletResponse res)
     throws IOException, ServletException {
  String userAgent = req.getHeader("User-Agent");
  String accept = req.getHeader("Accept");
       
  if (userAgent != null) {
  ...

Ein umfassendes Beispiel wird im Abschnitt »Kinoauskunft« vorgestellt.

Setzen des Content-Types

Nachdem der Typ des Clients als Webbrowser oder als mobiles Gerät ermittelt wurde, kann eine Antwort gesendet werden. Die folgende Tabelle zeigt die drei gebräuchlichsten Content-Types.

Tabelle: MIME Content-Types

MIME-Type Endung Beschreibung
text/vnd.wap.wml .wml WML-Quellcode
text/vnd.wap.wmlscript .wmls WMLScript-Quellcode
image/vnd.wap.wbmp .wmlc Bitmaps für mobile Geräte

Das bedeutet, daß vor dem Senden einer WML-Antwort an das Client-Gerät der folgende Code im Servlet ausgeführt werden muß:

public void doGet(HttpServletRequest req, HttpServletResponse res) ... {
  res.setContentType("text/vnd.wap.wml");
  // hole einen PrintWriter oder einen OutputStream und führe
  // die XSLT-Transformation durch...

Für dynamisch erzeugte Seiten ist das alles, was zu tun ist. Besteht eine Webanwendung auch aus statischen Ressourcen, wie WMLScript-Dateien oder WBMP-Bildern, sollte der Anwendungsdeskriptor der Webanwendung ebenfalls angepaßt werden. Das folgende Beispiel zeigt einige Zeilen, die zum Anwendungsdeskriptor hinzugefügt werden sollten.

Beispiel: MIME-Abbildungen im Anwendungsdeskriptor

<mime-mapping>
    <extension>.wml</extension>
    <mime-type>text/vnd.wap.wml</mime-type>
</mime-mapping>
<mime-mapping>
    <extension>.wmls</extension>
    <mime-type>text/vnd.wap.wmlscript</mime-type>
</mime-mapping>
<mime-mapping>
    <extension>.wmlc</extension>
    <mime-type>image/vnd.wap.wbmp</mime-type>
</mime-mapping>

Dies benachrichtigt den Webserver über den jeweils zu verwendenden MIME-Type, wenn der Client eine Datei mit einer bestimmten Endung anfordert.

Kinoauskunft

Das war zugegebenermaßen eine sehr knappe Einführung in WML, deshalb soll nun ein ausführliches Beispiel diese Konzepte vertiefen.

Ablaufplan

Das Beispiel besteht aus drei WML-Stapeln und mehreren Karten. Über dieses Interface können Nutzer eine Stadt und ein bestimmtes Kino dieser Stadt auswählen und schließlich einen Spielplan des Kinos abrufen. Das Diagramm aus der folgenden Abbildung verdeutlicht in einem Ablaufplan, wie ein Bildschirm mit dem nächsten verbunden ist.

Ablaufplan

Abbildung: Ablaufplan

Der erste Stapel enthält einen Startbildschirm, der für 1,5 Sekunden angezeigt wird. Hierfür wird ein WML-Timer verwendet, so daß nach Ablauf dieser Zeit automatisch eine Auswahlseite angezeigt wird, auf der aus einer Liste von Städten gewählt werden kann.

Der zweite Stapel besteht aus einer einzigen Karte mit der Liste der Kinos für die gewählte Stadt. Sobald der Nutzer ein bestimmtes Kino anwählt, wird der dritte Stapel angezeigt, wobei die Anzahl der Karten hier der Anzahl der Filme dieses Kinos entspricht. Der Nutzer kann von einem Film zum anderen wechseln, ohne dabei weitere Daten vom Server anzufordern.

Das Servlet benötigt bestimmte Parameter für den dynamischen Aufbau der einzelnen Stapel und Karten. Diese Parameter werden dem XSLT-Stylesheet übergeben, so daß es die passenden Daten aus der XML-Datei auswählen kann. Die folgende Tabelle zeigt die von jedem Stapel benötigten Parameter. Diese tauchen sowohl in jeder WML-Datei als auch im Servlet und im XSLT-Stylesheet auf. Wenn ein Parameter ungültig ist oder fehlt, wird der Nutzer einfach zurück auf die Homepage befördert.

Tabelle: Benötigte Parameter

Stapel Parameter Bemerkungen
1 keiner zeigt alle Städte
2 aktion=kinos
stadt=stadt_id
zeigt die Kinos einer Stadt
3 aktion=spielplan
stadt=stadt_id
kino=kino_id
zeigt alle Filme eines Kinos aus einer bestimmten Stadt

XML-Daten

Um die Aktualisierungen für die Kinobesitzer einfach zu gestalten, erzeugt die Anwendung alle Seiten aus einer einzigen XML-Datei auf dem Server. Die DTD für diese Datei ist im folgenden Beispiel zu sehen.

Beispiel: DTD für die Kinoauskunft

<!ELEMENT filme (filmdef+, stadt+)>
<!ELEMENT filmdef (kurzName, langName)>
<!ELEMENT stadt (name, kino+)>
<!ELEMENT name (#PCDATA)>
<!ELEMENT kurzName (#PCDATA)>
<!ELEMENT langName (#PCDATA)>
<!ELEMENT kino (name, film+)>
<!ELEMENT film (zeiten)>
<!ELEMENT zeiten (#PCDATA)>
<!ATTLIST stadt
id ID #REQUIRED
>
<!ATTLIST film
ref IDREF #REQUIRED
>
<!ATTLIST filmdef
id ID #REQUIRED
>
<!ATTLIST kino
id ID #REQUIRED
>

Hier soll der Unterschied zwischen dem Element <filmdef> und <film> erläutert werden. Ein <filmdef> definiert eine kurze und eine lange Beschreibung eines Filmes. Da ein Film mit großer Wahrscheinlichkeit in mehreren Kinos läuft, macht es Sinn, einmal einen <filmdef> zu definieren und diesen aus verschiedenen Teilen des Dokuments mit einem <film>-Element zu referenzieren.

Das folgende Beispiel zeigt einen Teil einer XML-Datei, die diese DTD einhält. Es handelt sich um die Daten, die in den folgenden Abbildungen dargestellt werden.

Beispiel: Kino XML-Datei

<?xml version="1.0" encoding="ISO-8859-1"?>
<?xml-stylesheet type="text/xsl" href="../xslt/wml/spielplan.xslt"?>
<filme>
    <!-- alle Filme -->
    <filmdef id="ringe1">
        <kurzName>Der Herr der Ringe 1</kurzName>
        <langName>Der Herr der Ringe 1: Die Gefährten</langName>
    </filmdef>
    <filmdef id="ringe2">
        <kurzName>Der Herr der Ringe 2</kurzName>
        <langName>Der Herr der Ringe 2: Die zwei Türme</langName>
    </filmdef>
    <filmdef id="ringe3">
        <kurzName>Der Herr der Ringe 3:</kurzName>
        <langName>Der Herr der Ringe 3: Die Wiederkehr des Königs</langName>
    </filmdef>
    <!-- ... weitere filmdef-Elemente -->
    <stadt id="ef">
        <name>Erfurt</name>
        <kino id="cinestar">
            <name>Cinestar</name>
            <film ref="ringe1">
                <zeiten>14:15, 17:45, 21:15</zeiten>
            </film>
            <film ref="ringe2">
                <zeiten>12:00, 15:30, 19:00</zeiten>
            </film>
            <film ref="ringe3">
                <zeiten>18:00, 21:30</zeiten>
            </film>
            <film ref="bond3">
                <zeiten>17:00, 20:00</zeiten>
            </film>
        </kino>
        <kino id="ufa">
            <name>UfA-Palast</name>
            <film ref="ringe1">
                <zeiten>17:00, 20:30</zeiten>
            </film>
            <film ref="ringe2">
                <zeiten>14:30, 18:00</zeiten>
            </film>
            <film ref="ringe3">
                <zeiten>19:00, 21:00</zeiten>
            </film>
        </kino>
        <!-- ... weitere kino-Elemente -->
    </stadt>
    <!-- ... weitere stadt-Elemente -->
</filme>

Nichts im XML-Code schränkt die Ausgabe auf WML ein. Die Anwendung kann mit verschiedenen XSLT-Stylesheets sowohl XHTML- als auch WML-Ausgaben erzeugen. Natürlich ist die Unterstützung von WML das Hauptziel. Für die Unterstützung mobiler Geräte wurde das Element <kurzName> eingeführt. Wenn die Anwendung nur für Webbrowser gedacht wäre, könnte auf dieses Element verzichtet werden.

WML-Prototypen und Screenshots

Für die Erzeugung von XHTML bzw. WML mit XSLT ist es sinnvoll, zunächst Prototypen zu schreiben. Statische WML-Dateien können einfach entwickelt und dabei im Simulator getestet werden. Wenn alles funktioniert, kann das XSLT-Stylesheet für die Transformation von XML nach WML geschrieben werden.

Das folgende Beispiel zeigt den ersten WML-Stapel dieses Beispiels. Wie schon erwähnt, besteht er aus zwei Karten, von denen die erste für 1,5 Sekunden einen Anfangsbildschirm zeigt.

Beispiel: WML-Beispiel für die Homepage

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN" "http://www.wapforum.org/DTD/wml_1.1.xml">
<wml>
    <card ontimer="#home" title="KinoInfo" id="splash">
        <timer value="15"/>
        <p align="center"><big>Willkommen bei KinoInfo</big></p>
        <p>Mmmm...Popcorn...</p>
        <do type="accept">
            <go href="#home"/>
        </do>
    </card>
    <card newcontext="true" title="Home" id="home">
        <p align="center">Wählen Sie eine Stadt:
            <select multiple="false" name="stadt">
                <option value="EF">Erfurt</option>
                <option value="WE">Weimar</option>
                <option value="IL">Ilmenau</option>
            </select>
        </p>
        <p><em><a href="movieguide?aktion=kinos&amp;stadt=$(stadt)">Kinos anzeigen...</a></em></p>
    </card>
</wml>

Das Attribut ontimer der ersten Karte legt die URL fest, die nach Ablauf des Elements <timer> geladen wird. Der Timer-Wert ist 15, er wird in Zehntelsekunden angegeben. Die Karte enthält auch das Element <do>, da es dem Nutzer erlaubt, einen Button zu betätigen, falls er nicht auf den Timer warten will. Wie in XHTML steht das Element <p> für einen Textabschnitt, der auf einer neuen Zeile dargestellt wird.

Die nächste Karte enthält ein <select>-Element, mit dem der Nutzer aus einer Liste von Städten auswählen kann. Das Ergebnis der Auswahl wird der Variablen stadt zugewiesen, so daß diese Information mit dem <a>-Element an den Server gesendet werden kann:

<a href="movieguide?aktion=kinos&amp;stadt=$(stadt)">Kinos anzeigen...</a>

Hierbei handelt es sich um die URL, die in der fertigen Anwendung verwendet wird, und nicht um eine Prototyp-URL. In der Entwurfsphase ist der folgende Link besser geeignet:

<a href="kinos.wml">Kinos anzeigen...</a>

Indem die URLs auf statische WML-Dateien verweisen, kann zwischen den Seiten gewechselt werden, bevor ein Servlet geschrieben wurde. Die folgende Abbildung zeigt diese ersten beiden Seiten auf einem Handy-Simulator.

Unsere Homepage auf einem Handy-Simulator

Abbildung: Unsere Homepage auf einem Handy-Simulator

Das linke Bild zeigt den Anfangsbildschirm, der nach 1,5 Sekunden durch das rechte Bild ersetzt wird. Bei diesem Mobiltelefon wird mit den Cursor-Tasten nach oben und unten navigiert, und mit der Taste YES wird die Auswahl getroffen.

Die WML-Seite aus dem folgenden Beispiel zeigt eine Liste von Kinos für die aktuelle Stadt. In diesem Beispiel besteht die Liste aus einer Serie von Hyperlinks. Natürlich hätte auch hier das Element <select> verwendet werden können, aber bei Verwendung von Hyperlinks und <br/>-Tags kann der Nutzer die gesamte Liste sehen. Natürlich muß bei kleinen Displays auch nach unten gescrollt werden, um alle Einträge zu sehen.

Beispiel: WML-Datei für eine Kinoliste

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN" "http://www.wapforum.org/DTD/wml_1.1.xml">
<wml>
    <card title="Kinos" id="kinos">
        <p><big>Erfurt</big></p>
        <p>Wählen Sie ein Kino:</p>
        <p><a href="movieguide?aktion=spielplan&amp;stadt=ef&amp;kino=cinestar">Cinestar</a>
            <br/>
            <a href="movieguide?aktion=spielplan&amp;stadt=ef&amp;kino=ufa">UfA-Palast</a>
            <br/>
            <a href="movieguide?aktion=spielplan&amp;stadt=ef&amp;kino=citykino">CityKino</a>
            <br/>
        </p>
        <p><em><a href="movieguide">Stadt wechseln...</a></em></p>
    </card>
</wml>

Diese WML-Datei wird auf der linken Seite der folgenden Abbildung mit einem anderen Handy-Simulator dargestellt. Auf der rechten Seite der Abbildung ist eine XHTML-Repräsentation derselben Daten in einem Webbrowser zu sehen. Diese Bilder wurden mit einem Servlet und einer XML-Datei, aber verschiedenen XSLT-Stylesheets erzeugt.

Ausgabe der Kinoliste - Handy-SimulatorAusgabe der Kinoliste - Webbrowser

Abbildungen: Ausgabe der Kinoliste

Der letzte Stapel ist im folgenden Beispiel zu sehen. Er besteht aus mehreren Karten, einer für jeden Film.

Beispiel: WML-Datei für die Vorführungen

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN" "http://www.wapforum.org/DTD/wml_1.1.xml">
<wml>
    <template>
        <do name="common_prev" label="Zurück" type="prev">
            <prev/>
        </do>
    </template>
    <card title="Filme" id="filme">
        <do name="common_prev" type="prev">
            <noop/>
        </do>
        <p><big>Cinestar</big></p>
        <p>Wählen Sie einen Film:</p>
        <p>
            <a href="#ringe1">Der Herr der Ringe 1</a>
            <br/>
            <a href="#ringe2">Der Herr der Ringe 2</a>
            <br/>
            <a href="#ringe3">Der Herr der Ringe 3</a>
            <br/>
        </p>
        <p><em><a href="movieguide?aktion=kinos&amp;stadt=ef">Kino wechseln...</a></em></p>
    </card>
    <card title="Spielplan" id="ringe1">
        <p><em>Der Herr der Ringe 1: Die Gefährten</em></p>
        <p>14:15, 17:45, 21:15</p>
    </card>
    <card title="Spielplan" id="ringe2">
        <p><em>Der Herr der Ringe 2: Die zwei Türme</em></p>
        <p>12:00, 15:30, 19:00</p>
    </card>
    <card title="Spielplan" id="ringe3">
        <p><em>Der Herr der Ringe 3: Die Wiederkehr des Königs</em></p>
        <p>18:00, 21:30</p>
    </card>
</wml>

Diese WML-Datei zeigt, wie ein Stück Markup-Code als <template> definiert wird, das von allen Karten und Stapeln wiederverwendet werden kann. Dieses spezielle Template definiert den Zurück-Button (Back), der in jeder Instanz der Karte Spielplan dargestellt wird und mit dem der Nutzer zurück zur Liste von Filmen wechseln kann.

Da dieser Button nicht in der Liste von Filmen auftauchen soll, wird er wie folgt versteckt:

<do name="common_prev" type="prev">
  <noop/>
</do>

Das Element <noop/> bedeutet »Keine Operation« und entfernt das Element <do>, das im Template common_prev definiert wurde. Wird in einer Karte ein Element mit dem Namen eines Templates definiert, so hat das neue Element den Vorrang. In einer Karte kann also das Verhalten eines Templates geändert oder wie hier mit dem Element <noop/> unterdrückt werden.

Der folgende Screenshot zeigt das Aussehen dieser Karten, wenn sie in einem Handy dargestellt werden. Wie gewünscht, ist der Back-Button nicht in der Liste von Filmen, aber in der Karte Spielplan zu sehen.

WML-Ausgabe der Spielzeiten

Abbildung: WML-Ausgabe der Spielzeiten

Der letzte folgende Screenshot zeigt, wie in der großen Anzeige eines Webbrowsers die gesamten Informationen in einer einzigen Tabelle dargestellt werden können. Hier kommt ein anderes XSLT-Stylesheet zum Einsatz, das die XML-Daten nach XHTML konvertiert.

XHTML-Ausgabe der Spielzeiten

Abbildung: XHTML-Ausgabe der Spielzeiten

Obwohl WML das Element <table> definiert, wird es von vielen erhältlichen Geräten nicht unterstützt, da eine Tabelle meistens nicht auf das Display eines Handys paßt.

Implementierung des Servlets

Die Anwendung besteht aus einem einzigen Servlet, siehe das folgende Beispiel, das die folgenden Funktionen hat:

  • Parsen der Anfrageparameter und Ermitteln der nächsten darzustellenden Seite
  • Identifikation des Clients
  • Ausführen der entsprechenden XSLT-Transformation

Beispiel: MovieServlet.java

package chap10;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.xml.transform.*;
import javax.xml.transform.stream.*;

/**
 * Ein Servlet, das die Spielpläne von Kinos mehrerer Städte darstellt.
 * Unterstützt normale Webbrowser und PDAs oder Handys mit WAP-Unterstützung.
 */
public class MovieServlet extends HttpServlet {
  
  // unterstützt zwei Typen von Clients (kann natürlich erweitert werden)
  private static final int XHTML_CLIENT_TYPE = 1;
  private static final int WML_CLIENT_TYPE = 2;
  
  // diese Webanwendung besitzt drei Seiten
  private static final int HOME_PAGE = 100;
  private static final int KINOS_PAGE = 101;
  private static final int SPIELPLAN_PAGE = 102;
  
  /**
   * Dieses Servlet unterstützt GET- und POST-Anfragen.
   */
  public void doGet(HttpServletRequest req, HttpServletResponse res)
      throws IOException, ServletException {
    doPost(req, res);
  }
  
  public void doPost(HttpServletRequest req, HttpServletResponse res)
      throws IOException, ServletException {
    try {
      String aktion = req.getParameter("aktion");
      String stadt = req.getParameter("stadt");
      String kino = req.getParameter("kino");
      
      // voreingestellt ist die Homepage
      int pageToShow = HOME_PAGE;
      
      if ("kinos".equals(aktion) && stadt != null) {
        // stadt wird als Parameter der Kinoliste benötigt
        pageToShow = KINOS_PAGE;
      } else if ("spielplan".equals(aktion) && stadt != null
          && kino != null) {
        // stadt und kino werden als Parameter für spielplan benötigt
        pageToShow = SPIELPLAN_PAGE;
      }
      
      // setze den Content-Type der Antwort
      int clientType = determineClientType(req);
      switch (clientType) {
      case XHTML_CLIENT_TYPE:
        res.setContentType("text/html");
        break;
      case WML_CLIENT_TYPE:
        res.setContentType("text/vnd.wap.wml");
        break;
      default:
        res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        return;
      }
      
      File xsltFile = locateStylesheet(req, clientType, pageToShow);
      
      // bereite die Transformation mit JAXP vor
      TransformerFactory transFact = TransformerFactory.newInstance( );
      Transformer trans = transFact.newTransformer(
        new StreamSource(xsltFile));
      
      // übergib die Parameter an das XSLT-Stylesheet
      if (stadt != null) {
        trans.setParameter("stadt_id", stadt);
      }
      if (kino != null) {
        trans.setParameter("kino_id", kino);
      }
      
      // alle Seiten, ob WML oder XHTML, benutzen dieselbe XML-Datei
      InputStream xmlIn = getServletContext( ).getResourceAsStream(
        "/WEB-INF/xml/movies.xml");
      
      // führe die Transformation durch
      trans.transform(new StreamSource(xmlIn),
        new StreamResult(res.getOutputStream( )));
    } catch (TransformerException te) {
        throw new ServletException(te);
    }
  }
  
  /**
   * @param clientType eine der in dieser Klasse definierten Konstanten,
   * also WML_CLIENT_TYPE oder XHTML_CLIENT_TYPE.
   * @param pageToShow eine der _PAGE-Konstanten dieser Klasse.
   * @return den Namen des passenden XSLT-Stylesheets.
   */
  private File locateStylesheet(HttpServletRequest req,
    int clientType, int pageToShow) {
    String xsltDir = null;
    switch (clientType) {
    case WML_CLIENT_TYPE:
      xsltDir = "wml";
      break;
    case XHTML_CLIENT_TYPE:
      xsltDir = "xhtml";
      break;
    default:
      throw new IllegalArgumentException("Ungültiger Clienttyp: "
        + clientType);
    }
      
    String xsltName = null;
    switch (pageToShow) {
    case HOME_PAGE:
      xsltName = "home.xslt";
      break;
    case KINOS_PAGE:
      xsltName = "kinos.xslt";
      break;
    case SPIELPLAN_PAGE:
      xsltName = "spielplan.xslt";
      break;
    default:
      throw new IllegalArgumentException("Ungültige darzustellende Seite: "
        + pageToShow);
    }
      
    // erzeuge einen plattformabhängigen Pfad
    String fullPath = getServletContext( ).getRealPath(
      "/WEB-INF/xslt/" + xsltDir + "/" + xsltName);
    return new File(fullPath);
  }
  
  /**
   * Ermittelt den Clienttyp.
   *
   * @return XHTML_CLIENT_TYPE oder WML_CLIENT_TYPE.
   */
  private int determineClientType(HttpServletRequest req) {
    // prüfe auf normale Webbrowser, die behaupten,
    // Mozilla-kompatibel zu sein
    String userAgent = req.getHeader("User-Agent");
    if (userAgent != null
        && userAgent.toLowerCase( ).startsWith("mozilla")) {
      return XHTML_CLIENT_TYPE;
    }
    
    // akzeptiert der Client wml, handelt es sich um ein WAP-kompatibles Gerät
    String accept = req.getHeader("Accept");
    if (accept != null && accept.indexOf("text/vnd.wap.wml") > -1) {
      return WML_CLIENT_TYPE;
    }
  
    // verwende ansonsten XHTML
    return XHTML_CLIENT_TYPE;
  }
}

Das Servlet ermittelt den Clienttyp durch die Einträge User-Agent und Accept im HTTP-Header. Diese Funktionalität befindet sich in der Methode determineClientType( ), die zuerst den User-Agent auf Mozilla-kompatible Browser wie den Microsoft Internet Explorer und den Netscape Navigator prüft. Handelt es sich beim Client um keinen dieser Browser, wird im Header-Eintrag Accept der String text/vnd.wap.wml gesucht. Schlagen beide Tests fehl, wird XHTML für die Ausgabe verwendet, da das Gerät den WML-Content-Type nicht akzeptiert.

Wenn der Clientbrowser identifiziert ist, wird der Content-Type der HTTP-Antwort auf den zugehörigen MIME-Type eingestellt:

switch (clientType) {
case XHTML_CLIENT_TYPE:
  res.setContentType("text/html");
  break;
case WML_CLIENT_TYPE:
  res.setContentType("text/vnd.wap.wml");
  break;
default:
  res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
  return;
}

Der Default-Zweig wird nur betreten, wenn das Servlet fehlerhaft ist. Deshalb wird einfach ein interner Serverfehler ausgelöst. Die Hilfsmethode locateStylesheet( ) ermittelt dann das passende XSLT-Stylesheet:

File xsltFile = locateStylesheet(req, clientType, pageToShow);

In dieser Anwendung werden zwei Gruppen von XSLT-Stylesheets verwendet, eine befindet sich im Verzeichnis wml, die andere im Verzeichnis xhtml. Wie in den Beispielen vorheriger Kapitel wird ServletContext verwendet, um den Ort dieser Dateien auf portable Weise zu bestimmen:

String fullPath = getServletContext( ).getRealPath("/WEB-INF/xslt/" + xsltDir + "/" + xsltName);

Schließlich und endlich wird die XSLT-Transformation unter Verwendung der JAXP-API durchgeführt.

XSLT-Stylesheets

Die Anwendung enthält sechs XSLT-Stylesheets, wobei hier nur die drei Stylesheets zur Erzeugung der WML-Stapel aufgelistet werden. Die drei anderen generieren den XHTML-Code und können mit dem Rest der Beispiele dieses Buches heruntergeladen werden. Das erste Stylesheet im folgenden Beispiel erzeugt den Stapel für die Homepage.

Beispiel: Homepage-XSLT

<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
 ***********************************************************************
 ** Erzeugt die Homepage für Geräte mit WML-Unterstützung.
 ******************************************************************** -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="ISO-8859-1" indent="yes" doctype-public="-//WAPFORUM//DTD WML 1.1//EN" doctype-system="http://www.wapforum.org/DTD/wml_1.1.xml"/>
    <!--
     *********************************************************************
     ** Das Root-Template erzeugt den Stapel und die Home-Karte.
     ****************************************************************** -->
    <xsl:template match="/filme">
        <wml>
            <!-- rufe das Template zur Erzeugung des Anfangsbildschirms auf -->
            <xsl:call-template name="createSplashCard"/>
            <card id="home" title="Home" newcontext="true">
                <p align="center">Wählen Sie bitte Ihre Stadt aus: <select name="stadt" multiple="false"><xsl:apply-templates select="stadt"/></select></p>
                <p><em><a href="movieguide?aktion=kinos&amp;stadt=$(stadt)">Zeige Kinos...</a></em></p>
            </card>
        </wml>
    </xsl:template>
    <!--
     *********************************************************************
     ** Erzeuge ein <option>-Element für eine Stadt
     ****************************************************************** -->
    <xsl:template match="stadt">
        <option value="{@id}">
            <xsl:value-of select="name"/>
        </option>
    </xsl:template>
    <!--
     *********************************************************************
     ** Erzeuge den Anfangsbildschirm.
     ****************************************************************** -->
    <xsl:template name="createSplashCard">
        <card id="splash" title="KinoInfo" ontimer="#home">
            <timer value="15"/>
            <p align="center"><big>Willkommen bei KinoInfo</big></p>
            <p>Mmmm...Popcorn...</p>
            <do type="accept">
                <go href="#home"/>
            </do>
        </card>
    </xsl:template>
</xsl:stylesheet>

Es handelt sich hierbei um ein sehr einfaches Stylesheet. Am wichtigsten ist das Element <xsl:output>, das die XML-Ausgabemethode und die WML-DTD festlegt. Um die Kompatibilität mit möglichst vielen Handys zu gewährleisten, entspricht die Anwendung der Version 1.1 von WML, obwohl mittlerweile neuere Versionen existieren.

Der einzige etwas kompliziertere Teil des Stylesheets ist folgende Zeile:

<a href="movieguide?aktion=kinos&amp;stadt=$(stadt)">Zeige Kinos...</a>

Sie erzeugt einen Hyperlink zum nächsten Stapel und übergibt dabei die Parameter für aktion und stadt. Das Kaufmanns-Und (&) muß als &amp; geschrieben werden, damit es vom XML-Parser korrekt behandelt wird. Die Syntax von $(stadt) sieht einem Attributwert-Template sehr ähnlich, es handelt sich aber um eine WML-Variable. (Die Syntax von XSLT-AVTs ist {$var}.) Auf diese Weise wird beim Betätigen des Hyperlinks die ausgewählte Stadt an das Servlet übergeben. In XHTML könnte dies nur durch ein Formular oder eine Skriptsprache erreicht werden.

Das folgende Stylesheet erzeugt eine Liste von Kinos für eine Stadt.

Beispiel: XSLT-Code für die Filmliste

<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
 ***********************************************************************
 ** Erzeugt eine Liste von Kinos für Geräte mit WML-Unterstützung.
 ******************************************************************** -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:param name="stadt_id" select="'ef'"/>
    <xsl:output method="xml" version="1.0" encoding="ISO-8859-1" indent="yes" doctype-public="-//WAPFORUM//DTD WML 1.1//EN" doctype-system="http://www.wapforum.org/DTD/wml_1.1.xml"/>
    <!--
     *********************************************************************
     ** Das Wurzel-Template erzeugt den Stapel und die Karte kinos.
     ****************************************************************** -->
    <xsl:template match="/filme">
        <wml>
            <card id="kinos" title="Kinos">
                <!-- wähle die passende Stadt aus -->
                <xsl:apply-templates select="stadt[@id=$stadt_id]"/>
                <p><em><a href="movieguide">Stadt wechseln...</a></em></p>
            </card>
        </wml>
    </xsl:template>
    <!--
     *********************************************************************
     ** Zeige die Kinos einer Stadt.
     ****************************************************************** -->
    <xsl:template match="stadt">
        <p><big><xsl:value-of select="name"/></big></p>
        <p>Wählen Sie ein Kino:</p>
        <p>
            <!-- zeige die Liste der Kinos dieser Stadt -->
            <xsl:apply-templates select="kino"/>
        </p>
    </xsl:template>
    <!--
     *********************************************************************
     ** Erzeuge einen Link für ein bestimmtes Kino.
     ****************************************************************** -->
    <xsl:template match="kino">
        <a href="movieguide?aktion=spielplan&amp;stadt={$stadt_id}&amp;kino={@id}">
            <xsl:value-of select="name"/>
        </a>
        <br/>
    </xsl:template>
</xsl:stylesheet>

Anders als das erste Stylesheet benötigt dieses einen Parameter für die Stadt:

<xsl:param name="stadt_id" select="'ef'"/>

Während der Testphase hat dieser Parameter die Voreinstellung ef, in der realen Anwendung sollte er vom Servlet übergeben werden. Das ist notwendig, da die Daten für alle Städte in einer großen XML-Datei enthalten sind. Mit diesem Parameter kann das Stylesheet die Informationen für eine Stadt aus der Datei extrahieren. Zum Beispiel verwendet das erste <xsl:apply-templates> ein Prädikat, um die Stadt auszuwählen, deren Attribut id mit dem Stylesheet-Parameter stadt_id übereinstimmt:

<xsl:apply-templates select="stadt[@id=$stadt_id]"/>

Im Rest des Stylesheets wird einfach eine Liste der Kinos dieser Stadt ausgegeben. Das letzte Stylesheet erzeugt die Programmvorschau eines Kinos als Liste. Es ist das komplexeste Stylesheet, da es mehrere Karten erzeugt.

<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
 ***********************************************************************
 ** Erzeugt ein Kinoprogramm für Geräte mit WML-Unterstützung.
 ******************************************************************** -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:param name="stadt_id" select="'ef'"/>
    <xsl:param name="kino_id" select="'cinestar'"/>
    <xsl:output method="xml" version="1.0" encoding="ISO-8859-1" indent="yes" doctype-public="-//WAPFORUM//DTD WML 1.1//EN" doctype-system="http://www.wapforum.org/DTD/wml_1.1.xml"/>
    <!--
     *********************************************************************
     ** Das Wurzel-Template erzeugt den Stapel und die Karten für die Filme.
     ******************************************************************  -->
    <xsl:template match="/filme">
        <wml>
            <!-- erzeuge das WML-Template -->
            <template>
                <do type="prev" label="Zurück" name="common_prev">
                    <prev/>
                </do>
            </template>
            <card id="filme" title="Filme">
                <!-- verstecke das Template in dieser Karte -->
                <do type="prev" name="common_prev">
                    <noop/>
                </do>
                <!-- wähle das Kino mit passenden Stylesheet-Parametern für stadt_id und kino_id -->
                <xsl:apply-templates select="stadt[@id=$stadt_id]/kino[@id=$kino_id]"/>
            </card>
            <!-- erzeuge mehrere Karten, eine pro Film -->
            <xsl:apply-templates select="stadt[@id=$stadt_id]/kino[@id=$kino_id]/film" mode="createCard"/>
        </wml>
    </xsl:template>
    <!--
     *********************************************************************
     ** Zeige weitere Informationen über ein Kino.
     ****************************************************************** -->
    <xsl:template match="kino">
        <p><big><xsl:value-of select="name"/></big></p>
        <p>Wählen Sie einen Film:</p>
        <p><xsl:apply-templates select="film"/></p>
        <p><em><a href="movieguide?aktion=kinos&amp;stadt={$stadt_id}">Kino wechseln...</a></em></p>
    </xsl:template>
    <!--
     *********************************************************************
     ** Zeige weitere Informationen über einen Film in der Hauptkarte.
     ****************************************************************** -->
    <xsl:template match="film">
        <xsl:variable name="curId" select="@ref"/>
        <!-- der Text des Hyperlinks ist der kurzName aus der <filmdef> -->
        <a href="#{$curId}">
            <xsl:value-of select="/filme/filmdef[@id=$curId]/kurzName"/>
        </a>
        <br/>
    </xsl:template>
    <!--
     *********************************************************************
     ** Erzeuge eine Karte mit dem Spielplan für einen Film.
     ****************************************************************** -->
    <xsl:template match="film" mode="createCard">
        <xsl:variable name="curId" select="@ref"/>
        <card id="{$curId}" title="Spielplan">
            <p><em><xsl:value-of select="/filme/filmdef[@id=$curId]/langName"/></em></p>
            <p><xsl:value-of select="zeiten"/></p>
        </card>
    </xsl:template>
</xsl:stylesheet>

Wie bereits erwähnt, erzeugt dieser Stapel ein Template, das den Back-Button definiert, der auf allen außer der ersten Karte zu sehen ist. Das Template wird vor der ersten Karte erzeugt, die auch gerade diejenige ist, die das Template mit dem Element <noop/> überschreibt.

Die Stadt und das Kino werden aufgrund der Stylesheet-Parameter stadt_id und kino_id mit dem folgenden <xsl:apply-templates>-Element ausgewählt:

<xsl:apply-templates select="stadt[@id=$stadt_id]/kino[@id=$kino_id]"/>

Obwohl diese Syntax bereits bekannt ist, soll sie hier noch einmal kurz erläutert werden:

  1. Wähle alle <stadt>-Kindelemente des Elements <filme>.
  2. Verwende das Prädikat [@id=$stadt_id] zum Einschränken der Liste auf die korrekte Stadt.
  3. Wähle alle <kino>-Kindelemente der <stadt>.
  4. Verwende das Prädikat [@id=$kino_id] zum Einschränken der Liste auf ein einziges <kino>.

Nachdem die Home-Karte erzeugt wurde, wird mit <xsl:apply-templates> eine Karte für jeden Film erzeugt:

<xsl:apply-templates select="stadt[@id=$stadt_id]/kino[@id=$kino_id]/film" mode="createCard"/>

Hier wird ein Template-Modus verwendet, eine Technik, die unter Parameter und Variablen besprochen wurde. Das folgende Template wird nun instantiiert, da es einen passenden Modus aufweist:

<xsl:template match="film" mode="createCard">
   ...erzeuge eine Karte mit dem Spielplan für einen Film
</xsl:template>
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