Im Rahmen der Veranstaltung "XML-Technologien" am Institut für Informatik der Freien Universität war ein Projekt anzufertigen, in dem durch die Anwendung verschiedener Technologien der Vorlesungsstoff gefestigt werden sollte. Für die Umsetzung des Projektes wurde etwa ein Monat Zeit gewährt. Das Projekt sollte sich am Wettbewerb Coding Da Vinci orientieren, allerdings war eine offizielle Teilnahme nicht Pflicht.
Unsere Gruppe hat sich auf die Projektidee "Kunstquiz" geeinigt. In diesem Projekt entstand eine Webanwendung, in dem der Nutzer 10, 15 oder 20 Fragen auswählen kann und diese dann lösen muss. Inhalt der Fragen sind Kunstwerke, die ursprünglich ebenfalls eingeplanten Bücher und Gebäude konnten aufgrund der kurzen Zeit leider nicht mehr integriert werden. Der Benutzer soll aus vier Antwortmöglichkeiten den richtigen Künstler, der das Werk gemalt hat, oder das Jahr, in dem es gemalt wurde, bestimmen. Die Daten, die wir für das Projekt benötigten, stammen von der Deutsche Digitale Bibliothek und der DBpedia.
Nachfolgend werden die eingesetzten Technologien genauer vorgestellt
Zunächst wurden von der Deutschen Digitalen Bibliothek geeignete Personen extrahiert.
Um diese Personen mit Daten anzureichern, zu denen man Fragen stellen kann, wurde als LOD-Endpoint die DBpedia ausgewält.
Die Herausforderung hierbei war es, die Relation zwischen DDB{Person}->DBpedia{Person->Kunstwerke}
herzustellen.
Hier bot es sich an, alle Daten in einer Ausführung auszulesen, sodass die dafür eingesetzten PHP-Skripte erst die Personen aus der DDB extrahieren und anhand dieser Personen die Relationen in der DBPedia ermittelten.
Da die Personen aus der DDB nur als JSON verfügbar waren, wurden auch die Daten aus der DBPedia als JSON ausgelesen.
Um die Daten in XML zu transformieren, wurde der XML_Serializer
von Pear verwendet.
Genaue Abfolge der Datensammlung:
-
Personen aus der DDB anhand von ausgewählten Berufen extrahieren. Um die DDB verwenden zu können benötig man einen API-Key, den man im HTTP-Header angeben kann.
Query:
api.deutsche-digitale-bibliothek.de/entities?rows=10000&query=professionOrOccupation:[Beruf(e)]
-
Die wichtigsten Daten des Personen-JSON-Objekts in einem Personen-PHP-Objekt speichern.
-
Finden der Person in der DBPedia.
Query:
SELECT ?person,?abstract,?wikilink WHERE { ?person a dbpedia-owl:Person. ?person dbpedia-owl:abstract ?abstract. ?person foaf:name ?name. ?person prov:wasDerivedFrom ?wikilink. FILTER (LANG(?abstract)="de" &&(?name = "'.$person.'"@en || ?name = "'.$person.'"@de)) }
-
Zusätzliche Metadaten im entsprechenden Personen-PHP-Objekt speichern.
-
Anhand des Unique-Identifiers einer Person die zugehörigen Kunstwerke ermitteln.
Wichtigste Teile der Artwork-Query:
SELECT ?artwork,?thumbnail,?name,?year,?abstract,?wikilink,?type WHERE { ?artwork a dbpedia-owl:Artwork. /* hier gibt es im Original noch mehr Attribute */ ?artwork dbpedia-owl:author ?author. ?author prov:wasDerivedFrom ?id. /* im Original beginnen hier optionale Attribute */ FILTER (?id=<'.$personURI.'>) } LIMIT 1000
Es gibt noch Queries für Gebäude und Bücher
-
Extrahierte Daten der Kunstwerke im entsprechenden Personen-PHP-Objekt speichern.
-
Das Personen-PHP-Objekt in XML transformieren.
Code:
function obj_to_xml($obj) { $serializer = new XML_Serializer(); if ($serializer->serialize($obj)) { return $serializer->getSerializedData(); } else { return null; } }
-
XML-Datei speichern.
Zu jeder Person gab es am Ende eine eigene XML-Datei. Die weitere Aufarbeitung der Daten wurde dann mit XSLT durchgeführt.
Das XML-Schema diente dazu, den Aufbau der Datenbank auf eine einheitliche Form festzulegen. Die hierfür vorhandenen Daten lassen sich in zwei Gruppen zusammenfassen. Zum einen in Künstler, Architekten und Autoren, zum anderen in Kunstwerke, Bücher und Gebäude sowie deren zugehörige Details. Der Zusammenhang zwischen Künstlern und ihren Werken stellt eine 1:N-Beziehung dar. Daher werden die beiden Gruppen für einen effizienteren Zugriff in einzelne Dateien aufgeteilt und über generierte IDs eindeutig zugeordnet. Desweiteren wurden Datensätze wie z.B. der dbp-Resource-Link einer Person, die in den Fragen keine Verwendung fanden, nicht übernommen.
Mittels XSLT wurden die Rohdaten transformiert, so dass sie dem erstellen XML-Schema entsprachen.
Als Ausgangsdaten lag zu jedem Künstler je eine einzelne XML-Datei vor, die auch deren Kunstwerke enthielt. Diese Datein wurden in einem Vorverarbeitungsschritt zu einer großen XML-Datei zusammengefasst. Nun wurde den Künstlern per XSLT eine eindeutige ID zugewiesen. Danach wurde für jede Tabelle (Personen, Bilder, Bücher, Architektur) eine XSLT-Transformation erstellt, die die Daten entsprechend der Schemata selektierte und die Kunstwerke über die Personen-ID mit dem entsprechenden Künstler verknüpfte.
Somit entstanden vier XML-Dokumente, welche die XML-Datenbank darstellen.
Beispiel buildings.xslt:
<?xml version="1.0"?>
<xsl:stylesheet
version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<buildings>
<xsl:apply-templates select="/persons/person/buildings/building"/>
</buildings>
</xsl:template>
<xsl:template match="building">
<building>
<xsl:copy-of select="../../personID"/>
<xsl:copy-of select="*"/>
</building>
</xsl:template>
</xsl:stylesheet>
Die Rest-API, welche in PHP entwickelt wurde, liefert über das URL-Muster http://URL/api/entity/action/args/...
die entsprechenden Daten im JSON-Format.
Dafür wird intern der HTTP Methodentyp (z.B. GET
) ausgewertet und versucht, auf dem Objekt Entity
die Funktion GET
mit den Parameter action
und weiteren optionalen Parametern args
aufzurufen.
In diesen Funktionen werden dann die Anfragen an die Datenbank mit XQuerys ausgeführt, welche ein XML-Ergebnis liefern, das dann in ein PHP-Objekt transformiert und in für die Rückgabe in JSON kodiert wird.
class Artwork extends Base {
public static $Database = '/var/www/backend/db/artworks_database.xml';
public function GET($verb, $args) {
if ($verb == 'year') {
$queryStr = "
import module namespace r = \"http://www.zorba-xquery.com/modules/random\";
for \$artworks in doc('".Artwork::$Database."')/artworks,
\$persons in doc('".Person::$Database."')/persons
let \$artwork := \$artworks/artwork[matches(year/text(), '^[0-9][0-9][0-9][0-9]\$')]
let \$rows := count(\$artwork)
let \$rand0 := r:random-between(1, \$rows)
let \$rand1 := r:random-between(1, \$rows)
let \$rand2 := r:random-between(1, \$rows)
let \$rand3 := r:random-between(1, \$rows)
let \$id := \$artwork[\$rand0]/personID/@ID
let \$painter := \$persons/person[personID[@ID=\$id]]/name/text()
return
<q>
<question>Aus welchem Jahr stammt dieses Bild?</question>
<hint>Das Bild ist von {\$painter}.</hint>
<image>{\$artwork[\$rand0]/thumbnail/text()}</image>
<answers>
<rightAnswer>{\$artwork[\$rand0]/year/text()}</rightAnswer>
<wrongAnswer1>{\$artwork[\$rand1]/year/text()}</wrongAnswer1>
<wrongAnswer2>{\$artwork[\$rand2]/year/text()}</wrongAnswer2>
<wrongAnswer3>{\$artwork[\$rand3]/year/text()}</wrongAnswer3>
</answers>
<wikilink>{\$artwork[\$rand0]/abstract/text()}</wikilink>
</q>
";
$query = $this->_zorba->compileQuery($queryStr);
$result = $query->execute();
$query->destroy();
return $this->jsonencode($result);
}
// ...
}
// ...
}
In obigen Beispiel wird eine Frage generiert, die eine richtige Antwort, drei falsche Antworten, den Künstler und den Wikilink enthält.
Über vier Zufallszahlen wurden dabei sowohl das abzufragende Kunstwerk als auch die drei falschen Antworten zufällig ausgewählt.
So können dann im Frontend zufällige Fragen nach dem Muster http://URL/question/random
abgerufen werden.
Intern wird dafür zufällig entschieden, welcher Fragetyp ausgewählt wird und die Anfrage beispielsweise an /antwort/year
weitergeleitet (s.o.).
Für die Entwicklung des Webinterfaces wurden PHP, HTML5, Javascript und CSS3 verwendet. Dabei wurde "Bootstrap 3" als CSS-Framework benutzt und das Webinterface dementsprechend responsiv gestaltet. Beim Aufruf des Webinterfaces gelangt der Benutzer auf eine Startseite, in der er aus 10, 15 oder 20 Fragen auswählen kann. Die Fragen werden dann nacheinander angezeigt und dabei wird eine Statistik über richtige und falsche Antworten geführt und unten rechts angezeigt. Die Fragen werden über die REST-API einzeln vom Server abgerufen und enthalten jeweils immer folgende Felder:
- die Frage nach dem Titel des Werkes oder der Jahreszahl, aus der es stammt
- ein Hinweis über das Werk
- die URL des Bildes
- die richtige Antwort mit drei weiteren ähnlichen, falschen Antwortmöglichkeiten
- und der Wikipedia-Link zum Autor des Werkes
Der Wikipedia-Link zum Autor wurde in Microformats in das obere rechte Icon aus drei Balken eingebettet. Die richtige Antwort bleibt beim Server gespeichert und wird beim Client(Browser) nicht angezeigt. Die vier Antwortmöglichkeiten werden zufällig sortiert und in einem Formular angezeigt. Die Überprüfung der gewählten Antwort nach der Richtigkeit erfolgt dann über ein AJAX-Aufruf durch jQuery, sodass das Ergebnis sofort angezeigt wird und die Statistik-Anzeige sofort aktulisiert wird. Am Ende des Quiz wird wieder die Statistik über richtige und falsche Antworten angezeigt. Der Benutzer kann hier ein neues Spiel starten und die Statistikdaten werden dann zurückgesetzt.
Die Installation wird durch den Einsatz eines Docker-Containers stark vereinfacht. Um diese Webanwendung auszuführen, wird ein Webserver (hier Apache), PHP und Zorba benötigt. Vor allem die Installation von Zorba ist sehr fehleranfällig, da das Zorba-Paket wegen eines fehlerhaften Versionsvergleiches während der Installation auf einigen Distributionen nicht verwendet werden kann, und da der enthaltene PHP-API-Wrapper PHP-Funktionen enthält, welche von Apache nicht mehr unterstützt werden. Diese Besonderheiten und weitere Konfigurationen werden durch den Docker-Container abgenommen.
Um den Docker-Container zu erstellen und die Webanwendung automatisch in den Container einzubinden, werden Docker und Docker-Compose benötigt.
Wurden diese installiert, muss der Benutzer, der den Container starten möchte, außerdem der docker
-Gruppe hinzugefügt werden.
Außerdem muss mit diesem Repository das docker-apache-php-zorba
Submodul ausgecheckt werden.
Allgemein kann zum auschecken aller Submodule folgender Befehl verwendet werden.
git submodule update --init --recursive
Nun kann aus dem docker
-Verzeichnis heraus der Container gestartet werden.
Dafür müssen nur die beiden nachfolgenden Befehle ausgeführt werden.
Bitte beachten Sie, dass das initiale Aufsetzen eines Containers viel Zeit in Anspruch nehmen kann.
docker-compose build
docker-compose up -d
Standardmäßig wird der Apache-Webserver aus dem Container an den Port 8081
gebunden.
Dies kann über die Änderung des Ports in der Konfigurationsdatei docker-compose.yml
angepassst werden.
Sollten Sie Debian Jessie verwenden, führen sie den gesamten Installationsprozess ganz einfach durch folgenden Aufruf aus.
Dafür muss der das Skript jedoch von einem Benutzer aus der sudoer
Gruppe oder dem Superuser root
aufgerufen werden.
./configure