<data>
<article number="1" />
<article number="2" />
<article number="3" />
</data>
Die sicherlich bedeutendste Eigenschaft des Publishers ist die Fähigkeit, sehr flexible Layoutanforderungen umzusetzen. Das wird hauptsächlich durch die eingebaute Programmiersprache in Zusammenhang mit den Abfragemöglichkeiten des Publishers erreicht.
Die Programmausführung läuft gleichzeitig mit der Erzeugung des PDFs. Daher kann der speedata Publisher sehr flexibel auf die Eingabedaten reagieren. Abfragen wie »Ist noch genügend Platz für dieses Objekt vorhanden?« sind damit möglich. Das unterscheidet den Publisher von anderer Software zur Erstellung von PDF-Dateien. |
Es sind grundlegende Programmierkenntnisse vonnöten, um die volle Funktionalität des Publishers auszureizen. Die Programmiersprache wurde so einfach wie möglich gehalten, um die Lesbarkeit des Layouts zu erhalten.
Alle Variablen sind global sichtbar. Das heißt, dass eine Variable nie ungültig wird. Ein Beispiel:
<data>
<article number="1" />
<article number="2" />
<article number="3" />
</data>
<Layout xmlns="urn:speedata.de:2009/publisher/en"
xmlns:sd="urn:speedata:2009/publisher/functions/en">
<Record element="data">
<ProcessNode select="article"/>
<Message select="$nr"/>
</Record>
<Record element="article">
<SetVariable variable="nr" select="@number"/>
</Record>
</Layout>
Die globale Sichtbarkeit ist nötig, weil die Programmausführung im Layout manchmal »hin und her springt«.
Am Seitenende wird der Inhalt von <AtPageShipout>
im aktuellen Seitentyp ausgeführt.
Dort muss auch auf die Variablen zugegriffen werden können.
In Variablen können nicht nur einfache Werte gespeichert werden, sondern auch komplexe XML-Abschnitte:
<Record element="data">
<SetVariable variable="foo">
<Paragraph>
<Value>Hello world!</Value>
</Paragraph>
</SetVariable>
<PlaceObject>
<Textblock>
<Copy-of select="$foo"/>
</Textblock>
</PlaceObject>
</Record>
Ergibt die erwartete Ausgabe von »Hello world!«. Ein Anwendungsfall ist, Tabellenbreitendeklarationen zu speichern:
<SetVariable variable="tablecolumns">
<Columns>
<Column width="1cm"/>
<Column width="4mm"/>
<Column width="1cm"/>
</Columns>
</SetVariable>
um diese dann anschließend in mehreren Tabellen zu benutzen:
<PlaceObject>
<Table>
<Copy-of select="$tablecolumns"/>
<Tr>
..
</Tr>
</Table>
</PlaceObject>
Durch die einmalige Definition und Wiederverwendung spart man sich Tipparbeit und verringert die Fehlerquellen.
Die Inhalte von Variablen, die Kindelemente enthalten, werden sofort evaluiert. D.h. im folgenden Fall
<SetVariable variable="tmp">
<Paragraph><Value select="$greeting"/></Paragraph>
</SetVariable>
muss die Variable greeting
schon definiert sein.
Eine nachträgliche Änderung der Ausgabe im Absatz passiert nicht.
Daraus folgt, dass die Variablen keine Ausgabebefehle enthalten dürfen, wie z.B. <PlaceObject>
oder <ClearPage>
, da diese sofort wirksam wären.
Es gibt die Möglichkeit diese Evaluierungszeit auf die Anwendung bei <Copy-of>
zu verschieben (execute="later"
):
<SetVariable variable="tmp" execute="later">
<Paragraph><Value select="$greeting"/></Paragraph>
</SetVariable>
<SetVariable variable="greeting" select="'Hello User'"/>
<PlaceObject>
<Textblock>
<Copy-of select="$tmp" />
</Textblock>
</PlaceObject>
Hier wird erst bei der Benutzung von <Copy-of>
der Inhalt von $tmp
ausgewertet, anschließend ausgegeben.
Das funktioniert auch mit Ausgabebefehlen:
<SetVariable variable="tmp" execute="later">
<PlaceObject>
<Textblock>
<Paragraph><Value select="$greeting"/></Paragraph>
</Textblock>
</PlaceObject>
<ClearPage />
<PlaceObject>
<Textblock>
<Paragraph><Value>Hello user</Value></Paragraph>
</Textblock>
</PlaceObject>
</SetVariable>
<SetVariable variable="greeting" select="'Hello User'"/>
<Copy-of select="$tmp" />
erzeugt zweimal die Ausgabe 'Hello User' auf einer eigenen Seite.
<Copy-of>
wurde oben schon benutzt.
Damit werden Inhalte der Variablen an die aktuelle Stelle kopiert.
Der Inhalt der Variablen bleibt beim Kopieren unverändert.
variable =
Copy-of variable
neuer Wert
damit wird der neue Wert an die vorherigen angehängt.
<SetVariable variable="chapter">
<Copy-of select="$chapter"/>
<Element name="entry">
<Attribute name="chaptername" select="@name"/>
<Attribute name="page" select="sd:current-page()"/>
</Element>
</SetVariable>
In XPath kann man einfache Wenn-Dann Abfragen durchführen.
Die Syntax hierfür ist if (Bedingung) then … else …
.
<PlaceObject>
<Textblock>
<Paragraph>
<Value select="if (sd:odd(sd:current-page())) then 'recto' else 'verso'"/>
</Paragraph>
</Textblock>
</PlaceObject>
Fallunterscheidungen entsprechen der Konstruktion switch/case
aus C-ähnlichen Programmiersprachen.
Sie werden wie folgt im Publisher angewendet:
<Switch>
<Case test="$i = 1">
...
</Case>
<Case test="$i = 2">
...
</Case>
...
<Otherwise>
...
</Otherwise>
</Switch>
Alle Befehle innerhalb des ersten möglichen <Case>
-Falls werden abgearbeitet, wenn die Bedingung in test
dort zutrifft.
In test
wird ein XPath-Ausdruck erwartet, der true()
oder false()
ergibt, etwa $i = 1
.
Wenn kein Fall eintritt, so wird der Inhalt des optionalen <Otherwise>
-Abschnittes ausgeführt.
Es gibt verschiedene Schleifen im speedata Publisher.
Die einfache Variante ist <Loop>
:
<Loop select="10">
...
</Loop>
Dieser Befehl führt die eingeschlossenen Befehle so oft aus, wie der Ausdruck in select
ergibt.
Der Schleifenzähler ist, sofern nicht per variable="…"
anders eingestellt, in der Variablen _loopcounter
gespeichert.
Neben der einfachen Schleife gibt es noch Schleifen mit Bedingungen:
<Record element="data">
<SetVariable variable="i" select="1"/>
<While test="$i <= 4">
<PlaceObject>
<Textblock>
<Paragraph>
<Value select="$i"/>
</Paragraph>
</Textblock>
</PlaceObject>
<SetVariable variable="i" select="$i + 1"/>
</While>
</Record>
Den Ausdruck $i <= 4
muss man als $i <= 4
lesen, da die öffnende spitze Klammer an dieser Stelle im XML ein Syntaxfehler ist.
Die Schleife oben wird so oft ausgeführt, solange der Inhalt der Variablen i kleiner oder gleich 4 ist.
Nicht vergessen, die Variable auch zu erhöhen, sonst entsteht eine Endlosschleife.
Neben der while-Schleife gibt es noch die until-Schleife, die analog funktioniert:
<Record element="data">
<SetVariable variable="i" select="1"/>
<Until test="$i <= 4">
<PlaceObject>
<Textblock>
<Paragraph>
<Value select="$i"/>
</Paragraph>
</Textblock>
</PlaceObject>
<SetVariable variable="i" select="$i + 1"/>
</Until>
</Record>
Es ist mit dem neuen XPath-Modul möglich, Funktionen zu definieren:
<Layout xmlns="urn:speedata.de:2009/publisher/en"
xmlns:sd="urn:speedata:2009/publisher/functions/en"
xmlns:fn="mynamespace">
<Record element="data">
<PlaceObject>
<Textblock>
<Paragraph>
<Value select="fn:add(3,4)" />
</Paragraph>
</Textblock>
</PlaceObject>
</Record>
<Function name="fn:add">
<Param name="a" />
<Param name="b" />
<Value select="$a + $b" />
</Function>
</Layout>
Die Funktionen können auch komplexere Ausdrücke enthalten:
<Layout xmlns="urn:speedata.de:2009/publisher/en"
xmlns:sd="urn:speedata:2009/publisher/functions/en"
xmlns:fn="mynamespace">
<Record element="data">
<Value select="fn:chapter('First chapter')" />
</Record>
<Function name="fn:chapter">
<Param name="chaptername" />
<PlaceObject>
<Textblock>
<Paragraph>
<Value select="$chaptername"/>
</Paragraph>
</Textblock>
</PlaceObject>
</Function>
</Layout>
Der Namensraum für die Funktion muss im Wurzelelement definiert werden (hier: xmlns:fn="…"
). Variablen, die in der Funktion definiert werden, bleiben lokal, d.h. sind nicht in anderen Programmteilen sichtbar.
Der speedata Publisher bietet keine direkte Unterstützung für Datenstrukturen wie Arrays (Felder) oder Dictionaries (Hashes oder Wörterbücher). Diese können über Variablen simuliert werden. Das Feld a1, a2, …, ai könnte wie folgt belegt werden:
<SetVariable variable="{ concat('a',1) }" select="'Value for a1'"/>
<SetVariable variable="{ concat('a',2) }" select="'Value for a2'"/>
...
Natürlich könnte hier auch direkt a1
als Variablenname angegeben werden.
In diesem Beispiel könnte sowohl der Präfix als auch der Suffix dynamisch erzeugt werden:
<SetVariable variable="prefix" select="'a'" />
<SetVariable variable="{ concat($prefix,1) }" select="'Value for a1'"/>
<SetVariable variable="{ concat($prefix,2) }" select="'Value for a2'"/>
...
Der lesende Zugriff geht über sd:variable(…):
<SetVariable variable="prefix" select="'a'" />
<Message select="sd:variable($prefix,1)"/>
<Message select="sd:variable($prefix,2)"/>
...
Die Funktion sd:variable()
konkateniert alle Argumente als Zeichenkette und nimmt das Ergebnis als Variablennamen.