----
[[tustep:loesungen:tustep_und_xml|Zurück zur Übersichtsseite - TUSTEP und XML]]
----
====== Abfrage einer XML-Datenbank mit TUSTEP ======
- {{files_open:benutzericons:chmoser-lg.jpg?nolink&16x16|chmoser}} chmoser\\ \\
===== Ausgangslage =====
Sie oder ihr Team arbeiten dezentral mit XML-Daten und speichern diese zentral in einer XML-Datenbank (z.B. [[http://exist-db.org/exist/apps/homepage/index.html|exist-db]] oder [[http://basex.org/|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 [[http://exist-db.org/exist/apps/homepage/index.html|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 [[tustep:loesungen:tustep_und_xml:xquery#Beispiel 1|hier]].\\ \\
===== WebDAV =====
Eine ganz simple Lösung für ihr Anliegen, mit Tustep auf die Datenbank zuzugreifen, besteht in der Nutzung der [[http://de.wikipedia.org/wiki/WebDAV|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 [[http://exist-db.org/exist/apps/doc/webdav.xml|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 [[http://de.wikipedia.org/wiki/Representational_State_Transfer|REST]]-Schnittstelle (//Representational State Transfer//) von exist-db realisiert werden. Die entsprechende Dokumentation findet sich [[http://exist-db.org/exist/apps/doc/devguide_rest.xml|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 -Element umschlossen werden sollen (Standardeinstellung = yes)\\ |
| ...\\ | Beschreibungen von weiteren Parametern finden sich [[http://exist-db.org/exist/apps/doc/devguide_rest.xml#D2.2.3.14|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 Bereich, in dem die Abfrage ausgeführt wird, ist die collection: //db/apps/tonhalle/data//
* es soll die Abfrage //konzert// ausgeführt werden (Bedeutung: liefere alle Elemente //konzert//)
* Resultat mit pretty print: ja
* Codierung des Resultats in UTF-8
* maximal 1000 Treffer
* Anzeige ab Treffer Nr. 1
* Umschließung der Resultate mit dem Element : ja
\\ 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:\\ \\ {{files_open:bilder:exist_result_browser.PNG|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:\\
* mit REQUEST wird die Abfrage ''http://localhost:8080/exist/rest/db/apps/tonhalle/data?_query=//konzert'' in die Variable //data// geladen.
* wie oben erwähnt, liefert exist-db das Resultat standardmäßig in UTF-8, deshalb wird mit DECODE eine Decodierung von UTF-8 nach Tustep vorgenommen.
* der Inhalt der Variable //data// wird in die Datei //ergebnis// geschrieben.
\\ Das Resultat in der Datei //ergebnis// sodann:\\ \\ {{files_open:bilder:exist_result_tustep.PNG|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 [[http://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers|hier]] und [[http://www.boutell.com/newfaq/misc/urllength.html|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: "[[http://exist-db.org/exist/apps/doc/devguide_rest.xml#D2.2.3.18|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: "[[http://exist-db.org/exist/apps/doc/devguide_rest.xml#D2.2.3.17|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 {$n}"
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 {$n}"
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 -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:\\ \\ {{files_open:bilder:exist_formular.PNG|exist_formular.PNG}}
----
[[tustep:loesungen:tustep_und_xml|Zurück zur Übersichtsseite - TUSTEP und XML]]