von Patrick Gundlach |

Feature der Woche: Datenverarbeitung

Kein Feature an sich, aber eine Erklärung wert: Wie funktioniert die Datenverarbeitung im speedata Publisher? Ein etwas umfangreicherer Beitrag, es lohnt sich.

Datenquelle: XML – wohlgeformt und strukturiert

Die erste Voraussetzung ist, dass die Datenquelle im XML-Format vorliegt. Andere Formate werden mit dem Publisher nicht verarbeitet. In der Praxis spielt das keine Rolle, weil quasi alles nach XML konvertiert werden kann.

Häufig wird die Frage gestellt: wie muss denn das XML aufgebaut sein? Gibt es Vorgaben? Die Antwort darauf ist einfach: es gibt keine Vorgaben, außer dass das XML den Regeln entsprechen muss (korrekte Schachtelung von Tags etc.). Dann gibt es sinnvolle Strukturierungsempfehlungen:

  1. Das XML sollte in der Leserichtung angeordnet sein. D.h. die Daten für die ersten Seiten »oben«, die Daten für die letzten Seiten »unten« im XML-Baum.

  2. Die Daten sollen dann im XML-Baum vorkommen, wenn sie benötigt werden. Datenverarbeitung im Publisher kostet Zeit und Speicher, so dass die Informationen dort vorhanden sein soll, wo sie benötigt werden. Es gibt natürlich Ausnahmen. Beispielsweise können globale Einstellungen (Farben, zu übersetzende Texte und so weiter) am Anfang der Datei vorkommen.

  3. Unterschiedliche Darstellungen (Varianten) müssen anhand der Daten ablesbar sein. Wenn z.B. ein Seitenwechsel bei einer neuen Artikelgruppe (im Produktkatalog) passieren soll, muss in den Daten ein Wechsel der Artikelgruppe erkennbar sein. Andernfalls müsste man raten, und darin sind Computer noch nicht gut genug.

  4. Die Daten sollten möglichst strukturiert sein. Wenn zum Beispiel ein Produktkatalog erzeugt wird, kann man vielleicht die Artikelgruppe anhand der Artikelnummer feststellen. Vielleicht sind die Artikelnummern in der Form 123-12345 und die ersten drei Ziffern stellen die Artikelgruppe dar. Jetzt gibt es zwei Möglichkeiten: entweder der Publisher erkennt die unterschiedliche Artikelgruppe anhand Mustererkennung (z.B. reguläre Ausdrücke), oder die Daten werden schon so strukturiert, dass es keiner Erkennung bedarf.

Ein einfaches Beispiel für die Anordnung:

<Produktdaten>
  <GlobaleEinstellungen>
    ...
  </GlobaleEinstellungen>
  <Artikelgruppe name="Innenleuchten" nummer="123">
    <Artikel nummer="123-12345">
      <Eigenschaft1>...</Eigenschaft1>
      <Eigenschaft2>...</Eigenschaft2>
    </Artikel>
    <Artikel nummer="123-12346">
      <Eigenschaft1>...</Eigenschaft1>
      <Eigenschaft2>...</Eigenschaft2>
    </Artikel>
  </Artikelgruppe>
  <Artikelgruppe name="Außenleuchten" nummer="124">
    <Artikel nummer="124-23456">
      <Eigenschaft1>...</Eigenschaft1>
      <Eigenschaft2>...</Eigenschaft2>
    </Artikel>
    <Artikel nummer="124-54321">
      <Eigenschaft1>...</Eigenschaft1>
      <Eigenschaft2>...</Eigenschaft2>
    </Artikel>
  </Artikelgruppe>
</Produktdaten>

Redundanz schadet hier nicht, im Gegenteil. Da im Beispiel die Artikelgruppe eine eindeutige Ziffernfolge (123 bzw. 124) hat, würde bei den Artikeln die letzten fünf Ziffern ausreichen. Man kann ja die Zahl aus Artikelgruppe/@nummer, - und Artikel/@nummer selbst zusammenbauen. Um sich den Schritt zu sparen, speichert man am Artikel einfach die vollständige Nummer.

Wie greife ich im Layout auf die Daten zu?

Da die Datendatei beliebig strukturiert sein kann, bedarf es spezieller Befehle, um auf die Daten zugreifen zu können. Im Folgenden gehe ich von folgender Datendatei aus:

<katalog>
  <artikel nr="12345" preis="99,95" ve="1">
    <beschreibung>Text</beschreibung>
    <bild>art12345.pdf</bild>
  </artikel>
  <artikel nr="56789" preis="45,95" ve="5">
    <beschreibung>Text für 56789</beschreibung>
    <bild>art56789.pdf</bild>
  </artikel>
</katalog>

Diese Datendatei wird unter dem Namen data.xml gespeichert, damit der Publisher sie finden kann. Wenn sie einen anderen Namen hat, muss man entweder beim Aufruf von sp die Option --data mitgeben, z.B.

sp --data katalogdaten.xml

oder man erzeugt eine Konfigurationsdatei publisher.cfg, die folgende Zeile enthält:

data=katalogdaten.xml

Dass die Konfigurationsdatei auch einen anderen Namen haben kann, erwähne ich jetzt nicht, das würde den Rahmen sprengen.

Die Layout-Datei (Name: layout.xml, kann, wie die Datendatei, auch einen anderen Namen enthalten. Der Konfigurationsparameter hierfür ist layout.) wird beim Einlesen ausgeführt: alle Befehle wie <Record> werden für die spätere Verarbeitung gespeichert, alle anderen haben sofortige Wirkung. D.h. wenn ein Befehl <DefineColor> auf der obersten Ebene im Layoutregelwerk enthalten ist, ist er ausgeführt, bevor die eigentliche Datenverarbeitung beginnt.

Vielleicht fange ich zur Erläuterung einfach mal mit einer minimalen Layout-Datei an:

<Layout xmlns="urn:speedata.de:2009/publisher/en"
  xmlns:sd="urn:speedata:2009/publisher/functions/en">

</Layout>

Lässt man diese durchlaufen (sp auf der Kommandozeile aufrufen), dann wird folgende Fehlermeldung ausgegeben:

Kann den Befehl »Datensatz« für das Wurzelelement nicht finden.

Das heißt, der Publisher weiß nicht, wo er anfangen soll zu arbeiten. Also erweitere ich das Layout von oben:

<Layout xmlns="urn:speedata.de:2009/publisher/en"
  xmlns:sd="urn:speedata:2009/publisher/functions/en">

  <Record element="katalog">

  </Record>

</Layout>

Nach dem Aufruf von sp passiert – gar nichts. Es wird keine Seite erzeugt, kein Fehler ausgegeben, der Publisher beendet sich einfach:

...
Loading layout instructions "/home/example/layout-en.xml"
Loading data file "/home/example/data.xml"
Stop processing data
0 errors occurred
Duration: 0.158941 seconds
node_mem_usage=1 glue, 3 glue_spec, 1 dir, 1 user_defined
luastate_bytes=0

No pages of output.
Transcript written on publisher.log.
Total run time: 209.499431ms

Die für die Verarbeitung notwendige Struktur ist folgende:

<Layout xmlns="urn:speedata.de:2009/publisher/en"
  xmlns:sd="urn:speedata:2009/publisher/functions/en">

  <Record element="katalog">
    <!-- ... Befehle, die vor den ersten Kindelementen
         ausgeführt werden sollen, z.B. Titelseite oder
         Inhaltsverzeichnis erzeugen -->

    <!-- und hier werden alle Kindelemente einzeln aufgerufen -->
    <ProcessNode select="*"/>
  </Record>

  <Record element="artikel">
    <!-- Für jedes Kindelement 'artikel' werden diese Befehle
         ausgeführt. Der »Fokus« ist jetzt bei einem Artikel,
         man kann jetzt auf Attribute und Kindelemente zugreifen. -->
  </Record>

</Layout>

Innerhalb des unteren <Record> kann man nun auf Kindelemente und Attribute zugreifen. Beispiele:

  • @nr ergibt im ersten Aufruf die Zeichenkette 12345, im zweiten Durchlauf 56789.
  • beschreibung ergibt eine Sequenz mit einem Element, dem Inhalt Text (erster Artikel).
  • bild/@wichtig ist im ersten Fall ‘ja’, im zweiten Fall leer.

Alternativ zur Vorgehensweise mit <ProcessNode> und dem Gegenstück <Record> kann auch mit <ForAll> auf Kindelemente zugegriffen werden. Ich werfe mal ein vollständiges Beispiel in den Blog:

<Layout xmlns="urn:speedata.de:2009/publisher/en"
  xmlns:sd="urn:speedata:2009/publisher/functions/en">

  <Record element="katalog">
    <PlaceObject>
      <Table stretch="max">
        <Tablehead>
          <Tr backgroundcolor="gray">
            <Td><Paragraph><Value>Artikelnummer</Value></Paragraph></Td>
            <Td><Paragraph><Value>Bild</Value></Paragraph></Td>
            <Td><Paragraph><Value>Beschreibung</Value></Paragraph></Td>
            <Td><Paragraph><Value>Preis</Value></Paragraph></Td>
          </Tr>
        </Tablehead>
        <ForAll select="artikel">
          <Tr>
            <Td><Paragraph><Value select="@nr"/></Paragraph></Td>
            <Td><Image file="{bild}" width="4"/></Td>
            <Td><Paragraph><Value select="beschreibung"/></Paragraph></Td>
            <Td><Paragraph><Value select="@preis"/></Paragraph></Td>
          </Tr>
        </ForAll>
      </Table>
    </PlaceObject>
  </Record>

</Layout>

Eigentlich ist das Layout selbsterklärend. Es wird eine Tabelle ausgegeben mit einer Kopfzeile (die auf jeder Seite wiederholt wird) und mehreren Tabellenzeilen, für jeden Artikel eine. Innerhalb des <ForAll> kann auf die Attribute und Kindelemente von jedem Artikel zugegriffen werden, genau wie im oberen Beispiel.

Eine kleine Besonderheit ist im Beispiel enthalten. Normalerweise erwartet der Befehl <Image> bei der Angabe der Bilddatei einen festen Wert. Da ich die Bilddatei aber aus den Daten entnehmen möchte (Kindelement bild), muss ich kurzzeitig in den XPath-Modus springen. Das mache ich mit den geschweiften Klammern, alles was innerhalb der Klammern steht, wird als XPath-Ausdruck interpretiert.

In der Serie »Feature der Woche« beschreibe ich jeden Montag mehr oder weniger nützliche Eigenschaften des Publishers. Kommentare gerne an mich per E-Mail oder einfach im Kommentarfeld.