Deutsch English
Home
About tdbengine
Newsletter
Download
Helpware
Forum
Chat
Documentation
Basic Course
Basics
Programmierumgebung
CGI Aufbereitung
EASY Programmierung
Standard-Bibliothek
Die Datenbank
HTML-Formulare
Function reference
HOWTO...?
Projects
Links
Benchmarks
Bug Reporting
Support request
 
Home    Overview    Search    Impressum    Contact    Members
Lesson 5: The standard library of EASY
In the last part we learned the basic elements of the program languege EASY. Now we can write proceduresWir 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.
Zahlen-Funktionen
Manchmal muss ein Programm rechnen. Wir haben in der letzten Folge bereits die mathematischen Operatoren kennengelernt:
+ Summe
- Differenz
* Multiplikation
/ Division
DIV Ganzzahlige Division ohne Rest
MOD Rest bei ganzzahliger Division

In Zahlenkonstanten wird das Komma immer durch einen Dezimalpunkt dargestellt. Die Standardbibliothek bietet u.a. folgende Funktionen für Zahlen:

ABS(x) Absolutbetrag
COS(x) Cosinus
EXP(x) Exponentialfunktion (e hoch x)
FRAC(x) Gebrochener Anteil einer Zahl
INT(x) Ganzzahliger Anzeil einer Zahl
LOG(x) Natürlicher Logarithmus (zur Basis e)
RANDOM(x) Ganzzahlige Zufallszahl von 0 bis x-1
ROUND(x,n) Kaufmännische Rundung von x auf n Nachkommastellen
SIN(x) Sinus
SQRT(x) Quadratwurzel

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
ROUND(0.149999,1) = 0.1
So sollten Sie z.B. bei Währungs-Umrechnungen möglichst spät runden, bei der Aufsummierung des Steueranteils einzelner Posten in einem Online-Shop wesentlich früher (bei jedem Posten).

String-Funktionen
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.
EXCHANGE(s1,s2,s3) s1, nachdem alle Vorkommen von s2 durch s3 ersetzt wurden
LEFTSTR(s,n) Die ersten n Zeichen von s
LOWER(s) s in Kleinbuchstaben
LTRIM(s) s ohne führende Leerzeichen
POS(substr,s) Erstes Vorkommen (als Zeichenposition) von substr in s
RIGHTSTR(s,n) Die letzten n Zeichen von s
RTRIM(s) s ohne Leerzeichen am Ende
UPPER(s) s in Grossbuchstaben

Zahl/String-Konvertierung
Um eine Zahl in eine Zeichenette zu konvertieren, gibt es die Funktion Str()
STR(x) Zahl als Zeichenkette (auf ganze Zahl gerundet)
STR(x,s) Wie oben, aber mit insgesamt s Zeichen
STR(x,s,n) Wie zuvor aber mit n Nachkommastellen
STR(x,s,n,t) Wie zuvor und mit Tausendertrennungzeichen t
STR(x,s,n,t,f) Wie zuvor, aber linke Leerstellen werden durch das Füllzeichen f ersetzt
STR(x,s,n,t,f,k) Wie oben, aber statt Dezimalkomma wird das Zeichen k eingesetzt

Beispiele:

STR(123.456) "123"
STR(123.456,10) "       123"
STR(123.456,10,2) "    123,46"
STR(123456,1,0,'.') "123.456"
STR(123456,1,2,'.') "123.456,00"
STR(123456,13,2,' ','_','.') "___123 456.00"

Es gibt natürlich auch eine Möglichkeit, eine ZeichenKette in eine Zahl zu konvertieren. Dafür ist die Funktion Val() zuständig:

VAL(s) Zeichenkette als Zahl

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 über die Funktion SetPara() gesetzt.

SETPARA(Schalter) Systemschalter setzen

Schalter sind wiederum Zeichenketten, die so aufgebaut sind: Kürzel Wert

Folgendes Kürzel ist in unserem Zusammenhang interessant:

EC Error Check (Fehlerbehandlung durch Programm)

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:

SetPara('EC 1')
x:=Val(CGIGetParam('Wert'))

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

TDB_LASTERROR Fehlercode der letzten Operation

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.

SetPara('EC 1')
x:=Val(CGIGetParam('Wert'))
SetPara('EC 0')
IF TDB_LastError<>0 THEN
  CGIWriteLn('Es ist ein Fehler aufgetreten')
ELSE
  CGIWriteLn('hier ist alles IN Ordnung')
END

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«.

Datums- und Zeitfunktionen
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.1900 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, verwendet man die Funtion DateStr():
DATESTR(x) Zeichenkette der Form "Tag.Monat.Jahr"

Das Systemdatum erhalten Sie mit der Funktion TODAY:

TODAY Systemdatum

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.

NOW Systemzeit (in Minuten seit Mitternacht)

Die Funktion, um eine Zeitangabe in der gewohnten Form anzuzeigen, heisst TimeStr().

TIMESTR(x) Zeichenkette der Form "Stunden:Minuten"

Es gibt aber auch eine erweiterte Form dieser Funktion:

TIMESTR(x,0) Zeichenkette de Form "Stunden:Minuten:Sekunden"

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:

TIMESTR(NOW,2) "12:14:08.26"

Den Wochentag eines Datums erhalten Sie mit DayOfWeek()

DAYOFWEEK(x) Wochentag als (deutsche) Zeichenkette

Beispiel: DayOfWeek(Today) = "Sonntag"

System-Funktionen
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.

Verzeichnis-Funktionen

Zunächst wollen wir die Verzeichnis-Funktionen kennenlernen:

MAKEDIR(s) legt das Verzeichnis s an
REMDIR(s) löscht das Verzeichnis s
CHDIR(s) wechselt in das Verzeichnis s

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:

FIRSTDIR(Suchmuster,Attribute) Erster Verzeichniseintrag
NEXTDIR Nächster Verzeichniseintrag

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:

D Verzeichnisse
H Versteckte Dateien
S Systemdateien

*) 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:

Zeichen Inhalt
1 bis 63 Dateiname
64 bis 70 Attribute (d = Directory ... )
71 bis 82 Dateigrösse
84 bis 94 Datum der letzten Änderung
96 bis 108 Zeit der letzten Änderung
110 bis 127 Zugriffsrechte (dereit nur unter Linux)
128 bis 255 Verzeichnis absolut

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 den Quelltext:

PROCEDURE Main
VARDEF s_dir : STRING
  CGIWriteLn('content-type: text/html') // Header
  CGIWriteLn('') // Ende Header
  CGIWrite('<pre>') // keine HTML-Formatierung
  s_dir:=FirstDir('*','D')
  WHILE s_dir<>'' DO
    CGIWriteLn(s_dir)
    s_dir:=NextDir
  END
  CGIWriteLn('</pre>')
ENDPROC

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:

PROCEDURE WriteDirEntry(entry : STRING)
VAR d_name, d_attr, d_size, d_date, d_time : STRING
  d_name:=ToHTML(RTrim(entry[1,63]))
  d_attr:=entry[64,7]
  d_size:=entry[71,12]
  d_date:=entry[84,10]
  d_time:=entry[96,8]
  CGIWriteLn(d_attr+' '+d_size+d_date+' '+d_time+' '+d_name)
ENDPROC


PROCEDURE Main
VARDEF s_dir : STRING
  CGIWriteLn('content-type: text/html') // Header
  CGIWriteLn('') // Ende Header
  CGIWrite('<pre>') // keine HTML-Formatierung
  s_dir:=FirstDir('*','D')
  WHILE s_dir<>'' DO
    WriteDirEntry(s_dir)
    s_dir:=NextDir
  END
  CGIWriteLn('</pre>')
ENDPROC

Diese Programm arbeitet schon wesentlich übersichtlicher. Im nächsten Schritt wollen wir aber noch eine kleine Erweiterung einführen: Das Suchmuster unseres Verzeichnisses 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:
PROCEDURE Main
VARDEF s_dir, s_pattern : STRING
  CGIWriteLn('content-type: text/html') // Header
  CGIWriteLn('') // Ende Header
  s_pattern:=GetQueryString('dir') // Parameter holen
  IF s_pattern='' THEN s_pattern:='*' END
  CGIWrite('<pre>') // keine HTML-Formatierung
  s_dir:=FirstDir(s_pattern,'D')
  WHILE s_dir<>'' DO
    WriteDirEntry(s_dir)
    s_dir:=NextDir
  END
  CGIWriteLn('</pre>')
ENDPROC


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.

PROCEDURE WriteDirEntry(entry : STRING)
VARDEF d_name, d_attr, d_size, d_date, d_time : STRING
  d_name:=tohtaml(RTrim(entry[1,63]))
  d_attr:=entry[64,7]
  d_size:=entry[71,12]
  d_date:=entry[84,10]
  d_time:=entry[96,8]
  IF Pos('d',Lower(d_attr)) THEN
    CGIWrite('<a href="/cgi-tdb/scandir.prg')
    CGIWrite('?dir='+rtrim(entry[129,127])+d_name+'/*">')
  END
  CGIWrite(d_attr+' '+d_size+d_date+' '+d_time+' '+d_name)
  IF Pos('d',Lower(d_attr)) THEN CGIWrite('</a>') END
  CGIWriteLn('')
ENDPROC

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():
GETDIR(Laufwerk) Aktuelles Verzeichnis im angegeben Laufwerk

Laufwerk 0 = aktuelles Laufwerk 1 = A:
2 = B:
... Unter Linux ist nur 0 erlaubt (hier gibt es keine Laufwerke).

Die verfügbare Plattenkapazität erfahren Sie mit DiskFree()

DISKFREE(Laufwerk) Verfügbare Plattenkapazität


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.

Allgemeine Datei-Funktionen

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():

DELFILE(Pfad) Löscht eine Datei
RENAME(Alter_Pfad, Neuer_Pfad) Benennt eine Datei um

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().

GETSIZE(Pfad) Dateigrösse ermitteln (in Bytes)

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:

COPYFILE(Pfad,Kopie) Kopiert Datei nach Kopie

Besonderheit: CopyFile() kann auch mit Ramtexten (siehe unten) und der Standardausgabe umgehen.

COPYFILE(Pfad,'con') Kopiert eine Datei in die Standardausgabe

Text-Funktionen
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 Standardwerkzeugen (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:
  • HTML-Seiten
  • Konfigurationsdateien
  • Templates (Schablonen für HTML-Seiten)

EASY kennt zwei Arten von Textdateien:

externe, die auf einem Datenträger vorliegen, und
interne, die nur Speicher und für die Dauer des Programmaublaufes vorliegen.

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

RESET() Textdatei zum Lesen öffnen
REWRITE() Textdatei zum Schreiben öffnen (neu anlegen)
TAPPEND() Textdatei zum Anhängen öfnen

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
CLOSE(Texthandle) Schliesst eine offene Textdatei


Beispiel:

VAR text : INTEGER
  ...
  text:=Reset('meinetextdatei.txt')
  ...
  Close(text)

Lesen und Schreiben in Textdateien

Mit den beiden Funktionen Read() und ReadLn() kann aus einer Textdatei gelesen werden:

READ(texthandle) liest ein Zeichen aus der Textdatei
READ(texthandl,anzahl|trennzeichen) liest anzahl Zeichen aus der Textdatei (max 255)
READLN(texthandle) liest eine Zeile aus der Textdatei (max 255 Zeichen)

Damit nicht über das Ende einer Datei hinausgelesen wird, gibt die Funktion EOF() Aufschluss darüber, ob das Dateiende bereits erreicht ist:
EOF(texthandle) 1 = Dateiende erreicht, 0 sonst

Somit schaut ein Programmfragment, das einen Text komplett zeilenweise ausliest, so aus:
VAR text : INTEGER
VAR zeile : STRING
...
  IF text:=Reset('meinetextdatei.txt') THEN
    WHILE NOT EOT(text) DO
      zeile:=ReadLn(text)
      ...
    END // hier ist EOT=1
    Close(text)
  ELSE
    // Textdatei konnte NICHT geöffnet werden
  END


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:

<form action="/cgi-tdb/auswertung.prg" method="post">
<textarea name="text:bemerkung"></textarea>
</form>

Der über dieses Formular übertragene Inhalt der Textarea kann dann so ausgelesen werden:

VAR text : INTEGER
VAR zeile : STRING
  ...
  IF text:=Reset('ramtext:text:bemerkung') THEN
    WHILE NOT EOT(text)
      ...

Zum Schreiben in eine Textdatei muss diese entsprechend geöffnet werden. Anschliessend kann man mit Write() und WriteLn() in die Textdatei schreiben:

WRITE(texthandle,string) schreibt den string in die Textdatei
WRITELN(texthandle,string) schreibt den string gefolgt von CR/LF in die Textdatei

Beispiel:
VAR text : INTEGER
  IF text:=Rewrite('textfiles/meintext.txt')<>0 THEN
    WriteLn(text,'Das ist die erste Zeile.')
    WriteLn(text,'Das ist die zweite Zeile')
    Write(text,'Das ist eine Zeile ohne Zeilenvorschub')
    Close(text)
  END

Hinweis zu 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:

PROCEDURE WriteLn(text : REAL; s : STRING)
  Write(text,s); Write(t,Chr(10))
ENDPROC


Die Funktion ReadLn() kommt mit beiden EOL-Konventionen zurecht.

Spezielle Textdateien
Zu den speziellen Textdateien wollen wir Konfigurationsdateien und Templates zählen.
Die tdbengine unterstützt Konfigurationsdateien im folgenden Stil:

Konfigurationsdateien

[Gruppe 1]
Eintrag1=...
Eintrag2=...
...
[Gruppe 2]
Eintrag1=...
Eintrag2=...
...

Jeder Eintrag kann aus bis zu 255 Zeichen bestehen.
Zur Bearbeitung von derartigen Dateien stellt EASY zwei Funktionen zur Verfügung: SetIdent() und GetIdent().
SETIDENT(Konfigurationsdatei,Eintrag,Wert) schreibt Eintrag=Wert in Konfigurationsdatei
GETIDENT(Konfigurationsdatei,Eintrag) liefert den zum Eintrag gehörenden Wert

Einträge werden dabei in Form »Gruppe.Eintrag« angegeben.

Beispiel:

PROCEDURE Main
VAR ini : STRING
  ini:='test.ini'
  CGIWriteLn('content-type: text/html')
  CGIWriteLn('')
  SetIdent(ini,'Administrator.Name','Hans Huber')
  SetIdent(ini,'Administrator.Passwort','geheim')
  SetIdent(ini,'Datenbank.Adressen','database/adressen.dat')
  CGICloseBuffer
  CGIWrite('<pre>')
  CopyFile(ini,'con')
  CGIWrite('</pre>')
ENDPROC

Dieses kleine Programm legt die Datei »test.ini« mit folgendem Inhalt an:
[Administrator]
Name=Hans Huber
Passwort=geheim
[Datenbank]
Adressen=database/adressen.dat


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'
GetIdent('test.ini',Datenbank.Adressen) -> 'database/adressen.dat'

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.

Templates

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(Template) lädt das Template in den Arbeitsspeicher
CGIWRITETEMPLATE sendet das Template an den Klienten
SUBST(target,...) ersetzt ein Target (Platzhalter) im Template


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:

<html>
<head>
<title>Ein Template für die tdbengine</title>
<body bgcolor="white">
Datum: #datum#    Zeit: #zeit#<p>
<h3> Das ist eine HTML-Seite, die durch die tdbengine bearbeitet wurde</h3>
</body>
</html>

(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:

PROCEDURE Main
  CGIWriteLn('content-type: text/html')
  CGIWriteLn('')
  IF LoadTemplate('templates/meine_seite.html')=0 THEN
    Subst('#datum#',DateStr(Today))
    Subst('#zeit#',TimeStr(Now,0))
    CGIWriteTemplate
  ELSE
    CGIWriteLn('template wurde NICHT gefunden')
  END
ENDPROC

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«.
SUBST(target,string,modus) Ersetzt das target ...


Modus Bedeutung
0 (Vorgabe) Ersetzung ohne weitere Bearbeitung
1 Alle Zeichen werden nach HTML konvertiert
2 Alle Zeichen werden nach ANSI konvertiert
4 Harte Zeilenumbrüche werden durch »<br>« ersetzt
16 Der externe Text liegt im ASCII-Format vor (statt ANSI)
32 Es wird nur der BODY-Teil einer externen HTML-Seite gelesen


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 folgenden Feldern:

Name max. 40 Zeichen
Vorname max. 30 Zeichen
Strasse max. 30 Zeichen
PLZ max. 5 Zeichen
Ort max. 30 Zeichen
Telefon max. 20 Zeichen
Email max. 30 Zeichen

Dieses Formular legen wir unter »templates/adressen_eingabe.html« ab. Hier der einfache HTML-Text:
<html>
<head><title>Eingabeformular für Adressen</title></head>
<body bgcolor="white">
<h3>Eingabeformular</h3>
#fehlermeldung#
<form action="#action#" method="post">
<table>
  <tr><td>Name</td><td><Input type="text" name="Name" maxlength="40" value="#Name#"></td></tr>
  <tr><td>Vorname</td><td><Input type="text" name="Vorname" maxlength="30" value="#Vorname#"></td></tr>
  <tr><td>Strasse</td><td><Input type="text" name="Strasse" maxlength="30" value="#Strasse#"></td></tr>
  <tr><td>PLZ</td><td><Input type="text" name="PLZ" maxlength="5" value="#PLZ#"></td></tr>
  <tr><td>Ort</td><td><Input type="text" name="Ort" maxlength="30" value="#Ort#"></td></tr>
  <tr><td>Telefon</td><td><Input type="text" name="Telefon" maxlength="20" value="#Telefon#"></td></tr>
  <tr><td>E-Mail</td><td><Input type="text" name="Email" maxlength="30" value="#Email#"></td></tr>
  <tr><td colspan="2" align="center"><Input type="submit" name="send" value="absenden"></td></tr>
</table>
</form>
</body>
</html>


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'
PARAMSTR(2) = 'parameter_a'
PARAMSTR(3) = 'parameter_b'

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:

hole die übertragenen Werte für Name, Vorname etc.
falls die Eingaben noch NICHT genügen,
  lade das Formular-Template
  fülle es mit den bisher eingegeben Informationen aus
  setze die entsprechende Fehlermeldung ein
  schick das Formular zurück an den Klienten
sonst
  verarbeite die übertragenen Informationen

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.

VAR Name,Vorname,Strasse,PLZ,Ort,Telefon,Email : STRING

Eine Prozedur, die diese Informationen vom Klienten holt, schaut nun so aus:

PROCEDURE HoleDieInformationen
  Name:=CGIGetParam('Name')
  Vorname:=CGIGetParam('Vorname')
  Strasse:=CGIGetParam('Strasse')
  PLZ:=CGIGetParam('PLZ')
  Ort:=CGIGetParam('Ort')
  Telefon:=CGIGetParam('Telefon')
  Email:=CGIGetParam('Email')
ENDPROC

Auch die Frage, ob die bisher vom Klienten gelieferten Informationen genügen, wollen wir eine einer eigenen (Funktions-)Prozedur klären:
PROCEDURE EingabeIstOkay : REAL
  IF Name<>'',Vorname<>'',Strasse<>'',PLZ<>'',Ort<>'' THEN
    RETURN 1
  ELSE
    RETURN 0
  END
ENDPROC

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.
PROCEDURE FormularAusfüllen
  Subst('#action#',ParamStr(0))
  Subst('#Name#',Name,1)
  Subst('#Vorname#',Vorname,1)
  Subst('#Strasse#',Strasse,1)
  Subst('#PLZ#',PLZ,1)
  Subst('#Ort#',Ort,1)
  Subst('#Telefon#',Telefon,1)
  Subst('#Email#',Email,1)
ENDPROC

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:

PROCEDURE Main
  CGIWriteLn('content-type: text/html')
  CGIWriteLn('')
  HoleDieInformationen
  IF EingabeIstOkay THEN
    CGIWriteLn('<h3>Ihre Informationen werden bearbeitet</h3>')
  ELSE
    LoadTemplate('templates/adressen_eingabe.html')
    Subst('#fehlermeldung#','<h3>Geben Sie Ihre Adresse an</h3>')
    FormularAusfüllen
    CGIWriteTemplate
  END
ENDPROC

Zusammenfassung
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.

Aufgaben
  1. Wie muss im obigen Beispiel die Prozedur EingabeIstOkay verändert werden, dass die Gültigkeit nur besteht, wenn sämtliche Felder (also auch Telefon und Email) ausgefüllt sind.
  2. Verändern Sie das Programm so, dass eine genauere Fehlermeldung erfolgt, wie 'Bitte geben Sie die Strasse an'
  3. Schreiben Sie eine Prozedur, in der die übertragenen Informationen (Name,Vorname...) in eine Konfigurationsdatei geschrieben werden:
Name=...
Vorname=...
usw.


tdbengine Anwendungen im Web:

Open-Source Web CMS


Open-Source Bug-Tracking


Free wiki hosting

Open-Source Wiki-System

Kostenloses Foren-Hosting

Diät mit tdbengine 8-)

tdbengine chat
irc.tdbengine.org
#tdbengine

   Copyright © 2003-2004 tdb Software Service GmbH
   Alle rechte vorbehalten. / All rights reserved
   Last changed: 05.05.2004
{Fehler für :execmacro{execmacro="sessionspy"}


ranking-charts.de

Programmers Heaven - Where programmers go!