- 1 Intro
- 2 Anwendungsgebiete
- 2.1 XSLT - die Programmiersprache im XML Bereich
- 2.2 Aktuelle und vergangene Anwendungen
- 2.3 Professionelle XML Verarbeitung
- 2.4 Technische Dokumentation
- 3 Wichtige Konzepte
- 3.1 Push vs. Pull Stylesheets
- 3.2 Eindeutigkeit der Regelbasis
- 3.3 Namespaces
- 3.4 Schemata
- 3.5 Standards
- 3.5.1 DITA
- 3.5.2 DITA Inhaltsmodell
- 3.5.1 DITA
- 4 Ausgewählte Themen
- 4.1 Transformationen mit XSLT
- 4.1.1 Vortransformationen
- 4.1.2 Komplexe XML-2-XML Transformationen
- 4.1.2.8 Vererbung
- 4.1.2.8 Vererbung
- 4.1.3 XSLT Streaming
- 4.1.3.1 XSLT Akkumulator
- 4.1.3.2 XSLT Iterator
- 4.1.4 Reguläre Ausdrücke
- 4.1.5 Modus vs. Tunnel Lösung
- 4.1.6 Identifikation mit
generate-id()
- 4.1.6.4 XPath-Achsenbereich selektieren
- 4.1.6.4.1 Funktionen und Module
- 4.1.6.4.1 Funktionen und Module
- 4.1.6.4 XPath-Achsenbereich selektieren
- 4.1.7 Webservice Calls mit doc() und unparsed-text()
- 4.1.8 Stylesheet-Parameter auf der Kommandozeile
- 4.1.9 Leerzeichenbehandlung
- 4.1.10 Mit
translate
Zeichen ersetzen
- 4.1.10.1 Spass mit dem Sequenzvergleich
- 4.1.11 Character Mappings in der Ausgabe
- 4.1.12 JSON mit XSLT 1.0 und Python lxml
- 4.1.1 Vortransformationen
- 4.2 Abfragen mit XQuery
- 4.2.5 XQuery als Programmiersprache
- 4.2.5.3
if..then..else
Ausdrücke
- 4.2.5.3.2 SQL Views in MarkLogic
- 4.2.5.3
if..then..else
Ausdrücke
- 4.2.6 Hilfreiche XQuery Schippsel
- 4.2.5 XQuery als Programmiersprache
- 4.3 XML Datenbanken
- 4.3.1 Connector zu Marklogic in Oxygen
- 4.3.2 Bi-Temporale Dokumente
- 4.3.2.1 Anlegen des Testszenarios auf der ML Konsole
- 4.3.2.2 Ausführen einiger Beispiel-Queries
- 4.3.3 Webapps mit MarkLogic
- 4.3.3.5 Wikipedia Scrapper Applikation
- 4.3.3.5 Wikipedia Scrapper Applikation
- 4.3.4 Dokument-Rechte in MarkLogic
- 4.3.5 MarkLogic Tools
- 4.3.5.1 EXPath Konsole
- 4.3.5.2 mlcp - MarkLogic Content Pump
- 4.3.5.3 Deployment-Tools
- 4.4 XSL-FO mit XSLT1.x
- 4.5 Testing
- 4.5.1 Validierung mit Schematron
- 4.5.2 Erste Schritte mit XSpec
- 4.5.1 Validierung mit Schematron
- 4.6 Performanz-Optimierung
- 4.1 Transformationen mit XSLT
- 5 Zusätzliches Know-How
- 5.1 XML Editoren
- 5.2 Quellcode-Versionskontrolle
- 5.2.1 Kurze Geschichte zur Versionskontrolle Test
- 5.2.2 GIT Kommandos
- 5.2.1 Kurze Geschichte zur Versionskontrolle Test
- 5.1 XML Editoren
- 6 Glossary
- 7 Tektur CCMS
4.1.2 Komplexe XML-2-XML Transformationen
Der erfahrene XML-Entwickler schreibt schlanken, performanten und einfachen Code, den auch andere gut verstehen. Er meistert alle Bereiche der XML-Entwicklung und hat sich mit Publishing Standards befasst, wie Docbook, DITA und JATS. Er erstellt mittels XSL-FO und verschiedenen Seitenvorlagen schöne PDF Dokumente und hat auch schon in anderen Anwendungsbereichen geabeitet, wie bspw. im EDI Umfeld. Er beherrscht XML Datenbanken, wie Marklogic oder eXist und deren Abfragesprache XQuery.
Als Königsdisziplin stellen sich komplexe XML-2-XML Transformationen heraus. Insbesondere solche, die von einem relativ freien Inhaltsmodell auf ein restriktives Inhaltsmodell abbilden. Dabei können diese ausserhalb und auch innerhalb einer XML Datenbank ablaufen - oder über Webrequests verteilt statt finden.
Es hat sich bewährt solche komplexen Transformationen auf mehrere Schritte aufzuteilen, die jeder für sich genommen, eine abgeschlossene und leicht zu testende Einheit bildet.
Schritt-für-Schritt wird dabei die XML Eingabeinstanz transformiert, bis schliesslich das validierbare Ergebnis herauskommt. Das XML der Zwischenschritte kann dabei meistens nicht gegen ein Schema validiert werden, weshalb eine besondere Sorgfalt bei der Entwicklung erforderlich ist.
4.1.2.1 Schritt-für-Schritt Python Skript
Bei einer mehrstufigen Transformation möchte man bei der Entwicklung leicht die Zwischenschritte überprüfen können. Dabei hilft eine andere Skriptsprache, wie bspw. Python. Das folgende Skript nimmt die XML Daten in einem Ordner
input
, transformiert diese in Sortierreihenfolge mit den XSLT Skripten, die im Ordner
xslt
liegen und schreibt die Ausgabe übersichtlich in den Ordner
output
.
import glob, os, shutil, getopt, sys, subprocess SAXON_JAR = "/mnt/c/saxon/saxon9pe_98.jar" JAVA_CMD = "java" def transform(): for fpath in glob.glob('input/*'): file_name= os.path.basename(fpath) input_folder = os.path.dirname(os.path.realpath(fpath)) input_file = os.path.join(input_folder, file_name) steps = os.listdir("xslt") steps.sort() step = None for step_file in steps: if not step_file.startswith("step"): continue step = step_file.split("_")[0] output_folder = input_folder.replace("input","output/"+step) current_step = os.path.join(output_folder, file_name) os.makedirs(output_folder,exist_ok=True) args = [ JAVA_CMD, "-classpath", SAXON_JAR, "net.sf.saxon.Transform", "-s:"+input_file, "-o:"+current_step, "-xsl:xslt/"+step_file, "filename="+os.path.basename(input_file).replace(".xml","") ] try: subprocess.call(args) except: print ("ERROR: Could not transform file: "+fpath+" with: "+step_file) input_file = current_step print ("Transformed "+step+": "+fpath) transform() print ("Done")
Das Skript kann natürlich noch um weitere Funktionen erweitert werden, wie bspw. einer Validierung für den letzten Schritt oder einem Deltavergleich der Zwischenergebnisse mit denen des vorherigen Transformationslaufs.
Will man das Ganze noch weiter treiben, kann man auch eine BPMN Engine, wie Camunda ↗ verwenden (einen speziellen Task-Executor für Camunda, der genau für diese XML Zwecke gemacht wurde, findet man auch in meinem Github Repository ↗).
4.1.2.2 Patterns für wiederkehrende Schritte
Eine mehrstufige Transformation, die auf ein restriktives Inhaltsmodell abbildet, funktioniert vielleicht wie eine Goldschürfer-Pipeline, in der gesiebt und gerüttelt wird, bis das erwartete Ergebnis vorliegt.
Folgende Patterns für wiederkehrende Schritte lassen sich dabei identifizieren:
4.1.2.3 Elemente markieren
Wenn man alles auf einmal transformieren will, kommt man schnell in Bedrängnis. Es empfiehlt sich zunächst zu markieren und im nächsten Schritt dann auf den markierten Elementen bestimmte Operationen auszuführen.
<xsl:template match="*[name()=$outline-element-names]"> <xsl:copy> <xsl:apply-templates select="@*"/> <xsl:if test="preceding-sibling::*[1][@otherprops=$list-fragment-marker]"> <xsl:attribute name="copy-target-id" select="preceding-sibling::*[@otherprops=$list-fragment-marker][1]/ descendant::*[@copy-id][last()]/@copy-id"/> </xsl:if> <xsl:apply-templates select="node()|@*"/> </xsl:copy> </xsl:template>
Hier wird ein künstliches Attribut
@copy-target-id
mit einem Wert von
@copy-id
an ein Outline Element gesetzt, das im folgenden Schritt an die Stelle nach der
@copy-id
kopiert wird.
4.1.2.4 Elemente kopieren
Ein wiederverwendbarer Schritt, der mit
@copy-target-id
markierte Elemente nach eine Stelle kopiert, die mit
@copy-id
markiert wurde, könnte z.B. so aussehen:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="2.0"> <xsl:key name="targets" match="*[@copy-target-id]" use="@copy-target-id"/> <!-- copy elements from src to target --> <xsl:template match="*[@copy-id]"> <xsl:copy> <xsl:apply-templates select="node()|@*"/> <xsl:apply-templates select="key('targets',@copy-id)" mode="copied"/> </xsl:copy> </xsl:template> <!-- remove original position and attributes --> <xsl:template match="*[@copy-target-id]"/> <xsl:template match="@copy-target-id|@copy-id" mode="copied"/> <xsl:template match="node()|@*" mode="#all"> <xsl:copy> <xsl:apply-templates select="node()|@*" mode="#current"/> </xsl:copy> </xsl:template> </xsl:stylesheet>
Mit so einer Vorgehensweise kann man sukkzessive und mittels einzelner Kopierschritte die XML Instanz umbauen und die Zwischenergebnisse verfolgen. So eine explorative Herangehensweise hat enorme Vorteile, wenn man sich über den Algorithmus noch nicht ganz im Klaren ist.
4.1.2.5 Elemente nach oben ziehen
Falls ein tieferliegendes Element nicht an die Stelle in der Ziel-DTD passt, kann man es mit folgenden Templates "nach oben ziehen":
<xsl:template match="table[descendant::table or descendant::ol]"> <xsl:copy> <xsl:apply-templates mode="remove-table-ol"/> </xsl:copy> <xsl:apply-templates select="descendant::ol | descendant::table"/> </xsl:template> <xsl:template match="*[self::ol or self::table]" mode="remove-table-ol"/>
Hier werden Tabellen und Listen in einer Tabelle nach der Tabelle gesetzt. Über einen Modus (vgl. auch ein Beispiel zum Modus hier: Modus vs. Tunnel Lösung werden diese Knoten aus dem XML Zielbaum "ausgeschnitten".
4.1.2.6 Blöcke auszeichnen
Falls Blockstrukturen geklammert werden sollen, bspw. wenn diese im HTML Kapitel nur mittels
h1
Überschriften-Tags gekennzeichnet sind, dann hilft vielleicht ein Template wie dieses weiter:
<xsl:template match="body"> <xsl:for-each select="h1"> <block> <title> <xsl:apply-templates /> </title> <xsl:apply-templates select="following-sibling::*[not(self::h1)] [preceding-sibling::h1[1][generate-id()=current()/generate-id()]]"/> </block> </xsl:for-each> </xsl:template>
Mittels der XPath
fn:is()
Funktion liesse sich der
generate-id()
Vergleich sogar noch abkürzen.
4.1.2.7 Mixed Content wrappen
Sehr unangenehm ist sporadisch auftretender XML Mixed Content, z.B. zwischen Paras. Mit folgenden Templates lässt sich das handeln. Zuerst kann man den Mixed Content in einem vorhergehenden Schritt markieren und in einen künstlichen Para packen ...
<!-- wrap a p around PCDATA in li --> <xsl:template match="text()[parent::li]"> <p content="mixed"> <xsl:value-of select="."/> </p> </xsl:template>
... hier markiert mit
@content="mixed"
. Im folgenden Schritt werden dann die ursprünglichen Paras, die jetzt verschachtelt im künstlichen Para liegen wieder ausgepackt:
<xsl:variable name="inline-elements" select="('sub','sup','b','i','br','u')"/> <xsl:template match="p[@content='mixed' and not(preceding-sibling::p[@content='mixed'])]"> <xsl:variable name="first-p-id" select="(preceding-sibling::*[1]/generate-id(), generate-id())[1]"/> <p> <xsl:copy-of select="preceding-sibling::*[name()=$inline-elements]"/> <xsl:apply-templates select="node()|following-sibling::*[(self::p[@content='mixed'] or name()=$inline-elements) and not(preceding-sibling::p[not(@content='mixed')][1]/ generate-id()!=$first-p-id)]" mode="unwrap"/> </p> </xsl:template> <xsl:template match="p[@content='mixed' and preceding-sibling::p[@content='mixed']]"/> <xsl:template match="p" mode="unwrap"> <xsl:apply-templates/> </xsl:template> <xsl:template match="*[not(self::p)]" mode="unwrap"> <xsl:copy> <xsl:apply-templates select="node()|@*"/> </xsl:copy> </xsl:template> <xsl:template match="li[p[@content='mixed']]/*[name()=$inline-elements]"/>