cScript ist eine von WERK II implementierte und in die priint:comet Plug-Ins und comet_pdf integrierte Programmiersprache.
Die Syntax von cScript lehnt sich an die der Programmiersprache C an. Dieses Dokument beschreibt den generellen Aufbau und die Verwendung von cScript. Informationen zu den implementierten Funktionen finden Sie hier.
InDesign® verfügt über exzellente Skript-Schnittstellen: Mit Hilfe eines integrierten Objektmodels, der sogenannten InDesign® Scripting DOM (Dokument-Objekt-Model) kann InDesign® gleich mit drei Skriptsprachen gesteuert werden:
Die Skriptsprachen bilden dabei die Basis der Programmierung. Die Scripting DOM enthält - in der jeweiligen Syntax - alles, was zur Bearbeitung von InDesign®-Dokumenten nötig ist. Eine sehr gute Online-Referenz dazu finden Sie hier.
Plug-in-Entwickler wie WERK II können die Scripting DOM um eigene Objekte und Funktionen erweitern und damit den Skriptschnittstellen die Funktionalitäten ihrer eigenen Plug-ins zur Verfügung stellen. Hier finden Sie eine vollständige Doku der InDesign® Scripting DOM Extensions der priint:comet Plug-in. Die Erweiterungen der Standard-InDesign® Scripting DOM sind in allen drei integrierten Skriptsprachen verfügbar. Ein AppleScript-Beipiel der priint.comet Scripting DOM Extension finden Sie hier.
Alle drei oben genannten Skriptpsprachen haben eins gemeinsam: Sie sind hauptsächlich dafür gemacht, InDesign® von außen zu steuern. Die priint:comet Platzhalter ändern das Dokument aber von innen.
Es ist natürlich möglich, die integrierten Skriptschnittstellen auch intern aufzurufen. Dazu muß aber jedesmal zuerst eine neue Instanz einer "Maschine" zum Ausführen des Skriptes gestartet werden.
Angenommen, ein Skript zum Laden eines Platzhalters wäre in JavaScript geschrieben. Dann ergäbe sich eine Situation wie im Bild dargestellt:
Jeder Aufruf eines Platzhalter-Skriptes hätte also den Start einer neuen JavaScrpit-Maschine zur Folge. Und in jedem dieser Skripte muß die Zielstelle der Bearbeitung neu ermittelt werden. Will man das vermeiden und trotzdem die Fexibilität skriptbasierter Platzhalter behalten, bleibt nur eine eigene Skriptsprache: Deshalb cScript.
Natürlich wird, wie Sie weiter unten sehen werden, auch für jedes cScript eine eigene "Maschine" gestartet. Aber diese Maschinen sind wesentlich kleiner und können entsprechend schneller geladen werden. Und: Sie arbeiten gewissermaßen innerhalb von InDesign® und "kennen" das jeweilige Ziel bereits. Damit ein Textplatzhalter seinen Inhalt ändern kann, ist zum Beispiel lediglich eine Zeile nötig:
textmodel::replace ("neuer Text");
5:1 cScript ist um einiges schneller als JavaScript mit InDesign® Scripting DOM. In einem Test haben haben wir 900 Rahmen eingefärbt. JavaScript hat dafür 2.355 ms benötigt. cScript hat die gleiche Aufgabe in 452 ms bewältigt. cScript war also ungefähr 5 mal schneller! Hier die beiden Testfunktionen:
Das Javascript färbt den Hintergrund aller Rahmen der ersten drei Dokumentseiten mit der Farbe "Blau".
app.doScript(main, ScriptLanguage.javascript, undefined, UndoModes.entireScript, "Blue Color"); function main () { var start = new Date(); for (var p = 0; p < 3; p++) { for (var i = 0; i < app.activeDocument.pages.item(p).allPageItems.length; i++) { var f = app.activeDocument.pages.item(p).allPageItems [i];f.fillColor = "Blau"; } } var time = new Date() - start; alert (time); }
Das cScript färbt den Hintergrund aller Rahmen der ersten drei Dokumentseiten mit der Farbe "Blau".
int main () { ItemList li; ItemRef f = item::alloc (); int i, p; for (p = 1; p < 4; p++) { li = itemlist::pageframes (p); for (i = 0; i < itemlist::length (li); i++) { itemlist::get (li, f, i);frame::color (f, "Blau"); } itemlist::release (li); } return 0; }
cScript als Sprache ist unabhängig von InDesign® und in andere Softwarekomponenten integriert werden. cScript kann daher problelos von comet_pdf verwendet werden.
Achtung: Dass cScript in comet_pdf verwendet werden kann, heißt nicht, dass dort auch alle Skriptfunktionen 1:1 verwendet werden können! Welche der Funktionen im Einzelnen verfügbar sind, entnehmen Sie bitte der entsprechenden cScript-Doku.
Wir bemühen uns, cScript so umfangreich zu gestalten, dass alle Standardaufgaben damit gelöst werden können. Trotzdem ist cScript kein vollständiger Ersatz für InDesign®-Javascript. Mit der Dolmetscher-Funktion run_javascript kann JavaScript auch aus cScript heraus ausgeführt. Umgekehrt bieten die Funktionen app.comet.exec und app.comet.eval die Möglichkeit, aus JavaScript (oder AppleScript oder VBScript) heraus cScript-Programme auszuführen:
Skripte in cScript werden aus einer Untermenge der Schlüsselworte der Programmiersprache C gebildet und entsprechen weitgehend der dort verwendeten Syntax. Der Funktionsumfang der Sprache cScript ist speziell auf die Erfordernisse der priint:comet Komponenten zugeschnitten. Allgemein gilt :
Erkennt ein C-Compiler in einem cScript syntaktische Fehler, ist das Skript falsch. Aber von einem C-Compiler als fehlerfrei erkannter Quellcode muss nicht cScript-konform sein.
Hier finden Sie eine Beschreibung der Grammatik von cScript.
Literaturtips zu C sind in großer Zahl zu finden. Hier zwei davon:
Das Beispiel zeigt das Programm Hello World in cscript
Zerige das InDesign®-Nachrichtenfenster mit dem Text Hello World. Comet_pdf schreibt die Nachricht in die Konsole.
int main () { showmessage ("Hello World"); return 0; }
Die Bearbeitung eines cScript Skriptes wird immer bei der Funktion main begonnen. Die Funktion muss vom Typ int sein und darf (anders als in C) keine Funktionsparameter haben.
int main ()
Enthält ein Skript diese Funktion nicht, wird es nicht ausgeführt.
Ein häufiger Fehler im Hauptprogramm ist ein fehlendes return am Ende der Funktion. Bei der Skriptausführung durch die Plugins wird das Ergebnis der Ausführung (der Wert von return) abgefragt. Fehlt die Angabe, ist dieser Wert zufällig und möglicherweise schliesst das aufrufende Plugin aus diesem zufälligen Wert, dass das Skript fehlerhaft beendet wurde.
Die folgende Tabelle enthält eine vollständige alphabetische Aufzählung aller in C definierten Schlüsselwörter. Die durchgestrichenen Worte sind in cScript nicht definiert. Erschrecken Sie nicht, dass so viel durchgestrichen ist, auch mit dem Rest können Sie theoretisch alles programmieren. Und schliesslich ist ja auch der Sprachumfang von C selbst recht klein und reicht doch, so komplexe Programme wie InDesign® und die priint:comet Plugins zu schreiben.
Schlüsselwort | Beschreibung |
auto |
Speicherklassen werden von cscript nicht unterstützt |
break | |
case |
switch-case Konstruktionen sind in cscript nicht enthalten. |
char | |
continue |
In for- und while-Schleifen muss continue mit Hilfe von if-else realisiert werden. |
default |
switch-case Konstruktionen sind in cScript nicht enthalten. |
do |
Do-while Schleifen werden von cScript nicht unterstützt |
double | |
else | |
enum |
In cscript können keine eigenen Datentypen definiert werden. |
extern |
Speicherklassen werden von cscript nicht unterstützt. |
float | |
for | |
goto | |
if | |
int | |
long |
wie int |
register |
Speicherklassen werden von cscript nicht unterstützt. |
return | |
short |
wie int |
sizeof | |
static |
Speicherklassen werden von cScript nicht unterstützt. |
struct |
In cScript können keine eigenen Datentypen definiert werden. |
switch | |
typedef |
In cscript können keine eigenen Datentypen definiert werden. |
union |
In cscript können keine eigenen Datentypen definiert werden. |
unsigned | |
void | |
volatile |
Speicherklassen werden von cscript nicht unterstützt. |
while |
cScript akzeptiert neben C-Kommentaren (/* */) auch die in C++ verwendeten //-Kommentare, die immer bis zum Zeilenende gelten. /* */- Kommentare dürfen, wie in C und C++ nicht geschachtelt werden.
/* Mehrzeiliger Kommentar. Die Kommentare können nicht geschachtelt werden! */ // Kommentar bis zum Zeilenende
Skripte werden wie eigene kleine Apps ausgeführt. Jedes Skript verfügt während seiner Ausführung innerhalb des Arbeitsspeichers über einen eigenen zusammenhängenden Speicherbereich fester Größe. Nach der Ausführung des Skriptes wird dieser Speicher wieder freigegeben. Die Größe des benötigten Speichers hängt wesentlich von der Länge des Skriptes und der Anzahl und Größe der verwendeten Variablen ab.
Der Skriptspeicher besteht im wesentlichen aus drei Teilen:
In Abhängigkeit von den verwendeten Symbolen (Stack) und deren Daten (Heap) bewegen sich Stack und Heap während der Laufzeit des Skriptes aufeinander zu. Wird der Gültigkeitsbereich eines Bezeichners verlassen, werden das Symbol und dessen statische Daten wieder entfernt. Achtung : Mit alloc-Funkionen reservierter Speicher wird dabei nicht automatisch freigeben.
Zur Laufzeit können Stack und Heap aufeinander zuwachsen (und sich wieder voneinander entfernen). Treffen beide aufeinander, gibt es den berühmten Stack overflow. Dann war der verfügbare Skriptspeicher zu klein.
Sie können die Größe des Skriptspeichers in der Palette CometXML unter dem Punkt cScript-Puffer in kB und mit Hilfe des Skriptbefehles prefs::set_script_buffer einstellen. Standardwert ist 512 kB.
Achtung : Wie Sie weiter unten sehen werden, ist es mit Hilfe der alloc-Fubkionen möglich, innerhalb eines Skriptes Arbeitsspeicher auch außerhalb des Skriptspeichers zu reservieren. Nicht freigegebener Speicher wird zum Ende des Skriptes automatisch wieder freigegeben. Es ist aber guter Stil, reservierten Speicher auch selbst wieder freizugeben.
Die Sprache cScript kennt vier Datentypen: int, float, char und String.
Datentyp | Beschreibung |
int, short, long |
Ganzzahlen, 8 Byte (also 64bit). Die Angaben sind gleichwertig. |
float, real, double |
Kommazahlen, 8 Byte. Die Angaben sind gleichwertig. |
char |
1 Byte |
String | Eingebauter Datentyp für Zeichenketten. String ist eine Adresse und muß mit string::alloc allokiert werden. |
Die C-Schlüsselworte unsigned, typedef, enum und struct sind in cScript nicht definiert. Es können keine eigenen Typdefinitionen gemacht werden.
Zur besseren Lesbarkeit Ihrer Skripte gibt es zusätzlich eine Reihe modulspezifischer Typnamen. Alle diese Datentypen sind Pointer vom Typ int*. Hier finden Sie eine Liste aller definierten Datentypen.
Arrays sind zusammenhängende Speicherbereiche mit einer festen Anzahl von Elementen des gleichen Datentypes. Die häufigste Anwendung von Arrays sind statische Zeichenketten (z.B. char[512]).
Die Größe des reservierten Speicher eines Arrays kann zur Laufzeit nicht mehr geändert werden. Speicherfreigaben mit release auf eine Array-Variable führen zu schweren Fehlern und können InDesign® zum Absturz bringen!
Arrays werden durch die Angabe des Datentypes, eines Namens und der Anzahl der Elemente in eckigen Klammern [] vereinbart. Das erste Element des Arrays wird bereits bei der Deklaration mit 0 (bzw. 0,0 bei floats) initialisiert. Alle weiteren Elemente bleiben undefiniert.
Da Datentypen unterschiedlich breit sein können, hängt die tatsächliche Länge eines Arrays im Arbeitsspeicher nicht nur von der Anzahl der Elemente sondern auch vom Datentyp ab.
Hier zwei Beispiele für Arrays
int data [16]; //Array mit 16 Zahlen (16*8=128 Bytes auf dem Heap) char s [16]; //Array für 16 Ascii-Zeichen (16*1=16 Bytes auf dsem Heap)
Der Zugriff auf Elemente eines Arrays erfolgt über den Index des gewünschten Elementes. Der Index bezeichnet dabei das Element, nicht das Byte! Das erste Element hat den Index 0. Der Index wird in eckigen Klammern angegeben. Alternativ zu den eckigen Klammern kann auch folgende Schreibweise verwendet werden:
array [idx] entspricht *(array+idx)
Zugriffe auf nicht initialisierte Werte können zu unerwarteten Ergebnissen führen.
Der Aufruf zeigt das zweimal das vierte Zeichen char*Strings s:
int main () { char s [16]; strcpy (s, "Hello world"); showmessage ("%c ?= %c", s[4], *(s+4)); return 0; }
Deklaration und Füllen eines int-Arrays. Die Elemente bekommen als Wert jeweils ihren doppelten Index.
int data [16]; int k; for (k = 0; k < 16; k++) { data [k] = k*2; }
TIPP : Es ist gute Praxis, for-Schleifen über Arrays immer von 0 bis Länge-1 zu machen.
In cScript kann mit globalen Variablen gearbeitet werden. Globale Variable werden über die Palette Einstellungen angelegt und geändert.
Bitte beachten Sie, dass globale Variablen vom PubishingServer nicht unterstützt werden!
In Skriptem können globale Variablen wie jede andere Variable auch verwendet werden. Folgenden Datentypen werden unterstützt :
Die Variablen werden gelesen, wenn eine Verbindung zum Datenpool hergestellt wurde. Jede InDesign-Instanz verwendet dabei eine eigene Kopie der Daten. Wertänderungen an den Variablen gelten immer nur lokal. Mit der Skriptfunktion system::commit_global können Änderungen der Werte in den Datenpool zurückgeschrieben werden. Lokal geänderte Variablen werden in der Palette Einstellungen rot markiert.
Eine neue globale Variable legen Sie mit an. Sie werden in einem Dialog nach Name, Beschreibung und Typ der Variable gefragt. Nach Klick von OK erscheint ein Fenster, in dem Sie den Wert der neuen Variablen angeben können.
Die Beschreibung wird in der Palette Einstellungen als Tooltip gezeigt des Variablennamens gezeigt.
Zum Ändern der Einträge verwenden Sie das Button in der ersten Zeile der Liste. Der Tooltip der Buttons gibt Ihnen weitere Informationen.
Die in der Liste Einstellungen gezeigten Einträge können natürlich auch direkt über die Datenquelle konfiguriert werden. Die Definitionen der Variablen und ihre Werte werden in der Datei datafiles.xml (XML und SOAP) bzw. in der Tabelle globals (ODBC und Oracle) abgelegt. Die folgende Tabelle beschreibt die nötigen Attribute von datafiles und globals.
Name | Typ | Beschreibung |
id | int |
Die ID wird zur Bestimmung des Datentypes der Variable benutzt: : -1 : char [8192] Die ID kann nicht als Primärschlüssel der Tabelle globals verwendet werden! Verwenden Sie hier die Kombination id/alias als Primärschlüssel : ALTER TABLE `globals` ADD PRIMARY KEY ( `id` , `alias` ) |
alias | varchar (255) |
Eindeutiger Name der Variable. Namen müssen mit einem Buchstaben oder _ beginnen und dürfen nur Buchstaben, Ziffern und _ enthalten. Die Verwendung unterscheidet Groß- und Kleinschreibung. |
path | text |
Wert der Variable. Bei Zahlen muss der Text einen entsprechenden Zahlenstring enthalten. Als Dezimaltrenner bei float wird der Punkt (.) erwartet. |
description | text |
Beschreibung der Variable. Der Text wird in den Hilfetexten der Palette Einstellungen gezeigt. |
enabled | int |
Der Wert wird bisher ignoriert. |
Es können bis zu 8192 globale Variable definiert werden.
Bitte haben Sie etwas Geduld. Diese Datei ist zur Zeit in Bearbeitung.
Beachten Sie aber bitte in jedem Fall die folgenden wichtigen Hinweise:
main als Einstiegspunkt
alloc/release Balance
64 Bit
Auswertung von Ausdrücken von hinten nach vorne
Bedingungsanweisungen
float braucht Kommazahl
Statische String/char*-Rückgaben werden in drei Fällen gemacht:
Für die Funktionsergebnisse muss im Aufruf kein Speicherplatz reserviert werden. Die Funktionen geben entweder den Wert einer globalen Adresse oder einer Adresse im befragten Objekt zurück. Folgende Dinge müssen deshalb unbedingt beachtet werden :
Außer bei der Verwendung als Funktionsparameter sollten die Ergebnisse von Funktionen mit statischen String/char*-Rückgabewerten immer in lokale Variablen kopiert werden. Eine einfache Adress-Zuweisung an eine entsprechende lokale Variable genügt nicht - weil Sie dann ja nur die Adresse des Ergebnisses behalten (die sich außerdem, eben weil statische Variable, sowieso nicht ändern wird).
Verwendung als Funktionsparameter
showmessage ("%s", file::get_nth ("$DESKTOP", 0));
Direktes Kopieren in eine Skriptvariable
String str = string::alloc (file::get_nth ("$DESKTOP", 0));
Kopieren in eine Skriptvariable
String str = string::alloc ();
:
:
string::set (str, file::get_nth ("$DESKTOP", 0));
Fehler in der Verwendung globaler Variablen sind schwer zu erkennen. Hier eine kleiner Sammlung häufiger Fehler, die Sie unbedingt vermeiden sollten.
Der Rückgabewert darf nicht an allokierte Variablen zugewiesen werden. Die Variable ändert dadurch ihren Wert (Adresse) und zeigt damit auch nicht mehr auf den von Ihnen reservierten Speicherbereich. Mit alloc reservierter Speicher kann mit release nicht mehr freigegeben werden.
char * str = alloc (1024); // oder char str [1024]
:
str = file::get_nth ("$DESKTOP", 0); // FALSCH! str ist eine allokierte Variable.
Die Funktionen dürfen nicht an Stellen verwendet werden, an denen ihr Inhalt geändert wird (wie etwa im ersten Parameter von strcat oder strcpy).
strcat (file::get_nth ("$DESKTOP", 0), "pp"); // FALSCH! Der Wert des Ergebnisses wird verändert.
Auch über den (weniger offensichtlichen) Weg einer definierten lokalen Variable dürfen die Inhalte nicht verändert werden.
char * str;
:
str = file::get_nth ("$DESKTOP", 0); // Bis hierher ist noch alles richtig
strcat (str, "pp"); // FALSCH! str ist eine statische Variable
Variablen, deren Wert durch eine Zuweisung an eine r/o-Ergebnis-Funktion definiert wurde, dürfen nicht gelöscht werden. (verbotene Variablenänderung und freigeben von nicht selbst allokiertem Speicher).
char * str;
:
str = file::get_nth ("$DESKTOP", 0); // Bis hierher ist noch alles richtig
:
release (str); // FALSCH! r/o Variable darf nicht gelöscht werden.
Nachfolgende Aufrufe einer Funktion mit r/o-Rückgabe kann das Ergebnis von Vorgängeraufrufen der gleichen Funktion überschreiben. Verwenden Sie deshalb solche Funktionen nie mehrfach innerhalb einer Anweisung.
wlog ("",
"'%s', '%s'\n",
file::get_nth ("$DESKTOP", 0),
file::get_nth ("$DESKTOP", 1)); // FALSCH! Ergebnis von get_nth wird mglw. überschrieben
Nachfolgende Aufrufe einer Funktion mit r/o-Rückgabe kann das Ergebnis von Vorgängeraufrufen der gleichen Funktion überschreiben. Verwenden Sie deshalb solche Funktionen nie mehrfach innerhalb einer Anweisung.
String str = string::alloc ();
char * s1;
:
s1 = frame::get_text (gFrame, str, 0, kEnd);
:
wlog ("", "%s\n", s1);
string::release (str);
wlog ("", "%s\n", s1); // FALSCH! str wurde mittlerweile gelöscht!
Strings sind Zeiger! Funkionen mit einem String als Rückgabewert dürfen diese Variable keinesfalls selbst allokieren: Mit dem Verlassen einer Funktion ist auch die Gültigkeit lokalen Variablen beendet, der Rückgabewert der Funktion ist also nach dem Ende der Funktion selbst auch nicht mehr gültig. Die Lösung ist, den String außerhalb der Funktion zu allokieren und der Funktion als Zusatz-Parameter mitzugeben. Damit ist der String sicher definiert und kann trotzdem als bequemer Rückgabewert verwendet werden.
Hier die gültige und gefahrlose Definition einer String-Funktion:
String my_string_func (String result) { string::set (result, "your new value"); return result; }
Die Aufrufstelle ist für die Variable zuständig:
String myString = string::alloc (); : wlog ("", "%s\n", my_string (myString)); : string::release (myString)
Das Gleiche wie für String-Funkionen gilt analog für alle 'Zeiger'-Typen wie ItemRef, XMLTree, Query, int*, usw. .