Sinnvoller Einsatz von Patterns
(Auszug aus "Schematron - Effiziente Business Rules für XML-Dokumente", Kapitel 5)
Zur Veranschaulichung wollen wir für unser Beispiel das zweite Pattern ersetzen:
<pattern>
<rule context="arc:tier[@fleischfresser='ja']">
<report test="parent::*/arc:tier[@fleischfresser='nein']">Es gibt Fleischfresser und Pflanzenfresser in einer Unterkunft. Die Tiere sind keine Nahrungsquelle!</report>
<report test="parent::*/arc:tier/arc:gewicht < (arc:gewicht div 2)">Noah, dieser Fleischfresser ist zu stark (schwer) für seinen Zimmergenossen. Er könnte ihn als Nahrungsquelle benutzen.</report>
</rule>
</pattern>
<pattern>
<rule context="arc:tier">
<report test="parent::*/arc:tier/arc:gewicht < (arc:gewicht div 10)">Noah, das Tier ist zu schwer für seine Zimmergenossen! Es könnte einen zertrampeln.</report>
</rule>
</pattern>
Das zweite Pattern enthält eine Regel, die für alle <tier>-Elemente überprüft, ob sie nicht bedeutend schwerer als ihre Zimmergenossen sind. Hierbei soll die Bedingung gelten, dass ein Tier maximal zehnmal so schwer sein darf wie sein leichtester Zimmergenosse.
Wie zu erkennen ist, ähnelt dieser Test dem zweiten aus der schon bekannten Regel. Die Abfrage ist dabei die gleiche, nur das Kriterium bei der Fleischfresser-Regel ist strenger: Ein Fleischfresser darf maximal doppelt so schwer sein, alle Tiere dagegen maximal zehnmal so schwer wie ihre Zimmergenossen.
Das bedeutet: Ein Tier, das den Test in der Fleischfresser-Regel besteht, besteht in jedem Fall auch den Test in der neuen Regel. Eine erneute Überprüfung der Fleischfresser anhand der zweiten Regel ist somit überflüssig und die neue Regel könnte also auch nur die Nicht-Fleischfresser überprüfen:
<pattern>
<rule context="arc:tier[@fleischfresser='ja']">
<report test="parent::*/arc:tier[@fleischfresser='nein']">Es gibt Fleischfresser und Pflanzenfresser in einer Unterkunft. Die Tiere sind keine Nahrungsquelle!</report>
<report test="parent::*/arc:tier/arc:gewicht < (arc:gewicht div 2)">Noah, dieser Fleischfresser ist zu stark (schwer) für seinen Zimmergenossen. Er könnte ihn als Nahrungsquelle benutzen.</report>
</rule>
</pattern>
<pattern>
<rule context=" arc:tier[@fleischfresser='nein']">
<report test="parent::*/arc:tier/arc:gewicht < (arc:gewicht div 10)">Noah, das Tier ist zu schwer für seine Zimmergenossen! Es könnte einen zertrampeln.</report>
</rule>
</pattern>
Diese Version beinhaltet das Risiko, dass die Instanz beispielsweise um einen weiteren Wert für das Attribut fleischfresser ergänzt werden kann. Sollen Veganer in die Instanz mit dem Wert veganer aufgenommen werden, werden diese von keiner der beiden Regeln getestet. Der entstehende Fehler ließe sich durch die Bedingung [not(@fleischfresser='ja')] an Stelle von [@fleischfresser='nein'] beheben. Allerdings gibt es auch eine elegantere und leistungsstärkere Methode, beide Regeln in einem Pattern zu gliedern:
<pattern>
<rule context="arc:tier[@fleischfresser='ja']">
<report test="parent::*/arc:tier[@fleischfresser='nein']">Es gibt Fleischfresser und Pflanzenfresser in einer Unterkunft. Die Tiere sind keine Nahrungsquelle!</report>
<report test="parent::*/arc:tier/arc:gewicht < (arc:gewicht div 2)">Noah, dieser Fleischfresser ist zu stark (schwer) für seinen Zimmergenossen. Er könnte ihn als Nahrungsquelle benutzen.</report>
</rule>
<rule context="arc:tier">
<report test="parent::*/arc:tier/arc:gewicht < (arc:gewicht div 10)">Noah, das Tier ist zu schwer für seine Zimmergenossen! Es könnte einen zertrampeln.</report>
</rule>
</pattern>
Stehen beide Regeln in einem Pattern, kann die Reihenfolge der Regeln ausgenutzt werden: Jedes <tier>-Element, das von der ersten Regel als Kontextknoten ausgewählt wurde, wird von der zweiten Regel nicht mehr überprüft. Alle anderen Tiere – also alle Tiere, die keine Fleischfresser sind – werden von der zweiten Regel überprüft. Deshalb kann hier auf weitere Bedingungen für das auszuwählende <tier>-Element verzichtet werden.
Da der Parser für jedes Pattern einmal den gesamten Knotenbaum der Instanz durchläuft und für jeden Knoten überprüfen muss, ob dieser auf eine der enthaltenen Regeln passt, sind zwei Regeln in einem Pattern wesentlich performanter. Die Pattern-Gliederung wird uns aber auch noch im Zusammenhang mit dem <phase>-Element begegnen.
Anmerkung für XSLT-Profis:
Für XSLT-Kenner wollen wir nun noch einen Blick hinter den Vorhang werfen. Eine XSLT-Implementierung setzt das Schematron-Schema mit zwei Patterns etwa folgendermaßen um:
<xsl:template match="/">
<xsl:apply-templates mode="P1"/> (1)
<xsl:apply-templates mode="P2"/> (1)
</xsl:template>
<xsl:template match="arc:tier[@fleischfresser='ja']" mode="P1"> (2)
<xsl:if test="parent::*/arc:tier[@fleischfresser='nein']">
<!-- Umsetzung der Fehlermeldung -->
</xsl:if>
<xsl:if test="parent::*/arc:tier/arc:gewicht < (arc:gewicht div 2)">
<!-- Umsetzung der Fehlermeldung -->
</xsl:if>
</xsl:template>
<xsl:template match="arc:tier[@fleischfresser='nein']" mode="P2"> (2)
<xsl:if test="parent::*/arc:tier/arc:gewicht < (arc:gewicht div 10)">
<!-- Umsetzung der Fehlermeldung -->
</xsl:if>
</xsl:template>
(1) Für jedes Pattern wird ein <xsl:apply-templates>-Befehl in einem eigenen Modus ausgeführt.
(2) Zu jeder Regel wird für den jeweiligen Modus ein separates <xsl:template> erzeugt.
Zum Vergleich folgt nun die Darstellung, wie die Implementierung die Ein-Pattern-Lösung umsetzt:
<xsl:template match="/">
<xsl:apply-templates mode="P1"/> (1)
</xsl:template>
<xsl:template match="arc:tier[@fleischfresser='ja']" priority="100" mode="P1"> (2)
<xsl:if test="parent::*/arc:tier[@fleischfresser='nein']">
<!-- Umsetzung der Fehlermeldung -->
</xsl:if>
<xsl:if test="parent::*/arc:tier/arc:gewicht < (arc:gewicht div 2)">
<!-- Umsetzung der Fehlermeldung -->
</xsl:if>
</xsl:template>
<xsl:template match="arc:tier" priority="99" mode="P1"> (2)
<xsl:if test="parent::*/arc:tier/arc:gewicht < (arc:gewicht div 10)">
<!-- Umsetzung der Fehlermeldung -->
</xsl:if>
</xsl:template>
(1) Durch das wegfallende Pattern entfällt auch der zweite <xsl:apply-templates>-Befehl.
(2) Da nun beide Regeln im gleichen Pattern stehen, sind die Templates ebenfalls im gleichen Modus. Der Konflikt, der entsteht, wenn ein Knoten auf mehrere Regeln bzw. Templates passt, wird durch das Attribut priority gelöst, das abhängig von der Reihenfolge der Regeln erzeugt wird. Hier zeigt sich, warum Knoten, die auf eine Regel passen, nicht anhand einer weiteren Regel im gleichen Pattern überprüft werden.
Da wir nun tiefe Einblicke in die Skeleton-Umsetzung gegeben haben, soll folgende Tabelle einen Überblick über die Umsetzung der Schematron-Konstrukte in XSLT-Elemente geben:
Schematron | XSLT |
<assert test="XPath">Fehlermeldung</assert> | <xsl:choose> <xsl:when test="XPath"/> <xsl:otherwise>Fehlermeldung </xsl:otherwise> </xsl:choose> |
<report test="XPath">Fehlermeldung</report> | <xsl:if test="XPath">Fehlermeldung</xsl:if> |
<rule context="XPath"> | <xsl:template match="XPath" priority="xxx" mode="Pattern-ID"> |
<pattern> | <xsl:apply-templates select ="/" mode="Pattern-ID"/> |
<< zurück | vor >> |
Tipp der data2type-Redaktion: Zum Thema Schematron bieten wir auch folgende Schulungen zur Vertiefung und professionellen Fortbildung an: |
Copyright © dpunkt.verlag GmbH 2011
Für Ihren privaten Gebrauch dürfen Sie die Online-Version ausdrucken. Ansonsten unterliegt dieses Kapitel aus dem Buch "Schematron - Effiziente Business Rules für XML-Dokumente" 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.
dpunkt.verlag GmbH, Ringstraße 19B, 69115 Heidelberg, fon 06221-14830, fax 06221-148399, hallo(at)dpunkt.de