David Maus M. A.

Digital Humanities Publishing

XProc Step by Step: Check text content after structural transformations

David Maus, 25.04.2019 · Permalink

When working with TEI documents stemming from a Word-to-XML-conversion I usually perform some structural modifications like joining tei:hi elements with identical rend attribute if they are immediate siblings. Although I consider my XSLT good enough and use XSpec to test the merging algorithm, I like to verify that no text content was lost for good measure.

One tried and true method of doing this is dumping the text nodes of the document before and after the transformation, and then compare both. Because I only want to know if the text is the same, comparing a SHA1 checksum is sufficient.

Yesterday I finally sat down and wrote an XProc 1.0 step that does exactly this.

Step 1: Calculate the checksum

The first step dmaus:content-checksum reads a document and adds the attribute dmaus:checksum containing the SHA1 checksum of the document's text content to the outermost element.

dmaus:content-checksum
<p:declare-step type="dmaus:content-checksum" name="content-checksum">  <p:documentation>    Add @dmaus:checksum attribute containing the SHA1 checksum of the document content to the outermost element.  </p:documentation>  <p:input  port="source"/>  <p:output port="result"/>  <p:add-attribute attribute-name="dmaus:checksum" attribute-value="" match="/*"/>  <p:hash algorithm="sha" version="1" match="@dmaus:checksum">    <p:with-option name="value" select="/"/>    <p:input port="parameters">      <p:empty/>    </p:input>  </p:hash></p:declare-step>

Step 2: Compare checksums

The second step dmaus:check-text-content-match has two input ports source and other. It calculates the checksum for the documents appearing on either port and compares the two. If the checksums differ, the step raises an error. For convenient use in a pipeline the document from source is passed through to the primary output port.

dmaus:check-text-content-match
<p:declare-step type="dmaus:check-text-content-match" name="check-text-content-match">  <p:documentation>    Signal an error if the content of the document appearing on the 'source' port differs from the content of the    document appearing on the 'other' port.  </p:documentation>  <p:input  port="source"/>  <p:input  port="other"/>  <p:output port="result"/>  <dmaus:content-checksum name="checksum-source">    <p:input port="source">      <p:pipe step="check-text-content-match" port="source"/>    </p:input>  </dmaus:content-checksum>  <dmaus:content-checksum name="checksum-other">    <p:input port="source">      <p:pipe step="check-text-content-match" port="other"/>    </p:input>  </dmaus:content-checksum>  <p:group>    <p:variable name="source" select="/*/@dmaus:checksum">      <p:pipe step="checksum-source" port="result"/>    </p:variable>    <p:variable name="other" select="/*/@dmaus:checksum">      <p:pipe step="checksum-other" port="result"/>    </p:variable>    <p:choose>      <p:when test="$other ne $source">        <p:error code="dmaus:content-mismatch">          <p:input port="source">            <p:inline>              <message>The content of the two documents does not match</message>            </p:inline>          </p:input>        </p:error>      </p:when>      <p:otherwise>        <p:identity>          <p:input port="source">            <p:pipe step="check-text-content-match" port="source"/>          </p:input>        </p:identity>      </p:otherwise>    </p:choose>  </p:group></p:declare-step>

Step 3: Use the step

Both steps are defined in a library I can import in my pipeline. In this contrived example I connect the step dmaus:check-text-content-match to the result of a p:xslt that implements the structural modification and the original document appearing on the pipeline's source port.

Example pipeline
<p:declare-step version="1.0" name="main"                xmlns:dmaus="tag:dmaus@dmaus.name,2019:XProc"                xmlns:p="http://www.w3.org/ns/xproc">  <p:input  port="source"/>  <p:output port="result"/>  <p:import href="library.xpl"/>  <p:xslt name="structural-modification">    <p:input port="stylesheet">      <p:document href="…"/>    </p:input>  </p:xslt>  <dmaus:check-text-content-match>    <p:input port="source">      <p:pipe step="structural-modification" port="result"/>    </p:input>    <p:input port="other">      <p:pipe step="main" port="source"/>    </p:input>  </dmaus:check-text-content-match></p:declare-step>

Release of SchXslt version 1.1

David Maus, 16.04.2019 · Permalink

I am happy to announce the release of version 1.1 of SchXslt, an XSLT-based Schematron processor.

You can download this version SchXslt from its project page. Developers using SchXslt in a Java-based project can add or update the Maven artifact name.dmaus.schxslt.schxslt to version 1.1.

Enhancements

Callback API

SchXslt follows the footsteps of the Skeleton implementation and lets you customize the reporting output. The callback API defines named templates that are called to create parts of the validation stylesheet that report on active patterns, fired rules, failed asserts and successful asserts.

A documentation of the API can be found in the project's wiki.

Java classes

The SchXslt Maven artifact also provides Java classes implementing Schematron validation.

Ant Task

The Ant task is updated to use version 1.1 of SchXslt.

Fixed bugs

Release of SchXslt, a new XSLT-based Schematron processor v1.0

David Maus, 22.02.2019 · Permalink

I am happy to announce that I released version 1.0 of SchXslt.

SchXslt is a conforming open-source Schematron processor implemented entirely in XSLT. It operates as a three-stage transformation process that translates a Schematron to an XSLT validation stylesheet. This stylesheet outputs a validation report in the Schematron Validation Report Language (SVRL) when applied to an instance document.

SchXslt utilizes features of XSLT 2.0 to improve the validation and is well tested against the ISO specification.

SchXslt is also available as a Maven artifact to ease integration into Java-based applications (name.dmaus.schxslt.schxslt) and as an Ant task to perform Schematron validation with SchXslt.

Both, SchXslt and SchXslt Ant, are released under the terms of the MIT license.

Status update on SchXslt

David Maus, 13.02.2019 · Permalink

I just published release candidate 5 of SchXslt, my XSLT-based Schematron processor. My goal is to publish a final version 1.0 at the end of February.

Version 1.0 will suffer from the same defect as the skeleton implementation with regards to equally named global, phase, and pattern variables. SchXslt will terminate if a global variable has the same name as a phase variable, a phase variable has the same name as a pattern variable, or a global variable has the same name as a pattern variable.

Given the fact that the skeleton has the same defect for more than 10 years, it should pose no immediate problem for people switching to SchXslt.

Today I Learned: ICC Farbprofil bei Imagekonversion mit ImageMagick erhalten

David Maus, 06.07.2018 · Permalink

Update 2019-02-12: Ohne Farbprofile keine farbechte Darstellung! Der Effekt ist bei farbigen Objekten augenfällig.

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:

ICC Profile mit ImageMagick erhalten
magick convert 00001.tif 00001.iccmagick 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.

My presentation @ XML Prague 2019

David Maus, 08.02.2019 · Permalink

Update 2019-02-11: The recording is available, too.

The slides from my talk at this year's XML Prague are now available. Slides 15 and 16 discuss the conditions under which xsl:next-match based approaches can run in a single mode.

Zotero Standalone, Revisited

David Maus, 29.12.2018 · Permalink

Letztes Jahr habe ich das Setup beschrieben, mit dem ich auf meinen Systemen Zotero ohne Abhängigkeit von DBUS und GTK+ 3 installiere. Ein Jahr später habe ich die Weihnachtstage genutzt, um das Setup mit Hilfe von Vagrant und Ansible weitgehend zu automatisieren. Ein einfaches vagrant up oder vagrant provision reicht jetzt aus, um eine aktuelle Version von Zotero mit meiner Firefox-Runtime zu erstellen.

Nota bene: Ich verwende Pale Moon anstelle des offiziellen Firefox und muss daher die Namen des Installationsverzeichnisses und der ausführbaren Binärdateien anpassen.

Today I Learned: Normalisierung von Attributwerten

David Maus, 13.11.2018 · Permalink

Ein XML-Prozessor ist ein Programm, dass XML-Dokument gemäß der XML-Spezifikation verarbeitet. Wenn ein solcher Prozessor ein XML-Dokument einliest, dann werden die Werte in Attributen normalisiert, d.h. in eine standardisierte Form überführt. Die Regeln dieser Normalisierung sind in der XML-Spezifikation unter dem Punkt Attribute Value Normalization aufgeführt.

  1. Numerische Zeichenreferenzen werden durch das referenzierte Zeichen ersetzt.

    Der Prozessor ersetzt die Zeichenreferenz &#xe4; durch das referenzierte Zeichen 'ä' (U+00E4, LATIN SMALL LETTER A WITH DIAERESIS). Eine Anwendung, die mit dem so prozessierten XML-Dokument arbeitet "sieht" nie die Referenz, sondern immer nur das Zeichen.

  2. Entitätsreferenzen werden durch die referenzierte Entität ersetzt.

    Ist in der Dokumenttypdeklaration die Entität 'bspw.' als Zeichenfolge 'beispielsweise' deklariert, dann ersetzt der Prozessor jede im Attributwert vorkommende Referenz durch die entsprechende Zeichenfolge.

  3. Die Leerraumzeichen U+000D (CARRIAGE RETURN), U+000A (LINE FEED) und U+0009 (CHARACTER TABULATION) werden durch je ein Leerzeichen (U+0020, SPACE) ersetzt.

    Eine Ausnahme bildet die Abfolge von U+000D (CARRIAGE RETURN) und U+000A (LINE FEED), wenn sie aus einer deklarierten Entität stammt. Diese Abfolge von zwei Zeichen wird dann durch ein einzelnes Leerzeichen (U+0020, SPACE) ersetzt.

  4. Alle anderen Zeichen werden unverändert übernommen.

  5. Für Attribute deren Typ als Name, Nmtoken oder eine Liste eines dieser Typen definiert ist gelten weitere Regeln.

    In diesen Attributen können Leerzeichen allenfalls als Trennzeichen in einer Liste von Werten auftreten. Deshalb werden Leerzeichen am Anfang und am Ende des Attributwertes entfernt und aufeinanderfolgende Leerzeichen durch genau ein Leerzeichen ersetzt.

Notabene: Attribute, für die kein Datentyp definiert oder für die keine Datentypdefinition gelesen wurde, werden mit dem Datentyp CDATA prozessiert und unterliegen damit nicht dem fünften Normalisierungsschritt.

Praktisch bedeutet das, dass ich mich in der Arbeit mit XML nur auf die Normalisierungsschritte 1 bis 4 verlassen kann, da in den seltensten Fällen Datentypen definiert oder Datentypdefinitionen via Schemata gelesen werden. Glücklicherweise gibt es die XPath-Funktion normalize-space(), die den fünften und für die Arbeit mit Wertelisten notwendigen Normalisierungsschritt durchführt.

Sprich: tokenize(normalize-space(@ADMID), ' ') anstatt tokenize(@ADMID, ' ')

XProc Step by Step: Implementing a DOCX to TEI step

David Maus, 20.08.2018 · 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.

docx2tei.xpl
<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>