Lua-Filter / Datenvorverarbeitung

Manchmal möchte man vor der eigentlichen PDF-Erstellung die Daten in ein anderes Format überführen oder auf Korrektheit überprüfen. Dafür gibt es die Möglichkeit (seit Version 3.1.9), ein Lua-Skript vor dem eigentlichen Publishing-Lauf auszuführen. Lua ist eine einfache, aber mächtige Programmiersprache, die dafür vorgesehen ist, in andere Programme als Skriptsprache einzubauen.

Im Beispiele-Repository gibt es zwei Anwendungsfälle zum anschauen.

In den älteren Versionen des Publishers gab es die Möglichkeit, einen XProc-Filter zu benutzen. Das funktioniert aus Kompatibilitätsgründen noch, wird aber nicht weiter beschrieben.

Aufruf des Lua-Skripts

Gestartet wird der Filter entweder über die Kommandozeile

sp --filter myfile.lua

oder über die Konfigurationsdatei, die folgenden Eintrag enthalten muss:

filter=myfile.lua

Das angegebene Lua-Skript wird ausgeführt, bevor die Erzeugung der PDF-Datei beginnt. Daher ist die Hauptanwendung dieses Pre-Processing die Transformation von Daten in ein Format, das für den speedata Publisher geeignet ist. So können CSV oder Excel-Dateien nach XML konvertiert und anschließend die PDF-Erzeugung gestartet werden. Daneben ist es auch möglich, Daten zu validieren.

Im Folgenden werden die Anwendungsmöglichkeiten beschrieben, ganz zum Schluss gibt es nochmals eine Übersicht über die eingebauten Funktionen und Methoden.

Validieren von Eingabedaten

Um RELAX NG-Dateien zu validieren gibt es mehrere Möglichkeiten. Neben einen Variante, direkt im XML Editor das Schema zu validieren (siehe den entsprechenden Abschnitt im Handbuch) gibt es die Möglichkeit, ein externes Programm dafür zu nutzen. Eines davon ist Jing. Es wird mit dem Publisher mitgeliefert.

In der Lua-Vorverarbeitung gibt es eine Funktion, die die Validierung übernimmt:

runtime = require("runtime")
runtime.validate_relaxng(xmldatei,schemadatei)

Die Funktion liefert im Fehlerfall false und die Fehlermeldung zurück. Beispiel:

-- die Pfade natürlich anpassen!
runtime = require("runtime")
ok, msg = runtime.validate_relaxng("layout.xml","../schema/layoutschema-de.rng")
if not ok then
    print(msg)
    os.exit(-1)
end

Das speichert man in einer Datei, z.B. validate.lua und ruft dann den Publisher mit

sp --filter validate.lua

auf. Vor jedem Lauf wird nun überprüft, ob die Layoutdatei dem Schema entspricht und nur dann wird mit der Verarbeitung fortgefahren.

Man kann nicht nur die Layoutdatei auf Korrektheit überprüfen, sondern auch alle anderen XML-Dateien. Hierzu muss man sich jedoch ein eigenes RELAX NG-Schema erstellen. Eine Anleitung dazu ist unter https://speedata.github.io/relaxngtutorial-de/ verfügbar. Je nach Datendatei ist das auch recht einfach. Insbesondere wenn man immer wieder Daten aus fremden Quellen geliefert bekommt, kann man sich so sicher sein, dass die gewünschte Struktur eingehalten wird.

Ausführen einer Transformation

Eine XSLT-Transformation verarbeitet eine XML-Datei mit einem XSLT-Skript und erzeugt eine Ausgabedatei. XSLT ist eine Programmiersprache, die für die Verarbeitung von XML-Daten entworfen wurde. Häufig sind Daten aus PIM-Systemen oder Datenbanken nicht in der Form, die für den Publisher optimal sind. Mit XSLT kann man solche Ausgangsdaten verarbeiten und verändern.

Das Programm saxon (mit dem speedata Publisher mitgeliefert) kann ein XSLT-Skript ausführen. Der Aufruf ist folgender:

runtime = require("runtime")
runtime.run_saxon(XSL,Quelldatei,Ausgabedatei,Parameter)

Die Parameter haben die Form keyword=value und werden durch Leerzeichen getrennt.

runtime = require("runtime")
ok, msg = runtime.run_saxon("transformation.xsl","quelldatei.xml","data.xml")

-- den Publishing-Prozess abbrechen, wenn die Transformation fehl schlägt.
if not ok then
    print(msg)
    os.exit(-1)
end

Erstellen von XML-Dateien

Man kann die zu verarbeitende Datendatei auch mit dem Lua-Skript erstellen. Dazu gibt es im Modul xml die Funktion encode_table(), die aus einer Lua-Tabelle eine XML-Datei erstellt.

Das Skript

xml = require("xml")
tbl = {
    ["_type"] = "element",
    ["_name"] = "data",
    {
       ["_type"] = "element",
       ["_name"] = "child",
       "Hallo Welt",
    }
}


ok, msg = xml.encode_table(tbl)
if not ok then
    print(msg)
    os.exit(-1)
end

erzeugt die XML-Datei

<data><child>Hallo Welt</child></data>

die für den folgenden Publisher-Lauf zur Verfügung steht. Das ist besonders dann von Nutzen, wenn die Datenquelle nicht in XML vorliegt.

Verarbeiten von Excel-Dateien

Ein häufig anzutreffender Anwendungsfall ist, dass die Daten für die Verarbeitung aus Excel-Dateien gelesen werden sollen. Dazu gibt es im Modul xlsx die Funktion open(), die eine vorhandene Datei öffnet:

xlsx = require("xlsx")
spreadsheet, err = xlsx.open("myfile.xlsx")
if not spreadsheet then
    print(err)
    os.exit(-1)
end

Das Objekt spreadsheet beinhaltet die einzelnen Arbeitsblätter (Worksheets). Die Anzahl der Arbeitsblätter lässt sich über den length-Operator feststellen und die einzelnen Arbeitsblätter per Index (1 ist das erste Arbeitsblatt).

numWorksheets = #spreadsheet
ws = spreadsheet[1]

Mit dem Objekt ws kann direkt auf die Zelleninhalte zugegriffen werden. Dazu wird es als Funktion aufgerufen und liefert eine Zeichenkette zurück. Die erste Zelle oben links hat die Koordinaten 1,1, die erste Zelle in der zweiten Zeile 1,2 und so weiter.

cell1 = ws(1,1)
cell2 = ws(1,2)

Den Namen des Arbeitsblattes kann man über den Wert name ermitteln:

name = ws.name

Lesen von CSV-Dateien

Ähnlich wie bei Excel-Dateien kann man auch CSV-Dateien direkt einlesen. Die Struktur ist jedoch einfacher, da es nur ein »Arbeitsblatt« gibt.

csv = require("csv")
csvtab, msg = csv.decode("myfile.csv",{columns = {1,2,3}})
if not csvtab then
    print(msg)
    os.exit(-1)
end

Der zweite Parameter bei csv.deocde() ist optional. In diesem Beispiel werden nur die Spalten 1, 2 und 3 ausgegeben. Das Ergebnis ist eine Tabelle aus Zeilen. Jede Zeile ist wiederum eine Tabelle, die die einzelnen Werte der Zeile enthält.

Im Beispiele-Repository wird gezeigt, wie man aus der CSV-Datei eine XML-Datei erstellen kann.

Funktionsreferenz

runtime

In diesem Modul werden alle Funktionen und Einstellungen gesammelt, die eher allgemeiner Natur sind.

projectdir

Eine Zeichenkette, die das aktuelle Projektverzeichnis enthält (das Verzeichnis mit der layout.xml bzw. publisher.cfg-Datei)

projectdir

Eine Tabelle mit alle Variablen, die per -v auf der Kommandozeile oder vars=.​.. in der Konfigurationsdatei angegeben wurden.

validate_relaxng(‹xmldatei›,‹schemadatei›)

Diese Funktion validiert die angegebene XML-Datei mit dem im zweiten Parameter angegebenen RELAX NG (XML-Syntax) Schema. Die Rückgabe ist ein boolean-Wert, der true ist, wenn der Befehl fehlerfrei ausgeführt wurde. Ansonsten wird ein zweiter Rückgabewert (string) zurück gegeben, der die Fehlermeldung enthält.

run_saxon(‹XSL›,‹Quelldatei›,‹Ausgabedatei›,‹Parameter›)

Diese Funktion ruft das zum Publisher mitgelieferte Programm saxon auf. Sie erwartet drei string-Argumente (das Stylesheet, die Eingabe- und die Ausgabedatei) und ein optionales Argument das als Parameter an saxon übergeben wird. Die Rückgabe ist ein boolean-Wert, der true ist, wenn der Befehl fehlerfrei ausgeführt wurde. Ansonsten wird ein zweiter Rückgabewert (string) zurück gegeben, der die Fehlermeldung enthält. Die Parameter haben die Form keyword1=value1 keyword2=value2 (mit Leerzeichen getrennt).

xml

Mit dem XML-Modul werden XML-Dateien erzeugt. Zuerst muss die XML-Struktur in einer Lua-Tabelle erzeugt werden, anschließend wird sie mit encode_table() unter dem Namen data.xml gespeichert.

xml.encode_table(‹tabelle›,[dateiname])

Erzeugt eine XML-Datei (data.xml bzw. der optional gegebene Dateiname) der übergebenen Tabelle. Rückgabewert 1 ist ein bool (success), Wert 2 ist die Fehlermeldung, wenn der erste Wert false ist. Die Tabelle hat folgende Struktur:

element = {
    ["_type"] = "element",
    ["_name"] = "elementname",
    attribute1 = "value1",
    attribute2 = "value2",
    child1,
    child2,
    child3,
    ...
}

child1, .​.. sind entweder Zeichenketten, Elemente oder Kommentare. Kommentare haben folgende Form:

comment = {
         _type = "comment",
         _value = " Das ist ein Kommentar! "
   }

CSV

CSV-Dateien

decode(‹dateiname›,‹parameter›)

Liest eine CSV-Datei ein. Der Rückgabewert ist eine Tabelle bzw. im Fehlerfall false und eine Fehlermeldung.

Die parameter werden in einer Tabelle kodiert:

charset

Wenn die CSV-Datei Latin-1 kodiert ist, muss dieser Wert auf ISO-8859-1 stehen. Andere Kodierungen auf Anfrage.

separator

Entweder ein Komma (Voreinstellung), ein Semikolon oder das entsprechend genutzte Trennzeichen.

columns

Eine Tabelle, die die gewünschten Spalten in ihrer Reihenfolge enthält. Z.B. {3,2,1} für die ersten drei Spalten in umgekehrter Reihenfolge.

xlsx

Liest eine Excel-Datei ein.

open(‹dateiname›)

Öffnet die angegebene Datei. Der Rückgabewert ist ein spreadsheet-Objekt bzw. im Fehlerfall false und eine Fehlermeldung.

Das spreadsheet-Objekt beinhaltet die einzelnen Arbeitsblätter. Die Anzahl der Arbeitsblätter kann mit dem #-Operator ermittelt werden. Auf die einzelnen Arbeitsblätter kann man mit dem Index-Operator [] zugreifen, wobei das erste Arbeitsblatt den Index 1 hat.

Die einzelnen Arbeitsblätter können als Funktion mit zwei Parametern benutzt werden (siehe Beispiel oben). Die Parameter sind die x und y Koordinaten der auszulesenden Zelle, die erste Zelle oben links hat die Koordinate 1,1. Die Ausmaße des Inhalts kann über die Parameter minrow, maxrow, mincol und und maxcol ermittelt werden. Der Name ist im Parameter name enthalten.

string_to_date(‹string›)

Wandelt eine Zahl (kodiert als Zeichenkette) in ein Datum um. Rückgabe ist eine Tabelle mit den Schlüsseln day, month, year, hour, minute und second. Beispiel: xlsx.string_to_date("43458") ergibt

{
  ["day"] = "24"
  ["month"] = "12"
  ["year"] = "2018"
  ["hour"] = "0"
  ["minute"] = "0"
  ["second"] = "0"
}