Verzeichnisse erstellen

Dieses Kapitel widmet sich der Erstellung von Verzeichnissen. Es gibt prinzipiell zwei Varianten, ein Verzeichnis zu erzeugen. Daher handelt der nächste Abschnitt von dem eingebauten Mechanismus der Marker (<Mark>), die darauf folgenden Abschnitte von den Weg per <Element> und <Attribute> und dem manuellen Speichern der Daten für einen weiteren Durchlauf.

Damit der Publisher mehrfach durchläuft, muss der Parameter runs auf der Kommandozeile bzw. in der Konfigurationsdatei gesetzt werden, beispielsweise mit sp --runs=2 (Kommandozeile) bzw. runs = 2 (Konfigurationsdatei). Im Abschnitt Inhaltsverzeichnis in einem Durchlauf erstellen wird ein Mechanismus gezeigt, wie in manchen Fällen ein Verzeichnis in nur einem Durchlauf erzeugt werden kann.

Marker

Marker sind unsichtbare Zeichen, die in den Text eingefügt werden. Diesen Zeichen wird immer ein Name zugeordnet. Nach Ausgabe des Zeichens auf einer Seite kann man den Publisher nach der Seitenzahl fragen. Der Aufbau ist folgendermaßen:

<PlaceObject>
  <Textblock>
    <Action>
      <Mark select="'textstart'"/>
    </Action>
    <Paragraph>
      <Value>
      Row
      Row
      Row
      Row
       </Value>
    </Paragraph>
  </Textblock>
</PlaceObject>

Nach Ausgabe der Seite ist nun mit sd:pagenumber('textstart') die Seitennummer ermittelbar.

Die Marker werden automatisch in einer internen Hilfsdatei publisher.aux gespeichert, so dass bei einem weiteren Durchlauf auf die Seitenzahlen über sd:pagenumber() bereits vor dem Platzieren der Seite verfügbar sind. Als Beispiel wird eine einfache Textstruktur genommen (es ist dasselbe Beispiel wie im nächsten Abschnitt):

<data>
  <chapter title="Foreword">
    <text>...</text>
  </chapter>
  <chapter title="Introduction">
    <text>...</text>
  </chapter>
  <chapter title="Conclusion">
    <text>...</text>
  </chapter>
</data>

Die mit dem folgenden Layout ausgegeben wird:

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

  <DefineFontfamily name="title" fontsize="18" leading="20">
    <Regular fontface="sans"/>
  </DefineFontfamily>

  <Record element="data">
    <!-- This point will be completed further below -->
    <ProcessNode select="chapter"/>
  </Record>

  <Record element="chapter">
    <PlaceObject>
      <Textblock>
        <Action>
          <Mark select="@title"/>
        </Action>
        <Paragraph fontfamily="title">
          <Value select="@title"/>
        </Paragraph>
        <Paragraph>
          <Value select="text"/>
        </Paragraph>
      </Textblock>
    </PlaceObject>
    <ClearPage/>
  </Record>
</Layout>

Das Grundgerüst für die Marker. Die Stelle für das Inhaltsverzeichnis wird später erweitert (siehe Kommentar).

Der Publisher ordnet nun die Kapitel der Seitenzahl zu. Man kann nun das Verzeichnis im nächsten Durchlauf ausgeben:

  <PlaceObject>
    <Table padding="5pt">
      <ForAll select="chapter">
        <Tr>
          <Td><Paragraph><Value select="@title"/></Paragraph></Td>
          <Td><Paragraph>
               <Value select="sd:pagenumber(@title)"/>          </Paragraph></Td>
        </Tr>
      </ForAll>
    </Table>
  </PlaceObject>
  <ClearPage/>

Dieser Teil wird in das Layout oben eingefügt, um das Inhaltsverzeichnis auszugeben.

① In einem weiteren Durchlauf stehen die Seitenzahlen zur Verfügung, bevor die eigentlichen Kapitel auf die Folgeseiten geschrieben werden.

Verzeichnisse erstellen (XML-Struktur)

Im vorherigen Kapitel wurden Verzeichnisse über die Marker erstellt. In diesem Kapitel wird ein Mechanismus benutzt, der etwas mehr manuelle Arbeit bedeutet, aber flexibler ist.

Der speedata Publisher kann beliebige Verzeichnistypen erstellen. Ob Inhaltsverzeichnis, Artikelliste oder Stichwortindex – alle Listen funktionieren nach demselben Prinzip: die notwendigen Daten (z. B. Seitenzahlen, Artikelnummern) werden explizit in einer eigenen Datenstruktur gespeichert, auf Festplatte geschrieben. Beim nächsten Lauf des Publishers werden diese Daten eingelesen und stehen zur Verfügung.

Schritt 1: Sammeln der Informationen

Die beiden Befehle <Element> und <Attribute> dienen zur Strukturierung von Daten, die während der Verarbeitung gelesen werden. Das ist schon im Kapitel Erzeugen von XML-Strukturen beschrieben worden. Mit diesen Befehlen lassen sich neue XML Datensatzdateien erzeugen. Folgende Struktur könnte für eine Artikelliste sinnvoll sein:

<articlelist>
  <article number="1" page="10"/>
  <article number="2" page="12"/>
  <article number="3" page="14"/>
</articlelist>

Um diese Struktur im Layoutregelwerk zu erstellen, muss sie aus den Befehlen <Element> und <Attribute> wie folgt zusammengesetzt werden:

<Element name="articlelist">
  <Element name="article">
    <Attribute name="number" select="1"/>
    <Attribute name="page" select="10"/>
  </Element>
  <Element name="article">
    <Attribute name="number" select="2"/>
    <Attribute name="page" select="12"/>
  </Element>
  <Element name="article">
    <Attribute name="number" select="3"/>
    <Attribute name="page" select="14"/>
  </Element>
</Element>

Schritt 2: Speichern und Laden der Informationen

Mit dem Befehl <SaveDataset> wird diese Struktur auf die Festplatte gespeichert und mit <LoadDataset> wird sie wieder geladen. Existiert die Datei beim Laden nicht, so wird kein Fehler gemeldet, da es sich um den ersten Durchlauf handeln könnte, wo die Datei naturgemäß noch nicht existiert.

Schritt 3: Verarbeiten der Informationen

Direkt nach dem Laden wird die XML-Verarbeitung mit dem ersten Element der gerade geladenen Struktur fortgesetzt, im Beispiel oben würde nach dem folgenden Befehl im Layoutregelwerk gesucht:

<Record element="articlelist">
  ...
</Record>

Das heißt, dass die eigentliche Datenverarbeitung zeitweilig unterbrochen und mit dem neuen Datensatz aus <LoadDataset> fortgeführt wird.

Beispiel

Das ist dasselbe Beispiel wie aus dem vorherigen Abschnitt (Verzeichnisse erstellen). Als Beispiel wird eine einfache Datendatei genommen:

<data>
  <chapter title="Foreword">
    <text>...</text>
  </chapter>
  <chapter title="Introduction">
    <text>...</text>
  </chapter>
  <chapter title="Conclusion">
    <text>...</text>
  </chapter>
</data>

Die mit dem folgenden Layout ausgegeben wird:

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

  <DefineFontfamily name="title" fontsize="18" leading="20">
    <Regular fontface="sans"/>
  </DefineFontfamily>


  <Record element="data">
    <ProcessNode select="chapter"/>
  </Record>

  <Record element="chapter">
    <PlaceObject>
      <Textblock>
        <Paragraph fontfamily="title">
          <Value select="@title"/>
        </Paragraph>
        <Paragraph>
          <Value select="text"/>
        </Paragraph>
      </Textblock>
    </PlaceObject>
    <ClearPage/>
  </Record>
</Layout>

Grundgerüst für die Ausgabe eines Inhaltsverzeichnisses über die XML-Struktur. Der Code wird im Laufe des Abschnitts ergänzt.

① Die Ausgabe des eigentlichen Inhaltsverzeichnisses wird hier eingefügt.

② Der Abschnitt data wird erweitert um das Laden und Speichern der XML-Daten für das Verzeichnis

③ Hier wird Code eingefügt, der die XML-Struktur zusammenbaut (siehe unten)

Nun wird eine Variable definiert (entries), die die Informationen über die Kapitelanfänge enthält. Die Zielstruktur soll folgende sein:

<tableofcontent>
  <entry chaptername="Foreword" page="2"/>
  <entry chaptername="Introduction" page="3"/>
  <entry chaptername="Conclusion" page="4"/>
</tableofcontent>

Im Abschnitt chapter (Punkt 3 im Layout oben) wird oben der Code eingefügt, um die Variable entries mit den Inhalten zu füllen:

  <Record element="chapter">
    <SetVariable variable="entries">
      <Copy-of select="$entries"/>
      <Element name="entry">
        <Attribute name="chaptername" select="@title"/>
        <Attribute name="page" select="sd:current-page()"/>
      </Element>
    </SetVariable>

    <PlaceObject>
    ...

Es wird also mittels <Copy-of> jeweils etwas neues zu einer Variablen hinzugefügt.

Die Struktur muss am Anfang geladen und am Ende des Durchlaufs gespeichert werden, damit sie immer aktuell ist. Wenn die Datei toc noch nicht vorhanden ist, wird der Befehl einfach übergangen. Der neue Abschnitt data sieht nun so aus und wird an die Stelle 2 im Layout oben eingefügt (anstelle des dort vorhandenen Records):

  <Record element="data">
    <LoadDataset name="toc"/>
    <SetVariable variable="entries"/>
    <ProcessNode select="chapter"/>
    <SaveDataset name="toc" elementname="tableofcontents"
                 select="$entries"/>
  </Record>

Beim nächsten Durchlauf greift der Befehl <LoadDataset> und öffnet die zuvor gespeicherte XML-Datei. Im Layoutregelwerk wird ein Abschnitt für das Element tableofcontents gesucht, das ja das Wurzelelement der gespeicherten Datei ist. Das muss noch in das Layoutregelwerk eingefügt werden (Stelle 1 im Layout oben):

<Record element="tableofcontents">
  <PlaceObject>
    <Table padding="5pt">
      <ForAll select="entry">
        <Tr>
          <Td><Paragraph><Value select="@chaptername"/></Paragraph></Td>
          <Td><Paragraph><Value select="@page"/></Paragraph></Td>
        </Tr>
      </ForAll>
    </Table>
  </PlaceObject>
  <ClearPage/>
</Record>

Es wird eine Tabelle ausgegeben mit einer Zeile für jedes Kindelement entry. Durch den anschließenden Seitenumbruch wird der nachfolgende Text nach hinten geschoben. Dadurch muss man das Dokument drei Mal durchlaufen lassen, bevor das Inhaltsverzeichnis korrekt ist:

  1. Im ersten Durchlauf wird die Datenstruktur zusammengestellt.
  2. Anschließend kann das Inhaltsverzeichnis erstellt werden, durch den Seitenumbruch verschiebt sich der Inhalt um eine Seite nach hinten, die Datenstruktur wird entsprechend aktualisiert.
  3. Erst im dritten Durchlauf ist das Inhaltsverzeichnis korrekt.

Wenn man weiß, dass das Inhaltsverzeichnis nur eine Seite in Anspruch nehmen wird, dann kann man den Seitenumbruch auch schon im ersten Durchlauf einfügen. Damit spart man sich einen Durchlauf.

Sortierung von Stichwortverzeichnissen

In der Regel sind Stichwortverzeichnisse am Ende eines Dokuments zu finden, um in gedruckten Werken relevante Seiten schnell aufzufinden. Bei diesen Stichworten kann es sich um Wörter oder auch um Artikelnummern oder andere Bezeichnungen handeln.

Im Gegensatz zum Inhaltsverzeichnis (das meist vorne in einer Publikation ist), müssen die Daten nur zusammengestellt werden, ein Zwischenspeichern für den nächsten Lauf entfällt in der Regel.

Beispiel

stichwortverzeichnis
Stichwortverzeichnis aus dem Beispiel

Die Beispiele sind naturgemäß immer etwas konstruiert, das ist hier ganz besonders der Fall. Der Index wird in der Praxis natürlich anders zusammengestellt. Da hier nur die Sortierung gezeigt werden soll, wird das Stichwort und die Seitenzahl vorgegeben:

<data>
  <keyword word="Giraffe" page="1"/>
  <keyword word="Garage" page="2"/>
  <keyword word="Greeting" page="3"/>
  <keyword word="Elevator" page="4"/>
</data>

Die Layoutdatei besteht aus drei Abschnitten, die einzeln erläutert werden.

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

  <Record element="data">    ...
  </Record>

  <Record element="keyword">    ...
  </Record>

  <Record element="index">    ...
  </Record>
</Layout>

Das Gerüst für die Sortierung und Ausgabe des Stichwortverzeichnisses

① Der Rahmen, der erst die Einträge zusammenbaut, sortiert und anschließend ausgibt.

② Hier werden die Einträge einzeln in der Variablen indexeinträge gespeichert.

③ Die sortierten Einträge werden in einer Tabelle ausgegeben.

Der Abschnitt data ist der erste Teil aus dem vorherigen Listing:.

<Record element="data">
  <SetVariable variable="indexentries"/>  <ProcessNode select="keyword"/>

  <SetVariable variable="index">    <Element name="index">
      <Makeindex select="$indexentries" sortkey="name" section="section"
                 pagenumber="page" />
    </Element>
  </SetVariable>

  <ProcessNode select="$index"/></Record>

① Eine leere Variable indexentries wird deklariert. Diese wird im Record entry mit den einzelnen Elementen gefüllt (s.u.).

② Die nun gefüllte Variable indexentries wird um das Eltern-Element Index ergänzt, sortiert und in $index gespeichert.

③ Hier wird der Inhalt der Variablen $index als Datenstruktur interpretiert und ausgeführt (siehe die Ergänzung unten).

Der Befehl <Makeindex> sortiert und gruppiert die Daten, die im Attribut select übergeben werden. Die Sortierung erfolgt anhand des Attributs, der bei sortkey angegeben ist. Die Gruppierung erfolgt anhand des ersten Buchstabens des Sortierschlüssels. Die Elementstruktur, die mit dem Befehl <Makeindex> aufgebaut wird, ist folgende:

<index>
  <section name="E">
    <indexentry name="Elevator" page="4"/>
  </section>
  <section name="G">
    <indexentry name="Garage" page="2"/>
    <indexentry name="Giraffe" page="1"/>
    <indexentry name="Greeting" page="3"/>
  </section>
</index>

Der Abschnitt zum Element keyword (einfügen an Stelle 1 im Listing Das Gerüst für die Sortierung und Ausgabe des Stichwortverzeichnisses) ist einfach gehalten, und entspricht dem »Copy-of« Muster (siehe Copy-of). Hier wird die Variable indexeinträge um jeweils einen Eintrag ergänzt.

<Record element="keyword">
  <SetVariable variable="indexentries">
    <Copy-of select="$indexentries"/>
    <Element name="indexentry">
      <Attribute name="name" select="@word"/>      <Attribute name="page" select="@page"/>
    </Element>
  </SetVariable>
</Record>

① In der aktuellen Publisher-Version muss der Eintrag, der sortiert wird, in einem Attribut mit dem Namen name gespeichert werden.

Im letzten Teil wird die Tabelle ausgegeben (einfügen an Stelle 3 im Listing Das Gerüst für die Sortierung und Ausgabe des Stichwortverzeichnisses). Für jeden Abschnitt (Element section in <Makeindex>) wird eine Zeile in Hellgrau ausgegeben mit dem Sortierschlüssel. Anschließend wird für jeden Eintrag innerhalb dieses Abschnittes eine Zeile mit dem Namen des Eintrags und der Seitenzahl ausgegeben.

<Record element="index">
  <PlaceObject column="1">
    <Table width="3" stretch="max">
      <ForAll select="section">
        <Tr break-below="no" top-distance="10pt">
          <Td colspan="2" background-color="lightgray">
            <Paragraph><Value select="@name"></Value></Paragraph>
          </Td>
        </Tr>
        <ForAll select="indexentry">
          <Tr>
            <Td>
              <Paragraph><Value select="@name"/></Paragraph>
            </Td>
            <Td align="right">
              <Paragraph><Value select="@page"/></Paragraph>
            </Td>
          </Tr>
        </ForAll>
      </ForAll>
    </Table>
  </PlaceObject>
</Record>