Home · Blog · Planet Digital Humanities

David Maus M. A.

Digital Humanities Publishing

XProc Step by Step: Implementing a DOCX to TEI step

David Maus, 20.08.2018 · Twitter · Comment · Permalink

Some of our projects use an XML first workflow where the principal researchers transcribe and annotate historical documents in Microsoft Word documents. While this solution is far from perfect, it allows humanists to capture valuable content with a tool they are comformtable with.

Such is the case in The Nuns' Network. The project aims at a digital edition of letters written by the Benedictine nuns of the Lüne monestery between 1460 and 1555. Being comprised of a couple of hundred letters, automating repetitive tasks is key. The project recently entered a stage were the publication workflow is stable enough to be eased and/or partially replaced with automated processes.

The first step of the current workflow is an initial transformation of a Word in a TEI document. The project uses the official transformation stylesheet provided by the Text Encoding Initiative and included in the <oXygen/> XML editor. It is executed manually: <oXygen/> provides an Ant-based scenario that unzips the Word document and applies the respective transformation.

In order to integrate this first step into an XProc pipeline I implemented this initial transformation as an XProc step. The step utilizes the XProc extension steps pxp:unzip and pxf:delete, and Calabash's cx:depend-on attribute. It unzips all XML documents to a temporary folder, runs the TEI transformation, and deletes the temporary folder.

The final TEI document is sent to the result port of the step.

<p:declare-step version="1.0" type="ndn:docx2tei"
xmlns:c="http://www.w3.org/ns/xproc-step"
xmlns:cx="http://xmlcalabash.com/ns/extensions"
xmlns:ndn="http://diglib.hab.de/edoc/ed000248/ns"
xmlns:pxp="http://exproc.org/proposed/steps"
xmlns:pxf="http://exproc.org/proposed/steps/file"
xmlns:p="http://www.w3.org/ns/xproc">

<p:output port="result" primary="true">
<p:pipe step="apply-transform" port="result"/>
</p:output>

<p:option name="inputFile" required="true"/>

<p:import href="http://xmlcalabash.com/extension/steps/library-1.0.xpl"/>

<p:variable name="outputTempDir" select="concat($inputFile, '.tmp')"/>
<p:variable name="outputTempDirUri" select="resolve-uri(encode-for-uri($outputTempDir))"/>

<pxp:unzip name="list-zip-directory">
<p:with-option name="href" select="$inputFile"/>
</pxp:unzip>

<p:for-each name="iterate-zip-directory">
<p:iteration-source select="/c:zipfile/c:file[ends-with(@name, '.xml') or ends-with(@name, '.rels')]">
<p:pipe step="list-zip-directory" port="result"/>
</p:iteration-source>
<p:variable name="outputTempName" select="/c:file/@name"/>
<pxp:unzip>
<p:with-option name="href" select="$inputFile"/>
<p:with-option name="file" select="$outputTempName"/>
</pxp:unzip>
<p:store method="xml">
<p:with-option name="href" select="concat($outputTempDirUri, '/', encode-for-uri($outputTempName))"/>
</p:store>
</p:for-each>

<p:load name="load-stylesheet">
<p:with-option name="href" select="resolve-uri('stylesheet/profiles/default/docx/from.xsl')"/>
</p:load>

<p:load name="load-source" cx:depends-on="iterate-zip-directory">
<p:with-option name="href" select="concat($outputTempDirUri, '/word/document.xml')"/>
</p:load>

<p:xslt name="apply-transform">
<p:with-param name="word-directory" select="$outputTempDirUri"/>
<p:input port="stylesheet">
<p:pipe step="load-stylesheet" port="result"/>
</p:input>
<p:input port="source">
<p:pipe step="load-source" port="result"/>
</p:input>
</p:xslt>

<pxf:delete recursive="true" fail-on-error="false" cx:depends-on="apply-transform">
<p:with-option name="href" select="$outputTempDir"/>
</pxf:delete>

</p:declare-step>

Unicode, UTF-8 und Kollation in MySQL

David Maus, 20.07.2018 · Twitter · Comment · Permalink

Zeichenkodierung

Die Zeichenkodierung utf8 kann nur Unicode-Zeichen verarbeiten die sich in maximal 3 Byte UTF-8 kodieren lassen. Dadurch erschöpft sich der verwendbare Zeichenvorrat auf die Zeichen der Basic Multilingual Plane (BMP).

Die Zeichenkodierung utf8mb4 unterstützt auch Zeichen der höheren Unicode-Ebenen, allerdings um den Preis verkürzter Indexschlüssel.

Die Maximallänge dieser Schlüssel beträgt unabhängig vom Datentyp im Fall der InnoDB-Engine 767 Byte. Die Zeichenkodierung utf8 verwendet maximal 3 Byte pro Zeichen, so dass ein Indexschlüssel aus 767 ÷ 3 = 255 Zeichen bestehen kann; die Zeichenkodierung utf8mb4 verwendet maximal 4 Byte pro Zeichen, wodurch sich eine maximale Länge eines Indexschlüssel von 767 ÷ 4 = 191 Zeichen ergibt.

Für die MyISAM-Engine mit einer Schlüssellänge von 1000 Byte gelten die Einschränkungen analog.

Wenn ein Index über ein TEXT, VARCHAR oder CHAR-Feld benötigt wird, der mehr als 255 bzw. 191 Zeichen umfasst, dann ist der Einsatz eines Einweg-Prüfsummenalgorithmus mit geringer Kollisionswahrscheinlichkeit wie MD5 oder SHA1 als Option zu prüfen.

Kollation

Die Kollation legt fest, wie Zeichen miteinander verglichen werden. Sie gilt nicht nur für Suchanfragen, sondern auch für die Berechnung von Indexschlüsseln. Praktisch bedeutet das, dass bei Verwendung einer Kollation, die z.B. Umlaut und Grundbuchstaben gleich behandelt, für die Werte 'zahlen' und 'zählen' identische Indexschlüssel berechnet werden.

mysql> create database datenbank;
mysql> use datenbank;
mysql> create table tabelle ( text varchar(100) collate utf8_unicode_ci );
mysql> insert into tabelle (text) values ('zahlen'), ('zählen');
mysql> select count(*) from tabelle where text = 'zahlen';
+----------+
| count(*) |
+----------+
| 2 |
+----------+
1 row in set (0.01 sec)
mysql> create unique index idx_text on tabelle (text);
ERROR 1062 (23000): Duplicate entry 'zahlen' for key 'idx_text'

Gleiches gilt für die Groß- und Kleinschreibung:

mysql> delete from tabelle;
mysql> insert into tabelle (text) values ('zahlen'), ('Zahlen');
mysql> create unique index idx_text on tabelle (text) ;
ERROR 1062 (23000): Duplicate entry 'zahlen' for key 'idx_text'

Wenn ein UNIQUE Index verwendet werden soll aber Datenbankbenutzer auch komfortabel suchen können sollen, dann gibt es mindestens zwei Möglichkeiten: Konversion zur Abfragezeit oder Verwendung einer gesonderten Spalte.

Konversion zur Abfragezeit

mysql> alter table tabelle change text text varchar(100) collate utf8_bin ;
mysql> select count(*) from tabelle where 'zahlen' = text;
+----------+
| count(*) |
+----------+
| 1 |
+----------+
1 row in set (0.00 sec)
mysql> select count(*) from tabelle where 'zahlen' = text collate utf8_unicode_ci;
+----------+
| count(*) |
+----------+
| 2 |
+----------+
1 row in set (0.00 sec)

Verwendung einer gesonderten Spalte

Eine Spalte text mit der Kollation utf8_bin wird für den UNIQUE-Index verwendet, eine Spalte text_suche mit der Kollation utf8_german2_ci für die Suche.

mysql> drop table tabelle;
mysql> create table tabelle ( text varchar(100) collate utf8_bin; text_suche varchar(100) collate utf8_german2_ci );
mysql> insert into tabelle (text, text_suche) values ('zahlen', 'zahlen'), ('zählen', 'zählen');
mysql> select count(*) from tabelle where 'zahlen' = text;
+----------+
| count(*) |
+----------+
| 1 |
+----------+
1 row in set (0.00 sec)
mysql> select count(*) from tabelle where 'zahlen' = text_suche;
+----------+
| count(*) |
+----------+
| 2 |
+----------+
1 row in set (0.00 sec)

Es ist abhängig vom Anwendungsbereich, welche der Möglichkeiten sinnvoll ist. Die Konversion zur Abfragezeit spart Speicherplatz auf Kosten der Rechenleistung, die Verwendung einer gesonderten Spalte spart Rechenleistung auf Kosten von Speicherplatz.

Umwandlung von Altdaten

Bei der Umwandlung von Altdaten sind in jedem Fall die verschiedenen Einstellungsmöglichkeiten der Zeichenkodierung zu beachten Schwartz, Zaitsev und Tkachenko 2012, S. 298ff. In der Regel werden die Zeichen "on the fly" bei einem Datenbankzugriff in die verbindungsspezifische Kodierung gewandelt. Dadurch ist nicht ohne weiteres möglich, festzustellen, wie ein Zeichen tatsächlich in der Datenbank gespeichert ist. Um das festzustellen kann der Text noch während der Abfrage in eine hexadezimale Darstellung gewandelt werden.

mysql> select hex(text),text from tabelle;
+----------------+--------+
| hex(text) | text |
+----------------+--------+
| 7A61686C656E | zahlen |
| 7AC3A4686C656E | zählen |
+----------------+--------+
2 rows in set (0.00 sec)

Literatur

Schwartz, Baron, Peter Zaitsev, und Vadim Tkachenko. High performance MySQL: Optimization, backups, and replication. Beijing et al.: O’Reilly, 2012.

Today I Learned: ICC Farbprofil bei Imagekonversion mit ImageMagick erhalten

David Maus, 06.07.2018 · Twitter · Comment · Permalink

Im Zuge einer geplanten und längst überfälligen Modernisierung der Wolfenbütteler Digitalen Bibliothek spiele ich mit dem Gedanken, zukünftig auch die bei der Digitalisierung verwendeten ICC Farbprofile mit in den veröffentlichten JPEGs anzubieten. Alle anderen Metadaten aus den TIFF-Dateien sollen aber wie gehabt entfernt werden.

Nach einigem Rumwerkeln bin ich auf folgende Lösung gekommen:

magick convert 00001.tif 00001.icc
magick convert 00001.tif -strip -profile 00001.icc 00001.jpg

Im ersten Schritt schreibe ich das Farbprofil in eine temporäre Datei. Im zweiten entferne ich erst alle Metadaten und ergänze dann das extrahierte Profil.

<oXygen/>-Funktion "Transparenzwähler für Tags"

David Maus, 18.05.2018 · Twitter · Comment · Permalink

Gerade entdeckt: Über die Konfiguration der Werkzeugleiste lässt sich in <oXygen/> oXygen 18 unter dem Punkt "Quelle" das Werkzeug "Transparenzwähler für Tags" auswählen. Dieses Werkzeug ändert die Darstellung des XML-Quelltextes und erlaubt es, den Kontrast zwischen Markup und Inhalten zu ändern und damit aufgabenbezogen die Lesbarkeit von Markup bzw. Inhalt erhöhen, weil das jeweils andere durch Reduktion des Kontrasts in den Hintergrund rückt.

Ich kann mir vorstellen, dass das Ausblenden des Markups für die inhaltliche Korrektur tief getaggter Text hilfreich sein kann. Oder umgekehrt das Ausblenden des Inhalts für den Entwurf von XSL Transformationen.

Die unterschiedlichen Transparenzeinstellungen (oXygen 18, Windows)

Die unterschiedlichen Transparenzeinstellungen

HAB goes eduroam, Teil 2

David Maus, 02.05.2018 · Twitter · Comment · Permalink

Auch die Einstellungen auf einem Android-Smartphone Android 8.1 sind überschaubar und können ohne Zusatzanwendung vorgenommen werden. Im Verbindungsdialog folgende Einstellungen auswählen:

EAP-Methode
PEAP
Phase 2-Authentifizierung
MSCHAPV2
CA-Zertifikat
Systemzertifikat verwenden
Domain
radius1.hab.de
Identität
maus@hab.de
Anonyme Identität
anonymous@hab.de

A quick one: Use Metafacture to create a list of records with cataloging errors

David Maus, 24.04.2018 · Twitter · Comment · Permalink

Some records in our library catalog contain an error that causes trouble when we distribute catalog data to national aggregators like the Zentrales Verzeichnis Digitalisierter Drucke (ZVDD) central access point to printed works from the 15th century up to today, digitized in Germany. The catalogers made a typo and didn't separate the name of the publisher and the place of publication.

Metafacture is a really helpful suite of tools when working with a comparatively large set of records in a somewhat unwieldy format. The following Metamorph transformation runs over a dump of our catalog and outputs a list that contains the record's primary key Pica production number, PPN, an indicator of the resource type, and the erroneous publication statement.

<metamorph xmlns="http://www.culturegraph.org/metamorph" version="1" entityMarker="$">
<rules>

<!-- Titel, bei denen Verlag und Ort nicht getrennt sind -->
<!-- Bsp. http://uri.hab.de/instance/proxy/opac-de-23/391719076.xml -->
<!-- hier 033A $p[Strassburg: Prüss] -->
<combine name="" value="${ppn} - ${mat} - ${fehler-verlag}">
<data source="003@$0" name="ppn"/>
<data source="002@$0" name="mat">
<substring start="0" end="1"/>
</data>
<data source="033A$p" name="fehler-verlag">
<regexp match=".+: .+" format="${0}"/>
<normalize-utf8/>
</data>
</combine>

</rules>
</metamorph>

If the subfield p of the field 033A matches the specified regular expression, and both the subfield 0 of field 003@ and subfield 0 of field 002@ are present, combine these three fields to an unnamed output entity. Because 002@ and 003@ are always present, the combine acts as a filter and generates an output entity only if the erroneous 033A is detected.

I run this morph with a simple flux pipeline.

default src = "s:\\edoc\\ed000242\\2017\\20170429-title.pp";
default dst = "stdout";

src|
open-file|
as-records|
decode-pica|
morph(FLUX_DIR + morph)|
encode-literals|
write(dst);

Turns out that only 678 of appr. 1.2 million records or 0.06% are affected. This speaks volumes for the dedication of our staff and makes the problem managable.

Quick'n'dirty: Compile XSL-FO to PDF from Emacs

David Maus, 16.04.2018 · Twitter · Comment · Permalink

Execute Apache FOP from within Emacs and compile current file to PDF (or any other supported output formats).

;;; xslfo-mode.el --- Minor mode for working with XSL-FO files

(defvar xslfo-fop-outspec '(("pdf" . "pdf")
("rtf" . "rtf")
("txt" . "txt")
("at" . "xml")
("if" . "xml")
("png" . "png")
("tiff" . "tiff")
("ps" . "ps")
("pcl" . "pcl")
("pdfa1b" . "pdf"))
"Apache FOP output formats.")

(define-minor-mode xslfo-mode
"Minor mode for working with XSL-FO files."
:init-value nil
:lighter "XSL-FO"
:keymap `((,(kbd "C-c C-c") . xslfo-compile)))

(defmacro xslfo-fop-outspec-extension (name)
`(or (cdr (assoc ,name xslfo-fop-outspec)) "bin"))

(defun xslfo-compile (&optional arg)
(interactive "P")
(let* ((outspec (if arg (completing-read "Output format: " (mapcar #'car xslfo-fop-outspec) #'stringp t (caar xslfo-fop-outspec))
"pdf"))
(srcfile (or buffer-file-name (error "Buffer not visiting a file")))
(outfile (concat (expand-file-name (file-name-sans-extension srcfile)) "." (xslfo-fop-outspec-extension outspec)))
(bufname (concat "*XSL-FO: " outfile "*")))
(with-current-buffer (get-buffer-create bufname)
(erase-buffer)
(when (= 0 (apply #'call-process (executable-find "fop") nil t t (list (concat "-" outspec) outfile srcfile)))
(message "Wrote %s" outfile)
(display-buffer bufname)))))

(provide 'xslfo-mode)

;;; xslfo-mode.el ends here

Identitätstransformation in XSLT 3.0

David Maus, 11.04.2018 · Twitter · Comment · Permalink

Eine Identitätstransformation ist bei meiner Arbeit oft Ausgangspunkt von inkrementellen Änderungen einer XML-Datei. Während man in XSLT 2.0 und XSLT 1.0 noch ein eigenes Template schreiben musste, dass rekursiv alle Knoten des Quelldokuments kopiert, so kürzt sich die Identitätstransformation in XSLT 3.0 auf eine Zeile zusammen.

Identitätstransformation in XSLT 3.0

<xsl:transform version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:mode on-no-match="shallow-copy"/>
</xsl:transform>

Das on-no-match wählt die integrierten Verarbeitungsregeln built-in template rules aus, die für den unbenannten Modus unnamed mode angewendet werden. In 3.0 stehen sechs verschiedene Regelsätze zur Verfügung.

text-only-copy
Nur der Text des Eingabedokuments wird in das Ausgabedokument kopiert.
Dieser Modus entspricht den integrierten Verarbeitungsregeln der Vorgängerversionen.
deep-copy
Knoten werden unverändert und einschließlich ihrer Kindknoten in das Ausgabedokument geschrieben. Verarbeitungsregeln werden nicht auf die Kindknoten angewendet.
shallow-copy
Knoten werden unverändert in das Ausgabedokument geschrieben. Verarbeitungsregeln werden auf die Kindknoten angewendet.
deep-skip
Knoten werden ignoriert. Verarbeitungsregeln werden nicht auf die Kindknoten angewendet.
shallow-skip
Knoten werden ignoriert. Verarbeitungsregeln werden auf die Kindknoten angewendet.
fail
Die Verarbeitung wird abgebrochen.

Hallo XSLT 3.0

David Maus, 29.03.2018 · Twitter · Comment · Permalink

Gestern hatte ich erstmals die Gelegenheit, einige der Neuerung in XSLT 3.0 nutzen zu können. Die (selbstgestellte) Aufgabe bestand darin, die RDF/XML-kodierten Metadaten zu meiner Person in JSON-LD umzuwandeln.

Im Zentrum stand die mit XSLT 3.0 eingeführte native Verarbeitung von JSON. Die Funktionen json-to-xml und xml-to-json wandeln JSON in XML bzw. umgekehrt XML in JSON. Die Spezifikation definiert eine XML-Repräsentation, die Ausgangs- bzw. Endpunkt der Umwandlung ist.

Die Hauptaufgabe meiner Transformation besteht darin, die in RDF/XML kodierten Daten in die XML-Repräsentation von JSON umzuwandeln. Diese Umwandlung ist straight forward: Ich erstelle eine json:map, die ich mit Schlüssel-Wert-Paaren der ausgewählten Informationen befülle.

Da ich die Umwandlung im Stile des push processing Kay, Michael. XSLT 2.0 and XPath 2.0, 4th Edition. Indianapolis, IN: Wiley Publishing, 2008. S. 74ff schreibe, muss ich sie in zwei Schritten vornehmen. Der erste Schritt erstellt Einträge in der json:map ohne gleiche Eigenschaften zu gruppieren.

Also aus

<foaf:Person rdf:about="http://dmaus.name#me">
<owl:sameAs rdf:resource="http://orcid.org/0000-0001-9292-5673"/>
<owl:sameAs rdf:resource="https://orcid.org/0000-0001-9292-5673"/>
<owl:sameAs rdf:resource="http://d-nb.info/gnd/1141920794"/>

</foaf:Person>

und

<xsl:template match="owl:sameAs">
<json:string key="sameAs">{@rdf:resource}</json:string>
</xsl:template>

wird im ersten Schritt

<json:map>
<json:string key="@context">http://schema.org</json:string>
<json:string key="@type">Person</json:string>
<json:string key="@id">http://dmaus.name#me</json:string>
<json:string key="sameAs">http://orcid.org/0000-0001-9292-5673</json:string>
<json:string key="sameAs">https://orcid.org/0000-0001-9292-5673</json:string>
<json:string key="sameAs">http://d-nb.info/gnd/1141920794</json:string>

</json:map>

Der zweite Schritt gruppiert gleiche Eigenschaften in ein json:array.

<xsl:template name="normalize">
<xsl:param name="json-map" required="true"/>
<json:map>
<xsl:for-each-group select="$json-map/*" group-by="@key">
<xsl:choose>
<xsl:when test="count(current-group()) eq 1">
<xsl:sequence select="current-group()"/>
</xsl:when>
<xsl:otherwise>
<json:array key="{current-grouping-key()}">
<xsl:for-each select="current-group()">
<xsl:copy>
<xsl:sequence select="node()"/>
</xsl:copy>
</xsl:for-each>
</json:array>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</json:map>
</xsl:template>

Das Ergebnis des zweiten Schritts kann ich dann mit xml-to-json umwandeln und in meine Webseite einfügen.

<xsl:template match="foaf:Person[@rdf:about = $me]">
<xsl:variable name="json-ld">
<xsl:apply-templates select="." mode="json.header"/>
</xsl:variable>

<script type="application/ld+json">
<xsl:value-of select="xml-to-json($json-ld)"/>
</script>

</xsl:template>

Merke, dass ich im ersten Schritt auch ein weiteres Feature von XSLT 3.0 benutze. Mit der Einführung von text value templates wurde das Konzept der bereits in XSLT 1.0 definierten attribute value templates verallgemeinert. Wenn die Expansion von text value templates aktiviert ist, dann lassen sich auch in Elementinhalten XPath-Ausdrücke in geschweifte Klammern fassen, die während der Transformation mit dem Ergebnis des XPath-Ausdrucks ersetzt werden.

PDF-Lesezeichen und Inhaltsverzeichnis mit XSL-FO

David Maus, 21.03.2018 · Twitter · Comment · Permalink

Um praktische Erfahrungen mit XSL-FO zu sammeln, erstelle ich aus den im Projekt Selbstzeugnisse der Frühen Neuzeit in der Herzog August Bibliothek publizierten Daten einfache PDF-Dateien.

Heute habe ich mich mit dem Erstellen von PDF-Lesezeichen beschäftigt. Im PDF der Edition sollen zusätzlich zum Inhaltsverzeichnis auch PDF-Lesezeichen enthalten sein, mit denen der Benutzer im Dokument navigieren kann.

Da Lesezeichen und Inhaltsverzeichnis die selbe Information abbilden, habe ich ein Template geschrieben, das die benötigten Informationen in einem Zwischenformat zusammenstellt. Der Einfachheit halber habe ich das TEI-Vokabular für die Struktur der Liste gewählt. Für jeden Eintrag wird ein item erzeugt, in dessen corresp das Sprungziel abgelegt wird. Das Label des Eintrags wird in einem label angegeben.

<xsl:template name="inhaltsverzeichnis-einträge">
<list>
<xsl:for-each select="text/front//div/head[count(ancestor::div/head) eq 1]">
<item corresp="{generate-id()}">
<label><xsl:value-of select="normalize-space()"/></label>
</item>
</xsl:for-each>
<item corresp="diarium">
<label>Ephemerides. Sive Diarium (1594-1635)</label>
<list>
<xsl:for-each select="text/group/text/body">
<item corresp="{generate-id()}">
<label>
<xsl:value-of>
<xsl:value-of select="format-date((.//div/head/date[1]/@when)[1], '[Y]')"/>
<xsl:text> (</xsl:text>
<xsl:value-of select="format-date((.//div/head/date[1]/@when)[1], '[D01].[M01].')"/>
<xsl:text>–</xsl:text>
<xsl:value-of select="format-date((.//div/head/date/@when)[last()], '[D01].[M01].[Y]')"/>
<xsl:text>)</xsl:text>
</xsl:value-of>
</label>
</item>
</xsl:for-each>
</list>
</item>
<item corresp="register">
<label>Register</label>
</item>
</list>
</xsl:template>

Diese Inhaltsangaben werden nun durch je ein Template für Lesezeichen und Inhaltsverzeichnis formatiert. Die Hierarchie des Inhalts bilde ich im Inhaltsverzeichnis durch Einrückung der Einträge an.

<xsl:template match="item" mode="inhaltsverzeichnis">
<fo:block margin-left="{(count(ancestor::list) - 1)}em">
<fo:basic-link internal-destination="{@corresp}">
<xsl:value-of select="label"/>
<fo:inline>
<fo:leader leader-pattern="dots"/>
</fo:inline>
<fo:page-number-citation ref-id="{@corresp}"/>
</fo:basic-link>
</fo:block>
<xsl:apply-templates mode="#current"/>
</xsl:template>
<xsl:template match="item" mode="lesezeichen">
<fo:bookmark internal-destination="{@corresp}">
<fo:bookmark-title>
<xsl:value-of select="label"/>
</fo:bookmark-title>
<xsl:apply-templates mode="#current"/>
</fo:bookmark>
</xsl:template>

Das Ergebnis ist ganz passabel!

Screenshot Lesezeichen im PDF

Provide a random UUID to XSLT with XProc <p:uuid>

David Maus, 19.02.2018 · Twitter · Comment · Permalink

Create a step that acts as source of the UUID. The step provides an XML document with a single uuid-element at its primary output port result. The uuid has a single attribute value which contains the UUID as value.

<p:uuid version="4" match="@value" name="uuid-source">
<p:input port="source">
<p:inline>
<uuid value=""/>
</p:inline>
</p:input>
</p:uuid>

Connect the step to the p:with-param inside the p:xslt step.

<p:xslt>

<p:with-param name="uuid" select="/uuid/@value">
<p:pipe step="uuid-source" port="result"/>
</p:with-param>

</p:xslt>

Done.

Rückblick XML Prague 2018

David Maus, 12.02.2018 · Twitter · Comment · Permalink

XML Prague stand dieses Jahr für mich im Zeichen der Qualitätssicherung. Am unconference day hat Matthieu Ricaud-Dussarget eine Sammlung von Schematron-Regeln vorgestellt, mit dem in seinem Team XSLT-Stylesheets auf problematische Konstruktionen und die Einhaltung von Konventionen geprüft werden. Im Anschluss trafen sich bestehende und prospektive Nutzer von XSpec, einem Testframework für XSLT, XQuery und Schematron. Ich habe im letzten Jahr damit begonnen, einige Transformationen und Schemata mit XSpec abzusichern und kann es nur weiterempfehlen.

Der Vortrag Using Maven with XML Projects von Christophe Marchand and Matthieu Ricaud-Dussarget am Folgetag hat mich (wieder einmal) daran erinnert, mich näher mit continuous integration zu beschäftigen. Kommt mir zwar vor wie overkill, aber hey: Vielleicht ist das Aufsetzen eines CI-Systems einfacher, als ich denke?!

Letztlich am Samstag der XSLT-Checker der data2type GmbH. Ein System, mit dem sich Transformationsregeln ermitteln lassen, die in einem XSLT-Stylesheet nicht mehr verwendet werden. Auch hier ein im Grunde recht einfaches Prinzip: Das betreffende Stylesheet wird so modifiziert, dass es für jede Regeln eine Nachricht ausgibt, wenn sie greift. Dann wird eine repräsentatitve Menge von Quelldateien transformiert und am Ende geschaut, welche Regeln keine Nachricht produziert haben.

Darüber hinaus war allein der Vortrag von Hans-Juergen Rennau den Besuch wert. Der Codename SHAX steht für die Idee einer XML und RDF übergreifenden Modellierungssprache, mit der sich Beschränkungen zulässiger Eigenschaften von Informationsobjekten ausdrücken lassen. Ein in SHAX beschriebenes Modell lässt sich in Regeln für ein XML Schema, ein JSON Schema oder eine RDF Shape Expression (SHACL) übersetzen. Informationen können dann in den korrespondierenden Formaten (XML, JSON oder RDF) produziert, konsumiert und vor allem validiert werden.

Am Ende des Projektes Emblematica Online sind wir genau auf diesen Punkt gestoßen: Wir publizieren unsere Embleme als "kanonisches" RDF/XML, dass wir mit einem Schema beschreiben. In der (noch fälligen) Dokumentation der publizierten Metadaten würden wir genau die selben Angaben machen. Zwar ist das Projekt vorbei, doch wenn ich RDF Shape Expressions formulieren wollen würde, dann müsste ich die selben Angaben erneut wiederholen. SHAX to the rescue?

Letztlich bot der letzte Konferenztag natürlich auch die Gelegenheit, den 20. Geburtstag der XML-Spezifikation zu feiern. Stilecht mit einer kurzen Aufführung des XML-Tanzes.

Oh, und save the date: Markup UK am 9. und 10. Juni 2018 in London.

XML Prague – Statusupdate

David Maus, 08.02.2018 · Twitter · Comment · Permalink

XProc.Next Working Group

Die ersten zwei Tage meines Arbeitsprogramms in Prag sind vorüber und mein Eindruck ist, dass die XProc.Next-Arbeitsgruppe wieder einen großen Schritt in Richtung XProc 3.0 gemacht hat. Wenn alles gut geht, dann werden wir eine Vorabversion (Candidate Release) der Spezifikation im Juni veröffentlichen.

Heute: unconference day. Geplant ist der Besuch der beiden Workshops zu paged media sowie des Schematron und das XSpec Anwendertreffen. Der Workshop "XML and Emacs" fällt leider aus.

XML Prague 2018

David Maus, 11.01.2018 · Twitter · Comment · Permalink

Auch dieses Jahr zieht es mich im Februar gen Osten zur diesjährigen XML Prague.

Neben dem Meeting der XProc Community Group am 6. und 7. freue ich mich besonders auf den Workshop "XML and Emacs" am unconference day und die Vorträge von Hans-Juergen Rennau (Combining graph and tree: writing SHAX, obtaining SHACL, XSD and more), Bert Willems (Assisted Structured Authoring using Conditional Random Fields) und dem wunderbar eloquenten Steven Pemberton (Form, and Content).

Liste der Workshops und Sessions der XML Prague 2018