Programmierung

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.

Variablen

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>

Datendatei (data.xml)

<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>

Und die dazugehörige Layout-Datei (layout.xml). Die Ausgabe des Befehls <Message> ist 3. Wäre die Variable nr mit lokaler Sichtbarkeit deklariert, dann könnte sie im Element data nicht ausgelesen werden.

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.

Ausführungszeit

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

<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

Pseudocode. Mit Copy-of fügt man den Inhalt der Variablen an diese Stelle ein. Der Inhalt können auch komplexe XML-Strukturen wie Absätze sein.

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>

Ein Beispiel für Copy-of in der Praxis ist das Zusammenbauen von XML-Strukturen, mit denen Informationen gespeichert werden können. Ausführlich beschreiben wird dieses Beispiel im Kochbuch (Kaptiel 8), dort im Abschnitt Verzeichnisse erstellen (XML-Struktur).

If-then-else

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>

In XPath können einfache Wenn-Dann Abfragen benutzt werden.

Fallunterscheidungen

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.

Schleifen

Es gibt verschiedene Schleifen im speedata Publisher. Die einfache Variante ist <Loop>:

<Loop select="10">
  ...
</Loop>

Diese Schleife wird 10 Mal durchlaufen.

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 &lt;= 4">
    <PlaceObject>
      <Textblock>
        <Paragraph>
          <Value select="$i"/>
        </Paragraph>
      </Textblock>
    </PlaceObject>
    <SetVariable variable="i" select="$i + 1"/>
  </While>
</Record>

Die while-Schleife führt die eingeschlossenen Befehle aus, solange die Bedingung »wahr« ergibt. Es werden die Zahlen 1 bis 4 ausgegeben.

Den Ausdruck $i &lt;= 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 &lt;= 4">
    <PlaceObject>
      <Textblock>
        <Paragraph>
          <Value select="$i"/>
        </Paragraph>
      </Textblock>
    </PlaceObject>
    <SetVariable variable="i" select="$i + 1"/>
  </Until>
</Record>

Da die until-Schleife so lange ausgeführt wird, bis die Bedingung wahr ist, wird nur die Zahl 1 ausgegeben.

Funktionen

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.

Datenstrukturen

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.