Mit URIs weg von ausgetretenen Pfaden

Ein wesentlicher Baustein für die modulare Architektur unseres XProc-Frameworks transpect ist die Verwendung von URIs für die Repräsentation von Pipelines, Stylesheets und Datensätzen. Auch andere Frameworks wie das von Romain Deltour auf der XML Prague 2013 vorgestellte DAISY Pipeline Framework und Florent Georges EXPath-Packaging System machen sich dieses Konzept zu eigen. URIs können die Wiederverwendbarkeit von Bibliotheken vereinfachen und das Handling von XML-Dokumenten und externen Ressourcen erleichtern.

Uniform Resource Identifier

Externe Ressourcen wie Bilder, Videos oder andere XML-Dokumente werden häufig als Pfad relativ zur XML-Datei angegeben. Referenzen können aber auch mittels Uniform Ressource Identifiers (URI) ausgedrückt werden. URIs bieten ein einfaches Schema um Ressourcen zu beschreiben. Nach RFC3986 müssen URIs immer mit einem Schema-Namen gefolgt von einem Doppelpunkt beginnen. Danach folgt eine Zeichenkette, welche die Ressource beschreibt. Welche Konventionen die nachfolgende Zeichenkette erfüllen muss, wird dann durch ein Schema beschrieben, welches mit dem Schema-Namen assoziiert ist.

http://www.xporc.net/2014/07/24/xproc-und-xml-catalogs
mailto:hi @ xporc.com
tel:+49-341-355356-143
urn:isbn:0201853922

Wie man sieht, entsprechen auch die aus dem Web bekannten Uniform Resource Locators (URL) der URI-Syntax. Die Referenzierung von Ressourcen im Web birgt aber auch Probleme. Enthält eine XML-Datei URL-Referenzen, kann die Verarbeitung davon abhängig sein, ob diese auch über das Netzwerk erreichbar sind. Dieses Problem besteht etwa bei XML-Dateien mit Dokumenttypdeklaration. Die Dokumenttypdeklaration gibt den Pfad zu einer DTD an, die der XML-Parser benötigt um Entitäten richtig aufzulösen. Das Auflösen einer DTD über das Web verzögert die Verarbeitung, sofern keine gefunden wird, kann die XML-Datei nicht geladen werden.

XML Catalogs

Um dieses Problem zu umgehen, bedient man sich XML Catalogs. Das Konzept von XML Catalogs beruht darauf, dass man für eine referenzierte Ressource eine andere Referenz in einem XML Catalog als Ersatz angibt. Die im Catalog referenzierte Ressource wird dann anstelle der Referenz in der XML-Datei geladen. Hier ist ein Beispiel für eine Dokumenttypdeklaration in einer XHTML-Datei, die durch einen Catalog zu einer lokalen Datei aufgelöst wird.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  (…)
</html>
<?xml version="1.0"?>
<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
 
  <system systemId="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
          uri="xhtml1-strict.dtd"/>
  
</catalog>

Das Einsatzgebiet von XML Catalogs muss sich aber nicht nur auf die Auflösung von DTDs beschränken. Auch Referenzen in XProc oder XSLT lassen sich damit auflösen. Das hat den Vorteil, dass benötigte Bibliotheken nicht immer an demselben Ort abgelegt werden müssen. Verwendet man statt eines Dateisystempfades eine abstrakte URI um eine Ressource zu identifizieren, muss man sich nur darum kümmern, dass die URI über den Catalog richtig aufgelöst wird.

Am Beispiel der transpect-Bibliothek word2tex lässt sich diese Technik leicht nachvollziehen. Die zentrale XProc-Pipeline konvertiert eine docx-Datei nach LaTeX und importiert dafür weitere transpect-Bibliotheken über deren URIs. Die URIs liegen in Form von HTTP-URLs vor, die aber nicht einer realen Web-Adresse entsprechen. Die URLs stellen lediglich eine virtuelle Repräsentation der Bibliothek dar, nicht mehr und nicht weniger.

Jede Bibliothek verfügt über einen eigenen zentralen XML Catalog, in dem die URL der Bibliothek bekannt gemacht wird. Dieser hat per Konvention immer denselben Pfad. Der XML Catalog der bei word2tex benötigten Bibliothek docx2hub liegt unter dem Pfad xmlcatalog/catalog.xml und weist die URL aus, über welche Ressourcen aus der Bibliothek geladen werden können.

<?xml version="1.0"?>
<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
    <rewriteURI uriStartString="http://transpect.le-tex.de/docx2hub/" 
                rewritePrefix="../"/>
</catalog>

Mit rewriteURI wird für der in uriStartString angegebene Anfang der URI auf einen relativen Pfad umgeschrieben. In diesem Fall werden URIs die mit http://transpect.le-tex.de/docx2hub/ beginnen, auf den darüberliegenden Ordner (vom Catalog aus gesehen) gemappt. Im zentralen XML Catalog von word2tex wird dieser Catalog dann einfach mit dem nextCatalog-Element verknüpft.

<?xml version="1.0" encoding="UTF-8"?>
<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
  <rewriteURI uriStartString="http://transpect.le-tex.de/word2tex" 
              rewritePrefix="../"/>
  <nextCatalog catalog="../docx2hub/xmlcatalog/catalog.xml"/>
  <!-- hier folgen weitere Einträge ... -->
</catalog>

Mit p:import werden in XProc externe Pipelines eingebunden. Word2tex importiert darüber die URI der Pipeline von docx2hub. Ein Catalog Resolver kümmert sich dann bei der Verarbeitung der Pipeline darum, die URIs richtig aufzulösen.

<?xml version="1.0" encoding="UTF-8"?>
<p:declare-step 
  xmlns:p="http://www.w3.org/ns/xproc"
  xmlns:c="http://www.w3.org/ns/xproc-step" 
  xmlns:docx2hub="http://www.le-tex.de/namespace/docx2hub"
  xmlns:word2tex="http://www.le-tex.de/namespace/word2tex"
  xmlns:xml2tex="http://www.le-tex.de/namespace/xml2tex"
  xmlns:hub="http://www.le-tex.de/namespace/hub"
  version="1.0"
  name="word2tex">
  <!-- Code für mehr Übersichtlichkeit auskommentiert -->
  <p:import href="http://transpect.le-tex.de/docx2hub/wml2hub.xpl"/>
  <!-- auskommentiert -->
  <docx2hub:convert name="docx2hub">
    <p:documentation>Converts DOCX to Hub XML.</p:documentation>
    <p:with-option name="docx" select="$docx"/>
    <p:with-option name="debug" select="$debug"/>
    <p:with-option name="debug-dir-uri" select="$debug-dir-uri"/>
  </docx2hub:convert>
  <!-- auskommentiert -->
</declare-step>

base URIs

Eine der Besonderheiten von XML ist, dass man den physischen Ort von XML-Dokumenten durch eine base URI repräsentieren kann. In XProc und XSLT lässt sich mit der XPath-Funktion base-uri() die base URI eines XML-Nodes ermitteln. Bei XProc steht dafür das Element p:add-xml-base zur Verfügung. Im Gegensatz zu base-uri() wird hier nicht einfach die base URI ermittelt und zurückgegeben, sondern als Wert des Attributs xml:base an das Wurzelelement angehängt.

Das xml:base-Attribut ist eine Empfehlung des W3C. Es repräsentiert den physischen Ort eines XML-Dokuments und soll das Auflösen von relativen Pfaden in Referenzen auf externe Dateien erleichtern. Enthält ein XML-Dokument eine base URI kann wie in dem Beispiel unten der Pfad des Bildes relativ zur base URI ermittelt werden.

<?xml version="1.0" encoding="UTF-8"?>
<html xmlns="http://www.w3.org/1999/xhtml"
xml:base="file:///C:/home/xporc/test.xhtml">
  <head>
    <title>This is my title</title>
  </head>
  <body>
    <p>
      <img src="images/bild.jpg"/> 
    </p>
  </body>
</html>

Die Bild-Referenz images/bild.jpg würde dann zu file:///C:/home/xporc/images/bild.jpg aufgelöst werden. Auf diese Weise ist man nicht gezwungen, die base URI vermittelt über Parameter oder Optionen durch den XProc-Code zu schleusen, da man sie mit p:add-xml-base direkt an das XML-Dokument anheften kann. Dabei muss die base URI nicht zwingend den tatsächlichen Ort des XML-Dokuments repräsentieren. In der Empfehlung des W3C heißt es sogar ausdrücklich:

The attribute xml:base may be inserted in XML documents to specify a base URI other than the base URI of the document or external entity

Eine base URI vom physischen Ort der XML-Datei zu entkoppeln kann mehrere Gründe haben. So kann es sein, dass die XML-Datei später zu einem anderen Bestimmungsort kopiert werden soll. Dann ließe sich der Bestimmungsort als base URI verwenden. Wenn man XML Catalogs verwendet, kann die base URI auch nur virtuell sein und während der Verarbeitung auf den im Catalog angegeben Pfad umgeschrieben werden.

Unterm Strich

Die über XML Catalogs vermittelte Repräsentation von XProc-Pipelines und XSLT-Stylesheets über URIs erleichtert maßgeblich die Modularisierung und Wiederverwendbarkeit von XProc-Anwendungen. Das Einrichten von XML Catalogs erzeugt zwar zunächst mehr Bürokratie. Dafür garantiert diese Methode mehr Flexibilität als der Import von Komponenten über statische Dateipfade. Die XProc-Implementierung von base URIs macht zudem das Auswerten von relativen Pfaden in XML-Dokumenten einfacher und ist bei Dateisystemoperationen nützlich. Es lohnt sich also Wege abseits ausgetretener (Datei-)Pfade zu beschreiten.

Kommentar schreiben

Hallo Martin,

erst Mal vielen Dank für den sehr interessanten Artikel! Dateipfade sind tatsächlich immer wieder ein Hindernis zu guter Modularisierung von Bibliotheken und XML Catalogs ist ein gutes Hilfsmittel dafür.

Was hier noch interessant wäre, wäre die Frage, wie der Catalog den typischen Engines der XML-Welt bekannt gemacht wird. Gerade beim Beispiel XProc/calabash ist das ja ziemlich kompliziert und nirgendwo ordentlich dokumentiert. Oder habe ich was übersehen?

Viele Grüße
Nico

Hi Nico,

Dankeschön :-). Ich wollte den Artikel allgemein halten und nicht zu sehr auf einen bestimmten XProc-Prozessor zuschneiden. Aus diesem Grund habe ich das Thema Resolver-Einrichtung außen vor gelassen.

Die Dokumentation von XML Calabash ist wirklich sehr knapp. Wenn man es sich einfach machen will, kann man unser Calabash-Repository mit integriertem Resolver via SVN auschecken:

svn co https://subversion.le-tex.de/common/calabash/

Ansonsten kann man sich natürlich alles auch selbst einrichten. Dafür muss man sich den XML Commons Resolver herunterladen und den Pfad in den Java Classpath einfügen. Beim Java Aufruf muss mit der Option Dxml.catalog.files der Pfad zum ersten XML Catalog gesetzt werden. Zudem muss man bei Calabash mit den Optionen -E und -U die Java-Klasse des Resolvers setzen. Ich hoffe dieser Beispiel-Aufruf ist hilfreich:

java \
   -cp "xmlresolver.jar:calabash.jar" \
   "-Dxml.catalog.files=catalog.xml" \
   -Dxml.catalog.staticCatalog=1 \
   com.xmlcalabash.drivers.Main  \
   -E org.xmlresolver.Resolver \
   -U org.xmlresolver.Resolver

Grüße, Martin