1 / 27

Schematron

Erinnerung: Mittel zur Modellierung von Dokumenttypen

Alle sind kontextfreie, grammatik-orientierte Sprachen.

Das bedeutet:


2 / 27

Beispiele für zusätzliche Anforderungen


3 / 27

Beispiele für zusätzliche Anforderungen (II)

Konsequenz: Überprüfung dieser Bedingungen muss anwendungsspezifisch erfolgen.

Das heißt, eine Anwendung, die das zu verarbeitende Vokabular kennt, muss die Überprüfung selbst vornehmen.

4 / 27

Mit XSLT validieren

Beobachtung: Verstöße gegen Gültigkeitsregeln können als Ergebnis einer Formatierung bzw. Transformation gemeldet werden.

Beispiel: Die Summe der Werte der anteil-Attribute aller teilhaber-Elemente muss 100 ergeben.

<xsl:template match="firma">
  <xsl:if test="sum(teilhaber/@anteil) != 100">
    Die Summe der Anteile ergibt nicht 100.
  </xsl:if>
</xsl:template>

Validierung ist unabhängig von der Anwendung möglich!


5 / 27

Mit XSLT validieren (II)

Allgemeines Schema:

<xsl:template match="Kontext">
  <xsl:if test="Bedingung">
    Fehlermeldung
  </xsl:if>
  <xsl:apply-templates />
</xsl:template>
xsl:apply-templates wird benötigt, damit weitere (in weiteren eigenen Vorlagen spezifizierte) Tests für die Kindelemente ebenfalls ausgeführt werden.

Für das Beispiel:


6 / 27

Mit XSLT validieren (III)

zusätzlich:

<xsl:template match="text()" />

Die voreingestellte Vorlage für Textknoten muss überschrieben werden, damit Text aus dem Dokument nicht in die Ausgabe kopiert wird.


Ergebnis:
Ein Dokument ist fehlerfrei, wenn ein Validierungs-Stylesheet keine Ausgaben, d.h. keine Fehlermeldungen erzeugt.


7 / 27

Die Schematron-Sprache

vereinfachte Grundstruktur eines Schematron-Schemas:

- <schema>
  - <title>?
  - <phase>*
    - <active>+
  - <pattern>+
    - <rule>+
      - <extends>*
      - (<assert> | <report>)+
  - <diagnostics>?
    - <diagnostic>+
Das Wurzelelement schema enthält optional einen Titel (title), mehre phase-Elemente, anschließend ein oder mehrere Muster (pattern-Elemente) und zuletzt optional ein diagnostics-Element. Der Begriff "Muster" ist hier als "Gültigkeitsmuster" oder "Anwendungsfall" zu verstehen. Jedes Muster enthält mehrere Regeln (rule), die wiederum optional aus extends-Elementen für Basis-Regeln sowie Zusicherungen (assert) oder Meldungen (report) bestehen. Inhalt dieser letzten Elemente ist die jeweilige Fehlermeldung.

8 / 27

Das Summen-Beispiel als minimales Schematron-Schema:

<schema
    xmlns="http://www.ascc.net/xml/schematron">
  <title>Validierungsschema für Firmen</title>
  <pattern name="Anteilsverteilung">
    <rule context="Firma">
      <assert test="sum(teilhaber/@anteil) = 100">
        Die Summe aller Anteile muss 100 ergeben.
      </assert>
    </rule>
  </pattern>
</schema>

assert drückt hier eine Zusicherung (eine positive Bedingung) aus, deren Verletzung gemeldet wird.
Mit report kann man negative Bedingungen ausdrücken, deren Zutreffen gemeldet wird.

Das bedeutet letztendlich, dass assert und report zueinander entgegengesetzte Elemente sind. Man könnte vollständig auf eines der beiden verzichten und den Test jeweils geeignet negieren.
Das vorgestellte XSLT-Beispiel entspricht daher eigentlich <report test="sum(teilhaber/@anteil) != 100">

9 / 27

Ein Schematron-Schema kann in ein Validierungs-Stylesheet transformiert werden:

Das Schematron-Schema sieht so aus, wie auf der vorherigen Seite dargestellt. Im Ergebnis der Transformation entsteht ein Validierungs-Stylesheet, das im wesentlichen aus Vorlagen, wie auf Seite 4 dargestellt, besteht. Der Schematron-Präprozessor ist das "Programm" (die Transformations-Datei), das die Regeln (Vorlagen) für die Umwandlung des Schematron-Schemas in das Validierungs-Stylesheet enthält. Dieses kann von der Schematron-Homepage geladen werden.
XML-Dokument steht schließlich für das zu überprüfende Dokument, also beispielsweise für das mit den Firmen-Daten.

10 / 27

Ausdrücken von Bedingungen

Formulierung von Zusicherungen:

<assert test="Zusicherung">
   Text beim Feststellen einer Verletzung
</assert>

Zusicherung ist eine positive Bedingung.


Formulierung von Fehlerzuständen:

<report test="Fehlerzustand">
   Text beim Zutreffen des Zustandes
</report>

Fehlerzustand ist eine negative Bedingung.


11 / 27

Fehlerdiagnose

Erläuterung zu den Fehlermeldungen:
Hinzufügen eines diagnostics-Attributes

<assert test="..." diagnostics="Namen">
<report test="..." diagnostics="Namen">

Diagnose-Meldungen werden dann separat unter diagnostics aufgeführt:

<diagnostics>
  <diagnostic id="Name">
    Diagnose-Meldung
  </diagnostic>
  <!-- weitere Diagnosen -->
  ...
</diagnostics>
Der Text direkt unter assert oder report dient nur als kurze Fehlermeldung. Ausführlichere Fehlerbeschreibungen werden davon getrennt aufgeschrieben. Sie können dann beispielsweise (abhängig vom Schematron-Werkzeug) nur bei Bedarf angezeigt werden.
Im diagnostics-Attribut von assert oder report können mehrere Diagnose-Namen angegeben werden, die dann alle benutzt werden.

12 / 27

Spezielle Elemente innerhalb einer Fehlermeldung

Innerhalb von assert- oder report-Meldungen kann das leere Element name verwendet werden.
Bedeutung: der Name des Kontextknotens
(d.h. <xsl:value-of select="name(.)" />)

Innerhalb der Diagnose-Texte kann das leere Element value-of mit einem Attribut select verwendet werden.
Bedeutung: xsl:value-of

<rule context="html:*">
  <report test="@style">
    Anstelle von style sollte in <name />
    ein class-Attribut verwendet werden.
  </report>
</rule>
Diese Regel überprüft alle Elemente aus dem HTML-Namensraum, ob sie ein style-Attribut besitzen, und meldet dies gegebenenfalls.

13 / 27

Regeln

Zusammenfassung der Tests innerhalb einer Regel:

<rule context="XSLT-Pattern">
   assert- oder report-Elemente
</rule>

Das context-Attribut gibt an, für welche Knoten die Bedingungen überprüft werden sollen.

<rule context="zubereitung/zutat">
  <assert test="@ref">
    Jede Zutat in der Anleitung muss einen
    Verweis auf die Zutatenliste enthalten.
  </assert>
</rule>

14 / 27

Regeln können als abstrakt gekennzeichnet sein, d.h. sie dienen als Vorlage für andere konkrete Regeln:

<rule abstract="true" id="Name">
   assert- oder report-Elemente
</rule>

<rule context="Kontext">
  <extends rule="Name" />
  ...
</rule>
Abstrakte Regeln dürfen kein context-Attribut besitzen.
<rule abstract="true" id="bezeichner">
  <assert test="@id">
    Das Element <name /> muss ein id-Attribut
    besitzen.
  </assert>
</rule>
Diese Regel ist abstrakt und kann für ausgewählte Elemente (d.h. konkrete Regeln) unter dem Namen bezeichner wiederverwendet werden.

15 / 27

Gültigkeitsmuster

Zusammenfassung mehrerer Regeln:

<pattern name="Bezeichnung">
   rule-Elemente
<pattern>

Ein pattern-Element fasst mehrere Regeln zusammen, die zu einem Gültigkeitstest gehören. Zum Beispiel:

XSLT-Hintergrund:
Das zu überprüfende XML-Dokument wird für jedes Gültigkeitsmuster genau einmal durchlaufen.

Das bedeutet auch, dass aus konkurrierenden Regeln innerhalb desselben pattern-Elements nur eine zum Zuge kommt, nämlich die mit der höchsten Priorität. Das liegt daran, dass für jedes rule-Element ein xsl:template-Element im Validierungs-Stylesheet generiert wird.

16 / 27

Phasen

Über Phasen kann man festlegen, welche Gültigkeitsmuster tatsächlich überprüft werden sollen.

<phase id="Phasen-Name">
  <active pattern="Muster-ID" />
  <!-- weitere in dieser Phase aktive Muster -->
  ...
</phase>

Jedes Muster muss dann zusätzlich ein id-Attribut besitzen, auf das Muster-ID verweist.

Standardmäßig sind alle Muster aktiv. Alternativ kann im schema-Element auch eine Standard-Phase angegeben werden:

<schema defaultPhase="Phasen-Name" ... >

17 / 27

Beispiel: Anforderungen an das Kochbuch


18 / 27

Zunächst: Aufteilung in Gültigkeitsmuster

  1. Zutatenverweise
    alle Regeln, die sich mit zutat-Elementen und deren Attributen beschäftigen
  2. Autorennamen
    die Anforderung an die Nennung der Autoren
  3. Textlängen
    die Vorschläge zu Anleitungs- und Titellänge

Muster 1 enthält strenge Anforderungen, Muster 2 und 3 können als zusätzliche Hinweise verstanden werden.


Festlegung von Phasen:

Eine Phase für alle drei Muster und eine nur für Muster 1


19 / 27
<schema xmlns="http://www.ascc.net/xml/schematron"
        defaultPhase="Struktur">
  <title>Validierungsschema für Rezepte</title>

  <phase id="alles">
    <active pattern="verweis" />
    <active pattern="autor" />
    <active pattern="text" />
  </phase>

  <phase id="Struktur">
    <active pattern="verweis" />
  </phase>
  
  <pattern name="Zutatenverweise" id="verweis">
    ...
  </pattern>
      
  <pattern name="Autorennamen" id="autor">
    ...
  </pattern>
      
  <pattern name="Textlängen" id="text">
    ...
  </pattern>
</schema>

20 / 27

Zu den einzelnen Regeln:

Bei den Zutatenverweisen gibt es zwei Kontexte: Zutaten in der Zutatenliste und Zutaten in der Zubereitungsanleitung.

<rule context="zutaten/zutat">
  <report test="@ref">
    Das Attribut ref ist für Zutaten in der Zutatenliste nicht
    erlaubt.
  </report>
  <assert test="not(@id) or
                ../../zubereitung/zutat[@ref = current()/@id]">
    Auf die id einer Zutat in der Zutatenliste sollte in der
    Zubereitungsanleitung auch verwiesen werden.
  </assert>
</rule>
Diese zweite Testbedingung kann man sich folgendermaßen überlegen. Die zu testende Bedingung lautet eigentlich: wenn eine Zutat in der Zutatenliste ein id-Attribut besitzt, so muss es auch eine entsprechende Zutat in der Zubereitungsanleitung geben. Oder als logische Formel: "aus A folgt B". Diese Formel ist äquivalent zu "nicht A oder B", und genau das steht als Test oben im assert-Element.

21 / 27

Anforderungen an Zutaten in der Zubereitungsanleitung:

<rule context="zubereitung/zutat">
  <assert test="@ref">
    Jede Zutat in der Zubereitungsanleitung muss ein ref-Attribut
    besitzen.
  </assert>
  <report test="@id">
    Das Attribut id ist für Zutaten in der Zubereitungsanleitung
    nicht erlaubt.
  </report>
  <report test="@menge">
    Das Attribut menge ist für Zutaten in der Zubereitungsanleitung
    nicht erlaubt.
  </report>
  ...
</rule>

22 / 27

Anforderungen an Zutaten in der Zubereitungsanleitung (II)

Noch in der gleichen Regel:

<rule context="zubereitung/zutat">
  ...
  <assert test="not(@ref) or
                ../../zutaten/zutat[@id=current()/@ref]">
    Das Attribut ref muss auf eine der aufgezählten Zutaten verweisen.
  </assert>
</rule>
Würde man not(@ref) weglassen, würde man die Meldung auch erhalten, wenn das ref-Attribut völlig fehlt. Das ist zwar auch nicht erlaubt, wird aber bereits durch einen anderen Test überprüft. Auf diese Weise wird die ansonsten etwas irreführende Meldung verhindert.

23 / 27

Zum Muster "Autorennamen":

Zwei Varianten:

<pattern name="Autorennamen" id="autor">
  <rule context="idee">
    <assert test="count(../idee[@name = current()/@name]) = 1">
      Ein Autorenname darf nur einmal pro Rezept angegeben werden.
    </assert>
  </rule>
</pattern>
oder alternativ
<pattern name="Autorennamen" id="autor">
  <rule context="idee">
    <report test="preceding-sibling::idee[@name = current()/@name]">
      Ein Autorenname darf nur einmal pro Rezept angegeben werden.
    </report>
  </rule>
</pattern>
Welche Variante ist besser? Aus der Formulierung der Bedingung heraus ist die erste vielleicht intuitiver. Die zweite drückt aus: "Ein Autor darf kein zweites Mal genannt werden".
Die zweite Variante hat damit zur Folge, dass nur das zweite Auftreten als Fehler gemeldet wird. In der ersten Variante würde auf alle Vorkommen desselben Namens mit dieser Fehlermeldung reagiert werden.

24 / 27

Zum Muster "Textlängen":

<pattern name="Textlängen" id="text">
  <rule context="zubereitung">
    <report test="string-length(.) &lt; 100">
      Das Rezept ist verdächtig kurz.
    </report>
  </rule>
  <rule context="rezept/titel">
    <report test="string-length(.) &gt; 40">
      Der Titel ist zu lang.
    </report>
  </rule>
</pattern>

25 / 27

Einfügen von Diagnosemeldungen
Uns interessiert jeweils die konkrete Länge.

Zusätzlich: diagnostics-Attribut

<pattern name="Textlängen" id="text">
  ...
    <report test="string-length(.) &lt; 100" diagnostics="länge">
    ...
    <report test="string-length(.) &gt; 40" diagnostics="länge">
    ...
</pattern>

Anschließend separate Formulierung der Diagnose-Meldung:

<diagnostics>
  <diagnostic id="länge">
    Die aktuelle Länge ist <value-of select="string-length(.)" />.
  </diagnostic>
</diagnostics>

26 / 27

Benutzung der Schematron-Werkzeuge

Alle Ressourcen rund um Schematron (Implementationen, Spezifikationen, Tutorials) findet man unter der Adresse

http://www.ascc.net/xml/schematron
Diese Adresse wurde ebenfalls als Namensraum für Schematron-Elemente gewählt. Das heißt, dort befindet sich eine RDDL-Beschreibung der Ressourcen für Schematron.

Minimale textbasierte Implementierung:
Schematron-Basic

Zweistufiger Prozess (siehe auch Grafik auf Seite 9):

saxon rezepte.sch schematron-basic.xsl > val.xsl
saxon rezepte.xml val.xsl
Die erste Zeile generiert das Validierungs-Stylesheet (val.xsl) aus der Schematron-Datei (rezepte.sch), die zweite Zeile benutzt dieses Stylesheet dann zur Validierung des XML-Dokuments (rezepte.xml).

Problem dabei: keine Informationen über den Ort der Fehlerquelle (keine Zeilennummern)


27 / 27

Intelligentere Lösung: Verweise auf die XML-Quelle
Schematron-Report

Die Verweisziele werden mit Hilfe der XSLT-Funktion generate-id erzeugt.
saxon rezepte.sch schematron-report.xsl phase=alles > val.xsl
saxon rezepte.xml val.xsl > schematron-errors.html
saxon rezepte.xml verbid.xsl > schematron-out.html
Die erste Zeile erzeugt wieder das Validierungs-Stylesheet, wobei hier anstelle der defaultPhase die Phase mit dem Namen alles aktiv sein soll.
Die Ausgabe der Validierung wird nun in eine Datei umgeleitet, da gefundene Fehler als HTML-Datei ausgegeben werden (zweiter Aufruf). Die dritte Zeile erzeugt schließlich das HTML-Abbild von rezepte.xml. Alle benötigten Dateien (schematron-report.xsl, verbid.xsl, ...) findet man über die Schematron-Homepage.

Ergebnis:
Eingabedateien: rezepte2.xml, rezepte.sch
Frame für die Ausgabe: schematron-frame.html