Ein Schema entwickeln ("Schema Evolution")

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

Blickt man jenseits der HTML-Erzeugung, ist eine Schlüsselrolle von XSLT die Transformation eines XML-Formats in ein anderes. In vielen Fällen sind dies keine radikalen Transformationen, sondern kleinere Verbesserungen, wie das Hinzufügen von neuen Attributen, die Änderung der Elementreihenfolge oder die Entfernung von ungenutzten Daten. Wenn Sie nur eine Handvoll XML-Dateien zu transformieren haben, ist es wesentlich einfacher, das XML direkt zu bearbeiten, anstatt den ganzen Aufwand zu treiben, den ein Stylesheet mit sich bringt. In den Fällen, wo allerdings eine große Sammlung von XML-Dokumenten existiert, kann ein einzelnes XSLT-Stylesheet Transformationen an einer ganzen Bibliothek von XML-Dateien in einem einzelnen Durchlauf durchführen. Für B2B-Anwendungen ist die Entwicklung eines Schemas nützlich, wenn beispielsweise verschiedene Kunden dieselben Daten in unterschiedlicher Form benötigen.

Eine XML-Beispieldatei

Lassen Sie uns annehmen, Sie haben eine Log-API für Ihre Java-Programme geschrieben. Die Logdateien werden in XML ausgegeben, und zwar formatiert, wie im folgenden Beispiel gezeigt.

Beispiel: Logdatei vor der Transformation

<?xml version="1.0" encoding="ISO-8859-1"?>
<log>
   <meldung text="Eingabeparameter war null">
      <typ>FEHLER</typ>
      <wann>
         <jahr>2000</jahr>
         <monat>01</monat>
         <tag>15</tag>
         <stunde>03</stunde>
         <minute>12</minute>
         <sekunde>18</sekunde>
      </wann>
      <wo>
         <klasse>com.irgendwas.util.StringUtil</klasse>
         <methode>reverse(String)</methode>
      </wo>
   </meldung>
   <meldung text="config-Datei kann nicht gelesen werden">
      <typ>WARNUNG</typ>
      <wann>
         <jahr>2000</jahr>
         <monat>01</monat>
         <tag>15</tag>
         <stunde>06</stunde>
         <minute>35</minute>
         <sekunde>44</sekunde>
      </wann>
      <wo>
         <klasse>com.foobar.servlet.MainServlet</klasse>
         <methode>init( )</methode>
      </wo>
   </meldung>
   <!-- weitere Meldungen... -->
</log>

Wie Sie in diesem Beispiel sehen können, ist das Dateiformat sehr ausführlich. Besonders interessant ist, wie Datum und Uhrzeit geschrieben sind. Da Logdateien sehr groß sein können, wäre es eine gute Idee, ein kürzeres Format für diese Information zu wählen. Zusätzlich wird der Text als Attribut des <meldung>-Elements und der Typ als Kindelement gespeichert. Es wäre sinnvoller, den Typ als Attribut und die Meldung als Element anzugeben. Zum Beispiel:

<meldung typ="WARNUNG">
  <text>Dies ist der Meldungstext. Mehrzeilige Meldungen lassen sich leichter realisieren, wenn statt eines Attributs ein Element verwendet wird.</text>
  ... Rest ausgelassen

Die Identitätstransformation

Wenn Sie ein Schema-Evolutions-Stylesheet schreiben, ist es eine gute Idee, mit einer Identitätstransformation zu beginnen. Dabei handelt es sich um ein sehr einfaches Template, das nur das Original-XML-Dokument lädt und es in ein neues Dokument mit denselben Elementen und Attributen wie im Originaldokument »transformiert«. Das folgende Beispiel zeigt ein Stylesheet, das ein Template zur Identitätstransformation enthält.

Beispiel: Identitaetstransformation.xslt

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" version="1.0" encoding="ISO-8859-1" indent="yes"/>
  <xsl:template match="@*|node( )">
    <xsl:copy>
      <xsl:apply-templates select="@*|node( )"/>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

Erstaunlicherweise benötigt man nur ein einzelnes Template, um die Identitätstransformation durchzuführen, egal wie komplex die XML-Daten sind. Unser Stylesheet kodiert das Ergebnis in ISO-8859-1 und rückt die Zeilen ein, unabhängig vom Original-XML-Format. In XPath ist node( ) ein Knotentest, der auf alle Kindelemente des aktuellen Kontexts paßt. Das ist gut, läßt aber die Attribute des aktuellen Kontexts aus. Aus diesem Grund muß @* mit node( ) wie folgt kombiniert werden:

<xsl:template match="@*|node( )">

Auf deutsch heißt das nichts anderes, als daß das Template auf alle Attribute und jedes Kindelement des aktuellen Kontexts paßt. Da node( ) Elemente, Kommentare, Verarbeitungsanweisungen und sogar Text umfaßt, wird dieses Template auf alles passen, was im XML-Dokument vorkommen kann.

Innerhalb unseres Templates verwenden wir <xsl:copy>. Wie Sie sicher schon vermutet haben, weist es den XSLT-Prozessor an, den aktuellen Knoten einfach in den Ergebnisbaum zu kopieren. Um mit der Verarbeitung fortzufahren, wählt <xsl:apply-templates> dann mit dem folgenden Code alle Attribute oder Kindelemente des aktuellen Kontexts aus:

<xsl:apply-templates select="@*|node( )"/>

Transformation von Elementen und Attributen

Haben Sie die Identitätstransformation erst einmal abgetippt und getestet, wird es Zeit, zusätzliche Templates hinzuzufügen, die die eigentliche Schema-Evolution durchführen. In XSLT ist es möglich, daß zwei oder mehr Templates auf ein Muster in den XML-Daten passen. In diesen Fällen wird das spezifischere Template instantiiert. Ohne hier technisch zu sehr ins Detail zu gehen, hat eine explizite Übereinstimmung wie <xsl:template match="when"> Vorrang vor dem Identitätstransformations-Template, das grundsätzlich ein auf jedes Attribut und jeden Knoten passendes Wildcard-Muster darstellt. Um spezifische Elemente und Attribute zu modifizieren, fügen Sie einfach spezifischere Templates zum existierenden Identitätstransformations-Stylesheet hinzu.

Im Beispiel mit der Logdatei ist ein Hauptproblem die Menge der XML-Daten, die für jedes <wann>-Element geschrieben werden. Anstatt das Datum und die Uhrzeit mit einer Reihe von Kindelementen darzustellen, bietet sich die folgende, kürzere Syntax an:

<timestamp zeit="06:35:44" tag="15" monat="01" jahr="2002"/>

Das folgende Template führt die dafür notwendige Transformation durch:

<xsl:template match="wann">
  <!-- 'wann' wird in 'timestamp' geändert und seine Kindelemente werden in Attribute umgewandelt -->
  <timestamp zeit="{stunde}:{minute}:{sekunde}" tag="{tag}" monat="{monat}" jahr="{jahr}"/>
</xsl:template>

Dieses Template kann zum Identitätstransformations-Stylesheet hinzugefügt werden und hat Vorrang, wenn ein <wann>-Element auftaucht. Anstatt <xsl:copy> zu verwenden, produziert dieses Template ein neues <timestamp>-Element. Dabei werden AVTs eingesetzt, um die Attribute für dieses Element zu spezifizieren und die Elemente effektiv in Attribute umzuwandeln. Die AVT-Syntax {stunde} ist äquivalent zur Auswahl des <stunde>-Kindelements des <wann>-Elements. Sie werden bemerken, daß XSLT-Prozessoren die Reihenfolge der Attribute nicht notwendigerweise beibehalten. Das ist auch nicht wichtig, da die relative Reihenfolge von Attributen in XML bedeutungslos ist und Sie die Reihenfolge von XML-Attributen nicht erzwingen können.

Als nächstes gilt es, das <meldung>-Element anzupacken. Wie bereits erwähnt, möchten wir das text-Attribut in ein Element und das <typ>-Element in ein Attribut umzuwandeln. Wie zuvor fügt man ein neues Template hinzu, das auf das <meldung>-Element paßt und Vorrang vor der Identitätstransformation erhält. Kommentare im Code erläutern, was bei jedem Schritt passiert.

<!-- <meldung>-Elemente lokalisieren -->
<xsl:template match="meldung">
   <!-- der aktuelle Knoten wird kopiert, nicht jedoch seine Attribute -->
   <xsl:copy>
      <!-- das <typ>-Element wird in ein Attribut umgewandelt -->
      <xsl:attribute name="typ">
         <xsl:value-of select="typ"/>
      </xsl:attribute>
      <!-- das text-Attribut wird in ein Kindelement umgewandelt -->
      <xsl:element name="text">
         <xsl:value-of select="@text"/>
      </xsl:element>
      <!-- da das select-Attribut fehlt, verarbeitet xsl:apply-templates alle Kindelemente des aktuellen Knotens (nicht jedoch Attribute oder Verarbeitungsanweisungen!) -->
      <xsl:apply-templates/>
   </xsl:copy>
</xsl:template>

Damit ist das Stylesheet fast komplett. <xsl:copy> kopiert einfach das <meldung>-Element in den Ergebnisbaum, jedoch ohne seine Attribute oder Kindelemente. Wir können neue Attribute explizit mit <xsl:attribute> hinzufügen und neue Kindelemente mit <xsl:element>. Danach weist <xsl:apply-templates> den Prozessor an, mit dem Transformationsprozeß für die Kindelemente von <meldung> fortzufahren. Ein Problem ist an dieser Stelle, daß das <typ>-Element zwar in ein Attribut umgewandelt, aber noch nicht aus dem Dokument entfernt wurde. Die Identitätstransformation kopiert immer noch das <type>-Element ohne Modifikation in den Ergebnisbaum. Um das zu beheben, fügt man einfach ein leeres Template wie folgt hinzu:

<xsl:template match="typ"/>

Das komplette Schema-Evolutions-Stylesheet enthält die vorangegangenen Templates. Ohne den kompletten Code zu duplizieren, hier ist seine grobe Struktur:

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <xsl:output method="xml" version="1.0" encoding="ISO-8859-1" indent="yes"/>
   <!-- die Identitätstransformation -->
   <xsl:template match="@*|node( )">
      ...
   </xsl:template>
   <!-- <meldung>-Elemente lokalisieren -->
   <xsl:template match="meldung">
      ...
   </xsl:template>
   <!-- <wann>-Elemente lokalisieren -->
   <xsl:template match="wann">
      ...
   </xsl:template>
   <!-- das <typ>-Element unterdrücken -->
   <xsl:template match="type"/>
</xsl:stylesheet>

Die Ergebnisdatei

Nun, da das Stylesheet fertig ist, kann es mit einem einfachen Shell-Skript oder einer Stapelverarbeitungsdatei auf alle existierenden XML-Logdateien angewandt werden. Die resultierende XML-Datei ist im folgenden Beispiel zu sehen.

Beispiel: Ergebnis der Transformation

<?xml version="1.0" encoding="ISO-8859-1"?>
<?xml-stylesheet type="text/xsl" href="schemaUmwandlung.xslt"?>
<log>
  <meldung typ="FEHLER">
    <text>Eingabeparameter war null</text>
    <timestamp zeit="03:12:18" tag="15" monat="01" jahr="2000"/>
    <wo>
      <klasse>com.irgendwas.util.StringUtil</klasse>
      <methode>reverse(String)</methode>
    </wo>
  </meldung>
  <meldung typ="WARNUNG">
    <text>config-Datei kann nicht gelesen werden</text>
    <timestamp zeit="06:35:44" tag="15" monat="01" jahr="2000"/>
    <wo>
      <klasse>com.irgendwas.servlet.MainServlet</klasse>
      <methode>init( )</methode>
    </wo>
  </meldung>
  <meldung typ="FEHLER">
    <text>negative Dauer ist nicht erlaubt</text>
    <timestamp zeit="10:01:49" tag="17" monat="01" jahr="2000"/>
    <wo>
      <klasse>com.irgendwas.util.DateUtil</klasse>
      <methode>getWoche(int)</methode>
    </wo>
  </meldung>
</log>

   

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