Archiv für Kategorie Schematron
Benutzerdefinierte Funktionen und externe Funktionsbibliotheken in Schematron
Verfasst von Stf unter Beispiele, Schematron, XML-Validierung, XSLT und XPath am 1. Mai 2012
In manchen Situationen reicht der Umfang von XPath oder auch von XPath 2.0 nicht aus, um die gewünschten Tests zu formulieren, etwa wenn rekursive Funktionsaufrufe nötig sind. In anderen Situationen möchte man Algorithmen in verschiedenen Tests wiederverwenden. In solchen Situationen helfen benutzerdefinierte Funktionen weiter. Mit XSLT 2.0 geht das recht einfach:
<schema xmlns="http://purl.oclc.org/dsdl/schematron" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" queryBinding="xslt2" > <ns prefix="my" uri="test"/> <xsl:function name="my:literal-autor" as="xs:string?"> <xsl:param name="vorname" as="xs:string?"/> <xsl:param name="nachname" as="xs:string?"/> <xsl:sequence select="concat($nachname, ', ', $vorname)"/> </xsl:function> <pattern id="p3"> <rule context="autor"> <assert test="my:literal-autor(//person[@xml:id eq current()/@ref]/vorname, //person[@xml:id eq current()/@ref]/nachname) eq .">[p3] autor muss eine gültige Kombination aus person/vorname und person/nachname sein.</assert> </rule> </pattern> </schema> |
Im äußersten schema
-Element wird der XSL-Namespace definiert und mit queryBinding="xslt2"
XSLT 2.0 als Abfragesprache festgelegt. Mit dem ns
-Element wird analog zu XSLT 2.0 der Namespace für die benutzerdefinierte Funktionen deklariert.
Anschließend folgt die Funktionsdefinition 1:1 wie in XSLT 2.0. Das Beispiel fügt Nachname und Vorname – getrennt durch ein Komma – zusammen. xsl:function
-Elemente können an beliebiger Position direkt unterhalb von schema
stehen.
Schließlich wird im assert
die so definierte Funktion verwendet. Das Beispiel testet, ob der Inhalt des autor
-Elements mit dem referenzierten person
-Element korrespondiert.
Das dazugehörige XML könnte so aussehen:
<literatur> <buecher> <buch xml:id="b1"> <autor ref="p1">Mann, Thomas</autor> <titel>Der Zauberberg</titel> <isbn>978-3-596-29433-6</isbn> <href>http://d-nb.info/942764498</href> </buch> <buch xml:id="b2"> <autor ref="p2">Mann,Klaus</autor> <titel>Mephisto</titel> <isbn>3-10-046705-1</isbn> <href>http://d nb.info/959653694</href> </buch> <buch xml:id="b3"> <autor ref="b1"></autor> <titel></titel> </buch> </buecher> <autoren> <person xml:id="p1"> <vorname>Thomas</vorname> <nachname>Mann</nachname> </person> <person xml:id="p2"> <vorname>Klaus</vorname> <nachname>Mann</nachname> </person> </autoren> </literatur> |
Bei buch xml:id="b2"
wird ein Fehler gemeldet, weil das Leerzeichen nach dem Komma fehlt, bei buch xml:id="b3"
wegen des fehlenden Inhaltes. Schema und XML habe ich in der Beispielsammlung abgelegt.
In OxygenXML muss die Verarbeitung von XSLT innerhalb von Schematron ggfs. erst aktiviert werden. Dazu muss in den Einstellungen unter XML ⇒ XML-Parser
ein Häkchen bei ISO Schematron ⇒ Fremde Elemente erlauben (allow-foreign)
gesetzt werden, vgl. Bild rechts. [Edit: Ein Hinweis darauf, dass das Häkchen fehlt, ist die Fehlermeldung »unrecognized element … from namespace http://www.w3.org/1999/XSL/Transform
«, wobei an Stelle der drei Pünktchen der Name eines XSL-Elements steht, bspw. xsl:function
oder xsl:include
]
externe Funktionsbibliotheken
Oft liegen die benötigten Funktionen bereits in einer Bibliothek vor. Beispielsweise lassen sich URLs mit misc:is-url()
aus der XSLT-SB auf Gültigkeit testen. Auch das Einbinden externen Bibliotheken geht mit Schematron recht einfach:
<schema xmlns="http://purl.oclc.org/dsdl/schematron" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" queryBinding="xslt2" > <xsl:include href="http://www.expedimentum.org/example/xslt/xslt-sb/files.xsl"/> <ns prefix="xsb" uri="http://www.expedimentum.org/XSLT/SB"/> <pattern id="p4"> <rule context="href"> <assert test="xsb:is-url(.)">[p4] href muss eine gültige URL beinhalten</assert> </rule> </pattern> </schema> |
Mit diesem Schema wird das href
-Element bei buch xml:id="b2"
bemängelt, da ein Leerzeichen kein gültiges Zeichen in einer URL ist.
Übrigens hatte ich mit <xsl:import/>
keinen Erfolg; mir fällt aber kein Beispiel ein, wo man nicht statt dessen per <xsl:include/>
ein (ggfs. angepasstes) externes Stylesheet verwenden könnte. Über Beispiele und/oder Hinweise zur Lösung würde ich mich freuen.
Auch dieses Schema habe ich in der Beispielsammlung abgelegt.
xsl:key in Schematron verwenden
Verfasst von Stf unter Schematron, XML-Validierung, XSLT und XPath am 1. Mai 2012
Schlüssel (xsl:key
) sind eine Möglichkeit, um Transformationen über größere Dokumente mit vielen (internen oder externen) Verweisen zu beschleunigen. Im Sprachumfang von Schematron sind Daten-Schlüssel nicht enthalten, allerdings ist es sehr einfach, xsl:key
zu verwenden. Ein einfaches Beispiel: in einer Literaturliste verweisen die autor
-Elemente über das ref
-Attribut auf ein korrespondierendes person
-Element aus einer Liste:
<literatur> <buecher> <buch xml:id="b1"> <autor ref="p1">Mann, Thomas</autor> <titel>Der Zauberberg</titel> <isbn>978-3-596-29433-6</isbn> <href>http://d-nb.info/942764498</href> </buch> <buch xml:id="b2"> <autor ref="p2">Mann,Klaus</autor> <titel>Mephisto</titel> <isbn>3-10-046705-1</isbn> <href>http://d nb.info/959653694</href> </buch> <buch xml:id="b3"> <autor ref="b1"></autor> <titel></titel> </buch> </buecher> <autoren> <person xml:id="p1"> <vorname>Thomas</vorname> <nachname>Mann</nachname> </person> <person xml:id="p2"> <vorname>Klaus</vorname> <nachname>Mann</nachname> </person> </autoren> </literatur> |
Natürlich lässt sich die Referenz problemlos über XPath-Lokalisierungsschritte wie //person[@xml:id eq current()/@ref]
prüfen, aber eleganter und vermutlich schneller geht das mit Schlüsseln. Um xsl:key
in Schematron zu verwenden, muss das queryBinding
im äußersten schema
-Element auf xslt
(das ist ohnehin der Standardwert) oder xslt2
gesetzt und der XSL-Namespace deklariert werden, danach können xsl:key
-Elemente und die key()
-Funktion wie in XSLT verwendet werden:
<schema xmlns="http://purl.oclc.org/dsdl/schematron" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" queryBinding="xslt" > <xsl:key name="myKey" match="person" use="@xml:id"/> <pattern id="p3"> <rule context="autor"> <assert test="key('myKey', @ref)">[p3] autor/@ref muss auf ein person/@xml:id verweisen</assert> </rule> </pattern> </schema> |
Das assert
überprüft, ob jedes autor
-Element ein ref
-Attribut hat, und ob dieses ref
-Attribut auf ein person
-Element in der Autorenliste verweist. Entsprechend wird im obigen XML bei buch xml:id="b3"
die Referenz auf b1
als Fehler gemeldet.
xsl:key
-Elemente sollen im Schema vor dem ersten pattern
-Element stehen. Leider ist der Schematron-Standard diesbezüglich (und wie so oft) nicht sehr präzise; bei einem kurzen Test mit OxygenXML hat aber auch ein xsl:key
nach dem pattern
funktioniert.
XML-Beispiel und Schematron habe ich in der Beispielsammlung abgelegt.
Weblink
- Dave Pawson, Roger Costello, Florent Georges: ISO Schematron tutorial. Using keys for assertions (englisch)
Schematron: Wie werden assert/report, rule, pattern und phase verwendet?
Verfasst von Stf unter Grundlagen, Schematron, XML-Validierung am 30. April 2012
Schematron bietet eine differenzierte Logik, um Tests zu organisieren. Dafür werden die hierarchisch organisierten Elemente pattern
, rule
sowie assert
bzw. report
verwendet. Mit phase
gibt es eine Möglichkeit, einzelne oder mehrere pattern
und damit nur Teile des Schemas zur Validierung zu verwenden.
Die eigentlichen Tests werden mit assert
und report
auf der untersten Hierarchiestufe definiert. report
gibt einen Fehler aus, wenn der Ausdruck im test
-Attribute wahr
wird, assert
hingegen, wenn der Ausdruck nicht erfüllt ist. Außer diesem Unterschied funktionieren beide Elemente exakt gleich; dass es beide gibt, macht Schemata übersichtlicher.
assert
und report
stehen innerhalb von rule
. Dieses Element dient hauptsächlich dazu, den Kontext für die enthaltenen assert
und report
zu bestimmen. Achtung! Stehen mehrere rule
mit identischem Kontext innerhalb eines pattern
, wird nur die erste rule
berücksichtigt, weitere rule
werden ignoriert. Meist wird man statt mehrerer rule
mit identischem Kontext eine rule
mit mehreren assert
bzw. report
verwenden können, falls nicht, müssen getrennte pattern
mit diesen rule
geschrieben werden.
pattern
fassen eine oder mehrere rule
zusammen. Außerdem dienen sie zur Entkopplung mehrerer rule
mit identischem Kontext (s.o.). Eine besondere Bedeutung erlangen sie im Zusammenhang mit phase
. Innerhalb von phase
-Elementen werden einzelne pattern
referenziert und damit verschiedene Validierungsumfänge definiert.
An Hand eines einfachen Beispiels möchte ich das erläutern. In einem Literaturverzeichnis sind Bücher gelistet:
<literatur> <buecher> <buch xml:id="b1"> <autor ref="p1">Mann, Thomas</autor> <titel>Der Zauberberg</titel> </buch> <buch xml:id="b3"> <autor ref="b1"></autor> <titel></titel> </buch> </buecher> </literatur> |
autor
– und titel
-Elemente sollen nicht leer sein. Das entsprechende Schema sieht so aus:
<schema xmlns="http://purl.oclc.org/dsdl/schematron" > <pattern id="p1"> <rule context="buch"> <assert test="normalize-space(autor)">[p1] autor darf nicht leer sein</assert> <assert test="normalize-space(titel)">[p1] titel darf nicht leer sein</assert> </rule> </pattern> <pattern id="p2"> <rule context="buch"> <assert test="normalize-space(autor)">[p2] autor darf nicht leer sein</assert> </rule> <rule context="buch"> <assert test="normalize-space(titel)">[p2] titel darf nicht leer sein</assert> </rule> </pattern> <phase id="phase_1"> <active pattern="p1"/> </phase> <phase id="phase_2"> <active pattern="p2"/> </phase> </schema> |
Schauen wir zuerst auf pattern id="p1"
. Im rule
-Element wird der Kontext auf alle buch
-Elemente gesetzt und dann mit zwei nahezu identischen assert
geprüft, ob die Elemente autor
und titel
Text enthalten (wobei Text, der ausschließlich aus Leerzeichen, Tabs u.ä. – kurz whitespace – besteht, mit normalize-space()
entfernt wird).
pattern id="p2"
demonstriert, dass bei mehreren rule
mit identischem Kontext nur das erste rule
ausgewertet wird: während bei p1
zwei Fehler gemeldet werden (autor
und titel
ohne Text bei b3
), wird von p2
nur der erste Fehler gemeldet.
Die beiden phase
-Elemete demonstrieren, wie man ein Schema logisch aufteilen kann. Per default (und ohne Eintrag im defaultPhase
-Attribut im äußersten schema
-Element) werden alle pattern
validiert. Wenn mit phase
-Elementen verschiedene Phasen definiert werden, könne diese getrennt ausgeführt werden. Beispielsweise fragt OxygenXML dann nach, welche Phase validiert werden soll, vgl. Bild rechts.
XML-Beispiel und Schematron sind in der Beispielsammlung abgelegt.
Weblink
- Dave Pawson, Roger Costello, Florent Georges: ISO Schematron tutorial. Getting Started (englisch)
Include mehrerer Pattern in Schematron
Verfasst von Stf unter Schematron, XML-Validierung am 17. Juni 2010
Schematron bringt einen recht simplen Mechanismus zum Einbinden externen Schemata mit: das Element <sch:include/>
bindet ein externes XML-Dokument ein, und zwar genau an der Stelle, wo <sch:include/>
im Code steht. Das partielles Überschreiben oder gezielte Einbinden einzelner Pattern und Rules ist nicht möglich. Dies hat zwei Folgen:
- Da unterhalb von
<sch:schema/>
kein weiteres<sch:schema/>
eingebunden werden darf, kann die eingebundene Datei nicht alleinstehend zum Validieren benutzt werden. - Da
<sch:pattern/>
direkt unterhalb von<sch:schema/>
stehen muss (es gibt in Schematron kein klammerndes Element wie etwa<define/>
in RelaxNG), können mehrere Pattern nur eingebunden werden, wenn sie in der eingebundenen Datei als Wurzelelemente direkt nebeneinander stehen. Eine solche Datei ist aber kein wohlgeformtes XML, folgerichtig verweigert Schematron den Include.
Ein Thread auf der DSDL-Mailingliste und eine Anfrage auf der XML-DEV-Mailingliste ergaben zwar, dass das Problem bekannt ist, die vorgeschlagenen Lösungen sollen aber erst in zukünftigen Versionen von Schematron festgezurrt werden, und sie funktionieren jetzt noch nicht mit <OxygenXML/>. Deshalb entschied ich mich, auf die im grundlegenden XML-Standard definierte Einbindung externer Entities zurückzugreifen. Dieser Workaround sieht so aus:
Basis-Schematron:
<!DOCTYPE schema [ <!ENTITY basic-pattern SYSTEM "basic-pattern.ent"> ]> <schema xmlns="http://purl.oclc.org/dsdl/schematron" queryBinding="xslt2"> &basic-pattern; </schema> |
eingebundene Entity-Datei (basic-pattern.ent
):
<pattern xmlns="http://purl.oclc.org/dsdl/schematron"> <!-- some rule --> </pattern> <pattern xmlns="http://purl.oclc.org/dsdl/schematron"> <!-- some rule --> </pattern> <!-- add some more pattern here --> |
Diese Datei ist kein wohlgeformtes XML, weil sie mehrere Wurzelelemente hat, sie wird aber nur eingebunden in andere Dateien verwendet – die dann wohlgeformt sind. In der Praxis wird man wohl einfach ein Schematron entwickeln und die Includes dann per Cut&Paste in die Entity-Datei verschieben, so dass die fehlende Wohlgeformtheit kein Nachteil ist.
erweitertes Schematron:
<!DOCTYPE schema [ <!ENTITY basic-pattern SYSTEM "basic-pattern.ent"> ]> <schema xmlns="http://purl.oclc.org/dsdl/schematron" queryBinding="xslt2"> &basic-pattern; <!-- add some more pattern here --> </schema> |
Dieser Workaround hat verschiedene Vorteile:
- Externe Entity-Dateien sind konform zum XML- und Schematron-Standard.
- Die einbindenden Dateien lassen sich zusammen mit den eingebundenen Dateien validieren.
- Es sind keine Änderungen an etablierten Werkzeugen und Workflows notwendig (so ist es mir mit flüchtiger Recherche nicht gelungen, <OxygenXML/> andere Schematron-Stylesheets unterzuschieben).
Ich habe die drei Beispieldateien unter http://www.expedimentum.org/example/Schematron/ abgelegt.