This file is part of the pdr/pdx project.
Copyright (C) 2010 Torsten Mueller, Bern, Switzerland

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/.

pdr/pdx 1.1.2 - Benutzerhandbuch

pdr ("personal data recorder") und pdx ("personal data expert") sind freie Programme zur Erfassung, Verwaltung und Auswertung von persönlichen, meist numerischen Daten.

Inhalt

1. Einleitung und Grundlagen

2. Begriffe
2.1. collections
2.2. rejections
2.3. Ausdrücke
2.4. Selektion

3. Arbeitsweise
3.1. pdr
3.1.1. Funktionsweise
3.1.2. Datenquellen und Transaktionen
3.1.3. Eingabe per Kommandozeile
3.1.4. Eingabe per Mail (POP3)
3.1.5. Eingabe über Textdateien
3.1.6. Eingabe über CSV-Dateien
3.1.7. Eingabe über XML-Dateien
3.1.7.1. Das pdr-XML-Format
3.1.8. Interaktiver Betrieb
3.1.9. XML-Export
3.2. pdx
3.2.1. Funktionsweise
3.2.2. Eingebaute Funktionen
3.2.2.1. Datums- und Zeitfunktionen
3.2.2.2. Funktionen zur Datengewinnung
3.2.2.3. Statistische Funktionen
3.2.2.4. Arithmetische Funktionen
3.2.2.5. Funktionen für Berichte
3.2.2.6. Funktionen für Diagramme
3.2.2.7. Sonstige Funktionen
3.2.3. Interaktiver Betrieb
3.2.4. Alles zusammen: die Erzeugung von Berichten und Diagrammen

4. Aufruf
4.1. pdr
4.1.1. Argumente
4.1.2. Optionen
4.1.3. Fallbeispiele
4.2. pdx
4.2.1. Optionen
4.2.2. Fallbeispiele

5. Konfiguration
5.1. Generelle Optionen
5.2. Datenbankoptionen
5.2.1. SQLite
5.2.2. MySQL
5.3. Eingabeoptionen
5.3.1. Konfiguration eines POP3-Postfachs
5.3.2. Konfiguration einer Textdatei
5.3.3. Konfiguration einer CSV-Datei
5.3.4. Konfiguration einer XML-Datei
5.4. Ausgabeoptionen
5.4.1. Konfiguration eines Berichts
5.4.2. Konfiguration eines Diagramms


1. Einleitung und Grundlagen

Menschen arbeiten heute an Computern, oft mehrere Stunden tagtäglich. Praktisch jeder hat zudem in seiner computerfreien Zeit ein einsatzbereites Mobiltelefon, einen Palmtop oder ein ähnlich mobiles Gerät. Persönliche Daten, die im Laufe eines Tages anfallen, könnten mit Hilfe ganz verschiedener Infrastruktur einer Auswertung zugeführt werden, sofern solche Wege nutzbar gemacht würden. Der Benutzer muß dasjenige wählen und nutzen können, was er gerade zur Verfügung hat. Das können je nach Ort, Tag und Tageszeit durchaus ganz unterschiedliche Mittel sein: Auf einem PC kann er seine Daten sowohl direkt mit pdr eingeben oder über Betriebssystemgrenzen hinweg eine e-mail-Nachricht schicken, vielleicht unter Benutzung eines Kommandozeilenwerkzeugs wie sendmail oder aus einer Office-Anwendung heraus. Mit seinem Mobiltelefon kann er ebenso e-mails oder SMS versenden. Er benutzt aber vielleicht auch Meßgeräte, die Daten in privaten Speichern sammeln, so daß er sie später über ein USB-Kabel, per Bluetooth, Infrarot o.ä. auf einen Computer übertragen kann. Und möglicherweise liefert bereits eine Software Daten in einem XML-Format. All diese Wege müssen gleichwertig offenstehen.

Die Ausgangslage besteht in folgenden Annahmen:
  1. Für Daten im hier relevanten Sinne liegt mindestens ein brauchbares Medium vor, über das die Daten mit vertretbarem Aufwand auf einen Computer gelangen können.
  2. Datenerfassung und -auswertung erfolgen nicht zeitgleich, insbesondere nicht in Echtzeit. Daten fallen (ggf. viel) häufiger an, als sie ausgewertet werden müssen.
  3. Die Datenerfassung muß deshalb schnell, einfach und mobil sein. Das ist das wesentliche Kriterium für ihre Akzeptanz.
  4. Bei der Datenauswertung ist der Zeitbedarf hingegen unkritisch, dort spielen Leistungsumfang und Konfigurierbarkeit die wesentliche Rolle.
  5. Als Datenauswertung gilt ein generierter, statischer Bericht ggf. mit Diagrammen. Es ist zunächst keine interaktive Arbeit am Datenbestand erforderlich.
Hintergrund: Ursprünglich stand die Erfassung medizinischer Daten im Vordergrund (Blutzucker, Blutdruck, Temperatur, Puls, Gewicht sowie auch notwendiger Medikamentierung). Insbesondere bei insulinpflichtigen Diabetikern fällt pro Tag eine ganze Reihe solcher Daten an, die zu erfassen und mit Kommentaren zu versehen für sie (und z.B. auch für Ärzte) interessant ist.

Die Applikationen sind allerdings nicht auf medizinische Anwendungsfälle spezialisiert und festgelegt. Man könnte sie auch für technische, Sport-, Wetter-, Umwelt- oder Finanzdaten verwenden, beispielsweise Strecken und Zeiten beim Joggen oder den Benzinverbrauch eines Autos, dessen gefahrene Strecken und die verursachten Kosten erfassen. Wichtig ist lediglich, daß mehr oder weniger kontinuierlich Daten anfallen, die erfaßt und statistisch ausgewertet werden sollen.


2. Begriffe

2.1. collections

Die Datenbank ist das zentrale Bindeglied zwischen pdr und pdx. Um ihre interne Gestalt muß sich der Benutzer normalerweise nicht kümmern. Der Benutzer arbeitet über die Programme pdr und pdx fast ausschließlich mit sog. collections (Meßreihen). Dies ist ein Konzept: Eine collection speichert alle Werte jeweils eines konkreten Meßparameters zusammen mit je einem eindeutigen Zeitstempel:

[...]
2008-12-17 21:45:00    5.9
2008-12-18 05:00:00    6.1
2008-12-18 12:45:00    5.3
2008-12-18 18:45:00    5.3
2008-12-18 21:45:00    4.7
2008-12-19 05:00:00    5.2
2008-12-19 12:45:00    5.4
2008-12-19 18:45:00    4.7
2008-12-19 21:45:00    5.7
[...]

Wenn fünf verschiedene Meßparameter aufzuzeichnen sind, werden auch fünf collections benötigt. Der Benutzer kann mit pdr solche collections jederzeit anzeigen, nach Bedarf und in beliebiger Anzahl anlegen und auch wieder löschen.

Jede collection trägt einen eindeutigen Namen. Dieser Name ist eine Kombination aus folgenden Zeichen:

A...Z   a...z   _ * + ! ? ^ ° § $ / &  [ ] { } = ~

Groß- und Kleinschreibung werden unterschieden. Die Anzahl der collections und die Länge ihres Namens sind nicht beschränkt.

Hinweis: Aufgrund dessen, daß diese Namen in Ausdrücken und deshalb sehr häufig, nämlich bei jeder Eingabe, verwendet werden, sollten sie möglichst kurz sein. Es spricht nichts dagegen, einzelne Buchstaben zu vergeben.

Zwei collections haben feste Namen: * und #. Erstere ist die sog. default collection, sie ist vom Typ numeric. Die andere ist die Kommentar-collection, sie ist vom Typ text. Diese beiden collections müssen nicht explizit angelegt werden, sie existieren immer. Der Grund der Existenz dieser beiden collections ist ihre spezielle (namenlose) Benutzung in Ausdrücken. Man sollte sie folglich beide für den jeweils häufigsten Anwendungsfall benutzen.

collections haben je einen konkreten Typ, der für alle ihre Datenwerte zutrifft und beim Anlegen vereinbart werden muß. Gemischte collections sind konzeptionell nicht vorgesehen. Es gibt drei mögliche Typen:

2.2. rejections

Bei Eingaben, die in die Datenbank gelangen, kann es vorkommen, daß eine solche nicht den Anforderungen genügt, möglicherweise weil sie falsch geschrieben ist oder ein ungültiges Datum besitzt. Diese Eingaben werden zurückgewiesen (rejected). Das bedeutet, sie gelangen nicht in den gültigen Datenbestand, nicht in collections. Sie gelangen allerdings in eine eigene Tabelle und sind nicht verloren.

Die Idee dahinter ist, daß diese Datensätze später aufgelistet und interaktiv nachbearbeitet werden können. Das ist besonders bei der Eingabe per e-mail wichtig, weil e-mail-Nachrichten auf dem Server nicht korrigiert werden können, es aber auch keinen Sinn hat, sie unkorrigiert auf dem Server zu belassen. Im Moment werden nur wenn nötig e-mail-Daten rejected, alle anderen Datenquellen können korrigiert und müssen nicht in dieser Form behandelt werden.

Zur Behandlung von rejections besitzt pdr zwei eigene Kommandozeilenparameter.

2.3. Ausdrücke

pdr wertet bei der Eingabe über e-mail-Postfächer, die Kommandozeile oder Textdateien sog. Ausdrücke (expressions) aus. Jede Zeile wird als ein solcher Ausdruck betrachtet und interpretiert. Ein Ausdruck kann verschiedene Meßparameter (also Datenwerte) enthalten, weshalb man bekanntgeben muß, welcher Wert in welche collection gelangen soll. Hierfür steht eine einfache Syntax zur Verfügung, der Name der collection wird an den Wert angehängt:

[Datum] [Zeit] (Wert[collection])* [; Kommentar]

Diese Definition besagt:
Hinweis: wenn innerhalb ein und desselben Ausdrucks zwei Werte für ein und dieselbe collection angegeben sind, so wird nur der hintere übernommen, denn es kann pro Zeitstempel in einer collection nur einen Wert geben.

Datum und Uhrzeit besitzen eine konkrete, nicht lokalisierte Syntax, die strikt einzuhalten ist:

[CCYY-]MM-DD   sowie   hh:mm[:ss]

Beispiele

Angenommen, wir haben folgende numerische collections in der Datenbank vereinbart: l, m, n, dazu ohnehin * und #. Folgende Ausdrücke wären somit korrekt:

5.2                                 (implizite Verwendung der default collection)
5.2*                               
(explizite Verwendung der default collection)
5.2 8l 7n 1m
75/123                             
(collection /)
"Dies ist eine Zeichenkette"z
2009-08-16 12:34 5.3 9n ; dies ist mein Kommentar
23:45 15l
; nur Kommentar

Es ist zu bemerken, daß das Primat hier klar auf leichter Eingabe liegt, auch wenn es im ersten Moment ein wenig kryptisch anmutet. Diese Ausdrücke (siehe die ersten drei Zeilen) sind auch auf Mobiltelefonen wirklich leicht zu schreiben. Gelesen werden müssen sie nur von einer Maschine. Man kann solche Ausdrücke, falls tatsächlich einmal keine Möglichkeit besteht, Daten zu übermitteln, auch leicht in einen Texteditor eingeben oder selbst auf einen Zettel schreiben und später abtippen.

2.4. Selektion

Eine Selektion ist ein Ausschnitt aus einer collection. Der Begriff ist spezifisch für pdx und die dort vorgenommenen Auswertungen und Berechnungen. Man gewinnt eine Selektion durch einen Aufruf der Funktion select oder durch eine Berechnung, die eine Selektion zurückliefert. Die Einschränkung der collection erfolgt, da eine collection nur die Zeit als Dimension besitzt, immer zeitlich, d.h. durch Auswählen bestimmter, einzelner Werte, die durch ihren Zeitstempel eindeutig benannt werden. Diese Einzelwerte müssen dabei nicht fortlaufend sein, eine Selektion darf auch Lücken haben. Man kann beispielsweise für einen ganzen Monat die Werte eines jeden Tages zwischen 8 und 9 Uhr selektieren.


3. Arbeitsweise

3.1. pdr

3.1.1. Funktionsweise

pdr sammelt und erfaßt Daten aus verschiedenen Quellen und trägt diese in collections der Datenbank ein:

Mobiltelefon -> e-mail-Postfach
                        ...     \
                       Meßgerät -+-> pdr -> Datenbank
                        ...     /
                      XML-Datei

Die Datenbank dient als Bindeglied zwischen pdr und pdx.

3.1.2. Datenquellen und Transaktionen

Momentan stehen folgende Typen von Datenquellen zur Verfügung:

diese drei Datenquellen arbeiten mit Ausdrücken
diese beiden Datenquellen arbeiten mit
verschiedenen, spezifischen Datenformaten
Die meisten Datenquellen (inputs) müssen einzeln konfiguriert werden. Die Datenquellen werden beim Aufruf in der konfigurierten Reihenfolge abgefragt in der Annahme, daß sich dort unverarbeitete Daten befinden.

pdr arbeitet mit Transaktionen, um die Integrität der Datenbank so weit wie möglich zu gewährleisten. Diese Transaktionen erstrecken sich vom Aufruf des Programms (d.h. der Entgegennahme der Parameter) bis zum Einfügen von Werten in die Datenbank. Es soll zuverlässig ausgeschlossen werden, daß die Daten einer Datenquelle nur zum Teil in die Datenbank gelangen und zum Teil nicht. Wenn bei der Verarbeitung ein Fehler auftritt, kann die Datenquelle korrigiert und die Verarbeitung erneut gestartet werden.

Hinweis: E-mail-Postfächer stellen dahingehend aufgrund der besonderen Gestalt ihrer Daten einen Sonderfall dar (siehe dort).

Konfigurierte Datenquellen werden jede in einer jeweils eigenen Transaktion verarbeitet.

Datenquellen die auf der Kommandozeile angegeben werden, werden je in einer eigenen Transaktion verarbeitet, sofern es sich um Dateien handelt. Ausdrücke, die auf der Kommandozeile angegeben werden, werden zusammengefaßt und alle in einer gemeinsamen Transaktion verarbeitet.

3.1.3. Eingabe per Kommandozeile

Die einfachste (und zugleich unkomfortabelste) Eingabemöglichkeit ist die Kommandozeile, d.h. der Aufruf von pdr selbst. Hierzu muß nichts speziell konfiguriert werden.

pdr besitzt den Kommandozeilenparameter -e bzw. --expression, der die Angabe genau eines Ausdrucks erlaubt. Dieser Parameter ist mehrfach verwendbar. Ferner werden alle hinter pdr eingegebenen Zeichen, die nicht Teil oder Argument eines Kommandozeilenparameters sind, zu einem Ausdruck zusammengefaßt und gemeinsam ausgewertet (siehe dort).

Besitzt der angegebene Ausdruck keinen Zeitstempel werden die aktuellen Werte für Datum und Uhrzeit benutzt.

Tritt bei der Verarbeitung ein Fehler auf, weil der Ausdruck nicht korrekt war, so gibt pdr eine Meldung aus. Eine Übernahme des Ausdrucks in die rejections erfolgt nicht.

3.1.4. Eingabe per Mail (POP3)

Bei der Abfrage von e-mail-Postfächern wird davon ausgegangen, daß der Benutzer anfallende Daten mit einem anderen Programm oder einem Mobiltelefon an ein e-mail-Postfach sendet und diese dort liegenbleiben (d.h. nicht anderweitig abgeholt werden). Diese e-mails müssen folgende Eigenschaften aufweisen:
  1. ein eindeutiges subject
  2. einen verwertbaren Zeitstempel (dafür sorgt normalerweise der SMTP-Server beim Senden)
  3. reines Text-Format (kein HTML, RTF o.ä.)
  4. Text ausschließlich in Ausdrücken
Ist für pdr eine e-mail-Datenquelle konfiguriert, so wird sie beim nächsten Aufruf automatisch abgefragt. pdr schaut dann nach, ob sich mails im Postfach befinden, prüft deren subject und wertet passende e-mails der Reihe nach aus, wobei jede Zeile als jeweils ein Ausdruck interpretiert wird. Besitzt eine Zeile einen eigenen Zeitstempel, so besitzt dieser Priorität, ansonsten gilt für alle Zeilen implizit der Zeitstempel der e-mail-Nachricht. Das ist insofern praktisch, weil man üblicherweise in einzeiligen e-mails nie einen Zeitstempel manuell eingeben muß.

Hier einmal ein vollständiger e-mail-Quelltext:

From: Torsten Mueller <MyMail@gmx.net>
To: MyMail@gmx.net
Subject: Q
Date: Thu, 04 Feb 2010 17:56:11 +0100
Message-ID: <87pr4ley8k.fsf@castor.ch>
MIME-Version: 1.0
Content-Type: text/plain; charset=us-ascii

5.3 8i

Die meisten Werte in den Headerzeilen kann man in der Regel aus Listen entnehmen. Date und Message-ID setzt der Server hinzu, MIME-Version und Content-Type stammen vom verwendeten e-mail-Client. Die einzigen Texte, die man wirklich tippen muß, sind das subject (es empfiehlt sich darum, das subject so kurz wie möglich zu vereinbaren, hier ein einzelner Buchstabe) sowie der Inhalt der Nachricht (letzte Zeile).

Verarbeitete e-mails werden anschließend unabhängig vom Erfolg der Verarbeitung automatisch auf dem Server gelöscht, so daß sie später nicht ein zweites Mal verarbeitet werden. Dieses automatische Löschen kann bei der Konfiguration der Datenquelle unterbunden werden.

Tritt bei der Verarbeitung ein Fehler auf, weil ein Ausdruck nicht korrekt war o.ä., so gelangen die betreffenden Ausdrücke in die rejections und müssen nachbearbeitet werden. pdr gibt in diesem Falle eine Meldung aus.

3.1.5. Eingabe über Textdateien

Bei der Verwendung von Textdateien zur Eingabe wird jede Zeile als Ausdruck interpretiert. Dieses Verfahren ist nützlich, wenn man über einen gewissen Zeitraum Daten sammeln, aber nicht online übermitteln kann. Man schreibt die Ausdrücke einfach mit einem Editor Ausdruck für Ausdruck, d.h. Zeile für Zeile in eine Textdatei.

Zeilen, die innerhalb solcher Textdateien mit # beginnen, werden nicht ausgewertet.

Tritt bei der Verarbeitung ein Fehler auf, bleibt die gesamte Datei unverarbeitet. Eine Übernahme in die rejections erfolgt nicht.

Erfolgreich verarbeitete Textdateien werden, sofern sie in der Konfigurationsdatei als Datenquelle angegeben sind, automatisch gelöscht, damit sie nicht ein zweites Mal verarbeitet werden. Dieses Löschen kann bei der Konfiguration unterbunden werden.

3.1.6. Eingabe über CSV-Dateien

Die Abkürzung CSV steht für "comma separated values", durch Komma getrennte Werte. Das Komma ist nicht ganz wörtlich zu nehmen, pdr akzeptiert das Komma, das Semikolon und den Tabulator als Separator zwischen den Werten.

Es gibt zwei Möglichkeiten, pdr mitzuteilen, welcher kommaseparierter Datenwert in welche collection gelangen soll:
  1. über eine Steuerzeile in der CSV-Datei, die den Datenzeilen vorangeht
  2. über eine Steuerzeile in der Konfigurationsdatei, die für die gesamte CSV-Datei gilt
Im ersten Fall besitzt eine von pdr verwendbare CSV-Datei folgende Struktur:

Steuerzeile
Datenzeile1
[...]
DatenzeileN

Steuerzeile
Datenzeile1
[...]
DatenzeileN

[...]

Diese Art der Verwendung von Steuerzeilen ist ungewöhnlich, liefert aber die gewünschte Flexibilität und Offenheit. In der Regel kann man sie sehr leicht einfügen, entweder von Hand oder über ein Programm wie sed. Im zweiten Fall enthielte die CSV-Datei wie erwartet nur Datenzeilen.

Eine Steuerzeile hat folgenden Aufbau:

[# pdr] datetime [Separator collection]+

z.B.

# pdr datetime, *, n, l; h; q»p, #            (» soll ein Tabulator sein)

Dies ist eine Steuerzeile für nachfolgende Datenzeilen mit jeweils einem Zeitstempel und sieben weiteren Werten für die collections *, n, l, h, q, p und #.

Jede Steuerzeile wird innerhalb einer CSV-Datei an ihrer Einleitung # pdr erkannt, bei Angabe der Steuerzeile in der Konfigurationsdatei ist diese Kennung nicht erforderlich. Das folgende Schlüsselwort datetime kennzeichnet die Lage des Zeitstempels auf den Datenzeilen. Es muß nicht am Anfang stehen, jedoch auf jeder Zeile enthalten sein - Datenwerte ohne Zeitstempel sind nicht denkbar. Im Beispiel ist zu sehen, daß verschiedene Separatoren innerhalb einer Zeile benutzt werden können. Datenzeilen, die zu dieser Steuerzeile passen, hätten damit folgendes Aussehen:

2008-10-11 12:31:38, 5.2, 7, 8; 42.3; 12»96, erste Messung
2008-10-12 12:48:08, 6.1,  , 8; 53.1; 16
»93,
2008-10-13 12:43:57, 5.8, 7, 7; 34.2; 15
»94, dritte Messung

In der zweiten Zeile fehlen Werte für die collections n und # - im Fall fehlender Werte erfolgen einfach keine Eingaben.

Besitzt man CSV-Dateien, die mehr Werte enthalten, als man in collections importieren will, so kann man auf der Steuerzeile Auslassungen vereinbaren:

# pdr datetime, a, b, , , , c, d, e

Hier werden hinter dem Zeitstempel zwei collections gelesen, dann jeweils drei Datenwerte auf den Datenzeilen ausgelassen, und dann die restlichen drei collections gelesen.

Zeilen, die innerhalb von CSV-Dateien mit dem Symbol # beginnen, werden als Kommentar betrachtet und nicht weiter ausgewertet.

Beim Einfügen von Werten in die Datenbank wird eine gesamte CSV-Datei in einer einzigen Transaktion behandelt. Wenn dabei ein Fehler auftritt, beispielsweise weil ein Datenwert nicht zum Typ der in der Steuerzeile vereinbarten collection paßt, so wird die gesamte Datei verworfen. Es kann nicht passieren, daß eine Datei zur Hälfte eingelesen wird und zur anderen Hälfte nicht und man hinterher gar keine rechte Kontrolle mehr darüber hat. Eine Übernahme in die rejections erfolgt nicht.

Erfolgreich verarbeitete CSV-Dateien werden, sofern sie in der Konfigurationsdatei als Datenquelle angegeben sind, automatisch gelöscht, damit sie nicht ein zweites Mal verarbeitet werden. Dieses Löschen kann bei der Konfiguration unterbunden werden.

3.1.7. Eingabe über XML-Dateien

pdr bietet die Möglichkeit, XML-Dateien einzulesen. Diese Dateien sind wohlstrukturiert, les- und editierbar, und sind damit ideal zum Austausch von Daten zwischen verschiedenen Softwaresystemen geeignet. pdr definiert zunächst ein eigenes, absichtlich sehr einfaches Format. Der entsprechende Programmteil ist aber dafür ausgelegt, nach entsprechender Erweiterung auch andere XML-Formate zu lesen.
3.1.7.1. Das pdr-XML-Format
Das Format ist vollständig abgelegt in der Datei pdr.xsd:

<?xml version="1.0" encoding="iso-8859-1" ?>

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" >

  <xsd:annotation>
    <xsd:documentation xml:lang="en">
     pdr XML input file definition (C) T.M. Bremgarten 2010-01-31
    </xsd:documentation>
  </xsd:annotation>

  <xsd:element name="pdr">
    <xsd:complexType>
      <xsd:sequence>
        <xsd:element name="collection" type="collection" minOccurs="0" maxOccurs="unbounded" />
      </xsd:sequence>
    </xsd:complexType>
  </xsd:element>

  <xsd:complexType name="collection">
    <xsd:sequence>
      <xsd:element name="item" minOccurs="0" maxOccurs="unbounded">
        <xsd:complexType>
           <xsd:attribute name="datetime" type="xs:string" />
           <xsd:attribute name="value" type="xs:string" />
        </xsd:complexType>
      </xsd:element>
    </xsd:sequence>
    <xsd:attribute name="name" type="xs:string" />
  </xsd:complexType>

</xsd:schema>

Diese Definition erlaubt Dateien, die folgendes Aussehen haben:

<?xml version="1.0" encoding="ISO-8859-1"?>
<pdr>
    <collection name="#" type="text">
        <item datetime="2001-07-09 18:27:11" value="erste Messung"/>
        <item date
time="2001-07-10 07:52:01" value="zweite Messung"/>
        <item date
time="2001-07-10 10:07:00" value="dritte Messung"/>
        [...]
    </collection>
    <collection name="*" type="numeric">
        <item date
time="2001-07-12 13:57:01" value="9.3"/>
        <item date
time="2001-07-12 14:46:45" value="5.6"/>
        <item date
time="2001-07-12 18:25:36" value="5.7"/>
        [...]
    </collection>
    <collection name="l" type="numeric">
        <item date
time="2001-07-03 21:41:58" value="7"/>
        <item date
time="2001-07-04 21:48:43" value="8"/>
        <item date
time="2001-07-05 21:50:49" value="7"/>
        [...]
    </collection>
</pdr>

Das Format ist beinahe selbsterklärend. Die Daten der collections werden direkt lesbar angegeben und genau so eingelesen.

Beim Einfügen von Werten in die Datenbank wird eine gesamte XML-Datei in einer einzigen Transaktion behandelt. Wenn dabei ein Fehler auftritt, beispielsweise weil ein Datenwert nicht zum Typ der angegebenen collection paßt, so wird die gesamte Datei verworfen. Es kann nicht passieren, daß eine Datei zur Hälfte eingelesen wird und zur anderen Hälfte nicht und man hinterher gar keine rechte Kontrolle mehr darüber hat. Eine Übernahme in die rejections erfolgt nicht.

Erfolgreich verarbeitete XML-Dateien werden, sofern sie in der Konfigurationsdatei als Datenquelle angegeben sind, automatisch gelöscht, damit sie nicht ein zweites Mal verarbeitet werden. Dieses Löschen kann bei der Konfiguration unterbunden werden.
3.1.7.2 (weitere XML-Formate)
...

3.1.8. Interaktiver Betrieb

pdr besitzt eine interaktive Betriebsart, die das Editieren, Ergänzen und Löschen von bereits in der Datenbank befindlichen Datenwerten erlaubt. Dies ist sehr nützlich einerseits zur Korrektur von Fehlern, andererseits zum nachträglichen Anbringen von Kommentaren und weit einfacher und komfortabler als die manuelle Ausführung von SQL-Statements in der Datenbank.

Die interaktive Betriebsart wird über den Kommandozeilenparameter -i eingeschaltet:

$ pdr -i
pdr 0.3.7, interactive mode (press ? for help)
2010-03-03 12:38:01 6.2 1m 6n                       >

Das Symbol rechts ist ein Prompt. pdr wartet auf Eingaben. Auf der linken Seite der Zeile wird die letzte, d.h. jüngste Datenzeile ausgegeben, die der Benutzer zuvor über eine Eingabe erzeugt hat. Man kann nun mit Hilfe von Navigationskommandos seinen Datenbestand Zeile für Zeile durchlaufen und dabei Manipulationen an den Daten vornehmen. Es stehen folgende Kommandos zur Verfügung:

-
zur vorhergehenden, nächst älteren Zeile springen

+
zur nachfolgenden, nächst jüngeren Zeile springen

^
zur ersten, ältesten Zeile springen

$
zur letzten, jüngsten Zeile springen

RET
die letzte Navigation wiederholen

D[collection+]
eine oder mehrere Teilausdrücke auf der aktuellen Zeile löschen, die Teilausdrücke werden in Form der collection-Namen angegeben, die durch Leerzeichen getrennt werden, werden keine collection-Namen angegeben, wird die gesamte Zeile gelöscht, Beispiele:
D
D a b c

expression
die aktuelle Zeile um den angegeben Ausdruck erweitern, besitzt die Zeile bereits Datenwerte in collections, die auch in dem neuen Ausdruck vorkommen, so erfolgt einer Aktualisierung, d.h. ein Überschreiben dieser Datenwerte, anderenfalls ein Einfügen, der Zeitstempel der Zeile kann nicht geändert werden

TAB
den Ausdruck der aktuellen Zeile vollständig in die Eingabe übernehmen, dies ist nützlich zur Korrektur ggf. längerer Kommentare, die man nicht in voller Länge neu eintippen möchte

?
einen Hilfebildschirm ausgeben

q
die interaktive Betriebsart und pdr beenden

Beispielsitzung:

$ pdr -i
pdr 0.3.7, interactive mode (press q to quit)
2010-03-03 12:38:01 6.2 1m 6n                       > -
2010-03-03 08:13:32 5.7 1m 6n 7l                    > Dm
2010-03-03 08:13:32 5.7 6n 7l                       > ; Kommentar
2010-03-03 08:13:32 5.7 6n 7l ; Kommentar           > D ; l n
2010-03-03 08:13:32 5.7                             > D
2010-03-02 22:10:56 5.8 10l                         > 12l 80.3k
2010-03-02 22:10:56 5.8 12l 80.3k                   > +
2010-03-03 12:38:01 6.2 1m 6n                       > q
$

3.1.9. XML-Export

pdr besitzt mit dem Kommandozeilenparameter -X die Möglichkeit, die Daten seiner Datenbank in eine XML-Datei zu schreiben. Das Format dieser XML-Datei entspricht dem aus Abschnitt 3.1.7.1. Damit hat der Benutzer ein Werkzeug zum Datenaustausch zwischen verschiedenen Datenbanken. Eine Einschränkung des Datenumfangs vor dem Export ist nicht vorgesehen. Die XML-Datei kann aber nachträglich leicht bearbeitet werden.

3.2. pdx

3.2.1. Funktionsweise

pdx wertet Daten aus collections aus und erstellt daraus ggf. unter Anwendung statistischer Funktionen Berichte, Tabellen und/oder Diagramme. Für die Berichte werden Textvorlagen (templates) benutzt, die zunächst Platzhalter enthalten, welche später von pdx aufgelöst und ersetzt werden. Es ist somit möglich, Berichte in praktisch jedem beliebigen (Text-)Format zu erstellen, z.B. ASCII, XML, HTML, RTF, CSV, SQL usw. Mit etwas Aufwand wäre sogar ein Backend für ODF möglich. Für Diagramme wird momentan nur das SVG-Format unterstützt. (Weitere sind denkbar.) pdx arbeitet nach folgendem Schema:

    Berichtsvorlagen              Berichte (HTML, XML, TXT)
                     \          /
           Datenbank -+-> pdx -+
                     /          \
Diagrammdefinitionen              Diagramme (SVG, PNG)

Die erzeugten Ausgaben müssen einzeln konfiguriert werden. Die wesentliche Vorarbeit für den erfolgreichen Betrieb von pdx ist die Erstellung der Berichtsvorlagen und Diagrammdefinitionen, wozu ein wenig Theorie notwendig wird.

3.2.2. Eingebaute Funktionen

pdx besitzt einen umfangreichen Satz an eingebauten Funktionen zur Gewinnung von Daten aus der Datenbank und ihrer statistischen Auswertung. Die Arbeitsweise wird damit sehr flexibel programmierbar. Die Syntax ist stark an die funktionale Programmiersprache Lisp angelehnt.

Hinweis: Es handelt sich nicht um einen echten Lisp-Interpreter. Insbesondere fehlt das Merkmal der Neudefinition von Funktionen völlig. Die Funktionsabarbeitung erfolgt allerdings wie bei Lisp streng funktional. Der Grund für die Lisp-ähnliche Syntax liegt in der genial einfachen Struktur dieser Anweisungen, die man augenblicklich durch Hinschauen versteht, ohne erst komplizierte syntaktische Konstruktionen begreifen zu müssen.

Hinweis: Eine Liste aller eingebauten Funktionen kann im interaktiven Betrieb mit ? angezeigt werden. Die meisten dieser Funktionen können im interaktiven Betrieb auch unmittelbar ausprobiert werden.

Die folgenden Abschnitte dokumentieren alle eingebauten Funktionen mit einer einheitlichen Syntax:

( Funktionsname Funktionsparameter* ) -> Ergebnistyp

Diese Zeile besagt folgendes:
  1. jeder Funktionsaufruf beginnt und endet mit einer runden Klammer
  2. jede Funktion hat einen Namen
  3. nach dem Namen folgen 0 oder beliebig viele Parameter
  4. jede Funktion hat einen Ergebistyp
Der Funktionsname allein muß nicht eindeutig, er kann überladen (overloaded) sein. Eindeutigkeit muß dann anhand der angegebenen Parameter (-anzahl und -typen) hergestellt werden.

Einige Funktionen haben eine offene Parameterliste: ... (ellipse). D.h. daß weder die Anzahl noch die Typen der Parameter festliegen. Diese Funktionen können mit beliebigen Parametern aufgerufen werden.

Alle hier verwendeten Funktionen sind streng(stens) typisiert. Der Superlativ bezieht sich darauf, daß nicht einmal Typumwandlungen (type casts) existieren. Wenn also {int} dasteht, ist auch ausschließlich {int} gemeint, ein {double}-Wert führt zu einem Fehler. Typen werden im folgenden zur Unterscheidung von Werten in geschweiften Klammern geschrieben. Typen tauchen in Funtionsdefinitionen auf, Werte bei Funktionsaufrufen. Es sind folgende Typen möglich:

{int}, {double} vorzeichenbehaftete Zahlen 5, 3.14
{string} Zeichenketten beliebiger Länge
"Hugo"
{time} meist eine Zeitdauer, selten tatsächlich eine Uhrzeit 09:13
{timestamp} ein konkreter Zeitpunkt mit Datum und Uhrzeit 2009-12-31 7:30:01
{selection} eine Menge von Zeitstempel-Wert-Paaren
{color} eine RGB-Farbe in hexadezimaler Schreibweise
#00FF00
{nothing} nur für Funktionsrückgabetypen: das Ergebnis
der Funktion ist leer und kann nicht weiter
ausgewertet werden


3.2.2.1. Datums- und Zeitfunktionen
Wo immer ein Wert vom Typ {time} benötigt wird, kann eine der folgenden Funktionen benutzt werden:

(year)          ->  {time}
(year    {int}) ->  {time}
(years   {int}) ->  {time}
(month)         ->  {time}
(month   {int}) ->  {time}
(months  {int}) ->  {time}
(week)          ->  {time}
(week    {int}) ->  {time}
(weeks   {int}) ->  {time}
(day)           ->  {time}
(day     {int}) ->  {time}
(days    {int}) ->  {time}
(hour)          ->  {time}
(hour    {int}) ->  {time}
(hours   {int}) ->  {time}
(minute)        ->  {time}
(minute  {int}) ->  {time}
(minutes {int}) ->  {time}
(second)        ->  {time}
(second  {int}) ->  {time}
(seconds {int}) ->  {time}

Diese Funktionen berechnen eine Zeitdauer mit klar definierter Länge. Die Namen sind selbsterklärend. Das {int}-Argument ist einfach ein Faktor, d.h. die Anzahl solcher Zeiteinheiten. Fehlt es, so gilt implizit der Wert 1.

Hinweis: Ein Jahr und ein Monat sind in der EDV sehr problematisch, weil sie in der Realität keine festen Längen haben. Für pdx sind die Angaben (year) und (month) per definitionem identisch mit (days 365) bzw. (days 30).

Die Funktion now gibt das aktuelle Datum und die aktuelle Uhrzeit zurück:

(now)  ->  {timestamp}

Hinweis: der Wert entspricht bei Verwendung des Kommandozeilenparameters -f dem Zeitpunkt des Programmstarts, auch wenn seitdem aufgrund längerer Berechnungen bereits ein paar Sekunden vergangen sein mögen. Alle now-Aufrufe liefern bei Verwendung von -f denselben Zeitstempel.

Hinweis: der von der Funktion now zurückgegebene Wert kann mit dem Kommandozeilenparameter -n explizit auf eine konkrete Angabe gesetzt werden. Damit lassen sich bei geschickt gestalteten Berichtsvorlagen und Diagrammdefinitionen Berichte und Diagramme für beliebige Zeitpunkte in der Vergangenheit erstellen.

Beispiele siehe nächster Abschnitt.

Mit Hilfe der Funktionen + und - lassen sich auch relative Zeitpunkte berechnen. Die Funktionen haben folgende Signaturen:

(+ {timestamp} {time})   ->   {timestamp}
(- {timestamp} {time})   ->   {timestamp}

Meist wird man für den ersten Parameter einen Aufruf von now verwenden, um einen Zeitpunkt zu berechnen, der von jetzt aus gesehen eine Woche oder einen Monat zurückliegt. Diese Funktionen sind gedacht für den Einsatz im Zusammenhang mit folding.
3.2.2.2. Funktionen zur Datengewinnung
Die folgenden Funktionen holen einen Ausschnitt aus genau einer collection und geben diesen als {selection} zurück. Der {string}-Parameter benennt jeweils die collection, mit den übrigen Parametern kann die Ergebnismenge zeitlich eingeschränkt werden:

(select {string})                          ->  {selection}
(select {string} {timestamp})              ->  {selection}
(select {string} {timestamp} {timestamp})  ->  {selection}
(select {string} {time})                   ->  {selection}
(select {string} {time} {timestamp})       ->  {selection}

Die erste Implementierung holt einfach uneingeschränkt alle Daten der angegebenen collection. Das kann ggf. lange dauern. Die zweite holt alle Daten ab dem angegebenen Zeitpunkt, die dritte alle Daten zwischen den beiden angegebenen Zeitpunkten, wobei der zweite Zeitpunkt, das Ende, nicht mit zur Ergebnismenge gehört. Die vierte Implementierung holt alle Daten in der angegebenen Zeitdauer bis jetzt, d.h. dem Zeitpunkt, den ein Aufruf von now liefern würde. Die fünfte holt alle Daten in der angegebenen Zeitdauer bis zum angegebenen Zeitpunkt.

Beispiele:

(select "*")
alle Daten der default collection holen

(select "*" 2009-12-01-12:34)
Daten der default collection seit dem 01.12.2009 12:34 holen, Hinweis beachten!

(select "n" 2009-01-01-0:00 2010-01-01-0:00)
alle Daten der collection n des Jahres 2009 holen
 
(select "l" (weeks 2))
alle Daten der collection l der letzten zwei Wochen holen

(select "l" (months 3) 2009-06-01-0:00)
alle Daten der collection l aus den drei Monaten vor dem 01.06.2009 holen

Hinweis: In einigen Fällen werden hier Zeitstempel als Parameter verwendet. Zeitstempel besitzen eigentlich die Syntax CCYY-MM-DD hh:mm[:ss], haben also eigentlich ein Leerzeichen in der Mitte. Damit hier beim Funktionsaufruf klar ist, daß es sich bei diesen Zeitstempeln um einen und nicht um zwei Parameter handelt, ist hier ein - (Minuszeichen) als Bindeglied zwischen Datum und Uhrzeit erforderlich.

Mit Hilfe der Funktion merge lassen sich Selektionen vereinigen. Das Ergebnis ist eine einzige, reguläre Selektion:

(merge keyword ...) -> {selection}

mit keyword = avg, min, max, sum, first oder last

Die Funktion erwartet beim Aufruf anstelle der Ellipse Parameter von Typ {selection}. Diese werden anhand ihrer Zeitstempel in eine einzige Selektion sortiert. Dabei kann der Fall auftreten, dass zwei Selektionen je einen Wert für ein und denselben Zeitstempel besitzen. In diesem Fall erlangt keyword Bedeutung: es kennzeichnet die Operation, die dann auf die beiden Werte angewandt wird, um einen neuen Wert zu berechnen. avg berechtet den Durchschnitt beider Werte, min nimmt den kleineren, max den grösseren, sum addiert beide Werte, first nimmt den der weiter links stehenden, last den der weiter rechts stehenden Selektion. Einige Operationen ergeben nur bei numerischen Daten einen Sinn. Alle beteiligten Selektionen müssen gleichen Typs sein. Es können auf diese Weise auch problemlos drei oder mehr Selektionen gemischt werden. Beispiel:

selection a                 selection b             (merge avg (select "a") (select "b"))
--------------------        --------------------    -------------------------------------
                            2009-11-17 12:38 9.3   
2009-11-17 12:38 9.3
2009-12-01 13:01 5.2                                2009-12-01 13:01 5.2
2009-12-02 13:02 5.7                               
2009-12-02 13:02 5.7
2009-12-03 13:03 3.2                               
2009-12-03 13:03 3.2
                            2009-12-03 19:17 8.4   
2009-12-03 19:17 8.4
2009-12-04 13:04 4.8                                2009-12-04 13:04 4.8
2009-12-05 13:05 5.7        2009-12-05 13:05 4.7   
2009-12-05 13:05 5.2 <- avg!
2009-12-06 13:06 5.3                                2009-12-06 13:06 5.3

Die Funktion fold erlaubt das "Falten" der Zeitachse einer Selektion. Man stelle sie sich auf einem Streifen Papier vor und falte diesen in Gedanken ein paar Mal, so daß Zeitabschnitte übereinanderliegen. Auf diese Weise wird es möglich, beispielsweise Tage oder Monate miteinander zu vergleichen. Die Funktion fold besitzt folgenden Prototyp:

(fold keyword1 keyword2 {selection}) -> {selection}

mit keyword1 = year, month, day, hour oder minute
und keyword2 = avg, min, max, sum, first oder last

keyword1 steuert das Interval, anhand dessen die Faltung erfolgen soll. Wird beispielsweise mit der Angabe day gefaltet, so werden aus den Zeitstempeln der Selektion alle Bestandteile bis hin zum Tag entfernt, d.h. es bleibt nur noch die Uhrzeit übrig. Alle Werte liegen dann auf einer Zeitachse, die nur noch 24 Stunden umfaßt. Beispiel:

selection a                 (fold day avg (select "a"))
--------------------        ---------------------------
2009-12-01 13:01 5.2        9999-01-01 13:01 5.45 <- avg!
2009-12-02 13:02 5.7        9999
-01-01 13:02 5.7
2009-12-03 13:03 3.2       
9999-01-01 13:03 3.2
2009-12-04 13:04 4.8        9999-01-01 13:04 4.8
2009-12-05 13:01 5.7

2009-12-06 13:06 5.3        9999-01-01 13:06 5.3

Der Sinn dieser Funktion liegt darin, beispielsweise den Verlauf einer durchschnittlichen Tageskurve zu ermitteln, jedoch auf Basis mehrer Tage mit dann sehr vielen Meßwerten.

Hinweis: auch die Selektion im Ergebnis einer fold-Operation muß gültige Zeitstempel enthalten. Es kann jedoch bei der Faltung eines Zeitbereichs kein absoluter Zeitstempel mehr ausgemacht werden, mehrere liegen ja übereinander. Aus diesem Grund tragen die Zeitstempel das Jahr 9999. Entsprechend dem benutzten Interval sind zudem auch weitere Bestandteile dieser Zeitstempel (Monat, Tag usw.) zwar syntaktisch gültig, aber inhaltlich nicht sinnvoll.
3.2.2.3. Statistische Funktionen
Alle Funktionen dieses Abschnitts führen auf einer Selektion eine statistische Zusammenfassung durch und geben grundsätzlich wieder eine Selektion zurück, in der jede Zeile einen zusammengefaßten Wert enthält. Auf diese Weise ist es möglich, beispielsweise über einen Monat den jeweiligen Tagesdurchschnitt o.ä. auszurechnen.

Das Parameterschema ist bei allen statistischen Funktionen identisch:
Die folgenden Funktionen berechnen den arithmetischen Durchschnitt (average):

(avg {selection})                          ->  {selection}   (a)
(avg {selection} {time} {time})            ->  {selection}   (b)
(avg {selection} keyword)                  ->  {selection}   (c)
(avg {selection} keyword {time})           ->  {selection}   (d)
(avg {selection} keyword {time} {time})    ->  {selection}   (e)
(avg {selection} {int} {int})              ->  {selection}   (f)

Standardabweichung (standard deviation):

(sdv {selection})                          ->  {selection}
(sdv {selection} {time} {time})            ->  {selection}
(sdv {selection} keyword)                  ->  {selection}
(sdv {selection} keyword {time})           ->  {selection}
(sdv {selection} keyword {time} {time})    ->  {selection}
(sdv {selection} {int} {int})              ->  {selection}

Anzahl:

(count {selection})                        ->  {selection}
(count {selection} {time} {time})          ->  {selection}
(count {selection} keyword)                ->  {selection}
(count {selection} keyword {time})         ->  {selection}
(count {selection} keyword {time} {time})  ->  {selection}

Die count-Funktion liefert immer eine Zeile mit einem {double}-Wert im Ergebnis, unabhängig vom Typ der Selektion.

Das arithmetische Maximum und Minimum:

(max {selection})                          ->  {selection}
(max {selection} {time} {time})            ->  {selection}
(max {selection} keyword)                  ->  {selection}
(max {selection} keyword {time})           ->  {selection}
(max {selection} keyword {time} {time})    ->  {selection}

(min {selection})                          ->  {selection}

(min {selection} {time} {time})            ->  {selection}
(min {selection} keyword)                  ->  {selection}
(min {selection} keyword {time})           ->  {selection}
(min {selection} keyword {time} {time})    ->  {selection}

Die Funktionen max und min liefern neben dem Wert, der als Maximum oder Minimum erkannt wurde, auch dessen Zeitstempel mit.

Die Summe:

(sum {selection})                          ->  {selection}
(sum {selection} {time} {time})            ->  {selection}
(sum {selection} keyword)                  ->  {selection}
(sum {selection} keyword {time})           ->  {selection}
(sum {selection} keyword {time} {time})    ->  {selection}

Die jeweils erste oder letzte, d.h. älteste oder jüngste Zeile einer Selektion:

(first {selection})                        ->  {selection}
(first {selection} {time} {time})          ->  {selection}
(first {selection} keyword)                ->  {selection}
(first {selection} keyword {time})         ->  {selection}
(first {selection} keyword {time} {time})  ->  {selection}

(last {selection})                         ->  {selection}
(last {selection} {time} {time})           ->  {selection}
(last {selection} keyword)                 ->  {selection}
(last {selection} keyword {time})          ->  {selection}
(last {selection} keyword {time} {time})   ->  {selection}

Die Funktionen first und last liefern neben dem Wert, der als ältester oder jüngster erkannt wurde, auch dessen Zeitstempel mit.

Hinweis: die meisten dieser Funtionen sind nur auf numerischen Selektionen erlaubt, d.h. solche deren Werte numerischen Typs sind ({double}). count, first und last funktionieren immer. sum funktioniert auch auf Selektionen mit Zeichenketten, beispielsweise bei Kommentaren. Diese werden dann verkettet. avg funktioniert auch auf Selektionen mit Ratio-Werten, wobei die Durchschnittsberechnung bei Zähler und Nenner getrennt durchgeführt wird.

Beispiele:

(avg (select "*"))
den Durchschnitt über alle Werte der default collection bilden

(max (select "*") day 2:00)
das tägliche Maximum der default collection ermitteln, annehmen, daß der Tageswechsel um 2:00 erfolgt

(sum (select "n" 3:30 9:00) day)
die tägliche Summe über Werte der collection n bilden, aber nur Werte zwischen 3:30 und 9:00 berücksichtigen

(avg (select "l" (month)) 5 5)
den gleitenden Durchschnitt über je 11 Werte der collection l des letzten Monats bilden

(first (select "*" (month)) day 2:00)
die jeweils erste Zeile eines jeden Tages des letzten Monats aus der default collection holen

(last (select "*" (day)) hour)
die jeweils letzte Zeile einer Stunde des letzten Tages aus der default collection holen
3.2.2.4. Arithmetische Funktionen
pdx besitzt einen bescheidenen Satz von arithmetischen Funktionen, nämlich für die vier Grundrechenarten. Diese existieren jeweils in drei verschiedenen Implementierungen:

(X {double}    {double})    -> {selection}     mit X = +, -, * oder /
(X {selection} {double})    -> {selection}
(X {selection} {selection}) -> {selection}

Die erste Form wendet die jeweilige Rechenoperation schlicht auf die beiden numerischen Operanden an. Das Ergebnis enthält genau eine Zeile. Die zweite Implementierung vollzieht die Operation mit jeder Zeile des {selection}-Parameters und dem {double}-Parameter. Das Ergebnis enthält folglich genauso viele Zeilen und dieselben Zeitstempel. Diese beiden Implementierungen sind vor allem zur Konvertierung von Maßeinheiten gedacht.

Interessant ist die dritte Implementierung. Diese erlaubt die zeilenweise arithmetische Verknüpfung zweier Selektionen. Dabei werden ihre Zeitstempel verglichen. Die Anzahlen der Zeilen in beiden Selektionen müssen allerdings nicht gleich sein. Wenn in der zweiten Selektion keine gleich alte Zeile wie in der ersten Selektion vorhanden ist, wird die nächst ältere genommen. Das Ergebnis hat so viele Zeilen wie der erste {selection}-Parameter:

selection a                 selection b                                    (* (select "a") (select "b"))
--------------------        --------------------                           -----------------------------
                            2009-11-17 12:38 9.3

2009-12-01 13:00 5.2                                ->   5.2 * 9.3 =       2009-12-01 13:00 48.36
2009-12-02 13:00 5.7                                ->   5.7 * 9.3 =      
2009-12-02 13:00 53.01
2009-12-03 13:00 3.2                                ->   3.2 * 9.3 =      
2009-12-03 13:00 18.24
                            2009-12-03 19:17 8.4

2009-12-04 13:00 4.8                                ->   4.8 * 8.4 =       2009-12-04 13:00 40.32
2009-12-05 13:00 5.7        2009-12-05 13:00 4.7    ->   5.7 * 4.7 =      
2009-12-05 13:00 26.79
2009-12-06 13:00 5.3                                ->   5.3 * 4.7 =       2009-12-06 13:00 30.21

Die Zeitstempel des Ergebnisses entsprechen ebenfalls dem ersten {selection}-Parameter. Wie man jetzt leicht erkennt, ist wesentlich, daß auch für die erste Zeile im ersten {selection}-Parameter eine brauchbare, d.h. gleich alte oder ältere Zeile im zweiten {selection}-Parameter erforderlich ist. pdx gibt einen Fehler aus, wenn diese Bedingung nicht erfüllt ist. - Nützlich ist diese Implementierung insbesondere dann, wenn man zwei Selektionen hat, die Zähler und Nenner eines Quotienten darstellen, wenn man also Werte hat, die auf irgendeine Basis bezogen sind, d.h. spezifische Werte, z.B. ein Benzinverbrauch pro 100km.
3.2.2.5. Funktionen für Berichte
Die Funktionen dieses Abschnitts sind notwendig für die Erstellung von Berichten. Sie geben jeweils eine Zeichenkette zurück, oft einen umfangreichen Block Text. pdx liest die Textvorlage des Berichts, trifft darin auf eine format-Anweisung, wertet diese augenblicklich aus und ersetzt sie an derselben Position im Text mit deren Ergebnis. Auch diese Funktionen können interaktiv getestet werden.

Die format-Funktion ist trotz ihres simplen Prototyps vergleichsweise komplex:

(format ...)      ->  {string}

Sie erwartet eine beliebig lange Liste von Parametern, die aus Text, Funktionsergebnissen, Formatierungsanweisungen und Schlüsselworten bestehen können. Das Ergebnis kann ein ein- oder mehrzeiliges Stück Text beliebiger Länge sein. Am einfachsten ist die Funktion wahrscheinlich anhand von Beispielen zu verstehen.

Beispiel 1:

(format
    (avg (select "*" (days  7)))     <1.2>
)

Dieser Aufruf erzeugt einen einzelnen, formatierten Wert. Das erste Argument der format-Funktion ist hier das Ergebnis einer statistischen Berechnung (avg-Funktion), die eine einzeilige Selektion mit einem numerischen Wert liefert. Danach folgt eine Formatierungsanweisung in spitzen Klammern, die besagt: das Ergebnis soll mindestens eine Vor- und in jedem Falle zwei Nachkommastellen haben.

Wesentlich schwieriger ist es zu verstehen, wenn unter den Parametern mehrere Selektionen auftauchen, die vielleicht auch noch mehrere und womöglich unterschiedlich viele (!) Ergebniszeilen liefern. Aber darin liegt gerade die Mächtigkeit der format-Funktion.

Beispiel 2:

(format
    "<tr>"
    "<td>"   datetime                          "</td>"
    "<td>"   (select "*" (days 7))    <1.1>    "</td>"
    "<td>"   (select "n" (days 7))    <1>      "</td>"
    "<td>"   (select "l" (days 7))    <1>      "</td>"
    "<td>"   (select "m" (days 7))    <1.0>    "</td>"
    "<td>"   (select "x" (days 7))    <1.1>    "</td>"
    "<td>"   (select "#" (days 7))             "</td>"
    "</tr>"
    newline

)

Dieser Aufruf erzeugt HTML-Zeilen für eine HTML-Tabelle mit sämtlichen Daten der collections *, n, l, m, x und # der jeweils letzten sieben Tage. (Die Tabellendefinition steht außerhalb, sie ist nicht Teil der hiesigen Betrachtung.)

Man erkennt auf den ersten Blick folgendes:
Die format-Funktion analysiert nun zunächst die Selektionen und bildet daraus eine unsichtbare Tabelle. Dann gibt sie Zeile für Zeile dieser resultierenden Tabelle aus, formatiert deren Werte und ordnet die Zeichenketten zwischen den Spalten an.

Hinweis: bei derartigen Konstrukten, die noch weit komplizierter aussehen können, lohnt es sich, sehr sauber zu schreiben und viele Leerzeichen zu verwenden, um Dinge so anzuordnen, daß man die Funktionsweise erkennen kann.

Das Ergebnis eines solchen Funktionsaufrufs ist echtes HTML und sieht so aus:

[...]
<tr><td>2009-01-17 18:58:13</td><td></td><td>6</td><td></td><td></td><td></td><td></td></tr>

<tr><td>2009-01-17 21:42:49</td><td>5.6</td><td></td><td>16</td><td></td><td></td><td></td></tr>
<tr><td>2009-01-18 05:54:41</td><td>6.8</td><td>7</td><td>8</td><td>1</td><td></td><td></td></tr>
<tr><td>2009-01-18 12:17:22</td><td>5.4</td><td>6</td><td></td><td>1</td><td></td><td></td></tr>
[...]

Die Anzahl der Zeilen wird allein durch die Selektionen bestimmt. Die fett hervorgehobenen Werte stammen aus den Selektionen, alles andere aus den Zeichenketten in der format-Funktion. Enthält eine der Selektionen auf einer Zeile keinen Wert, so bleibt das Feld leer, d.h. es steht dort <td></td>, was übrigens von vielen Browsern oft nicht gut interpretiert wird, besser wäre für leere Felder eine Angabe wie <td><br></td>.

Innerhalb einer format-Funktion besteht also gelegentlich das Problem, daß man in der Ausgabe leere Werte kennzeichnen, d.h. sichtbar machen will oder muß. Leere Werte entstehen durch die Verknüpfung mehrerer Selektionen zu einer mehrspaltigen Tabelle (outer join). Mit Hilfe der empty-Funktion

(empty {string})  ->  {string}

kann man der umgebenden format-Funktion mitteilen, welche Zeichenkette anstelle eines leeren Werts in die Ausgabe eingefügt werden soll.

Beispiel:

(format
    (empty "nil")
    [...]
)

Immer, wenn jetzt ein leerer Wert auftaucht, schreibt die format-Funktion die Zeichenkette nil aus.

Die folgenden kleinen, parameterlosen Funktionen sind sehr einfach:

(build)           ->  {string}
(version)         ->  {string}
(database)        ->  {string}

build
gibt eine Zeichenkette mit pdx-Build-Informationen aus, während version die aktuelle Programmversion liefert. Diese Werte sind dafür gedacht, in einen Bericht eingebaut zu werden, um anzuzeigen, von welcher pdx-Version dieser stammt. database zeigt die Version der aktuell geöffneten Datenbank an.
3.2.2.6. Funktionen für Diagramme
Die folgenden Funktionen werden zur Erstellung von Diagrammen verwendet. Sie geben nichts zurück, sie zeichnen ein Diagramm. Dies ist auch der Grund dafür, warum sie nicht interaktiv getestet werden können.

Die diagram-Funktion dient als Container. Alle anderen Diagrammfunktionen dürfen nur als Parameter der diagram-Funktion aufgerufen werden. Wie zu erwarten hat sie deshalb eine offene Parameterliste:

(diagram {int} {int} {color} ...)                                  ->  {nothing}

Die beiden {int}-Parameter sind Breite und Höhe des Diagramms in Pixeln.

Hinweis: diese Zahlen beinhalten nicht die Beschriftung der Achsen, sondern bezeichnen quasi das Achseninnere. Das erzeugte Diagramm ist um diese Beschriftung größer. Dies hat interne Gründe, die mit der nicht ganz unproblematischen Erzeugung einer Beschriftung in SVG zusammenhängen.

Es folgt als dritter, fester Parameter die Hintergrundfarbe.

Beispiel:

(diagram 400 300 #FDFDFD
    [...]
)

Die axes-Funktion zeichnet jeweils ein Koordinatensystem (1.Quadrant):

(axes {timestamp} {timestamp} {double} {double} {double} {color})  ->  {nothing}
(axes {time}      {timestamp} {double} {double} {double} {color})  ->  {nothing}
(axes {time}                  {double} {double} {double} {color})  ->  {nothing}
(axes keyword                 {double} {double} {double} {color})  ->  {nothing}

mit keyword = year, month, day, hour oder minute

Die erste Implementierung zeichnet die x-Achse vom ersten zum letzten angegebenen Zeitstempel, die zweite über die angegebene Zeitdauer vor dem angegebenen Zeitstempel, die dritte über die angegebene Zeitdauer vor der aktuellen Zeit. Die vierte Implementierung ist speziell für das Zeichnen von Daten, die aus einem Aufruf der Funktion fold stammen. Dabei ist dasselbe Interval anzugeben. Die Achse berechnet daraus alles Nötige. Die Schrittweite der Beschriftung der x-Achse wird automatisch festgelegt. Die folgenden drei {double}-Werte sind Untergrenze, Obergrenze und Schrittweite der y-Achse. Der {color}-Parameter benennt die Farbe der Achsen und der Achsenbeschriftung.

Beispiel:

(axes 2009-08-01-0:00 2009-09-01-0:00   2.0 10.0 1.0   #000000)
(axes (months 3)                        7.0 15.0 0.5   #101010)

Mit Hilfe der hline-Funktion lassen sich sehr nützliche, horizontale Linien in das Diagramm einfügen:

(hline {double}          {color})                                  ->  {nothing}
(hline {double} {double} {color})                                  ->  {nothing}

Der erste {double}-Parameter bezeichnet die Lage der Linie, also einen y-Wert, der optionale zweite gibt die Strichdicke an. Die Farbe der Linie steht im {color}-Parameter.

Beispiel:

(hline 7.0 0.25 #101010)

Für vertikale Linien steht die vline-Funktion zur Verfügung. Diese existiert in vier verschiedenen Varianten:

(vline {timestamp}          {color})
(vline {timestamp} {double} {color})
(vline {time}               {color})
(vline {time}      {double} {color})

Die ersten beiden Implementierungen benutzen einen absoluten Zeitstempel, die letzten beiden einen relativen, d.h. wiederkehrenden Zeitpunkt für gefaltete Zeitachsen. Der {double}-Parameter bezeichnet die Strichdicke, der {color}-Parameter die Farbe der Linie.

Beispiel:

(vline 5:45 0.25 #101010)           gefaltete Zeitachse!

Die letzte und wesentlichste Diagramm-Funktion ist die curve-Funktion. Mit ihr lassen sich Kurven in verschiedenen Stilen darstellen. Grundlage ist jeweils eine Selektion:

(curve {selection} {color} ...)                                    ->  {nothing}

Ohne weitere Parameter zeichnet die curve-Funktion eine Zickzacklinie in der angegebenen Farbe durch Verbinden der Datenwerte, die in der Selektion übergeben werden. Durch zusätzliche Parameter kann das Verhalten erheblich beeinflußt werden:
Bei Balkendarstellung können mit Hilfe von zwei {int}-Zahlen mehrere Balken pro Aggregationsintervall dargestellt werden. Dies klingt schwierig, ist aber anhand eines Beispiels leicht zu verstehen. Angenommen, wir haben Werte an vier verschiedenen Tageszeiten, sagen wir morgens, mittags, abends und spät. Und wir wollen jeden Tag durch vier nebeneinanderstehende Balken darstellen, die für diese Tageszeiten stehen. Dann wäre es erforderlich, die Balken so zu platzieren, daß sie sich nicht überdecken:

(curve   (sum (select "n" (month 1)) day  3:30  9:30)    #FF1000    bars 1 4)
(curve   (sum (select "n" (month 1)) day 11:00 14:30)    #FF5000    bars 2 4)
(curve   (sum (select "n" (month 1)) day 17:30 20:30)    #FF9000    bars 3 4)
(curve   (sum (select "n" (month 1)) day 21:00  2:00)    #FFB000    bars 4 4)

Diese vier Zeilen unterscheiden sich in den Selektionen, den Balkenfarben und dem ersten {int}-Parameter. Dieser benennt die Nummer des aktuellen Balkens, der zweite sagt, wieviele Balken es überhaupt gibt. Die erste Zeile zeichnet also den ersten von vier Balken. pdx berechnet dann, wie breit die Balken gezeichnet werden müssen, damit sie sauber nebeneinander kommen. Im Beispiel hat also ein Balken jeweils die Breite eines Viertels der Breite eines Tages auf der x-Achse. Man kann damit etwas experimentieren. Man muß nicht jeden Balken zeichnen, d.h. man könnte damit auch Lücken erzeugen, Balken gruppieren usw.
3.2.2.7. Sonstige Funktionen
pdx besitzt genau eine Funktion, die in kein Schema paßt und die ausnahmsweise auch spezifisch für Diabetiker ist: die HbA1c-Funktion:

(HbA1c {string})                          ->  {selection}
(HbA1c {string} {timestamp})              ->  {selection}
(HbA1c {string} {timestamp} {timestamp})  ->  {selection}
(HbA1c {string} {time})                   ->  {selection}
(HbA1c {string} {time} {timestamp})       ->  {selection}

Diese Funktion berechnet den HbA1c-Wert in Prozent durch ein Näherungsverfahren. Man benötigt dazu Blutzuckermeßwerte über einen Zeitraum von mindestens drei Monaten, d.h. um eine Kurve über einen Monat zu zeichnen also Meßdaten von mindestens vier Monaten. Der Absolutwert ist definitiv nicht sehr genau, aber man kann am Kurvenverlauf Schwankungen gut erkennen. Die Parameter entsprechen denen der select-Funktion. Die erste Implementierung berechnet den Wert von heute, die zweite den am angegebenen Datum.

Die HbA1c-Funktionen existieren jeweils noch in einer zweiten Implementierung:

(HbA1c2 {string})                          ->  {selection}
(HbA1c2 {string} {timestamp})              ->  {selection}
(HbA1c2 {string} {timestamp} {timestamp})  ->  {selection}
(HbA1c2 {string} {time})                   ->  {selection}
(HbA1c2 {string} {time} {timestamp})       ->  {selection}

Diese Implementierungen geben jüngeren Meßwerten ein höheres Gewicht als älteren, in der Annahme, daß dies dem natürlichen Verhalten näher kommt. Die Kurve schwankt etwas stärker als die der obigen Implementierungen.

3.2.3. Interaktiver Betrieb

pdx besitzt eine interaktive Betriebsart. Diese interaktive Betriebsart erweist sich als sehr nützlich beim Testen von Funktionsaufrufen, bevor man sie in einer Berichtsvorlage oder einer Diagrammdefinition niederschreibt. Man kann zudem sehr schnell kurze Abfragen ausführen, z.B. Wieviel Werte gibt es überhaupt in collection x? oder Was war das Allzeit-Maximum? Die interaktive Betriebsart wird über den Kommandozeilenparameter -i veranlaßt. pdx gibt dann ein Promptsymbol aus und wartet auf Eingaben:

$ pdx -i
pdx 0.3.1 (2010-01-03 16:43:29 on castor, GNU/Linux 2.6.32-ARCH x86_64)
>

An diesem Prompt gibt es zwei Befehle, ? und q, jede andere Eingabe wird als Funktionsaufruf interpretiert.

Der Befehl ? listet Implementierungen eingebauter Funktionen auf. Ohne weiteren Parameter zeigt ? alle eingebauten Funktionen mit ihren Parametertypen und ihrem jeweiligen Rückgabetyp an. ? akzeptiert als Parameter noch einen regulären Ausdruck, mit dessen Hilfe die Ergebnismenge eingeschränkt werden kann:

> ?min    alle Implementierungen der min-Funktion anzeigen
> ?a.*    alle mit a beginnenden Funktionen anzeigen
> ?m..    Funktionen anzeigen, die mit m beginnen und genau zwei weitere Zeichen haben

Der Befehl q beendet den interaktiven Betrieb und damit auch pdx. Der interaktive Betrieb kann außerdem mit Ctrl-D oder Ctrl-C beendet werden.

Die Ausführung von Funktionen erfolgt unmittelbar. Dabei wird das Ergebnis dargestellt. Ein Aufruf wie

> (select "*")

zeigt unmittelbar alle momentan verfügbaren Werte der default collection an.

3.2.4. Alles zusammen: die Erzeugung von Berichten und Diagrammen

Berichte entstehen aus Textvorlagen. pdx sucht innerhalb einer solchen Vorlage nach einem gekennzeichneten Abschnitt mit Funktionsaufrufen, meist einer format-Funktion. Dieser Abschnitt wird dann aus der Vorlage herausgeschnitten, ausgewertet und durch das Ergebnis ersetzt. In einer Vorlage können beliebig viele Abschnitte mit Funktionsaufrufen angeordnet werden.

Textvorlagen sind entweder einfacher ASCII-Text oder Text in einer Formatierungssprache wie HTML oder XML bzw. einer Programmiersprache wie C oder SQL, im Sinne dieser Abhandlung eine Wirtssprache. Worum es sich dabei konkret handelt, soll absichtlich in keiner Weise eingeschränkt werden. pdx muß aber wissen, woran die Abschnitte mit den Funktionsaufrufen zu erkennen sind. Deshalb werden solche Abschnitte generell in Kommentaren der jeweiligen Wirtssprache angeordnet, in HTML oder XML also zwischen <!-- und -->, in C hingegen zwischen /* und */. Dabei bleibt die Vorlage eine unvollständige, aber korrekte Datei ihres Typs, was den Vorteil hat, daß man sie nach wie vor mit Werkzeugen (z.B. HTML-Editoren) bearbeiten kann. Es ist klug, diese pdx-Kommentarblöcke noch weiter zu kennzeichnen, um sie von bereits existierenden, echten Kommentaren zu unterscheiden. Man verwende also klugerweise z.B. <!--- und ---> oder /** und **/. Eine vollständige, kleine Vorlage für eine HTML-Datei mit pdx-Anweisungen würde also beispielsweise wie folgt aussehen:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html lang="de-ch">
<head>
    <meta http-equiv="CONTENT-TYPE" content="text/html; charset=iso-8859-1">
    <title>MyTitle</title>
</head>

<body style="direction: ltr;" lang="de-DE">

<!--- (now) --->, <b>pdx</b> <!--- (version) ---> (<!--- (build) --->)                          *

</body>
</html>

In der mit * markierten Zeile stehen drei pdx-Abschnitte, in denen je eine der Funktionen now, version und build aufgerufen wird. Die Ausgabe, die diese Zeile erzeugt, sieht ungefähr so aus:

2009-12-27 15:14:51, pdx 0.3.0 (2009-12-27 10:28:14 on castor, GNU/Linux 2.6.31-ARCH x86_64)

Man sieht, daß alle Bestandteile der Zeile, die nicht innerhalb pdx-Abschnitten stehen, (sowie auch der gesamte umgebende Text) unverändert in die Ausgabe übernommen werden. Diese Zeile hätte übrigens unter Benutzung der format-Funktion auch anders formuliert werden können:

<!--- (format (now) ", <b>pdx</b> " (version) " (" (build) ")") --->

Um nun in HTML eine komplette Tabelle anzuordnen, würde man beispielsweise wie folgt vorgehen:

[...]

<table style="page-break-before: always; page-break-inside: avoid;
             width: 800px;" border="1" cellpadding="1" cellspacing="1">

  <tbody>
    <tr valign="top">
      <td>Datum/Zeit</td>
      <td>*</td>
      <td>n</td>
      <td>l</td>
      <td>m</td>
      <td>x</td>
      <td>Kommentar</td>
    </tr>
<!---
(format
    (empty "<br>")
    "<tr valign=top>"
    "<td>"    datetime                         "</td>"
    "<td>"    (select "*" (days 7))   <1.1>    "</td>"
    "<td>"    (select "n" (days 7))   <1>      "</td>"
    "<td>"    (select "l" (days 7))   <1>      "</td>"
    "<td>"    (select "m" (days 7))   <1.0>    "</td>"
    "<td>"    (select "x" (days 7))   <1.1>    "</td>"
    "<td>"    (select "#" (days 7))            "</td>"
    "</tr>" newline
)
--->
  </tbody>
</table>

[...]

Die Tabelle beginnt außerhalb des pdx-Abschnitts und endet wiederum außerhalb. Die Zeilen der Tabelle mit Ausnahme der Kopfzeile werden aber vollständig von pdx generiert. Diese Zeilen besitzen alle das gleiche Aussehen.

Diagramme entstehen aus Diagrammdefinitionsdateien. Diese enthalten immer genau eine Diagrammdefinition, d.h. einen Aufruf der diagram-Funktion. In Diagrammdefinitionsdateien sind keine Kommentar-Abschnitte notwendig, da es keine Wirtssprache gibt. Eine vollständige Diagrammdefinition sieht beispielsweise so aus:

(diagram 400 300 #FFFDFD

    (axes (month 1) 3.0 9.0 1.0 #0)

    (hline 5.0 #C0C0C0)
    (hline 6.0 #C0C0C0)
    (hline 7.0 #C0C0C0)

    (curve (sum (select "*" (month 1)) day  3:30  9:30)    #FF0000)
    (curve (sum (select "*" (month 1)) day 11:00 14:30)    #00FF00)
    (curve (sum (select "*" (month 1)) day 17:30 20:30)    #0000FF)
    (curve (sum (select "*" (month 1)) day 21:00  2:00)    #FFFF00)
    (curve (avg (select "*" (month 1)) day        2:00)    #0       1.0)
)

Bei der Entwicklung neuer Berichtsvorlagen bzw. Diagrammdefinitionen ist es klug, sich existierende Daten herzunehmen und leicht zu modifizieren.


4. Aufruf

4.1. pdr

pdr akzeptiert Optionen und Argumente.

Hinweis: Optionen können selbst Argumente haben, was nicht verwechselt werden darf.

Optionen werden grundsätzlich durch ein Minuszeichen eingeleitet. Ein zweites Minuszeichen kennzeichnet sog. lange Optionen.

4.1.1. Argumente

Alles, was hinter dem Programmnamen pdr auf der Kommandozeile folgt und nicht mit einem Minuszeichen beginnt, wird als Argument gewertet. Alle Argumente werden zu einem einzigen Ausdruck zusammengefügt und gemeinsam abgearbeitet:

$ pdr 5.2 5n 8l -v \; Kommentar bis zum Ende der Zeile

Der resultierende Ausdruck lautet:

5.2 5n 8l ; Kommentar bis zum Ende der Zeile

-v gehört nicht dazu, dies ist eine erkennbare Option von pdr, sie wird nicht mit in den Ausdruck übernommen. Der backslash vor dem Semikolon ist eine Spezialität unter unixartigen Betriebssystemen. Bei solchen wertet die shell ihrerseits oft das Semikolon aus, was uns jedoch als Kommentartrennzeichen dient. Um dies zu verhindern, muß man ihm einen backslash voranstellen. Dieser wird allerdings bereits von der jeweiligen shell entfernt und gelangt gar nicht in die Eingabe von pdr.

4.1.2. Optionen

-?
obigen Hilfebilfschirm anzeigen

-V
die pdr-Versionsnummer anzeigen

-v
Ausgaben machen, ist diese Option nicht gesetzt, macht pdr nur
dann Ausgaben, wenn Fehler aufgetreten sind

-c filename
die Datei filename als Konfigurationsdatei benutzen, diese Option
hat Priorität gegenüber der standardmäßig benutzten
Konfigurationsdatei ~/.pdrx

-l
die momentan verfügbaren collections auflisten

-a "name,type"
eine collection hinzufügen, das Argument ist eine Zeichenkette,
die den Namen und den Typ der neuen collection angibt, als Typen
kommen in Frage n, r oder t (für numeric, ratio oder text)

-d name
eine collection löschen, als Argument muß der Name übergeben
werden

-D
alle collections löschen, die collections * und # werden nicht
gelöscht, sie werden nur geleert

-r
alle aktuellen rejections auflisten

-R
alle aktuellen rejections löschen

-e "expr"
die als Argument übergebene Zeichenkette aus Ausdruck
betrachten und auswerten

-t filename
eine Textdatei einlesen

-C filename
eine CSV-Datei einlesen

-x filename
eine XML-Datei einlesen

-n
keine in der Konfigurationsdatei vorkonfigurierten Datenquellen
benutzen, nur die Kommandozeile
-i
die interaktive Betriebsart starten
-X filename
den Inhalt der gesamten Datenbank in eine XML-Datei schreiben, das Format ist kompatible zum Import über -x

4.1.2. Fallbeispiele

Wichtig sind die Optionen zur Handhabung von collections. -l bzw. --list-collections listet zunächst alle vorhandenen collections auf und liefert ein paar statistische Daten:

$ pdr -l
  name   type     table  recs    first                last
  #      text     C1     160     2008-11-25 18:45:00  2010-01-02 21:55:37
  *      numeric  C0     1636    2008-11-25 05:00:00  2010-01-03 12:10:00
  h      numeric  C6     1       2009-05-19 16:00:00  2009-05-19 16:00:00
  l      numeric  C3     707     2008-11-25 05:00:00  2010-01-03 06:26:01
  m      numeric  C4     612     2009-03-04 05:00:00  2010-01-03 06:26:01
  n      numeric  C2     1275    2008-11-25 05:00:00  2010-01-03 12:10:00
  x      numeric  C5     119     2009-03-22 09:28:09  2010-01-03 10:31:01

Diese Auflistung zeigt Name und Typ jeder collection, die physische SQL-Tabelle in der Datenbank, die Anzahl der dort befindlichen Zeilen, d.h. Meßwerte sowie den Zeitstempel des ältesten und des jüngsten Eintrags.

Mit -a bzw. --add-collection und einem string-Argument läßt sich eine collection hinzugügen:

$ pdr -a "k,n"

$ pdr -l
  name   type     table  recs    first                last
  #      text     C1     160     2008-11-25 18:45:00  2010-01-02 21:55:37
  *      numeric  C0     1636    2008-11-25 05:00:00  2010-01-03 12:10:00
  h      numeric  C6     1       2009-05-19 16:00:00  2009-05-19 16:00:00
  k      numeric  C7     0
  l      numeric  C3     707     2008-11-25 05:00:00  2010-01-03 06:26:01
  m      numeric  C4     612     2009-03-04 05:00:00  2010-01-03 06:26:01
  n      numeric  C2     1275    2008-11-25 05:00:00  2010-01-03 12:10:00
  x      numeric  C5     119     2009-03-22 09:28:09  2010-01-03 10:31:01

Das Argument enthält den Namen der neuen collection, ein Komma und dahinter den Typ in Form von n (für numeric), r (für ratio) oder t (für text).

Eine nicht mehr benötigte collection kann mit -d bzw. --delete-collection und dem Namen der collection gelöscht werden:

$ pdr -d k

Sämtliche collections können mit -D bzw. --delete-all-collections gelöscht werden. Die collections * und # bleiben dabei erhalten, werden aber geleert:

$ pdr -D

$ pdr -l
  name   type     table  recs    first                last
  #      text     C1     0
  *      numeric  C0     0

Zwei Optionen dienen der Behandlung von rejections, d.h. zurückgewiesenen Daten, bei denen ein erkennbares Problem vorlag, aufgrunddessen die Daten nicht in die Datenbank übernommen werden konnten. Diese Daten gelangen dann in eine Schattentabelle. pdr liefert beim Auftreten solcher Fehler eine Ausschrift:

at least one expression has been rejected, try -r to list rejections

Mit -r bzw. --list-rejections können diese Daten nachträglich angezeigt werden:

$ pdr -r
  timestamp            expression
  2010-01-03 17:46:20  12.0k                               (Fehler: die collection k existiert nicht)

Wenn tatsächlich ein Schreibfehler vorliegt, kann die Eingabe wiederholt werden. Nach erfolgter Korrektur aller rejections kann die Schattentabelle geleert werden:

$ pdr -R

Die Option -e bzw. --expression erlaubt die Angabe jeweils eines Ausdrucks, der zur Eingabe benutzt wird:

$ pdr   -e "5.2"   -e "2009-12-31 17:28:03 7.9"

Die Option kann mehrfach verwendet werden, was jeweils einen neuen Ausdruck erfordert. Die Ausdrücke bleiben untereinander unabhängig und werden einzeln verarbeitet.

Die Option -n bzw. --none erlaubt das Außerkraftsetzen aller konfigurierten Datenquellen. Dies dient dem Fall, daß der Benutzer in kurzer Zeit mehrfach hintereinander Aufrufe des Programms tätigt, z.B. um Eingaben über die Kommandozeile durchzuführen. Viele mail server melden dann beispielsweise, daß pro Minute nur eine bestimmte Anzahl an Logins durchgeführt werden darf o.ä. Das ständige Herstellen der Verbindung kostet zudem spürbar Laufzeit. Mit -n arbeitet man ganz privat einfach auf der lokalen Datenbank.

4.2. pdx

pdx besitzt keine Argumente, nur Optionen.

Optionen werden grundsätzlich durch ein Minuszeichen eingeleitet. Ein zweites Minuszeichen kennzeichnet sog. lange Optionen.

4.2.1. Optionen

-?
obigen Hilfebildschirm anzeigen

-V
die pdx-Versionsnummer anzeigen

-v
Ausgaben machen, ist diese Option nicht gesetzt, macht pdx
nur dann Ausgaben, wenn Fehler aufgetreten sind

-c filename
die Datei filename als Konfigurationsdatei benutzen, diese Option
hat Priorität gegenüber der standardmäßig benutzten
Konfigurationsdatei ~/.pdrx

-n timestamp
den Wert der Funktion now auf timestamp festlegen

-i
die interaktive Betriebsart einschalten

-f
den schnellen Modus einschalten

4.2.2. Fallbeispiele

-n bzw. --now läßt die Angabe des Wertes der Funktion now zu. Überall, wo now explizit oder implizit verwendet wird, wird dann statt des aktuellen Zeitstempels der angegebene Wert verwendet. Das ist sehr praktisch, um mit wenigen Handgriffen die gesamte Generierung von Berichten und/oder Diagrammen für einen konkreten Zeitpunkt, etwa für den letzten Monat o.ä. durchzuführen, ohne dies aufwendig erst konfigurieren zu müssen. Vorausetzung dafür ist, daß man in den Berichtsvorlagen und/oder Diagrammdefinitionen keine festen Zeitstempelwerte konfiguriert. Zeitangaben im Argument sind optional. Was fehlt, wird mit Nullen ergänzt - die folgenden Aufrufe sind absolut gleichbedeutend:

$ pdx -n 2009-10-01
$ pdx -n 2009-10-01-00:00
$ pdx -n 2009-10-01-00:00:00

-f bzw. --fast verbessert das Laufzeitverhalten möglicherweise erheblich. Dazu werden einmal berechnete Ergebnisse intern gespeichert, so daß sie bei der nächsten Verwendung augenblicklich zur Verfügung stehen. Dies kostet Arbeitsspeicher, beschleunigt das Programm aber spürbar. Man sollte wenn immer möglich mit -f arbeiten, es am besten gleich in die Konfigurationsdatei ~/.pdrx eintragen. Einen Einfluß auf die Ergebnisse hat -f selbstverständlich nicht.


5. Konfiguration

pdr und pdx erfordern einige Konfiguration, da ihr Verhalten in weiten Grenzen beeinflußbar ist. All diese Einstellungen werden in einer lokalen Konfigurationsdatei mit dem Namen ~/.pdrx abgelegt. Diese Datei hat vier Abschnitte:
  1. Generelle Optionen
  2. Datenbankoptionen
  3. Eingabeoptionen (d.h. pdr-spezifische Optionen)
  4. Ausgabeoptionen (d.h. pdx-spezifische Optionen)
Die Reihenfolge der Abschnitte oder Zeilen spielt keine Rolle.

5.1. Generelle Optionen

In diesem Abschnitt werden zwei Dinge konfiguriert:
Grundlegende Einstellungen entsprechen Kommandozeilenparametern. Nicht jeder Kommandozeilenparameter ist hier verwendbar, aber einige:

verbose = true

Diese Zeile legt fest, daß pdr und pdx während ihrer Arbeit grundsätzlich Ausgaben machen sollen, damit man die Arbeitsweise verfolgen kann. Anderenfalls werden nur Fehlerausschriften gemacht. (empfehlenswert)

fast = true

Diese Einstellung bewirkt, daß pdx im fast-Modus läuft. (empfehlenswert)

Wer pdx nicht zum Generieren von Berichten und Diagrammen benutzt, sondern lediglich interaktive Abfragen auf den Daten ausführen will, kann pdx mit der Zeile

interactive = true

grunsdätzlich in den interaktiven Betrieb versetzen.

Die Zeile

encoding = UTF-8

legt das Encoding fest, das immer dann benutzt wird, wenn keine konkretere Angabe gemacht wird. Diese Option ist dafür verantwortlich, daß Texte (z.B. Kommentare, man denke insbesondere an deutsche Umlaute) korrekt in die Datenbank gelangen. Auf modernen Systemen wird man UTF-8 oder ISO-8859-1 anwenden. pdr erlaubt ASCII, UTF-8, UTF-16, ISO-8859-1, ISO-8859-15, Windows1252.

Wesentlich ist die Konfiguration von inputs und outputs.

inputs = e-mail-postfach, file1, file2, file3
outputs = report1, diagram1, diagram2, diagram3, diagram4

Die erste Zeile definiert inputs für pdr, d.h. vier Datenquellen, nämlich e-mail-postfach, file1, file2 und file3, die nacheinander und zwar in dieser Reihenfolge abgefragt werden. Worum es sich dabei konkret handelt, wird später bei den Eingabeoptionen konfiguriert. Die zweite Zeile benennt gleichermaßen fünf outputs für pdx, nämlich einen Bericht und vier Diagramme. Ihre Konfiguration erfolgt später bei den Ausgabeoptionen.

5.2. Datenbankoptionen

Hier werden Typ und Zugang zur verwendeten Datenbank konfiguriert.

5.2.1. SQLite

database.type = sqlite
database.connect = ~/local/share/my_data.db

Die erste Zeile besagt, daß die konkrete Datenbank eine SQLite-Datenbank ist. Die zweite Zeile enthält den vollständigen connect string zur Datenbank. Im Fall von SQLite ist dies schlicht der Name der Datenbankdatei mit Pfad, damit die Programme die Datenbank finden. Da es sich um persönliche Applikationen handelt, d.h. es keinen Mehrbenutzerbetrieb auf der Datenbank gibt, sollte diese in einem lokalen Benutzerverzeichnis liegen. Das Erzeugen der physischen Datenbank ist nicht Aufgabe von pdr oder pdx. Der Benutzer muß dies mit den Mitteln des jeweiligen Datenbank- bzw. Betriebssystems selbst vornehmen. Bei SQLite ist dies ganz einfach:

$ cat > my_database.db      (mit Ctrl-D beenden)

Dieser Aufruf erzeugt eine 0 Byte große Datei, die pdr als leere Datenbank akzeptiert.

5.2.2. MySQL

database.type = mysql
database.connect = user=my_db_user_name;password=my_db_user_password;db=my_db_name;compress=true;auto-reconnect=true

Die erste Zeile besagt, daß die konkrete Datenbank eine MySQL-Datenbank ist. Die zweite Zeile enthält den vollständigen connect string zur Datenbank. Dieser enthält Paare der Form Schlüssel=Wert, die Schlüssel sind oben fett gemacht. Voraussetzungen zur Bildung dieses connect strings sind folgende:
  1. Die Datenbank muß existieren, d.h. sie muß zuvor von einem Datenbankadministrator angelegt worden sein. Dieser vergibt dabei einen Namen, der auf dem Datenbankserver eindeutig ist, z.B. pdrx. Es ist wahrscheinlich empfehlenswert, auf Servern, an denen mehrere Benutzer arbeiten, mehrere benutzerspezifische Datenbanken anzulegen und am Namen zu unterscheiden.
  2. Der Benutzer (ein Benutzer des Datenbankservers, nicht des Betriebssystems) muß existieren und das Recht besitzen, Tabellen anzulegen, zu löschen, zu selektieren und zu manipulieren.

5.3. Eingabeoptionen

Im Fall der häufigen Verwendung immer gleicher Datenquellen zur Eingabe können diese in die Konfigurationsdatei eingetragen und administriert werden. Diese Datenquellen werden dann bei jedem Aufruf von pdr automatisch abgefragt.

5.3.1. Konfiguration eines POP3-Postfachs

Die Konfiguration eines POP3-Postfachs (e-mail-postfach) erfordert folgende Einstellungen:

e-mail-postfach.type     = pop3
e-mail-postfach.server   = pop.gmx.net
e-mail-postfach.account  = MyAccount@gmx.net
e-mail-postfach.password = MyPassword
e-mail-postfach.subject  = Z
e-mail-postfach.keep     = yes

Die erste Zeile legt fest, dass es sich bei e-mail-postfach um ein Postfach handelt, das über POP3 abgefragt wird. Die nächsten drei Zeilen sind selbsterklärend. Die vierte Zeile benennt die Betreffzeile (subject), an der pdr relevante e-mails in diesem Postfach erkennt. Nur mails mit dieser Betreffzeile werden bearbeitet, alle anderen ignoriert. Auf diese Weise muß man nicht zwingend ein neues Postfach anlegen, man kann ein existierendes mitbenutzen. Hinweis: diese Betreffzeile muß bei jedem mail angegeben werden. Wer viele Datenmails verschickt, muß dies also ggf. sehr oft tun. Man halte sie deshalb möglichst kurz, aber zweifelsfrei eindeutig. Die letzte Zeile legt fest, ob ene e-mail-Nachricht nach erfolgreicher Verarbeitung gelöscht werden soll. Die Option akzeptiert true, false, yes und no. Im Fall von true oder yes bleibt die e-mail-Nachricht auf dem Server bestehen. Fehlt diese Option, wird sie gelöscht.

5.3.2. Konfiguration einer Textdatei

Die Verwendung einer Textdatei als Eingabe erfordert folgende Konfiguration:

file1.type     = txt
file1.filename = ~/my_file.txt
file1.encoding = ISO-8859-1
file1.keep     = true

Die erste Zeile legt fest, daß es sich bei file1 um eine Textdatei mit interpretierbaren Ausdrücken handelt. Die zweite Zeile gibt den Dateinamen bekannt, die dritte Zeile ein Encoding. Fehlt sie, wird default_encoding benutzt. Die letzte Zeile legt fest, ob die Textdatei nach erfolgreicher Verarbeitung gelöscht werden soll. Die Option akzeptiert true, false, yes und no. Im Fall von true oder yes bleibt die Datei bestehen. Fehlt diese Option, wird die Datei gelöscht.

filename erlaubt die Verwendung von wildcards (* und ?) um eine Datei mit nur unvollständig bekanntem oder wechselndem Namen oder gleich eine ganze Gruppe von Dateien zu verarbeiten. Der enthaltene Pfad muß vollständig sein, im Dateinamen kann man jedoch eine Angabe wie *.txt machen, um alle Dateien im angegebenen Verzeichnis zu verarbeiten.

5.3.3. Konfiguration einer CSV-Datei

Die Verwendung einer CSV-Datei als Eingabe erfordert folgende Konfiguration:

file2.type      = csv
file2.filename  = ~/my_file.csv
file2.encoding  = ISO-8859-1
file2.ctrl_line = datetime, x, y, z
file2.keep      = false

Die erste Zeile legt fest, daß es sich bei file2 um eine CSV-Datei handelt. Die zweite Zeile gibt den Dateinamen bekannt, die dritte Zeile ein Encoding. Fehlt sie, wird die generelle Option encoding benutzt. Die Option ctrl_line legt bei Bedarf eine Steuerzeile für die CSV-Datei fest. Die CSV-Datei selbst muß dann keine Steuerzeile enthalten. Die letzte Zeile legt fest, ob die CSV-Datei nach erfolgreicher Verarbeitung gelöscht werden soll. Die Option akzeptiert true, false, yes und no. Im Fall von true oder yes bleibt die Datei bestehen. Fehlt diese Option, wird die Datei gelöscht.

filename erlaubt die Verwendung von wildcards (* und ?) um eine Datei mit nur unvollständig bekanntem oder wechselndem Namen oder gleich eine ganze Gruppe von Dateien zu verarbeiten. Der enthaltene Pfad muß vollständig sein, im Dateinamen kann man jedoch eine Angabe wie *.csv machen, um alle Dateien im angegebenen Verzeichnis zu verarbeiten.

5.3.4. Konfiguration einer XML-Datei

Die Verwendung einer XML-Datei als Eingabe erfordert folgende Konfiguration:

file3.type     = xml
file3.filename = ~/my_file.xml
file3.keep     = no

Die erste Zeile legt fest, daß es sich bei file3 um eine XML-Datei handelt. Die zweite Zeile gibt den Dateinamen bekannt. Eine Angabe des Encodings fehlt hier, da die XML-Datei diese Information in sich trägt. Die letzte Zeile legt fest, ob die XML-Datei nach erfolgreicher Verarbeitung gelöscht werden soll. Die Option akzeptiert true, false, yes und no. Im Fall von true oder yes bleibt die Datei bestehen. Fehlt diese Option, wird die Datei gelöscht.

filename erlaubt die Verwendung von wildcards (* und ?) um eine Datei mit nur unvollständig bekanntem oder wechselndem Namen oder gleich eine ganze Gruppe von Dateien zu verarbeiten. Der enthaltene Pfad muß vollständig sein, im Dateinamen kann man jedoch eine Angabe wie *.xml machen, um alle Dateien im angegebenen Verzeichnis zu verarbeiten.

5.4. Ausgabeoptionen

5.4.1. Konfiguration eines Berichts

Ein von pdx zu erzeugender Bericht erfordert folgende Konfiguration:

report1.type          = report
report1.comment_begin = "<!---"

report1.comment_end   = "--->"
report1.input_file    = input/report1.html
report1.output_file   = output/report1.html
report1.encoding      = ISO-8859-1

Die erste Zeile besagt, daß report1 ein Bericht ist. Die nächsten beiden Zeilen benennen die beiden Kommentarkennzeichnungen, an denen pdx seine Anweisungen innerhalb der als Vorlage verwendeten Eingabedatei erkennt. Die vierte und fünfte Zeile benennen die Ein- und die Ausgabedatei. Die letzte Zeile gibt bekannt, in welchem Zeichensatz die Eingabedatei vorliegt und die Ausgabedatei erzeugt wird, notwendig für Dateien, die das Encoding nicht in sich tragen.

5.4.2. Konfiguration eines Diagramms

Die Konfiguration eines Diagramms ist ähnlich der eines Berichts, aber etwas einfacher:

diagram1.type = diagram
diagram1.input_file=input/diagram1.tmpl
diagram1.output_file=output/diagram1.png
diagram1.antialias=true;

Kommentarkennzeichnungen sind in Diagrammdefinitionen nicht nötig. De letzte Option antialias ist nur für png-Dateien verfügbar. Wird diese Option benutzt, so erzeugt die Cairo-Bibliothek Diagramme, die in der Regel wesentlich besser aussehen, sofern sie Zickzack-Linien enthalten.