XML und InDesign II – Ein Skript hilft da, wo’s schmerzt?

Wie im vorangegangen Teil deutlich wurde, bleibt beim XML-Import in InDesign einiges auf der Strecke. Fußnoten, Querverweise, Indexeinträge und Hyperlinks werden nicht importiert. Auch verankerte Rahmen für Marginalien, Bilder und Boxen lassen sich nicht automatisch erstellen. Wer mit importiertem XML mehr als ein Plakat setzen möchte, ist also auf Alternativen angewiesen.

Dafür bietet sich InDesigns Skripting-Schnittstelle an. Mit ihr lassen sich eigene Skripte ausführen, die z. B. automatisch XML-Elemente in entsprechende Layoutobjekte umwandeln. InDesign unterstützt dafür JavaScript und je nach Plattform VBScript (Windows) oder AppleScript (Mac OSX). InDesign-Skripte gehören auch in professionellen Satzbetrieben zum festen Inventar, lassen sich damit doch viele Aufgaben automatisieren bzw. schmerzlich vermisste InDesign-Funktionen nachrüsten.

Nach dem XML-Import liegen Fußnoten, Indexeinträge usw. zunächst als einfacher Text vor. Um diese Strukturen via Skript in Layoutobjekte umzuwandeln, bieten sich zwei unterschiedliche Ansätze an. Einerseits kann man den XML-Elementen Formatvorlagen zuweisen und die Skripte auf die Formatvorlagen anwenden. Andererseits kann man mit Skripten auch direkt auf das zugrunde liegende XML zugreifen.

XML-Formatvorlagen-Abbildung

Beim ersten Ansatz würde man beim Import den entsprechenden XML-Elementen Formatvorlagen zuordnen. So würde man das Element footnote dem Zeichenformat Fussnote zuordnen:

InDesign XML-Fußnote entsprechendem Zeichenformat zuordnen

Diese Formatvorlagen dienen anschließend einem InDesign-Skript als Vorlage. Das Skript sucht nach Inhalten, die mit dem Zeichenformat Fussnote ausgezeichnet sind. An den Fundstellen wird eine InDesign-Fußnotenreferenz eingefügt. Das folgende Beispiel demonstriert ein einfaches Skript für diesen Zweck.

var myDoc = app.documents[0]
// über Rahmen iterieren
for (i = 0; i < myDoc.textFrames.length; i++) {
  var currentFrame = myDoc.textFrames[i];
  var footnoteArray = [];
  // über Zeichenketten mit Zeichenformat iterieren
  for (j = 0; j < currentFrame.textStyleRanges.length; j++) {
    var currentStyleRange = currentFrame.textStyleRanges[j];
    var prevStyleRange = currentFrame.textStyleRanges[j-1];
    // Fußnote vor Text mit Zeichenformat "Fussnote" einfügen  
    // und Text anschließend entfernen
    if(currentStyleRange.appliedCharacterStyle.name == "Fussnote"){
      var fnText = currentStyleRange.contents;
      var fNote = prevStyleRange.insertionPoints[-1].footnotes.add();
      fNote.insertionPoints[-1].contents = fnText;
      currentStyleRange.remove();
    }		
  }	
}

XML-Fußnote wurde von Skript zu InDesign-Fußnote umgewandelt

Unser Beispiel-Skript würde jedoch vielen Publikationen nicht standhalten. Eine Fußnote kann weitere Zeichenformate enthalten. Damit würde das Zeichenformat Fussnote nicht durchgängig den Inhalt des Elements footnote überspannen. Ausgehend vom Zeichenformat könnte man nicht mehr zweifelsfrei auf das XML-Element schließen. Da unser Skript für jede Instanz des Zeichenformats eine Fußnote erstellt, wären letztlich mehr Fußnoten in InDesign als im XML vorhanden.

Auch wenn Skripte in der freien Wildbahn weitaus komplexer als in unserem Beispiel sind, markiert die Abbildung von XML-Strukturen auf Formatvorlagen eine Grenze. Absatz- und Zeichenformate stellen zwei Ebenen für die Abbildung von XML-Markup dar. Tiefer verschachtelte XML-Strukturen lassen sich nicht auf diesen zwei Ebenen abbilden. Da der XML-Import zudem nicht die Generierung von verankerten Rahmen unterstützt, ist man mangels Objektformaten auch um ein InDesign-Element für XML-Container beraubt. Dennoch hat die Abbildung auf Absatz- und Zeichenformate einen unbestreitbaren Vorteil: Es ist das Handwerkszeug, auf das sich jeder Satzbetrieb versteht.

XML via Skript manipulieren

InDesign bietet verschiedene Möglichkeiten, das mitgeführte XML direkt via Skript zu manipulieren. Im Gegensatz zur Abbildung auf Formatvorlagen lassen sich damit auch komplexere Strukturen manipulieren. Zunächst kann man damit beginnen, den Vorgang des XML-Imports zu automatisieren. Statt sich durch InDesign-Dialoge zu klicken, nimmt das folgende kleine Skript die gewünschten Einstellungen vor und importiert im Anschluss eine XML-Datei.

var myDoc = app.documents[0];
var myXMLImportPreferences = myDoc.xmlImportPreferences;
myXMLImportPreferences.importCALSTables = true;
myDoc.importXML(File("/c/Users/Martin/article.xml"));

Per Skript lassen sich zudem auch automatisch Formatvorlagen zuordnen:

myDoc.xmlImportMaps.add(myDocument.xmlTags.item("title"),
myDoc.paragraphStyles.item("Titel"));

Das Skript unten registriert ein neues XML-Element mit dem Namen link und fügt es mit dem Text „http://www.xporc.net“ in das Dokument ein.

var myDoc = app.documents[0]
var hyperlinkTag = myDoc.xmlTags.add("link");
var hyperlinkElement = myDoc.xmlElements.item(0).xmlElements.add(hyperlinkTag);
hyperlinkElement.contents = "http://www.xporc.net";

Mit der seit CS4 eingeführten Methode evaluateXPathExpression sind zudem XPath-Abfragen möglich. Das Fußnotenskript ließe sich auf diese Weise so ergänzen, dass es Fußnoten nicht mehr anhand ihres Zeichenformats, sondern am XML-Element selbst erkennt und in echte InDesign-Fußnoten umwandelt.

var root = app.activeDocument.xmlElements.item("article");
// Namespace deklarieren
var nspart = []
nspart[0] = "dbk";
nspart[1] = "http://docbook.org/ns/docbook";
var ns = new Array(nspart)
// XPath auswerten und in Variable speichern
var footnotes = root.evaluateXPathExpression("//dbk:footnote", ns); 
// über Fussnoten iterieren
for (i = 0; i < footnotes.length; i++) {
    var currentElement = footnotes[i];
    var fnText = currentElement.contents;
    var fnCharRange = currentElement.parentStory.characters.itemByRange(currentElement.insertionPoints[1].index, currentElement.insertionPoints[-1].index);
    var fNote = currentElement.insertionPoints[0].footnotes.add();
    fNote.insertionPoints[-1].contents = fnText;
    fnCharRange.remove();
}

Regelbasierte XML-Manipulation

Seit InDesign CS3 gehören die sogenannten XML-Rules zum Funktionsumfang von InDesign. Damit lassen sich Regeln definieren, bei denen automatisch eine Aktion ausgelöst wird, sobald deren Bedingung auf das eingelesene XML zutrifft. Eine XML-Rule besteht aus einer XPath-Abfrage und einer Apply-Funktion, die ausgelöst wird, sobald die XPath-Abfrage ein Resultat zu Tage fördert. Um das Ganze zum Laufen zu bringen, muss man noch eine Skriptbibliothek einbinden, die Adobe selbst als „Glue Code“ betitelt. Diese Skriptbibliothek hilft bei der Interaktion mit dem eingebauten XML-Rules-Prozessor.

Im folgenden Skript kommt eine einfache XML Rule für unser Fußnotenbeispiel zum Einsatz. In unserer XML-Rule werden als XPath alle footnote-Elemente angegeben. In der Apply-Funktion sind die Anweisungen zum Einfügen der Fußnote gekapselt. Mit der Glue-Code-Funktion __processRuleSet() übergibt man an den XML-Rules-Prozessor einen XML-Schnipsel und ein oder mehrere XML-Rules.

#include "glue code.jsx"
var root = app.activeDocument.xmlElements.item("article");
// RuleSet definieren
var myRuleSet = new Array (new insertFootnote);
// XML Rule verarbeiten
__processRuleSet(root, myRuleSet);
// die XML Rule
function insertFootnote() {
  this.name = "insertFootnote";
  // XPath triggert Apply-Funktion
  this.xpath = "//footnote";
  // hier ist die Apply-Funktion
  this.apply = function(currentElement){
    var fnText = currentElement.contents;
    var fnCharRange = currentElement.parentStory.characters.itemByRange(currentElement.insertionPoints[1].index, currentElement.insertionPoints[-1].index);
    var fNote = currentElement.insertionPoints[0].footnotes.add();
    fNote.insertionPoints[-1].contents = fnText;
    fnCharRange.remove();
    return true;
  }
}

Für tiefgreifende XML-Transformationen sind XML-Rules ungeeignet. Ihr Zweck besteht eher in der Manipulation des Layouts auf Basis des mitgeführten XML. Durch XML Rules hervorgerufene Strukturänderungen können das Skript zum Absturz bringen, wenn beispielsweise erwartete Knoten zur Laufzeit entfernt oder verschoben werden. Daher empfiehlt Adobe für größere Umbauten auch XSLT.

In general, large-scale changes to the structure of an XML document are best done using an XSLT file to transform the document before or during XML import into InDesign.*

Der XML-Rule-Prozessor versteht leider nur eine limitierte Variante der ohnehin schon in die Jahre gekommenen Version 1.0 der XML-Abfragesprache. So kann man im XML nicht auf die Vorfahren (ancestor) eines Elements zugreifen oder boolesche Ausdrücke und Vergleichsoperatoren in Prädikaten verwenden.

In diesem Sinne liest sich der erste Satz unter der Überschrift „The Best Approach to Scripting XML in InDesign“ in Adobes Referenz nicht gerade wie eine Empfehlung.

You might want to do most of the work on an XML file outside InDesign, before you import the file into an InDesign layout. Working with XML outside InDesign, you can use a wide variety of excellent tools, such as XML editors and parsers.*

Nach dem Import ist vor dem Export

Aber Moment mal. Bedeutet XML-First nicht, dass wir das XML in InDesign importieren und nach dem Umbruch einfach wieder sauber exportieren? Trotz funktionaler Einschränkungen führt an Skripten kein Weg vorbei, wenn man auf den XML-Import von InDesign setzt. Das gilt leider auch für den XML-Export. Wenn man das mitgeführte XML in InDesign-Elemente umwandelt, verändert man zwangsläufig auch dessen Struktur. Unser Fußnotenskript gibt ein Beispiel: Es erstellt eine Fußnotenreferenz und verschiebt den Inhalt des Tags footnote in die erstellte Fußnote. Im Story-Editor scheint der Inhalt der Fußnote trotz der Umwandlung noch präsent zu sein.

Ansicht einer Fußnote im Story-Editor von InDesign

Nach dem XML-Export klafft eine Lücke zwischen dem Inhalt des Story-Editors und der exportierten XML-Datei. Der Fußnotentext fehlt, weil InDesign beim Export Fußnotenreferenzen ignoriert.

Beim XML-Export ignoriert InDesign Fußnotenreferenzen

Neben den Skripten für den XML-Import gesellen sich also weitere für den Export. Deren Aufgabe besteht im Grunde genommen darin, die XML-Struktur vor der Ausführung der Skripte wieder herzustellen. XML-First mit InDesign ist daher ohne aufwändige Vor- und Rückkonvertierungen nicht zu haben.

Die Skripte agieren aber nicht im luftleeren Raum. Während des Satzes wurden von Menschen weitere Änderungen am Dokument vorgenommen – Autorenkorrekturen wurden eingearbeitet, Textpassagen neu ausgezeichnet und der Feinumbruch ausgeführt. Es ist keineswegs selbstverständlich, dass das exportierte XML nach diesen Eingriffen vollständig und valide ist. Die Probleme beim Satz mit InDesign und XML werden daher Gegenstand des dritten Teils dieser Serie sein.

Kommentar schreiben

[…] Funktionen über InDesigns Skript-Schnittstelle nachrüstet. Der nächste Beitrag ist daher dem Einsatz von Skripten beim Satz mit InDesign und XML […]

Warum verwendest du XML-Rules anstatt ausschießlich evaluateXPathExpression()? Gibt es da Vorteile?
Meiner Erfahrung nach kann man sich die XML-Rules seit CS4 komplett sparen. Performance ist ungefähr gleich, XPath Limitierung gleich, Code ist mit evaluateXPathExpression() deutlich übersichtlicher.
Theoretisch könnte man mit XML-Rules Teilbäume bei der Verarbeitung auslassen. Die Anforderung ist mir aber noch nie untergekommen.

Wie schon getwittert, ging es mir nur darum alle Ansätze der Vollständigkeit halber darzustellen. XML-Rules werden so weit ich weiß noch in der Praxis verwendet, auch wenn ich aus meiner Sicht evaluateXPathExpression() auch schlanker finde. Vielleicht hat Adobe die XML Rules einfach aus Gründen der Abwärtskompatibilität im Code belassen.