4.7 Die Programmbibliothek für Dokumentenmethoden

Dieser Abschnitt beschäftigt sich mit dem Entwurf der Programmbibliothek für Dokumentenmethoden, die speziell zur Bearbeitung von SGML-Dokumenten ausgelegt ist. Zunächst wird ein Konzept zur Organisation der bereitgestellten Funktionalitäten in Paketen entwickelt. Es schließt sich die Aufteilung der Funktionalitäten auf die Pakete und die darin enthaltenen Klassen an. Schließlich werden die Methoden der Klassen hergeleitet und an Beispielen erläutert. In Kapitel 5 werden die Pakete realisiert, eine vollständige Liste der Methoden findet sich in Anhang C.

Die Programmbibliothek für Dokumentenmethoden bildet das Herz des Speichers. Hier wird den Dokumentenmethoden die gesamte Funktionalität des Speichers zur Verfügung gestellt. Die Programmbibliothek verbirgt alle Details der eingesetzten Basisprodukte. Außerdem stellt sie auch Methoden zur Verfügung, die dazu dienen den Speicher zu warten und zu konfigurieren, so daß zur Pflege kein spezielles Werkzeug erforderlich ist. Es werden einfach die entsprechenden Methoden aktiviert.

Die Entwicklung der Methoden in den von den Paketen bereitgestellten Klassen erfolgte ausgehend von der in Kapitel 4.6 beschriebenen Vorgehensweise beim Speichern von Dokumenten, die ihrerseits von den Anforderungen an den Speicher abgeleitet wurde.

Die Methoden sind hierarchisch organisiert. Die Methoden sind zunächst in Klassen zusammengefaßt und die Klassen ihrerseits in Pakete (packages). So kann man sich z.B. ein Paket mit Namen SGML vorstellen, das die Klassen sgml_store und sgml_search bereitstellt, die erste beinhaltet Operationen zum Speichern, die zweite Operationen zum Durchsuchen eines Dokuments. Die Klasse sgml_search könnte ihrerseits Operationen, wie z.B. search_tag zum Auffinden eines bestimmten Tags oder search_text zum Auffinden eines bestimmten Textstrings in einem gespeicherten SGML-Dokument, (s. Kap. 2.1) beinhalten. Abbildung 4.3 soll das Paket-Klassen-Konzept verdeutlichen.


PIC
Abbildung 4.3: Das Paket-Klassen-Konzept

Durch das Gruppieren der Klassen (packaging) und damit der beinhalteten Methoden ist es einfach, die Programmbibliothek um neue Funktionalitäten zu erweitern und an gesamtsystemspezifische Gegebenheiten anzupassen. Diese Klassifizierung der zur Verfügung stehenden Operationen ermöglicht weiterhin einen übersichtlichen und organisierten Zugriff auf die einzelnen Operationen und vermeidet weitgehend Namenskonflikte der Methoden. Weiterhin ist es möglich, das gleiche Paket in unterschiedlichen Entwicklungsstufen (Versionen bzw. Releases) bereitzustellen und somit Abwärtskompatibilität zu gewährleisten.

So kann man sich folgende einfache Klassifizierung der Methoden vorstellen:

  1. Systemmethoden; in diesem Paket findet man alle Klassen und Methoden, die der Speicher benötigt, um seine internen Daten zu verwalten. Dieses Paket wird nicht exportiert, d.h. die darin befindlichen Funktionalitäten stehen nur Administratoren und der Ablaufumgebung zur Verfügung.
  2. Maintenancemethoden; in diesem Paket findet man alle Klassen und Methoden, die zur Wartung benötigt werden (z.B. Reorganisation der Datenbank oder Reindizierung der Volltextindizes). Dieses Paket darf nur von autorisierten Instanzen benutzt werden, die darin befindlichen Klassen bzw. deren Methoden stehen nur Administratoren des Speichersystems zur Verfügung.
  3. Bitstrommethoden; in diesem Paket findet man alle Klassen und Methoden, die zur Verfügung gestellt werden, um Bitstromdaten zu verarbeiten bzw. zu durchsuchen.
  4. SGML-Methoden; in diesem Paket findet man alle Klassen und Methoden, die zur Verarbeitung bzw. zum Durchsuchen von SGML-Dokumenten zur Verfügung stehen.

Natürlich kann es Klassen geben, die mehreren Paketen zugeordnet sind. Beispielsweise könnte eine Methode, die den Dokumententyp eines SGML-Dokuments ausliest, im Paket-System und im Paket-SGML zu finden sein. Eine Dokumentenmethode, die zur Bearbeitung mehrerer Dokumenttypen dient, würde eine solche Methode benötigen, wie auch der Speicher, der grundsätzlich ermittelt um welchen Dokumenttyp es sich bei einem hinterlegten Dokument handelt, damit die Document Type Definition (DTD) des Dokuments immer nur einmal vorgehalten werden muß.

4.7.1 Definition der Basispakete des Speichers

Im letzten Abschnitt wurde eine einfache Klassifizierung der Klassen in vier Pakete vorgestellt. Die Klassen des System-Pakets und des Maintenance-Pakets sind implementierungsabhängig. Die Funktionalität des Maintenance-Pakets hängt immer von den verwendeten Basisprodukten (DBMS, Indizierer) ab, die Systemklassen von der Implementierung der Ablaufumgebungen.

Dem Bitstrom-Paket und dem SGML-Paket hingegen kann eine detailiertere Funktionalität zugeordnet werden, da die Vorgehensweise beim Speichern und damit auch die Bedingungen für den Zugriff auf die Dokumente bekannt sind. Im folgenden werden die Klassen dieser beiden Pakete näher spezifiziert.

Die Pakete Bitstrom und SGML sollen Klassen bereitstellen, die das Speichern und Durchsuchen von strukturiert oder als Bitstrom vorliegenden Dokumenten ermöglichen. Es werden prinzipiell vier Klassen benötigt:

Die Definition der Methoden dieser Klassen, die ihrerseits die bereits erwähnten atomaren Operationen realisieren, folgt in den nächsten Abschnitten.

Die Trennung der Funktionalität in Pakete und Klassen erfolgt auf dieser Ebene, um die Operationen einzeln zu entwickeln. Aus der Sicht einer Instanz, die Aufträge an den Speicher übermittelt, bilden sie eine einheitliche Schnittstelle zum Zugriff auf den Speicher.

4.7.2 Die Speicherklassen

Eine besondere Rolle kommt dem Vorgang des Speicherns zu. Bei allen anderen Vorgängen (Durchsuchen, Präsentieren, etc.) wird auf einem bereits gespeicherten Dokument operiert. Soll ein Dokument gespeichert werden, dann wird es während des Speichervorgangs übertragen und temporär zwischengespeichert. Um das Dokument zu speichern, müssen die Informationen Stück für Stück (bzw. Byte für Byte) aus dem temporären Dokument gelesen und dann im Speicher abgelegt werden. Die Vorgehensweise beim Speichern ist in Abbildung 4.4 dargestellt.
PIC
Abbildung 4.4: Der Speichervorgang

Das Speichern von Bitstromdaten ist eine verhältnismäßig einfache Aufgabe. Eine entsprechende Methode liest eine Anzahl von Bytes (oder Bits) aus dem temporären Zwischenspeicher, gegebenenfalls operiert sie auf den Daten (z.B. Dekomprimierung) und schreibt sie dann in den Bitstromspeicher. Ein einfacher Algorithmus in Python zum Speichern eines Bitstroms ist in Abbildung 4.5 gegeben.


obj=c_store() # instanziiere Objekt der Bitstromspeicherklasse
obj.open_in() # oeffne Eingabe-Bitstrom
obj.open_out() # oeffne Ausgabe-Bitstrom

decbuff=""
buff=" |~|       "

# solange das Ende des Eingabe-Bitstroms nicht erreicht ist
while buff !="":
    # lese 1024 Bytes vom Eingabe-Bitstrom
    buff = obj.read_in(1024)

    # dekomprimiere eingelesene Daten
    decbuff=decompress(buff)
   
    # schreibe in Ausgabe-Bitstrom
    obj.write_out(decbuff)
   
# schliesse Bitstroeme
obj.close_out()
obj.close_in()


Abbildung 4.5: Algorithmus zur Bitstromspeicherung (Python)

Um aus der temporären Datei zu lesen, muß der Dokumentenmethode ein Dateihandle bzw. ein Dateiname mitgeteilt werden. Selbiges gilt für die Ausgabe in den Bitstromspeicher, auch hier muß ein Dateiname oder -handle bekannt sein. Diese Informationen werden von der Ablaufumgebung vor Ausführung der Methode ermittelt und dieser als globale Variable zur Verfügung gestellt. Diese Variablen benutzt die Bitstromspeicherklasse, um mittels entsprechender Methoden (open_in, close_in, open_out und close_out) auf die Bitströme zuzugreifen. Der Speichervorgang selbst benötigt Methoden, um aus der temporären Datei zu lesen (read_in) und in den Bitstromspeicher zu schreiben (write_out). Beide Methoden arbeiten byte-orientiert, so daß eine bestimmte Anzahl von Bytes gelesen oder geschrieben werden kann.

Für SGML-Dokumente stellt sich diese Aufgabe etwas schwieriger dar. Im Kapitel 2.1 wurde erläutert, aus welchen Elementen sich ein SGML-Dokument zusammensetzt, aus sogenannten Tags und den Taginhalten. Desweiteren benötigt man zur vollständigen Beschreibung eines SGML-Dokuments, dessen Document Type Definition (DTD). Um das Dokument zu speichern, muß die DTD nicht näher betrachtet werden, es muß lediglich sichergestellt werden, daß der Speicher bei Bedarf auf die DTD zugreifen kann.

Bei den im folgenden entwickelten Operationen zum Speichern von Dokumenten wird davon ausgegangen, daß diese in Normalform vorliegen. Das heißt, das Dokument ist vollständig strukturiert, es dürfen keine Endtags weggelassen werden. Da frei verfügbare SGML-Parser für alle Plattformen existieren, bedeutet dies keine Einschränkung.

Um komfortabel aus dem im Zwischenspeicher gehaltenen SGML-Dokument lesen zu können, bedarf es Methoden, um die einzelnen Elemente nacheinander einzulesen und die Attribute der Tags zu ermitteln. Weiterhin müssen Funktionalitäten vorgehalten werden, um den Strukturbaum des SGML-Dokuments speichern zu können, da dieser wie in Kapitel 2.2 für eine Reihe von Operationen benötigt wird. Das heißt, es muß möglich sein, nur die Tags eines SGML-Dokuments sequentiell auszulesen und im Speicher abzulegen.

In Abbildung 4.6 ist ein einfacher Algorithmus zur Speicherung eines SGML-Dokuments in der Datenbank dargestellt.


obj=c_sgmlstore() # instanziiere SGML-Speicherobjekt
obj.open_in() # oeffne Eingabe-Bitstrom
obj.save_doctype() # speichere DTD in der Datenbank


# lese solange naechstes SGML Element bis
# das Ende des Eingabe-Bitstroms erreicht ist
buff=" |~|       "
while buff!=None:
    buff=obj.get_nextsgmlelement()

    # wenn es ein Tag ist speichere es in der
    # Tagklasse, sonst speichere es als Text
    # in der Datenbank
    if obj.is_tag(buff):
     obj.save_astag(buff)
    else:

     obj.save_astext(buff)

# schliesse Eingabe-Bitstrom
obj.close_in()


Abbildung 4.6: Algorithmus zur Dokumentenspeicherung (Python)

Wiederum erfolgt der Zugriff auf das zwischengespeicherte Dokument über ein zuvor von der Ablaufumgebung bereitgestelltes Dateihandle. Zur Speicherung der DTD des Dokuments existiert eine Methode (save_doctype), diese trägt die DTD in die dafür vorgesehene Klasse ein. Die Methoden open_out und close_out sind in diesem Fall nicht mehr erforderlich. Die Verbindung zu einem Datenbankserver wird automatisch und für den Methodenentwickler transparent auf- und abgebaut.

Eine Methode (get_nextsgmlelement), die unabhängig vom Elementtyp das nächste SGML-Element aus der temporären Datei liest, ist unverzichtbar. Stehen nur Methoden zur Verfügung, die lediglich bestimmte Elementtypen einlesen, muß der Autor der Dokumentenmethode immer die Reihenfolge der Elemente berücksichtigen, was zu einem nicht unerheblichen Mehraufwand bei der Programmierung führen würde.

Wie in Kapitel 4.6 beschrieben, wird beim Speichern in der Datenbank, nach Markup (Tags), Text und Binärdaten unterschieden. Daher ist es sinnvoll bereits beim Lesen aus dem Zwischenspeicher Mechanismen bereitzustellen, die ebenfalls diese Unterscheidung erlauben. Da beim Einlesen des nächsten Elements nicht bekannt ist, um welchen Elementtyp es sich dabei handelt, gibt es Methoden, die den Typ feststellen (is_tag, is_text, is_bin). Allerdings stößt man hier bei der Realisierung auf das Problem, Text und Binärdaten zu unterscheiden.

Neben der Methode zum Speichern eines Tags in der Datenbank (save_astag) existieren noch zwei weitere Methoden (save_astext, save_asbin) zum Ablegen der anderen beiden Elementtypen in den Datenbankklassen.

Ein weiteres Beispiel, das nur den Strukturbaum eines Dokuments abspeichert und dabei ein bestimmtes Tag überspringt, ist in Abbildung 4.7 dargestellt.


obj=c_sgmlstore() # instanziiere SGML-Speicherobjekt
obj.open_in() # oeffne Eingabe-Bitstrom

obj.save_doctype() # DTD in der Datenbank speichern

# lese solange naechstes SGML-Tag bis
# das Ende des Eingabe-Bitstroms erreicht ist
buff = " |~|       "

while buff != None:

    buff = obj.get_nexttag()

    # wenn das Tag eine Bemerkung ist,
    # ueberspringe den Inhalt durch suchen
    # des Endtags.
    if obj.split_tag(buff)[0]=="REMARK":
     obj.get_tag("REMARK")
    else:

     # sonst speichere Tag in der Datenbank
     obj.save_astag(buff)

# Bitstrom schliessen
obj.close_in()


Abbildung 4.7: Algorithmus zur Strukturbaumspeicherung (Python)

Die Methode get_nexttag dient dazu, das jeweils nächste Tag, unabhängig davon ob es ein Start- oder Endtag ist, einzulesen. Das Pendant zu dieser Methode (get_tag) findet ein bekanntes Tag. Um ein gelesenes Tag und seine Attribute auswerten zu können, wird eine Methode (split_tag) verwendet. Zu einem gegebenen Tag liefert diese Methode eine Liste zurück, die den Tagnamen und alle Attribute beinhaltet.

Die Berechnung des vereinfachten Strukturbaums (Kap. 2.1) erfolgt zur Laufzeit der Dokumentenmethode. Dabei werden die Tags in der Reihenfolge, in der sie mittels der Methode save_astag abgelegt werden betrachtet und Bruder- und Sohn-Felder nach folgenden Regeln berechnet:

  1. folgt auf ein Starttag wieder ein Starttag, dann ist das zweite Starttag der Sohn des Ersten
  2. folgt auf ein Endtag ein Starttag, dann ist das zweite Tag der (rechte) Bruder des ersten Tags

In Abbildung 4.8 sollen diese Zusammenhänge veranschaulicht werden.


PIC
Abbildung 4.8: Durchlaufen eines Strukturbaums

Die Linien zeigen den Weg, der beim Durchlaufen des Baums mit Depth-First-Search abgeschritten wird. Die schwarzen Linien verdeutlichen die Vater-Sohn- bzw. Bruder-Beziehungen, die gepunkteten Linien vervollständigen den Weg. Der Einfachheit halber wurden die Tags numeriert. Korrespondierende Start- und Endtags stehen jeweils in einem Knoten, damit die Aufteilung der Teilbäume deutlicher wird. Mathematisch korrekt wäre es, die Endtags als (rechten) Bruder des Starttags darzustellen.

4.7.3 Die Zugriffsklassen

Der Zugriff auf die gespeicherten Dokumente kann auf die unterschiedlichsten Arten erfolgen. Die Art des Zugriffs soll jedoch nicht vom Speicher vorgegeben sein. Daher müssen die Zugriffsklassen einen generischen und flexiblen Ansatz verfolgen, der es ermöglicht Dokumentenmethoden zu entwickeln, die es erlauben ein oder mehrere Dokumente zu durchsuchen oder direkt auf Teile eines bekannten Dokuments zuzugreifen. Das Durchsuchen mehrerer Dokumente erfolgt über ein im Speicher abgelegtes „Zugriffs-Dokument“, dem Dokumentenmethoden zugeordnet sind. Diese erlauben es den ganzen oder Teile des Dokumentenbestands zu durchsuchen. Das „Zugriffs-Dokument“ beinhaltet selbst keine Daten.

Zum Durchsuchen aller gespeicherten Dokumente nach und/oder-verknüpften Begriffen wird der Volltextindizierer verwendet. Es wird eine Methode vorgesehen, die unter Benutzung der Schnittstelle zum Volltextindizierer, alle als Bitstrom gespeicherten Dokumente nach einem gegebenen Ausdruck durchsucht. Der Aufbau des Ausdrucks, sowie die Möglichkeiten zur Konfiguration hängen von der Auswahl des Indizierers ab. Da für die Schnittstellen von Volltextindizierern bislang keine Standardisierungsbemühungen existieren, ist es nicht möglich zu diesem Punkt genauere Vorgaben zu machen. Selbst reguläre Ausdrücke finden nur teilweise Verwendung bei den derzeit verfügbaren Indizierern. Als minimale Anforderung wird die Suche nach und/oder-verknüpften Begriffen definiert.

Eine weitere Möglichkeit die Dokumente auf bestimmte Merkmale zu prüfen, ist die Tag-Klasse der Datenbank zu durchsuchen. Dies ist in Kombination mit der Auswahl einer bestimmten DTD von Interesse. Hat man beispielsweise Kenntnis über die Semantik eines bestimmten Tags oder einer DTD, dann muß es Methoden geben, die folgende Dokumente aus dem Speicher selektieren können:

  1. Alle Dokumente, die auf einer bestimmten DTD basieren
  2. Alle Dokumente, die ein bestimmtes Tag beinhalten
  3. Alle Dokumente, die einer bestimmten DTD entsprechen und ein bestimmtes Tag mit bestimmten Attributen enthalten

Als Ergebnis wird eine Liste der entsprechenden Dokumenten-ID’s zurückgegeben. Um diese Funktionalität zu erreichen, bedarf es dreier Methoden: Eine, die zu einer gegebenen DTD alle Dokumente findet, die auf dieser DTD basieren, eine, um die Tag-Klasse nach einem gegebenen Tag zu durchsuchen, die Dritte kombiniert die beiden vorigen und findet alle Dokumente, die einer bestimmten DTD entsprechen und ein gegebenes Tag beinhalten.

Die beschriebenen Funktionalitäten dienen dem Auffinden eines Dokuments im Speicher. Im folgenden werden Operationen definiert, die zum Zugriff auf ein bestimmtes Dokument benötigt werden. Zunächst sind ein paar grundlegende Methoden zu definieren, um das gesamte Dokument auszulesen, den Strukturbaum eines Dokuments zu erhalten und die DTD eines Dokuments zu erfragen.

Für den byte-orientierten Zugriff stehen Methoden bereit, wie sie bereits beim Auslesen der temporären Datei beschrieben wurden. Der strukturierte Zugriff auf ein Dokument geschieht immer über Tags, da diese wie unter Kapitel 2.1 beschrieben, die Struktur des Dokuments manifestieren. Vorausgesetzt wird bei dieser Vorgehensweise immer die Kenntnis über die Struktur des Dokuments, die über die oben beschriebenen Operationen erlangt werden kann. Es werden Methoden bereitgestellt, die das n-te Tag oder alle Tags eines gegebenen Typs auffinden und dessen Inhalt (das von Start- und Endtag eingeschlossenen Dokumentenstück) auslesen können. Weiterhin stehen Methoden zum Auslesen der Attribute und der Anzahl der vorkommenden Tags eines Typs zur Verfügung.

Als Beispiel findet sich in Abbildung 4.9 eine Dokumentenmethode, die als Ergebnis ein Inhaltsverzeichnis aller Kapitel des Dokuments zurückgibt.


obj=c_sgmlquery() # instanziiere Zugriffsobjekt
# initialisiere Lauf- und Ergebnisvariablen
n=1
erg=[" |~|       ",0]
contents={    }

# solange noch ein entsprechendes Tag gefunden wird
while erg!=["",0]:
    # hole n-tes KAPITEL-Tag aus DB
    erg=obj.sgmlquery_tag("KAPITEL",n)
    # rette Tag in Ergebnis Array
    contents[erg[1]]=erg[0]
    n=n+    1

return contents # gebe Ergebnis zurueck


Abbildung 4.9: Dokumentenmethode zur Erstellung eines Inhaltsverzeichnisses

Hierbei wird die Methode (sgmlquery_tag), die das n-te Tag eines bestimmten Typs ermittelt, benutzt um nacheinander alle Tags vom Typ „Kapitel“ aus der Datenbank abzufragen. Diese werden in der richtigen Reihenfolge als Ergebnis zurückgegeben.

Über den Zugriff auf und die Speicherung von Dokumenten-Metadaten im Speicher kann keine definitive Aussage getroffen werden, da diese in Abhängigkeit vom Gesamtsystem festgelegt werden. Es ist aber positiv davon auszugehen, daß eine eindeutige Dokumenten-ID, ein Eigentümer, ein Autor und ein Name (Titel) zu jedem Dokument vorgehalten werden. Hier werden Methoden vorgesehen, die es ermöglichen nach eben diesen Merkmalen zu suchen und die ebenfalls eine entsprechende Liste mit Dokumenten-ID’s zurückliefern.