XSLT-Templates für die Seitengestaltung

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

In den meisten Fällen sind dynamisch generierte und interaktive Webanwendungen übertrieben. Kleine Betriebe benötigen oft statische Websites zum Anzeigen von Job-Angeboten, Ankündigungen neuer Produkte und anderen grundlegenden Informationen. Betriebsinterne Intranets sind ein häufig anzutreffendes Anwendungsfeld. In einem typischen Intranet ist eine große Anzahl von Abteilungen mit individuellen Projektgruppen für eine Vielzahl von Websites innerhalb des Betriebs zuständig. Viele dieser Gruppen bestehen nicht aus Programmierern. Sie können einfache XHTML-Seiten erstellen, haben aber nicht die Ausbildung, um mit XML, XSLT und Servlets zu arbeiten. In jedem Fall ist ein konsistentes Look-and-Feel essentiell.

XSLT ist ein effektives Werkzeug zum Erzeugen eines konsistenten Seiten-Layouts. In dem hier vorgestellten Ansatz erstellen die Webautoren XHTML-Seiten mit den von ihnen bevorzugten Werkzeugen. Diese Seiten sollten weder Frames noch Navigationsleisten enthalten. Wie in der folgenden Abbildung zu sehen, wird ein XSLT-Stylesheet verwendet, um Navigationsleisten am oberen und am linken Rand der XHTML-Seiten einzublenden. Deshalb sollten die einzelnen Seiten keine eigenen Navigationsleisten enthalten.

Anordnung des XSLT-Templates

Abbildung: Anordnung des XSLT-Templates

Da die obere Navigationsleiste dynamisch ist, müssen die Webautoren ein <meta>-Tag in jede veröffentlichte XHTML-Seite einbinden:

<meta name="navigationsKategorie" content="home"/>

Durch dieses Tag kann die obere Navigationsleiste die Kategorie der aktuellen Seite visuell hervorheben, wobei das XSLT-Stylesheet den passenden XHTML-Code für die Navigationsleiste generiert. (Diese Vorgehensweise kann durch das Hinzufügen eines zweiten <meta>-Tags für Subkategorien erweitert werden.) Die folgende Abbildung zeigt, daß unser Stylesheet Hyperlinks für die Kategorien in der Navigationsleiste erzeugt. Derselbe Ansatz kann auch für graphische Navigationsleisten verwendet werden.

XHTML-Ausgabe mit Navigationsleisten

Abbildung: XHTML-Ausgabe mit Navigationsleisten

Da das Seiten-Layout von einem einzigen Stylesheet kontrolliert wird, wirken sich Änderungen an diesem auf die ganze Webpräsenz aus. Der Code der Homepage ist im folgenden Beispiel zu sehen.

Beispiel: home.xml

<?xml version="1.0" encoding="ISO-8859-1"?>
<html>
   <head>
      <title>Homepage</title>
      <meta name="navigationsKategorie" content="home"/>
   </head>
   <body>
      <h1>Willkommen auf unserer Homepage!</h1>
      <div>Das ist eine normale XHTML-Seite, wie sie von Autoren erzeugt wird. Dabei gelten die folgenden Richtlinien:
         <ul>
            <li>Jede Seite muß gültigen XHTML-Code enthalten</li>
            <li>Jede Seite muß ein Meta-Tag mit der Navigationskategorie enthalten.</li>
            <li>Das Stylesheet templatePage.xslt fügt die Navigationsleisten am oberen und am linken Rand hinzu.</li>
         </ul>
         Die Seiten werden in das Verzeichnis WEB-INF/xml der Webanwendung installiert. Dadurch sind die Clients gezwungen, über ein Servlet zuzugreifen, denn der Servlet-Container verhindert den Zugriff auf Unterverzeichnisse von WEB-INF.
      </div>
   </body>
</html>

Da XSLT zum Einfügen der Navigationsleisten verwendet wird, müssen alle Seiten aus wohlgeformtem XML bestehen. Das ist eine gute Übung und jeder, der HTML programmieren kann, sollte in der Lage sein, eine Konvertierung nach XHTML durchzuführen. (HTML TIDY ist ein kostenloses Tool zum Konvertien von HTML nach XHTML.)

Den Webautoren können von Programmierern Skripte zur Verfügung gestellt werden, die eine Validierung der XML-Daten nach einer der XHTML-DTDs durchführen und so Fehler vor dem Veröffentlichen der Seiten aufdecken.

Hinweis:
Das strikte Befolgen der XHTML-DTDs macht es für Programmierer einfacher, Programme zu schreiben, die den Inhalt einer Website verwalten, denn der Seiteninhalt ist konsistent strukturiert und kann einfach geparst werden.

Das XSLT-Stylesheet sucht das <meta>-Tag, deshalb werden die Elemente <html>, <head> und <meta> benötigt. Wird das <meta>-Tag nicht gefunden, ist die Navigationskategorie unbekannt, und es wird kein Link für die Navigation hervorgehoben. Der Inhalt der Abschnitte <head> und <body> wird einfach an die passende Stelle im Ergebnisdokument kopiert. Das folgende Beispiel zeigt das XSLT-Stylesheet, das die Navigationsleisten einfügt.

Beispiel: templatePage.xslt

<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
   *********************************************************************
   ** Dieses Stylesheet wird von allen Webseiten verwendet. Es definiert
   ** die Position des Seitenkopfes und der Navigationsleiste.
   ****************************************************************** -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <!--
      *******************************************************************
      ** Der Ergebnisbaum in XHTML
      **************************************************************** -->
   <xsl:output method="xml" doctype-public="-//W3C//DTD XHTML 1.0 Transitional//EN" doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" encoding="ISO-8859-1"/>
   <!--
      *******************************************************************
      ** Die Navigationskategorie wird durch das <meta>-Tag im XHTML-Quelldokument
      ** bestimmt. Die obere Navigationsleiste benutzt diese Variable.
      **************************************************************** -->
   <xsl:variable name="global.nav.kategorie">
      <xsl:choose>
         <xsl:when test="/html/head/meta[@name='navigationsKategorie']">
            <xsl:value-of select="/html/head/meta[@name='navigationsKategorie']/@content"/>
         </xsl:when>
         <xsl:otherwise>
            <xsl:text>unbekannt</xsl:text>
         </xsl:otherwise>
      </xsl:choose>
   </xsl:variable>
   <!--
      *******************************************************************
      ** Dieses Template erzeugt das XHTML-Dokument.
      **************************************************************** -->
   <xsl:template match="/">
      <html>
         <!-- kopiere das Element <head> aus dem Quelldokument -->
         <xsl:copy-of select="html/head"/>
         <body>
            <!-- diese Tabelle bestimmt das Layout der Seite -->
            <table width="100%" cellpadding="4" cellspacing="0" border="0">
               <tr bgcolor="#f0f0f0">
                  <td colspan="2">
                     <xsl:call-template name="createTopNavbar"/>
                  </td>
               </tr>
               <tr valign="top">
                  <td bgcolor="#cccccc" width="150px">
                     <xsl:call-template name="createLeftNavbar"/>
                  </td>
                  <td bgcolor="white">
                     <!--
                        *******************************************************
                        ** Kopiere den Inhalt des Abschnitts <body> aus dem XHTML-
                        ** Quelldokument in das XHTML-Ergebnisdokument.
                        **************************************************** -->
                     <xsl:copy-of select="html/body/* | html/body/text( )"/>
                  </td>
               </tr>
            </table>
         </body>
      </html>
   </xsl:template>
   <!--
      *******************************************************************
      ** Dieses Template erzeugt die obere Navigationsleiste.
      **************************************************************** -->
   <xsl:template name="createTopNavbar">
      <xsl:call-template name="navButton">
         <xsl:with-param name="kategorie" select="'home'"/>
         <xsl:with-param name="displayName" select="'Home'"/>
         <xsl:with-param name="url" select="'home.xml'"/>
      </xsl:call-template>
      |
      <xsl:call-template name="navButton">
         <xsl:with-param name="kategorie" select="'company'"/>
         <xsl:with-param name="displayName" select="'Firma'"/>
         <xsl:with-param name="url" select="'company.xml'"/>
      </xsl:call-template>
      |
      <xsl:call-template name="navButton">
         <xsl:with-param name="kategorie" select="'products'"/>
         <xsl:with-param name="displayName" select="'Produkte'"/>
         <xsl:with-param name="url" select="'products.xml'"/>
      </xsl:call-template>
      |
      <xsl:call-template name="navButton">
         <xsl:with-param name="kategorie" select="'jobs'"/>
         <xsl:with-param name="displayName" select="'Jobs'"/>
         <xsl:with-param name="url" select="'jobs.xml'"/>
      </xsl:call-template>
   </xsl:template>
   <!--
      *******************************************************************
      ** Dieses Template erzeugt einen Button in der oberen Navigationsleiste.
      **************************************************************** -->
   <xsl:template name="navButton">
      <xsl:param name="kategorie"/>
      <xsl:param name="displayName"/>
      <xsl:param name="url"/>
      <xsl:choose>
         <!-- Die aktuelle Kategorie wird als Text dargestellt -->
         <xsl:when test="$kategorie = $global.nav.kategorie">
            <xsl:value-of select="$displayName"/>
         </xsl:when>
         <!-- Alle anderen Kategorien werden als Hyperlinks dargestellt -->
         <xsl:otherwise>
            <a href="{$url}">
               <xsl:value-of select="$displayName"/>
            </a>
         </xsl:otherwise>
      </xsl:choose>
   </xsl:template>
   <!--
      *******************************************************************
      ** Dieses Template erzeugt die linke Navigationsleiste.
      **************************************************************** -->
   <xsl:template name="createLeftNavbar">Linke Navigationsleiste</xsl:template>
</xsl:stylesheet>

Das Konzept dieses Stylesheets ist relativ einfach. Als erstes wird die Variable global.nav.kategorie initialisiert. Das Stylesheet verwendet XPath, um die Existenz eines <meta>-Tags mit dem Attribut navigationsKategorie zu prüfen:

<xsl:variable name="global.nav.kategorie">
   <xsl:choose>
      <xsl:when test="/html/head/meta[@name='navigationsKategorie']">
         <xsl:value-of select="/html/head/meta[@name='navigationsKategorie']/@content"/>
      </xsl:when>
      <xsl:otherwise>
         <xsl:text>unbekannt</xsl:text>
      </xsl:otherwise>
   </xsl:choose>
</xsl:variable>
         

Der erste Teil des XPath-Ausdrucks in <xsl:when> lokalisiert alle <meta>-Tags:

/html/head/meta

Nun wird ein Prädikat verwendet, um das eine <meta>-Tag mit dem Attribut navigationsKategorie zu ermitteln:

[@name='navigationsKategorie']

Wenn dieses gefunden wurde, wird der Wert des Attributs content der Variablen global.nav.kategorie zugewiesen. Ansonsten ist ihr Wert unbekannt.

Das XSLT-Stylesheet enthält ein Template, das auf das Muster / paßt. Es definiert das Layout der XHTML-Seite, indem es eine <table> erzeugt. Der Abschnitt <head> wird einfach aus dem XHTML-Quelldokument kopiert:

<xsl:copy-of select="html/head"/>

Auf diese Weise werden alle Styles und Skripte, die die Webautoren in ihren Seiten verwenden, beibehalten. Ein Problem entsteht, wenn die Autoren CSS-Styles definieren, die das Look-and-Feel der Navigationsleisten, wie z.B. die Fonts und Farben der Seiten, ändern. Wenn Sie dies unterbinden wollen, können Sie ein XSLT-Stylesheet entwerfen, das alle <style>-Tags und style-Attribute des originalen XHTML-Dokuments ignoriert.

Nachdem der Abschnitt <head> kopiert wurde, erzeugt das XSLT-Stylesheet den <body> des Ergebnisbaums. Eine XHTML-<table> kontrolliert das Seiten-Layout, und weitere XSLT-Templates werden verwendet, um die Navigationsleisten zu erzeugen:

<xsl:call-template name="createTopNavbar"/>
...
<xsl:call-template name="createLeftNavbar"/>

Das Template createTopNavbar ist in der Lage, die aktuelle Kategorie hervorzuheben. Das Template createLeftNavbar kopiert dagegen nur einen statischen Inhalt ins Ergebnis. Schließlich wird der Inhalt des <body>-Tags vom Quelldokument in den Ergebnisbaum kopiert:

<xsl:copy-of select="html/body/* | html/body/text( )"/>

Anders als der Abschnitt <head> wird der Abschnitt <body> nicht direkt kopiert. Statt dessen werden alle Elemente und der Text des Abschnitts <body> kopiert. Dadurch wird der folgende ungültige XHTML-Code nicht erzeugt:

<tr><td><body>...</body></td></tr>

Das Template createTopNavbar wird verwendet, um eine Zeile mit Links in der oberen Navigationsleiste zu erzeugen. Für jede Navigationskategorie wird das Template navButton aufgerufen:

<xsl:call-template name="navButton">
   <xsl:with-param name="kategorie" select="'home'"/>
   <xsl:with-param name="displayName" select="'Home'"/>
   <xsl:with-param name="url" select="'home.xml'"/>
</xsl:call-template>

Der Parameter kategorie erlaubt es dem Template navButton zu bestimmen, ob der Parameter displayName als Hyperlink oder als Text dargestellt werden soll. Der Code hierfür ist im Template navButton im Beispiel templatePage.xslt zu sehen und wird hier nicht wiederholt.

Nichts hiervon funktioniert ohne ein Servlet, das den ganzen Prozeß steuert. In diesem Beispiel werden alle XHTML-Seiten im Verzeichnis WEB-INF der Webanwendung mit der Erweiterung .xml gespeichert. Es handelt sich hier um die Original-Webseiten ohne Navigationsleisten. Im Verzeichnis WEB-INF haben Clients keinen direkten Zugriff auf diese Seiten, sondern müssen das Servlet TemplateServlet verwenden, um sie abzurufen. Das Servlet lokalisiert die XML-Datei, führt die XSLT-Transformation mit dem Stylesheet templatePage.xslt durch und sendet das Ergebnis an den Clientbrowser. Dieser ganze Prozeß ist für die Clients transparent, da sie nur das Ergebnis der Transformation zu sehen bekommen.

Die folgende Tabelle zeigt die komplette Struktur der WAR-Datei dieses Beispiels.

Tabelle: Inhalt der WAR-Datei

Datei Konstante
WEB-INF/web.xml Der Anwendungsdeskriptor (siehe das Beispiel Anwendungsdeskriptor)
WEB-INF/classes/chap8/TemplateServlet.class Das Servlet, das die XSLT-Transformation steuert (siehe das Beispiel TemplateServlet.java)
WEB-INF/lib/javaxslt.jar Enthält die Klasse StylesheetCache
WEB-INF/xml/company.xml Eine Beispiel-Webseite
WEB-INF/xml/home.xml Eine Beispiel-Webseite (siehe das Beispiel home.xml )
WEB-INF/xml/jobs.xml Eine Beispiel-Webseite
WEB-INF/xml/products.xml Eine Beispiel-Webseite
WEB-INF/xslt/templatePage.xslt Das XSLT-Stylesheet (siehe das Beispiel templatePage.xslt)

Der Anwendungsdeskriptor web.xml ist im folgenden Beispiel zu sehen.

Beispiel: Anwendungsdeskriptor

<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN" "http://java.sun.com/j2ee/dtds/web-app_2.2.dtd">
<web-app>
   <servlet>
      <servlet-name>template</servlet-name>
      <servlet-class>chap8.TemplateServlet</servlet-class>
   </servlet>
   <servlet-mapping>
      <servlet-name>template</servlet-name>
      <url-pattern>/template/*</url-pattern>
   </servlet-mapping>
</web-app>

Da alle Dateien im Verzeichnis WEB-INF versteckt sind, können Clients nur über das im Anwendungsdeskriptor angegebene URL-Muster /template/* auf die Anwendung zugreifen. Läuft diese auf dem lokalen Rechner, wird die URL "http://localhost:8080/chap8/template/home.xml" verwendet.

Daraufhin wird die Seite aus der Abbildung XHTML-Ausgabe mit Navigationsleisten dargestellt. Das Wort template in der URL wird auf das Servlet abgebildet, und /home.xml ist die Pfadinformation. Sie wird vom Servlet durch die Methode getPathInfo( ) aus dem HttpServletRequest ermittelt. Der Quellcode von TemplateServlet ist im folgenden Beispiel zu sehen.

Beispiel: TemplateServlet.java

package chap8;

import com.oreilly.javaxslt.util.StylesheetCache;
import java.io.*;
import java.net.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.xml.transform.*;
import javax.xml.transform.stream.*;

/**
 * Wendet ein Standard-Stylesheet auf alle XML-Seiten an.
 */
public class TemplateServlet extends HttpServlet {
  private String xsltFileName;
  
  /**
   * Lokalisiere das Template-Stylesheet während der Initialisierung des Servlets.
   */
  public void init( ) throws UnavailableException {
    ServletContext ctx = getServletContext( );
    this.xsltFileName = ctx.getRealPath(
      "/WEB-INF/xslt/templatePage.xslt");
    File f = new File(this.xsltFileName);
    if (!f.exists( )) {
      throw new UnavailableException(
        "XSLT-Stylesheet nicht gefunden: "
        + this.xsltFileName, 30);
    }
  }
  
  public void doGet(HttpServletRequest req, HttpServletResponse res)
      throws ServletException, IOException {
    try {
      // benutze den ServletContext, um die XML-Datei zu finden
      ServletContext ctx = getServletContext( );
      String xmlFileName = ctx.getRealPath("/WEB-INF/xml"
        + req.getPathInfo( ));
      
      // prüfe, ob die Datei existiert
      if (!new File(xmlFileName).exists( )) {
        res.sendError(HttpServletResponse.SC_NOT_FOUND, xmlFileName);
      } else {
        res.setContentType("text/html");
       
        // lade die XML-Datei
        Source xmlSource = new StreamSource(new BufferedReader(
          new FileReader(xmlFileName)));
       
        // benutze eine zwischengespeicherte Version des XSLT-Stylesheets
        Transformer trans =
          StylesheetCache.newTransformer(xsltFileName);
        trans.transform(xmlSource, new StreamResult(res.getWriter( )));
      }
    } catch (TransformerConfigurationException tce) {
      throw new ServletException(tce);
    } catch (TransformerException te) {
      throw new ServletException(te);
    }
  }
}

Es handelt sich um ein relativ einfaches Servlet, das XSLT-Transformationen auf XML-Dateien durchführt. In der Methode init( ) wird das Template templatePage.xslt im Verzeichnis WEB-INF/xslt lokalisiert:

ServletContext ctx = getServletContext( );
this.xsltFileName = ctx.getRealPath(
         "/WEB-INF/xslt/templatePage.xslt");

Wie in vorherigen Kapiteln bereits erwähnt, konvertiert die Methode getRealPath( ) den Pfad in einen systemabhängigen Pfadnamen. Nur so kann die Klasse StylesheetCache das XSLT-Stylesheet korrekt lokalisieren. In der Methode doGet( ) des Servlets wird dieselbe Vorgehensweise zum Auffinden der nachgefragten XML-Datei verwendet:

ServletContext ctx = getServletContext( );
String xmlFileName = ctx.getRealPath("/WEB-INF/xml"
       + req.getPathInfo( ));

In den Quellen von TemplateServlet ist zu sehen, daß das Servlet dann die Existenz dieser Datei prüft und gegebenenfalls eine Fehlermeldung sendet. Andernfalls wird die XSLT-Transformation mittels JAXP durchgeführt, und die Navigationsleisten werden zum Dokument hinzugefügt.

Mehr zum Thema Cache
In der Klasse TemplateServlet werden die XSLT-Stylesheets durch die Klasse com.oreilly.javaxslt.util.StylesheetCache zwischengespeichert. In diesem speziellen Beispiel sind sowohl die XML-Daten als auch das XSLT-Stylesheet statisch. Da sie also nicht dynamisch erzeugt werden, kann auch das Transformationsergebnis result gepuffert werden, was zu kurzen Antwortzeiten führt. Das nächste Kapitel stellt hierfür die Klasse ResultCache vor.

XSLT-Stylesheets als Templates für das Seiten-Layout zu verwenden, hat den Vorteil, daß die Autoren der einzelnen Seiten weder Kopf- und Fußzeilen noch Navigationsleisten auf jeder Seite anlegen müssen. Durch die Zentralisierung des Seiten-Layouts in ein oder mehrere XSLT-Stylesheets sind nur wenige Änderungen nötig, um das Look-and-Feel einer ganzen Webpräsenz zu ändern.

   

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