Archiv für Kategorie Schematron

Benutzerdefinierte Funktionen und externe Funktionsbibliotheken in Schematron

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>

OxygenXML-Einstellungsdialog für SchematronBei 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.

1 Kommentar

xsl:key in Schematron verwenden

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

Keine Kommentare

Schematron: Wie werden assert/report, rule, pattern und phase verwendet?

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.

Auswahldialog für Phasen in OxygenXMLDie 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)

»crosslinked«

Keine Kommentare

Include mehrerer Pattern in Schematron

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:

  1. Da unterhalb von <sch:schema/> kein weiteres <sch:schema/> eingebunden werden darf, kann die eingebundene Datei nicht alleinstehend zum Validieren benutzt werden.
  2. 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:

  1. Externe Entity-Dateien sind konform zum XML- und Schematron-Standard.
  2. Die einbindenden Dateien lassen sich zusammen mit den eingebundenen Dateien validieren.
  3. 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.

Keine Kommentare