Fragebogengenerator - Programmiererhandbuch

 
 

1. Entwicklungskonfiguration

Entwicklungskonfiguration
Betriebssystem Linux
Kernel-Version 2.4.0-4GB
Distribution SuSE 7.1 Professional
HTTP Server Apache 1.3.14 (Unix)
Database Server mysql Version 11.12 Distribution 3.23.32 for pc-linux-gnu
Programmiersprache TCL 8.3
Zusätzliche Tools
Getestete Browser:

2. Problemanalyse und Realisation

2.1. Aufgabenstellung

Baukasten zur Erstellung von Web-Fragebögen

(Anmerkung: Das Original dieser Aufgabenstellung befindet sich auf den Seiten von Prof. Schmidt, Fachhochschule Wedel.)

Fragebogenerstellung

Entwicklung eines Systems, mit dem man in einem Web-Browser Fragebögen für Web-Umfragen entwerfen kann. Die Daten der Umfragen sollen in einer Datenbank auf dem Server gespeichert und in Auswertungssysteme exportiert werden können. Auf Klientenseite ist für die Erstellung HTML und einfaches JavaScript zu verwenden, die erstellten Fragebögen sollen reines HTML enthalten. Ein Prototyp für diese Aufgabe ist in einem Projektstudium entwickelt worden. Von diesem Prototypen ist das Datenbankschema zu übernehmen.

Programmiersprachen und Werkzeuge

1. Ansatz

Serverseitig ist das xml2html-Werkzeug der FH und der hierfür verfügbare Datenbankanschluss an eine mySQL-Datenbank zu verwenden. Hierbei ist eine saubere Trennung zwischen der DB-Zugriffsschicht der Fragebogenaufbereitung mittels xml2html einzuhalten. Eine genaue Beschreibung einer Fragebogenstruktur und der Struktur des Fragenlayouts mit XML ist hier sinnvoll.

2. Ansatz

Serverseitig sind Java-Servlets und die JDBC-Schnittstelle für den Datenbankanschluß zu verwenden. Die Bemerkungen über die Modularisierung des System gelten auch hier.

Umgebung

Beide Systeme sollen unter Linux und Apache laufen.

(Anmerkung: Von den Autoren dieses Programms wurde der 1. Ansatz mittels TCL und xml2html gewählt.)

2.2. Terminologie und Abkürzungen

Im Laufe der Arbeit bildete sich eine eigene Terminologie heraus, um Sachverhalte, Personen und Objekte kurz und prägnant benennen zu können. Da diese Begriffe auch im folgenden verwandt werden sollen, hier zu jedem eine kurze Erläuterung.
Begriff: Bedeutung:
Kunde Ein Benutzer, der sich einen in der Datenbank gespeicherten Fragebogen generieren lassen möchte, um ihn auszufüllen und abzusenden.
Administrator Ein Benutzer, der einen in der Datenbank gespeicherten Fragebogen bearbeiten möchte, also etwa Elemente hinzufügen, verändern oder löschen will.
Programmierer Ein unzufriedener Benutzer, der mit TCL und dem xml2html-Tool umgehen kann und das Programm erweitern oder um Fehler bereinigen will.
Element In sich abgeschlossene Einheit in einem Fragebogen. Ein Element ist eine Frage, ein Textblock oder ein Trenner. Blockfragen werden in ihrer Gesamtheit mit allen zugehörigen Unterfragen als Element gesehen. Jedes Element wird anhand einer für diesen Fragebogen eindeutigen ID identifiziert (OrderNo, anhand derer auch die Position im Fragebogen ermittelt wird), und durch eine Menge von Attributen beschrieben.
Attribut Ein Datensatz der Tabelle formdata, der ein Element teilweise beschreibt. Es gibt einige Schlüsselattribute, die den Typ eines Elements eindeutig bestimmbar machen.
Fragebogen Eine Liste von Elementen, geordnet nach deren OrderNo's. Jeder Fragebogen kann durch eine für die Datenbank eindeutige FormID identifiziert werden.
Style-Sheet, Style-File Eine Menge von Definitionen, nach denen xml2html die XML-Tags in gewöhnliches HTML übersetzt. Näheres dazu im xml2html-Tutorium von Götz Franke.
Modul Eine Datei mit TCL-Code. Jedes Modul besitzt seinen eigenen Namensraum, um Namenskonflikte weitestgehend auszuschließen - Ausnahmen (tableinfo.tcl) bestätigen die Regel. Ein Modul enthält die für die Bewältigung eines Aufgabenbereichs notwendigen Funktionen.

2.3. Problemanalyse

Um das entstehende Produkt gut erweiterbar und wartbar zu gestalten, aber dennoch die Vorteile des xml2html-Tools voll auszunutzen, wurde zu Beginn der Analyse-Phase festgelegt, daß sämtliche Funktionalität durch Schnittstellen von Programmmodulen zur Verfügung gestellt, aber das Layout der HTML-Seiten durch xml2html unter Verwendung von selbst erstellten Style-Files erzeugt werden sollte. Dies sollte im folgenden bedingen, daß die Programmteile als Resultat kein fertiges HTML, sondern lediglich XML im Sinne von xml2html erzeugen dürfen.

Um Fragebögen komfortabel verwalten zu können, wurde folgende Basis-Funktionalität als zu implementieren festgelegt, in der Hoffnung, damit den Wünschen eines zukünftigen Benutzers weitestmöglich zu entsprechen:

  • Neu anlegen,
  • kopieren,
  • bearbeiten,
  • umbenennen,
  • löschen und
  • generieren
von Fragebögen.

Zum Bearbeiten eines Fragebogen muß man Elemente

  • einfügen,
  • anhängen,
  • bearbeiten,
  • kopieren,
  • verschieben,
  • löschen sowie
  • die allgemeinen Fragebogeninformationen ändern
können.

Als Elemente wurden zunächst fünf verschiedene Typen definiert:

  • Textelemente, die erklärenden Text enthalten.
  • Trennelemente, die andere Elemente voneinander abgrenzen.
  • Auswahlfragen, die zu ihrer Beantwortung eine Menge von Antworten zur Auswahl mitliefern.
  • Blockfragen, die mehrere Auswahlfragen unter einer übergeordneten Fragestellung zusammenfassen.
  • Textfragen, die eine Antwort in Form von Freitext zulassen.
Für die Trennelemente wurden als Typen
  • Linien (in HTML "Horizontal Row" genannt, mit dem Tag <HR>),
  • leerer Zwischenraum und
  • Bilder
vorgesehen.

Abwärtskompatibilität

Da die Struktur der Datenbank durch den in der Aufgabenstellung erwähnten Prototypen vorgegeben war, mußten schon in der Analyse-Phase etwaige aus diesem Schema resultierende Probleme beachtet werden. Dazu wurde unter anderem auch die existierende Implementation analysiert.

Da dieser Prototyp schon an der FH im Einsatz war, lag es nahe, sich auch bei den Attributen und der Zusammensetzung der Elemente aus diesen sehr dicht an den Prototypen zu halten, um eine gewisse Abwärtskompatibilität garantieren zu können. Es sollte möglich sein, Fragebögen aus dem alten Fragebogengenerator problemlos mit diesem Produkt zu übernehmen und weiter bearbeiten zu können. Andererseits sollte aber eine einfache Erweiterbarkeit und Anpaßbarkeit gegeben sein. Um dieses Ziel zu erreichen, wurde die Verwendung einer Zwischenschicht zwischen dem Programm und der Datenbank festgelegt.

Neue Funktionalität

Ein wichtiger Gesichtspunkt bei der Analyse war die Zugangsauthentifizierung zu den Fragebögen. Um zu verhindern, daß die Fragebögen von unbefugten Personen verändert, gelöscht oder ausgewertet werden können, wurde ein zusätzliches Attribut FrmPassword definiert, daß als Wert eine Zeichenkette speichern sollte. Anhand dieses Attributes muß sich nun jeder Benutzer, der einen Fragebogen bearbeiten will, authentifizieren. Mißlingt dies, weil das eingegebene Paßwort nicht mit dem in der Datenbank gespeicherten übereinstimmt, wird dem Benutzer der Zugriff auf die Datenbank verweigert. Dieser Mechanismus wird weiter unten noch eingehend erläutert.

Abgrenzung der Module

Bei der Definition der Schnittstellen der einzelnen Module wurde aufgabenorientiert vorgegangen. So wurden folgende Module im Vorwege definiert und grob spezifiziert:

  • CGI - für das Einlesen von CGI-Parametern
  • Generator - als Hauptprogramm, für das Generieren einer HTML-Seite
  • Form - für das Bearbeiten von Fragebögen
  • Element - für das Bearbeiten von Fragebogenelementen
  • DBOperations - für die Datenbankschnittstelle
  • Builder - für das Generieren von Fragebögen aus der Datenbank
Im Laufe der Entwicklung wurden folgende weitere Module notwendig:
  • Error - zum Erzeugen von standardisierten Fehlermeldungen
  • ComplexInput - zum Erzeugen von komplizierteren HTML-Formular-Elementen
  • Util - für alles, was in den anderen Modulen nichts zu suchen hatte
Das Modul TableInfo wurde zudem aus dem Modul DBOperations ausgegliedert, um ein einfacheres Anpassen der Zuordnungen zwischen Attributen und den im Programm verwandten symbolischen Namen zu ermöglichen.

Das Programm als aktionsorientiertes System

Sieht man den Fragebogengenerator aus der vereinfachenden Sichtweise des EVA-Konzepts (Eingabe, Verarbeitung, Ausgabe) - nach dem er ja wie jedes andere Webinterface auch tatsächlich vorgeht, da Interaktionen des Benutzers mit dem Programm während des Programmlaufs unmöglich sind - so kommt man zu dem Resultat, daß der Benutzer für jede ihm mögliche Aktion (in der Eingabephase) ein geeignetes Interface in Form einer Eingabemaske zur Verfügung gestellt bekommen muß, um die Bedingungen für sein Handeln festlegen zu können.

Somit konnten über die beabsichtigten Aktionen des Benutzers die verschiedenen Seiten unterschieden werden, mit denen daß Programm auf sein Ansinnen reagieren soll. Hierbei mußte aber wieder beachtet werden, daß vom Programm pro originärer Benutzeraktion zwei damit verbundene Programmreaktionen erwartet werden:

  1. Das Erzeugen eines Eingabeformulars, um die Aktion den Wünschen und Umständen entsprechend parametrisieren zu können. (Schritt 1)
  2. Das Durchführen der eigentlichen Aktion mit den angegebenen Parametern auf der Datenbank. (Schritt 2)
Konkret bedeutet das, daß etwa zum Anlegen eines Fragebogens zunächst ein HTML-Formular erzeugt werden muß, in welchem der Benutzer die allgemeinen Daten des Fragebogens eingibt, wie zum Beispiel den Titel oder das Paßwort des Fragebogens. Nach dem Absenden dieses Formulars folgt im zweiten Schritt die Speicherung der übersandten Daten und daß Generieren eine Erfolgsmeldung über die Aktion, etwa "Fragebogen wurde angelegt".

Zu den verschiedenen Aktionsmöglichkeiten des Benutzers wurde ja weiter oben schon etwas gesagt. Mit dieser Zweischrittigkeit für die oben genannten Benutzeraktionen und zusätzlich der Erzeugung einer Einstiegsseite können 27 verschiedene Aktionen, und damit ebensoviele zu erzeugende HTML-Seiten unterschieden werden. Zusätzlich werden noch Seiten für Fehlermeldungen hinzukommen.

Im Rahmen dieser aktionsorientierten Sichtweise übernimmt im Hinblick auf die konkrete Aufgabenverteilung das Modul Element die Erzeugung der Formulare zum Bearbeiten von Fragebogenelementen, das Modul Form die Erzeugung der Formulare zum Bearbeiten von kompletten Fragebögen und das Modul DBOperations den zweiten Schritt komplett, sowohl für Elemente als auch für Fragebögen. Die Einstiegsseite erzeugt das Generator-Modul selbst.

Übermittlung des Programmzustands

Als Programmzustand soll im Folgenden die Aktion verstanden werden, die der Benutzer im Begriff ist durchzuführen. Will er beispielsweise gerade ein Fragebogenelement löschen, wäre der Zustand etwa "Löschen eines Elements". Damit sollen zur Vereinfachung die beiden oben genannten Schritte zunächst wieder zusammengefaßt werden.

Dabei stehen die Zustände oder Aktionen untereinander aber nicht zwingend in Beziehung - ein Benutzer kann, das nötige Hintergrundwissen vorrausgesetzt, durch aus mit der manuellen Eingabe eines URL in seinen Browser direkt ein Element aus einem Fragebogen löschen, wobei dies natürlich nicht trivial ist (vor allem muß der Benutzer das richtige Paßwort für den in Frage kommenden Fragebogen kennen). Diese Vorgehensweise ermöglicht aber die spätere Entwicklung eines alternativen GUI zur Bedienung des Fragebogengenerators, welche dann auch nur HTTP-Requests absetzen muß - etwa durch eine Java-Applikation.

Da der Fragebogengenerator aber im Laufe einer zweischrittigen Aktion nach dem ersten Schritt den gewünschten zweiten Schritt automatisch vollziehen muß, muß ihm ohne Einwirkung des Benutzers dieser zweite Schritt - aufgrund des vorliegenden "Programmzustands" - mitgeteilt werden.

Bei der Planung mußte zwischen drei grundlegend verschiedenen Strategien der Übermittlung des aktuellen Programmzustands entschieden werden:

  1. Definition des Programmzustands durch URLs
  2. Definition des Programmzustands durch CGI-Parameter
  3. Definition des Programmzustands durch Cookies
Die dritte Methode schied sehr bald durch den Umstand aus, daß die Benutzer von HTML-Browsern Cookies ablehnen können, wodurch ein konsistenter Programmablauf nicht mehr gewährleistet wäre. Da ein Teil der Benutzer Cookies aus Gründen der Privatheit grundsätzlich ablehnt, wären diese Benutzer damit von der Verwendung des Fragebogengenerators ausgeschlossen gewesen.

Die erste Methode, bei der für jeden möglichen Programmzustand ein eigener URL definiert wird, stellte sich als zu inflexibel heraus. Auch wäre bei den zu erwartenden 13 Zuständen diese Alternative schon relativ unübersichtlich geworden, und hätte damit der selbstauferlegten Pflicht der einfachen Wartbarkeit widersprochen.

Daher fiel die Entscheidung letztendlich zugunsten der Übergabe des Programmzustands durch CGI-Parameter. Dabei wird in einer generierten HTML-Seite die Zustandsinformation in Form eines versteckten Formular-Elementes gespeichert, und damit beim Absenden des Formulars wieder an den aufgerufenen Fragebogengenerator zurückgegeben. Diese Art der Speicherung von Zustandsinformationen bedingt aber eine sorgfältige Prüfung der CGI-Eingabeparameter, gerade im Zusammenhang mit der oben erwähnten Paßwort-Authentifizierung - um zu Verhindern, daß ein Benutzer unberechtigterweise etwa einen Fragebogen löscht.

Sicherheit und Authentifizierung

Um zu verhindern, daß unberechtigte Personen die Daten eines Fragebogens einsehen oder verändern können, war die Einführung eines neuen Attributs FrmPassword in die Tabelle mit den Fragebogendaten beschlossen worden. Zu Beginn einer Sitzung sollte sich ein Benutzer einmalig mit einem Paßwort einloggen müssen. Um eine Bearbeitung der Fragebögen ohne weitere Paßworteingaben zu ermöglichen, wurden zwei Möglichkeiten in Betracht gezogen:

  1. Vergabe einer Session-ID nach dem Log-In
  2. Weitergabe des Paßworts von Aktion zu Aktion ähnlich der Zustandsinformation
Da um das erste Verfahren einzusetzen die Datenbank hätte verändert werden müssen, wurde die zweite Variante gewählt. Allerdings dürfen die hiermit verbundenen Sicherheitsmängel nicht unerwähnt bleiben: So wird das Paßwort nicht nur in der URL codiert, sondern ist auch in den generierten HTML-Seiten als Hidden-Parameter enthalten. Wenn diese Seiten durch den Browser gecached werden, kann einem Unberechtigten eben doch der Zugriff auf die paßwortgeschützten Fragebögen gelingen, etwa an einem öffentlichen Surf-Terminal. Weiterhin besteht natürlich auch die Gefahr des Mitschneidens des HTTP-Datenverkehrs durch Netzwerk-Sniffer-Tools. Dieses Risiko kann evtl. durch Nutzung von Verschlüsselungsverfahren wie SSL bei der Seitenübertragung minimiert werden.

Generelle Implementations-Guidelines

Um das selbstgesteckte Ziel der leichten Wart- und Erweiterbarkeit zu erreichen, wurde unter anderem ein Satz von generellen Richtlinien zur Implementation definiert, und zwar je für die Aspekte "HTML" bzw. "Style-Files" und "TCL-Code" getrennt:

HTML / Style-Files
  • Es wird wohlgeformtes HTML geschrieben: Für jedes öffnende Tag existiert auch ein schließendes Tag, etc.
  • Es wird gültiges (valid) HTML geschrieben.
  • Die erste Zeile jedes Dokuments enthält die DTD.
  • Alle Tags werden GROSS, alle Attribute werden klein geschrieben.
  • Es werden nur allgemeinverständliche Tags benutzt. Sonderformen der großen Browser werden nicht verwandt.
  • Attributwerte stehen immer in doppelten Anführungszeichen.
  • Für das Layout wird CSS1 benutzt, wobei sich auf jene Elemente beschränkt wird, welche sowohl von Netscape 4+ als auch IE 4+ korrekt unterstützt werden.
  • Es werden keine Frames verwendet.
  • Für Images wird eine Beschreibung des Elements im alt-Attribut angegeben.
  • Es wird möglichst kein Java-Script verwendet. Falls Java-Script verwandt wird, existiert für jedes <SCRIPT>-Tag ein korrespondierendes <NOSCRIPT>-Tag.
  • Formulardaten werden immer per POST-Methode übergeben.
  • xml2html-Tags tragen ein Präfix, welches per Bindestrich vom Tagnamen getrennt wird und auf den Dateinamen des Style-Files hinweist.
TCL-Code
  • Es werden Namespaces verwendet, um die Sichtbarkeit von Variablen und Funktionen einzuschränken.
    • Exportiert werden nur die benötigten Schnittstellenfunktionen
    • Es gelten folgende Namenskonventionen:
      • Namensraum-globale Variablen: Nicht exportiert! Beginnen immer mit einem Kleinbuchstaben, der Name trennt mehrere Teilworte mit Unterstrichen, falls dies erforderlich wird. Werden über das "variable"-Kommando in den aktuellen (lokalen) Namensraum eingebunden. (z.B.: meine_globale_variable)
      • Lokale Variablen: (Erst recht nicht exportiert) Werden immer komplett klein geschrieben. (z.B.: zaehlvariable)
      • Exportierte Prozeduren: Beginnen mit einem Kleinbuchstaben, Unterstriche trennen die einzelnen Teilworte, die jeweils mit einem Großbuchstaben beginnen (z.B.: meine_Prozedur_Zum_Rechnen)
      • Nicht-Exportierte Prozeduren: Beginnen mit einem Kleinbuchstaben, enthalten keine Unterstriche, einzelne Teilworte beginnen jeweils mit einem Großbuchstaben (z.B.: meineEigeneProzedur)
      • Namensräume: Beginnen mit einem Großbuchstaben, einzelne Teilworte beginnen ebenfalls mit Großbuchstaben (z.B.: MeinNamensraum)
  • Substitution komplexer Strings, wie etwa regulärer Ausdrücke oder Teilen von xml2html-Seiten, wird möglichst nur auf Verlangen mit dem "subst"-Kommando durchgeführt, und kann somit kontrolliert werden.
  • Strings werden nie mit dem "expr"-Kommando, sondern immer mit "string compare" und "string equal" verglichen.
  • Listen werden immer mit dem "list"- oder "lappend"-Kommando konstruiert. Das Kommando "concat" und die doppelten Anführungszeichen werden nicht oder mit äußerster Sorgfalt verwendet. Dies gilt insbesondere für "eval"!
  • Die Kommandos "break" und "continue" werden nicht verwendet.
  • Das Kommando "exit" wird nur bei Auftreten eines unlösbaren Problems (z.B.: Programmfehler - also nie) verwendet.
  • Programmfehler werden an den kritischen Stellen per "catch" abgefangen. Es werden sinnvolle Fehlermeldungen erzeugt und in ein Logbuch geschrieben.
  • Referenzparameter werden durch Nutzung des "upvar"-Kommandos implementiert.
  • Pfadnamen werden nicht einfach durch Benutzen des "/" erzeugt, sondern per "file join"-Kommando.
Von diesen Maßnahmen wurde vor allem eine Verbesserung des programmierten Codes erwartet. Ob dieses Ziel erreicht wurde, muß allerdings von Dritten beurteilt werden.

2.4. Realisation

Allgemeiner Programmablauf

Wie von der Aufgabenstellung gefordert, wurde das Projekt unter Linux auf dem Apache-Webserver umgesetzt. Dabei wurde eine .htaccess-Datei dazu benutzt, um xml2html zu starten und eine Verteilerfunktion auf Grundlage der oben erwähnten Zustandsinformation aufzurufen, die dann in dem für die anliegende Aktion zuständigen Modul die jeweilige Prozedur aufruft. Dies ist der immer vorhandene CGI-Parameter ACTION. Fehlt dieser CGI-Parameter, so wird die Eingangsseite generiert - dies ist also die Default-Aktion.

Um zwischen dem ersten und dem zweiten Schritt der jeweiligen Aktion zu unterscheiden, wurde zusätzlich ein zweiter CGI-Parameter definiert. Um zu Verhindern, daß durch fehlerhaftes Übertragen oder Auslesen der CGI-Parameter fälschlicherweise der erste und der zweite Schritt vertauscht werden, enthält der ACTION-Parameter in diesem Fall nur noch daß Symbol dafür, daß eine Datenbankoperation durchgeführt werden soll (da dies in der Regel im zweiten Schritt der Fall ist) - konkret wird er mit dem Wert "db_operation" belegt.

Ist dies der Fall, so wird anhand des zweiten signifikanten CGI-Parameters, OPERATION entschieden, welche Aktion auf der Datenbank durchgeführt werden soll. Auch hier entscheidet wieder eine Verteilerfunktion, welche Prozedur aus dem Modul DBOperations zur Bearbeitung herangezogen werden muß.

Wurde die jeweilige Prozedur ermittelt, werden zusätzlich erwartete Parameter aus dem CG-Interface ausgelesen und beim Start der jeweiligen Funktion mit übergeben. Bei Prozeduren mit einer großen Anzahl an benötigten Parametern, wie etwa beim Anlegen eines neuen Elements, werden einfach alle eingegangenen CGI-Parameter an die Funktion weitergegeben - diese muß sich dann aus dieser Liste die tatsächlich für diese Aktion benötigten Parameter selbst heraussuchen. Außerdem ist die aufgerufene Funktion immer selbst für die Gültigkeitsprüfung der Parameter verantwortlich, da nur hier bekannt ist, in welchem Kontext diese stehen.

Als wichtigste CGI-Parameter, die bei fast jeder Aktion involviert sind, seien hier FORMID, ORDERNO und FRM_PASSWORD erwähnt. FORMID identifiziert den zu bearbeitenden Fragebogen eindeutig - fehlt dieser Parameter, so muß im Normalfall mit einer Fehlermeldung reagiert werden, da ansonsten das der Aktion zugrundeliegende Objekt ja nicht bestimmt werden kann. Lediglich beim Neuanlegen eines Fragebogens kann auf diesen Parameter verzichtet werden, da im Laufe dieses Aktion die FormID erst (auf Grundlage des aktuellen Datum-Zeit-Wertes) generiert wird.

In gleicher Weise identifiziert der Parameter ORDERNO ein Element eines Fragebogens eindeutig. Ist dieser Parameter nicht vorhanden, können Elemente nicht modifiziert werden. ORDERNO gibt aber zusätzlich zu seiner Funktion als Schlüsselwert auch noch die Reihenfolge der Elemente im Fragebogen an. Daher handelt es sich bei dem Wert dieses Parameters um einen Integer - je höher dieser ist, desto weiter unten steht das Element im Fragebogen.

Über den CGI-Parameter FRM_PASSWORD wird die Authentifizierung des Benutzers vorgenommen. Wie schon oben erwähnt, wird dieser Parameter nach dem ersten Log-In als Hidden-Parameter von Seite zu Seite weitergegeben. Auf die damit verbundenen Sicherheitsmängel wurde bereits hingewiesen - hier liegt es am Administrator, die gebührende Sorgfalt walten zu lassen.

Da dieses Attribut neu eingeführt wurde, und daher bei importierten Fragebögen des Prototypen nicht vorhanden ist, wird das per CGI-Parameter übergebene Paßwort nur überprüft, wenn in der Datenbank auch tatsächlich ein Paßwort-Attribut für diesen Fragebogen gespeichert ist. Damit wird erreicht, daß die importierten Fragebögen solange auch ohne Paßwort zugegriffen werden können, bis hier vom Administrator ein Paßwort vergeben wurde.

Diese und eventuell noch andere CGI-Parameter werden also von der zentralen Verteilerfunktion ausgelesen und beim Aufruf der für die anstehende Aufgabe vorgesehene Prozedur übergeben. Hier werden die Parameter dann zunächst auf Gültigkeit geprüft, und bei fehlerhaften Eingabewerten Fehlermeldungen generiert und der Anwender evtl. zur Neueingabe der betreffenden Parameter aufgefordert. Bei der Übergabe ganzer Parameterlisten muß die verarbeitende Funktion zudem diese Listen filtern, und alle nicht benötigten Eingabeparameter verfallen lassen. Damit wird insgesamt sichergestellt, daß nur im Kontext der Aktion sinnvolle Werte Eingang in die Datenbank finden.

Die nun folgende Verarbeitung ist von Aktion zu Aktion unterschiedlich, und soll daher hier übersprungen werden. Als Resultat ergibt sich aber in jedem Fall wieder eine XML-Seite, die von xml2html unter Nutzung der zu diesem Zweck erstellten Style-Files in HTML übersetzt wird. Bei Aktionen des 1. Schritts wird es sich dabei um ein Eingabeformular handeln. Hier werden keine Operationen auf der Datenbank durchgeführt. Bei Aktionen des 2. Schritts wird die resultierende Seite entweder eine Fehlermeldung sein, die, sofern es sich nicht um einen Datenbank- oder programminternen Fehler handelt, die Neueingabe eines Parameters fordert, oder aber eine Erfolgsmeldung, die mitteilt, daß die gewünschte Aufgabe ordnungsgemäß erledigt wurde.

Zwischenschicht zwischen Datenbank und Programm

Um das Programm bei Änderungen an der allgemeinen Datenbankstruktur - zum Beispiel bei Erweiterungen - schnell anpassen zu können, wurde zwischen Programm und Datenbank eine Zwischenschicht geschoben, die für die nötige Abstraktion sorgen soll. Dabei handelt es sich im Grunde um ein Array von Strings, der programmintern verwendete Symbole auf die in der Datenbank verwendeten Attributnamen für Elementattribute zuordnet, in Form eines einfachen Mappings. Außerdem werden die Namen der verwendeten Tabellen in dem von xml2html definierten global_options-Array zur Verfügung gestellt.

Um auf diese Maps gesichert zugreifen zu können, wurden außerdem noch Zugriffsfunktionen erstellt, die zunächst die Existenz des benötigten Arrays überprüfen, und so die häufigsten Fehlerfälle unterdrücken sollen. In dem Programmcode selber wurden daraufhin anstatt der eigentlichen, in der Datenbank vorliegenden Attribute, nur noch diese Zugriffsfunktionen im Zusammenhang mit den definierten Symbolen verwendet. Damit kann nach einer Änderung an der Datenbank durch einfaches Anpassen der jeweiligen Map die Änderung auf das Programm übertragen werden.

Das Array zur Übersetzung der programminternen Symbolik in reale Attributnamen wird im Modul TableInfo in der Datei tableinfo.tcl erzeugt, und hat in der ausgelieferten Form folgenden Aufbau:
Symbolischer Name (programmintern) Attributname in der Datenbank Bedeutung
FRM_TITLE FrmTitle Titel des Fragebogens
FRM_DESCRIPTION FrmDescription Kurzbeschreibung des Fragebogenthemas
FRM_CREATED_ON FrmCreatedOn Zeitpunkt des Fragebogenentwurfs
FRM_CREATED_BY FrmCreatedBy Autor des Fragebogens
FRM_LAST_UPDATED FrmLastUpdated Zeitpunkt der letzten Änderung am Fragebogen
FRM_SHOW_TITLE FrmShowTitle Gibt an, ob der Titel angezeigt werden soll
FRM_BG_COLOR FrmBgColor Hintergrundfarbe des Fragebogens
FRM_FG_COLOR FrmFgColor Vordergrundfarbe des Fragebogens (Textfarbe)
FRM_PASSWORD FrmPassword Den Fragebogen sicherndes Paßwort
TXT_TEXT TxtText Nur bei Textblöcken: der enthaltene Text
TXT_ALIGNMENT TxtAlignment Nur bei Textblöcken: Ausrichtung des Textes
TXT_SIZE TxtSize Nur bei Textblöcken: Textgröße
TXT_COLOR TxtColor Nur bei Textblöcken: Textfarbe
TXT_LINK_TARGET TxtLinkTarget Nur bei Textblöcken: Verlinkt den Textblock mit einer Frage
SEP_TYPE SepType Nur bei Trennern: Typ des verwendeten Trenners
SEP_DATA SepData Nur bei Trennern: Zusätzliche, zur Darstellung des Trenners benötigte Daten
QST_NUMBER QstNumber Nur bei Fragen: Nummer der Frage (nicht Position im Fragebogen!)
QST_SHOW_NUMBER QstShowNumber Nur bei Fragen: Gibt an, ob die Fragenummer angezeigt werden soll
QST_TEXT QstText Nur bei Fragen: Die Fragestellung
QST_ALIGNMENT QstAlignment Nur bei Fragen: Ausrichtung des Fragetextes
QST_ANSWER_ALIGNMENT QstANSWERAlignment Nur bei Fragen: Ausrichtung der Antworten
INP_TXT_LENGTH InpTxtLength Nur bei Textfragen: Maximal zugelassene Antwortlänge
INP_TXT_WIDTH InpTxtWidth Nur bei Textfragen: Breite des Eingabefeldes in Zeichen
INP_TXT_HEIGHT InpTxtHeight Nur bei Textfragen: Höhe des Eingabefeldes in Zeilen
INP_SEL_TYPE InpSelType Nur bei Auswahlfragen: Art des Auswahlelementes
INP_SEL_OPTION_VALUES InpSelOptionVALUEs Nur bei Auswahlfragen: Liste mit Antworten, aus denen gewählt werden kann
INP_SEL_DEFAULT InpSelDefault Nur bei Auswahlfragen: Wert der Default-Antwort
INP_SEL_ALIGN_TEXT InpSelAlignText Nur bei Auswahlfragen: Anordnung der Optionsbezeichner
INP_SEL_ALIGN_OPTIONS InpSelAlignOptions Nur bei Auswahlfragen: Anordnung der Antworten
INP_SEL_BLOCK_VALUES InpSelBlockVALUEs Nur bei Blockfragen: Liste mit Unterfragen
INP_SEL_SUB_QST_ALIGNMENT InpSelSubQstAlignment Nur bei Blockfragen: Anordnung der Unterfragen

Damit diese Zwischenschicht ihre abstrahierende Wirkung entfalten kann, muß die Verwendung der symbolischen Namen anstatt der in der Datenbank verwendeten natürlich im Programm selbst auch konsequent durchgehalten werden. Besonders im Zuge von Erweiterungen sollte hierauf geachtet werden, um sich spätere Verwirrungen und die daraus resultierenden Debug-Marathons zu ersparen.

2.5. Änderungen und Erweiterungen

Während der Planung und Implementierung des Projekts wurde versucht, zukünftige Änderungen zu antizipieren, und daraufhin die betroffenen Programmteile, wenn möglich an exponierte Stellen im Quelltext zu bringen, am besten in eine eigene Datei.

Während die Zuordnung der im Programm verwendeten symbolischen Namen zu den real in der Datenbank verwandten Attribut-Namen im Modul TableInfo in der Datei tableinfo.tcl verwendet werden, werden allgemeinere Informationen zur Datenbank, wie etwa der Datenbankhost, in der Datei dbinfo.tcl gemacht, die bei der Installation automatisch erzeugt werden sollte. Daher ist sie nicht inline-dokumentiert, ihr Aufbau und die Funktionsweise sollte allerdings nicht sehr komplex erscheinen.

Eine weitere Änderung könnte das Anpassen der CGI-Request-Method sein, über die gesteuert wird, auf welche Art und Weise der Webserver die CGI-Parameter an das aufgerufene Programm, hier also xml2html, weitergibt. Standardmäßig wird hier die POST-Methode verwendet, was den Vorteil hat, daß die übersandten Daten nicht für jeden offen sichtbar an den URL angehängt werden. Allerdings funktioniert diese Methode anscheinend nicht mit jeder Webserver-Konfiguration. Um das Programm auf die GET-Methode umzustellen, muß eine xml2html-Definition in dem Style-File Form (in der Datei form.style) anders parametrisiert werden, und zwar jene für das Tag <form-begin. Da das Programm den Default-Wert des Parameters METHOD nicht überschreibt, muß dieser einfach von "post" auf "get" umgestellt werden. Alternativ kann natürlich auch jede Stelle im Programm, an der dieser Tag auftaucht, modifiziert werden...

Fallbeispiel Erweiterungen: Vordefinierte Standardantworten

Um das generelle Vorgehen bei Erweiterungen zu erläutern, soll ein kurzes Fallbeispiel gegeben werden. So sollen für Auswahlfragen gewisse Listen von Standardantworten vordefiniert werden. Diese sollen aber nicht jedes Mal in der Datenbank abgelegt, sondern in einem Style-File definiert werden. Beispiele für solche Listen von Standardantworten wären etwa für Ja-Nein-Fragen
{ 1=Ja
  2=Nein }
oder für eine Bewertung nach dem Schulnotensystem
{ 1
  2
  3
  4
  5
  6 }
Der Benutzer soll aber trotzdem weiterhin auch die Möglichkeit besitzen, sich eigene Antwortmöglichkeiten zu erstellen.

Zunächst benötigen wir zur Speicherung der Information, mit welchen Antworten die Auswahlfrage beantwortet werden kann, ein neues Attribut, nennen wir es InpSelOptionListType (wir wollen ja sprechende Namen verwenden). Als erstes fügen wir dieses Attribut samt seinem Alias (etwa: INP_SEL_OPTION_LIST_TYPE dem formdata-Array im TableInfo-Modul hinzu, in dem wir vor der schließenden geschweiften Klammer folgende Zeile einfügen:

INP_SEL_OPTION_LIST_TYPE        InpSelOptionListType
Als Wertebereich für unser neues Attribut legen wir beispielsweise fest:
{ user | yesno | school }
Dabei gibt ein Wert "user" an, daß eine benutzerdefinierte Liste benutzt werden soll, die im Wert eines anderen Attributs in der Datenbank gespeichert ist. "yesno" gibt an, daß eine Frage mit den Antworten "Ja" und "Nein" generiert werden soll, und über den Wert "school" wird angezeigt, daß als Antworten die Schulnoten zur Verfügung stehen sollen.

Jetzt können wir in der Eingabemaske für Auswahlfragen und Blockfragen dieses neue Attribut durch ein Eingabeelement repräsentieren lassen. Dazu fügen wir im Style-File Template Input (in der Datei templateInput.style)im Body der Definition des Tags <template-selectquestionscommonsection> ein neues Formular- Element ein, welches die Auswahl zwischen den erlaubten Werten bietet:

<form-element label="Antwort-Liste:" valign="center">
   <form-dropdown cginame="INP_SEL_OPTION_LIST_TYPE"
      items="
         {user=Benutzerdefiniert}
         {yesno=Ja-Nein-Frage}
         {school=Benotung nach dem Schulnotensystem}
      "
      default="$INP_SEL_OPTION_LIST_TYPE">
</form-element>
Natürlich müssen wir nun für dieses Tag den Parameter INP_SEL_OPTION_LIST_TYPE auch bekannt machen:
INP_SEL_OPTION_LIST_TYPE='user'
Genauere Angaben zur Definition von eigenen Tags in Style-Files können im xml2html-Tutorium nachgesehen werden.

Um diesen Wert jetzt auch tatsächlich speichern zu können, müssen wir in dem Modul DBOperations noch einigen Anpassungen vornehmen:

  • INP_SEL_OPTION_LIST_TYPE in die Liste der gültigen Parameter für Auswahlfragen und Blockfragen aufnehmen
  • Dafür sorgen, daß der Wert von INP_SEL_OPTION_LIST_TYPE dem gültigen Wertebereich entspricht.
Dazu wird zunächst in der Prozedur DBOperations::getValidParametersForType der Liste selectquestionscommonparameters das Symbol INP_SEL_OPTION_LIST_TYPE hinzugefügt:
set selectquestionscommonparameters [list "INP_SEL_TYPE" \
   "INP_SEL_DEFAULT" \
   "INP_SEL_OPTION_VALUES" \
   "INP_SEL_OPTION_LIST_TYPE" \
]
Dann wird in der Prozedur DBOperations::checkParameter in dem Switch-Verteiler folgender Zweig hinzugefügt:
INP_SEL_OPTION_LIST_TYPE {
   set valid [list user yesno school]
   if {! [Util::is_In_List $valid "$value"]} {
      set message $defaultmsg
      set input [subst {
         <form-dropdown cginame="INP_SEL_OPTION_LIST_TYPE"
            items="
               {user=Benutzerdefiniert}
               {yesno=Ja-Nein-Frage}
               {school=Benotung nach dem Schulnotensystem}
            "
            default="$INP_SEL_OPTION_LIST_TYPE">
      }]
   }
}
Bereits jetzt wird die Eingabe in die Datenbank übernommen. Wir müssen nun nur noch dafür sorgen, daß auch bei der Generierung der Fragebögen für den Kunde hier die vordefinierten Antwortlisten benutzt werden. Dies geschieht im Modul Builder (in der Datei builder.tcl). Hier muß in der Prozedur Builder::buildQuestion, die für die Generierung von allen Arten von Fragen zuständig ist, der Variablen map ein weiteres Paar am Ende angefügt werden, welches die Zuordnung des Wertes von INP_SEL_OPTION_LIST_TYPE auf eine lokale Variable vornimmt:
set map {
   QST_TEXT text
   ...
   INP_SEL_OPTION_VALUES options
   ...
   INP_SEL_OPTION_LIST_TYPE listtype
}
Nun kann, vielleicht in einer kleinen Subroutine, der ebenfalls über die Map in die Variable options eingelesene Wert von INP_SEL_OPTION_VALUES überschrieben werden:
set options [optionOverride $options $listtype]
Eine beispielhafte Implementierung von der Prozedur Builder::optionOverride wäre:
proc Builder::optionOverride { {options {}} {listtype "user"} } {
   switch -exact $listtype {
      yesno { return "1=Ja\n2=Nein" }
      school { return "1\n2\n3\n4\n5\n6" }
      user { return $options }
      default { return $options }
   }
}
Damit ist diese Erweiterung einsetzbar. Das war doch gar nicht so kompliziert, oder?


3. Beschreibung grundlegender Datenstrukturen

Es werden lediglich die in der Skriptsprache TCL bekannten Datenstrukturen, also Strings und Listen, verwandt. An vielen Stellen werden in Listen Wertepaare und Sublisten transportiert, deren Aufbau aber problemspezifisch ist. Diese Listen werden aber im Programmtext meist eingehend erläutert.

Typische Beispiele für im Programm verwandte Listenstrukturen sind:

  • Name-Wert-Paare:
    { {name1 wert1}
      {name2 wert2}
      {name3 wert3}
      ... }
  • Array-Kompatible Listen: Mit diesen Listen läßt sich mit dem TCL-Kommando array set auf einfache Weise ein Array befüllen:
    { name1 wert1
      name2 wert2
      name3 wert3
      ... }
  • Datenbank-Kompatible Listen: Aus Datenbankinhalten werden häufig Listen generiert, die die beiden obigen Strukturen miteinander kombinieren:
    { { schlüssel1 { wert1_1 wert1_2 wert1_3 } }
      { schlüssel2 { wert2_1 wert2_2 wert2_3 } }
      { schlüssel3 { wert3_1 wert3_2 wert3_3 } }
      ... }
  • Name-Wert-Strings: Einige der in den Style-Files definierten xml2html-Elemente erwarten Listen mit Name-Wert-Paaren als Parameter. Um das Editieren dieser Listen, die oft statisch in den Style-Files codiert sind, relativ einfach zu halten, wird hier das Name-Wert-Paar in einem String gespeichert und durch ein Gleichheitszeichen verbunden:
    { { name1=wert1 }
      { name2=wert2 }
      { name3=wert3 }
      ... }

Anmerkung: Da TCL von Haus aus keine Integer kennt, müssen Strings auch Zahlen speichern. Ob in einem String nun eine gültige Zahl enthalten ist, kann allerdings nicht bei der Übergabe des Strings an eine andere Routine ermittelt werden, wie es in anderen, typsichereren Programmiersprachen meist schon durch den Compiler geschieht, sondern erst in der Routine selbst. Um trotzdem verdeutlichen zu können, welche Funktionen einen Integer als Eingabeparameter erwarten, wird im Folgenden der Begriff "Integer" für einen String "str" verwandt werden, für den gilt:

[string is integer -strict "$str"] == 1
Der String muß also eine Ganzzahl enthalten und darf nicht leer sein.

4. Beschreibung der Module

Die gesamte Programmfunktionalität, die komplette Datenbankschnittstelle und Methoden zum Erzeugen komplexer GUI-Elemente wurden in folgenden TCL-Modulen untergebracht:

Modulname: Funktion:
Builder
(in Datei builder.tcl)
Zusammensetzen der Datenbankinhalte zu einem fertigen Fragebogen, der von einem Kunden ausgefüllt (und damit beantwortet) werden kann. Dies wird sicherlich die am häufigsten benutzte Funktion des Fragebogengenerators sein. Um die unkomplizierte Austauschbarkeit dieses Moduls zu gewährleisten, wurde die Schnittstelle auf die Funktion Builder::build beschränkt.
CGI
(in Datei cgi.tcl)
Ein Adapter zu den CGI-orientierten Funktionen in xml2html. Notfalls können die benötigten Funktionalitäten zum Extrahieren von CGI-Parametern hier nochmal überschrieben werden.
ComplexInput
(in Datei complexInput.tcl)
Erzeugt aus Listen von Name-Wert-Paaren komplexere HTML-Formular-Eingabeelemente wie
  • List-Boxen
  • Combo-Boxen
  • Gruppen von Check-Boxen
  • Gruppen von Radio-Buttons
  • Gruppen von normalen Formular-Buttons
  • Gruppen von versteckten Elementen
Dieses Modul erzeugt als einziges fertiges HTML - die Ausgaben aller anderen Module müssen noch durch xml2html geparst werden.
DBOperations
(in Datei dbOperations.tcl)

Schnittstelle des Programms zu der Datenbank. Alle Datenbankoperationen werden von diesem Modul durchgeführt. Dabei stellt die Schnittstelle aber keine Funktionalität zum Einfügen, Lesen oder Löschen eines Datensatzes nach außen zur Verfügung, sondern nur aufgabenbezogene Funktionen, etwas das Bearbeiten, Kopieren, Verschieben oder Löschen eines Fragebogenelementes.

Alle Low-Level-Funktionalitäten zum Zugriff auf die Datenbank, etwa um einzelne Datensätze zu modifizieren, sowie Adapterfunktionen für die dbc-Datenbankschnittstelle bleiben von Außen unerreichbar gekapselt.

Element
(in Datei element.tcl)
Generiert alle Eingabeformulare, über die einzelne Fragebogen-Elemente verwaltet werden, also zum Beispiel zum Erstellen, Bearbeiten, Kopieren, Verschieben und Löschen von einer Frage oder einem anderen Element.
Error
(in Datei error.tcl)
Generiert Standard-Fehlermeldungen für die häufigsten Fälle.
Form
(in Datei form.tcl)
Generiert alle Eingabeformulare, über die komplette Fragebögen verwaltet werden, also zum Beispiel zum Erstellen, Bearbeiten, Kopieren, Umbenennen und Löschen von einem Fragebogen.
Generator
(in Datei generator.tcl)
Hauptmodul, verteilt die ankommenden Anfragen auf die anderen Module, und zwar je nach Aufgabengebiet. Stellt außerdem generelle Funktionalität zum Debugging und Logging zur Verfügung und generiert die Startseite.
TableInfo
(in Datei tableinfo.tcl)
Modul ohne eigenen Namensraum, welches lediglich dazu dient, eine Map zur Übersetzung der symbolischen Attributname in tatsächlich in der Tabelle formdata verwandten Attributnamen zur Verfügung zu stellen. Um diese Daten leicht anpassen zu können, wurden sie in ein eigenes Modul ausgelagert.
Util
(in Datei util.tcl)
Stellt allgemeine Funktionalitäten zur Verfügung, etwa zum Überprüfen häufig vorkommender Wert-Typen (Datum, URL, Integer in Strings) und zum Traversieren und Bearbeiten von Listen.

Das Definieren eines durchgängigen Seiten-Layouts und Zusammenfassen von häufig wiederkehrenden HTML-Konstrukten wird durch die Erzeugung von HTML-Code über xml2html und die Definition eigener Tags in den Style-Files ermöglicht:

Style-File Funktion:
Basic
(in Datei basic.style)
Stellt einfache und häufig benötigte Tags für Standardfunktionen wie Links, Timestamps und Dokument-Abschnitte zur Verfügung.
Body
(in Datei body.style)
Stellt Tags zur Verfügung, um ein einheitliches Layout des textuellen Inhaltes einer Seite sicher zu stellen, etwa in Tabellenform mit der Navigationsleiste links und dem eigentlichen Inhalt auf der rechten Seite.
Form
(in Datei form.style)
Stellt Tags für HTML-Formulare und komplexe Eingabe-Elemente zur Verfügung, wie etwa
  • List-Boxen
  • Combo-Boxen
  • Gruppen von Check-Boxen
  • Gruppen von Radio-Buttons
  • Gruppen von normalen Formular-Buttons
  • Gruppen von versteckten Elementen
Da zum Generieren dieser HTML-Formular-Elemente häufig Listen von Eingabewerten iterativ verarbeitet werden müssen, deligiert das Style-File diese Arbeit an das TCL-Modul ComplexInput, welches in TCL-Prozeduren das Erzeugen des eigentlichen HTML-Codes übernimmt.
Generator
(in Datei generator.style)
Hat nur die Funktion, aus dem TCL-Modul Generator die Hauptfunktion Generator::generate_Page aufzurufen, um damit die Verarbeitung (mit abschließendem Erzeugen einer HTML-Seite) zu starten.
Head
(in Datei head.style)
Stellt Tags für ein defaultmäßiges Erzeugen des Kopfes einer HTML-Seite zur Verfügung.
Page
(in Datei page.style)
Stellt Tags zum einfachen Erzeugen ganzer HTML-Seiten zur Verfügung, die ihrerseits auf die in den Style-Files Body und Head definierten Tags zurückgreifen.
Public Form
(in Datei publicForm.style)
Stellt Tags zum Generieren der Fragebögen aus der Datenbank zur Verfügung.
Static Input
(in Datei staticInput.style)
Stellt Tags für häufig vorkommende Eingabe-Elemente zur Verfügung, etwa für die Aktionsmenüs beim Bearbeiten der Fragebögen.
Template Input
(in Datei templateInput.style)
Stellt parametrisierbare Tags für das Bearbeiten einzelner Fragebogenelemente zur Verfügung.
 
       


Generiert am 2001-07-22 um 20:21:29.