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:
$bookDas aktuelle Buch. Der Beginn eines neuen Buches wird mit $book := {} markiert (siehe unten).
$book.authorAutor
$book.titleTitel
$book.idSignatur
$book.isbnISBN
$book.yearErscheinungsjahr
$book.statusBemerkung zum Verlängerungsstatus
$book.statusIdDer Status des Buches.
Für Bücher, die ausgeliehen sind oder bestellt wurden:
"normal"Das Buch kann verlängert werden
"curious"Das Buch kann verlängert werden und es gibt eine Bemerkung in $book.status
"critical"Das Buch kann nicht verlängert werden.
"ordered"Das Buch ist bestellt.
"reserved"Das Buch ist vorgemerkt.
"provided"Das Buch ist abholbereit.
 
Für Bücher, in den Suchergebnissen:
"available"Das Buch ist verfügbar/ausleihbar.
"lend"Das Buch ist von jemand anderem ausgeliehen.
"virtual"Das Buch ist rein virtuell, zum Beispiel eine elektronische Ressource.
"presentation"Das Buch gehört zum Präsenzbestand.
"interloan"Das Buch ist in einer anderen Bibliothek und könnte fernbestellt werden.
 
"unknown"Unbekannter Status
$book.dueDateFristdatum. Man schreibt $book.issueDate := parse-date(., 'd.m.yyyy'), um Daten in einem Format wie 1.10.2015 zu verarbeiten.
$usernameBenutzer/Kartennummer
$passwordPasswort oder Geburtsdatum
Zudem gibt es die folgenden seltener verwendeten Bucheigenschaften:
$book.barcodeMediennummer des Buches.
$book.categoryKategorie des aktuellen Buches
$book.issueDateAusleihdatum, entsprechend $book.dueDate
$book.libraryBranchZweigstelle der Bibliothek, in der das Buch ausgeliehen wurde.
$book.libraryLocationStandort des Buches.
$book.publisherVerlag.
$book.locationOrt (i.A. Sitz des Verlags).
$book.renewCountAnzahl der bisherigen Verlängerungen.
$book.pendingOrdersAnzahl der bisherigen Vormerkungen (i.A. Vormerkungen durch andere Leute).
$book.cancelableWenn das Buch vorgemerkt ist, ob man die Vormerkung abbrechen kann.
$book.orderableWenn das Buch nicht ausgeliehen ist, ob man es bestellen/vormerken kann.
$book.orderTitleDie Art der Vorbestellung, zum Beispiel "bestellen" oder "vormerken".
$book.holdingsListe von (ausleihbaren) Exemplaren. Jeder Wert in holdings kann alle Werte eines Buches annehmen, zum Beispiel $book.holdings := {"title": "Ein Untertitel"}.
$book.irgendwasFü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.
Es ist nicht nötig, jeder Variable etwas zuzuweisen, aber es können nur die gesetzten Werte später angezeigt werden.

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.
Zudem können die XPath/XQuery-Funktionen verwendet werden.

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 wird
Beispiel: <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 := valueWeist value der Variable $variable zu.
Beispiel: $foobar := 1+2+3 speichert 6 in $foobar
$varDer 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.
@attribDer 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 == s2true, falls die Strings s1 und s2 gleich sind. (ohne Berücksichtigung der Groß/Kleinschreibung)
Beispiel: "hallo" == "Hallo" ist true().
s1 != s2true, 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.