Ein universelles XSL-Stylesheet zum Aufteilen von XML-Dokumenten

Manchmal müssen große XML-Dokumente in kleinere Portionen zerlegt werden. In der Beispielsammlung gibt es dafür ein generisches Stylesheet (xml-aufteilen.xsl). Es teilt XML-Dateien auf der zweiten Ebene (unterhalb des Wurzelementes) in gleich große Gruppen mit bspw. 20 Elementen, umschließt diese Gruppen jeweils mit dem rekonstruierten Wurzelelement und ggfs. führenden Kommentaren und Processing Instructions und gibt die Gruppen als Einzeldateien aus.

Die Implementierung ist pures XSLT 2.0 auf der Grundlage von xsl:for-each-group group-adjacent="…" und xsl:result-document ohne Besonderheiten, deshalb gehe ich hier nicht näher auf den Code ein. Mit dem Stylesheet wird die zu teilende Eingabedatei transformiert, es entstehen eine Protokolldatei sowie die geteilten Dateien. Zahlreiche Parameter erlauben die Steuerung:

Auswahl der auszugebenden Elemente

elemente-pro-datei
(xs:integer) Anzahl der Elemente pro Ausgabedatei. Dieser Wert bestimmt die Größe der Gruppen und damit auch, welche Elemente als Gruppenanfang in Frage kommen. Ist bspw. $elemente-pro-datei gleich 20, dann sind mögliche Gruppenanfänge die Elemente mit der Position 1, 21, 41 usw. Damit wird sichergestellt, dass die Ausgabedateien (mit Ausnahme der letzten) immer vollständige Gruppen enthalten.
Mit einem Wert von 1 wird je Element eine Datei ausgegeben.
erstes-element
(xs:integer) Position des ersten zu kopierenden Elementes. Ohne Angabe eines Wertes wird das erste Element der zweiten Ebene gewählt. Fällt die angegebene Position nicht auf den Anfang einer Gruppe, der sich aus dem Wert für $elemente-pro-datei ergibt, wird das nächste vorhergehende Element, das ein Gruppenanfang ist, gewählt.
letztes-element
(xs:integer) Position des letzten zu kopierenden Elementes. Ohne Angabe eines Wertes wird das letzte Element der zweiten Ebene gewählt. Fällt die angegebene Position nicht auf das Ende einer Gruppe, der sich aus dem Wert für $elemente-pro-datei ergibt, wird das nächst folgende Element, das ein Gruppenende ist, gewählt.

Pfad und Name der Ausgabe-Dateien

Der vollständige Pfad zu den Ausgabedateien setzt sich aus einem für alle Dateien gleichem Pfad zum Ausgabeordner, einem Dateinamen-Präfix, den Zählstellen sowie der Dateityp-Erweiterung zusammen. Aus einer Eingabedatei mit Namen »daten.xml« entstehen bspw. »daten_001.xml«, »daten_002.xml« usw. (bei $elemente-pro-datei = 1) bzw. »daten_001-020.xml«, »daten_021-040.xml« usw. (bei $elemente-pro-datei = 20).

ausgabeordner
(xs:string) Ordner für die Ausgabe der geteilten Dateien als URI, ohne schließenden Schrägstrich. Ohne Angabe eines Ausgabeordners werden die Dateien in den Ordner des Ausgangsdokumentes in einen Unterordner »geteilt« geschrieben.
ausgabedatei-präfix
(xs:string) Präfix für die Ausgabedateien. Ohne Angabe eines Wertes wird der Name der Ausgangsdatei (ohne Dateierweiterung) mit einem nachgestellten Unterstrich verwendet.
ausgabedatei-erweiterung
(xs:string) Dateityp-Erweiterung für die Ausgabedateien. Ohne Angabe eines Wertes wird die Dateierweiterung der Ausgangsdatei verwendet.
picture-string
(xs:string) Formatierung der Zählstellen, als Picture-String (siehe http://www.w3schools.com/xsl/func_formatnumber.asp). Wird kein Wert übergeben, werden führende Nullen entsprechend der Anzahl der Elemente im Ausgangsdokument ausgegeben.

Extras

wiederhole-initiale-nodes
(xs:boolean) Steht dieser Parameter auf true, werden nodes() vor dem ersten Element (Kommentare und/oder Processing-Instructions) in jede Ausgabe-Datei an den Anfang kopiert. Bei false werden entsprechende Knoten nur in die erste Datei kopiert.
Vor dem Wurzelelement stehen oft Lizenzangaben in Kommentaren oder Processing Instructions wie bspw. das xml-model (siehe die W3C Working Group Note Associating Schemas with XML documents). Es kann wünschenswert sein, diese Informationen zu wiederholen.
schuetze-vorhandene-dateien
(xs:boolean) Verhindert, dass vorhandene Dateien überschrieben werden.
Um die Existenz einer Datei zu prüfen, muss die Datei geparst werden. Bei vielen und/oder großen bereits vorhandenen Ausgabedokumenten kann das sehr zeitaufwändig sein. In diesen Fällen empfiehlt es sich, erhaltenswerte Dokumente zu sichern und den Parameter auf false zu setzen.

Um das Stylesheet einfach an eigene Anforderungen anpassen zu können, wurden wichtige Subroutinen als Funktionen und benannte Templates angelegt. Diese können leicht überschrieben werden, um z.B. die auszugebenden Nodes zu filtern (Funktion local:nodes-auswaehlen() oder um ein eigenes Benennungsschema (Funktion local:dateiname()) zu realisieren.

»crosslinked«

Keine Kommentare

XSLT-SB 0.2.50: Bessere Dokumentation und neue Funktionen

XSLT-SB im Google-Code-Wiki

XSLT-SB im Google-Code-Wiki

Heute habe ich Release 0.2.50 der XSLT-SB veröffentlicht. Neben einigen neuen Funktionen bringt das Release vor allem eine stark verbesserte Dokumentation im Goolge-Code-Wiki. Zu jeder Funktion und zu jedem Template gibt es jetzt eine Einzelseite mit Beschreibung, Implementierung und Verweisen auf benutzte XSLT-SB-Funktionen und -Templates. Damit ist ein sehr einfacher Zugang zum Code – auch außerhalb der kompletten Stylesheets – möglich. Hier je ein Beispiel für eine Funktion und für ein Template.

Die neuen Funktionen – xsb:sort(), xsb:replace(), xsb:integer-to-hex(), xsb:hex-to-integer(), xsb:twos-complement(), xsb:reverse-twos-complement(), xsb:fill-left(), xsb:fill-right(), xsb:escape-for-regex(), xsb:escape-for-replacement(), xsb:count-matches(), xsb:index-of-first-match() und xsb:decode-from-url() haben sich im Laufe einiger Projekte bei mir angesammelt und sind nun in die XSLT-SB gewandert; einige möchte ich im Folgenden näher beschreiben.

xsb:sort()

Diese Funktion sortiert atomic values, also Zahlen, Strings usw. Das Fehlen dieser Funktion in den XPath-Funktionen ist ein Rätsel, deshalb liefert der XSLT-Standard wohl das Gerüst der Implementierung – als Wrapper für xsl:perform-sort – gleich mit. Ich habe das Beispiel um die Möglichkeit ergänzt, die Reihenfolge (aufsteigend/absteigend, englisch ascending/descending) in Funktionsaufruf zu übergeben. Das sieht dann so aus:

<xsl:function name="xsb:sort" as="xs:anyAtomicType*" intern:solved="EmptySequenceAllowed">
	<xsl:param name="input-sequence" as="xs:anyAtomicType*"/>
	<xsl:param name="order" as="xs:string"/>
	<xsl:perform-sort select="$input-sequence">
		<xsl:sort select="." order="{$order}"/>
	</xsl:perform-sort>
</xsl:function>

Sortiert werden können nur Werte, die mit dem lt-Operator verglichen werden können, also nur Sequenzen aus Strings, Zahlen, Daten; nicht aber gemischte Sequenzen aus diesen Typen. Gemischte Sequenzen können aber auf string gecastet werden, etwa mit for $i in $sequence return string($i).

Zur bequemeren Benutzung gibt es auch eine Version mit nur einem Argument, dann ist die Reihenfolge auf aufsteigend resp. ascending festgelegt.

xsb:index-of-first-match()

Diese Funktion ermittelt die Position des ersten Auftretens eines RegEx-Patterns in einem String. Mit fn:tokenize() wird der erste Teilstring vor dem Pattern ermittelt und zu dessen Länge 1 addiert:

<xsl:function name="xsb:index-of-first-match" as="xs:integer">
	<xsl:param name="input" as="xs:string?"/>
	<xsl:param name="pattern" as="xs:string?"/>
	<xsl:param name="flags" as="xs:string?"/>
	<xsl:choose>
		<xsl:when test="normalize-space($pattern) and matches($input, $pattern, $flags)">
			<xsl:sequence select="string-length(tokenize($input, $pattern, $flags)[1]) + 1"/>
		</xsl:when>
		<xsl:otherwise>0</xsl:otherwise>
	</xsl:choose>
</xsl:function>

Auch hier gibt es wieder die »bequeme« Version ohne flags.

xsb:replace()

xsb:replace() erweitert fn:replace() um die Möglichkeit, Listen (genauer Sequenzen) als Argumente für Such-Pattern und Ersetzungszeichenfolgen zu übergeben. Das ist praktisch, wenn in einem String paarweise verschiedene Fundstellen durch korrespondierende Texte ersetzt werden sollen, also z.B. alle »Jan.« durch »Januar«, »Feb.« durch »Februar«, »Apr.« durch »April« usw. Ohne benutzerdefinierte Funktionen müssen mehrere fn:replace() geschachtelt werden, es entstehen dann Ungetüme wie:

replace(
	replace(
		replace($input,
			'Apr\.', 'April'),
		Feb\.', 'Februar'),
	'Jan\.', 'Januar')

Viel übersichtlicher ist es dann doch so:

xsb:replace($input,
	('Jan\.', 'Feb.\', 'Apr\.'),
	('Januar', 'Februar', 'April') )

Neben der Übersichtlichkeit ist ein zweiter wesentlicher Vorteil, dass mit xsb:replace() die Länge der Sequenzen resp. die Anzahl der Ersetzungspaare nicht schon beim Schreiben des Stylesheets bekannt sein müssen. Dazu folgt weiter unter ein Beispiel. Aber erst noch ein paar Worte zur Implementierung, die so aussieht:

<xsl:function name="xsb:replace" as="xs:string">
	<xsl:param name="input" as="xs:string?"/>
	<xsl:param name="pattern" as="xs:string*"/>
	<xsl:param name="replacement" as="xs:string*"/>
	<xsl:param name="flags" as="xs:string?"/>
	<xsl:choose>
		<xsl:when test="exists($pattern[1])">
			<xsl:sequence select="
				xsb:replace(
					if (boolean($pattern[1]) )
						then replace($input, $pattern[1], string($replacement[1]), $flags)
						else $input,
					$pattern[position() gt 1],
					$replacement[position() gt 1],
					$flags
				)"/>
		</xsl:when>
		<xsl:otherwise>
			<xsl:sequence select="concat('', $input)"/>
		</xsl:otherwise>
	</xsl:choose>
</xsl:function>

Es handelt sich um eine rekursive Funktionsdefinition. Die Implementierung ist nicht ganz geradlinig, weil Leerstrings und Leersquenzen sinnvoll behandelt werden müssen. Der Reihe nach:

  • exists($pattern[1]) testet, ob ein weiteres pattern-Argument vorhanden ist. Wichtig ist, dass fn:exists() (im Unterschied zu fn:boolean()) den Leerstring zu true() evaluiert. Ist kein weiteres pattern-Argument vorhanden, sind die Ersetzungen abgeschlossen, und das Ergebnis der Funktion wird im xsl:otherwise-Zweig zurückgegeben. (Das Ergänzen eines Leerstrings dort stellt sicher, dass keine Leersequenz, sondern mindestens ein Leerstring ausgegeben wird.)
  • Wenn ein weiteres pattern-Argument (das auch ein Leerstring sein kann) vorhanden ist, wird xsb:replace() rekursiv aufgerufen
    • Für das zu übergebende input-Argument wird mit boolean($pattern[1]) geprüft, ob das aktuelle Such-Pattern Zeichen enthält, und nur in diesem Fall wird ein fn:replace() mit den aktuellen pattern und replacement aufgerufen. Im Fall eines Leerstrings wird input unverändert weitergereicht. Diese Akrobatik ist notwendig, weil der Leerstring kein gültiger Suchstring in fn:replace() ist, ich mir aber an dieser Stelle etwas Fehlertoleranz gewünscht habe.
    • Die in den pattern– und replacement-Sequenzen auf den jeweils ersten Wert folgenden Werte werden als neue pattern und replacement übergeben. Wenn keine weiteren Argumente vorhanden sind, wird halt eine Leersequenz weitergereicht, was im Fall von pattern im nächsten Durchlauf zum Abbruch der Rekursion und zur Ausgabe der Ergebnisses führt.
    • flags wird unverändert durchgereicht.

Leerstrings in der pattern-Sequenz werden als »nichts suchen« interpretiert und samt dem zugehörigen replacement übersprungen.

Sind mehr pattern-Werte als replacement-Werte vorhanden, werden die Fundstellen der »überzähligen« pattern-Werte gelöscht: xsb:replace('Affe Bär Elefant', ('Affe', 'Elefant') , ('monkey') ) ergibt »monkey Bär «. Dieses Verhalten entspricht dem des guten alten fn:translate().

xsb:replace kann man wunderbar zum Suchen-und-Ersetzen an Hand einer Ersetzungstabelle verwenden. Im folgenden Beispiel wird die Ersetzungstabelle als externes Dokument verwaltet, dass ggfs. unabhängig vom Stylesheet bearbeitet werden kann.

Ersetzungstabelle (search-and-replace_list.xml):

<root>
	<pair>
		<pattern>Affe</pattern>
		<replacement>monkey</replacement>
	</pair>
	<pair>
		<pattern>Wolf</pattern>
		<replacement>wolf</replacement>
	</pair>
</root>

Stylesheet:

<xsl:stylesheet
	xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
	xmlns:xs="http://www.w3.org/2001/XMLSchema"
	xmlns:xsb="http://www.expedimentum.org/XSLT/SB"
	exclude-result-prefixes="xs xsb"
	version="2.0">
	<xsl:import href="strings.xsl"/>
	<xsl:param name="search-and-replace-list">search-and-replace_list.xml</xsl:param>
	<xsl:template match="p">
		<xsl:copy>
			<xsl:value-of select="xsb:replace(
				.,
				doc($search-and-replace-list)//pair/string(pattern),
				doc($search-and-replace-list)//pair/string(replacement)
			)"/>
		</xsl:copy>
	</xsl:template>
	<xsl:template match="@*|node()">
		<xsl:copy>
			<xsl:apply-templates select="@*, node()"/>
		</xsl:copy>
	</xsl:template>
</xsl:stylesheet>

Beispieldokument:

<root>
	<p>Affe</p>
	<p>Wolf</p>
	<p>Zebra</p>
</root>

Transformationsergebnis:

<root>
	<p>monkey</p>
	<p>wolf</p>
	<p>Zebra</p>
</root>

Achtung! Leersequenzen innerhalb der pattern-Sequenz (bzw. fehlende pattern-Elemente in der Ersetzungstabelle) führen dazu, dass die folgenden Werte innerhalb der pattern-Sequenz nach links rücken: xsb:replace('Affe Wolf Zebra', ('Affe', (), 'Zebra'), ('monkey', 'wolf', 'zebra') ) ergibt – ebenso wie xsb:replace('Affe Wolf Zebra', ('Affe', 'Zebra'), ('monkey', 'wolf', 'zebra') ) – »monkey Wolf wolf«. Das gilt analog auch für die replacement-Sequenz bzw. replacement-Elemente der Ersetzungstabelle. Diesem Problem kann man mit einem rigiden Schema samt Validierung oder durch geschickte Konstruktion der pattern– und replacement-Sequenzen begegnen: im obigen Stylesheet wurde nicht das naheliegende doc($search-and-replace-list)//pattern verwendet, sondern statt dessen doc($search-and-replace-list)//pair/string(pattern), womit bei fehlendem pattern-Element mit fn:string() ein Leerstring erzeugt wird.

Exkurs: Im Gegensatz zu fn:string() evaluiert xs:string() die Leersequenz zu einer Leersequenz. Ich bin mir sicher, dass es für diesen Unterschied eine sachliche Begründung gibt, aber solche Inkonsistenzen machen das Programmieren nicht nur für Einsteiger unnötig kompliziert.

Durch die Verwendung von fn:replace() werden die Suchstrings als reguläre Ausdrücke interpretiert. Entsprechend wird im Beispiel oben der Punkt mit dem Backslash escapet. Wenn das händische Escapen nicht sinnvoll oder möglich ist, hilft die Funktion xsb:escape-for-regex(). Analog dazu gibt es auch im Ersetzungstext Steuerzeichen, die mit xsb:escape-for-replacement() escapet werden können.

Auch von xsb:replace() gibt es die »bequeme« Version ohne flags.

xsb:escape-for-regex() und xsb:escape-for-replacement()

Die Funktion xsb:escape-for-regex() escapet in Strings Steuerzeichen für reguläre Ausdrücke mit einem Backslash (\). Damit können Strings, die Steuerzeichen enthalten, als Suchmuster in regulären Ausdrücken verwendet werden. Im Beispiel oben musste beispielsweise der Punkt (steht in regulären Ausdrücken für ein beliebiges Zeichen) in Jan. escapet werden (Jan\.), damit nicht auch »Jana« ersetzt wird. Die Implementierung ist sehr simpel:

<xsl:function name="xsb:escape-for-regex" as="xs:string">
	<xsl:param name="input" as="xs:string?"/>
	<xsl:sequence select="concat('', replace($input, '[\\*.+?\^\$()\[\]{}|]', '\\$0') )"/>
</xsl:function>

Im pattern von fn:replace() werden die »verbotenen« Zeichen gesucht. Bemerkenswert ist vielleicht das replacement: der doppelte Backslash ist ein escapeter einfacher Backslash, und $0 steht für den gesamten gematchten Teilstring. Es wird also ein Backslash ausgegeben und anschließend die Fundstelle wiederholt.

Da es also auch im Ersetzungstext von fn:replace() Steuerzeichen – »\« und »$« – geben kann, macht eine Funktione zu Escapen eben dieses Ersetzungstextes das Leben einfacher: xsb:escape-for-replacement().

Ein Beispiel für die Nutzung der beiden Funktionen ergibt sich im Zusammenhang mit der oben beschriebenen Ersetzungstabelle. Wenn man davon ausgehen kann, dass in search-and-replace_list.xml keine regulären Ausdrücke notiert werden, kann das Stylesheet zur Absicherung modifiziert werden:

<xsl:template match="p">
	<xsl:copy>
		<xsl:value-of select="xsb:replace(
			.,
			doc($search-and-replace-list)//pair/xsb:escape-for-regex(string(pattern)),
			doc($search-and-replace-list)//pair/xsb:escape-for-replacement(string(replacement))
		)"/>
	</xsl:copy>
</xsl:template>

Quelldokument, Ersetzungstabelle und Stylesheet habe ich wie üblich in der Beispielsammlung abgelegt.

Weblinks:

Keine Kommentare

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)

Keine Kommentare

xsb:random(): Zufallszahlen mit XSLT

»Der Laie staunt, der Fachmann wundert sich.« Zufallszahlen in XSLT sind soetwas wie die Quadratur des Kreises. Warum? Eine der absoluten Grundlagen funktionaler Programmiersprachen, zu denen auch XPath und XSLT gehören, ist der Verzicht auf Seiteneffekte, d.h. das Ergebnis einer Operation oder Funktion hängt ausschließlich von den übergebenen Parametern ab und nicht von anderen Programmzuständen, die beispielsweise in Variablen gespeichert und extern verändert werden könnten. Ebenso werden während der Berechnung einer Funktion oder der Ausführung eines Templates keine Programmzustände geändert. Der Vorteil dieser Restriktion ist, dass Programmzustände wesentlich leichter vorhergesagt und damit die Ausführung von Programmen gut optimiert werden können: wenn bspw. einmal log10($input) berechnet wurde, kann der Prozessor beim nächster Auftreten von log10($input) auf das letzte Ergebnis zurückgreifen, weil sich $input per funktionalem Paradigma nicht ändern darf. Daraus folgt, dass eine Funktion random(), die bei jedem Aufruf ein anderes Ergebnis liefert, ein Paradox ist.

XSLT und XPath sind (manchmal zum Verzweifeln) strikt in Bezug auf Seiteneffektfreiheit. Ein Loch im System – das sogar durch den XSLT-Standard gedeckt ist – fand aber Vladimir Nesterovsky: generate-id() muss für jeden Knoten eine andere ID liefern; das schließt im Stylesheet generierte temporäre Knoten ein. Eine Funktion, die einen temporären Knoten erzeugt und für diesen generate-id() aufruft, gibt deshalb bei jedem Aufruf ein anderes Ergebnis zurück:

<xsl:stylesheet version="2.0"
  xmlns:f="data:,f"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 
<xsl:template match="/">
  <xsl:message select="
    for $i in 1 to 8 return
      f:fun()"/>
</xsl:template>
 
<xsl:function name="f:fun" as="xs:string">
  <xsl:variable name="x">!</xsl:variable>
 
  <xsl:sequence select="generate-id($x)"/>
</xsl:function>
 
</xsl:stylesheet>

Das Ergebnis mit Saxon ist bspw. tt2 tt3 tt4 tt5 tt6 tt7 tt8 tt9.

Damit kann man nun einen Pseudozufallsszahlengenerator füttern. Ich habe mich für einen linearen Kongruenzgenerator entschieden, weil er sich recht einfach implementieren lässt:

<xsl:function name="intern:linear-congruential-generator" as="xs:integer+">
	<xsl:param name="length" as="xs:integer"/>
	<xsl:param name="vortrag" as="xs:integer+"/>
	<xsl:choose>
		<xsl:when test="$length eq 0">
			<xsl:sequence select="$vortrag[position() gt 1]"/>
		</xsl:when>
		<xsl:otherwise>
			<xsl:sequence select="intern:linear-congruential-generator($length - 1, ($vortrag, (1103515245 *$vortrag[last()] + 12345) mod 4294967296) )"/>
		</xsl:otherwise>
	</xsl:choose>
</xsl:function>

Die numerischen Konstanten werden so bei der glibc verwendet, ich habe sie einfach übernommen. Die Funktion ist rekursiv, sie fügt bei jedem Aufruf an die vortrag-Sequenz (die mit einem möglichst zufälligem Startwert – englisch seed – initialisiert wird) eine Pseudozufallszahl an. Der Startwert wird aus der Rückgabe entfernt; so kann man diese Funktion (mit einer length von 1) zum einfachen Umwandeln eines Eingabewertes benutzen. intern:linear-congruential-generator(5, 1) ergibt bspw. 1103527590 2524885223 662824084 3295386429 4182499122. Dieses Ergebnis ist reproduzierbar, d.h. jeder Aufruf mit den Parametern 5, 1 liefert genau diese Sequenz zurück.

An diesem Punkt kommt die oben besprochene Funktion zum Erzeugen veränderlicher Werte ins Spiel. Ich habe sie um eine Verarbeitung von Datum und Uhrzeit ergänzt, damit bei jedem Aufruf des Stylesheets neue Pseudozufallswerte erzeugt werden:

<xsl:function name="intern:seed" as="xs:integer">
	<xsl:param name="seed" as="xs:anyAtomicType"/>
	<xsl:variable name="integer-of-seed" as="xs:integer" select="if ($seed castable as xs:integer) then xs:integer($seed) else sum(string-to-codepoints(string($seed) ) )"/>
	<xsl:variable name="integer-of-current-date" as="xs:integer" select="xs:integer(format-dateTime(current-dateTime(), '[Y][d][H][m][s][f]') )"/>
	<xsl:variable name="temporary_node" as="text()">?</xsl:variable>
	<xsl:variable name="sequence-of-weighted-id-integers" as="xs:integer+">
		<xsl:for-each select="string-to-codepoints(generate-id($temporary_node) )">
			<xsl:sequence select="intern:power(xs:integer(10), position()) * xs:integer(.)"/>
		</xsl:for-each>
	</xsl:variable>
	<xsl:sequence select="intern:linear-congruential-generator(1, $integer-of-seed + $integer-of-current-date + xs:integer(sum($sequence-of-weighted-id-integers) ) )"/>
</xsl:function>

Disclaimer: Ich bin kein Mathematiker und kann deshalb die Güte der Zufallszahlen nicht beurteilen. Sie eignen sich sicher nicht für kryptographische Anwendungen. Auf der anderen Seite zeigt ein Plot eine recht gleichmäßige Verteilung, so dass sie für die meisten Zwecke ausreichen dürften.

Bei den ersten Tests sahen die Ergebnisse sehr gut aus, aber dann schlug die Optimierung von Saxon zu: for $i in 1 to 100 return string(xsb:random(1)) lieferte 100 Mal den selben zufälligen Wert. Die Lösung ist, die Evaluierung der Funktion zu erzwingen, indem als Argument ein möglichst veränderlicher Wert übergeben wird. Das ist hier naheliegend die Zählvariable $i: for $i in 1 to 100 return string(xsb:random($i)) liefert wieder recht zufällige Werte. Aus pädagogischen Gründen habe ich deshalb in der XSLT-SB keine xsb:random()-Funktion ohne Argument implementiert. Natürlich könnte ein sehr intelligenter Prozessor auch hier wieder optimieren, aber je schwerer man ihm das macht, umso unwahrscheinlicher ist sein Erfolg.

siehe auch

Keine Kommentare

Der einfachste REST-Webservice mit XQuery und exist-db (Erste Schritte mit XRX – 2 von X)

In einem früheren Beitrag hatte ich mich schon begeistert über die einfachen und eleganten REST-Services, die mit XRX möglich sind, geäußert. Hier nun der Beweis, der einfachste REST-Webservice wo gibt:

declare namespace request="http://exist-db.org/xquery/request";
 
<result>
	{concat('Hallo ', request:get-parameter('name', 'Welt'), '!')}
</result>

Dieser Code kann einfach in einer XQuery-Datei innerhalb von exist-db gespeichert werden, in einer betterFORM-Installation etwa unterhalb des Hauptordners betterform, so dass sie sofort im betterFORM-Dashboard erscheint. Der Service kann dann z.B. mit http://localhost:8080/betterform/rest/db/betterform/service.xquery?name=Max aufgerufen werden, zurückgegeben wird das allseits beliebte »Hallo Max!«. Zugegeben: Das Beispiel macht nicht viel, aber an der Stelle des XPath-Ausdrucks kann natürlich jeder beliebig mächtige andere Ausdruck stehen.

Für die Parameterverarbeitung wird eine proprietäre Erweiterung von exist-db benutzt; in der Praxis empfiehlt sich deshalb, diese Funktion gegen eine selbst definierte in einer ausgelagerten Modul-Bibliothek zu ersetzen, um bei einem Plattformwechsel schnell die notwendigen Anpassungen vornehmen zu können.

Keine Kommentare

XML Schema: complexType mit simpleContent

Gelegentlich steht man vor dem Problem, in XML Schema ein Element mit einem oder mehreren Attributen und einem in irgendeiner Form beschränkten simpleContent (Text, Zahlen, URIs; aber keine Elemente) zu definieren. Die Syntax von XML Schema dazu ist wenig intuitiv:

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
 
	<xs:element name="MyElement">
		<xs:complexType>
			<xs:simpleContent>
				<xs:extension base="MySimpleElementType">
					<xs:attribute name="MyAttribute" type="MySimpleAttributeType"/>
				</xs:extension>
			</xs:simpleContent>
		</xs:complexType>
	</xs:element>
 
	<xs:simpleType name="MySimpleElementType">
		<xs:restriction base="xs:string">
			<xs:enumeration value="Value_A"/>
			<xs:enumeration value="Value_B"/>
		</xs:restriction>
	</xs:simpleType>
 
	<xs:simpleType name="MySimpleAttributeType">
		<xs:restriction base="xs:string">
			<xs:enumeration value="AttributeContent_A"/>
			<xs:enumeration value="AttributeContent_B"/>
		</xs:restriction>
	</xs:simpleType>
 
</xs:schema>

Es handelt sich um einen complexType (wegen des Attributes) mit simpleContent (hier xs:string mit aufgezählten gültigen Werten). Wenig intuitiv ist, dass ein simpleType mit einem Attribut erweitert wird und so faktisch ein complexContent wird, aber trotzdem innerhalb von simpleContent steht. Der Schlüssel ist sicher die Unterscheidung von Type und Content, denn der Inhalt des Elements ist ja immer noch einfach.

Das Schema und ein Instanzdokument habe ich in der Beispielsammlung abgelegt.

Keine Kommentare

Erste Schritte mit XRX (1 von X)

XRX ist eine Technik für die Entwicklung und den Betrieb von Webanwendungen. Das Akronym steht nach einem sehr allgemeinen Verständnis für XML für die Datenspeicherung im Client, REST-Schnittstellen und XML auf dem Server. Im engeren Sinne wird XRX meist für XForms, REST, XQuery verwendet, wobei XQuery in der Regel auf eine XML-Datenbank zugreift.

Als XML-affiner Zeitgenosse war ich natürlich neugierig auf diese Technik. Meine Erfahrungen bei den ersten Schritten möchte ich hier notieren – schließlich ist aller Anfang schwer, aber nicht notwendigerweise für alle.

Ein erstes Resümee vorab

XRX ist recht komplex, und die Entwicklung wird (nach meinem jetzigen Kenntnisstand) nicht mit dem gewohnten Komfort unterstützt. Zuverlässige, d.h. kontext-sensitive Content Completition habe ich bisher noch nicht gefunden, und beim Debugging fühle ich mich schlecht unterstützt: mir fehlen Informationen zu den internen Abläufen (und Fehlern) des XForms-Prozessors. Und gleich noch eine Warnung: ohne einigermaßen belastbare Kenntnisse von XPath, Namespaces und ein bisschen Erfahrung mit XML Schema, XQuery und/oder XSLT wird der Einstieg eher schwer.

Auf der anderen Seite wird man von XRX mit eleganter, effizienter und plattformunabhängiger Anwendungsentwicklung belohnt (obwohl ich nicht über genug Erfahrung mit anderen Plattformen verfüge, um solche Einschätzungen zu treffen, wage ich mal dieses Statement). Ein REST-Webservice in drei Zeilen XQuery löst bei mir ebenso Begeisterung aus wie die »magische« (d.h. codefreie) Aktualisierung von Formularfeldern durch XForms oder der unmittelbare Zugriff auf die soeben altualisierten XML-Daten via REST. Da bin ich doch recht zuversichtlich, XRX demnächst auch produktiv einsetzen zu können.

Installation

Es gibt verschiedene vorkonfigurierte Bundles aus XForms-Prozessor und XML-Datenbank; ich habe mich aus Bequemlichkeit (die Entwickler arbeiten in Berlin, und ich kenne sie persönlich) für die betterFORM XML Suite mit eXist-db als XML-Datenbank entschieden. Für Windows gibt es eine Installer-Exe, für den Mac ein JAR. Unter Windows bemäkelte der Installer das 7er SDK, aber nach einem Neustart lief die Installation einfach durch. betterFORM hat auf seiner Seite eine detaillierte Anleitung.

Nach der Installation kann man sich im betterFORM Dashboard einen ersten Eindruck von XForms-Anwendungen verschaffen.

Tipp: Der »eXist Admin Client« (erreichbar über Download und Start von exist.jnlp rechts oben im betterFORM Dashboard) erlaubt einen ersten Einblick in die Datenbank. Besonders zum Anlegen und Löschen von Collections und Dateien leistet er gute Dienste.

OxygenXML

Zum Entwickeln braucht es natürlich eine IDE. OxygenXML bietet eine gute Integration von eXist-db, die Einrichtung ist in der F1-Hilfe und auf der OxygenXML-Seite zu eXist-db detailliert beschrieben. Ich habe dann ewig gebraucht, um die URL der REST-Schnittstelle zu finden und bin später über die Lösung gestolpert: beim Start des »eXist Admin Client« ist die korrekte URL voreingestellt und kann einfach nach OxygenXML kopiert werden. Mit den Standardparametern ist die URL xmldb:exist://localhost:8080/betterform/xmlrpc.

Login-Dialog des »eXist-db Admin Client« mit URL zur RPC-Schnittstelle zur Datenbank

Konfiguration des OxygenXML-Konnektors zu eXist-db


Tipp: Die Arbeit mit Dateien und an der Datenbank geht mit OxygenXML sehr leicht von der Hand. Man kann Dateien öffnen, bearbeiten und zurück in die Datenbank speichern, außerdem sind Dateioperationen wie Löschen, Umbenennen oder Importieren möglich. Unbedingt ausprobieren!

Auswahl des passenden Dokumententyps bei neuen DateienZweite Hürde ist die Unterstützung durch Content Completition und Validierung. Während XQuery direkt unterstützt wird, ist für XForms etwas Handarbeit notwendig. Der einfachste Weg ist, die Processing Instruction <?xml-model href="http://www.oxygenxml.com/1999/xhtml/xhtml-xforms.nvdl" schematypens="http://purl.oclc.org/dsdl/nvdl/ns/structure/1.0"?> am Anfang eines XHTML-Dokumentes einzufügen. Außerdem können neue Dokumente über Datei/Neu mit dem Dokumenentyp (unter Framework templates/XHTML) »XHTML 1.0 RNG Based + XForms 1.1« angelegt werden. Die XForms-Unterstützung ist nicht perfekt, aber sehr praktisch.

»Hello World« und Tutorials

Das Hello-World-Beispiel aus dem XForms-Wikibook lief bei mir auf Anhieb. Die nächsten Schritte – Zugriff auf eXist-db, Anzeigen, Erstellen, Löschen, Laden und Speichern von Datensätzen – habe ich mir recht zügig mit dem guten »Orbeon Forms XForms Tutorial« erarbeitet. Danach helfen der XForms-Standard beim W3C, der »XForms Feature Explorer« (erreichbar über das betterFORM Dashboard) sowie die Online-Dokumentation von eXist-db weiter. Schließlich habe ich mir manche Einsicht und Anregung aus »A Beginners Guide XRX« von Dan McCreary und Joe Wicentowski geholt.

Im Rückblick war der Einstieg dann gar nicht so schwer. Größte und unerwartete Hürde für mich war das Erforschen und Verstehen des Model-View-Controller-Konzepts von XForms und der darauf aufbauenden Sprachkonstrukte. Und auch ein paar Pattern wollten erarbeitet sein. Dazu vielleicht später mehr.

Keine Kommentare

XSLT-SB: asin(), acos(), atan(), atan2(), dynamische Typung

Mit dem Sprung auf Version 0.2.37 kann die XSLT-SB, genauer math.xsl, jetzt auch die Arkusfunktionen und dynamische Typung. In Ergänzung der knappen Release-Notes hier ein paar Anmerkungen dazu.

Arkusfunktionen

Die Gruppe der Arkusfunktion lässt sich einfach auf der Grundlage von atan() implementieren – wenn man den richtigen Ansatz gefunden hat. Meine ersten Versuche, asin() und atan() über Taylor-Reihen zu implementierten scheiterten daran, dass diese Reihen zu langsam konvergieren. Zusammen mit den in der Reihenbildung verwendeten rekursiven Funktionen (Fakultät und Potenz) war schnell das Limit der Rekursionstiefe erreicht: Saxon bricht die Verarbeitung mit Hinweis auf das Überschreiten der maximalen Rekursionstiefe ab.

Die Lösung brachte ein alternativer Algorithmus zur Berechnung von atan(), den ich bei WolframMathWorld fand (Gleichungen Nr. 44 bis 48). Mittels des Arkustangens lässt sich einfach der Arkussinus und Arkuskosinus berechnen, so dass diese Implementierung ein Kinderspiel war (siehe Wikipedia).

dynamische Typung

Zweites großes Thema der Überarbeitung von math.xsl war die dynamische Typung. Die mathematischen Operatoren und Funktionen in XPath geben ihr Ergebnis i.d.R. mit dem Typ der Argumente zurück. Zum Beispiel ist das Ergebnis von fn:round-half-to-even() mit einem Argument vom Typ xs:decimal vom Typ xs:decimal, während mit einem xs:double-Argument das Ergebnis vom Typ xs:double ist.

Die Implementierung dieses Verhaltens barg einige Schwierigkeiten, weil manche Funktionen als Ergebnis NaN, -INF oder INF zurückgeben können. Diese speziellen Werte sind zwar mit den Typen xs:float und xs:double darstellbar, nicht aber mit xs:decimal (das aber genauere Ergebnisse als xs:double liefert) und xs:integer. Das gewünschte Verhalten ist wohl sehr vom Einsatzzweck der Funktionen abhängig. Der jetzige Kompromiss ist daher, dass bei diesen Werten der Cast von NaN, -INF oder INF auf ungeeignete Typen scheitert; wer Wert auf hohe Genauigkeit legt, kann vielleicht auch die kritischen Werte umschiffen.

Genauigkeit

Laut Standard müssen XPath-Implementierungen mit 18 signifikanten Stellen rechnen. Für die XSLT-SB habe ich mittels intern:round() die Genauigkeit auf 16 Stellen begrenzt, weil damit die meisten Tests erfolgreich absolviert werden. Trotzdem wird für manche Testwerte (etwa exp(100)) nicht das richtige Ergebnis ermittelt (bei vielen Berechnungsschritten summieren sich die Fehler halt). In diesen Fällen wird im Testprotokoll (hier z.B. für Saxon EE) eine Warnung (orange hinterlegt) ausgegeben. Diese sind zu unterscheiden von den gelb hinterlegten Fällen, bei denen ein Cast der Ergebnisse auf xs:decimal (den Typ der Tests) nicht möglich ist, siehe oben. In den Tests nicht berücksichtigt sind die Fälle, für die eine Funktion nicht definiert ist, etwa asin(3).

Intern (d.h. bei den Funktionen mit intern:-Prefix) wird mit einer höhren Stellenzahl gerechnet, diese müssen aber weder signifikant noch richtig sein.

Keine Kommentare