Inhaltsverzeichnis


Zurück zur Übersichtsseite - TUSTEP und XML


Abfrage einer XML-Datenbank mit TUSTEP

- chmoser chmoser

Ausgangslage

Sie oder ihr Team arbeiten dezentral mit XML-Daten und speichern diese zentral in einer XML-Datenbank (z.B. exist-db oder BaseX), die irgendwo auf einem Server läuft. Sie möchten nun aus Tustep heraus auf diese Datenbank zugreifen, sei es, um eine Statistik, einen Bericht oder irgendwelche Listen zu generieren oder sei es, um Texte mit Tustep zu setzen oder irgendwie weiter zu verarbeiten.

Für folgende Erläuterungen wird exist-db verwendet. Die Datenbank ist für diesen Zweck mit der Standardkonfiguration auf dem localhost installiert und lauscht auf Port 8080. Die XML-Daten liegen innerhalb der Datenbank in der collection (im „Ordner“) /db/apps/tonhalle/data und enthalten Informationen zu verschiedenen Konzerten. Die Struktur dieser Daten entspricht dem Beispiel hier.

WebDAV

Eine ganz simple Lösung für ihr Anliegen, mit Tustep auf die Datenbank zuzugreifen, besteht in der Nutzung der WebDAV-Schnittstelle (Web-based Distributed Authoring and Versioning), die exist-db bereitstellt. Dabei werden die collections und Dokumente der Datenbank wie ein Verzeichnis in das lokale Dateisystem eingebunden. Die entsprechende Dokumentation im Zusammenhang mit exist-db findet sich hier. Die Tustep-Zugriffsmöglichkeiten auf dieses Verzeichnis sind dann dieselben, wie wenn die Dokumente lokal auf dem Computer gespeichert vorliegen würden. Dies bedeutet aber, dass zwar einzelne Dokumente geöffnet, eingelesen und bearbeitet werden können, der eigentliche Vorteil einer XML-Datenbank - nämlich die Möglichkeit, die Datenbank im eigentlichen Sinne „abfragen“ zu können - kann mit dieser Methode aber nicht genutzt werden.

Zugriff über REST

Eigentliche Datenbankabfragen können über die REST-Schnittstelle (Representational State Transfer) von exist-db realisiert werden. Die entsprechende Dokumentation findet sich hier. Dabei wird die Datenbank über eine bestimmte URL aufgerufen, wobei Parameter übergeben werden können. Die Datenbank verarbeitet den Aufruf und liefert das Ergebnis zurück. Ein REST-basierter Zugriff auf exist-db (Methode = GET) hat folgende Syntax:

http://server-adresse/exist/rest/pfad-zur-collection?_parameter=wert&parameter=wert...

Beispiel:

http://localhost:8080/exist/rest/db/apps/tonhalle/data?_query=//konzert&_howmany=1000&_wrap=yes


Parameter

_query=XPath/XQuery
enthält die eigentliche Abfrage in Form einer XPath oder einer XQuery-Anweisung
_indent=yes | no
spezifiziert, ob das gelieferte Resultat als pretty print geliefert werden soll (Standardeinstellung = yes)
_encoding=Codierung
definiert die Codierung des Resultats (Standardeinstellung: UTF-8)
_howmany=Anzahl
Anzahl der Treffer, die geliefert werden sollen (Standardeinstellung: 10)
_start=Position
spezifiert die Index-Postion, ab welcher die Treffer geliefert werden sollen (Standardeinstellung: 1)
_wrap=yes | no
definiert, ob die Resultate von einem <exist:result>-Element umschlossen werden sollen (Standardeinstellung = yes)

Beschreibungen von weiteren Parametern finden sich hier


Der Aufruf von

http://localhost:8080/exist/rest/db/apps/tonhalle/data?_query=//konzert&_indent=yes&_encoding=UTF-8&_howmany=1000&_start=1&_wrap=yes

bedeutet also:


Der Aufruf

http://localhost:8080/exist/rest/db/apps/tonhalle/data?_query=//konzert

würde wegen den Standardwerten der Parameter dasselbe Resultat liefern, außer dass maximal 10 Treffer geliefert werden.

Das Resultat im Browser sodann:

exist_result_browser.PNG


Aufruf von Tustep aus
Nun wollen wir uns als Tustep-Enthusiasten dieses Resultat aber selbstverständlich nicht im Browser, sondern direkt in Tustep ansehen.
Dies wird bewerkstelligt mit der Tuscript-Funktion REQUEST:

#makro
$$ MODE TUSCRIPT

SET data = REQUEST("http://localhost:8080/exist/rest/db/apps/tonhalle/data?_query=//konzert")
SET data = DECODE(data, UTF8)
FILE/ERASE "ergebnis" = data

*eof

Erläuterung:


Das Resultat in der Datei ergebnis sodann:

exist_result_tustep.PNG


Varianten

Der eben beschriebene REST-Zugang wurde nach der GET-Methode vorgenommen, d.h. alle nötigen Informationen wurden der Datenbank über die URL übermittelt. Die Spezifikation des HTTP-Protokolls beschränkt die maximale Länge einer URL zwar nicht, doch können Browser und/oder Server ab einer bestimmten Länge Probleme bekommen (Hinweise hier und hier). Dies bedeutet, dass die GET-Methode möglichweise an Grenzen stößt, falls sehr komplexe XQuery-Abfragen (z.B. mit Funktionsdefinitionen etc.) getätigt werden sollen. Für diesen Fall gibt es zwei Möglichkeiten:

XQuery-Modul in der Datenbank
Die eigentliche Abfrage wird als XQuery-Modul in der Datenbank abgelegt. Dieses Modul wird sodann direkt aufgerufen, z.B.:

SET data = REQUEST("http://localhost:8080/exist/rest/db/apps/tonhalle/beispielmodul.xq")

Je nachdem, wie das Modul angelegt ist, können beim Aufruf desselben auch Parameter übergeben werden. Siehe die Dokumentation: „Calling Stored XQueries“.

Methode Post
Bei einem Aufruf mit der Methode POST werden die Parameter nicht über der URL, sondern in einem XML-Fragment übergeben. Zur Struktur und Syntax solcher Fragmente siehe die Dokumentation: „POST Requests“.


Interaktive Abfrage über ein Formular

Schließlich sei noch auf die Möglichkeit hingewiesen, die Datenbank-Abfrage in Tustep über ein Formular abzusetzen.
Das folgende Makro enthält als Beispiel ein solches Formular, das die benötigten Angaben entgegennimmt, die Abfrage entprechend dem Formularinhalt vornimmt, das Resultat in der spezifizierten Datei speichert und diese Datei schließlich öffnet:

#makro
$$-
$$- Rudimentäres Beispiel für ein Formular, das Informationen für eine Datenbankabfrage entgegennimmt und die Abfrage sodann ausführt.
$$- Das Makro funktioniert zwar grundsätzlich, wurde aber nicht umfassend getestet.
$$-
$$ MODE TUSCRIPT

---------------------
SECTION doit

-***Maskenwerte verarbeiten

-1] Verbindung
IF (connnumvar .EQ. "1") THEN
   SET path = "http://localhost:8080/exist/rest/db/apps/tonhalle/data"
ELSEIF (connnumvar .EQ. "2") THEN
   SET path = "http://localhost:8080/exist/rest/db/apps/projekt2/data"
ELSEIF (connnumvar .EQ. "3") THEN
   SET path = "http://localhost:8080/exist/rest/db/apps/projekt3/data"
ENDIF

-2] Abfrage
SET query = xqtxtvar

-3] Indent
IF (indnumvar .EQ. "2") THEN
SET ind = "&_indent=no"
ELSE
SET ind = ""
ENDIF

-4] Wrap
IF (wrapnumvar .EQ. "2") THEN
SET wrap = "&_wrap=no"
ELSE
SET wrap = ""
ENDIF

-*** Abfrage absetzen
SET data = REQUEST("{path}?_query={query}{ind}{wrap}&_howmany=1000")
SET data = DECODE(data, UTF8)
FILE/ERASE "{zdtxtvar}" = data

ENDSECTION
---------------------

WINDOW existwindow: [] 80 lstfeld, aktfeld, nxtfeld

DATA +---------------------------------------------------------------------------------------------------------------+
DATA |                                                                                                               |
DATA |                       DATENBANK ABFRAGEN                                                                      |
DATA |                       ******************                                                                      |
DATA |                                                                                                               |
DATA |                                                                                                               |
DATA |     Verbindung                                XQuery                                                          |
DATA |     ----------                                ------                                                          |
DATA |     [connection                     ]         [xquery                                                  ]      |
DATA |     [connection                     ]         [xquery                                                  ]      |
DATA |     [connection                     ]         [xquery                                                  ]      |
DATA |                                               [xquery                                                  ]      |
DATA |                                               [xquery                                                  ]      |
DATA |     Aufgabe                                   [xquery                                                  ]      |
DATA |     ----------------                          [xquery                                                  ]      |
DATA |     [stdaufgaben                    ]         [xquery                                                  ]      |
DATA |     [stdaufgaben                    ]         [xquery                                                  ]      |
DATA |     [stdaufgaben                    ]                                                                         |
DATA |     [stdaufgaben                    ]                                                                         |
DATA |     [stdaufgaben                    ]         Optionen:  Indent [ind ] ja*     Wrap [wrap] ja*                |
DATA |                                                                 [ind ] nein         [wrap] nein               |
DATA |                                                                                                               |
DATA |                                                                                                               |
DATA |                                                                                                               |
DATA |     Zieldatei                                                                                                 |
DATA |     ---------                                                                                                 |
DATA |     [zd              ]                        [   ok   ]     [   ca   ]    [   he   ]    [   re  ]            |
DATA |                                                                                                               |
DATA |                                                                                                               |
DATA |     [message                                                                                           ]      |
DATA |                                                                                                               |
DATA +---------------------------------------------------------------------------------------------------------------+

FIELD connection: SELECT 70 conntxtvar, connnumvar, connposvar
   LOOP
   EDIT
   IF (CANCEL) THEN
   EXIT
   ELSEIF (connnumvar .EQ. "0") THEN
   SET mess = "Es muss eine Verbindung ausgewählt werden"
   GOTO FIELD connection
   ELSE
   GOTO FIELD stdaufgaben
   ENDIF
   ENDLOOP

FIELD stdaufgaben: SELECT 70 stdtxtvar, stdnumvar, stdposvar
   LOOP
   EDIT
   IF (CANCEL) THEN
   EXIT
   ELSEIF (stdnumvar .EQ. "1") THEN
   SET mess = "Bitte XQuery-Abfrage eintragen"
   SET xqtxtvar = ""
   GOTO FIELD xquery
   ELSEIF (stdnumvar .EQ. "0") THEN
   SET mess = "Es muss eine Aufgabe ausgewählt werden"
   GOTO FIELD stdaufgaben
   ELSEIF (stdnumvar .EQ. "2") THEN
   SET  xqtxtvar = "for $n in //konzert order by $n/datum return $n"
   GOTO FIELD ind
   ELSEIF (stdnumvar .EQ. "3") THEN
   SET  xqtxtvar = "for $n in distinct-values(//komponist) order by $n return <komp>{$n}</komp>"
   GOTO FIELD ind
   ELSEIF (stdnumvar .EQ. "4") THEN
   SET  xqtxtvar = "for $n in distinct-values(//leitung) order by $n return $n"
   GOTO FIELD ind
   ELSEIF (stdnumvar .EQ. "5") THEN
   SET  xqtxtvar = "for $n in distinct-values(//werktitel) order by $n return <werk>{$n}</werk>"
   GOTO FIELD ind
   ELSE
   SET mess = ""
   GOTO FIELD ind
   ENDIF
   ENDLOOP

FIELD xquery: INPUT/EDIT 79 xqtxtvar, stdnumvar, stdposvar
   LOOP
   EDIT
   IF (CANCEL) THEN
   EXIT
   ELSEIF (stdnumvar .EQ. "1" .AND. xqtxtvar .EQ. "") THEN
   SET mess = "bei 'manuelle Eingabe': gewünschte XQuery-Abfrage in das Feld 'XQuery' eintragen"
   GOTO FIELD xquery
   SET stdnumvar = "1"
   ELSEIF (stdnumvar .EQ. "1" .AND. xqtxtvar .NE. "") THEN
   SET stdnumvar = "1"
   GOTO FIELD ind
   ELSEIF (stdnumvar .NE. "1" .AND. xqtxtvar .NE. "") THEN
   SET stdnumvar = "1"
   GOTO FIELD ind
   ELSE
   ENDIF
   ENDLOOP

FIELD ind: FLAGS/SINGLE 70 indnumvar
   EDIT
 
FIELD wrap: FLAGS/SINGLE 70 wrapnumvar
   EDIT

FIELD zd: INPUT 60 zdtxtvar, zdnumvar, zdposvar
   LOOP
   EDIT
   IF (CANCEL) THEN
   EXIT
   ELSE
      SET filestat = CHECK(zdtxtvar)
      IF (filestat .NE. "OK") THEN
      SET mess = "Zieldatei: kein Tustep-konformer Dateinamen"
      SET zdtxtvar = ""
      GOTO FIELD zd
      ELSE
      SET filestat = CHECK(zdtxtvar, WRITE)
         IF (filestat .NE. "OK") THEN
         SET mess = "Zieldatei existiert nicht bzw. besitzt keine Schreibrechte - Datei wird kreiert bzw. zum Schreiben angemeldet"
         SET filestat = CREATE(zdtxtvar, seq-o)
            IF (filestat .EQ. "OK") THEN
            SET mess = "Achtung: Inhalte der Zieldatei werden überschrieben"
            GOTO FIELD ok
            ELSE
            SET mess = "Zieldatei konnte nicht kreiert bzw. zum Schreiben angemeldet werden"
            ENDIF
         ELSE
         SET mess = "Achtung: Inhalte der Zieldatei werden überschrieben"
         GOTO FIELD ok
         ENDIF
        ENDIF
    ENDIF
    ENDLOOP

FIELD ok: BUTTON/ENTER C0 oktxtvar, oknumvar, okposvar
   EDIT
   IF (ENTER) THEN
   DO doit
   ENDIF

FIELD ca: BUTTON/CANCEL C0 catxtvar, canumvar, caposvar
   EDIT

FIELD he: BUTTON/HELP C0 hetxtvar, henumvar, heposvar
   EDIT
   IF (HELP) THEN
   SET mess = "Ach, das ist jetzt blöd: kein Hilfetext vorhanden..."
   GO TO FIELD {aktfeld}
   ENDIF

FIELD re: BUTTON C0 retxtvar, renumvar, reposvar
   EDIT
   IF (CR .OR. CLICK) THEN
   SET connnumvar  = "0"
   SET banumvar    = "0"
   SET xqtxtvar    = ""
   SET indnumvar   = "0"
   SET wrapnumvar  = "0"
   SET zdtxtvar    = ""
   GOTO FIELD connection
   ENDIF

FIELD message: OUTPUT 71 mess

ENDWINDOW

   SET  conntxtvar = *
   DATA Tonhalle
   DATA Projekt 2
   DATA Projekt 3

   SET  stdtxtvar = *
   DATA manuelle Eingabe
   DATA alle Konzerte, nach Datum
   DATA alle Komponisten, alphabetisch
   DATA alle Dirigenten, alphabetisch
   DATA Werke, alphabetisch

   SET  oktxtvar = *
   DATA OK
   SET  catxtvar = *
   DATA CANCEL
   SET  hetxtvar = *
   DATA HELP
   SET retxtvar = *
   DATA RESET

DISPLAY existwindow

EXECUTE #e,{zdtxtvar}
*eof

Hinweise zu den Formularfeldern:

- //Verbindung//: Auswahl der collection, auf die sich die Abfrage beziehen soll. Die Auswahl "Tonhalle" entspricht z.B. dem Pfad //http://localhost:8080/exist/rest/db/apps/tonhalle/data
- Aufgabe//: Hier wird entweder "manuelle Eingabe" oder eine vordefinierte Abfrage (z.B. "alle Dirigenten, alphabetisch") ausgewählt. Bei "manueller Eingabe" muss das gewünschte XQuery- oder XPath-Statement anschließend im Feld nächsten Feld "XQuery" eingetragen werden. Falls eine vordefinierte Abfrage ausgewählt wird, wird das Feld "XQuery" automatisch ausgefüllt (und kann nach Belieben ebd. noch verändert werden).//
- XQuery//: siehe oben Nr. 2.//
- Indent//: Auswahl, ob das Resultat mit pretty print formatiert werden soll oder nicht (Standard: ja).//
- Wrap//: Auswahl, ob das Resultat von einem <exist:result>-Element umschlossen werden soll (Standard: ja).//
- Zieldatei//: Datei, in die das Resultat der Abfrage gespeichert werden soll. Existiert die Datei noch nicht oder ist sie nicht zum Schreiben angemeldet, wird sie kreiert bzw. zum Schreiben angemeldet.


Zum Abschluss ein Screenshot des Makros in Aktion:

exist_formular.PNG


Zurück zur Übersichtsseite - TUSTEP und XML