Zurück zur Übersichtsseite - TUSTEP und XML
- chmoser
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.
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.
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¶meter=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:
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:
http://localhost:8080/exist/rest/db/apps/tonhalle/data?_query=//konzert
in die Variable //data// geladen.
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“.
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: