|
1. Entwicklungskonfiguration
2. Problemanalyse und Realisation
2.1. Aufgabenstellung
2.2. Terminologie und Abkürzungen
2.3. Problemanalyse
2.4. Realisation
2.5. Änderungen und Erweiterungen
3. Beschreibung grundlegender Datenstrukturen
4. Beschreibung der Module
|
1. Entwicklungskonfiguration
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:
- Das Erzeugen eines Eingabeformulars, um die Aktion den Wünschen und
Umständen entsprechend parametrisieren zu können. (Schritt 1)
- 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:
- Definition des Programmzustands durch URLs
- Definition des Programmzustands durch CGI-Parameter
- 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:
- Vergabe einer Session-ID nach dem Log-In
- 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.
|
|
|