Archiv für Kategorie Grundlagen

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

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

AltovaXML in OxygenXML einbinden

Um AltovaXML in OxygenXML nutzen zu können, muss man diesen XSLT-Prozessor als »Custom Engine« einrichten. So geht’s:

Unter Einstellungen/XML/XSLT-FO-XQuery/Custom Engines einen neuen Prozessor anlegen:

OxygenXML: einen neuen XSLT-Prozessor anlegen, Seite »Custom Engines«

OxygenXML: einen neuen XSLT-Prozessor anlegen

Als Prozessortyp ist »XSLT« vorausgewählt und richtig. Name und Beschreibung eingeben und dann in das Feld Kommandozeile »"C:\Program Files (x86)\Altova\AltovaXML2011\AltovaXML.exe" /xslt2 ${xsl} /in ${xml} /out ${out}« eintragen (Pfad zu AltovaXML bitte an lokale Gegebenheiten anpassen).

OxygenXML: einen neuen XSLT-Prozessor anlegen: Standardeinstellung für AltovaXML

OxygenXML: Standardeinstellung für AltovaXML

Speichern, fertig. In den Transformationsszenarien kann jetzt der neu angelegte Prozessor ausgewählt werden:

OxygenXML: Auswahl der XSLT-Prozessors im Transformationsszenario

OxygenXML: Auswahl des XSLT-Prozessors im Transformationsszenario

Leider kann OxygenXML keine Parameter oder initiale Modes bzw. initiale Templates an den so erzeugten Prozessor übergeben. Ein Workaround ist, für definierte Fälle eigene Prozessoren anzulegen. Für den ad-hoc-Selbsttest der XSL-SB sieht das so aus:

OxygenXML: einen neuen XSLT-Prozessor anlegen: AltovaXML mit erweiterten Einstellungen für initialen Mode und Parameter

OxygenXML: AltovaXML mit erweiterten Einstellungen für initialen Mode und Parameter

Der erste Teil der Kommandozeile »"C:\Program Files (x86)\Altova\AltovaXML2011\AltovaXML.exe" /xslt2 ${xsl} /in ${xml} /out ${out} /m internals.self-test -param internals.logging-level="'DEBUG'" -param internals.errors.die-on-critical-errors="'no'"« ist identisch zu oben, initialer Mode und Parameter werden zusätzlich hart codiert übergeben. Hier sollten die Entwickler von OxygenXML gelegentlich einmal nachbessern, es kann ja nicht so schwer sein, einen benannten Parameter als benanntes Makro anzubieten.

Keine Kommentare

Encoding und Zeilenumbrüche ändern mit XSLT

Beim Datenaustausch mit XML entsteht immer wieder das Problem, dass eine bestimmte Anwendung nur Daten in einem bestimmten Encoding »versteht«, es ist also gelegentlich eine Konvertierung des Encodings notwendig. Ich verwende dazu eine »Identity Transformation«, die bis auf das Encoding tatsächlich überhaupt keine Änderungen vornimmt:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
	version="1.0"
	xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
	>
	<!--  -->
	<xsl:output encoding="Windows-1252" indent="no" method="xml"/>
	<!--<xsl:output encoding="UTF-8" indent="no" method="xml"/>-->
	<!--<xsl:output encoding="UTF-16" indent="no" method="xml"/>-->
	<!--<xsl:output encoding="ISO-8859-1" indent="no" method="xml"/>-->
	<!--  -->
	<xsl:preserve-space elements="*"/>
	<!--  -->
	<xsl:template match="@* | node()">
		<xsl:copy>
			<xsl:apply-templates select="@* | node()"/>
		</xsl:copy>
	</xsl:template>
	<!--  -->
</xsl:stylesheet>

In XSLT kann man das Encoding des Output-Dokumentes mit <xsl:output encoding="xxx"/> festlegen. Im obigen Beispiel habe ich die gängigen Encodings vorgegeben, zur Anwendung müssen nur die jeweilige Zeile vom Kommentar befreit und die Zeile auskommentiert werden.

Die unterstützen Encodings sind vom jeweiligen XSLT-Prozessor und im Fall von Saxon zudem von der darunter liegenden Java-Installation abhängig. Obwohl laut XML-Standard nur UTF-8 und UTF-16 unterstützt werden müssen, kommen viele XSLT-Prozessoren auch mit ISO 8859-1, Windows-1252 und anderen zurecht.

Zeilenumbrüche

Im vorigen Post habe ich das Problem der falschen Zeilenumbrüche angesprochen, aber keine Lösung angeboten. Die folgt nun hier, allerdings muss ich dazu etwas weiter ausholen:

Zeilenumbrüche sind sogenannte Steuerzeichen, d.h. Zeichen, die normalerweise nicht dargestellt werden, sondern die Darstellung steuern. Historisch haben sich drei Varianten etabliert: Unix verwendet das Steuerzeichen LF (engl. line feed, hexadezimal &0A;), der Mac bis Mac OS 9 CR (engl. carriage return, hex &0D;) und Windows die Sequenz CRLF (&0A;&0D;). Unicode und alle Encodings kennen beide Zeichen, allerding müssen laut XML-Standard bei der Verarbeitung von XML-Dokumenten CR und CRLF zu LF normalisiert werden, so dass ausgegebene Dokumente normalerweise Unix-Zeilenumbrüche enthalten.

Um trotzdem Windows-Zeilenumbrüche in der Ausgabe zu erhalten, muss der XSLT-Prozessor bei der Ausgabe LF wieder in CRLF umwandeln. Soweit ich weiß, gibt es in Saxon keine Möglichkeit dazu, allerdings schreibt Microsofts msxsl.exe Windows-Zeilenumbrüche. <OxygenXML/> erlaubt in der Konfiguration von Transformations-Szenarien die Auswahl von MSXML4.0 als Transformator (sic!). Einzige Einschränkung: Microsoft unterstützt nur XSLT in der Version 1.0.

Ich habe das Stylesheet im Beispiel-Ordner hinterlegt und das Beispieldokument um Testfälle ergänzt. Auch die <OxygenXML/>-Projekt-Datei liegt in einer ergänzten Version vor.

Keine Kommentare

Unicode, Encoding, Numeric Character References und Entities

So etwa beim 2. Schritt mit XML stolpert wohl jeder über Probleme mit dem Encoding: Sonderzeichen werden verstümmelt, Zeilenumbrüche verschwinden, Software verschluckt sich. Wo ist das Problem? Eine Datei ist zuerst eine Sequenz von Bytes. Alle Beteiligten müssen diesen Bytes die selbe Bedeutung zumessen, damit die Kommunikation klappt: es ist meist sinnlos, eine Textdatei mit einem Bildeditor zu bearbeiten. Nicht anders ist das bei XML: Probleme entstehen, wenn eine Software die Daten anders interpretiert, als sie vom Sender gemeint waren.

Encoding und Unicode

Technisch ist der Vorgang einfach: am Beginn der XML-Verarbeitung liest ein sogenannter Parser die XML-Daten ein. Zu einem relativ frühen Zeitpunkt wandelt er den Bytestrom in systeminterne normalisierte Zeichen um (Decoding), etwa in Unicode-Codepoints (vereinfacht: Bytes oder Bytegruppen, die Schriftzeichen eindeutig identifizieren). Die weitere Verarbeitung – etwa die Umwandlung in ein Objektmodell und eine XSL-Transformation – findet über diese Codepoints statt. Am Ende der Verarbeitung werden die Objekte und Codepoints wieder in einen Bytestrom – z.B. eine Datei – umgewandelt (Encoding). Der Parser muss also das Encoding der Eingabe kennen, um sie intern sinnvoll verarbeiten zu können; ebenso muss die weiterverarbeitende Software das Encoding der Ausgabe unterstützen.

Ein Encoding ist eine definierte Zuordnung von Bytes und Bytegruppen zu einem bestimmten (Zeichen-) Symbol. Die meisten Encodings sind historisch gewachsen; weit verbreitet sind ASCII, ISO 8859-1 und Windows-1252. Diesen Encodings gemeinsam ist, dass sie nicht alle weltweit vorkommenden Zeichen unterstützen, schon Griechisch oder Kyrillisch liegen außerhalb des definierten Zeichenumfanges.

Unicode wurde entwickelt, um perspektivisch alle sinnvollen Schriftzeichen der Menschheit identifizieren und darstellen zu können. Nach einer bestimmten Systematik wurden Bereiche eingeteilt (sogenannte Planes), in denen wiederum Blöcke (englisch blocks) mehr oder weniger zusammengehörige Zeichen (z.B. eines Schriftsystems) zusammenfassen. Ein einzelnes Zeichen (aber bspw. auch sogenannte Modifier, die andere Zeichen verändern, wie z.B. diakritische Zeichen) wird mit einem Codepoint definiert, d.h. per Definition wird festgelegt, dass ein bestimmtes Zeichen eine bestimmte Position innerhalb eines Blockes hat. Die Position wird einfach durchgezählt. So ist das Zeichen »Ё« das 2. Zeichen im Kyrillisch-Block ab 400, in Unicode-Notierung U+0401. Wichtig: für die Definition des Unicode-Codepoints eines Zeichens haben nur systematische Kriterien eine Rolle gespielt (wobei natürlich über praktische Erwägungen bei der Definition doch technische Gesichtspunkte Einfluss hatten). Mit UTF-8 wurde ein Encoding entwickelt, um alle Unicode-Codepoints in einem Bytestrom darstellen zu können.

Numeric Character References und Character Entity References

Was aber, wenn ein Encoding oder die verwendete Software bestimmte Zeichen nicht verarbeiten oder darstellen kann? Beispielsweise unterstützen nur wenige europäische Zeichensätze Symbole für Chinesisch oder Arabisch. In diesen Fällen können numerische Zeichenreferenzen (engl. Numeric Character References, kurz NCR) verwendet werden. Sie verweisen laut XML-Standard auf einen Unicode-Codepoint. So verweist die numerische Zeichenreferenz &#65; (hexadezimal &#x41;) auf U+0041 (den Großbuchstaben »A«). Achtung: Da numerische Zeichenreferenzen auf Unicode verweisen, sind Referenzen auf den Cp-1252-(Windows-) Bereich zwischen 128 und 159 (hexadezimal 80 und 9F) ungültig (auch wenn viele Programme – wie Webbrowser – diese fehlerhaften Referenzen stillschweigend korrigieren).

Ein anderer Fall sind Character Entity References, umgangsprachlich meist Entities genannt. In SGML-basierten Sprachen wie HTML und XML können Character Entity References für Zeichen und Zeichengruppen vorab definiert werden, die dann im Text an Stelle der referenzierten Zeichen verwendet werden. So steht in HTML &uuml; für das kleine »ü«. Bei XML müssen Character Entity References (mit Ausnahme der vordefinierten &amp;, &lt;, &gt; und &quot;) im Dokument oder in einer externen DTD definiert werden.

Die Behandlung von Numeric Character References und Character Entity References ist Bestandteil der SGML-, HTML- und XML-Standards. Damit obliegt sie dem entsprechenden Parser. Unicode und UTF-8 kennen diese Konstrukte nicht.

Probleme

Die häufigsten Probleme liegen jetzt auf der Hand:

  • Die Software erkennt das Encoding nicht richtig. Meist wird in diesen Fällen ein Standard-Encoding verwendet, dass nicht unbedingt jenes des Dokumentes sein muss.
  • Das Dokument hat ein anderes Encoding, als deklariert wurde.
  • Das Dokument verwendet Zeichen, die im deklarierten Encoding nicht vorkommen. Hier ist in Windows-Umgebungen ein häufiger Fehler, das als Encoding ISO 8859-1 oder UTF-8 angegeben wird, im Dokument aber Zeichen oder numerische Zeichenreferenzen aus dem Cp-1252-Bereich zwischen 128 und 159 (hexadezimal 80 und 9F) verwendet werden.
  • Die Software kann bestimmte Codepoints nicht darstellen und verwendet dafür Ersatzzeichen wie das Fragezeichen.
  • Die Software kann auf die Definition von Character Entity References nicht zugreifen, etwa weil die DTD nicht angegeben wurde oder nicht verfügbar ist.

Tückisch ist, dass viele Anwendungen bei diesen Problemen die Arbeit nicht verweigern (können), weil ja zumindest die Zeichen aus dem ASCII-Bereich (kleiner 128) ihren Dienst leisten. In der Folge entstehen Ausgabedokumente mit falsche Zeichen, oder gespeicherte Dokumente werden irreparabel beschädigt. Deshalb ist es besser, auf eine korrekte Abstimmung des Encodings zu achten, als zweifelhafte Workarounds zu bemühen.

Quellen

Standard: http://www.w3.org/TR/xml/#sec-references

Wikipedia (zum Lesen nicht wirklich zu empfehlen): http://de.wikipedia.org/wiki/Unicode, http://de.wikipedia.org/wiki/UTF-8, http://de.wikipedia.org/wiki/ISO_8859-1, http://de.wikipedia.org/wiki/Entit%C3%A4ten_in_Auszeichnungssprachen

Nachtrag (englisch, dafür gut lesbar): http://lachy.id.au/log/2005/10/char-refs

Nachtrag II: Zu den hier nur kurz erwähnten Zeilenumbrüchen siehe Encoding und Zeilenumbrüche ändern mit XSLT

Nachtrag III: Sehr gute Erklärung zu Unicode und Encoding: Joel Spolsky: The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!) (englisch)

Keine Kommentare

Die einfachste XSL-Transformation

Meine ersten Gehversuche mit XSLT hatten das Ziel, kleine Änderungen an XML-Dokumenten vorzunehmen: einzelne Elemente löschen, Werte neu berechnen und ähnliches. Die Lösung dafür ist die sogenannte »Identity Transformation«:

<xsl:stylesheet
	version="2.0"
	xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
	>
	<!--  -->
	<xsl:template match="@* | node()">
		<xsl:copy>
			<xsl:apply-templates select="@* | node()"/>
		</xsl:copy>
	</xsl:template>
	<!--  -->
</xsl:stylesheet>

Dieses Stylesheet kopiert das Eingabedokument vollständig in das Ausgabedokument. Das einzige Template matcht alle Attribute (@*) und alle Knoten (node(), das sind Elemente, Text, Kommentare und sogenannte Processing Instructions).

Wie kann man nun Änderungen am Dokument vornehmen? Ganz einfach: Es werden zusätzliche Templates eingefügt, die nur die zu ändernden Knoten beeinflussen. Sollen zum Beispiel aus einem XHTML-Dokument alle <code/>-Elemente gelöscht werden, hilft folgendes Stylesheet:

<xsl:stylesheet
	version="2.0"
	xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
	xmlns:xhtml="http://www.w3.org/1999/xhtml"
	>
	<!--  -->
	<xsl:template match="@* | node()">
		<xsl:copy>
			<xsl:apply-templates select="@* | node()"/>
		</xsl:copy>
	</xsl:template>
	<!--  -->
	<xsl:template match="xhtml:code"/>
	<!--  -->
</xsl:stylesheet>

Leider wird jetzt der enthaltene Text ebenfalls gelöscht. Wenn die Kind-Knoten (z.B. text()-Knoten) erhalten werden sollen, muss für diese die allgemeine Kopierregel angewendet werden, am einfachsten durch ein <xsl:apply-templates/>:

<xsl:stylesheet
	version="2.0"
	xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
	xmlns:xhtml="http://www.w3.org/1999/xhtml"
	>
	<!--  -->
	<xsl:template match="@* | node()">
		<xsl:copy>
			<xsl:apply-templates select="@* | node()"/>
		</xsl:copy>
	</xsl:template>
	<!--  -->
	<xsl:template match="xhtml:code">
		<xsl:apply-templates/>
	</xsl:template>
	<!--  -->
</xsl:stylesheet>

Voilà! Alle <code/>-Tags sind entfernt, der Inhalt ist noch da, und alles ohne Suchen&Ersetzen mit regulären Ausdrücken.

Letztes Beispiel: die <code/>-Tags sollen durch <span class="code"/>-Tags ersetzt werden. Dazu werden hier die neuen Elemente einfach als sogenannte literale Elemente (englisch Literal Result Elements) in das Template geschrieben:

<xsl:stylesheet
	version="2.0"
	xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
	xmlns="http://www.w3.org/1999/xhtml"
	xpath-default-namespace="http://www.w3.org/1999/xhtml"
	exclude-result-prefixes="#default"
	>
	<!--  -->
	<xsl:template match="@* | node()">
		<xsl:copy>
			<xsl:apply-templates select="@* | node()"/>
		</xsl:copy>
	</xsl:template>
	<!--  -->
	<xsl:template match="code">
		<span class="code"><xsl:apply-templates/></span>
	</xsl:template>
	<!--  -->
</xsl:stylesheet>

Man beachte die Verwendung von xpath-default-namespace und exclude-result-prefixes. Dies sorgt dafür, dass das Namespace-Präfix nicht mehr explizit z.B. in das match-Attribut geschrieben werden muss und keine unnötigen Namespace-Angaben in die Ausgabedatei geschrieben werden.

Das letzte Stylesheet gibt es zum Herunterladen, ein passendes Transformations-Szenario für OxygenXML in der Projekt-Datei example.xpr

Tipp: Ich habe die Identity Transformation als Dokumentenvorlage in OxygenXML hinterlegt, weil ich sie sehr oft brauche. Dann sind Mini-Stylesheets sehr schnell geschrieben.

Nachtrag: Die oben angebotene Identity-Transformation hat einige Nebeneffekte: Standard-Attribute aus der DTD werden ergänzt, Zeilenumbrüche werden nicht genau rekonstruiert, Numeric Character References werden in Zeichen umgewandelt. Eine kompliziertere Alternative wird unter http://www.xmlplease.com/identity-template (englisch) vorgestellt.

Keine Kommentare