CiteProc

Kürzlich wurde mein Artikel zu CiteProc in der deutschsprachigen Wikipedia gelöscht, weil die Software nicht den einschlägigen Relevanzkriterien entspricht. Ich bin mir sicher, dass so unbedarfte Wikipedia-Leser prima vor den Fährnissen bibliografischer Formatierung geschützt sind. Da ich hier aber darauf keine Rücksicht nehmen muss, habe ich den Artikel in das Glossar eingebaut. SCNR.

Keine Kommentare

Experten überarbeiten Wikipedia-Artikel zu XML

Auf Grund eines Posts von Tim Bray hat in den letzten Tagen auf der xml-dev-Mailingliste ein spannender Austausch zur Überarbeitung des XML-Artikels in der englischen Wikipedia stattgefunden. Aufregend sind daran zwei Dinge: Erstens war das Who’s Who der XML-Gilde an der Überarbeitung des Artikels beteiligt; dieser Grundlagenartikel ist nun von absoluten Experten verfasst und geprüft (Um ehrlich zu sein: das Review dauert aktuell noch an). Zweitens: Die Diskussion auf der Mailingliste, das Ringen um korrekte Formulierungen ist ebenso lehrreich wie unterhaltsam. Zwei Wermutstropfen bleiben: Die Diskussion wäre auf der Diskussionsseite besser aufgehoben gewesen, und es ist ein Artikel entstanden, der passagenweise von den Wikipedia-Konventionen abweicht – hoffen wir, dass der Inhalt unbeschadet diverse minor edits und Bots übersteht.

Keine Kommentare

XSLT-Dokumentation mit XSLStyle™

Leider gibt es für XSLT keine standardisierten oder auch nur weitverbreiteten Regeln für die Dokumentation der Stylesheets. Ein Grund dafür könnte sein, dass einfach zu bedienende Werkzeuge nicht verfügbar sind. Für die Bespiele auf dieser Website habe ich mir noch einmal ein paar Ansätze angeschaut und mich dann für XSLStyle™ von G. Ken Holman entschieden, weil a) dieses Tool als XSL-Transformation direkt auf das Stylesheet selbst angewandt werden kann und b) XSLStyle™ auch noch ein paar Tests auf guten Programmierstil durchführt.

<OxygenXML/>-Transformations-Szenario für XSLStyle™

<OxygenXML/>-Transformations-Szenario für XSLStyle™

Die Installation ist recht einfach: zip-Datei herunterladen, in einem beliebigen Verzeichnis entpacken, fertig. XSLStyle™ kann über die Kommandozeile auf das zu dokumentierende Stylesheet angewandt werden, bequemer ist natürlich, in <OxygenXML/> ein Transformations-Szenario einzurichten.

Die Dokumentation erfolgt über Elemente im http://www.CraneSoftwrights.com/ns/xslstyle-Namensraum, die unmittelbar vor dem zu dokumentierenden XSL-Element stehen. XSLStyle™ kann bei fehlender Dokumentation warnen. Weil bei mir das von XSLStyle™ vorgegebene Präfix xs: immer für den http://www.w3.org/2001/XMLSchema-Namensraum steht, habe ich das Präfix doc: verwendet.

Innerhalb der XSLStyle™-Elemente kann die Dokumentation in beliebigem Markup erfolgen, G. Ken Holman gibt Stylesheets für DocBook und DITA mit. Ich habe mich pragmatisch für DocBook entschieden, weil ich damit aus einem früheren Projekt etwas Erfahrung habe. Es steht ein großer Teil des DocBook-Vokabulars zur Verfügung; nachdem meine Experimente mit einem recht elaboriertem Markup (wie refentry und funcsynopsis) primär aufgeblähten Code hervorgebracht haben, schien mir eine stark vereinfachte Version jedoch die bessere Variante. Der Code aus einem der letzten Posts zum Dateien-Testen sieht mit Dokumentation so aus:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet
	version="2.0"
	xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
	xmlns:xs="http://www.w3.org/2001/XMLSchema"
	xmlns:misc="http://www.expedimentum.org/XSLT/Misc"
	xmlns:saxon="http://saxon.sf.net/"
	xmlns:doc="http://www.CraneSoftwrights.com/ns/xslstyle"
	xmlns:docv="http://www.CraneSoftwrights.com/ns/xslstyle/vocabulary"
	exclude-result-prefixes="doc docv"
	extension-element-prefixes="saxon"
	>
	<!--  -->
	<doc:doc filename="files.xsl" internal-ns="docv" global-ns="doc misc" vocabulary="DocBook" info="$Revision: 29 $, $Date: 2009-05-03 02:11:03 +0200 (So, 03 Mai 2009) $">
		<doc:title>Dateien und Dateisystem</doc:title>
		<para>Dieses Stylesheet enthält Funktionen rund um das Dateisystem.</para>
		<para>Dieses Stylesheet unterliegt  mangels Schöpungshöhe keinen urheberrechtlichen Beschränkungen.</para>
		<revhistory>
			<revision>
				<revnumber>0.28</revnumber>
				<date>2008-05-03</date>
				<authorinitials>Stf</authorinitials>
				<revremark>erste Version mit Dokumentation</revremark>
			</revision>
		</revhistory>
	</doc:doc>
	<!--  -->
	<!-- __________     misc:file-exist()     __________ -->
	<doc:function>
		<doc:param name="href"><para>Pfad zur zu überprüfenden Datei (URI-encoded)</para></doc:param>
		<para>Diese Funktion überprüft, ob die angegebene Datei vorhanden ist. Das Ergebnis ist ein Wahrheitswert 
			(<code>xs:boolean</code>, <code>true()</code> oder <code>false()</code>). 
			<emphasis role="bold">Achtung!</emphasis> Diese Funktion erfordert bei Ausführung Saxon, 
			da die Saxon-spezifische Erweiterung <code>saxon:file-last-modified()</code>  verwendet wird.</para>
	</doc:function>
	<xsl:function name="misc:file-exists" as="xs:boolean">
		<xsl:param name="href" as="xs:string?"/>
		<xsl:value-of select="boolean(normalize-space(string(saxon:file-last-modified($href))))"/>
	</xsl:function>
	<!--  -->
</xsl:stylesheet>

Damit XSLStyle™ eine Dokumentation ausgibt, müssen der http://www.CraneSoftwrights.com/ns/xslstyle und ein weiter Namensraum für interne Zwecke (http://www.CraneSoftwrights.com/ns/xslstyle/vocabulary) im zu dokumentierenden Stylesheet deklariert werden. Außerdem erwartet XSLStyle™ am Anfang der Dokumentation ein <doc:doc/>-Element mit Basisinformationen, fehlt es (oder auch nur einzelne Attribute), gibt es bei mir merkwürdige Effekte.

Das vollständige Beispiel habe ich unter http://www.expedimentum.org/example/xslt/files.xsl abgelegt, die resultierende Dokumentation liegt unter http://www.expedimentum.org/example/xslt/doc/files.html. Hier bzw. hier gibt es ein etwas umfangreicheres Beispiel mit importierten Stylesheets.

Zum Schluss folgt noch eine Sammlung zufälliger Links zur XSL-Dokumentation, die bei der Recherche entstanden ist. Falls XSLStyle™ nicht passt, findet sich sicher eine geignetere Lösung. Mir hat XSLTdoc relativ gut gefallen, und mit einem Ant-Script lässt sich auch die umständliche Handhabung bändigen, leider lässt es sich nicht direkt als Transformation auf das zu dokumentierende Stylesheet anwenden.

siehe auch: Florent Georges: XSLStyle and oXygen (englisch)

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

Mit XSLT testen, ob eine Datei existiert

XSLT bietet recht wenig Möglichkeiten, mit der Außenwelt zu kommunizieren. Informationen über die Umwelt zur Laufzeit des Stylesheets lassen sich mit Bordmitteln nur in sehr geringem Umfang ermitteln, Wirkungen nur über die Dateiausgabe erzielen. Dies mag daher kommen, dass das funktionale Paradigma von XSLT Seiteneffekte verbietet, führt aber in der Praxis gelegentlich zu merkwürdigen Verrenkungen. Beispielsweise habe ich öfters das Problem, Stylesheets abhängig von der Existenz einer externen Datei aufzurufen. Bisher habe ich dazu in einer externen Anwendung ein XML mit Informationen zum Dateisystem erzeugt und dieses dann im XSLT ausgewertet. Das funktioniert ganz gut mit statischen Daten, bei häufigen Änderungen im Dateisystem ist dieses Verfahren aber zu umständlich.

XPath 2.0 bietet mit doc-available() zwar eine dedizierte Funktion, diese liefert aber nur bei der Existenz eines wohlgeformten XML-Dokumentes ein true(). Ebenso hängt ein logisches wahr bei unparsed-text-available() von der Existenz eines Textes ab. Was aber, wenn ich wissen will, ob eine Bild-Datei (nein, nicht SVG ;-)) oder eine leere Datei existiert? Die wenigen Lösungsvorschläge verweisen auf xslt-externe Erweiterungsfunktionen. Diese setzen in der Regel die Existenz von Java und manchmal auch zusätzliche Java-Klassen voraus, was die Weiterverwendung vorhandener Stylesheets auf verschiedenen Rechnern erheblich erschwert.

Da in meiner Umgebung ohnehin meist Saxon eingesetzt wird, bietet es sich an, die Saxon-eigenen Erweiterungsfunktionen zu benutzen. Zwar bietet Saxon nicht den gesuchten Test, aber mit file-last-modified() eine Funktion, die einen Leerstring zurückgibt, wenn die Datei nicht vorhanden ist. Damit war die gesuchte Funktion schnell geschrieben:

<xsl:function name="misc:file-exists" as="xs:boolean" xmlns:saxon="http://saxon.sf.net/" extension-element-prefixes="saxon">
	<xsl:param name="href" as="xs:string?"/>
	<xsl:value-of select="boolean(normalize-space(string(saxon:file-last-modified($href))))"/>
</xsl:function>

Zu beachten ist, dass diese Funktion nur mit Saxon funktioniert und keine Fehlerbehandlung stattfindet. Außerdem muss der Parameter URI-codiert werden (z.B. Leerzeichen durch %20 ersetzen). Es gibt also noch genug Raum für Optimierungen.

Nachtrag: Diese Funktion ist nun in der Beispielsammlung abgelegt.

Nachtrag II: saxon:file-last-modified() ist spezifisch für Saxon 9.1. In Saxon 9.2 PE/EE wurde diese Funktion in saxon:last-modified() integriert, in Saxon HE stehen keine Erweiterungsfunktionen zur Verfügung (vgl. Post von Michael Kay auf der Saxon-Mailingliste). Ich habe das Stylesheet entsprechend aktualisiert.

Nachtrag III: Eine Lösung unter Verwendung von Java, die weniger prozessorabhängig ist, habe ich hier beschrieben.

Nachtrag IV: Ich habe die verschiedenen Lösungen in der XSLT-SB zusammengeführt. Der Code kann in der Beispielsammlung eingesehen werden.

Keine Kommentare

Glossar aktiviert

Ich habe heute ein Glossar aktiviert. Erster Versuch war das WordPress-Glossary-Plugin. Leider mag es bestimmte Glossar-Einträge (wie HTML) nicht, weil es diese Ausdrücke blind durch einen Link (<a href=".."/>) ersetzt. Damit entfallen für das Glossar alle Begriffe, deren Ersetzung einen HTML- oder DOM-Syntaxfehler erzeugen, etwa XSLT (auf dieser Seite als href-Attribut unter den Kategorien). Auch dieser Fehler zeigt, dass Suchen&Ersetzen über XML-Daten kein guter Weg ist; mit XPath/XSLT könnte man das Ersetzen auf text()-Nodes einschränken. Leider habe ich zu wenig Ahnung von PHP, um den Code umzuschreiben.

Nachdem diverse Fehler auch auf Seiten ohne Glossar-Worte (Impressum u.a.) aufgetreten sind, habe ich das Plugin wieder deaktiviert. Nächster Versuch: Das Cross-linker-Plugin. Jetzt muss ich die Glossar-Begriffe zwar von Hand im Plugin registrieren und auf der Glossar-Seite nachtragen, aber es funktioniert.

Nachtrag: Da mir das manuelle Ergänzen der Glossar-Seite doch zu aufwändig war, habe ich kurzerhand im Glossary-Plugin die automatische Verlinkung abgeschaltet und es wieder aktiviert. Damit wurde die Glossar-Seite automatisch generiert, auch wenn die alphabetische Sortierung noch hakte. Nach einer halben Stunde Recherche und Probieren habe ich entdeckt, dass die verwendete WordPress-Funktion get_children Sortier-Parameter mitbringt. Leider steht das nicht bei der Beschreibung dieser Funktion, sondern bei get_posts :-(.

2 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

Römische Zahlen in Integer konvertieren

Für ein Projekt stand ich vor der Aufgabe, a) einen String darauf zu testen, ob er eine römische Zahl ist und b) und diesen String dann in einen Integer zu konvertieren. Ich kannte mich mit römischen Zahlen nicht wirklich aus, also erst einmal in der Wikipedia nachschlagen. Dabei lernte ich gleich, dass „römische Zahlen“ auf englisch „roman numerals“ heißen – gut, dass konnte ich in der anschließenden Google-Suche gebrauchen.

Im ersten Anlauf fand ich in Sal Manganos XSLT Cookbook eine angestaubte XSLT 1.0-Lösung, die mir überhaupt nicht gefiel. Der Test auf römische Zahlen ist ein Test auf gültige Zeichen, und anschließend wird mangels Funktionen eine Menge mit rekursiven Templates gemacht. Mit XSLT 2.0 muss das doch eleganter gehen. Die freche Lösung – allerdings ohne Gültigkeitstest – liefert Mukul Gandhi in der segensreichen xsl-Mailing-Liste ab. Er zählt solange von 1 bis 10000, bis das Ergebnis von <xsl:number/> gleich dem Input-String ist:

<xsl:stylesheet
	version="2.0"
	xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
	xmlns:xs="http://www.w3.org/2001/XMLSchema"
	xmlns:num="http://whatever">
	<!--  -->
	<xsl:output method="text"/>
	<!--  -->
	<xsl:variable name="max" select="10000"/>
	<!--  -->
	<xsl:template match="/">
		<xsl:call-template name="RomanToInteger">
			<xsl:with-param name="roman_number" select="'IIX'"/>
		</xsl:call-template>
	</xsl:template>
	<!--  -->
	<xsl:template name="RomanToInteger">
		<xsl:param name="roman_number"/>
		<xsl:for-each select="1 to $max">
			<xsl:if test="num:toRoman(.) = $roman_number">
				<xsl:value-of select="."/>
			</xsl:if>
		</xsl:for-each>
	</xsl:template>
	<!--  -->
	<xsl:function name="num:toRoman" as="xs:string">
		<xsl:param name="value" as="xs:integer"/>
		<xsl:number value="$value" format="I"/>
	</xsl:function>
	<!--  -->
</xsl:stylesheet>

Nachteil 1: Bei großen Zahlen kann dieser Algorithmus nicht effektiv sein. Nachteil 2: Die Beschränkung auf 10000 ist ein Schutz vor ungültigen Eingaben und der daraus resultierenden Endlosschleife, größere Zahlen sind aber vorstellbar. Also selbst entwickeln.

a) einen String darauf testen, ob er eine gültige römische Zahl ist

Der von Mangano angebotene Test auf die gültigen Zeichen „I“, „V“, „X“, „L“, „C“, „D“, „M“, „i“, „v“, „x“, „l“, „c“, „d“, „m“ ist nicht ausreichend, weil damit auch ungültige Kombinationen wie „IIX“ möglich sind (die unter anderem Mukul Gandhis Algorithmus bis zum Abbruch laufen lassen). Besser ist ein regulärer Ausdruck, dank Google gefunden bei regexlib.com:

<xsl:function name="misc:IsRomanNumeral" as="xs:boolean">
	<xsl:param name="Input" as="xs:string?"/>
	<xsl:variable name="temp" as="xs:string?" select="normalize-space(upper-case($Input))"/>
	<xsl:value-of select="not(matches($temp,'(([IXCM])2{3,})|[^IVXLCDM]|([IL][LCDM])|([XD][DM])|(V[VXLCDM])|(IX[VXLC])|(VI[VX])|(XC[LCDM])|(LX[LC])|((CM|DC)[DM])|(I[VX]I)|(X[CL]X)|(C[DM]C)|(I{2,}[VX])|(X{2,}[CL])|(C{2,}[DM])'))"/>
</xsl:function>

In der Variablen $temp wird der eingegebene String vorbereitet. Da der reguläre Ausdruck auf ungültige Kombinationen testet, war noch ein zusätzliches not() notwendig.

b) einen String mit einer römischen Zahl in einen Integer umwandeln

In der Wikipedia ist der Algorithmus beschrieben: Die Buchstaben werden durch ihre Integer-Werte ersetzt (dafür nehme ich eine Hilfsfunktion) und dann aufaddiert. Eine Ausnahme ist die Subtraktionsschreibweise. Vereinfacht: Steht genau eine kleinere Ziffer vor einer größeren, wird deren Wert von der Summe abgezogen. Ich setze das geradlinig um:

<xsl:function name="misc:GetIntegerFromRomanNumeral" as="xs:integer">
	<xsl:param name="Input" as="xs:string?"/>
	<xsl:variable name="temp" as="xs:string?" select="normalize-space(upper-case($Input))"/>
	<xsl:choose>
		<xsl:when test="misc:IsRomanNumeral($temp)">
			<xsl:variable name="Values" as="xs:integer*">
				<xsl:for-each select="for $i in 1 to string-length($temp) return $i">
					<xsl:variable name="CharValue" as="xs:integer"
						select="misc:GetIntegerFromRomanNumberChar(substring($temp, position(), 1))"/>
					<xsl:variable name="NextCharValue" as="xs:integer"
						select="misc:GetIntegerFromRomanNumberChar(substring($temp, position() + 1, 1) )"/>
					<xsl:choose>
						<xsl:when test="$CharValue lt $NextCharValue">
							<xsl:value-of select="- $CharValue"/>
						</xsl:when>
						<xsl:otherwise>
							<xsl:value-of select="$CharValue"/>
						</xsl:otherwise>
					</xsl:choose>
				</xsl:for-each>
			</xsl:variable>
			<xsl:value-of select="sum($Values)"/>
		</xsl:when>
		<xsl:otherwise>0</xsl:otherwise>
	</xsl:choose>
</xsl:function>
<!--  -->
<xsl:function name="misc:GetIntegerFromRomanNumberChar" as="xs:integer">
	<xsl:param name="Input" as="xs:string?"/>
	<xsl:variable name="temp" as="xs:string?" select="upper-case(normalize-space($Input))"/>
	<xsl:choose>
		<xsl:when test="$temp = 'I' ">1</xsl:when>
		<xsl:when test="$temp = 'V' ">5</xsl:when>
		<xsl:when test="$temp = 'X' ">10</xsl:when>
		<xsl:when test="$temp = 'L' ">50</xsl:when>
		<xsl:when test="$temp = 'C' ">100</xsl:when>
		<xsl:when test="$temp = 'D' ">500</xsl:when>
		<xsl:when test="$temp = 'M' ">1000</xsl:when>
		<xsl:otherwise>0</xsl:otherwise>
	</xsl:choose>
</xsl:function>

In der Variablen $Values wird für jedes Zeichen des Strings ein Wert erzeugt, am Ende enthält die Variable eine Sequenz von Werten. Diese Sequenz wird mit sum() einfach aufaddiert. Ein Test, ob bei $NextCharValue das nächste Zeichen hinter dem letzen Zeichen liegt, ist hier übrigens nicht notwendig, weil in diesem Fall substring() einen Leerstring zurückgibt, dessen Wert in misc:GetIntegerFromRomanNumberChar() als 0 definiert ist.

Nachtrag: Diese Funktionen sind nun in der Beispielsammlung abgelegt.

Keine Kommentare