Validieren mit Stylesheets (Ein Tutorium)

Man kann XSLT-Stylesheets dazu benutzen, ein Dokument formal-inhaltlich zu validieren, zum Beispiel eine nach den Regeln des Funktionsdesigns gestaltete Bedienungsanleitung gegen ein abstraktes Model der beschriebenen Maschine validieren. Stylesheets als Mittel der Validierung eröffnen Möglichkeiten, die eine DTD nicht bietet. Mit einer DTD kann man eine Grammatik aufstellen, die festlegt, welche Elemente in einem XML-Dokument in welchen anderen vorkommen dürfen. Auf diese Weise kann man aber keine Strukturen definieren und überprüfen, bei denen das Vorhandensein eines Elements von Strukturen in einem anderen Teil eines Dokuments oder von den Werten von Attributen abhängt. Die Einhaltung derartiger Bedingungen kann man aber nach dem Vorbild des [Ext. Link]Schematrons relativ einfach mit XSL Transformationen überprüfen.

Dokumente über Pumpen

Das Beispiel, das ich hier benutze, ist eine im [Ext. Link]ABZ 5-7/1999 von [Ext. Link]Tanner Dokuments beschriebene Pumpe, bei der darauf geachtet werden muss, dass sich vor dem Einschalten keine Flüssigkeit mehr im einem Behälter des Geräts befindet. Dort illustriert diese Pumpe einige Konzepte des Funktionsdesigns, vor allem die Notwendigkeit, die Elemente einer Bedienungsanleitung nach ihrer Funktion zu gliedern und systematisch anzuordnen. Für jede Handlungssequenz muss das damit verfolgte Ziel genannt werden, die möglichen Gefahren und Vorbedingungen müssen klar benannt werden und erst dann die einzelnen Handlungsschritte mit ihren Auswirkungen beschrieben werden.

Also nicht:

Stellen Sie den Schalter A auf Position 1. Die grüne Kontrollampe leuchtet auf. Dabei sollten sie darauf achten, dass sich keine Flüssigkeit im Behälter befindet. Damit ist die Pumpe betriebsbereit.
Sondern klar strukturiert:
Vorbereiten der Pumpe
Sachschäden durch überlaufende Füssigkeit!
Behälter vor dem Einschalten leeren.
 Schalter A auf Position 1 stellen.
 Grüne Kontrollampe leuchtet auf.
 Pumpe ist betriebsbereit.

Dass diese Vorgaben eingehalten werden und zum Beispiel alle Warnhinweise vor der ersten Handlungsaufforderung stehen, kann man dadurch sicher stellen, dass man die Bedienungsanleitung der Pumpe als ein SGML- oder XML-Dokument erstellt und die richtige Reihenfolge in der benutzten DTD festschreibt. Die Definition für die Beschreibung einer Handlungssequenz könnte dann zum Beispiel so aussehen (bei den hier nicht deklarierten Elementen kann man o. B. d. A. annehmen, dass ihre Definition eine Variante des Musters "fortlaufender Text" ist):

<!ELEMENT Handlung        (Ziel, Warnhinweis*,
                          (Aufforderung, Zwischenzustand?)+,
                          Endzustand)>
Innerhalb einer Sequenz muss immer zuerst das Ziel benannt werden, dann kommen die Warnhinweise, sofern sie vorhanden sind. Darauf folgt eine Liste von konkreten Handlungsaufforderungen, gegebenenfalls mit der Beschreibung der damit erreichten Zwischenzustände, und schließlich die Beschreibung des mit der Handlung erreichten Endzustands:
<Handlung>
  <Ziel>Vorbereiten der Pumpe</Ziel>
  <Warnhinweis>Sachschäden durch überlaufende Flüssigkeit!
  Behälter vor dem Einschalten leeren.</Warnhinweis>
  <Aufforderung>Schalter A auf Position 1 stellen.</Aufforderung>
  <Zwischenzustand>Die grüne Kontrolllampe leuchtet auf.</Zwischenzustand>
  <Endzustand>Die Pumpe ist betriebsbereit.</Endzustand>
</Handlung>

So kann man aber nicht überprüfen, ob die Warnung tatsächlich zu der beschriebenen Handlungssequenz passt, und auch nicht, ob nicht vielleicht ein Warnhinweis vergessen worden ist.

Ein abstraktes Modell der Pumpe

Um mehr als nur die korrete Struktur der Anleitung zu überprüfen, muss man sie gegen eine abstrakte Beschreibung der Pumpe validieren. Das bedeutet natürlich zunächst einmal Mehrarbeit: Man muss das abstrakte Modell entwickeln, und man muss in der Bedienungsanleitung Verweise auf die Elemente dieses Modells erfassen.

Eine Möglichkeit ist es, die zu beschreibende Maschine als einen endlichen Automaten zu modelieren. Das Maschinenmodell als XML-Datei besteht aus einer Reihe von eindeutig gekennzeichneten Zuständen:

<!ELEMENT Maschinenmodell (Zustand+)>
<!ELEMENT Zustand         (Übergang+)>
<!ATTLIST Zustand
   id     ID              #REQUIRED
>
Zu jedem Zustand gehört dabei eine Liste von möglichen Übergängen in andere Zustände, die durch die vorzunehmende Operation gekennzeichnet werden:
<!ELEMENT Übergang        (Warnung?)>
<!ATTLIST Übergang
   oper   NMTOKEN         #REQUIRED
   ziel   IDREF           #REQUIRED
>
Wenn die Maschine durch diese Operation in einen gültigen Betriebszustand übergeht, kann man ihn mit einem Attribut kodieren, für dieses Beispiel ist das aber irrelevant. Hier kommte es nur darauf an, dass Übergänge mit Warnungen verbunden sein können, auf deren Vorhandensein das Stylesheet testen kann.

Eine minimales Modell der Pumpe aus dem Beispiel hat drei Zustände:

  • Aus: Die Pumpe ist ausgeschaltet und leer.
  • Ein: Die Pumpe ist eingeschaltet und pumpt.
  • Rest: Die Pumpe ist ausgeschaltet, aber es befindet sich möglicherweise noch ein Rest Flüssigkeit im Behälter.
Außerdem gibt es drei mögliche Operationen:
  • A1: Der Schalter A wird in Position 1 gestellt
  • A0: Der Schalter A wird in Position 0 gestellt
  • L: Der Behälter wird entleert.
In jedem Zustand gibt es in diesem Modell eine sinnvolle Operation. Außerdem gibt es die mögliche Fehlbedienung, die Pumpe mit der Operation A1 einzuschalten, wenn sie sich im Zustand Rest befindet (Datei pumpenmodell.xml):
<?xml version="1.0" encoding="iso-8859-1"?>
<Maschinenmodell>
  <Zustand id="Aus">
    <Übergang oper="A1" ziel="Ein"></Übergang>
  </Zustand>
  <Zustand id="Ein">
    <Übergang oper="A0" ziel="Rest"></Übergang>
  </Zustand>
  <Zustand id="Rest">
    <Übergang oper="L" ziel="Aus"></Übergang>
    <Übergang oper="A1">
      <Warnung>Schäden durch Flüssigkeit im Behälter</Warnung>
    </Übergang>
  </Zustand>
</Maschinenmodell>

Bedienungsanleitung und Maschinenmodell

Um nun die Bedienunganleitung gegen das Maschinenmodell validieren zu können, muss man erst einmal ein Verbindung zwischen beiden herstellen. Zum Beispiel kann man das Modell als eine externe Entität in die Bedienungsanleitung integrieren (Datei pumpe.xml):

<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE anleitung SYSTEM "anleitung.dtd" [
  <!ENTITY maschinenmodell SYSTEM "pumpenmodell.xml">
]>
<Anleitung>
  &maschinenmodell;
  <Text>
  [...]
  </Text>
</Anleitung>

Die Beschreibungen der einzelnen Handlungssequenzen finden dann ihren Platz in dem Text-Element der Anleitung.

Für dieses Beispiel wird eine sehr einfache Logik für die Validierung zu Grunde gelegt: Wenn die Handlungssequenz eine Aufforderung zu einer Operation enthält, für die es eine Warnung gibt, muss die Bedienungsanleitung einen Warnhinweis enthalten, der sagt, bei welchem Zustand der Maschine es mit dieser Operation Probleme geben kann.

Die wichtigste daraus folgende Änderung bei der Erfassung der Anleitung gegenüber der ursprünglichen Version ist, dass bei jeder Aufforderung auch die Operation, die durchgeführt werden soll, als ein Attribut angegeben werden muss:

<Aufforderung oper="A1">Schalter A auf Position 1 stellen.</Aufforderung>

Ebenso muss bei den Wanrhinweisen festgehalten werden, auf welchen Zustand sie sich beziehen:

<Warnhinweis zust="Rest">Sachschäden durch überlaufende Flüssigkeit!
Behälter vor dem Einschalten leeren.</Warnhinweis>

Das Stylesheet

Wenn zusätzlich zu dem Modell der Maschinen diese Angaben vorhanden sind, kann man mit einem XSLT-Stylesheet die vollständigkeit der Warnungen überprüfen. Den Ansatz, Stylesheets zu benutzen, um eine nicht als Übereinstimmung mit einer Grammatik verstandene Validierung von Dokumenten durchzuführen, hat vor allem das [Ext. Link]Schematron von Rick Jelliffe populär gemacht. Der hier verfolgte Ansatz ist stark vom Schematron inspiriert worden, allerdings ist das Schematron für die hier angestrebte Funktionalität nicht flexibel genug. Es dürfte aber nicht schwierig sein, ein Stylesheet, das nach dem hier beschriebenen Muster aufgebaut ist, mit einem Schematron Stylesheet zu kombinieren.

Der erste Schritt des Stylesheets (Datei val-anleitung.xsl) ist es, einen Schlüssel zu erzeugen, der es erlaubt, über die Bezeichnungen der mit Wanungen versehenen Operationen auf die Zustände des Modells der Maschine zuzugreifen:

<xsl:key name="Warnungen"
         match="/Anleitung/Maschinenmodell/Zustand"
         use="Übergang[Warnung]/@oper" />

Die Bearbeitung der Datei wird durch ein Template gestartet, das das Ergebnis mit etwas HTML umgibt und sonst nichts weiter macht, als für die weitere Verarbeitung einen Modus zu setzen, in dem der normale Text des Dokuments nicht ausgegeben wird:

<xsl:output method="html"/>
<xsl:template match="/">
  <dl>
    <xsl:apply-templates mode="M1"/>
  </dl>
</xsl:template>
<xsl:template match="text()" priority="-1" mode="M1"/>

Die eigentliche Logik steckt in dem Template, das in diesem Modus für die Verarbeitung der in einer Handlungssequenz enthaltenen Aufforderungen zuständig ist:

<xsl:template match="Handlung/Aufforderung" priority="4000" mode="M1">

Das Template weist zunächst drei relativ zu der aktuellen Aufforderung definierte Größen an Variablen zu und bearbeitet dann mit Hilfe des anfangs definierten Schlüssels alle Zustände aus dem Modell der Maschine, für die es Warnungen bezüglich der aktuellen Operation gibt:

  <xsl:variable name="oper" select="@oper"/>
  <xsl:variable name="handlung" select="../Ziel"/>
  <xsl:variable name="warnhinweise" select="../Warnhinweis"/>
  <xsl:for-each select="key('Warnungen',$oper)">

Die Zwischenspeicherung in Variablen ist notwendig, da innerhalb dieses xsl:for-each-Elements die Zustände des Modells und nicht mehr die Aufforderungen in der Handlungssequenz der aktuelle Kontext der Verabeitung sind.

Für jeden dieser Zustände wird dann getestet, ob es in der Anleitung einen ihm entsprechenden Warnhinweis gibt. Wenn es keinen gibt, wird eine Warnung ausgegeben, die das Ziel der aktuellen Handlungssequenz, die Operation, den problematischen Zustand und den Text der im Maschinenmodell enthaltenen Warnung ausgibt, hier wieder in etwas HTML verpackt:

    <xsl:variable name="zustand" select="@id"/>
    <xsl:if test="count($warnhinweise[@zust=$zustand]) = 0">
      <dt><xsl:apply-templates select="$handlung"/></dt>
      <dd>Operation "<xsl:value-of select="$oper"/>":
          Problem möglich mit Zustand "<xsl:value-of select="@id"/>":
      <br />
        <xsl:apply-templates
            select='Übergang[@oper=$oper]/Warnung'/>
      </dd>
    </xsl:if>
  </xsl:for-each>
  <xsl:apply-templates mode="M1"/>
</xsl:template>

Wenn alles in Ordnung ist, produziert das Stylesheet nur eine leere Liste von Typ dl. Fehlt in dem Beispiel dagegen die Warnung, den Behälter vor dem Einschalten der Pumpe zu leeren, sieht das Ergebnis so aus:

Vorbereiten der Pumpe
Operation "A1": Problem möglich mit Zustand "Rest":
Schäden durch Flüssigkeit im Behälter

Je nach Vorlieben und Funktionsumfang des XSLT-Prozessors kann man dafür natürlich auch andere Darstellungsformate wählen, die es z. B. ermöglichen können, dass der für die Erstellung der Anleitung benutzte Editor gleich an die Stelle springt, an der der Warnhinweis fehlt.

Florian Hars <florian@hars.de>, 2007-10-15 (orig: 2000-07-30)