dbTEX Ein Präprozessor für den TEX-Satz von Datenbanken Reference Manual, Tutorial & Programmdokumentation V.1.1 (Februar 1991)

W. Kraml

KPMG Alpen-Treuhand Gesellschaft mbH Kolingasse 19, A-1090 Wien

Inhaltsverzeichnis

1 Vorbemerkungen 2 2 dbTEX-Aufruf 4 3 dbTEX-Befehle 5 4 Besonderheiten, Probleme und Fehler 14 5 dbTEX-Fehlermeldungen 16 6 Ein kurzes dbTEX-Tutorial 17 7 Programmdokumentation 24

1 Vorbemerkungen

Will man Daten aus einer Datenbank in ansprechender Form mit TEX setzen (z.B. ein Telefonverzeichnis), so steht man vor der Wahl, diese Daten entweder manuell mit einem Texteditor mit den notwendigen TEX-Kommandos zu versehen, oder ein Programm zu schreiben, welches die Daten liest und die Kommandos einfügt (eine besonders empfehlenswerte Programmiersprache für solche Zwecke ist AWK, vgl. E. Neuwirth, Quick and Dirty Databases with Nice Output: AWK and TEX, TUGboat 11(1990), No. 3 – 1990 Conference Proceedings).

Während die erste Methode bei größeren Datenmenge wohl sofort ausscheidet, hat auch die zweite einen gravierenden Nachteil: das Layout des Dokuments liegt quasi im Programm-Sourcecode verborgen. Sowohl die Erstellung des Layouts (in der Regel eine Reihe von Trial-and-Error-Zyklen von Editor, TEX und Previewer bzw. Ausdruck) als auch später eventuell notwendige Änderungen werden dadurch erschwert.

Mit dbTEX wird ein dritter Weg vorgeschlagen. Ausgangspunkt ist ein Skript, welches seinerseits ein normales TEX-Inputfile darstellt. Dieses Skript ist das Resultat der Layouterstellung und wird daher in der Regel auch einige Testdatensätze enthalten. Innerhalb von TEX-Kommentaren werden nun spezielle dbTEX-Befehle eingestreut, die unter anderem das Auskommentieren der ursprünglichen Testdatensätze und das Inkludieren der echten Datensätze betreffen.

Dieses Skript wird anschließend vom dbTEX-Präprozessor abgearbeitet; dabei werden die speziellen dbTEX-Befehle interpretiert (also Musterdaten auskommentiert und echte Daten eingefügt), der Rest des Skriptfiles jedoch nicht verändert. Das Resultat ist die endgültige TEX-Eingabedatei.

Das Einfügen der Datensätze geschieht unter Zuhilfenahme von ”Templates“, so daß je nach Wunsch auch nur einzelne Datenfelder verwendet oder zusätzliche Informationen innerhalb der Daten (z.B. TEX-Makroaufrufe) eingefügt werden können.

Ein kurzes dbTEX-Skript könnte etwa folgendermaßen aussehen:

\documentstyle[12pt,german,a4]{article}
\begin{document}
normaler Text und TeX-Definitionen ...
%! template %2%: %1% %2%, Tel. %3% %4% %5% %6% %7%\\
%! examples
Bauer: Fritz Bauer, Tel. 12 34 55\\
Brown: Charly Brown, Tel. 34 55 66\\
Becker: Biggy Becker, Tel. 56 77 88\\
%! include adr.dat
ev.weiterer Text hier ...
\end{document}

Nach Bearbeitung durch den dbTEX-Präprozessor sieht die Datei dann so aus:

% dbTeX (1.1) Output generated Tue Nov 13 19:27:05 1990
\documentstyle[12pt,german,a4]{article}
\begin{document}
normaler Text und TeX-Definitionen ...
%! template %2%: %1% %2%, Tel. %3% %4% %5% %6% %7%\\
%! examples
%Bauer: Fritz Bauer, Tel. 12 34 55 \\
%Brown: Charly Brown, Tel. 34 55 66 \\
%Becker: Biggy Becker, Tel. 56 77 88 \\
%! include adr.dat
Alteisen: Richi Alteisen, Tel. 99 80 70  \\
Artmann: Robby Artmann, Tel. 12 12 22  \\
Brown: Conny Brown, Tel. 14 14 09  \\
Lang: Fritz Lang, Tel. 77 66 55  \\
Mayer: Rudi Mayer, Tel. 88 99 00  \\
ev.weiterer Text hier ...
\end{document}

Das hier gezeigte Beispiel ist etwas vereinfacht, da man in der Praxis noch weitere dbTEX-Befehle benötigen wird. Die inkludierte Datei hat für dieses Beispiel folgendes Aussehen:

Richi Alteisen 99 80 70
Robby Artmann 12 12 22
Conny Brown 14 14 09
Fritz Lang 77 66 55
Rudi Mayer 88 99 00

Für die Realisierung einer ersten Version von dbTEX wurde die Sprache AWK verwendet, die sowohl für einfache Datenmanipulationen als auch für die Implementation von einfachen Interpretersprachen wie dem vorgestellten Präprozessor ein sehr gut geeignetes Werkzeug ist. Im speziellen Fall wurde unter MS-DOS auf PolyAWK (von Sage Software, vormals Polytron) zurückgegriffen; da es für diesen Implementation auch einen Compiler gibt, konnte das Programm auch problemlos als Stand-Alone-Version aufbereitet werden. Es sollte ohne wesentliche Änderungen möglich sein, das Programm auch mit anderen AWK-Versionen zum Laufen zu bringen (z.B. mit GNU-AWK, das im Rahmen der Free Software Foundation erhältlich ist).

Für das vorliegende Programm und seine Dokumentation wird kein Copyright beansprucht; es soll im Gegenteil als Ausgangspunkt für eigene Verbesserungen, Ergänzungen, Erweiterungen und Experimente betrachtet werden. Aus diesem Grunde werden allerdings auch keine Verpflichtungen (zur Wartung, Fehlerbehebung oder Weiterentwicklung) übernommen. Der Autor ist allerdings sehr an Verbesserungsvorschlägen oder Weiterentwicklungen interessiert und entweder schriftlich (unter der im Titel angegebenen Adresse) oder mit E-Mail (EARN: A7511daa @ awiuni11) erreichbar.

2 dbTEX-Aufruf

Der Programmaufruf ist davon abhängig, ob der AWK-Interpreter aufgerufen wird oder eine kompilierte Form des Programms. Im ersten Fall lautet der Aufruf so:

Syntax:

awk -fdbtex.awk input-file > output-file

input-file Die Eingabedatei (dbTEX-Skript)

output-file Die Ausgabedatei (sollte die Extension .tex aufweisen. da diese Datei mit TEX gesetzt wird)

Der Aufruf der kompilierten Form sieht folgendermaßen aus:

Syntax:

dbtex input-file > output-file

input-file Die Eingabedatei (dbTEX-Skript)

output-file Die Ausgabedatei (sollte die Extension .tex aufweisen, da diese Datei mit TEX gesetzt wird)

3 dbTEX-Befehle

Jeder dbTEX-Befehl beginnt mit der Sequenz %!; diese muß am Zeilenanfang stehen. Anschließend steht ein Schlüsselwort, das auch durch ein oder mehrere Blanks von der Anfangssequenz getrennt sein kann. In weiterer Folge können noch Argumente zu stehen kommen, die ebenfalls durch Blanks getrennt sind.

Alle dbTEX-Befehle mit einer Ausnahme können nur eine Zeile lang sein; die Ausnahme ist der %! template-Befehl, bei dem auch Fortsetzungszeilen erlaubt sind. Diese Fortsetzungszeilen beginnen mit der Sequenz % .

Da die dbTEX-Anfangssequenzen mit einem Prozentzeichen beginnen, wird eine solche Zeile von TEX als Kommentar behandelt; es sind also sowohl das dbTEX-Skript als auch der von dbTEX erzeugte Output gültige TEX-Eingabedateien.

%! break

Befehl zum Einfügen von Daten anläßlich eines Gruppenwechsels (Änderung des Inhalts eines Feldes der zu inkludierenden Datei).

Syntax:

%! break <num> <template-name> [<flag>]

<num> Eine Zahl, die auch in der Form %n% geschrieben sein kann. Sie bezieht sich auf das Feld <num> des zu inkludierenden Datenfiles.

<template-name> Name des Templates, das in die Ausgabe eingefügt werden soll, wenn sich der Inhalt von Feld <num> ändert.

<flag> Beliebiges Symbol bzw. beliebige Zeichenkette; wenn dieses Flag vorhanden ist, wird schon vor dem ersten einzufügenden Datensatz das Template eingefügt (z.B. als Überschrift etc.).

Beispiel:  %! break 2 tabellenende

Falls sich der Inhalt des zweiten Feldes ändert, soll das Template tabellenende benutzt werden (da kein Flag vorhanden ist, wird vor dem ersten Datensatz kein Template eingefügt).

Siehe auch: clearbreaks, record, template

%! change

Dieser Befehl dient zur Spezifikation einer Zeichenkettenersetzung. Es ist damit möglich, bestimmte Zeichenketten der Eingabedatei durch andere Zeichenketten zu ersetzen. Es können auf diese Weise z.B. bestimmte Zeichen, die in TEX eigens kodiert werden müssen, übersetzt werden.

Syntax:

            %! change <string1> <string2>

<string1> Der zu ersetzende String. Er muß zwischen zwei Zeichen, die sonst nicht im String vorkommen und nicht Blank sein dürfen, begrenzt werden.

<string2> Der String, womit ersetzt werden soll. Die formalen Regeln sind analog zum vorhergehenden.

Beispiel:  %! change /TeX/ /\TeX{}/

Die Zeichenkette TeX in der Datenbankdatei soll durch das entsprechende TEX-Makro ersetzt werden

Siehe auch: translate; Abschnitt 4

%! clearbreaks

Mit diesem Befehl werden die bisher spezifizierten Gruppenwechsel wieder gelöscht.

Syntax:

            %! clearbreaks

-- (Dieser Befehl verwendet keine Argumente.)

Siehe auch: break

%! examples

Mit diesem Befehl wird dbTEX angezeigt, daß die folgenden Zeilen im Skript als Beispielrecords zu verstehen sind: bei der Bearbeitung durch den Präprozessor werden die folgenden Zeilen solange (durch ein Prozentzeichen am Zeilenanfang) auskommentiert, bis wieder ein dbTEX-Kommando erkannt wird. Dies wird in der Praxis meist ein %! include-Kommando sein.

Syntax:  %! examples

-- (Dieser Befehl verwendet keine Argumente.)

%! include

Dieser Befehl veranlaßt dbTEX, Datensätze aus einer Datendatei zu lesen und in die Outputdatei einzufügen. Dabei werden die einzelnen Felder der Datensätze entsprechend einem Template eingefügt, dessen Name in einem %! record-Befehl angegeben und das in einem %! template-Befehl definiert wurde. Außerdem werden eventuell Zeichenkettenersetzungen durchgeführt, wenn mittels des %! translate-Befehls eine Ersetzungstabelle geladen wurde und/oder einzelne Ersetzungen in einem %! change-Befehl verlangt wurden. Der Wechsel im Inhalt eines Datenfeldes führt zur Ausgabe eines eigenen Templates, wenn dieses Datenfeld in einem %! break-Befehl referenziert wurde; damit lassen sich spezielle Aktionen anläßlich eines Gruppenwechsels steuern.

Syntax:

            %! include <filename>

<filename> Die Datei namens <filename> enthält die zu inkludierenden Datensätze.

Beispiel: Im ersten Beispiel wird für jeden Datensatz das Template t1 verwendet und die Datei xyz.dat verwendet:

            %! record t1
            %! template t1 \datensatz{Name: %1% %3%}{%2%}
            %! include xyz.dat

Das zweite Beispiel zeigt, wie die Auswahl des Templates auch durch den Inhalt der Datei selbst bestimmt werden kann:

            %! record %4%
            %! template name \inclname{%1%}{%2% %3%}
            %! template adr \incladr{%3% %2% %1%}
            % {%4% (Firma)}
            %! include adressen.dat

Siehe auch: record, template, translate, change, break

%! record

Der %! record-Befehl spezifiziert, wie die Sätze aus der Datendatei einzulesen und in die Ausgabedatei zu inkludieren sind. Es wird damit festgelegt, welches Template und welcher Field- bzw. Record-Separator (entsprechend den AWK-Variablen FS und RS) zu verwenden ist.

Das erste Argument ist entweder ein Name (der Name eines Templates oder eine Zahl n zwischen Prozentzeichen; in letzterem Fall wird erwartet, daß in dem Feld n der Datendatei (vgl. AWK’s $n) ein Name eines Templates steht. Damit ist es möglich, das zu verwendende Template durch die Daten zu definieren.

Syntax:

%! record <id> [<field-sep> [<record-sep>]]

<id> Dies ist entweder ein Name, der einem Template-Namen entspricht, oder ein Verweis auf ein Datenfeld in der Form %n%. In diesem Fall wird der Name des Templates für den aktuellen Datensatz aus der Datei selbst, und zwar durch den Inhalt von Feld Nr. n bestimmt.

<field-sep> Dies ist ein String, der zwischen zwei Zeichen eingeschlossen sein muß, die nicht im String selbst enthalten sein und nicht Blank sein dürfen. Er dient zur Festlegung jener Zeichen, die als Feldseparatoren dienen. Der Defaultwert ist (wie in AWK) Blank und Tab.

<record-sep> Dieser String, für den bezüglich seines Aufbaus dasselbe gilt wie im vorigen Absatz, definiert den Recordseparator. Defaultwert ist Newline.

Beispiel: Im folgenden Beispiel wird ein Feld der Datendatei spezifiziert, welches den Namen des Templates zu enthalten hat, und ein vom Default abweichender Feldseparator.

%! record %1% /:::/

%! template

Mit diesem Befehl kann eine beliebige Anzahl von Templates definiert werden, die entweder als Schema zum Einfügen von Datensätzen dienen oder anläßlich eines Gruppenwechsels verwendet werden. Die Positionen, an denen Daten aus dem aktuellen Satz der Datendatei stehen sollen, werden durch Platzhalter der Form %n% markiert; es wird dort das Feld Nr. n eingefügt.

Syntax:

            %! template <name> [<template>]

<name> Ein Name, über welchen das betreffende Template identifiziert wird.

<template> Das Textmuster, welches Platzhalter der Form %n% enthalten kann, und das für jeden gelesenen Datensatz in die Ausgabedatei geschrieben wird, wobei die Platzhalter durch die aktuellen Werte aus der Datendatei ersetzt werden. Dieses Template kann auch mithilfe von Fortsetzungszeilen über mehrere Zeilen gehen.

Die Fortsetzungszeilen haben folgende Struktur:

Syntax:

            %  <template>

<template> Textmuster mit Platzhaltern, siehe oben.

Beispiel: Der im folgende Beispiel vorkommende Platzhalter %0% bezieht sich auf den ganzen Datensatz (analog zu den Konventionen für $0 in AWK):

            %! template adresse Adresse: %0%
            % \hline

Siehe auch: break, record, include

%! translate

Mit diesem Befehl wird eine Übersetzungstabelle geladen, mit deren Hilfe Zeichenkettenersetzungen beim Einlesen des Datenfiles durchgeführt werden. Zusätzlich können einzelne Ersetzungen durch den %! change-Befehl spezifiziert werden.

Syntax:

%! translate <tr-filename>

<tr-filename> Der Name der Datei, die die Ersetzungstabelle enthält. Diese Datei enthält Einträge entsprechend der Syntax des %! change-Befehls, allerdings ohne das Befehlswort selbst (die einzelnen Zeilen haben also die Struktur

<string1> <string2>

wobei der erste String die zu ersetzende Kette und der zweite die Ersatzzeichenkette repräsentiert). Wird anstelle eines Dateinamens das Schlüsselwort OFF bzw. off verwendet, so werden alle bisher geladenen Übersetzungstabellen gelöscht.

Beispiel: Das folgende Beispiel spezifiziert, daß die Datei ascii.tbl als Übersetzungstabelle verwendet werden soll.

%! translate ascii.tbl

Siehe auch: change, include; Abschnitt 4

4 Besonderheiten, Probleme und Fehler

4.1 AWK’s Erbe

Die zur Implementation benutzte Sprache AWK liegt vielen Besonderheiten zugrunde; dies sind vor allem:

• Die Angabe von Feldern der einzufügenden Datensätze ist ähnlich wie bei AWK: %0% bezieht sich auf den vollständigen Datensatz, %1%, %2% usw. beziehen sich auf die Felder 1, 2 etc. Dies ist mit den AWK-Konstrukten $0, $1, $2 usw. zu vergleichen.

• Die Definitionen von ”Fieldseparator“ und ”Recordseparator“ entsprechen denen von AWK; dies heißt insbesondere, daß diese Zeichen(ketten) als (dynamische) reguläre Ausdrücke interpretiert werden.

• Das zuvor gesagte bezieht sich auch auf die Zeichenkettenersetzungen; die zu ersetzende Zeichenkette ist als regulärer Ausdruck aufzufassen, und das Zeichen & hat im Ersetzungtext die Funktion eines Platzhalters für den gesamten Teilstring, der durch diesen regulären Ausdruck repräsentiert wird, und ist deshalb als \& zu schreiben, wenn das &- Zeichen selbst gemeint ist.

Diese ”ererbten Features“ sind allerdings keineswegs negativ zu verstehen; reguläre Ausdrücke etwa sind so mächtige Formulierungsmechanismen, daß man nur ungern auf sie verzichten will, wenn man sich einmal mit ihrer etwas willkürlichen Syntax vertraut gemacht hat (und es liegt eben in der Natur von metasprachlichen Beschreibungen, daß sie manchmal etwa kryptisch anmuten).

4.2 Zeichenkettenersetzungen

Die Ersetzungen werden als globales Suchen und Ersetzen hintereinander durchgeführt; das heißt, das das Ergebnis einer Ersetzung möglicherweise durch eine nachfolgende Ersetzung weiter verändert wird. Vor allem beim Ersetzen von ganzen Zeichenketten ist entsprechende Vorsicht geboten und aus diesem Grund können derzeit keine Backslashes ersetzt werden!

Es werden zuerst die Ersetzungen aus den geladenen Tabellen und anschließend die des %! change-Befehls durchgeführt.

4.3 Gruppenwechsel

Der derzeit vorhandene Mechanismus zur speziellen Behandlung von Gruppenwechsel ist ziemlich ad hoc entstanden; so wird z.B. nicht zwischen ”break before“ und ”break after“ unterschieden. Auch gibt es keine Mechanismen zur Erzeugung von Zwischensummen und ähnlichem.

4.4 Robustheit

Der vorliegenden Version mangelt es sicherlich noch an Robustheit; so sind auch die Fehlermeldungen eher spärlich und vielleicht nicht immer sehr hilfreich. Manche Fehler werden überhaupt stillschweigend ignoriert, so z.B. Strings mit falschen oder fehlenden Delimitern, die zu höchst merkwürdigen Resultaten führen können.

In der derzeitigen Form ist dbTEX überhaupt mehr als Prototyp zu verstehen; auch in bezug auf die eingebauten Features bzw. auf die Syntax sind mit Sicherheit Verbesserungen und Erweiterungen möglich bzw. wünschenswert. dbTEX (bzw. seine Implementierung in AWK) lädt zu Experimenten geradezu ein!

4.5 Bugs und ernste Probleme

Die mithilfe des PolyAWK Compilers Version 1.0 (von Sage-Polytron) übersetzte Version des Programms hat Probleme beim Inkludieren von etwas umfangreicheren Datenfiles (Fehlermeldung: ”out of memory“); dies dürfte an einer mangelhaften Implementierung der Garbage Collection in der Runtime-Library liegen, bei der Interpreter-Version wurde dieser Fehler nicht beobachtet.

4.6 Desiderata

Der Mechanismus zur Zeichenersetzung und derjenige für den Gruppenwechsel sollte einer eingehenden Revision unterzogen werden!

Eventuell sollte die Syntax etwas anders aussehen; es wäre z.B. durchaus möglich, den %! record- und den %! include-Befehl in einen einzigen Befehl zu integrieren!

Umfassendere und genauere Fehlermeldungen!

Es gibt derzeit keine Möglichkeit, Suchpfade für Daten- oder Tabellenfiles anzugeben; dies könnte eventuell über das Environment gesteuert werden oder über einen Aufrufparameter!

5 dbTEX-Fehlermeldungen

Alle Fehlermeldungen wie auch informative Meldungen werden sowohl direkt auf der Konsole ausgegeben als auch an der betreffenden Stelle als Kommentar in die Ausgabedatei übernommen.

5.0.1 Error: FILE … NOT FOUND OR NOT ACCESSIBLE!

Es wurde die in einem %! include-Befehl angegebene Datei entweder nicht gefunden oder sie konnte nicht zum Lesen geöffnet werden.

5.0.2 Error: INVALID CONTINUATION Es wurde eine Fortsetzungszeile (% …) angetroffen, die nicht unmittelbar nach einem %! template-Befehl steht.

5.0.3 Error: TRANSLATION TABLE … NOT FOUND Die spezifizierte Ersetzungstabelle wurde nicht gefunden und konnte daher nicht geladen werden.

5.0.4 Error: UNKNOWN COMMAND Nach der dbTEX-Startsequenz %! steht ein unbekanntes Befehlswort.

6 Ein kurzes dbTEX-Tutorial

6.1 Aufgabenstellung

Ausgehend von einer Adressendatei soll ein Adreßbuch mit TEX gesetzt werden; die Adressen kommen aus einer Datenbank und liegen im ASCII-Format vor, wobei jeder Datensatz aus mehreren Zeilen besteht und die einzelnen Datensätze durch Leerzeilen getrennt sind. Die Datensätze sind bereits richtig sortiert und haben folgende Struktur:

     Initialbuchstabe
     Sortierbegriff
     Name
     Adresse
     Postleitzahl und Ort
     Land
     Telefonnummer(n)

Die ersten Datensätze dieser Datei könnten etwa so aussehen:

A
Alpen-Treuhand
KPMG Alpen-Treuhand Ges.m.b.H
Kolingasse 19
1090      Wien
Oesterreich
34 36 45
A
Andersen
Bente Gaarder Andersen
The Norwegian Institute of Technology
N-7034  Trondheim
Norwegen
+47 7 59 26 95

Das Adressenverzeichnis soll dann etwa folgendermaßen aussehen:

A

KPMG Alpen-Treuhand Ges.m.b.H Kolingasse 19 1090 Wien (Österreich) Tel: 34 36 45

Bente Gaarder Andersen The Norwegian Institute of Technology N-7034 Trondheim (Norwegen) Tel: +47 7 59 26 95 6.2 1. Schritt: Entwurf des Layouts in TEX

Mithilfe von einigen Testdaten wird zuerst ein TEX-Dokument erzeugt und das endgültige Layout festgelegt. Dies könnte in unserem einfachen Fall etwa so aussehen:

\documentstyle[12pt,german]{article}
\title{Meine wichtigsten Adressen von $\cal A-\cal Z$}
\author{}\date{}
\newcommand{\eintrag}[5]{\begin{minipage}{10cm}\begin{flushleft}
{\bf #1}\\
{#2}\\
{#3 (#4)}\\
{\small Tel:} #5\\\vspace{4mm}\end{flushleft}\end{minipage}\par}
\begin{document}\parindent 0pt\raggedbottom
\maketitle
\vspace{5mm}{\Huge A}\par\vspace{5mm}
\eintrag{Name}{Adresse}%
{Ort}{Land}{Telefon}
\eintrag{KPMG Alpen-Treuhand Ges.m.b.H}{Kolingasse 19}%
{1090  Wien}{"Osterreich}{34 36 45}
\end{document}

Dieses File wird nun gesetzt und eventuell so lange geändert, bis das Layout den Wünschen entspricht.

6.3 2. Schritt: dbTEX-Kommandos einfügen

dbTEX-Kommandos beginnen immer mit einem Prozentzeichen in der ersten Zeile, gefolgt von einem Ausrufzeichen (oder einem Underline im Falle von Fortsetzungszeilen). Damit sind sie für TEX nur gewöhnliche Kommentare; jedes dbTEX-Skript ist daher auch ein syntaktisch korrektes TEX-Inputfile.

6.3.1 Musterdaten wegkommentieren

Zuerst wollen wir angeben, welche Datensätze nur als Muster in unserem Skript stehen; dazu fügen wir vor diesen den Befehl %! examples ein.

6.3.2 Festlegen der Dateistruktur

dbTEX muß wissen, wie die einzulesende Datei in Datensätze und jeder Datensatz wiederum in einzelne Felder aufzugliedern ist. Diese Angaben werden mit dem %! record-Befehl gemacht, wobei ein Name für den Datensatz definiert wird und weiters Zeichenketten, die als Trennung für Datenfelder (in unserem Fall "\n" für Newline) und Datensätze dienen. Der vorliegende (Sonder-)Fall von Datensätzen, die durch eine Leerzeile getrennt sind, wird durch den ”leeren“ String "" festgelegt. Ganz allgemein gilt, daß für die Definition von Feld- und Record-Separatoren die selben Konventionen wie in der Sprache AWK gelten; es handelt sich dabei also jeweils um dynamische reguläre Ausdrücke, so daß auch ziemlich komplexe Trennangaben definiert werden können. Für beide Delimiter gibt es auch Defaultwerte, und zwar "\n" (Zeilenende) für den Recorddelimiter und " " (Leerzeichen oder Tab) für den Felddelimiter.

6.3.3 Erstellung eines Templates (Einfügeschablone)

Nun weiß dbTEX zwar, wie die einzulesende Datei zu gliedern ist, aber noch nicht, in welcher Form diese Daten in die Ausgabedatei zu stellen sind. Dafür muß nun eine Schablone definiert werden, wozu der %! template-Befehl dient. Dies ist eine beliebige Zeichenkette, die für jeden Datensatz in die Ausgabedatei gestellt wird. Innerhalb dieses Templates können nun Platzhalter der Form %n% stehen. Diese Platzhalter werden durch die entsprechenden Datenfelder ersetzt, wobei n der Nummer des Datenfeldes entspricht. Ein Sonderfall ist die Nummer 0, die für den gesamten Datensatz (inkl. der Feldtrennzeichen) steht. Diese Feldnumerierungskonventionen entsprechen wiederum der Sprache AWK (dort wird mit $n auf die Felder zugegriffen).

Da Einfügemuster unter Umständen länger sein können und mehrere Zeilen beanspruchen können, sind dafür in dbTEX auch Fortsetzungszeilen vorgesehen, die mit der Sequenz % beginnen müssen.

6.3.4 Angabe der zu inkludierenden Datei

An der richtigen Stelle im Skript (meist nach den Musterdaten) ist nun anzugeben, welche Datei die zu inkludierenden Daten enthält, wobei hierfür der %! include-Befehl verwendet wird.

6.3.5 Der Stand der Dinge bis hierher

Unser Skript sieht nun nach dem Einfügen der bisher besprochenen Kommandos folgendermaßen aus:

\documentstyle[12pt,german]{article}
\title{Meine wichtigsten Adressen von $\cal A-\cal Z$}
\author{}\date{}
\newcommand{\eintrag}[5]{\begin{minipage}{10cm}\begin{flushleft}
{\bf #1}\\
{#2}\\
{#3 (#4)}\\
{\small Tel:} #5\\\vspace{4mm}\end{flushleft}\end{minipage}\par}
\begin{document}\parindent 0pt\raggedbottom
\maketitle
% Struktur von Datei "`adressen": 7 Zeilen pro Record
% 1. Initiale
% 2. Sortierwort
% 3. Name
% 4. Adresse
% 5. PLZ und Ort
% 6. Land
% 7. Telefon
%! record adresse ''\n'' ''''
%! template adresse \eintrag{%3%}{%4%}%
%_{%5%}{%6%}{%7%}
%! examples
\vspace{5mm}{\Huge A}\par\vspace{5mm}
\eintrag{Name}{Adresse}%
{Ort}{Land}{Telefon}

\eintrag{KPMG Alpen-Treuhand Ges.m.b.H}{Kolingasse 19}%
{1090  Wien}{"Osterreich}{34 36 45}
%! include adressen
\end{document}

Wird diese Datei mit dbTEX bearbeitet, so werden wir als Resultat folgende Datei erhalten:

% dbTeX (1.1.beta) Output generated Tue Feb 12 12:27:11 1991
% dbTeX       Input file: adrex1.tex
\documentstyle[12pt,german]{article}
\title{Meine wichtigsten Adressen von $\cal A-\cal Z$}
\author{}\date{}
\newcommand{\eintrag}[5]{\begin{minipage}{10cm}\begin{flushleft}
{\bf #1}\\
{#2}\\
{#3 (#4)}\\
{\small Tel:} #5\\\vspace{4mm}\end{flushleft}\end{minipage}\par}
\begin{document}\parindent 0pt\raggedbottom
\maketitle
% Struktur von Datei "`adressen": 7 Zeilen pro Record
% 1. Initiale
% 2. Sortierwort
% 3. Name
% 4. Adresse
% 5. PLZ und Ort
% 6. Land
% 7. Telefon
%! record adresse ''\n'' ''''
%! template adresse \eintrag{%3%}{%4%}%
%_{%5%}{%6%}{%7%}
%! examples
%\vspace{5mm}{\Huge A}\par\vspace{5mm}
%\eintrag{Name}{Adresse}%
%{Ort}{Land}{Telefon}
%\eintrag{KPMG Alpen-Treuhand Ges.m.b.H}{Kolingasse 19}%
%{1090  Wien}{"Osterreich}{34 36 45}
%! include adressen
\eintrag{KPMG Alpen-Treuhand Ges.m.b.H}{Kolingasse 19}%
 {1090      Wien}{"Osterreich}{34 36 45}
\eintrag{Bente Gaarder Andersen}{The Norwegian Institute of Technology}%
 {N-7034  Trondheim}{Norwegen}{+47 7 59 26 95}

…

% (dbTeX) Info: 17 record(s) included from adressen.
\end{document}
% (dbTeX) Info: 29 line(s) input, 17 record(s) included, 0 error(s).

6.4 3. Schritt: Weitere dbTEX-Kommandos einfügen

6.4.1 Gruppenwechsel bzw. Überschriften

In unserem Beispiel soll vor jedem neuen Anfangsbuchstaben eine Initiale gedruckt werden. Dies können wir mit dem in dbTEX vorhandenen Mechanismus zur Behandlung eines Gruppenwechsels bewerkstelligen. Falls ein %! break-Kommando spezifiziert wurde, überprüft dbTEX, ob sich der Inhalt des dabei spezifizierten Feldes geändert hat. Wenn ja, dann wird dieses Template in die Ausgabedatei gestellt, wobei Ersetzungen wie bei gewöhnlichen Datensatztemplates vorgenommen werden. Bei diesem Befehl gibt es auch ein optionales Argument; wenn es (mit einem beliebigen Zeichen) vorhanden ist, dann wird das ”Breaktemplate“ in jedem Fall auch schon vor dem ersten Datensatz eingefügt (wie es für Überschriften auch richtig ist). Sollen damit aber nur zwischen verschiedenen Gruppen von Datensätzen spezielle Einschübe erzeugt werden, wird dieser optionale Parameter einfach ausgelassen.

Im %! break-Befehl wird neben der Nummer des zu beobachtenden Datenfeldes auch der Name des zu verwendenden Templates spezifiziert; es ist daher auch dieses entsprechende Template im Skript zu definieren.

Die entsprechenden Befehlszeilen, die vor dem %! include-Kommando stehen müssen, lauten für unser Beispiel:

%! break 1 initiale +
%! template initiale \pagebreak[3]\vspace{5mm plus 4mm}{\Huge $\cal %1%$}%
%_\nopagebreak\par\vspace{5mm plus 2mm}\nopagebreak

6.4.2 Zeichenersetzungen

Ein letztes Problem soll im Rahmen unseres Beispiels noch behandelt werden: TEX verlangt, daß gewisse Sonderzeichen mit einer speziellen Kodierung eingegeben werden, da diese Zeichen sonst als spezielle Kommandos interpretiert werden. Dies gilt z.B. für das Dollarzeichen (Mathematikmodus), das Prozentzeichen (Kommentarbeginn), Zirkumflex und Unterstreichungsstrich (Hoch- bzw. Tiefstellung) und so weiter.

Kommen solche Zeichen in den einzufügenden Daten vor, ergeben sich natürlich alle möglichen Fehlerquellen. Aus diesem Grund sind in dbTEX Zeichenersetzungsmöglichkeiten vorgesehen, und zwar auf zweierlei Art: zum einen gibt es den %! change-Befehl, der ein Zeichen (oder auch eine ganze Zeichenkette) durch eine andere Zeichenkette ersetzt. Zum anderen ist es möglich, mehrere solcher Ersetzungen in eine Datei zu schreiben und dann mit dem %! translate-Befehl zu veranlassen, daß eine solche Ersetzungstabelle geladen wird.

Mit demselben Mechanismus kann man auch 8-bit-ASCII-Zeichen in die entsprechende TEX-Konventionen übersetzen (also etwa die deutschen Sonderzeichen im IBM-PC-Zeichensatz).

Für unser Beispiel wollen wir zum Ersatz der deutschen Sonderzeichen die Tabelle germtex.tbl laden, die folgenden Inhalt hat:

# standard translation table for german characters (needs german.sty)
"\"A" /"A/
"\"O" /"O/
"\"U" /"U/
"\"a" /"a/
"\"o" /"o/

"\"u" /"u/
"\ss{}" /"s/

Und weiters laden wir eine Tabelle tex.tbl folgenden Inhalts:

# translation table for some ASCII characters that are special in TeX
"\%" "\%"
"&"  "\\&"
"\$" "\$"
"#"  "\#"
"_"  "\_"
"{"  "\{"
"}"  "\}"
"~"  "\verb?~?"
"\^" "\verb?^?"

Man beachte, daß Zeilen, die mit einem #-Zeichen beginnen, als Kommentar behandelt werden!

Die (vor dem %! include-Kommando) noch einzufügenden Zeilen sind daher:

%! translate germtex.tbl
%! translate tex.tbl

Nach der Bearbeitung mit dbTEX sieht dann die Datei folgendermaßen aus (und man erzielt mit ihr das gewünschte Ergebnis):

% dbTeX (1.1.beta) Output generated Tue Feb 12 15:59:00 1991
% dbTeX       Input file: adrex3.tex
\documentstyle[12pt,german]{article}
\title{Meine wichtigsten Adressen von $\cal A-\cal Z$}
\author{}\date{}
\newcommand{\eintrag}[5]{\begin{minipage}{10cm}\begin{flushleft}
{\bf #1}\\
{#2}\\
{#3 (#4)}\\
{\small Tel:} #5\\\vspace{4mm}\end{flushleft}\end{minipage}\par}
\begin{document}\parindent 0pt\raggedbottom
\maketitle
% Struktur von Datei "adressen": 7 Zeilen pro Record
% 1. Initiale
% 2. Sortierwort
% 3. Name
% 4. Adresse
% 5. PLZ und Ort
% 6. Land
% 7. Telefon
%! record adresse "\n"' "`"'
%! template adresse \eintrag{%3%}{%4%}%
%_{%5%}{%6%}{%7%}
%! break 1 initiale +
%! template initiale \pagebreak[3]\vspace{5mm plus 4mm}{\Huge $\cal %1%$}%
%_\nopagebreak\par\vspace{5mm plus 2mm}\nopagebreak
%! examples
%\vspace{5mm}{\Huge A}\par\vspace{5mm}
%\eintrag{Name}{Adresse}%

%{Ort}{Land}{Telefon}
%\eintrag{KPMG Alpen-Treuhand Ges.m.b.H}{Kolingasse 19}%
%{1090  Wien}{"Osterreich}{34 36 45}
%! translate germtex.tbl
%! translate tex.tbl
%! include adressen
\pagebreak[3]\vspace{5mm plus 4mm}{\Huge $\cal A$}%
 \nopagebreak\par\vspace{5mm plus 2mm}\nopagebreak
\eintrag{KPMG Alpen-Treuhand Ges.m.b.H}{Kolingasse 19}%
 {1090      Wien}{"Osterreich}{34 36 45}
\eintrag{Bente Gaarder Andersen}{The Norwegian Institute of Technology}%
 {N-7034  Trondheim}{Norwegen}{+47 7 59 26 95}

…

\eintrag{Atominstitut d. "Ost. Hochsch.}{Sch"uttelstra"se}%
 {1020      Wien}{"Osterreich}{72 51 36}
\pagebreak[3]\vspace{5mm plus 4mm}{\Huge $\cal B$}%
 \nopagebreak\par\vspace{5mm plus 2mm}\nopagebreak
\eintrag{Gwen Bagshaw}{20 Heatherlands, Vicarage Road}%
 {Sunbury-on-Thames, Middx TWI6 7QU}{United Kingdom}{0932 -- 787026 }
\eintrag{Nicola Barr}{The Rectory, Cross Ave.}%
 {Blackrock, Co.Dublin}{Irland}{00353-1-88 71 18}
\eintrag{Nicola Barr,  A.\&L. Goodbody}{1 Earlsfort Centre, Lr. Hatch Street}%
 {Dublin 2}{Irland}{(Fax)  00353-1-61 32 78}
\eintrag{Weinschank Binder}{Speckbachergasse}%
 {1160      Wien}{"Osterreich}{46 02 30}

…

% (dbTeX) Info: 17 record(s) included from adressen.
\end{document}
% (dbTeX) Info: 34 line(s) input, 17 record(s) included, 0 error(s).

6.5 In diesem Beispiel nicht behandelte Features

Neben der Möglichkeit, Zeichenersetzungen über Tabellen zu spezifizieren, kann man ad hoc zu bestimmten Anwendungsfällen einzelne Ersetzungen auch über den %! change-Befehl veranlassen.

Wird beim %! translate-Befehl anstelle eines Dateinamens der Parameter OFF angegeben, werden alle zu diesem Zeitpunkt gültigen Ersetzungen gelöscht; damit ist es möglich, in einem Job mehrere unterschiedliche Ersetzungen zu verwenden.

Beim %! record-Befehl gibt es auch die Möglichkeit, anstelle des Identifiers, der das zu verwendende Template bestimmt, eine numerische Größe zwischen Prozentzeichen anzugeben. Dann wird erwartet, daß in dem Datenfeld mit der betreffenden Nummer ein String enthalten ist, welcher das zu verwendende Template angibt. Man kann damit auf verhältnismäßig einfache Weise datengesteuert verschiedene Muster innerhalb einer Datei verwenden.

7 Programmdokumentation

7.1 Kurzbeschreibung der einzelnen Funktionen

7.1.1 Oberste Ebene – l. 25-49

Eingabezeilen werden auf eingestreute dbTEX-Kommandos hin untersucht und ausgegeben; falls es sich um Zeilen nach einem %! examples-Befehl handelt, wird ein Prozentzeichen vorgesetzt, um diese Zeilen auszukommentieren.

7.1.2 add trtbl() – l. 171-181

Ein Eintrag wird in eine der beiden intern vorhandenen Tabellen für die Zeichenersetzung geschrieben: std tbl ist die Tabelle für Ersetzungen mittels entsprechender Tabellendateien, und usr tbl enthält die mit dem %! change-Befehl spezifizierten Übersetzungen.

7.1.3 advance() – l. 140-170

Diese Funktion ist der Tokenizer des dbTEX-Interpreters; ihm wird als Argument der erwartete Tokentyp übergeben (ID für Identifier, TEMPLATE für Muster oder STRING für Zeichenketten). Falls ein entsprechendes Token als nächstes in der Variablen buffer steht (führende Leerzeichen oder Tabs werden übersprungen), wird es daraus entfernt, der Variablen tok zugewiesen und die Länge des Tokens als Rückkehrwert zurückgeliefert. Wird kein passendes Token gefunden, wird 0 zurückgeliefert.

7.1.4 console msg() – l. 59-62

Diese Funktion gibt ihr Argument auf der Konsole aus.

7.1.5 dbTeX command() – l. 80-140

Diese Funktion ist dafür zuständig, die einzelnen dbTEX-Kommandos auszuführen und ist somit die oberste Ebene für den Fall, daß ein dbTEX-Befehl erkannt wurde. Für jeden Befehl existiert ein if-Block, der die zu dem entsprechenden Befehl gehörigen Aktionen enthält.

7.1.6 do include() – l. 200-224

Diese Funktion sit in gewisser Weise die wichtigste; mit ihrer Hilfe werden die Datensätze in den Ausgabestrom übernommen. Dabei wird darauf geachtet, ob eine break-Bedingung eintritt (also die Notwendigkeit für einen Gruppenwechsel) und gegebenenfalls ein entsprechendes Gruppenwechselmuster geschrieben. Das eigentliche Inkludieren der Datensätze wird von incl () durchgeführt.

7.1.7 error msg() – l. 63-69

Diese Funktion schreibt das ihr übergebene Argument als Fehlermeldung sowohl auf die Konsole als auch in die Ausgabedatei.

7.1.8 incl () – l. 225-236

Diese Funktion schreibt den aktuellen Datensatz entsprechend dem Template, welches ihr als Argument übergeben wurde, in den Ausgabestrom.

7.1.9 info msg() – l. 71-76

Eine informative Nachricht wird mit dieser Funktion sowohl auf die Konsole als auch in die Ausgabedatei geshrieben.

7.1.10 set translation() – l. 181-200

Mithilfe dieser Funktion wird eine Übersetzungstabellendatei eingelesen und jede darin spezifizierte Ersetzung in die Tabelle std tbl aufgenommen; Zeilen, die mit # beginnen, werden dabei ignoriert, können also als Kommentar benutzt werden.

Falls anstelle eines Dateinamens OFF spezifiziert wurde, werden alle derzeit gültigen Ersetzungen gelöscht.

7.1.11 txlate() – l. 238-245

Mit dieser Funktion, die von incl aus aufgerufen wird, werden die in den Ersetzungtabellen spezifizierten Zeichenersetzungen auf den übergebenen String ausgeführt, der in enbtsprechend veränderter Form schließlich wieder zurückgeliefert wird.

7.2 AWK-Source von dbTEX

# dbTeX.awk − a simple preprocessor for generating TeX output from # database files # Version 1.1.beta (1991−02−12) dr. w. kraml, KPMG Alpen−Treuhand Wien # see dbTeX.tex for documentation!

BEGIN { console msg("This is dbTeX Version 1.1.beta (1991-02-12)")
printf("%% dbTeX (1.1.beta) Output generated " ctime() "\n")

# ˆ PolyAwk specific!

printf("%% dbTeX       Input file: %s\n", ARGV[1])  10

# a number of initialisations and defs follow here:

FieldS = FS RecordS = RS NRincluded = 0 example flag = 0 buffer = "" STANDARD = 0 STRING = 1 TEMPLATE = 2

ID = 3  20

USER = 1 }

# main loop − do this for each line in script

{  if ($0 ˜ /ˆ %!/) {  # embedded dbTeX−command

in template = 0

buffer = substr($0,3)

print $0 # output for reference 30

dbTeX command() } else if ($0 ˜ /ˆ % /) { # template contin. line

print $0

if (in template) { # start cont. with newlines nl = length(template[t name]) ? "\n" : "" template[t name] = template[t name] nl substr($0,3)

}

   else error msg("INVALID CONTINUATION")

}  40

else if (example flag) { # embedded examples in template = 0 print "%" $0

} else {

in template = 0

print $0 # original TeX line }

}

50

END { s = NR " line(s) input, " NRincluded " record(s) included, "

s = s (errors+0) " error(s)" info msg(s)

}

# functions performing helpful tasks with various messages

console msg() ⇐

function console msg(s) {  # output to console  60

print s > "/dev/tty"

}

error msg() ⇐

function error msg(msg, s) { # print error on console s = "% (dbTeX) Error: " msg "!" # and in output file ++errors print s console msg(s " -> line " NR ":") console msg($0)

}  70
info msg() ⇐

function info msg(msg, s) { # print info on console s = "% (dbTeX) Info: " msg "." # and in output file print s console msg(s)

}

# dbTeX functions

80

dbTeX command() ⇐

function dbTeX command() { # the dbTeX main function advance(ID) example flag = 0 if (tok == "examples") example flag = 1 # %! examples else if (tok == "record") { # %! record <id> <FS> [<RS>]

if (advance(ID)) {

if (tok ˜ /ˆ %[0−9]+%$/) { # data−named template templ typ = 1 templ field = substr(tok,2,length(tok)−2)

}  90

else { # script−named template templ typ = 0 templ name = tok

} } FieldS = advance(STRING) ? tok : FS RecordS = advance(STRING) ? tok : RS

} else if (tok=="template") { # %! template <id> <template>

in template = 1  100

if (advance(ID)) # get name

t name = tok if (advance(TEMPLATE))

template[t name] = tok } else if (tok=="break") { # %! break %f% <id>

if (advance(ID)) {

if (tok ˜ /ˆ %[0−9]+%$/)

breakfield = substr(tok,2,length(tok)−2)

else  110

breakfield = tok } if (advance(ID))

breaktemplate[breakfield] = tok if (advance(ID))

breakmode[breakfield] = 1 else breakmode[breakfield] = 0

} else if (tok=="clearbreaks") { # %! clearbreaks

for (i in oldvalue)  120

delete oldvalue[i] for (i in breaktemplate) delete breaktemplate[i] for (i in breakmode) delete breakmode[i]

} else if (tok=="translate") { # %! translate <id>

if (advance(ID))

set translation(tok)

}  130

else if (tok=="change") { # %! change <s1> <s2>

add trtbl(USER) } else if (tok=="include") { # %! include <filename>

if (advance(ID))

do include(tok) }

else error msg("UNKNOWN COMMAND")

}

140

advance() ⇐

function advance(type, sep,pos) { # get next token of type sub(/ˆ [ \t]+/, "", buffer) # remove white space if (type == ID) {

match(buffer, /ˆ [ˆ \t]+/) # all up to white space

tok = tolower(substr(buffer, 1, RLENGTH)) # tolower(): PolyAwk & GNU only!

buffer = substr(buffer, RLENGTH+1)

return RLENGTH }

else if (type == TEMPLATE) { # rest of line 150

tok = buffer

buffer = ""

return length(tok) } else if (type == STRING) { # string delimited by

sep = substr(buffer,1,1) # unique character

buffer = substr(buffer,2)

pos = index(buffer, sep) if (pos) {

tok = substr(buffer,1,pos−1)  160

buffer = substr(buffer, pos+1)

return pos } else {

tok = ""

return 0 }

} else return 0

}  170

add trtbl() ⇐

function add trtbl(tbl) { # add an entry to tr table

if (advance(STRING)) { tmp = tok if (advance(STRING)) tbl==USER ? (usr tbl[tmp] = tok) : (std tbl[tmp] = tok) else tbl==USER ? (usr tbl[tmp] = "") : (std tbl[tmp] = "")

}  180

}

set translation() ⇐

function set translation(id) { # install translation table

if (toupper(id) == "OFF") {

for (i in std tbl)

delete std tbl[i] for (i in usr tbl)

delete usr tbl[i]

}  190

else { # read translation table

while ((status = getline line < id) > 0) { buffer = line if (buffer !˜ /ˆ #/)

add trtbl(STANDARD) } if (status == −1)

   error msg("TRANSLATION TABLE " id " NOT FOUND")

}

}  200

do include() ⇐

function do include(file) { # include records of <file> old FS = FS; old RS = RS FS = FieldS; RS = RecordS inc = 0 while (getline < file > 0) {

for (i in breaktemplate) # check for breaks

if (oldvalue[i] != $i) {

oldvalue[i] = $i  210

if (inc || breakmode[i]) # no break before 1st rec.,

incl (template[breaktemplate[i]]) } # if not explicitly wanted!

if (templ typ)

incl (template[$templ field]) else incl (template[templ name]) ++NRincluded; ++inc

}

FS = old FS; RS = old RS  220

close(file)

if (!inc) error msg("FILE " file " NOT FOUND OR NOT ACCESSIBLE!")
info msg(inc " record(s) included from " file)

}

incl () ⇐

function incl (t) { # include record with template t

s = ""

while (match(t, /%[0−9]+%/)) {

l = RSTART−1  230

s = s substr(t,1,l) txlate($(substr(t,RSTART+1,RLENGTH−2)))

# translated data field

t = substr(t,l+RLENGTH+1) } print s t

}

txlate() ⇐

function txlate(s) { # perform translations on string s

for (target in std tbl)  240

gsub(target, std tbl[target], s) for (target in usr tbl)

gsub(target, usr tbl[target], s) return s

}