![]() Home Dokumentation |
Einführungskurs:
CGI-Programmierung mit der tdbengine
Die Lektionen des Kurses:
Groß-/Kleinschreibung
von Standardbezeichnern
Im letzten Teil haben wir die Basiselemente der Programmiersprache EASY kennengelernt. Wir können nun Prozeduren schreiben, Variablen und Parameter einsetzen und Anweisungen anwenden. In diesem Teil werfen wir zunächst einen Blick in die Standardbibliothek von EASY. In der zweiten Hälfte geht es ganz speziell um Textdateien und die Standardfunktionen zu deren Bearbeitung. Damit schaffen wir die Grundlagen, um in der nächsten Folge die Datenbankfunktionen von EASY kennenzulernen. Groß-/Kleinschreibung von Standardbezeichnern Bei allen Standardbezeichnern ist die Groß-/Kleinschreibung egal: TIMESTR, timestr, TimeStr... In diesem Kurs verwenden wir verschiedene Schreibweisen. In den meisten Fällen hat sich eine Mischform wie »TimeStr« als besonders übersichtlich herausgestellt. Auf den folgenden Seiten, wenn wir die einzelnen Funktionen vorstellen, verwenden wir jedoch erst einmal nur Großschreibung. Manchmal muss ein Programm rechnen. Wir haben in der letzten
Folge bereits die mathematischen Operatoren kennengelernt:
In Zahlenkonstanten wird das Komma immer durch einen Dezimalpunkt dargestellt. Die Standardbibliothek bietet u.a. folgende Funktionen
für Zahlen:
Die wichtigste Funktion daraus im Zusammenhang mit Datenbanken dürfte wohl ROUND sein. Vor allem wenn es sich um Geldbeträge handelt, ist auf eine rechtskonforme Rundung zu achten: ROUND(0.15,1) = 0.2
So sollten Sie z.B. bei Euro-Umrechnungen möglichst spät runden, bei der Aufsummierung des Steueranteils einzelner Posten in einem Online-Shop wesentlich früher (bei jedem Posten). Aus der Fülle der Stringfunktionen wollen wir hier
nur die wichtigsten vorstellen. Den Teilstring-Operator haben Sie ja schon
in der letzten Folge kennengelernt.
Um eine Zahl in eine Zeichenette zu konvertieren, gibt
es die Funktion STR
Beispiele:
Es gibt natürlich auch eine Möglichkeit, eine
Zeichenette in eine Zahl zu konvertieren. Dafür ist die Funktion VAL
zuständig:
Achtung: Wenn es sich bei s um eine illegale Zahlenkonstante handelt, wird ein Laufzeitfehler ausgelöst, das Programm also abgebrochen. Dieses Vorgehen ist durchaus sinnvoll, da offenbar keine vernünftigen Werte vorliegen. Auf der anderen Seite ist es selbstverständlich lästig, wenn ein Programm kommentarlos abgebrochen wird, nur weil irgendein Anwender in eine Eingabefeld etwas falsches einträgt. Hier kommt zum ersten Mal die Fehlerbehandlung in das Spiel. EASY bietet eine Reihe von »Schaltern«, mit
dem sich das Verhalten des Systems steuern lässt. Diese Schalter werden
entweder als sogenannte Punktkommandos oder über die Funktion SETPARA
gesetzt.
Schalter sind wiederum Zeichenketten, die so aufgebaut sind: Kürzel Wert Folgendes Kürzel sind in unserem Zusammenhang interessant:
Mit dem Wert 1 wird die Fehlerbehandlung durch das Programm eingeschaltet, mit dem Wert 0 wieder ausgeschaltet. Um also die Fehlerbehandlung durch das System zu unterbinden, wird folgender Funktionsaufruf eingesetzt: SETPARA('EC 1') Eine typische Parameterauswertung mittels VAL schaut demnach
so aus:
Jetzt wird kein Laufzeitfehler mehr ausgelöst, wenn in der CGI-Variablen »Wert« eine illegale Zahl übergeben wird. Woran erkennen Sie jetzt aber, ob eine korrekte Zahl vorlag? Die Variable x auf Null zu prüfen ist recht sinnlos, denn schließlich kann der Anwender ja auch die Zahl Null eigeben. Der Schlüssel liegt in der Debug-Funktion TDB_LASTERROR
Wenn nach dem Aufruf von x:=VAL... die Funktion TDB_LASTERROR
den Wert 0 liefert, war alles in Ordnung. Andernfalls können Sie aus
dem Wert die Fehlerart bestimmen.
Hinweis: Es gibt Programmierer, die die Fehlerbehandlung durch die tdbengine am Programmanfang aus- und niemals mehr einschalten. Das ist zwar bequem, aber eigentlich nicht zu verantworten und führt oftmals zu fehlerhaften Datenbeständen. Darum der Tipp: Übernehmen Sie die Fehlerbehandlung mit »SetPara('EC 1')« nur dann, wenn Sie sie auch wirklich übernehmen (klingt tautologisch, ist es aber nicht). Schalten Sie die Fehlerbehandlung wieder ab, wenn die durch Sie geprüfte Operation abgeschlossen ist. Es ist allemal besser, wenn ein Programm mit einem Laufzeitfehler abgebrochen wird, als wenn es richtiggehend »Mist baut«. Ein Datum der Form »Tag.Monat.Jahr« wird von EASY immer in eine einzige Zahl umgerechnet, die die Anzahl der Tage seit dem 1.1.0 angibt. Damit können wir mit Datumsangaben ganz einfach rechnen. So ergibt beispielsweise die Differenz aus zwei Datumsangaben genau die Anzahl der Tage dazwischen. Damit man sich das Ergebnis einer Berechnung wieder in
der gewohnten Form anzeigen lassen kann, verwenden man die Funtion DATESTR:
Das Systemdatum erhalten Sie mit der Funktion TODAY:
Somit erhalten wir das Systemdatum als Zeichenkette mit: DATESTR(TODAY) Ähnlich verhält es sich mit Zeitangaben. Hier
rechnet EASY eine Zeitangabe in Minuten seit Mitternacht um.
Die Funktion, um eine Zeitangabe in der gewohnten Form
anzuzeigen, heisst TIMESTR.
Es gibt aber auch eine erweiterte Form dieser Funktion:
Und weil die Auflösung der Systemzeit noch wesentlich
genauer ist (1/100 Sekunde), können Sie in TIMESTR als zweiten Parameter
die Sekunden-Nachkommastellen festlegen. Beispiel:
Den Wochentag eines Datums erhalten Sie mit DAYOFWEEK
Beispiel: DAYOFWEEK(TODAY) = "Sonntag" Unter System-Funktionen versteht man solche, die direkt auf das Betriebssystem zugreifen, wie beispielsweise das Wechseln in ein anderes Verzeichnis oder das Löschen von Dateien. Zunächst wollen wir die Verzeichnis-Funktionen kennenlernen:
Alle drei Funktionen liefern als Ergebnis eine Zahl, die den Fehlercode des jeweiligen Betriebssystems repräsentiert. Der Wert 0 bedeutet dabei immer, dass die jeweilige Funktion ausgeführt werden konnte. Es ist wichtig, das Ergebnis der Funktionen zu prüfen, denn wenn beispielsweise ein CHDIR fehlschlägt und Sie dann dennoch irgendwelche Dateien anlegen, landen diese in einem völlig falschen Verzeichnis. Mit den folgenden beiden Funktionen können Sie ein
Verzeichnis durchsuchen:
So einfach diese Funktionen zunächst aussehen, so
komplex sind die Ergebnisse. Der erste Parameter von FIRSTDIR ist ein Suchmuster,
wie Sie es auch Ihrer Shell mit »ls« oder »dir«
übergeben. Sie dürfen hier auch die Joker »*« und
»?« verwenden. In einer der folgenden Versionen werden hier
sogar reguläre Ausdrücke erlaubt sein. Im zweiten Parameter geben
Sie sie Dateiarten an, die zusätzlich(!) zu normalen Dateien gesucht
werden sollen. Die Dateiarten geben Sie als Zeichenkette an, die aus folgenden
Buchstaben bestehen kann:
*) Unter Linux werden die Attribute derzeit nicht ausgewertet. Es werden immer alle Dateien gesucht. Ein Aufruf von FIRSTDIR könnte demnach so aussehen: FIRSTDIR('/home/tdbengine/*.mod','D') Damit würden alle normalen Dateien und Directories im Verzeichnis »/home/tdbengine« gesucht. Der Rückgabewert von FIRSTDIR ist entweder leer (also
''), wenn kein passender Verzeichniseintrag gefunden wurde, oder ein recht
langer String (für den ersten gefundenen Verzeichniseintrag), der
aus folgenden Komponenten besteht:
Die Funktion NEXTDIR liefert den nächsten Verzeichniseintrag im gleichen Format, oder aber einen Leerstring, wenn kein Eintrag mehr vorhanden ist. Wir wollen an dieser Stelle ein kleines Programm schreiben, das uns den Inhalt des aktuellen CGI-Verzeichnisses liefert. Wir wollen das Programm »scandir.mod« nennen.
Starten Sie also Ihre tdbengine-Entwicklungsumgebung mit http://localhost/cgi-tdb/pdk.prg,
geben als Dateinamen »scandir.mod« ein und schreiben Sie in
das Feld für en Quelltext:
Wenn Sie dieses Programm ausführen, erhalten Sie
eine imposante und zumindest recht breite Liste der Verzeichniseinträge.
Wir wollen deshalb die Ausgabe ein wenig übersichtlicher gestalten.
Dazu schreiben wir eine eigene Prozedur, die einen Verzeichniseintrag ausgibt.
Diese macht recht ausgiebigen Gebrauch vom Teilstring-Operator, den wir
schon in der letzten Folge kennengelernt haben. An einer Stelle finden
Sie die String-Funktion RTRIM, mit der die rechten Leerzeichen eines Strings
abgeschnitten werden. In der Hauptprozedur wird dann der bisherige Aufruf
zum Schreiben eines Verzeichniseintrags durch einen Aufruf unserer neuen
Prozedur ersetzt:
Diese Programm arbeitet schon wesentlich übersichtlicher. Im nächsten Schritt wollen wir aber noch eine kleine Erweiterung einführen: Das Suchmuster unseres Verzeihnisses soll als CGI-Variable »dir« in der URL mit angegeben werden. Als Vorgabewert, also wenn nichts angegeben wurde, soll das aktuelle Verzeichnis verwendet werden. Hier müssen wir nur eine kleine Modifikation in der Prozedur »Main«
vornehmen:
Schließlich wollen wir unser kleines Programm um eine ganz typische HTML-CGI-Komponente erweitern: Wir wollen aus den angezeigten Verzeichnissen aktive Links machen, mit denen wird uns den Inhalt des jeweligen Verzeichnisses anzeigen lassen können. Ein solcher Link müsste folgendermaßen aussehen: <a href="/cgi-tdb/scandir.prg?dir=...">...</a> Bei der Bestimmung des Verzeichnisses, in dem gesucht werden soll, greifen wir auf das absolute Verzeichnis zurück: rtrim(entry[129,127]) Ob es sich um Verzeichnis handelt, erfahren wir aus den Attributen: IF pos('d',lower(d_attr)) ... Hinweis: Die Funktion »LOWER« wandelt einen
String komplett in Kleichbuchstaben um.
Wenn Sie diese Funktion in unser Programm einsetzen, haben Sie einen recht schönen Verzeichnis-Browser (allerdings mit einem kleinen Schönheitsfehler - welchen?). Hinweis: Ein Aufruf von FIRSTDIR liest bereits das gesamte Verzeichnis in eine interne Liste, auf die dann NEXTDIR zugreift. Aus diesem Grund belegt FIRSTDIR keine(!) Systemressourcen. Die letzte Verzeichnisfunktion, die wir hier vorstellen
wollen, ist GETDIR:
Laufwerk 0 = aktuelles Laufwerk 1 = A:
Unter Linux ist nur 0 erlaubt (hier gibt es keine Laufwerke). Die verfügbare Plattenkapazität erfahren Sie
mit DISKFREE
Widerum ist unter Linux nur das Laufwerk 0 erlaubt. Wenn Sie die verfügbare Kapazität einer Partition erfahren möchten, müssen Sie vorher in ein Verzeichnis dieser Partition wechseln. Leider kommt DISKFREE in der aktuellen Version der tdbengine noch nicht mit Kapatitäten über 2 Gbyte zurecht. Das wird sich aber in einer Folgeversion ändern. Wenn Sie beim Löschen eines Verzeichnisses mit RMDIR
einen Fehler erhalten, kann das zum Beispiel daran liegen, dass das Verzeichnis
nicht leer ist. Zum Löschen einer einzelnen Datei verwendet man DELFILE,
zum Umbenennen RENAME:
Beide Funktionen liefern wieder der Fehlercode des Betriebssystems. O bedeutet, dass die Funktion erfolgreich ausgeführt wurde. Die Größe einer Datei erhalten Sie mit GETSIZE
Hier zeigt der Wert -1 an, dass die Datei nicht gefunden wurde. Unter Linux gilt wiederum, dass Dateien mit einer Größe jenseits 2 Gbyte zu falschen Werten führen. Schließlich müssen oft Kopien von Dateien erzeugt
werden. Dafür ist die Funktion COPYFILE zuständig:
Besonderheit: COPYFILE kann auch mit Ramtexten (siehe
unten) und der Standardausgabe umgehen.
Bevor wir (endlich) zu den Datenbank-Funktionen gelangen, müssen wir noch einen Blick auf die wesentlich einfacheren Textdateien werfen. Eine Textdatei enthält im Gegensatz zu einer Binärdatei nur druckbaren Text. Sie kann auf Shell-Ebene mit den Standardwekrzeugen (wie »type«, »more«, »cat« etc.) angesehen und mit sogenanten Texteditoren bearbeitet werden. Hier ein paar Beispiele für (im CGI-Bereich) häufig eingesetzte Textdateien:
externe, die auf einem Datenträger vorliegen,
und
Nachdem die internen Texte ausschließlich im Arbeitsspeicher der Computers - also im RAM - vorliegen, werden Sie auch Ramtexte genannt. Ein solcher Ramtext liegt immer dann vor, wenn der Dateiname »ramtext« lautet oder mit »ramtext:« beginnt. Der Vorteil von Ramtexten liegt darin, dass sie systembedingt wesentlich schneller bearbeitet werden können als externe Textdateien. Zudem gibt es einige Funktionen (wie SUBST), die nur mit Ramtexten arbeiten. Öffnen
und Schließen von Textdateien
Alle diese Funktionen liefern entweder 0, wenn die Datei nicht geöffnet werden konnte (hier wird zudem ein Laufzeitfehler ausgelöst), bzw. einen Texthandle (= eine Zahl), über den in der Folge auf die Textdatei zugegriffen werden kann. Mit CLOSE wird eine Textdatei wieder geschlossen
Beispiel: VARDEF text : REAL
Lesen und Schreiben in Textdateien Mit den beiden Funktionen READ und READLN kann aus einer
Textdatei gelesen werden:
Damit nicht über das Ende einer Datei hinausgelesen
wird, gibt die Funktion EOF Aufschluss darüber, ob das Dateiende bereits
erreicht ist:
Somit schaut ein Programmfragment, das einen Text komplett
zeilenweise ausliest, so aus:
Wie bereits in einer früheren Lektion erwähnt wurde, legt die tdbengine bei einer CGI-Variablen, deren Name mit »text:« beginnt, einen Ramtext gleichen Namens an. Beispiel: In einem HTML-Forumlar steht folgender Code:
Der über dieses Formular übertragene Inhalt
der Textarea kann dann so ausgelesen werden:
Zum Schreiben in eine Textdatei muss diese entsprechend
geöffnet werden. Anchliessend kann man mit WRITE und WRITELN in die
Textdatei schreiben:
Beispiel:
Hinweis zur READLN und WRITELN: WRITELN schreibt immer CHR(13)+CHR(10) in die Textdatei.
Unter Unix-Betriebssystemen ist es jedoch üblich, nur CHR(10) für
den Zeilenumbruch zu verwenden. Wenn also Textdateien zur Weiterverarbeitung
in dieser Plattform angelegt werden sollen, sollte man eine eigene Prozedur
schreiben:
Die Funktion READLN kommt mit beiden EOL-Konventionen zurecht. Zu den speziellen Textdateien wollen wir Konfigurationsdateien und Templates zählen. Die tdbengine unterstützt Konfigurationsdateien im folgenden Stil:
Jeder Eintrag kann aus bis zu 255 Zeichen bestehen. Zur Bearbeitung von derartigen Dateien stellt EASY zwei
Funktionen zur Verfügung: SETIDENT und GETIDENT.
Einträge werden dabei in Form »Gruppe.Eintrag« angegeben. Beispiel:
Dieses kleine Programm legt die Datei »test.ini«
mit folgendem Inhalt an:
Anmerkung: Die erzeugte Datei wird dann auch noch auf dem Bildschirm ausgegeben. Wichtig ist hier der Einsatz von »CGICLOSEBUFFER«, damit der interne Puffer vor dem Kopiervorgang ausgegeben wird. Mit GETIDENT können die einzelnen Einträge ausgelesen werden. Beispiel: GETIDENT('test.ini','Administrator.Name) -> 'Hans Huber'
Hinweis: Beim ersten Zugriff auf eine Konfigurationsdatei wird diese komplett in den Arbeitsspeicher des Computers gelesen und über eine Baumstruktur dem Programm zur Verfügung gestellt. Deshalb ist der Zugriff extrem schnell. Wir können in diesem Einführungskurs die Möglichkeiten von Konfigurationsdateien nur anreissen. Sie ersetzen in vielen Fällen Datenbanken! In einem Folgekurs werden wir ausführlich auf diese Thematik eingehen. Den zweiten Sonderfall von Textdateien stellen die sogenannten Templates dar. Darunter verstehen wir (im Zusammenhang mit der CGI-Programmierung) HTML-Schablonen. Diese können von HTML-Spezialisten erstellt werden, wodurch sich eine recht sinnvolle Arbeitsteilung ergeben kann. Das Programm liest also ein Template, setzt die Ergebnisse von Berechnungen (meist Datenbankinhalte) ein und schickt es derart ausgefüllt dann an den Klienten. Wir wollen in diesem Grundkurs nur folgende Funktionen
zur Template-Bearbeitung betrachten:
LOADTEMPLATE und CGIWRITETEMPLATE arbeiten grundsätzlich mit »ramtext«, einer internen Textdatei, die wir schon weiter oben kennengelernt haben. Sie liefern den Fehlercode des Betriebssystems, also wieder 0, wenn alles in Ordnung ist. Die Funktion SUBST gehört zur Gattung der Universalfunktionen. Sie ist mehrfach überladen, das bedeutet, sie kann mit recht unterschiedlichen Parametern aufgerufen werden. Hier die einfachste Form: SUBST(target,string) ersetzt das erste Vorkommen von target durch string Beispiel: Wir haben ein Template, in das wir das aktuelle
Datum und die aktuelle Uhrzeit (Serverzeit) einfügen wollen. Im Template
steht dazu folgender Text:
(die Einfassung von Targets mit dem '#'-Zeichen hat sich als recht vorteilhaft herausgestellt, weil hierbei die Verwechslungsgefahr am geringsten ist) Das Template sei unter »templates/meine_seite.html«
gespeichert. Das folgende kleine Programm löst dann unsere Aufgabe:
Wenn der einzufügende String mit »extern:« beginnt, wird nicht etwa dieser String eingefügt, sondern der Rest des Strings wird als Pfad zu einer Textdatei interpretiert und diese ersetzt dann das Target. Entsprechend verweist ein String, der mit »ramtext:« beginnt, auf eine interne Textdatei. Gerade, wenn das Target durch ganze Textdateien ersetzt
werden sollen, wird die Art der Ersetzung wichtig: Was passiert mit Zeilenumbrüchen?
In welchem Zeichensatz soll die Ersetzung durchgeführt werden? Das
kann mit einem weiteren Parameter festgelegt werden, dem »Modus«.
Diese Modi können auch noch addiert werden, um das gewünschte Ziel zu erreichen. So bedeutet beispieweise der Modus 5 (=1+4), dass bei der Ersetzung eine Konvertierung nach HTML stattfindet und die harten Zeilenumbrüche durch »<br>« ersetzt werden. Mit diesem Knowhow wollen wir zum Abschluss dieser Lektion eine geradezu klassische CGI-Aufgabe angehen. Der Anwender soll ein Formular ausfüllen. Die Bearbeitung des Formulars erfolgt in einem CGI-Programm. Wenn aber bestimmte Bedingungen nicht erfüllt sind (weil beispielsweise bestimmte Felder nicht ausgefüllt sind), soll das (teilweise ausgefüllte) Formular an der Klienten zurückgeschickt werden (mit einer entsprechenden Fehlermeldung). Das Formular soll (für unsere spätere Adressendatenbank)
eine Adresse aufnehmen mit fogenden Feldern:
Dieses Formular legen wir unter »templates/adressen_eingabe.html«
ab. Hier der einfache HTML-Text:
Während der Platzhalter #fehlermeldung# noch relativ einleuchten dürfte (hier werden Meldungen angezeigt ), scheint der Platzhalter #action# im Form-Tag zunächst recht seltsam. Doch die Erklärung hierfür ist einfach: Damit machen wir das Template programmunabhängig. Würden wir bei »<form action=...« eine feste URL eintragen, so müssten wir diese immer wieder austauschen, wenn das Template von einem anderen Programm (und damit mit einer anderen URL) verwendet wird. Das Programm wird also den Platzhalter #action# durch seine eigene URL ersetzen. Und das geht am elegantesten mit der Standardfunktion PARAMSTR. Diese Funktion liefert eigentlich die Parameter, mit denen die tdbengine an der Konsole aufgerufen wird: /home/tdbengine/bin/tdbengine test.prg parameter_a parameter_b PARAMSTR(1) = 'test.prg'
Das Argument 0 liefert immer den Aufruf selbst, bei der Konsolenanwendung also '/home/tdbengine/bin/tdbengine' Wenn jedoch die tdbengine als CGI-Interpreter eingesetzt wird, liefert PARAMSTR(0) den lokalen Teil der URL, mit der das CGI-Programm gestartet wird. Ist die URL beispielsweise http://www.tdb-engine.de/cgi-tdb/meinprogramm.prg dann liefert in diesem Programm PARAMSTR(0) '/cgi-tdb/meinprogramm.prg' Somit reicht es, wenn wir den Platzhalter #action# durch das Ergebnis von PARAMSTR(0) ersetzen, Protokoll und Domain werden vom Browser automatisch ergänzt. Unser Programm zur Auswertung des Formulars soll so aufgebaut
sein:
Diesen Algorithmus, so nennt man einen solchen Ablaufplan, wollen wir nun in ein EASY-Programm umsetzen. Die erste Zeile »hole die übertragenen Werte
für Name, Vorname etc.« setzt voraus, dass wir für diese
Informationen Variablen anlegen müssen. Solche Variablen legt man
meist global (also für das gesamte Programm sichtbar) an.
Eine Prozedur, die diese Informationen vom Klienten holt,
schaut nun so aus:
Auch die Frage, ob die bisher vom Klienten gelieferten
Informationen genügen, wollen wir eine einer eigenen (Funktions-)Prozedur
klären:
Wir verwenden hier das Komma als Abkürzung für AND, damit die Bedingung noch in eine Zeile passt. Für das Laden des Templates brauchen wir keine eigene
Prozedur, wohl aber für das Ausfüllen desselben.
Den Modus 1 wählen wir, weil die Information im HTML-Zeichensatz ersetzt werden müssen. Die weitere Bearbeitung der Informationen (Eintrag in eine Datenbank) müssen wir bis zur nächsten Lektion aufschieben. Hier wollen wir nur die Zeile 'Ihre Informationen werden bearbeitet' ausgeben. Somit könnte das CGI-Programm (bzw. dessen Main-Prozedur)
nun so formuliert werden:
In dieser Lektion haben wir einen Blick auf die wichtigsten Standardfunktionen von EASY geworfen. Wir haben Textdateien und deren Bearbeitung kennengelernt. Schliesslich haben wir anhand eines praktischen Beispiels den Einsatz von Templates geübt. In der nächsten Folge geht es dann ganz speziell um Datenbankfunktionen.
Vorname=... usw. |