Seitenauswertung
Allgemeines
Der folgende Text beschreibt, wie man ein HTML-Pattern (früher Seiten- oder HTML-Template genannt) erzeugt, welches den Inhalt einer bestimmten Seite des Büchereikatalogs auswertet (nicht zu verwechseln mit einem VideLibri-Template, das beschreibt wie die URLs der Seiten lauten und welches HTML-Pattern, welcher Seite zugeordnet wird).Ein solches HTML-Pattern muss für jede auszuwertende Seite erstellt werden und ist eine HTML-Datei, deren Struktur (DOM-Baum sozusagen) mit der Struktur der Webseite übereinstimmt und in der wichtige Elemente markiert sind. Um so ein Pattern zu erstellen, kopiert man von der Katalogwebseite der Bibliothek die Stellen, welche die Ausleihen/Bücher betreffen, und fügt dann Annotationen ein, die VideLibri die Bedeutung der Stellen mitteilen.
VideLibri sucht/matches beim Ausführen einer Templateaktion die HTML-Elemente und Inhalte des Patterns in der neu heruntergeladene Seite. Dies funktioniert, so ähnlich wie ein regulärer Ausdruck, bei dem z.B.: foo[A-Z]*bar Großbuchstaben zwischen foo..bar findet, nur dass die Buchstaben durch HTML-Tags ersetzt sind.
Zum Beispiel kann man mit <td>...</td>
die erste Tabellenzelle auswählen, und mit <div id="xy">... </div>
ein bestimmtes div.
Eine mögliche Markierung/Annotation ist {$book.title}
, welche den aktuellen Knoten als Buchtitel speichert.
Entsprechend kann man <table class="example"><tr><td/> <td> {$book.title} </td> </tr> </table>
verwenden, um die zweite Spalte der ersten Zeile einer Tabelle der Klasse example als Buchtitel zu lesen.
Oder man verwendet <table class="example"><tr><td>Überschrift XYZ: </td> <td> {$book.title} </td> </tr> </table>
, um die Tabellenzelle zu lesen, die nach der Zelle mit dem Inhalt Überschrift XYZ:
kommt.
Eine andere Annotation ist *
, um Wiederholungen zu markieren, und man kann zum Beispiel <table class="example"><tr><td/> <td> {$book.title} </td> </tr> * </table>
verwenden, um alle zweiten Spalten aller Zeilen zu lesen. Oder <table class="example"><tr><td>Überschrift XYZ:</td> <td> {$book.title} </td> </tr> * </table>
, um alle Tabellenzellen mit der entsprechenden Überschrift zu lesen.
Befinden sich auf der Internetseite zusätzliche Elemente, werden diese ignoriert. Man muss daher ins Pattern keine unnötigen Sachen wie beispielsweise Texte, divs oder css-styles kopieren.
Fehlen dagegen auf der Internetseite Elemente, die sich im Pattern befinden, so wird das Patternmatching abgebrochen. Dadurch wird jedesmal überprüft, ob sich der Büchereikatalog geändert hat, und es ist garantiert, dass keine falschen Daten gelesen werden.
Alle existierenden VideLibri-Templates sind bei der Desktopversion im Verzeichnis data/libraries/templates finden, und für einzelne Seiten kann man seine Templates online oder mit Xidel testen. Wenn man den VideLibri-Quellcode hat, kann man dazu auch die Beispielsprogrammen unter components/pascal/data/examples verwenden.
Beispiel
Das Beispiel zeigt, wie sich die Tabelle einer idealen Bücherei auswerten lässt, die Id, Titel, Autor und Abgabedatum in jeweils einer Spalte anzeigt und für jedes Buch eine Zeile besitzt:<table id="books"> <tr> {vl:delete-current-books()} <th>Id</th><th>Titel</th><th>Autor</th><th>Abgabedatum</th> </tr> <tr> { $book := {} } <td>{$book.id}</td> <td>{$book.title}</td> <td>{$book.author}</td> <td>{$book.dueDate := parse-date(., 'dd.mm.yyyy')}</td> </tr> * </table>
Variablen
Die wichtigste Markierung/Annotation ist{$variable}
mit der man VideLibri im Pattern mitteilt, welche Bedeutung ein bestimmtes HTML-Element auf der Seite hat. Zum Beispiel <td>{$book.author}</td>
, für eine Tabellenzelle, die den Autor eines Buches enthält. Im Beispiel ist $book.author
eine so genannte Variable.
Mittels <td>{$book.author := ..}</td>
kann man auch komplexere Sachen in so eine Variable schreiben, was nützlich ist, falls nicht nur der Autor in dieser Tabellenzelle steht.
Man kann beliebige eigene Variablen definieren, die folgenden Variablen haben eine spezielle Bedeutung für VideLibri:
$book | Das aktuelle Buch. Der Beginn eines neuen Buches wird mit $book := {} markiert (siehe unten). | ||||||||||||||||||||||||||||||||
$book.author | Autor | ||||||||||||||||||||||||||||||||
$book.title | Titel | ||||||||||||||||||||||||||||||||
$book.id | Signatur | ||||||||||||||||||||||||||||||||
$book.isbn | ISBN | ||||||||||||||||||||||||||||||||
$book.year | Erscheinungsjahr | ||||||||||||||||||||||||||||||||
$book.status | Bemerkung zum Verlängerungsstatus | ||||||||||||||||||||||||||||||||
$book.statusId | Der Status des Buches.
| ||||||||||||||||||||||||||||||||
$book.dueDate | Fristdatum. Man schreibt $book.issueDate := parse-date(., 'd.m.yyyy') , um Daten in einem Format wie 1.10.2015 zu verarbeiten. | ||||||||||||||||||||||||||||||||
$username | Benutzer/Kartennummer | ||||||||||||||||||||||||||||||||
$password | Passwort oder Geburtsdatum |
$book.barcode | Mediennummer des Buches. |
$book.category | Kategorie des aktuellen Buches |
$book.issueDate | Ausleihdatum, entsprechend $book.dueDate |
$book.libraryBranch | Zweigstelle der Bibliothek, in der das Buch ausgeliehen wurde. |
$book.libraryLocation | Standort des Buches. |
$book.publisher | Verlag. |
$book.location | Ort (i.A. Sitz des Verlags). |
$book.renewCount | Anzahl der bisherigen Verlängerungen. |
$book.pendingOrders | Anzahl der bisherigen Vormerkungen (i.A. Vormerkungen durch andere Leute). |
$book.cancelable | Wenn das Buch vorgemerkt ist, ob man die Vormerkung abbrechen kann. |
$book.orderable | Wenn das Buch nicht ausgeliehen ist, ob man es bestellen/vormerken kann. |
$book.orderTitle | Die Art der Vorbestellung, zum Beispiel "bestellen" oder "vormerken". |
$book.holdings | Liste von (ausleihbaren) Exemplaren. Jeder Wert in holdings kann alle Werte eines Buches annehmen, zum Beispiel $book.holdings := {"title": "Ein Untertitel"} . |
$book.irgendwas | Für irgendwas kann irgendetwas beliebiges stehen, der Wert wird bis zum Programmende für das Buch gespeichert. Zukünftige Version könnten ihn dauerhaft speichern |
$book.irgendwas! | Der Wert nicht nur temporär gespeichert, sondern auch angezeigt. |
Wenn ein neues Buch gelesen wird, muss ein entsprechend neues Buchobjekt mittels
$book := {}
erstellt werden.In einem Pattern, das aufgerufen, um ein bereits gelesenes Buch zu verlängern oder mehr Details über es zu ermitteln, ist bereits ein Buchobjekt
$book
vordefiniert, so dass kein Buchobjekt erstellt werden muss.Es ist auch möglich, ein bestimmtes Buch an Hand seiner ID auszuwählen, indem der pseudo-Eigenschaft select(id) mittels
book := {"select(id)": "12345"}
die gewünschte ID bei der Buchobjekterstellung zugewiesen wird.Nachdem das Objekt erstellt ist, können die Eigenschaften mit
{$book.author := "foobar", $book.title := "wie"}
geändert werden. Oder mittels $book.author
ausgelesen werden. (beim Setzen einer Eigenschaft durch :=, ist das $-Prefix optional)
Funktionen
Es ist auch möglich Annotation zu erstellen, die nicht die Bedeutung eines Elements angeben, sondern VideLibri auffordern, irgendetwas Bestimmtes zu tun. Zum Beispiel <html>{vl:delete-current-books()}</html>
, um alle aktuellen Ausleihen zu löschen. Dies nennt man einen Funktionsaufruf.
VideLibri definiert die folgenden Funktionen:
vl:delete-current-books() | Löscht die Liste der aktuellen Ausleihen. Sollte aufgerufen werden, bevor irgendwelche neuen Bücher definiert werden. |
vl:raise($message) | Erzeugt eine Fehlermeldung mit Text $message |
vl:raise-login($message) | Erzeugt eine "ungültiger Benutzername/Passwortfehlermeldung" mit Text $message |
vl:confirm($callback, $caption) | Zeigt einen Dialog mit Ja/Nein-Buttons an. Nachdem der Benutzer einen Button drückt, wird die Aktion $callback im Haupttemplate aufgerufen. Eine zusätzliche Variable $confirm-result ist true()/false(), je nachdem welcher Button gedrückt wurde. |
vl:choose($callback, $caption, $optionCaptions, $optionValues) | Zeigt einen Dialog mit einer Auswahlliste an, die alle Werte in der $optionCaptions Sequenzvariable anzeigt. Nachdem der Benutzer etwas ausgewählt hat, wird die Aktion $callback im Haupttemplate aufgerufen und der ausgewählte Wert als String in $choose-result gespeichert. Falls es zur Auswahl keinen Wert in $optionValues gibt, weil $optionValues weniger Werte als $optionCaptions enthält, erhält $choose-result den 1-basierten Index der Auswahl. Falls der Dialog abgebrochen wurde, ist $choose-result 0. Um zu testen, ob $choose-result eine Zahl ist, sollte $choose-result instance of xs:decimal verwendet werden. (Auf Grund des Typsystems kann = nicht verwenden, um Zahlen und String zu vergleichen. Eine Alternative ist der eq Operator) |
vl:select-book($queryBook) | Sucht ein Buch in den aktuellen Ausleihen. Es wird das Buch zurückgegeben, dass die gleichen Eigenschaften wie das $queryBook hat. |
Die Existenz von zuweisbaren Variablen und Funktionen dürfte bei Programmiererfahrenen den Eindruck erwecken, bei den beschriebenen Ausdrücken handele es sich um eine imperative Programmiersprache. Dem ist nicht so. Insbesondere haben die aufgerufenen Funktionen keinen unmittelbaren Effekt. Zum Beispiel kann man nach dem Aufruf von vl:delete-current-books()
weiterhin mit vl:select-book
ein Buch aus den Ausleihen auswählen, obwohl vl:delete-current-books()
alle Ausleihen gelöscht hat. Und obwohl vl:confirm()
einen Dialog anzeigt, werden alle darauffolgenden Ausdrücke ausgewertet, bevor der Dialog angezeigt wird.
Intern speichert VideLibri alle Variablenänderungen und Funktionsaufrufe in einem Changelog. Erst nachdem das Patternmatching vom Seitentemplate mit der HTML-Seite abgeschlossen ist, wird das Changelog abgearbeitet und die Berechnungen haben tatsächlichen eine Auswirkung. Dabei können alle Variablenänderungen/Funktionsaufrufe rückgängig gemacht werden, wenn sich herausstellt, dass die Strukturen nicht übereinstimmen.
Annotationen
Außer den Annotationen mit geschweiften Klammern gibt es noch, die folgenden wichtige Annotationen:<template:s>$var:= ..source.. </template:s> | Eine längere Form von {$var := ..source..} : Wertet den Ausdruck ..source.. aus und speichert das Ergebnis in der Variablen $var .Beispiel: <template:s>$foobar := 1+2</template:s> , um 3 in $foobar zu speichern. |
<template:read var=".." source=".."/> | Eine noch längere Form. Wertet den Ausdruck im Attribut source aus und speichert das Ergebnis in der Variablen $var .Beispiel: <template:read var="foobar" source="1+2"/> speichert 3 in variable $foobar . |
<template:loop> ... <template:/loop> | Wiederholt den Inhalt zwischen den geschlossenen Tags, solange wie möglich. (einschließlich niemals) Beispiel: <template:loop><tr>{$x := .}</tr></template:loop> , speichert alle Zeilen einer Tabelle, nacheinander in Variable $x. (überschreibt den alten Wert von x, aber VideLibri kann von außerhalb des Templates auf alle Werte von x zugreifen) |
<template:if test="condition"> ... <template:/if> | Ignoriert alle Elemente innerhalb des if-Tags, falls der Ausdruck in test nicht zu true ausgewertet wirdBeispiel: <template:fi test="contains(text(), 'verlängerbar')">{verlängerbar := 1} </template:if> , speichert 1 in der Variable $verlängerbar, falls der Text des aktuellen Elements "verlängerbar" enthält. |
template:optional="true" | Attribut, das ein Element als optional markiert. Z.B.: <div template:optional = "true"><h1>{$t := .}</h1></div> , speichert die erste Überschrift des ersten Div in Variable $t, falls ein Div, welches eine Überschrift enthält, auf der Seite existiert, und wird ignoriert, wenn es nicht existiert. |
? | Kurzform für template:optional. Z.B.: <div><h1>{$t := .}</h1></div>? , entspricht dem vorherigen Beispiel. |
* | Kurzform für <template:loop> . Z.B.: <div><h1>..</h1></div>* , wiederholt .. für alle divs, die eine Überschrift enthalten. |
+ | Kurzform für <template:loop min="1"> . Z.B.: <div><h1>..</h1></div>+ , wiederholt .. für alle divs, die eine Überschrift enthalten. Wenn keines existiert, wird eine Fehlermeldung ausgegeben. |
<template:meta encoding="windows-1252|utf-8"/> | Gibt an, ob die Datei in utf-8 oder Windows-1252 codiert ist. (optional) |
Der Namespaceprefix
template:
kann grundsätzlich mit t:
abgekürzt werden. (z.B.: t:s
statt template:s
)
Weitere Befehle und Details sind auf meiner Seite beschrieben.
erweiterte XPath/XQuery Ausdrücke
VideLibri implementiert einen XPath/XQuery 3 Interpreter mit einigen Erweiterungen.Einige der unterstützten Ausdrücke sind:
'abc...' oder "abc..." | Der String abc... |
$variable := value | Weist value der Variable $variable zu. Beispiel: $foobar := 1+2+3 speichert 6 in $foobar |
$var | Der Wert der Variable var. |
concat(s1,s2,s3,...) oder s1||s2||s3||... | Die Konkenation aller angegebenen Strings |
text() | Der Text des aktuellen Knotens als String. Beispiel: <td>{$foobar := text()}</td> liest den Text einer Tabellenzelle. Und <br/>{x:=$text()} liest den Text einer zweiten Zeile, hinter einem br-Element. |
@attrib | Der Wert des Attributes attrib als String Beispiel: <a>{$foobar := @href}</a> liest die Adresse eines Links. |
x"foo{$var}bar" | Ein erweiterter String, in dem XPath-Ausdrücke innerhalb von {} ausgewertet werden. Das Beispiel ist äquivalent zu concat("foo", $var, "bar") . |
s1 == s2 | true, falls die Strings s1 und s2 gleich sind. (ohne Berücksichtigung der Groß/Kleinschreibung) Beispiel: "hallo" == "Hallo" ist true(). |
s1 != s2 | true, falls die Strings s1 und s2 ungleich sind. (ohne Berücksichtigung der Groß/Kleinschreibung) |
extract(str, regex[,match]) | Sucht den regulären Ausdruck regex in str und gibt die gefundene Übereinstimmung zurück. Ist match angegeben, wird nur der match-te Teilausdruck zurückgeben. Beispiel: extract("Hallo Welt: 123", ".*: ([0-9]+)", 1) ergibt 123 |
css("..") | Wertet einen CSS 3 Selektor aus Beispiel: css("div.foobar")/text() gibt den Text aller divs mit Klasse foobar zurück. |
parse-date(date, format) | Wandelt einen String in ein Datum um. Beispiel: parse-date("2012-07-05", "YYYY-MM-DD") gibt das Datum 2012-07-05 in einem echten Datumstyp zurück. |
. oder deep-text() | Der gesamte Text des aktuellen Knotens. Beispiel: deep-text() == string-join(.//text(), "") ist immer true(). (von .//text() würde VideLibri dagegen nur den ersten Text anzeigen) |
{"name1": value1, "name2": value2} | Erzeugt ein Objekt mit den angegebenen Eigenschaften. Beispiel: abc := {"hallo": "welt", "foo": "bar"} speichert ein Objekt in Variable $abc , dessen Eigenschaft $abc.hallo den Wert "welt" hat, und dessen Eigenschaft $abc.foo den Wert "bar" hat. |
form(form, [parameter]) | Wandelt ein html <form> -Element in ein Objekt um, welches dem entsprechen GET/Post-Request entspricht. Falls parameter gegeben sind, werden sie zum Request hinzugefügt.Beispiel: form(//form[1], "foo=bar&hallo=789") mit einer HTML-Seite, die <form action="myurl" method="POST"><input name="abc" value="def"/><input name="foo" value="123"/></form> enthält, ergibt folgendes Objekt: {"url": "myurl", "method": "POST", "post": "abc=def&foo=bar&hallo=789"} . |
Weitere Befehle und Details sind auf meiner Seite beschrieben.