Hier beschreibe ich den von mir gewählten Weg, ein Inhaltsverzeichnis für Webseiten flexibel und erweiterungsfreundlich aufzubauen. Durch konsequenten Einsatz von XML und XSLT gelang mir eine strikte Trennung der Seitenpräsentation von dem eigentlichen Seiteninhalt, was den Pflegeaufwand bei Änderungen reduziert und die Seiten insgesamt flexibler für Umgestaltungen macht. [RP]

Über den Index

Inhalt

Ein Index - nichts weiter!
Dokumentstruktur
Ein Index in XML
index.xml als Startseite?
Die Gliederungsebenen
Text oder Liste?
Publikations- und Änderungsdatum
Eindeutige Kennzeichnung mit Guid's
Struktur der index.xml-Datei
Das Stylesheet
Das RSS-Format
Ein Perlscript für das WS_FTP-Log


Ein Index - nichts weiter!

Das Wort index kommt aus dem Lateinischen und bedeutet eigentlich "Ansager". Der Index ist ein Ansager für Inhalte – das kann eine Liste sein, ein Register, ein Verzeichnis oder ein Katalog. Hier wollen wir uns mit den Indexseiten für Internet-Inhalte beschäftigen.

Fragt man einen Webserver nicht nach einer Datei, sondern nach einem Ordner, so sucht dieser zunächst im angegebenen Ordner nach einer Datei mit Namen index.html oder index.htm. Ist eine solche Datei vorhanden, so wird sie als Antwort zurückgeben. Diese Funktion wird häufig genutzt, um den Server bei Requests auf die Domäne automatisch einen Einstiegspunkt auf das gesamte Webangebot finden zu lassen (eine Internetaddresse www.astrotexte.ch ist ein kleines bisschen einfacher zu merken als www.astrotexte.ch/index.html und führt bei Eingabe im Browser zu exakt dem gleichen Ergebnis). Ist eine solche Index-Datei dagegen nicht vorhanden, zeigen viele Webserver stattdessen eine Liste der Dateien und Unterordner des angegebenen Ordners an.

Dieses Verhalten zeigt uns den eigentlichen Zweck der Index-Datei: Indem sie die gewöhnliche Inhaltsanzeige eines Dateisystems durch eine individuelle Variante ersetzt, soll sie dem Administrator ermöglichen, die Dateien des Webangebots übersichtlich und strukturiert darzustellen. Wer neu ist, soll sich leicht einen ersten Überblick über die Inhalte verschaffen können. Wer wiederkommt, will durch eine einfache und übersichtliche Navigation zu den Beiträgen geführt werden, die ihn interessieren.

Beim Standardverhalten, der blossen Anzeige der Dateiliste, kann man es meist schon deshalb nicht belassen, weil nicht alle Dateien, die in einem Ordner aufgeführt sind, die gleiche Wertigkeit haben: Ein HTML-Dokument mag einige Bilder, ein bisschen Javascript und ein Stylesheet enthalten - all diese zusätzlichen Objekte wird man im Index üblicherweise nicht darstellen wollen. Der Index soll also nicht die physische, sondern die logische Dokumentstruktur darstellen: Die Inhalte, nicht die zu deren Präsentation benötigten Ressourcen, sind von Interesse.


Zum Inhaltsverzeichnis

Dokumentstruktur

Damit Dokumente schnell gefunden werden können, bietet es sich an, sie inhaltlich in Gruppen aufzuteilen. Diese Gruppen können, bei grösseren Anzahlen, in weitere Untergruppen geteilt werden. Dabei sollte man es belassen. Mehr als zwei Hierarchie-Ebenenen geben dem Baum der Hierarchien selbst eine zu grosse Wichtigkeit. Die Gruppen sollten so gewählt sein, dass eine ungefähr gleichmässige Aufteilung der einzelnen Dokumente auf die Gruppen möglich ist. Die Gruppen sind also keine Suchbegriffe – denn die Indexseiten sind kein Stichwortregister, sondern eine Navigationshilfe. Die Gruppen sollten darüberhinaus möglichst disjunkt sein, damit die zu wählende Navigation, um zu einen bestimmten Inhalt zu gelangen, möglichst offensichtlich und eindeutig ist. Meinen eigenen Index habe ich in disjunkte Gruppen geteilt – es wäre jedoch kein Problem, die hier vorgestellten Datenstrukturen auf den Fall zu erweitern, dass ein konkretes Dokument in mehreren Gruppen zu führen ist.

Die Präsentation der Daten sollte die gewählte Dokumentstruktur widerspiegeln. Dies geschieht durch Anbringen von Navigationsleisten oder -bäumen. Man sollte bei der Implementierung aber daran denken, dass man möglicherweise einmal Lust hat, die Datenpräsentation zu ändern: Vielleicht will man statt einer Drucktasten- oder Linkleiste auf einen Baum wechseln, oder umgekehrt. Für diesen Fall sollte man von vorneherein vorsorgen, denn der Geschmack wandelt sich! Aber: Wie kann man vorsorgen? Indem man

  1. die Präsentation von der logischen Dokumentstruktur trennt und
  2. sich einen Mechanismus überlegt, um aus der logischen Dokumentstruktur und der gewählten Präsentation die fertigen Webseiten zu erzeugen.


Zum Inhaltsverzeichnis

Ein Index in XML

Um dieses Ziel zu erreichen, bietet sich das XML-Format an. Mit einer frei definierbaren Struktur von Elementknoten, Textknoten und Attributen kann man seine logische Dokumentstruktur aufsetzen, ohne auch nur ein Wort über die Präsentation der Daten zu verlieren. Das ist so gewollt: Der eigentliche Inhalt der Daten soll getrennt werden von seiner Aufmachung.

Die Aufmachung der Daten wird bei diesem Konzept in einem XSLT Stylesheet codiert. Der Name ''Stylesheet'' für diese Objekte, die eigentlich kleine Programme zum Transformieren von XML- in HTML-, Text- oder andere XML-Dateien darstellen, ist mehr als gerechtfertigt, wenn wir an die CSS-Dateien für HTML-Dokumente denken: Auch diese ''Cascading Stylesheets'' haben bereits den Zweck, den Dokumentinhalt von seiner Aufmachung zu trennen. Der Dokumentinhalt kann sich bei Verwendung von CSS auf den eigentlichen Text und auf einige wenige HTML-Element wie die Tabellentags (<table>, <tr>, <td> usw.), die Tags für Überschriften (<h1>, <h2> usw.), Einbinden von Graphiken (<img>), Zeilenumbruch und neue Absätze (<br> und <p>) beschränken. Der Rest ist im Stylesheet festgelegt.

XSL ist, im Unterschied zu CSS, noch konsequenter und erlaubt die Abkapselung der gesamten Präsentationslogik vom Dokumentinhalt. Das ist bei der Kombination HTML/CSS nicht möglich. Denn immerhin sind ja auch Graphiken eigentlich noch "style", gehören nicht zum Inhalt, sondern zur Veranschaulichung desselben. Auch die Überschrift-Tags <h1>, <h2> usw. deuten zwar eine rein logische Funktion an, als würden sie verschiedene Hierarchiestufen von Kapitelüberschriften kennzeichnen. Sie können aber in einem HTML-Dokument ganz beliebig und bar jeder Ordnung angewendet werden; häufig werden sie nur verwendet, um eine bestimmte Schriftgrösse in der Präsentation anzusteuern. Die Überschrift-Tags vermischen also weiterhin die Präsentationsfunktion mit logischen Funktionen.

Ein XML-Dokument können wir frei von jeder Präsentation am Reissbrett definieren. Wie kann ein solches Dokument für einen Index aussehen? Reduziert auf den eigentlichen Inhalt, frei von jeder Präsentation, ist ein Index im Prinzip eine Liste von Verweisen. Man müsste für jeden Verweis das Sprungziel des Verweises (seine URL), einen Kurztext und eine Beschreibung in wenigen Sätzen angeben können. Ein einzelner Verweis könnte also wie folgt aussehen:

<item>
  <link href="./angebot1.html">Angebot 1</link>
  <shorttext>Hier geht es um ...  </shorttext>
</item>
Der gesamte Inhalt des Index könnte als XML-Dokument folgenden Aufbau haben:
<?xml version="1.0" encoding="ISO-8859-1"?>
<index>
  <item>
    <link href="./angebot1.html">Angebot 1</link>
    <shorttext>Hier geht es um ...  </shorttext>
  </item>
  <item>
    <link href="./angebot2.html">Angebot 2</link>
    <shorttext>Hier geht es um ...  </shorttext>
  </item>
  <item>
    <link href="./angebot3.html">Angebot 3</link>
    <shorttext>Hier geht es um ...  </shorttext>
  </item>
  ...
</index>
Mit einer XML-Datei dieser Art hätten wir die erste Aufgabe erledigt: Ein präsentationsfreies Verzeichnis unserer Dokumentinhalte zu erstellen.

Allerdings haben wir nur eine "flache Liste" unserer Inhalte. Es fehlt die Hierarchie, die Gliederung nach Thema und Unterthema. Dazu kommen wir noch.


Zum Inhaltsverzeichnis

index.xml als Startseite?

Man könnte nun auf die Idee kommen, ein solches XML-Dokument um ein passendes XSLT-Stylesheet zu ergänzen und als Startseite auf den Request zurückzusenden. Das scheitert aus verschiedenen Gründen:
  1. Viele Webserver suchen bei Request auf einen Ordner nicht nach einer Datei namens index.xml, so dass die Datei nur bei voller Angabe der URL zurückgesendet würde;
  2. Selbst wenn index.xml nun zurückkommt, so ist nicht gesagt, ob der Webbrowser des Anwenders etwas mit XML anfangen kann und ob er das mitgelieferte XSLT-Stylesheet korrekt interpretiert. Im real existierenden Browserzoo gibt es viele verschiedene XSLT-Prozessoren. Damit wächst die Wahrscheinlichkeit, dass ein XSLT-Stylesheet unterschiedlich interpretiert wird.
Man ist also auf jeden Fall sicherer, mit einem XSLT-Prozessor die Transformation bei Erstellung oder Änderung des Index selbst auszuführen, wobei Dateien mit einem möglichst elementaren HTML-Umfang für den Index generiert werden sollten, und diese HTML-Dateien dann auf den Server hochzuladen.

Da es vermutlich nicht mit einer HTML-Datei getan ist, sondern aus einer XML-Datei mehrere Zieldateien zu generieren wären (für jedes Thema, das kein Unterthema besitzt, und für jedes Unterthema jeweils eine eigene Seite), muss man zuerst nach einem geeigneten XSLT-Prozessor Ausschau halten. Ich entschied mich für den in den neueren Java-Versionen eingebauten Xalan-Prozessor. Denn dieser bietet unter anderem die Möglichkeit, mittels sogenannter Element-Extensions auch mehrere Zieldateien zu erzeugen.


Zum Inhaltsverzeichnis

Die Gliederungsebenen

Wie sind die Gliederungsebenen in unserem Index abzubilden? Ziemlich naheliegend: Wir brauchen zwei neue Elemente, nämlich <page> und <subpage> für Haupt- und Unterthemen. Entsprechend der Gliederung, ziehen wir diese Elementknoten in die oben beschriebene Liste von <item>s ein.

Sowohl <page> als auch <subpage> können die Attribute name, title und (optional) titleLong führen. Der name ist dabei der relative Pfad der zu generierenden HTML-Seite. title ist ein kurzes beschreibendes Stichwort, wie es als Aufschrift auf einer Schaltfläche verwendet werden könnte. titleLong schliesslich ist eine etwas längere Beschreibung der Gruppe - allerdings nicht ganze Sätze, sondern nur eine Überschrift. Überschriften sind vom Platz her nicht so eingeschränkt wie die Texte in Navigationsleisten, daher kann titleLong etwas länger und somit auch etwas spezifischer werden. Der folgende Code skizziert, wie die Gliederung mit den neuen Elementen <page> und <subpage> gestaltet wird:

<index>
  ...
  <page name="index_subthema2_1.html" title="Thema 2">
    ...
    <subpage name="index_subthema2_1.html" title="Subthema 2.1" titleLong="Alles über Subthema 2.1">
      <item>
      </item>
      ...
    </subpage>
    <subpage name="index_subthema2_2.html" title="Subthema 2.2" titleLong="Alles über Subthema 2.2">
      <item>
      </item>
      ...
    </subpage>
    ...
  </page>
  ...
</index>
Zu beachten ist, dass das Element <page> in diesem Fall keine eigene Zieldatei trägt, sondern die Zieldatei der ersten <subpage>. Warum? In meinem Konzept ist die <page>, wenn sie ihrerseits noch <subpage>s enthält, lediglich eine Gliederungseinheit, trägt aber selbst keine Inhalte. In der Sprache von Dateisystemen ist sie ein Ordner, der seinerseits nur Ordner enthält. Aber nur solche Ordner, die Dateien als Inhalte haben, sollen mit einer HTML-Seite dargestellt werden. Ordner, die ihrerseits nur Ordner enthalten, stellen nichts weiter als einen Zwischenknoten im Navigationsbaum dar.

Dennoch bedeutet es etwas, dass im Beispiel dem <page>-Element für Thema 2 die Seite des Subthemas 2.1 zugeordnet ist: Das Subthema 2.1 wird dadurch zum Defaultwert bei Anwahl des Themas 2 erklärt.

Ein <page>-Element kann jedoch auch eine eigene HTML-Seite als Name führen: Das ist genau dann der Fall, wenn es keine <subpage>s enthält und somit selbst Inhalt trägt.

Um den Mechanismus zu nutzen, dass der Server automatisch nach der Seite index.html sucht, empfiehlt es sich, der Einstiegs-<page> oder ihrer Default-<subpage> den Dateinamen index.html zuzuordnen.


Zum Inhaltsverzeichnis

Text oder Liste?

Auf manchen Seiten will man aber möglicherweise gar keine item-Liste haben, sondern lediglich einen informierenden Langtext darstellen. Um diese Möglichkeit zu bieten, habe ich ein weiteres Unterelement von <page> und <subpage> eingeführt, das einfach <text> heißt. Es kann zu Beginn oder zu Ende des <page>- oder <subpage>-Elements stehen, jedoch nicht mitten in der Auflistung der <item>s, da sonst die Liststruktur durchbrochen wäre. Somit ist auch die Mischform möglich: man kann einen Langtext und eine Liste von <item>s auf einer <page> oder<subpage> darstellen.

Der dem <text> untergeordnete Langtext muss kein reiner Text sein. Er kann seinerseits durch beliege HTML-Tags angereichert sein und soll mitsamt dieser HTML-Baumstruktur ins Zieldokument übertragen werden. Damit es nicht zu XML-Validierungsfehlern beim Parsen des Dokuments kommt, ist hierbei die XHTML-Syntax zu beachten: Zu jedem Tag muss ein schliessendes Tag existieren, Attribute müssen stets in der Form name="wert" angegeben werden, usw.

<index>
  ...
  <page name="index.html" title="Thema">
    <text>Willkommen auf diesen Seiten. Hier ein Konterfei
    ihres Autors: <img src="konterfei.jpg"/>...
    </text>
    <item>
      ...
    </item>
    ...
  </page>
</index>

Es entstand bei mir bald das Bedürfnis, etwaigen Langtext nicht in der zentralen Datei index.xml zu führen, sondern je in einer separaten Datei – das zentrale index.xml-Dokument wäre sonst zu unübersichtlich geworden. Das war leicht machbar, weil zum Sprachumfang von XPath die document()-Funktion gehört, die einen externen XML-Baum im XSLT-Programm zur Verfügung stellt. Im <text>-Tag selbst wird dann nur noch die Datei, die den Quellcode des Langtextes enthält, als Verweis geführt. Ein Beispiel:

<index>
  ...
  <page name="index.html" title="Thema">
    <text src="index_src.html">
  </page>
</index>
Zu beachten ist, dass die Quelldatei – in diesem Fall index_src.html – kein vollständiges HTML-Dokument ist, sondern nur ein Seitenfragment darstellt. Um es genau zu sagen, enthält sie einen Teilbaum unterhalb des <body>-Knotens im Ergebnisbaum.


Zum Inhaltsverzeichnis

Publikations- und Änderungsdatum

Einige kleine Erweiterungen runden das <item> noch ab: Wie auch in jedem ordentlichen Dateisystem üblich, sollten wir für jedes <item> ein Erstellungs- und Änderungsdatum führen. Für einen Webdienst ist das relevante "Erstellungsdatum" das Datum der Erstpublikation im Internet, also das Veröffentlichungsdatum, nicht der Zeitpunkt, zu dem die Datei physisch erstellt wurde. In erster Näherung ist dies das Datum des ersten Uploads. Warum nur in erster Näherung? Weil eine Datei eine gewisse Zeit im Dateisystem des Webservers vor sich hin dümpeln kann, ohne vom Publikum bemerkt zu werden. Erst durch die Aufnahme in die Indexseiten wird eine neue Seite dem Publikum aktiv bekannt gemacht. Vorher ist die Seite zwar nicht wirklich privat – denn jemand könnte zufällig ihre URL eingeben, und wenn die Robots der Suchmaschinen die Seite entdeckt haben, wird sie indiziert und möglicherweise vom Suchdienst aus angesprungen. Aber dies ist eher als ein ungewollter Effekt zu betrachten, solange man sich noch nicht entschlossen hat, die Seite in seinen Index aufzunehmen.

Ähnlich sieht es mit dem Änderungsdatum aus. Hier ist das Datum des letzten Uploads eine gute Näherung, muss aber nicht stimmen. Es kann einen technisch begründeten Upload geben, der nicht mit einer effektiven Änderung des Seiteninhalts verknüpft war.

Das bedeutet, wir brauchen zwei neue Attribute des <item>-Elements; ich habe sie pubDate und chgDate genannt. Aus noch zu besprechenden Kompatibilitätsgründen verwende ich für die Notation des Datums den in der Spezifikation RFC 822 beschriebenen Standard:

date-time   =  [ day "," ] date time        ; dd mm yy
                                            ;          hh:mm:ss zzz

     day         =  "Mon"  / "Tue" /  "Wed"  / "Thu"
                 /  "Fri"  / "Sat" /  "Sun"

     date        =  1*2DIGIT month 2DIGIT        ; day month year
                                                 ;  e.g. 20 Jun 82

     month       =  "Jan"  /  "Feb" /  "Mar"  /  "Apr"
                 /  "May"  /  "Jun" /  "Jul"  /  "Aug"
                 /  "Sep"  /  "Oct" /  "Nov"  /  "Dec"

     time        =  hour zone                    ; ANSI and Military

     hour        =  2DIGIT ":" 2DIGIT [":" 2DIGIT]
                                                 ; 00:00:00 - 23:59:59

     zone        =  "UT"  / "GMT"                ; Universal Time
                                                 ; North American : UT
                 /  "EST" / "EDT"                ;  Eastern:  - 5/ - 4
                 /  "CST" / "CDT"                ;  Central:  - 6/ - 5
                 /  "MST" / "MDT"                ;  Mountain: - 7/ - 6
                 /  "PST" / "PDT"                ;  Pacific:  - 8/ - 7
                 /  1ALPHA                       ; Military: Z = UT;
                                                 ;  A:-1; (J not used)
                                                 ;  M:-12; N:+1; Y:+12
                 / ( ("+" / "-") 4DIGIT )        ; Local differential
                                                 ;  hours+min. (HHMM)
Syntax einer Zeitangabe (nach RFC 822)


Zum Inhaltsverzeichnis

Eindeutige Kennzeichnung mit Guid's

Schliesslich stellt sich noch das Problem, dass der Aufbewahrungsort einer Webseite, d.h. seine durch die URL angegebene Position im Dateisystem des Webservers, nicht fest mit diesem Inhalt verknüpft sein muss. Man kann sich etwa vorstellen, das Dateisystem umzuorganisieren und eine andere Ordnerstruktur einzuführen. Es wäre aber nützlich, ein <item>-Element durch eine ID zu kennzeichnen, die nichts mit dem Dateisystem zu tun hat, sondern einfach nur eine eindeutige Identifizierung dieses Elements ermöglicht.

In Zeiten des World Wide Web bietet sich an, hier gleich eine global eindeutige ID (Guid) zu verwenden. Windows-Benutzer haben in der DLL rpcrt4.dll in ihrem Windows-Ordner eine Funktion UuidCreate() zur Verfügung, die solche IDs generiert. Indem die ID des Prozessors und die aktuelle Systemzeit in die Berechnung der Guid eingehen, ist sichergestellt, weder durch einen anderen Rechner noch zu einer anderen Zeit dieselbe Guid wieder ermittelt wird. Sie ist also wirklich global eindeutig. Hier ein Beispiel einer Guid, wie sie von meinem Windows-Rechner errechnet wurde:

{E14124E0-10DD-11D9-9F46-000374890932}
Eine Guid (erzeugt mit UuidCreate() in rpcrt4.dll)

Im wesentlichen handelt es sich also um einen 16-Byte-Binärwert, der von der Funktion noch in einem speziellen Ausgabeformat zurückgegeben wird (was aber eigentlich nicht wichtig ist). Auf jeden Fall garantiert Microsoft für die globale Eindeutigkeit dieser Guids. (Auch andere Plattformen dürften Funktionen zur Generierung von Guids anbieten).

Jedem auf dem Webserver abrufbaren <item> wird also bei der Publikation ein für allemal eine Guid zugeordnet, die dieses <item> von nun ab und für immer kennzeichnet. In meiner XML-Struktur habe ich die Guid als Attribut des <item>-Elements vorgesehen. Ich nutze sie allerdings nur für solche Elemente, die wirklich Verweise auf Inhalte meiner eigenen Webseiten darstellen, nicht für Links auf externe Webseiten. Hier ein Beispiel:

<item pubDate="Sat, 25 May 2002 21:58:00 GMT"
    chgDate="Sat, 15 May 2004 18:17:00 GMT"
    guid="{72209E00-0C1F-11D9-9F46-000374890932}">
  <link href="./sources/barbieri.html">Eine Einführung anhand eines Bildes</link>
  ...
</item>
Zuordnung der Guid zum Dokument im index.xml-File

Damit mithilfe der Guids eine alternative Lokation der Inhalte im Web möglich wird, benötigt man noch ein Servlet, das die zur Guid gehörige URL ermittelt (also das href-Attribut des zugehörigen <link>-Elements). Das nachfolgend aufgelistete Servlet GetByGuid.java leistet genau dies: Es führt ein "Dictionary" von Guids mit ihren zugehörigen URL's als statische Hashtabelle. Beim Request prüft es, ob die mitgegebene Guid im Dictionary enthalten ist. Wenn nicht, ist möglicherweise das index.xml-File mittlerweile um neue Items erweitert worden. Das Index-File wird also in diesem Fall neu geparsed. Wird die Guid dann gefunden, so veranlasst das Servlet den Browser, einen Redirect auf die zugeordnete URL auszuführen. Wird sie nicht gefunden, so erscheint eine entsprechende Fehlermeldung im text/plain-Format:

package astroIX;

import org.xml.sax.helpers.DefaultHandler;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.parsers.SAXParser;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.Attributes;
import java.util.HashMap;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class GetByGuid extends HttpServlet {

// Puffer für die Beziehung Guid -> Href
static HashMap hrefMap = new HashMap();

// SAX Event Handler als innere Klasse definieren
  class DocumentHandler extends DefaultHandler {

	String key=null;

    public void startElement(String namespaceURI,
                             String sName,
                             String qName,
                             Attributes attrs) throws SAXException {
      String href;

// <item>-Element und darin enthaltenes <link>-Element abfangen
// Wenn <item> ein guid-Attribut hat, guid mit href in Hash aufnehmen
		if (  qName.equals("item") &&
		     (key=attrs.getValue("guid"))!=null)
// Sonderzeichen aus Guid entfernen, um URL-Encoding zu vermeiden
		    key=key.replaceAll("[^0-9a-fA-F]","");

		else if ((key != null) && qName.equals("link") &&
		        ((href = attrs.getValue("href"))!=null)) {
// Eintrag aufnehmen
  	           hrefMap.put(key,href);
// Globales Feld key zurücksetzen
		       key = null;
		       }

	}

  }


public void doGet(HttpServletRequest request, HttpServletResponse response)    {

    java.io.PrintWriter out;
    String indexURL;
    String key;

// Um eine Fehlermeldung im Browser auszugeben, falls ID nicht gefunden
    response.setContentType("text/plain");
    try {
    out = response.getWriter();
    key = request.getRequestURI();
    key = key.substring(key.lastIndexOf('/')+1).toUpperCase();

// Guid nicht gefunden? Dann muss evtl. der Index neu gelesen werden
    if (hrefMap.get(key) == null) {
      indexURL = request.
                   getRequestURL().toString().
                   replaceFirst("(/servlet)?/getByGuid.*$","/sources/index.xml");
      SAXParserFactory factory = SAXParserFactory.newInstance();
      SAXParser saxParser = factory.newSAXParser();
      saxParser.parse( indexURL , new DocumentHandler() );
      }

// Nachschlagen. ob Guid existiert
    if (hrefMap.get(key) == null)
// Nein - Fehlermeldung ausgeben...
      out.println( "ID " + key + " wurde nicht gefunden");
    else
// Ja - Browser zum Redirect veranlassen
      response.sendRedirect( ((String) hrefMap.get(key)).substring(1));
// Alles abfangen
    } catch (Throwable t) { if (out == null) t.printStackTrace( System.out);
                            else t.printStackTrace( out ); }

  }

}
Das Servlet GetByGuid.java

Problematisch an der Funktion UuidCreate() ist, dass die von ihr erzeugten Guids Sonderzeichen enthalten, wenn auch nur in einer festen Formatierungsmaske, die die Lesbarkeit der Hexadezimalzahl verbessern soll. Um ein umständliches URL-Encoding der Sonderzeichen zu verhindern, vereinbaren wir, dass die Guid im Request ohne diese Formatierungs-Sonderzeichen mitgegeben wird. Wenn Sie beispielsweise folgende URL in Ihrem Browser eingeben,

http://www.astrotexte.ch/getByGuid/72209E000C1F11D99F46000374890932
so sollten Sie ohne Umschweife zur Betrachtung "Die Astrologie" weitergeleitet werden, also zur folgenden URL:
http://www.astrotexte.ch/sources/barbieri.html
Wenn Sie dagegen die Guid leicht abändern (und sie somit mit sehr hoher Wahrscheinlichkeit ungültig machen), erhalten Sie stattdessen im Browserfenster eine Fehlermeldung - zum Beispiel:
ID 72209E000C1F11D99F46000374890931 wurde nicht gefunden


Zum Inhaltsverzeichnis

Struktur der index.xml-Datei

Nach all diesen Erläuterungen müsste nun der Aufbau der Datei index.xml, dessen Anfang ich hier wiedergebe, verständlich sein:
<?xml version="1.0" encoding="ISO-8859-1"?>
<index title="Astrologische Untersuchungen"
       src="http://www.astrotexte.ch"
       mail="ruediger.plantiko@astrotextekeinSpam.ch"
       pubDate="Wed, 23 Sep 2004 12:00:00 GMT">
  <shorttext>
    Mit gründlichen und zeitlosen Studien
    weckt und fördert astrotexte.ch das Interesse an der Astrologie.
  </shorttext>
  <page name="index.html" title="Über diese Seiten">
    <subpage name="index.html" title="Editorial" titleLong="Liebe Leserin! Lieber Leser!">
      <text src="index_src.html"/>
    </subpage>
    <subpage name="index_neu.html" title="Was ist neu?">
      <text src="index_src_neu.html"/>
    </subpage>
    <subpage name="index_intro.html" title="Motivation">
      <item pubDate="Sat, 25 May 2002 21:58:00 GMT"
          chgDate="Sat, 15 May 2004 18:17:00 GMT"
          guid="{72209E00-0C1F-11D9-9F46-000374890932}">
        <link href="./sources/barbieri.html">Eine Einführung anhand eines Bildes</link>
        <shorttext>Eine allegorische Darstellung der Astrologie - und was man bei einer Bildbetrachtung
          über das Wesen der Astrologie lernen kann.
        </shorttext>
      </item>
      <item>
        <link href="http://www.astrologiezentrum.de/onlinetexte/einfuehrung/0_inhalt.html"
                 external="true">Peter Niehenke: Astrologie - eine Einführung</link>
        <shorttext>Für diejenigen, denen das ganze Gebiet der Astrologie noch fremd ist, stellt dieser
          auch als Reclam-Buch erschienene Text eine exzellente Einführung dar.
        </shorttext>
      </item>
      <item>
        <link href="http://www.astro.com/astrologie/in_intro_g.htm"
                 external="true">Astrodienst: Einführung in die Astrologie</link>
        <shorttext>Ein fundierter Schnellstart in die astrologische Symbolik, Ideal, um
         in das Thema einzusteigen und es von verschiedenen Seiten kennzulernen.
        </shorttext>
      </item>
      ...
Anfang meiner Indexdatei   (Vollständige index.xml-Datei anzeigen)


Zum Inhaltsverzeichnis

Das Stylesheet

Das unten folgende XSLT-Stylesheet übersetzt den Inhalt der index.xml-Datei in zur Zeit elf eigenständige HTML-Seiten, die zusammen meinen Index ausmachen. Um das XSLT-Programm aufzurufen, muss ich ledliglich ein winziges Java-Programm aufrufen, das den Xalan-Prozessor instanziiert und die XSLT-Anweisungen ausführt:
java xslt/Transform index.xsl index.xml >log.log
Diese Anweisung legt man sich sinnvollerweise in einem Batchfile ab, um einfach per Doppelclick die Aktualisierung des Index auszuführen, wenn sich am Inhalt (also an index.xml!) oder an der Darstellung desselben (also zwingend an index.xsl!) etwas geändert hat. Wenn Sie sich das nachfolgende XSLT-Stylesheet ansehen, werden Sie sehen, dass der grösste Teil der Präsentierungsdetails auch aus dem XSLT-Stylesheet verbannt ist und in ein eigenes Stylesheet index.css für die Indexseiten gewandert ist. Dort befinden sich Angaben wie Dimensionierungen der Elemente, die gelbe Hintergrundfarbe für den aktuell ausgewählten Eintrag, Angaben zu Tabellenrahmungen etc. Das XSLT-Stylesheet und somit auch der daraus erzeugte HTML-Quellcode enthält im wesentlichen nur noch Angaben zum Arrangement der Daten und stellt den Bezug der Daten zu ihren zugehörigen CSS-Stylesheet-Klassen und illustrierenden Graphiken (Icons) her.

Ein weiterer Vorteil der Seitengenerierung mit XSLT liegt darin, dass das resultierende HTML automatisch wohlgeformt ist, also z.B. keine ungültigen Schachtelungen enthält. Dass es den HTML-Regeln und nicht den Regeln der XML-Syntax folgt, liegt einzig und allein daran, dass ich dies im <xsl:output> explizit so festgelegt habe. Um eine andere Syntax – XML oder XHTML – zu erhalten, müsste man lediglich den Wert des method-Attributs auf das gewünschte Ausgabeformat verändern. Das Stylesheet muss ja einen wohlgeformten XML-Baum darstellen, sonst könnte es den Parser gar nicht passieren und somit auch nicht ausgeführt werden. Die Wohlgeformtheit des Zielbaums ist eine automatische Folge hiervon. (Es gibt zwar die Möglichkeit, diese Wohlgeformtheit für Teilknotenmengen des Quellbaums auszuschalten, aber warum sollte man hiervon Gebrauch machen?).

Das XSLT Stylesheet wird also von einem Java-Programm eingelesen und dann Zeile für Zeile interpretiert und ausgeführt. Letztlich ist XSLT also eine Skriptsprache. Die durch die Spezifikation festgelegten Grenzen machen sie jedoch im Vergleich zu anderen Programmiersprachen wenig flexibel und etwas schwerfällig. Es gibt aber auch klare Vorteile, mit XSLT zu arbeiten statt die Transformation selbst in einer anderen Programmiersprache zu definieren:

Darüberhinaus verschwimmen die Grenzen zwischen den verschiedenen Sprachwelten – denn Prozessoren wie Xalan bieten die Möglichkeit, im XSLT-Code Java-Klassen zu instanziieren, Attribute zu evaluieren und Methoden auszuführen. Wo XSLT zu schwerfällig für eine bestimmte Aufgabe erscheint, empfiehlt es sich, diese Aufgabe beispielsweise als Java-Methode zu implementieren und vom Stylesheet aus lediglich aufzurufen.
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:redirect="http://xml.apache.org/xalan/redirect"
                xmlns:date="http://exslt.org/dates-and-times"
                xmlns:xalan="http://xml.apache.org/xalan"
                exclude-result-prefixes="xalan"
                extension-element-prefixes=" date redirect ">
    <xsl:output method="html"
                encoding="iso-8859-1"
                indent="yes"
                doctype-public="-//W3C//DTD HTML 4.01 Transitional//EN"
                doctype-system="http://www.w3.org/TR/html4/loose.dtd"/>
    <xsl:template match="/">
    <!-- Erste Seite merken, wird nachher in Schleifensteuerung verwendet -->
    <xsl:variable name="firstPage" select="/index/page[1]"/>
    <!-- Hauptschleife über sämtliche page- und subpage-Knoten -->
    <xsl:for-each select="index/page | index/page/subpage">
      <!-- currentUnit repräsentiert die aktuelle Seite oder Unterseite -->
      <xsl:variable name="currentUnit" select="."/>
      <!-- currentPage repräsentiert die aktuelle Seite (nicht Unterseite) -->
      <xsl:variable name="currentPage" select="ancestor-or-self::page"/>
      <!-- Hat der aktuelle Knoten Subpages? Nein, dann neue Datei schreiben -->
      <xsl:if test="not($currentUnit/subpage)">
        <!-- Um mehr als eine Zieldatei zu erzeugen, verwenden wir die xalan-Erweiterung "redirect" -->
        <redirect:write file="{$currentUnit/@name}">
          <!-- Hier beginnt der HTML-DOM einer einzelnen Seite -->
          <html>
            <head>
              <!-- Zeitstempel und Version des XSLT-Prozessors sind nützliche Informationen
                   Für den Zeitstempel benötigen wir die Erweiterung date().
                   Die Funktion system-property() dagegen ist XSLT-Standard -->
              <xsl:comment>Automatisch generierte HTML-Seite
                Quelle: index.xml
                Transformation: index.xsl
                XSLT-Prozessor: Xalan Java <xsl:value-of select="system-property( 'version' )"/>
                Generierungsdatum: <xsl:value-of select="date:date-time()"/>
              </xsl:comment>
              <meta name="description" content="Astrologische Fachartikel zu verschiedenen Themen"/>
              <meta http-equiv="content-language" content="de"/>
              <meta http-equiv="content-script-type" content="text/javascript"/>
              <meta http-equiv="content-style-type" content="text/css"/>
              <meta name="author" content="Rüdiger Plantiko"/>
              <meta name="keywords" lang="de" content="Astrologe, Astrologie, astrologisch,
                                                       Horoskopie, horoskopisch, Sonne, Mond, Merkur,
                                                       Venus, Mars, Jupiter, Saturn,
                                                       Uranus, Neptun, Pluto, Aszendent, MC, Häuser,
                                                       Direktionen, Speculum, Alan Leo, Gauricus, Regiomontanus,
                                                       Campanus, Renaissance, Widder, Stier, Zwillinge, Krebs,
                                                       Löwe, Jungfrau, Waage,
                                                       Skorpion, Schütze, Steinbock, Wassermann, Fische"/>
              <link rel="stylesheet" type="text/css" href="./styles/main.css"></link>
              <link rel="stylesheet" type="text/css" href="./styles/index.css"></link>
              <script src="scripts/global.js" type="text/javascript"></script>
              <!-- Fenstertitel -->
              <title><xsl:value-of select="/index/@title"/></title>
            </head>
            <body>
                <!-- Überschrift -->
                <h1><xsl:value-of select="/index/@title"/></h1>
                <!-- Beginn der sichtbar gerahmten Tabelle -->
                <table class="index">
                  <!-- Hauptnavigation aufbauen: Über alle page-Elemente loopen -->
                  <xsl:for-each select="/index/page">
                    <tr>
                      <td class="topic">
                        <!-- Die aktuelle Seite bekommt zusätzlich die Stilklasse active -->
                        <xsl:if test=".=$currentPage">
                          <xsl:attribute name="class">topic active</xsl:attribute>
                        </xsl:if>
                        <!-- Link auf Seite in Hauptnavigation -->
                        <a href="{./@name}">
                          <xsl:value-of select="./@title"/>
                        </a>
                      </td>
                      <!-- Die Workarea mit dem aktuellen Seiteninhalt wird nur einmal prozessiert -->
                      <xsl:if test="./@name=$firstPage/@name">
                        <td rowspan="6" class="workArea">
                          <!-- Unternavigation aufbauen, wenn es Subpages gibt -->
                          <xsl:if test="$currentPage/subpage">
                            <table class="subIndex">
                              <tr>
                                <!-- Über alle Subpages der aktuellen Page loopen -->
                                <xsl:for-each select="$currentPage/subpage">
                                  <td class="subTopic">
                                    <!-- Die aktuelle Subpage bekommt zusätzlich die Stilklasse active -->
                                    <xsl:if test="./@name=$currentUnit/@name">
                                      <xsl:attribute name="class">subTopic active</xsl:attribute>
                                    </xsl:if>
                                    <a href="{./@name}"><xsl:value-of select="./@title"/></a>
                                  </td>
                                </xsl:for-each>
                              </tr>
                            </table>
                          </xsl:if>
                          <!-- Überschrift innerhalb der Workarea -->
                          <h2>
                            <xsl:choose>
                              <xsl:when test="$currentUnit/@titleLong">
                                <!-- Wenn ein Langtitel existiert, diesen nehmen... -->
                                <xsl:value-of select="$currentUnit/@titleLong"/>
                              </xsl:when>
                              <xsl:otherwise>
                                <!-- ... sonst den Kurztitel, der auch für die Navigation verwendet wird -->
                                <xsl:value-of select="$currentUnit/@title"/>
                              </xsl:otherwise>
                            </xsl:choose>
                          </h2>
                          <!-- Nach der Überschrift: Etwaigen <text>-Knoten transformieren -->
                          <xsl:apply-templates select="$currentUnit/text"/>
                          <!-- Schliesslich die Item-Liste aufbauen -->
                          <table class="contents">
                            <!-- Über alle items der aktuellen Seite loopen -->
                            <xsl:for-each select="$currentUnit/item">
                              <tr>
                                <td class="icons">
                                  <!-- Aufzählungs-Icon(s) ermitteln -->
                                  <xsl:if test="not(./link/@external='true')">
                                    <img src="./graphics/seite.gif"
                                         width="16"
                                         height="15"
                                         alt="Seite von astrotexte.ch"/>
                                  </xsl:if>
                                  <xsl:if test="./link/@external='true'">
                                    <img src="./graphics/url.gif"
                                         width="16"
                                         height="15"
                                         alt="Link auf externe Webseite"/>
                                  </xsl:if>
                                  <xsl:if test="./link/@pdf='true'">
                                    <img src="./graphics/pdf.gif"
                                         width="16"
                                         height="15"
                                         alt="Datei im PDF-Format"/>
                                  </xsl:if>
                                  <xsl:if test="./link/@commercial='true'">
                                    <img src="./graphics/kostet.gif"
                                         width="16"
                                         height="15"
                                         alt="Download ist kostenpflichtig"/>
                                  </xsl:if>
                                </td>
                                <!-- Link darstellen -->
                                <td class="contents">
                                  <a href="{./link/@href}">
                                    <xsl:value-of select="link"/>
                                  </a>
                                  <br/>
                                  <!-- Etwaigen erklärenden Zusatztext (<shorttext>) hier einfügen -->
                                  <xsl:apply-templates select="shorttext"/></td>
                              </tr>
                            </xsl:for-each>
                          </table>
                        </td>
                      </xsl:if>
                    </tr>
                  </xsl:for-each>
                </table>
                <!-- Nun ist die gerahmte Tabelle fertig - es folgt noch die Fusszeile -->
                <table class="footer">
                  <tr>
                    <td width="16.67%" style="text-align:left" nowrap="nowrap">
                      <font size="-1">
                        <i>Redaktion: Dr. Rüdiger Plantiko</i>
                      </font>
                    </td>
                    <td width="16.67%" style="text-align:center">
                      <a href="http://validator.w3.org/check?uri=referer">
                        <img border="0"
                             src="./graphics/valid-html401.gif"
                             alt="HTML Quellcode entspricht 4.01-Spezifikation"
                             height="31"
                             width="88"></img>
                      </a>
                    </td>
                    <td width="16.67%" style="text-align:center">
                      <a href="http://www.cs.fiu.edu/~flynnj/noframes.html">
                        <img border="0"
                             src="./graphics/framefree.gif"
                             width="70"
                             height="30"
                             alt="Dies ist eine framefreie Webseite!"></img>
                      </a>
                    </td>
                    <td width="16.67%" style="text-align:center">
                      <a href="http://jakarta.apache.org/tomcat/">
                        <img border="0"
                             src="./graphics/tomcat.gif"
                             width="52"
                             height="40"
                             alt="Es bedient Sie ein Apache Tomcat Java Web Server"></img>
                      </a>
                    </td>
                    <td width="16.67%" style="text-align:center">
                      <a href="erpressum.html">Impressum</a></td>
                    <td width="16.67%" style="text-align:right" nowrap="nowrap">
                      <font size="-1">
                        Mail an
                        <script type="text/javascript" language="javascript">
                          document.write(mailto("ruediger", "plantiko","astrotexte","ch"));
                        </script>
                      </font>
                    </td>
                  </tr>
                </table>
            </body>
          </html>
        </redirect:write>
      </xsl:if>
    </xsl:for-each>
  </xsl:template>
  <!-- Die Kurztexte werden mit all ihren HTML-Formatierungen kopiert. Die
       dafür nötige Rekursion wird durch folgende beiden Templates gewährleistet: -->
  <xsl:template match="shorttext">
    <xsl:apply-templates/>
  </xsl:template>
  <xsl:template match="text">
    <xsl:choose>
      <xsl:when test="./@src">
        <div class="pageText">
          <!-- Möglichkeit anbieten, dass Text aus externem Dokument eingelesen werden kann
               Sehr einfach mit der document()-Funktion zu realisieren (XSLT-Standard) -->
          <xsl:comment>Inkludiertes Quelldokument <xsl:value-of select="./@src"/></xsl:comment>
          <xsl:apply-templates select = "document(./@src)/include"/>
          <xsl:comment>Ende inkludiertes Quelldokument <xsl:value-of select="./@src"/></xsl:comment>
        </div>
      </xsl:when>
      <xsl:otherwise>
        <div class="pageText">
          <xsl:apply-templates/>
        </div>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
  <xsl:template match="text//*|shorttext//*|include//*">
    <xsl:copy-of select="."/>
  </xsl:template>
</xsl:stylesheet>
Das Stylesheet index.xsl


Zum Inhaltsverzeichnis

Das RSS-Format

Es braucht natürlich niemand zu glauben, dass das hier vorgestellte Format für Indexseiten eine besonders originelle Idee ist. Um eine Liste von Links zu verwalten, kommt man beinahe zwangsläufig auf ein Format, das dem hier vorgestellten sehr ähnlich ist. Ein solches Format braucht vor allem die Zunft der Weblogger, um Links auf ihre neuen Artikel an zentraler Stelle zu sammeln. Auch für Nachrichtenportale ist es nützlich, um Inhalte von verschiedenen Quellen, sogenannten Channels, an einer Stelle zusammenfassen zu können. So hat Netscape vor mehr als fünf Jahren das XML-basierte -Format aus der Taufe gehoben, das es inzwischen in der Version 2.0 gibt (die genaue Spezifikation findet man unter http://blogs.law.harvard.edu/tech/rss). Es sieht zur Zeit so aus, dass auch RSS 2.0 wohl noch nicht das end- und letztgültige Format für das Sammeln und Verteilen von Nachrichten und Inhalten ist.

Bei der weiten Verbreitung, die das Format mittlerweile jedoch gefunden hat, wäre es wünschenswert, die eigenen Indexseiten möglichst automatisch in dieses Format transformieren zu können. Dank der beschriebenen Trennung von Layout (index.xsl und index.css) und Inhalt (index.xml) ist das kein Problem: Man muss nur eine XSLT-Transformation schreiben, die diese Konvertierung vornimmt. In meinem Beispiel wird das durch die folgende Transformation erreicht:

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:java="http://xml.apache.org/xalan/java"
                exclude-result-prefixes="java">

  <xsl:output method="xml"
              encoding="iso-8859-1"
              indent="yes"/>


  <!-- Locale US, Formatierungsklasse und aktuelles Datum instanziieren -->
  <xsl:variable name="locale_us"
                select="java:java.util.Locale.new('en','us')"/>
  <xsl:variable name="rfc822_formatter"
                select="java:java.text.SimpleDateFormat.new('EEE, dd MMM yyyy HH:mm:ss Z',$locale_us)"/>
  <xsl:variable name="num_formatter"
                select="java:java.text.SimpleDateFormat.new('yyyyMMddHHmmss')"/>
  <xsl:variable name="date"
                select="java:java.util.Date.new()"/>


  <!-- Beginn der eigentlichen Verarbeitung -->
  <xsl:template match="/">
    <rss version="2.0">
      <channel>
         <!-- Kopfdaten (auf den gesamten Channel bezogen) -->
         <title><xsl:value-of select="/index/@title"/></title>
         <link><xsl:value-of select="/index/@src"/></link>
         <description><xsl:value-of select="/index/shorttext"/></description>
         <language>de-de</language>
         <pubDate><xsl:value-of select="/index/@pubDate"/></pubDate>
         <lastBuildDate><xsl:value-of select="java:format($rfc822_formatter, $date)"/></lastBuildDate>
         <!-- Link auf die Spezifikation des RSS 2.0 Formats -->
         <docs>http://blogs.law.harvard.edu/tech/rss</docs>
         <managingEditor><xsl:value-of select="/index/@mail"/></managingEditor>
         <!-- Schleife über die <item>s -->
         <!-- Nur den eigenen Content publizieren = items, die eine Guid haben -->
         <xsl:for-each select="index/page/item[@guid] | index/page/subpage/item[@guid]">
           <!-- Nach Datum sortieren - Datum aus RFC822 in sortierbaren Ziffernkette wandeln -->
           <xsl:sort select="string(java:format($num_formatter,java:parse($rfc822_formatter,string(./@pubDate))))"
                     order="descending"/>
           <item>
             <title><xsl:value-of select="./link"/></title>
             <!-- Absoluten Link aus relativem aufbauen -->
             <link><xsl:value-of select="/index/@src"/>/<xsl:value-of select="substring-after(./link/@href,'./')"/></link>
             <description><xsl:value-of select="./shorttext"/></description>
             <pubDate><xsl:value-of select="./@pubDate"/></pubDate>
             <guid>http://www.astrotexte.ch/getByGuid/<xsl:value-of
                 select="java:replaceAll(java:java.lang.String.new(string(./@guid)),'[^0-9a-fA-F]+','')"/></guid>
           </item>
         </xsl:for-each>
       </channel>
     </rss>
   </xsl:template>
</xsl:stylesheet>
Das Stylesheet index2rss.xsl

Vergleicht man diese Transformation mit der zuvor beschriebenen index.xsl, so fällt zunächst der viel geringere Umfang auf: Der meiste Aufwand ist der Formatkonvertierung gewidmet, für die Transformation des XML-Baums selbst werden nur wenigen Codezeilen benötigt. Das liegt zum einen daran, dass das Zielformat nun XML ist und nicht HTML, und zwar ein reines Datenformat ohne Präsentierungsinformation. Zum anderen liegt es natürlich daran, dass die Datenformate selbst so ähnlich sind, da sie beide im Prinzip das gleiche tun, nämlich eine Liste von Links zu beschreiben.

Ich erwähnte schon, dass XSLT für normale Programmieraufgaben etwas schwerfällig daherkommt. Es empfiehlt sich daher, für komplexere Berechnungen eine andere Programmiersprache heranzuziehen und diese in das XSLT-Stylesheet einzubinden. Im oben aufgeführten Stylesheet verwende ich Java-Standardklassen zur Datumsformatierung, um aus dem RFC822-Format (z.B. "Wed, 23 Sep 2004 12:00:00 +0200") eine Ziffernfolge zu erhalten (im Beispiel: "20040923120000"), mit welcher sich die Liste der <item>s absteigend nach Publikationsdatum sortieren läßt, so daß das neueste <item> zuoberst in der Liste erscheint. Der Xalan-Prozessor ist sehr flexibel durch andere Programmiersprachen erweiterbar. Speziell für die Sprache Java kommt man – wie in diesem Beispiel – häufig bereits mit dem Java-Standard aus, muss also keine zusätzlichen eigenen Hilfsklassen programmieren (obwohl auch dies selbstverständlich möglich ist). Darüberhinaus kann man auch Code in den gängigen Script-Sprachen wie Perl, JavaScript, VBScript, JPython in das XSLT-Stylesheet einbinden.

Einige Attribute der oben vorgestellten index.xml-Datei erklären sich nun: Die Information wird im RSS 2.0-Format benötigt, gehört aber als Inhalt dennoch in das index.xml. Beispielsweise die Angabe einer eMail-Adresse für das <managingEditor>-Tag. Ebenso erklärt sich aus der RSS 2.0-Spezifikation, dass ich für Datumsangaben das etwas altmodische RFC 822-Format verwendet habe. Auf die Idee, Guids einzusetzen, bin ich erst nach Lektüre der RSS-Spezifikation gekommen, ich halte sie aber, unabhängig von RSS, auch für meine eigene Indexverwaltung für eine nützliche Einrichtung. Das Attribut isPermaLink des <guid>-Tags, dessen Default auf true steht, besagt, dass der angegebene String das item nicht nur eindeutig kennzeichnet, sondern auch einen permanenten Link auf das Item darstellt. Würde man es auf false setzen, würde man den Wert des <guid>-Elements nur zur eindeutigen Kennzeichnung, nicht aber als Link verwenden. Meinem oben vorgestellten Servlet GetByGuid.java ist es zu danken, dass wir im RSS-File die Guids zugleich als Links anbringen können, so dass wir den Default des Attributs isPermaLink verwenden können. Die restlichen Anweisungen in der <guid>-Zeile des XSLT sind Standardmethoden der Klasse java.lang.String, die die Sonderzeichen aus der formatierten Guid entfernen (also aus dem Wert {72209E00-0C1F-11D9-9F46-000374890932} den Wert 72209E000C1F11D99F46000374890932 machen), da der aus reinen Hexadezimalzeichen bestehende Wert für die URL besser verwendbar ist.

Zu bemerken ist noch, dass bei dieser Transformation die durch die Pages und Subpages definierte Struktur verlorengeht. Das ist so und soll auch so sein, weil das RSS-Format nur eine flache Liste von Links darstellen kann. Strukturinformationen sind in diesem Format nicht vorgesehen und werden daher von der Transformation nicht übernommen. Es empfiehlt sich - wie hier - die Items des Index im RSS-Feed absteigend nach Veröffentlichungsdatum zu sortieren.


Zum Inhaltsverzeichnis

Ein Perl-Script für das WS_FTP-Log

Irgendwann ist für jede neue Webseite die Zeit reif, sie vom lokalen Verzeichnis ins Internet zu übertragen. Zum Publizieren gehört auch, im Index einen Verweis auf die neue Seite einzufügen. Das bedeutet, das neue <item> muss in die bestehende Struktur von Themen und Unterthemen eingeordnet werden. Der Eintrag muss eine Guid bekommen, und das Upload-Datum muss bestimmt und im RFC822-Format in das pubDate-Attribut des items eingefügt werden. Ich verwende zum Upload meines lokalen Verzeichnisses ins Internet das Programm WS_FTP von ipswitch. Dieses schreibt ein sehr einfaches, maschinenlesbares Log aller Upload-Aktivitäten in die Quellordner, von denen aus der Upload durchgeführt wurde. Es folgen zur Probe einige Zeilen aus diesem Log:
2003.06.04 22:24 B C:\webpages\sources\barbieri.html --> astrotexte.ch /sources barbieri.html
2003.06.10 00:59 B C:\webpages\sources\konst_f1.html --> astrotexte.ch /sources konst_f1.html
2003.06.10 00:59 B C:\webpages\sources\konstellationen.jsp --> astrotexte.ch /sources konstellationen.jsp
2003.06.10 01:06 B C:\webpages\sources\konst_f1.html --> astrotexte.ch /sources konst_f1.html
2003.06.10 01:07 B C:\webpages\sources\konst_f1.html --> astrotexte.ch /sources konst_f1.html
Einige Zeilen von WS_FTP.log

Mit dem folgenden Perl-Script erzeuge ich mir eine Liste von Publikations- und Änderungsdaten für alle Dateien, die von einem bestimmten Ordner aus hochgeladen wurden. Wenn ich als Argument einen Dateinamen angebe, so wird für diese Datei ein Vorschlag für das öffnende <item>-Tag erzeugt, wie ich es zum Einordnen in das Indexverzeichnis benötige. Das Script läuft mit meiner Active Perl-Installation auf einem Windows-Rechner, sollte aber mit wenig Aufwand auf andere Plattformen oder FTP-Log-Formate umgestellt werden können.

#!C:\Perl\bin\perl.exe

use Time::Local;
use Win32::Guidgen;

my $OF, $line = "", %pubdate=(), %chgdate=(), $i=0, $arg = shift, $name, $c, $p, $g;

# Das ganze Log muss man sowieso einlesen, auch wenn man nur eine einzelne Datei sucht
open(OF, "WS_FTP.log");
while ( $line = <OF> ) {
  if ( $line =~ /^(\d{4})\.(\d\d)\.(\d\d) (\d\d):(\d\d) B (.*\\(\w+\.\w+))/ ) {
    $t = timelocal 0,$5,$4,$3,$2-1,$1;
    # chgdate-Hash merkt sich das letzte Upload-Datum
    if (!($t1=$chgdate{$7}) or $t1<$t) {$chgdate{$7}=$t;}
    # pubdate-Hash merkt sich das erste Upload-Datum
    if (!($t1=$pubdate{$7}) or $t1>$t) {$pubdate{$7}=$t;}
    }
  }
close(OF);

# On the fly nach dem Dateinamen sortieren (case insensitive)
for $name ( sort { lc($a) cmp lc($b) } keys %pubdate ) {

  # Publikationsdatum
  $p = gmtime $pubdate{$name};
  # Das Output von gmtime ist noch nicht ganz RFC822,
  # es müssen noch einige Glieder umgestellt werden
  $p =~ /^(\w\w\w)\s+(\w\w\w)\s+(\d\d?)\s+([\d:]+)\s+(\d{4,4})/o;
  $p = "$1, $3 $2 $5 $4 GMT";

  # Änderungsdatum, gleiches Vorgehen
  $c = gmtime $chgdate{$name};
  $c =~ /^(\w\w\w)\s+(\w\w\w)\s+(\d\d?)\s+([\d:]+)\s+(\d{4,4})/o;
  $c = "$1, $3 $2 $5 $4 GMT";

  # Kein Argument übergeben? Dann Liste
  if ($arg eq "") {
    printf "%-30s %s\n%30s %s\n",$name, $p, " ",  $c;
    }
  # Argument übergeben? Dann <item>-Tag
  elsif ($name eq $arg) {
    $g = Win32::Guidgen::create();
    print qq(<item pubDate="$p"\n      chgDate="$c"\n      guid="$g">);
    }

  }
Das Perl-Script ws_ftp_eval.pl

Es folgt eine Beispielausgabe für den Aufruf des Scripts mit einem Dateinamen als Argument. Die aus dem Upload vorgeschlagenen Werte sind nur Näherungen für die tatsächlichen Werte, sie müssen aus den oben angeführten Gründen nicht immer stimmen. Bei der Guid ist zu beachten, dass sie natürlich nur bei der ersten Übernahme in den Index einzusetzen ist, nicht bei späteren Aktualisierungen (denn das Script zieht bei jedem Lauf eine neue Guid).

<item pubDate="Thu, 30 Sep 2004 21:35:00 GMT"
      chgDate="Sun, 3 Oct 2004 21:37:00 GMT"
      guid="{9E1DEC60-1656-11D9-9F46-000374890932}">
Das automatisch generierte <item>-Tag

Dagegen folgt hier ein Auszug der Liste, die mir bei Aufruf des Scripts ohne Parameter ausgegeben wird:

...
TaphiSpec.htm                  Mon, 12 Aug 2002 21:00:00 GMT
                               Sun, 12 Oct 2003 22:22:00 GMT
test.htm                       Sun, 26 May 2002 08:00:00 GMT
                               Sun, 26 May 2002 08:00:00 GMT
testHoro.html                  Mon, 10 Mar 2003 20:31:00 GMT
                               Mon, 10 Mar 2003 20:32:00 GMT
TrapSpec.htm                   Wed, 19 Jun 2002 20:29:00 GMT
                               Sun, 12 Oct 2003 22:22:00 GMT
uranusSpec.htm                 Sun, 20 Jun 2004 20:03:00 GMT
                               Sun, 20 Jun 2004 20:03:00 GMT
Vertex.zip                     Sun, 26 May 2002 08:00:00 GMT
                               Fri, 16 Aug 2002 20:19:00 GMT
...
Eine Liste mit Publikations- und Änderungsdaten


Zum Inhaltsverzeichnis Ohne Hintergrund ausdrucken Zurück zur Homepage