Die Bibliothek Extendables von Stijn Debrouwere ist inzwischen schon fast ein Jahr im Orbit. Die Idee ist ExtendScript, also die JavaScript Implementierung fast aller Adobe Anwendungen, mit nützlichen Funktionen zu erweitern. Die Klassiker sind die Erweiterungen der Klassen String und Array, wie z.B. die Methoden "Zeichenkette".trim() oder ".Zeichenkette".contains()
Neben vielen Erweiterungen und Gimmicks fällt vor allem das HTTP-Modul auf. Die bekannten Verfahren über das Socket-Objekt sind eher aufwändig und man braucht ein gesundes Verständnis des Protokolls HTTP. Mit Extendables kann man sich die Programmierung auf dieser Ebene sparen und direkt mit Webdiensten kommunizieren. Das Minimalbeispiel aus der Dokumentation zeigt das Vorgehen recht gut:
#include "extendables/extendables.jsx"; var http = require("http"); var response = http.get("http://www.w3c.org"); if (response.status == 200) { $.writeln(response.body); } else { $.writeln("Connection failed"); }
Mit Hilfe der #include Direktive wird die Bibliothek extendables.jsx eingebunden. Das http-Package wird über die Funktion require() aktiviert und kann dann über die Variable http angesprochen werden. Mit get() kann eine Webadresse abgerufen werden, das Ergebnis der Abfrage wird hier in der Variable response gespeichert, sie enthält in der Eigenschaft body die Website. In der hier nicht verwendeten Eigenschaft headers sind weitere Statusinformationen enthalten. Weitere Infos zum Thema HTTP spukt die gute alte Wikipedia recht übersichtlich aus.
Ich möchte hier ein Praxisbeispiel vorstellen. Gerade komplexe Skripte laufen manchmal nicht ganz fehlerfrei und brauchen ab und zu ein Update. Wenn diese Skripte dann noch auf verschiedenen Rechnern verteilt sind, wäre es praktisch, diese über ein Webupdate zu aktualisieren – ganz so wie das inzwischen jede moderne Software macht.
Für das Skript braucht man natürlich einen Webserver. Das Beispiel arbeitet mit einem Skript, das die Uhrzeit bei Erstellung anzeigt. Es wird minütlich auf meinem Server per Cronjob aktualisiert und bietet somit ein ideales Beispiel für das Update-Skript.
Das eigentliche Skript ist in der Funktion checkForUpdates() enthalten, die mit den Parametern _updateServerURL und _updateFile gesteuert werden kann.
checkForUpdates ("http://www.publishingx.de/software/test/", "package.zip");
Der aktuelle Pfad wird ebenfalls mit Hilfe der Extendables Erweiterung component()ermittelt.
var _path = Folder(File($.fileName).component('path'));
Die Versionsprüfung wird anhand des Inhalts der Textdatei etag.txt vorgenommen. Im sogenannten ETag speichert der Webserver die Versionsinformation einer Datei, dies wird z.B. beim Caching von Webbrowsern eingesetzt. Später beim Update speichert das Skript die Information, hier wird sie zunächst ausgelesen, um sie später mit der Version auf dem Server zu vergleichen. Das ist nicht der eigentliche Umgang mit dem HTTP Etag (304 Modified). Laut HTTP Standard sollte der Client anfragen, ob es überhaupt notwendig ist die neue Datei zu laden. Da dies in Extendables aber nicht implementiert ist, werde ich diesen Vergleich lokal vornehmen (und muss deswegen in jedem Fall den Header herunterladen).
var _etagFile = File (_path + "/etag.txt");
if (_etagFile.open("r")) {
var _localEtag = _etagFile.read();
_etagFile.close();
}
Zunächst wird geprüft, ob eine Internet-Verbindung besteht:
if( http.has_internet_access()) { /* ... */ }
Da eine ZIP-Datei heruntergeladen werden soll, muss die Anfrage im Binary-Mode vorgenommen werden. Deswegen muss der HTTPRequest selber gebaut werden, was aber kein Problem ist. Wichtig ist, das encoding des Requests auf BINARY zu stellen.
var _updateURL = _updateServerURL + _updateFile; var _req = new http.HTTPRequest("GET", _updateURL); _req.encoding("BINARY"); var _resp = _req.do();
In _resp ist nun die Antwort enthalten. Zunächst wird anhand der Eigenschaft status geprüft, ob die Daten vorhanden sind.
if( _resp.status == 200 ) { /* ... */ }
Wenn wir Daten erhalten haben, wird das ETag aus dem Header geladen und mit dem lokalen ETag verglichen.
var _remoteETag = _resp.headers["ETag"]; // == _resp.headers.ETag;
if (_remoteETag != _localEtag) { { /* ... */ }
Sollte eine veränderte Version vorliegen, muss diese gespeichert und entpackt werden. Dazu wird zunächst die ZIP-Datei in den TEMP-Folder geschrieben und dann mit app.unpackageUCF() entpackt. Beim Schreiben muss man natürlich wieder das richtige encoding einstellen.
var _zipFile = new File (Folder.temp + "/temp.zip"); _zipFile.encoding = "BINARY"; if (_zipFile.open("w")) { _zipFile.write(_resp.body); _zipFile.close(); } // ... app.unpackageUCF(_zipFile, _path);
Im Zip-Archiv befindet sich die Datei scriptToUpdate.jsx die dann automatisch auf dem lokalen Rechner überschrieben wird. Achten Sie darauf, dass InDesign unter Windows7 im Anwedungsordner normalerweise keine Schreibrechte hat, das Skript empfiehlt sich also für den Benutzer-Ordner.
Das Update Skript inklusive Extendables kann natürlich auch vollständig heruntergeladen werden: checkForUpdates.zip Leider ist in der aktuellen Version von Extendables ein Fehler bei er Verarbeitung von Binary-Daten enthalten. Stijn hat zugesagt diesen bald zu beheben, solange muss man leider noch die im Download-Archiv enthaltene und von mir gepatchte Version verwenden!
Die Anforderung, mehrere Leerzeichen zusammenzufassen, taucht immer wieder auf. Bei Suchen/Ersetzungen mit Leerräumen bzw. Whitespace ist allerdings besondere Vorsicht geboten.
Zwischen Leerräumen befinden sich oft Marker, die bei der Suche ignoriert werde. Diese werden dann bei der Ersetzung unbeabsichtigt gelöscht. Das Grundsätzliche Problem habe ich bereits in diesem Post zusammengefasst.
Bei der Suche mit GREP gibt es die Möglichkeit mit \s alle Leerräume inklusive der Umbruchzeichen zu finden. Ab InDesign CS4 empfiehlt es sich mit der Unicode-Zeichenklasse \p{Z*} nur noch die Leerräume zu suchen.
Wenn ausschließlich nach Leeräumen gesucht wird, werden die folgenden Zeichen gefunden (whitespace.indd):
Wenn jetzt mehrere dieser Zeichen hintereinander auftreten, und durch einen einzigen Leerraum ersetzt werden sollen. Entstehen meiner Ansicht nach zwei grundsätzliche Probleme:
- Ignorierte Zeichen wie z.B. Indexmarken oder XML-Tags, die innerhalb des Suchergebnisses stehen, werden ungefragt verworfen.
- Die verschiedenen Leerräume haben eine unterschiedliche Wertigkeit. Wenn z.B. ein geschütztes Leerzeichen im Suchtreffer enthalten ist, sollte dieses bei der Ersetzung nicht durch ein einfaches Leerzeichen ausgetauscht werden.
Der erste Punkt ist unstrittig, hier muss um einen InDesign-Bug herumprogrammiert werden. Der zweite Punkt wird vermutlich je nach Ansicht des Anwenders bzw. Produkts unterschiedlich beantwortet werden. Deswegen muss man eventuell die Priorität der Zeichen, die erhalten bleiben sollen, individuell anpassen.
Wenn mit der Zeichenklasse \s arbeitet muss man sich zusätzlich noch überlegen, was bei der Zusammenführung mit den Formatierungseinstellungen der Absätze geschehen soll.
InDesign überträgt bei der Ersetzung die Einstellungen des vorletzten Absatzes auf den letzten Absatz. Ich bevorzuge aber das gegensätzliche Verhalten: Die Formateinstellungen des letzten Absatzes sollen erhalten bleiben. Mir reicht es zunächst, wenn das Absatzformat gerettet wird. Einzelne Einstellungen können aber nach dem gleichen Muster übernommen werden.
Um diese Probleme in den Griff zu kriegen habe ich ein Skript entwickelt. Es basiert auf dem findAndDo-Skript. Das gesamte Skript whitespacer.jsx steht zum Download bereit. Die interessantesten Teile stelle ich im folgenden vor:
Mit GREP werden alle Textstellen mit mindestens zwei aufeinanderfolgenden Leerzeichen gesucht:
app.findGrepPreferences.findWhat = "\\s{2,}";
Das Dokument muss rückwärts durchsucht werden, da Textänderungen den Index verschieben.
var _results = app.activeDocument.findGrep (true);
Innerhalb einer for-Schleife werden die Suchergebnisse analysiert. Dazu wird mit indexOf geprüft, ob das Zeichen im Ergebnis vorhanden ist. Je weiter unten das Zeichen in Liste steht, desto höher ist die Priorität :
// Normal Space if (_results[i].contents.indexOf ("\u0020") > -1) _mostImportant = _results[i].contents.indexOf ("\u0020"); // Nonbreaking Space if (_results[i].contents.indexOf ("\u00A0") > -1) _mostImportant = _results[i].contents.indexOf ("\u00A0"); // ..
Falls mehrere Absätze konsolidiert werden, merke ich mir das Absatzformat des letzten Absatzes. Hier müssen wiederum zwei Fälle unterschieden werden: Wenn der letzte Absatz mit Leerraum beginnt, gehört er zum Suchergebnis. Wenn nicht muss mit nextItem der nächste Absatz adressiert werden.
var _savedPStyle = false;
var _REbreak = RegExp("\\r","g");
var _REStartsWhite = RegExp("^\\s");
var _res = _results[i].contents.match (_REbreak);
if (_res != null && _res.length > 1) {
_lastPar = _results[i].paragraphs[-1];
if (_REwhite.test (_lastPar.contents) )
_savedPStyle =_lastPar.appliedParagraphStyle;
else
_savedPStyle =_results[i].paragraphs.nextItem (_lastPar ).appliedParagraphStyle;
}
Bei der eigentlichen Ersetzung werden alle Zeichen entfernt. Das zu erhaltende Zeichen, Marken, und bei der Suche ignorierte Zeichen werden geschützt:
var _resArr = _results[i].characters.everyItem().getElements();
for (var k = _resArr.length -1; k >= 0; k--) {
var _char = _resArr[k];
if ( _char.contents != "\uFEFF" &&
_char.contents != SpecialCharacters.END_NESTED_STYLE &&
_char.contents != SpecialCharacters.INDENT_HERE_TAB &&
_char.contents != SpecialCharacters.DISCRETIONARY_HYPHEN &&
_char.contents != SpecialCharacters.DISCRETIONARY_LINE_BREAK &&
_char.contents != SpecialCharacters.ZERO_WIDTH_NONJOINER &&
_char.contents != SpecialCharacters.ZERO_WIDTH_JOINER &&
k != _mostImportant) _char.remove ();
}
}
Zu guter letzt wird das ehemalige Absatzformat erneut zugewiesen
if (_savedPStyle)
_char.parentStory.characters[(_char.paragraphs[0].characters[-1].index + 1)].
paragraphs[0].appliedParagraphStyle;
}
Das Skript ist noch nicht der Weisheit letzter Schluss. Insbesondere über die Performance müsste man noch nachdenken. Soweit ich das testen konnte läuft es sehr stabil.
Die Anforderung, mehrere Leerzeichen zusammenzufassen, taucht immer wieder auf. Bei Suchen/Ersetzungen mit Leerräumen bzw. Whitespace ist allerdings besondere Vorsicht geboten.
Zwischen Leerräumen befinden sich oft Marker, die bei der Suche ignoriert werde. Diese werden dann bei der Ersetzung unbeabsichtigt gelöscht. Das Grundsätzliche Problem habe ich bereits in diesem Post zusammengefasst.
Bei der Suche mit GREP gibt es die Möglichkeit mit \s alle Leerräume inklusive der Umbruchzeichen zu finden. Ab InDesign CS4 empfiehlt es sich mit der Unicode-Zeichenklasse \p{Z*} nur noch die Leerräume zu suchen.
Wenn ausschließlich nach Leeräumen gesucht wird, werden die folgenden Zeichen gefunden (whitespace.indd):
Wenn jetzt mehrere dieser Zeichen hintereinander auftreten, und durch einen einzigen Leerraum ersetzt werden sollen. Entstehen meiner Ansicht nach zwei grundsätzliche Probleme:
- Ignorierte Zeichen wie z.B. Indexmarken oder XML-Tags, die innerhalb des Suchergebnisses stehen, werden ungefragt verworfen.
- Die verschiedenen Leerräume haben eine unterschiedliche Wertigkeit. Wenn z.B. ein geschütztes Leerzeichen im Suchtreffer enthalten ist, sollte dieses bei der Ersetzung nicht durch ein einfaches Leerzeichen ausgetauscht werden.
Der erste Punkt ist unstrittig, hier muss um einen InDesign-Bug herumprogrammiert werden. Der zweite Punkt wird vermutlich je nach Ansicht des Anwenders bzw. Produkts unterschiedlich beantwortet werden. Deswegen muss man eventuell die Priorität der Zeichen, die erhalten bleiben sollen, individuell anpassen.
Wenn mit der Zeichenklasse \s arbeitet muss man sich zusätzlich noch überlegen, was bei der Zusammenführung mit den Formatierungseinstellungen der Absätze geschehen soll.
InDesign überträgt bei der Ersetzung die Einstellungen des vorletzten Absatzes auf den letzten Absatz. Ich bevorzuge aber das gegensätzliche Verhalten: Die Formateinstellungen des letzten Absatzes sollen erhalten bleiben. Mir reicht es zunächst, wenn das Absatzformat gerettet wird. Einzelne Einstellungen können aber nach dem gleichen Muster übernommen werden.
Um diese Probleme in den Griff zu kriegen habe ich ein Skript entwickelt. Es basiert auf dem findAndDo-Skript. Das gesamte Skript whitespacer.jsx steht zum Download bereit. Die interessantesten Teile stelle ich im folgenden vor:
Mit GREP werden alle Textstellen mit mindestens zwei aufeinanderfolgenden Leerzeichen gesucht:
app.findGrepPreferences.findWhat = "\\s{2,}";
Das Dokument muss rückwärts durchsucht werden, da Textänderungen den Index verschieben.
var _results = app.activeDocument.findGrep (true);
Innerhalb einer for-Schleife werden die Suchergebnisse analysiert. Dazu wird mit indexOf geprüft, ob das Zeichen im Ergebnis vorhanden ist. Je weiter unten das Zeichen in Liste steht, desto höher ist die Priorität :
// Normal Space if (_results[i].contents.indexOf ("\u0020") > -1) _mostImportant = _results[i].contents.indexOf ("\u0020"); // Nonbreaking Space if (_results[i].contents.indexOf ("\u00A0") > -1) _mostImportant = _results[i].contents.indexOf ("\u00A0"); // ..
Falls mehrere Absätze konsolidiert werden, merke ich mir das Absatzformat des letzten Absatzes. Hier müssen wiederum zwei Fälle unterschieden werden: Wenn der letzte Absatz mit Leerraum beginnt, gehört er zum Suchergebnis. Wenn nicht muss mit nextItem der nächste Absatz adressiert werden.
var _savedPStyle = false;
var _REbreak = RegExp("\\r","g");
var _REStartsWhite = RegExp("^\\s");
var _res = _results[i].contents.match (_REbreak);
if (_res != null && _res.length > 1) {
_lastPar = _results[i].paragraphs[-1];
if (_REwhite.test (_lastPar.contents) )
_savedPStyle =_lastPar.appliedParagraphStyle;
else
_savedPStyle =_results[i].paragraphs.nextItem (_lastPar ).appliedParagraphStyle;
}
Bei der eigentlichen Ersetzung werden alle Zeichen entfernt. Das zu erhaltende Zeichen, Marken, und bei der Suche ignorierte Zeichen werden geschützt:
var _resArr = _results[i].characters.everyItem().getElements();
for (var k = _resArr.length -1; k >= 0; k--) {
var _char = _resArr[k];
if ( _char.contents != "\uFEFF" &&
_char.contents != SpecialCharacters.END_NESTED_STYLE &&
_char.contents != SpecialCharacters.INDENT_HERE_TAB &&
_char.contents != SpecialCharacters.DISCRETIONARY_HYPHEN &&
_char.contents != SpecialCharacters.DISCRETIONARY_LINE_BREAK &&
_char.contents != SpecialCharacters.ZERO_WIDTH_NONJOINER &&
_char.contents != SpecialCharacters.ZERO_WIDTH_JOINER &&
k != _mostImportant) _char.remove ();
}
}
Zu guter letzt wird das ehemalige Absatzformat erneut zugewiesen
if (_savedPStyle)
_char.parentStory.characters[(_char.paragraphs[0].characters[-1].index + 1)].
paragraphs[0].appliedParagraphStyle;
}
Das Skript ist noch nicht der Weisheit letzter Schluss. Insbesondere über die Performance müsste man noch nachdenken. Soweit ich das testen konnte läuft es sehr stabil.
Bei der Arbeit mit GREP sucht man gerne nach Textbereichen bzw. nach zusammenhängenden Bereichen einer Zeichenklasse. Die Aufgabe, innerhalb eines Dokuments mit XML-Tags die Leerräume zu konsolidieren, errinerte mich wieder daran, dass ich schon länger dem Suchverhalten von InDesign auf den Grund gehen wollte.
Das grundlegende Problem ist, dass bestimmte Zeichen bei der Suche – egal ob GREP oder normal – ignoriert werden (müssen), damit erwartbare Ergebnisse erzielt werden. Zu diesen Zeichen zählen neben den XML-Tags insbesondere auch die Index-Einträge, Textanker und Notizen, sowie einige andere Spezialzeichen.
Im Screenshot erkennt man die Darstellung der verborgenen Zeichen in InDesign. Die Unicode-Codepoints entsprechen der Repräsentation der Zeichen in InDesign und entsprechen nicht den Zeichen die laut Standard an diesen Positionen stehen.
In der Datei ignoredChars.indd ist diese und die folgende Tabelle enthalten.
Ein Problem ensteht erst bei der Ersetzung, da diese Zeichen dann entfernt werden. Konkret: Wenn diese Zeichen innerhalb eines Suchtreffers enthalten sind, werden sie bei der Ersetzung entfernt. Wenn man nicht aufpasst, kann man also munter Index-Einträge, Textanker, XML-Tags u.s.w. ohne Rückmeldung aus dem Dokument löschen.
Nur Notizen und Marken für ausgeblendete Bedingte Texte werden bei der Ersetzung korrekt behandelt: Eine Notiz innerhalb eines Suchtreffers wird bei der Ersetzung vor den Ersetzungsbegriff verschoben. Dieses Verhalten wünscht man sich eigentlich auch für alle anderen Spezialzeichen, zumindest aber für Index-Einträge und XML-Tags. Das Problem zieht sich durch alle Versionen einschließlich CS5.
Neben diesen Zeichen gibt es natürlich noch einen großen Problembereich bei der Suche nach Ligaturen und zusammengesetzeten oder diakritischen Zeichen. Wer diese einsetzt, sollte sich darüber bewusst sein, dass ein echte Ligatut fl (U+FB02) anders als die automatisch von InDesign zusammengesetzte Version aus f + l, nicht mehr mit der Suche nach “fl” gefunden wird.
Was tun?
Zunächst ist es vermutlich wichtig sich über die Problematik bewusst zu werden. Ganz so klar wie bei den Notizen ist der Umgang mit gefundenen Spezialzeichen leider nicht. Soll z.B. ein Indexeintrag innerhalb eines Wortes, das durch ein ganz anderes ersetzt wird, erhalten bleiben – oder ist das Verhalten von InDesign sogar gewünscht?
Bei Indexeinträgen wäre z.B. ein Skript denkbar, das alle Indexmarken vor die Wörter stellt, so dass diese bei den folgenden Suchen nicht mehr zum Suchtreffer gehören. Alternativ könnte man die Ersetzung selber programmieren, was allerdings die Performance stark verlangsamen dürfte.
Die Aufgabe, den Leerraum zu konsolidieren, geht meines Erachtens sogar über das Problem mit den gelöschten Zeichen hinaus. Wenn man mit dem Regulären Ausdruck \s+ beliebig viel Leerraum durch einen einzelnen Leerraum ersetzen möchte, muss man bei der Ersetzung noch berücksichtigen, welcher Leerraum den höchsten Wert hat. Sollte z.B. ein geschütztes Leerzeichen gefunden werden, müsste dieses bei der Ersetzung erhalten bleiben. Noch höherwertiger wäre z.B. eine Zeilenschaltung.
Sobald mein Skript zur Konsolidierung von Whitepsace unter Berücksichtigung von ignorierten Zeichen fertig ist, werde ich es hier vorstellen.
Spezialzeichen die nicht ignoriert werden
Zum Abschluss noch die Tabelle mit Spezialzeichen, die bei der Suche nicht ignoriert werden. Eine verankertes Objekt innerhalb eines Wortes führt also z.B. dazu, dass das Wort nicht mehr über die Suche gefunden wird.
Bis auf Tabellen können alle Spezialzeichen explizit über GREP-Platzhalter gesucht werden.
Das Erstellen von Bildquellenverzeichnissen ist meist ein ungeliebter Teil im Produktionsprozess. Um diesen zu automatisieren habe ich ein kleines Skript geschrieben welches einem die Arbeit abnimmt.
Natürlich kann auch dieses Skript nicht erraten welches Bild von welchem Urheber kommt, dazu greife ich auf die XMP-Metadaten zurück. Dies müssen vorher natürlich in Photoshop, Bridge oder einem ähnlichen Porgramm korrekt eingetragen worden sein.
Der eigentliche Trick besteht darin die XMP-Daten des Link Objekts auszulesen:
_bildQuellen.push([_dok.links[i].linkXmp.author, getPageByObject(_dok.links[i])] )
Alle Autoren werden zusammen mit der Seitenzahl im Array _bildQuellen gesammelt. Dieser Array muss dann mit der eigenen Sortierfunktion sortCreator() am Ende noch nach alfabetisch nach den Rechteinhaber sortiert werden.
Eine interessante Erweiterung ist es, die Bildpositionen auf den einzelnen Seiten zu erfassen. Dazu müsste man den Aufbau allerdings etwas abändern und seitenweise durch das Dokument gehen.
Hier kann man das ganze Skript und eine Beispieldatei mit Wikipedia Bildern herunterladen.
Schön wärs! Für sehr einfache und lineare Titel ist das denkbar. Sobald Informationen durch das Layout gegliedert werden scheitert jede Lösung, die versucht seitenbasierte Druckdaten automatsiert in ein strukturiertes Format zu überführen. Ohne eine akzeptable Implementierung künstlicher Intelligenz oder sorgfältige Vorbereitung der Daten kann das auch gar nicht anders sein.
Aus kaufmännischer Sicht ist der Wunsch kostenneutral E-Books zu generieren nachvollziehbar. Auf technischer Seite zeigt sich schnell wer sich ernsthaft mit dem Thema beschäftigt hat.
In diesem Beitrag geht es natürlich um den EPub-Export von InDesign. Der Export über Datei -> Export for Digital Editions ist erstaunlich gut. Wer ein paar Punkte beachtet kann bei einfachen Titeln zu sehr guten Ergebnissen kommen.
Zunächst ist wichtig, das alle Seitenelemente die nicht ins E-Book übernommen werden sollen auf Musterseiten angelegt werden – ein per Hand erstellter Kolumnentitel oder eine versehentlich gelöste Seitenzahl wird höchstwahrscheinlich an einer völlig falschen Stelle im Ebook auftauchen.
Beim Textaufbau ist auf die richtige Textverkettung der Rahmen zu achten – Textabschnitte werden immer vollständig exportiert.
Die größten Probleme in klassischen InDesign-Datein dürften jedoch per Hand platzierte Bilder verursachen. Diese werden ganz am Ende, bzw. erst nach dem Textabschnitt zu dem sie zugeordnet sind platziert. Abhilfe schafft die Verankerung der Bilder an den entsprechenden Stellen im Text. Dies dürfte in klassischen Workflows fast immer zu Nacharbeiten führen.
Ein weiteres Problem sind Bilder mit Legenden, diese müssen zunächst gruppiert werden, damit sie beim Export zusammengehalten werden.
Das Einbetten von Schriften ist gleich dopplet Problematisch: InDesign bettet nur Schriften mit entsprechender Erlaubnis ein. Dazu ergibt sich ein rechtlicher Problem bei der Weitergabe von Schriften in epub-Daten, da diese lediglich kopiert durch einfaches entpacken extrahiert werden können. Mit der Verwendung von freien Schriften kann man sich entsprechenden Ärger ersparen.
Selbstverständlich können keine strukturellen oder semantischen Informationen – wobei die Frage offen bleibt inwieweit die semantischen Auszeichnung von Informationen in naher Zukunft an realer Bedeutung gewinnt – exportiert werden.
Wer dann alles richtig gemacht hat wird trotzdem in den meisten Fällen das erstellt EPub öffnen und einzelne Teile nacharbeiten. Es bietet sich an ein eigenes CSS für alle Publikationen festzulegen, in diesem können dann auch ganz elegant freie Schriftarten zugewiesen werden. Zur Nacharbeit bieten sich die Technologien Java, XSLT udn CSS an. Bei einem höheren Datenvolumen sollten diese Schritte natürlich automatisiert werden.
Zusammenfassend würde ich sagen das ein EPub-Export gelingen kann. Jedoch sollte man sich vom automatischen Traum schnell verabschieden:
- Es werden detaillierte Bearbeitungskonventionen für die Arbeit in InDesign benötigt.
- Ein »EBook-Preflight« per Skript oder Abhakliste sollte entwickelt werden.
- Eine Nachbearbeitung des EBooks im Bereich Metadaten, Titelei
Aus einer langen Pause starte ich mit einem doppeldeutigen Titel, natürlich könnte ich auch mal einfach ein wenig mehr bloggen, aber Adobe hätte mit dem gleich vorgestellten Skript vielen Menschen viel Frust ersparen können. Aber der Reihe nach:
Der erste Versuch XML-Dokumente innerhalb von InDesign zu verwenden scheitert meist kläglich. Das Problem ist das kuriose Whitespace Handling. Die meisten XML-Dokumente enthalten Whitespace zwischen nicht texttragenden Elementen um sie leichter lesbar zu machen (Pretty Print) oder weil ein vorheriger Bearbeitungsschritt diesen erzeugt hat.
<root>
-> Whitespace <abs>text</abs>
</root>
Jeder der schon einmal den Quelltext einer Website betrachtet hat kennt das Phänomen. InDesign importiert den Whitespace zwischen den Elementen und stellt in vollständig dar (ähnlich dem XML-Attribut xml:space=”preserve” oder dem <pre>-Tag von HTML). Unbrauchbare Dokumente und frustrierte Anwender sind die Folge.
Mit der Version CS3 dachten auch viele Adobe hätte das Problem in den Griff bekommen, denn beim XML-Import tauchte eine Option zum Whitespace Handling auf.
Leider ist diese Funktion praktisch nutzlos, denn entweder bekommt man einen langen Text ganz ohne Zeilenumbrüche oder eben das ehemalige Verhalten bei dem alle Whitespace Elemente umgesetzt wurden.
Zur Lösung des Problems musste man entweder ein XSLT zum Preprocessing verwenden oder aber XML-Rules selber skripten. Für Anfänger sind beide Technologien etwas komplex, insbesondere wenn man sich klar macht, das einfach nur strukturierte Daten importiert werden sollen. Auch in CS4 hat sich dahingehend leider nichts verändert.
In jeder InDesign-XML-Schulung sprach ich bis heute immer davon, das bei der Funktion Map Tags to Styles… (Befehl Formate zu Tags zuordnen… aus dem Kontextmenu der Strukturansicht) ein Häkchen fehlen würde, welches das entsprechende Element als Blocklevel Element auszeichnet. Diese Option würde steuern welche Elemente ganze Absätz beinhalten und entsprechend einen Zeilenumbruch hinzufügen.
Vor ein paar Tagen kam mir dann eine noch einfachere Idee: Einfach ein Skript schreiben welches allen Elementen, denen in der Zuordnungstabelle ein Absatzformat zugewiesen ist, beim Importprozess einen Zeilenumbruch mitgibt. Denn die Zuweisung eines Absatzformats kennzeichnet ja eben das Element als Blocklevel-Element.
Das geht dann auch erstaunlich einfach: Das importieren der XML-Daten kann von InDesign übernommen werden. Der Trick ist eine For-Schleife über die ImportMap (Zuordnungstabelle) mit Prüfung ob ein Absatzformat zugewiesen ist. Vorraussetzung ist natürlich, dass die Zuordnung von Absatzformaten zu Tags bereits vor dem Import vorgenommen wurde, ohne je ein Skript editieren zu müssen.
for (i = 0; i < _dokument.xmlImportMaps.length; i++) { if (_dokument.xmlImportMaps[i].mappedStyle.constructor.name == "ParagraphStyle" ) { ... } }
Jetzt muss nur noch eine XML-Rule für jedes so ermittelte Element programmiert werden:
_xpath = "//" + _dokument.xmlImportMaps[i].markupTag.name; ... var _proc = app.xmlRuleProcessors.add([_xpath]); var _match = _proc.startProcessingRuleSet(_dokument.xmlElements[0]); while( _match!=undefined ) { _node = _match.element; _node.insertTextAsContent ("\r", XMLElementPosition.afterElement) _match = _proc.findNextMatch(); }
Mit Hilfe dieses Skripts kann jetzt jeder ohne XSLT oder Programmierkenntnisse XML in InDesign verarbeiten. Einfach das Skript herunterladen und in die Skripts-Palette einfügen. Zum Testen habe ich eine XML-Instanz und ein InDesing Dokument vorbereitet: Download XML Test ZIP.
Viel Spaß!
Über mangelnde XML-Funktionen von InDesign wurde schon viel geschrieben. Praktisch ist das nachträgliche Erstellen von XML-Strukturen in einem klassisch aufgebauten InDesign-Dokument nur mit einfachsten Strukturen und Dokumenten möglich.
Über die Funktion Formate zu Tags zuweisen und die Tagging Voreinstellungen aus dem Kontext-Menu der Strukturpalette lassen sich Block-Level- und Inline-Elemente verknüpfen, Bilder und Tabellen werden zumindest ausgezeichnet.
Mit InDesign Bordmitteln lässt sich maximal das Folgende XML-Ergebnis erzielen:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <Root> <textrahmen> <u1>Überschrift </u1> <p>Lorem ipsum <bold>dolor sit amet</bold>, consetetur ....</p> </textrahmen> <textrahmen> <legende><Figure href="file:///C:/Dokumente%20und%20Einstellungen/hp/Desktop/bild.png"/> Diese Bild zeigt das Logo des InDesign-Blogs </legende> </textrahmen> <textrahmen> <p>2tes Lorem ipsum dolor sit amet, consetetur ...</p> </textrahmen> </Root>
Das aber auch nur, wenn das Bild als verankertes Objekt im Textrahmen der Legende platziert ist. In den meisten Fällen wird so allerdings nicht gearbeitet und Legende und Bild haben keinerlei Verknüpfung mehr. Die Anordnung der textrahmen-Elemente (Es handelt sich eigentlich um Stories, also auch verknüpfte Textrahmen) innerhalb der Strukur entspricht dem Erstellungszeitraum und nicht dem optischen Eindruck, was die Ergebnisse der meisten Exporte noch weitaus verwirrender gestaltet als das obige Beispiel.
Für ein produktives Konvertieren von alten Daten bietet sich ein Skripting-Workaround an. Als Konvetion wird festgelegt, das Legende und Abbildungspaar immer gruppiert sein müssen.
Das Skript-Snippet ist sehr einfach, für eine bessere Übersichtlichkeit verzichte ich auf das Handling der Gruppen und verarbeite nur die erste:
var _group = app.activeDocument.groups[0];
if (_group.allGraphics.length == 1 && _group.textFrames.length == 1) {
var _xmlAbb = app.activeDocument.xmlElements[0].xmlElements.add("abbildung");
// Falls die Grafik bereits verknüpft ist entfernen ich den Tag
if (_group.allGraphics[0].associatedXMLElement != null) _group.allGraphics[0].associatedXMLElement.untag();
_xmlAbb.xmlElements.add("bild", _group.allGraphics[0]);
// Falls der Textrahmen bereits verknüpft ist entfernen ich den Tag
if (_group.textFrames[0].associatedXMLElement != null) _group.textFrames[0].associatedXMLElement.untag();
_xmlAbb.xmlElements.add("legende", _group.textFrames[0]);
}
Jede Gruppe die genau eine Abbildung und einen Textrahmen enthält wird damit in die folgende Struktur, die eher einer sinnvollen XML-Struktur genügt, gebracht:
<abbildung> <bild href="...bild.png"></bild> <legende>Diese Bild zeigt das Logo des InDesign-Blogs </legende> </abbildung>
Eine Lösung für die Anordnung der Elmente in der richtigen Reihenfolge steht noch aus. Genauso ist es natürlich denkbar per Skript nah beeinander stehende Bild/Legende Objekte zu suchen und nicht den Umweg über die Gruppierung zu gehen.
InDesign rendered das Absatzattribut Abstand vor nur zwischen Textzeilen allerdings nicht in der ersten Zeile eines Textrahmens. Für einen automatischen Umbruch ist allerdings ein Feature notwendig, das bspw. abgesenkte Kapitelüberschriften die unterhalb des Satzspiegels beginnen ermöglicht. Textrahmen manuell anzupassen verbietet sich aus verschiedenen Gründen. Beim Attribut Abstand nach gibt es das gleiche Problem, allerdings fehlt hier die praktische Relevanz.
Ein möglicher Workaround ist es, die Schriftart der Überschrift auf einen größeren Wert zu stellen und dann wieder kleiner zu skalieren. Die größere Schriftgröße muss dann natürlich dem gewünschten Abstandswert entpsrechen was bei Prozentangaben nicht ganz trivial ist.
Im Bild ist die obere Überschrift in 12pt die untere in 30,125pt mit einem Skalierungsfaktor von 40% dargestellt. Zur Erzeugung des Screenshots wurden zwei Textrahmen übereinandergelegt.
Dieses Verfahren funktioniert erstaunlich gut, bei einem manuell eingestellten Zeilenabstand auch für mehrzeilige Überschriften. Wer etwas Rechenfaul ist dem kommt mein spacebefore-Skript zu gute. Eigentlich ziemlich straight-forward heruntergeschrieben:
var abstandVor = 5; //in Millimetern var par = app.selection[0].paragraphs[0]; //1mm == 2.834645669pt abstandVor = abstandVor * 2.834645669; var origGroesse = par.pointSize; // Schriftgröße neu setzen par.pointSize = origGroesse + abstandVor; // Scale berechnen und zuweisen var scale = (origGroesse / (origGroesse + abstandVor )) * 100; par.verticalScale = scale; par.horizontalScale = scale;
Leider entspricht das noch nicht ganz dem gewünschten Ergebnis, weil die Schrift anhand der Versalhöhe am Textrahmen ausgerichtet wird. Dazu muss der Schrift nochmal ungefähr die Hälfte der Größe hinzugegeben werden.
abstandVor = (abstandVor + (abstandVor/2.5)) * 2.834645669;
Der Wert 2.5 ist allerdings nur ein ungefährer Mittelwert, ein genauer Wert ist Abhängig von der Versalhöhe der Schrift. Auf diesen Wert hat man über InDesign keinen direkten Zugriff. Er ließe sich zwar theoretisch berechnen, wenn die Schrift temporär in Pfade umgewandelt würde. Bzw. in der ersten Zelle über den Abstand zwischen Grundlinie und Rahmenoberkante. Das überlasse ich aber mal jemanden anderen und gebe mich mit einem etwas ungenauen Ergebnis zufrieden.
Meine persönliche Lieblingsfunktion von CS4 ist die Möglichkeit mit so gennanten GREP-Styles bestimmten Textbereichen Zeichenformate zuzuweisen. Sicherlich nicht die wichtigste Neuerung aber als großer Fan von Regulären Ausdrücken ist dieses Feature Pflicht.
Die Funktion ist im Absatzformatvorlagen-Dialog unterhalb der Verschachtelten Formate zu finden. Die Anwedung ist durchaus intuitiv. Man muss allerdings Reguläre Ausdrücke beherrschen bzw. sich mit der angebotenen Übersicht arrangieren.
Im unteren Beispiel werden alle Preise ausgezeichnet die aus Ziffern bestehen und denen die Buchstabenkombination EUR folgt. Z.B. Dieser Blogeintrag könnte 15 EUR kosten.

Im Detail sieht das so aus:
- \d+ bedeutet mindestens beliebig viele Ziffern jedoch mindestens eine (1…n)
- mit (?=Ausdruck) wird sichergestellt, das nach den gefundenen Ziffern der Ausdruck stehen muss
- der Ausdruck \s?EUR erlaubt ein optionales Leerzeichen gefolgt von der Zeichenkette EUR.
Nun werden alle Ziffernfolgen mit dem entsprechenden Zeichenformat formatiert. Ein kleiner Fehler ist allerdings noch zu beheben: Damit auch durch Komma abgetrennte Preise erkannt werden muss der Ausdruck wie folgt erweitert werden:
[\d,]+(?=\s?EUR)
innerhalb der eckigen Klammern wird definiert welche Zeichen auftreten dürfen, im Beispiel Ziffern und das Komma.
Leider kann über das Skripting Interface nicht auf die Instanzen zugreifen auf die das Styling angewendet wurde.
Dazu muss man selber die entsprechenden Absätze mit dem Regulärem Ausdruck durchsuchen. Wenn man dies generisch lösen will, kann das insbesondere bei den schon seit CS3 vorhandenen verschachtelten Formaten aufwändiger werden.
Die Idee alle Textbereiche über
TextStyleRange.appliedNestedStyles
auszuwerten funktioniert nur so lange, bis zusätzliche Veränderungen innerhalb von vorformatierten Bereichen diese weiter aufteilen.










