<data>
<article number="1" />
<article number="2" />
<article number="3" />
</data>
Certainly the most important feature of the Publisher is the ability to implement very flexible layout requirements. This is mainly achieved by the built-in programming language in connection with the query options of the Publisher.
The program execution runs simultaneously with the creation of the PDF. Therefore, the speedata Publisher can react very flexibly to the input data. Queries such as “Is there still enough space for this object?” are thus possible. This distinguishes the Publisher from other software for creating PDF files. Basic programming knowledge is required to use the full functionality of the Publisher. The programming language has been kept as simple as possible to maintain the readability of the layout. |
All variables are globally visible. This means that a variable never becomes invalid. Here’s an example:
<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>
The global visibility is necessary because the program execution in the layout sometimes “jumps back and forth”. At the end of the page the content of <AtPageShipout>
is executed in the current page type. It must also be possible to access the variables there.
In variables not only simple values can be stored, but also complex XML sections:
<Record element="data">
<SetVariable variable="foo">
<Paragraph>
<Value>Hello world!</Value>
</Paragraph>
</SetVariable>
<PlaceObject>
<Textblock>
<Copy-of select="$foo"/>
</Textblock>
</PlaceObject>
</Record>
Results in the expected issue of "Hello world!". A use case is to store table width declarations:
<SetVariable variable="tablecolumns">
<Columns>
<Column width="1cm"/>
<Column width="4mm"/>
<Column width="1cm"/>
</Columns>
</SetVariable>
and then use them in several tables:
<PlaceObject>
<Table>
<Copy-of select="$tablecolumns"/>
<Tr>
..
</Tr>
</Table>
</PlaceObject>
The one-time definition and reuse saves typing work and reduces the sources of error.
The contents of variables containing child elements are evaluated immediately. I.e. in the following case
<SetVariable variable="tmp">
<Paragraph><Value select="$greeting"/></Paragraph>
</SetVariable>
the variable greeting
must already be defined.
Subsequent modification of the output in the paragraph does not happen.
It follows that the variables must not contain any output commands, such as <PlaceObject>
or <ClearPage>
, since these would take effect immediately.
There is an option to defer this evaluation time to the application at <Copy-of>
(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>
Here, only when <Copy-of>
is used, the contents of $tmp
are evaluated, then output.
This also works with output commands:
<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" />
generates twice the output 'Hello User' on a separate page.
<Copy-of> was already used before. This copies the contents of the variable to the current position. The contents of the variables remain unchanged during copying.
variable =
Copy-of variable
new value
This appends the new value to the previous ones.
<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 you can perform simple if-then queries. The syntax for this is if (condition) then … else …
:
<PlaceObject>
<Textblock>
<Paragraph>
<Value select="
if (sd:odd(sd:current-page()))
then 'recto' else 'verso'"/>
</Paragraph>
</Textblock>
</PlaceObject>
Case distinctions correspond to the construction switch/case from C-like programming languages. They are applied in the Publisher as follows:
<Switch>
<Case test="$i = 1">
...
</Case>
<Case test="$i = 2">
...
</Case>
...
<Otherwise>
...
</Otherwise>
</Switch>
All commands within the first possible <Case> case are processed if the condition in test applies there. In test, an XPath expression is expected that returns true()
or false()
, like $i = 1
, and if no case occurs, the contents of the optional <Otherwise>
section will be executed.
There are various loops in the speedata Publisher. The simple variant is <Loop>
:
<Loop select="10">
...
</Loop>
This command executes the enclosed commands as many times as the expression in select results in. The loop counter is stored in the variable _loopcounter, unless otherwise set by variable="…"
.
Besides the simple loop there are also loops with conditions:
<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>
The expression $i <= 4
must be read as $i <= 4
, because the opening angle bracket at this point in the XML is a syntax error. The loop above is executed as often as the content of the variable i is less than or equal to 4. Don’t forget to increase the variable as well, otherwise an endless loop is created.
In addition to the while loop, there is also the until loop, which works in the same way:
<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>
It is possible to define functions with the new XPath module:
<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>
The functions can also contain more complex expressions:
<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>
The namespace for the function must be defined in the root element (here: xmlns:fn="…"
). Variables defined in the function remain local, i.e. are not visible in other program parts.
The speedata Publisher does not offer direct support for data structures such as arrays (fields) or dictionaries (hashes or dictionaries). These can be simulated using variables. The field a1, a2, …, ai could be filled as follows:
<SetVariable variable="{ concat('a',1) }" select="'Value for a1'"/>
<SetVariable variable="{ concat('a',2) }" select="'Value for a2'"/>
...
Of course, a1 could also be specified directly as the variable name. In this example, both the prefix and the suffix could be created dynamically:
<SetVariable variable="prefix" select="'a'" />
<SetVariable variable="{ concat($prefix,1) }" select="'Value for a1'"/>
<SetVariable variable="{ concat($prefix,2) }" select="'Value for a2'"/>
...
The read access goes via sd:variable(…)
:
<SetVariable variable="prefix" select="'a'" />
<Message select="sd:variable($prefix,1)"/>
<Message select="sd:variable($prefix,2)"/>
...
The function sd:variable()
concatenates all arguments as a string and takes the result as variable name.