April 10, 2003
Die Programme in diesem Papier benötigen das Teachpack servlet2.ss.
Die Funktion single-query stellt dem Benutzer (durch den Web-Browser) eine Frage:
;; single-query : String -> String
Dabei ist die Eingabe von single-query der Text der zu stellenden Frage; der Web-Browser stellt zusätzlich ein Text-Eingabefeld dar. Single-query gibt den Text zurück, den der Benutzer eingetippt hat, nachdem dieser die Return-Taste gedrückt hat.
Es ist auch möglich, dem Benutzer einfach nur einen informativen Text zu schicken; die Funktion inform wartet solange, bis der Benutzer auf den Continue-Link klickt und gibt dann true zurück:
;; inform : String* -> true
Inform akzeptiert beliebig viele Zeichenketten als Eingabe, die allesamt auf der Web-Seite angezeigt werden.
Beide Funktionen lassen sich kombinieren:
(inform "Das Wetter ist heute offenbar" (single-query "Wie ist das Wetter heute?") ".")
Hier ist eine etwas spannendere Variante, die eine variierende Antwort auf die Wetterbeschreibung erzeugt:
(define (reply weather) (cond ((equal? "schön" weather) "prima") ((equal? "schlecht" weather) "bedauernswert") (else "auch in Ordnung"))) (inform "Das ist ja" (reply (single-query "Wie ist das Wetter heute?")) ".")
Single-query arbeitet ausschließlich auf Zeichenketten. Für Berechnungen mit Zahlen ist es notwendig, Zeichenkette in Zahlen umzuwandeln und umgekehrt. Dafür sind die Funktionen string->number und number>string da:
(string->number "5.05") => 5.05
(number->string 5.05) => "5.05"
String->number liefert allerdings false für Zeichenketten, die keine Zahlen repräsentieren.
Das folgende Programm ermittelt die Summe zweier eingegebener Zahlen:
(define number-1 (string->number (single-query "Geben Sie eine Zahl ein:"))) (define number-2 (string->number (single-query "Geben Sie noch eine Zahl ein:"))) (inform "Summe:" (number->string (+ number-1 number-2))))
Entwickeln Sie eine Funktion request-number, die eine Zeichenkette als Eingabe akzeptiert und diese benutzt, um eine Zahleneingabe vom Benutzer anzufordern. Request-number soll die eingegebene Zahl zurückgeben. Benutzen Sie dazu die folgende Hilfsfunktion string->number0, die sich wie string->number verhält, aber für nicht-numerische Eingaben 0 zurückgibt:
;; string->number0 : string -> number ;; Wandle Zeichenkette in Zahl um, wenn möglich, sonst gibt 0 zurück ;; (string->number0 "15") => 15 ;; (string->number0 "foo") => 0 (define (string->number0 s) (cond ((number? (string->number s)) (string->number s)) (else 0)))
Benutzen Sie die Funktion, um das Summationsprogramm robuster zu machen.
Entwickeln Sie eine Funktion, die den Namen einer Währung ("`Pfund"', "`Peso"', "`Dollar"') in einen Umrechnungsfaktor (von Euro) übersetzt.
Benutzen Sie diese Funktion, um einen Web-Service für Währungsumwandlung zu programmieren. Dieser soll aus drei Seiten bestehen: eine Seite fragt den Namen der Währung an, die nächste den Betrag, und die dritte zeigt den in Euro umgerechneten Betrag an.
Dabei sollte die dritte Seite nicht nur das Endresultat anzeigen, sondern auch die Eingaben zur Sicherheit noch einmal wiederholen:
Die Umrechnung von 5430 Pfund in Euro ergibt 7891.29.
Entwickeln Sie eine Funktion calculate, die als
Eingaben eine Zahl, eine Zeichenkette, und eine andere Zahl
akzeptiert. Dabei soll die Zeichenkette der Name einer
arithmetischen Funktion ("+"
, "-"
, "*"
,
"/"
, ...) darstellen. Calculate soll dann (wenn
möglich) das Resultat der Anwendung der Operation auf die beiden
Zahlen zurückgeben. Falls die Zeichenkette keine bekannte Operation
darstellt, soll die Funktion eine informative Meldung als
Zeichenkette zurückgeben-
Entwickeln Sie eine Funktion range-query, die zwei
Zahlen l und h als Eingaben akzeptiert. Sie soll über den
Browser eine Zahl vom Benutzer in diesem Bereich anfordern. Falls
sich die eingegebene Zahl n in diesem Bereich befindet, soll die
Funktion den Text "
n ist drin."
an den Browser ausgeben,
sonst "
n ist nicht drin."
. Range-query selbst soll
true zurückgeben.
Manche Web-Services benötigen einen Passwort-Schutz, um ihre Inhalte nur autorisierten Benutzern vorzubehalten. Passwörter sind zwar auch nur Zeichenketten, sollten aber beim Eintippen nicht im Browser erscheinen. Zu diesem Zweck kann ein Programm single-query mit einer password-Struktur aufrufen anstatt mit einer einfachen Zeichenkette. Die password-Struktur enthält dann die zu stellende Anfrage:
(make-password "Ihr Passwort, bitte:")
Hier ist ein Beispiel für die Benutzung einer password-Struktur: Das Programm fragt zunächst nach Benutzername und Passwort, überprüft diese, und informiert den Benutzer über das Resultat [password.scm]:
;; check-password: string x string -> boolean ;; geht sicher, daß NAME und PASSWORD zusammengehören (define (check-password name password) (cond ((equal? "sperber" name) (equal? "hasi" password)) ((equal? "bauer" name) (equal? "mausi" password)) ((equal? "klaeren" name) (equal? "klaeren" password)) ((equal? "gasbichler" name) (equal? "hasi" password)) (else false))) ;; response: string x string -> true ;; erzeugt eine Antwort auf eine Passwort-Überprüfung (define (response name password) (cond ((check-password name password) (inform "Login" "Willkommen!")) (else (inform "Schade" "Name und Passwort passen nicht zusammen.")))) (response (single-query "Name:") (single-query (make-password "Passwort:")))
Verändern Sie das obige Programm, so daß ein korrekt eingelogter Benutzer Währungen umrechnen kann.
Entwickeln Sie einen Web-Service, der neue Benutzer registriert. Der Service sollte nach einem Namen und einem Passwort mit zweimaliger Eingabe zur Bestätigung fragen. Falls die Passwörter übereinstimmen, zeigt der Service eine Bestätigung an mit dem Namen des Benutzers an, sonst eine Nachricht, daß die Registrierung fehlgeschlagen ist.
Das Summen-Programm aus Abschnitt 1.1 hat den Nachteil, daß es nicht für Eingaben funktioniert, die keine Zahlen sind, da string->number für solche Eingaben false liefert.
Aus diesem Grund bietet single-query eine Reihe von verschiedenen Methoden an, den Benutzer um eine Eingabe zu bitten. Single-query kann dabei als Eingabe eine sogenannte Anfrage akzeptieren. Eine Anfrage hat eine der folgenden Formen:
string
(make-password string)
(make-number string)
(make-boolean string)
(make-yes-no string string string)
Die ersten drei Varianten stellen immer eine Textanfrage, stellen aber jeweils sicher, daß die Eingabe die gewünschte Eigenschaft hat und geben einen Wert des entsprechenden Typs zurück. Eine boolean-Struktur zeigt eine "`Check-Box"' an; in diesem Fall ist der Rückgabewert von single-query ein Wahrheitswert, der besagt, ob das Kästchen angekreuzt wurde oder nicht. Eine yes-no-Struktur stellt eine "`Ja-oder-Nein-Frage"' mit zwei möglichen Phrasen als Antworten. Beispiel:
(single-query (make-yes-no "Wollen Sie?" "Ja, ich will." "Später vielleicht."))
Die ausgewählte Phrase wird dann von single-query zurückgegeben.
Mit Hilfe von number-Anfragen läßt sich das Summations-Programm verbessern und vereinfachen:
(define number-1 (single-query (make-number "Geben Sie eine Zahl ein:"))) (define number-2 (single-query (make-number "Geben Sie noch eine Zahl ein:"))) (inform "Summe:" (number->string (+ number-1 number-2))))
Schreiben Sie eine Datendefinition für den Rückgabewert von single-query und ordnen sie die Alternativen den Alternativen für Anfragen zu.
Ändern Sie die die Programme für den Währungsumrechner und den "`Taschenrechner"' aus Abschnitt 1.1 dahingehend, daß sie die neuen Arten von Anfragen benutzen.
Entwickeln Sie eine Struktur und Datendefinition für Kalenderdaten (Jahr, Monat, Tag). Schreiben Sie eine Funktion request-date, die true als Eingabe akzeptiert, und ein Kalenderdatum zurückliefert, das der Benutzer im Browser eingegeben hat. Achten Sie darauf, daß die eingegebenen Daten in den korrekten Bereichen legen.
Entwickeln Sie eine Funktion pick-toppings, die als Eingabe true akzeptiert und den Benutzer fragt, welche Beläge auf eine Pizza sollen. Dabei gibt es die Möglichkeiten Anchovies, Pepperoni und Gummi. Die Funktion soll eine Struktur mit drei booleschen Feldern zurückgeben.
Bisher fehlten bei single-query noch Anfragen, die den Benutzer zwischen einer größeren (>2) Anzahl von Alternativen auswählen lassen, zum Beispiel, um eine Farbe für einen Web-bestellten VW Phaeton auszuwählen. (Nur eine auf einmal ist möglich.) Dafür sind die sogenannten Radioknöpfe zuständig, die Multiple-Choice-Fragen stellen. Hierfür gibt es eine weitere Art Anfrage, welche die Liste aus dem vorherigen Abschnitt erweitert:
Eine Anfrage hat eine der folgenden Formen:
...
(make-radio string list-of-strings)
Dabei hat ein list-of-string eine der folgenden Formen:
empty
(cons string list-of-string)
So sieht eine Farbauswahl aus:
(single-query (make-radio "Wählen Sie eine Farbe aus:" (list "Cliffgrün" "Wildpflaume" "Mahagoni" "Azurblau" "Pechschwarz")))
Single-query gibt dann den Namen der Auswahl zurück.
Entwickeln Sie einen Web-Service, der ein Produkt "`verkauft"', das es in verschiedenen Größen gibt.
Schreiben Sie eine Funktion request-grade, die eine Liste von möglichen Schulnoten als Eingaben akzeptiert, und eine von ihnen (nach einer Interaktion mit dem Benutzer) zurückgibt.
Entwickeln Sie eine Variante von request-date (aus den Übungen des vorherigen Abschnitts) mit Radioknöpfen statt Eingabefeldern, damit Benutzer keine Bereichsfehler machen können.
Benutzen Sie die Funktion request-date aus der vorherigen Übung, um einen Zustellpausen-Dienst für eine Zeitung zu entwickeln: Abonnenten können die Zustellung ihrer Zeitung für einen bestimmten Zeitraum unterbinden. Der Dienst sollte folgendermaßen mit dem Benutzer interagieren:
Einloggen (mit Namen und Abo-Nummer)
Auswahl eines Anfangsdatums für die Aussetzung
Auswahl eines Enddatums für die Aussetzung
Anzeige der eingegebenen Daten
Stellen Sie sicher, daß die Daten in der richtigen Reihenfolge sind, und informieren Sie den Benutzer über Fehler.
Entwickeln Sie eine Funktion, die eine Online-Version von "`Wer wird Millionär?"' mit dem Benutzer spielt. Die Funktion akzeptiert eine Liste von Fragen und liefert true zurück. Jede Frage besteht aus einem Fragetext, der korrekten Antwort, und einer Liste aller Antworten. Das Programm stellt nacheinander alle Fragen und präsentiert jeweils die möglichen Antworten zur Auswahl. Solange der Benutzer richtig antwortet, geht das Spiel weiter. Sobald der Benutzer eine falsche Antwort gibt, ist das Spiel zu Ende. Beantwortet der Benutzer alle Fragen richtig, hat er gewonnen.
Hinweis Schreiben Sie eine Hilfsfunktion ask-all-questions. Diese akzeptiert eine Liste von Fragen mit zugeordneten Antworten, stellt alle Fragen, und liefert einen Wahrheitswert, der anzeigt, ob der Benutzer gewonnen oder verloren hat. Testen Sie ask-all-questions mit einer Funktion, die nur eine einzige Frage stellt.
Hinweis Benutzen Sie die Navigationsfähigkeiten Ihres Browsers, um ein Spiel mit drei unbeantwortbar schwierigen Fragen mit jeweils zwei Antworten zu gewinnen, ohne das Programm neu zu starten.
Single-query kann dem Benutzer stets nur eine Frage auf einmal stellen. Web-Browser bieten aber die Möglichkeit, mehrere Fragen auf einer Web-Seite in Form eines Formulars unterzubringen. Konzeptuell besteht ein Formular einfach aus einer Liste von Fragen:
Ein Formular (form hat eine der folgenden Formen:
empty
(cons form-item form)
Ein form-item ist eine Liste mit zwei Elementen:
(cons symbol (cons query empty))
Die Funktion form-query schickt ein Formular an den Web-Browser:
;; form-query: form -> table
Form-query gibt eine Liste zurück, die aus sovielen Elementen besteht, wie das Formular fragen enthält. Ein table hat eine der folgenden Formen:
empty
(cons table-item table)
Ein table-item ist eine Liste aus zwei Elementen:
(cons symbol (cons response empty))
Wie vorher ist ein response eine Zeichenkette, eine Zahl, oder ein Wahrheitswert, je nach zugehöriger Anfrage. Beispiel:
(form-query (cons (cons 'first (cons "Vorname" empty)) (cons (cons 'last (cons "Nachname" empty)) (cons (cons 'age (cons (make-number "Alter") empty)) empty))))
Dieser Ausdruck gibt einen Wert der folgenden Form zurück:
(cons (cons 'first (cons "Humpty" empty)) (cons (cons 'last (cons "Dumpty" empty)) (cons (cons 'age (cons 3 empty)) empty)))
Das Resultat ist also -- wie auch das Formular -- eine Liste mit drei Elementen, jedes mit einem Symbol versehen. Hier ist das Programmgerüst [likes.scm]:
;; string -> (list 'likes query) (define (likes lang) (list 'likes (make-boolean (string-append "Mögen Sie " lang " ?")))) ;; Formular (define language-query (cons (cons 'name (cons "Name:" empty)) (cons (likes "Vanille") (cons (likes "Banane") (cons (likes "Tutti-Frutti") empty))))) ;; likes-how-many: able -> number ;; zählt, wie oft how das Tag 'likes mit true assoziiert ist (define (likes-how-many a-table) ...) (inform "Sie mögen also " (number->string (likes-how-many (form-query language-query))) " Eissorten.")
Das Beispiel zeigt, daß die Markierungen nicht eindeutig sein müssen.
Vervollständigen Sie die Funktion likes-how-many aus dem Beispiel.
Entwickeln Sie eine Funktion choose-toppings, die eine Liste von Zeichenketten (Pizza-Beläge) akzeptiert, und alle als boolesche Anfragen auf einer einzigen Web-Seite präsentiert. Die Funktion soll eine Liste aller angekreuzten Belagnamen zurückgeben.
Schreiben Sie eine Funktion extract1, die ein Symbol s und eine Tabelle t als Eingaben akzeptiert. Die Funktion gibt eine Liste aller Tabellenelemente zurück, deren Markierung s ist.
Entwickeln Sie eine Funktion extract2. Auch sie akzeptiert ein Symbol s und eine Tabelle t als Eingaben. Die Funktion extrahiert den einzigen Wert, dessen Markierung s ist. Falls es mehrere oder keine passenden Einträge gibt, soll die Funktion einen Fehler signalisieren. Hinweis: Benutzen Sie dazu extract1 und schreiben Sie eine Hilfsfunktion, die ein einzelnes Element aus einer Liste von Antworten extrahiert, falls es genau eine gibt.
Das Teachpack servlet2.ss enthält zwei Funktionen, um Einträge aus einer Tabelle zu extrahieren. Die erste, extract/single, findet die Antwort, die mit einer eindeutigen Markierung versehen ist, und signalisiert einen Fehler, falls keine oder mehrere passende Antworten in der Tabelle stehen. Die zweite Funktion, extract, findet alle Antworten mit einer gegebenen Markierung. Mit extract ist es möglich, likes-how-many als Einzeiler zu realisieren:
(define (likes-how-many alor) (length (extract 'likes alor)))
Als Beispiel für extract/single dient ein Programm, das Anmeldungen für Konferenzen regelt. Das Programm erledigt dabei die folgenden Teilaufgaben:
Es fragt Kontaktdaten (Vorname, Nachname, Email-Adresse) ab.
Es fragt eine erste und eine zweite Wahl für gewünschten Konferenzort ab, und ob ein Zimmer benötigt wird.
Falls ein Zimmer gewünscht ist, fragt das Programm, ob es sich um ein Einzel- oder um ein Doppelzimmer handelt.
Das Programm läßt sich alle Daten noch einmal bestätigen. Eine naheliegende Übersetzung dieser Verkettung von Fragen ist die folgende Funktionskomposition:
(confirm-info+choices (determine-choices (basic-info true)))
Dabei fragt basic-info die Kontaktdaten ab, determine-choices stellt die Fragen bezüglich Ort und Zimmer, und confirm-info+choices läßt sich die erhaltenen Daten noch einmal bestätigen. Die Daten, die durch die Funktionen fließen, akkumulieren dabei immer mehr Informationen. Dementsprechend ist eine Tabelle eine natürliche Repräsentation für die Informationen, die an jeder Stufe vorliegen. Hier ist das Programmgerüst [signup.scm]:
;; basic-info: true -> table ;; Kontaktinformationen vom Benutzer Einholen (define (basic-info go) (form-query INFO-PAGE)) ;; form (define INFO-PAGE (cons (cons 'first (cons "Vorname:" empty)) (cons (cons 'last (cons "Nachname:" empty)) (cons (cons 'email (cons "Email:" empty)) empty)))) ;; determine-choices: table -> table ;; zusätzliche Entscheidungen einfordern, Tabelle ;; aller Entscheidungen zurückgeben (define (determine-choices contact-info) (refine-choices contact-info (form-query CHOICE-PAGE))) ;; list-of-strings [list of locations] (define LOCs (cons "Trochtelfingen" (cons "New York" (cons "Tokio" empty)))) ;; form[first,second,housing] (define CHOICE-PAGE (cons (cons 'loc (cons (make-radio "erste Wahl" LOCs) empty)) (cons (cons 'loc (cons (make-radio "zweite Wahl" LOCs) empty)) (cons (cons 'housing (cons (make-boolean "Brauchen Sie ein Zimmer?") empty)) empty)))) ;; refine-choices: table x table -> table (define (refine-choices t1 t2) (append t1 (extract1 'loc t2) (refine-housing (extract/single 'housing t2)))) ;; refine-housing: boolean -> table ;; wenn nötig, Zimmertyp abfragen ;; und Tabelle mit Zimmerentscheidung zurückgeben (define (refine-housing housing?) (cond (housing? (cons (cons 'housing-type (cons (single-query (make-yes-no "Was für ein Zimmer brauchen Sie?" "einzel" "doppel")) empty)) empty)) (else (cons (cons 'housing-type (cons "keins" empty)) empty)))) ;; confirm-info+choices: table -> true ;; Eingaben bestätigen (define (confirm-info+choices t) (inform "Bestätigung" ... (extract/single 'first t) ...)) (determine-choices (basic-info true))
Die Funktion refine-housing akzeptiert einen Wahrheitswert
als Eingabe, der angibt, ob der Teilnehmer um ein Zimmer gebeten hat.
Falls ja, stellt refine-housing eine zusätzliche Frage nach
dem Zimmertyp; andernfalls produziert die Funktion eine Tabelle, in
der für die Markierung 'housing-type der Wert "none"
eingetragen ist.
Vervollständigen Sie das Konferenz-Anmeldungsprogramm.
Erweitern Sie das Programm dergestalt, daß es bei einem Doppelzimmer anfragt, ob es eins mit Doppelbett oder zwei Betten sein soll, und ob der Zimmerpartner ebenfalls Konferenzteilnehmer ist.
Überarbeiten Sie das Zeitungszustellpausen-Programm, so daß es Formulare und Tabellen verwendet.
Entwickeln Sie einen Web-Service, um Pizza zu bestellen. Es sollte mindestens aus folgenden Teilen bestehen:
Abfrage von Name und einem Passwort des Kunden (der in einer Kundendatenbank gespeichert ist)
Abfrage, ob Lieferung gewünscht ist
Pizza-Spezifikation:
Bei gewünschter Lieferung wird auch noch die Adresse abgefragt.
Ein Bestätigungsdialog faßt die Information zusammen und zeigt einen berechneten Preis für die Pizza an.
Das Programm sollte eine Tabelle produzieren, in der Kundenname, Pizza-Spezifikation und Lieferinformationen enthalten sind.
Die Web-Seiten, die mit sinqle-query und form-query generiert werden können, sind allesamt eher einfach strukturiert. Programme, die komplexere Web-Seiten mit umfangreichen Layouts erzeugen sollen, benötigen Zugriff auf den vollen Umfang der darunterliegenden XHTML-Repräsentation für Web-Seiten. Darum geht es in diesem Abschnitt.
XHTML ist die Sprache, in der Web-Seiten geschrieben sind. Sie besteht aus Text und Elementen, welche die Strukturierung und Formatierung des Textes regeln, sowie Meta-Informationen wie Links und die Einbettung von Bildern angeben.
XHTML-Ausdrücke sind, wie Scheme-Ausdrücke, voll geklammert; jeder Ausdruck spezifiziert gleich am Anfang eine Operation. Damit stehen die gleichen syntaktischen Ideen hinter XHTML und Scheme. Der Hauptunterschied besteht darin, daß es für jedes XHTML-Konstrukt ein eigenes Klammernpaar gibt -- der Name des Konstrukts taucht sowohl in der öffnenden als auch in der schließenden Klammer auf. Hier ist ein Beispiel:
<html> <head> <title>Prima-Super-Webseite</title> </head> <body> <h1>Prima-Super-Webseite</h1> <p>Hallo, da draußen!</p> </body> </html>
Für das html-Konstrukt sind z.B. <html>
und
</html>
die öffnenden und schließenden Klammern. Die
XHTML-Elemente aus dem Beispiel haben die folgenden Bedeutungen:
|
<body bgcolor="#e5e5e5">Haha. Naja.</body>
Jedes Attribut hat seinerseits einen Namen (in diesem Fall
bgcolor) und einen Wert (hier "#e5e5e5"
).
Attributwerte sind immer Zeichenketten.
Die Elemente a und img müssen immer mit Attributen versehen werden. Das a-Element ist für Links zuständig; das href-Attribut gibt an, wo der Link hinzeigt. Das img-Element bindet ein Bild ein; das src-Attribut gibt die URL der Grafik-Datei an, die das Bild enthält. Zum Beispiel erzeugt
<a href="http://www-pu.informatik.uni-tuebingen.de/deinprogramm/"> DeinProgramm </a>
einen Link auf die DeinProgramm-Web-Seite. Der Text zwischen den Klammern läßt sich dann im Browser klicken. Ein img-Element kann z.B. so aussehen:
<img src="http://www-pu.informatik.uni-tuebingen.de/deinprogramm/logo-small.png" alt="DeinProgramm"> </img>
Dieses Beispiel sorgt dafür, daß in der Web-Seite das DeinProgramm-Logo eingebunden wird; falls der Browser keine Bilder darstellen kann, erscheint dort der Alternativtext DeinProgramm, spezifiziert durch das (optionale) alt-Attribut.
XHTML bietet außerdem Tabellen als Elemente an, die viele Möglichkeiten für komplexes Layout von Web-Seiten eröffnen. Hier sind die wichtigsten Elemente, aus denen Tabellen bestehen:
|
<table> <tr><th>Element</th> <th>Bedeutung</th></tr> <tr><td>table</td> <td>Tabelle</td></tr> <tr><td>tr</td> <td>Tabellenzeile</td></tr> <tr><td>td</td> <td>Spalte</td></tr> <tr><td>th</td> <td>Titelspalte</td></tr> </table>
In DrScheme ist es möglich, XHTML direkt in einer sogenannten XML-Box einzugeben. Dafür gibt es den Menüpunkt Special --> Insert XML Box.
Figure 1: XML-Box in DrScheme |
In solch einer Box erzeugt DrScheme die schließenden Klammern automatisch und rück den XHTML-Code ein. Abbildung 1 zeigt ein Beispiel. Eine XML-Box gilt in DrScheme als ganz normaler Ausdruck, der auch einen Scheme-Wert zurückliefert. Wie sich XML-Boxen benutzen lassen, um Web-Seiten zu generieren, wird im nächsten Abschnitt erklärt.
Die Analogie zwischen XHTML und der Scheme-Notation erlaubt die direkte Darstellung von XHTML-Seiten als Scheme-Daten. Diese Darstellung ermöglicht Scheme-Programmen, Web-Seiten systematisch zusammenzusetzen und Funktionen zu schreiben, die auf XHTML-Daten operieren.
In Scheme ist das Pendant zu einem XHTML-Element eine sogenannte X-Expression oder X-Exp. Eine X-Expression ist entweder eine Zeichenkette (die ihren Text repräsentiert) oder eine Liste, deren erstes Element der Name des XHTML-Elements (als Symbol dargestellt) ist. Eine X-Expression hat also eine der folgenden Formen:
string
(cons symbol (cons list-of-attributes list-of-X-exp))
Nach dem Element-Namen enthält die zweite Form eine Liste von Attributen, die eine der folgenden Formen hat:
empty
(cons attribute list-of-attributes)
Ein attribute ist dabei eine Liste mit zwei Elementen der Form (cons symbol (cons string empty)). Das Symbol ist der Name das Attributs, die Zeichenkette dessen Wert.
Schließlich hat eine Liste von Teilelementen list-of-X-exp eine der folgenden Formen:
empty
(cons X-exp list-of-X-exp)
An dem Beispiel läßt sich die Analogie zwischen XHTML und X-Expressions am einfachsten sehen:
<html> <head> <title>Prima-Super-Webseite</title> </head> <body> <h1>Prima-Super-Webseite</h1> <p>Hallo, da draußen!</p> </body> </html>
'(html () (head () (title () "Prima-Super-Webseite")) (body () (h1 "Prima-Super-Webseite") (p "Hallo, da draußen!")))
Der Vergleich zeigt, daß X-Expressions kompakter sind als ihre XHTML-Pendants, im wesentlichen, weil der Elementname nicht in der schließenden Klammer wiederholt wird.
Hier ist ein Beispiel für die Verwendung von Attributen (beim body-Element):
'(html () (head () (title () "Prima-Super-Bunt-Webseite")) (body ((bgcolor "white")) (h1 "Prima-Super-Bunt-Webseite") (p "Hallo, da draußen!")))
Hier ist eine einfache Funktion, die über einen Teil einer Web-Seite abstrahiert:
;; string -> X-exp ;; generiert Web-Seite aus Adjektiv (define (swell-web-page s) (local ((define title )) (list 'html empty (list 'head empty (list 'title empty (string-append "Prima-Super-" s "-Webseite"))) (list 'body (list (list 'bgcolor "white")) (list 'h1 empty (string-append "Prima-Super-" s "-Webseite")) (list 'p empty "Hallo, da draußen!")))))
Swell-web-page ist eine sehr einfach gestrickte Funktion, im wesentlichen eine "`Web-Seite mit Loch"'. Aber die Möglichkeiten gehen weiter. Die folgende Funktion erleichtert die Generierung von Tabellen [table.scm]:
;; Eine list-of-2strings hat eine der folgenden Formen ;; - empty ;; - (cons string-pair list-of-2strings) ;; Ein string-pair ist eine Liste mit zwei Zeichenketten: ;; (list string string) ;; create-table: list-of-2strings -> X-exp[table] (define (create-table alos) (cons 'table (cons empty (create-all-rows alos)))) ;; create-all-rows: list-of-2strings -> list-of-X[tr] (define (create-all-rows alos) (cond ((empty? alos) empty) (else (cons (create-one-row (first alos)) (create-all-rows (rest alos)))))) ;; create-one-row: string-pair -> X-exp[tr] (define (create-one-row s2) (list 'tr empty (list 'td empty (first s2)) (list 'td empty (list 'img (list (list 'src (second s2)) (list 'width "200") (list 'alt "pic"))))))
Die Funktion create-table generiert aus einer Liste von zweielementigen Listen eine zweispaltige Tabelle. Dazu ruft create-table create-all-rows auf, welche die Hauptarbeit erledigt. Diese wiederum ruft create-one-row auf, um eine einzelne Zeile zu generieren.
Das servlet2.ss-Teachpack stellt die Funktion inform/html bereit, die X-Expression (in XHTML umgewandelt) an den Browser schickt. Inform/html akzeptiert eine Liste von X-Expressions und schickt sie an den Web-Browser. Wie bei inform erscheint unten auf der Seite ein Link, der zur Fortsetzung des Programms führt.
Schreiben Sie eine Funktion create-paragraphs. Sie akzeptiert eine Liste von Zeichenketten und liefert eine X-Expression, in der jede der Zeichenketten in einem eigenen Absatz steht. Die Absätze sind selbst von einem div-Element umschlossen.
Benutzen Sie nach dem Test der Funktion inform/html, um das Resultat in einem Browser anzuzeigen.
Schreiben Sie eine Funktion create-enumeration, die eine Liste vopn Zeichenketten als Eingaben und produziert eine XHTML-Aufzählung. Eine Aufzählung ist von einem ol-Element umschlossen; die einzelnen Elemente stecken li-Elementen.
Benutzen Sie nach dem Test der Funktion inform/html, um das Resultat in einem Browser anzuzeigen.
Schreiben Sie eine Funktion enum-table, die eine natürliche Zahl als Eingabe akzeptiert, und eine Tabellenschablone mit einer entsprechenden Anzahl von Zeilen zurückliefert. Jede Zeile hat eine Spalte, und die Zelle enthält die Nummer als Text.
Entwickeln Sie eine Funktion create-matrix, die zwei natürliche Zahlen n und m als Eingaben akzeptiert und eine X-Expression als Ergebnis liefert und entsprechend der vorherigen Aufgabe eine zweidimensionale Tabelle generiert. Beispiel:
(equal? (create-matrix 3 2) '(table () (tr () (td () "cell00") (td () "cell01")) (tr () (td () "cell10") (td () "cell11")) (tr () (td () "cell20") (td () "cell21"))))
Testen Sie anhand einiger kleinerer Zahlen.
Figure 2: Eingebettete XML-Boxen und Scheme-Code |
Abbildung 2 zeigt, daß XML-Boxen selbst wieder Scheme-Code enthalten können. In create-one-row, die Boxen innerhalb von den XML-Boxen sind sogenannte Scheme-Boxen, die vom selben Menü aus eingefügt werden können. Der Werte eines Ausdrucks innerhalb einer Scheme-Box wird in den XHTML-Code um die Box drumherum eingefügt.
Die Box innerhalb der XML-Box in create-table ist eine sogenannte Scheme-Slice-Box. Der Ausdruck in solch einer Box muß eine Liste als Wert ergeben, deren Elemente in den umschließenden XHTML-Code eingefügt oder gespleißt werden. In diesem Fall werden die Zeilen in die XML-Box als die Zeilen der Tabelle eingefügt. Wäre diese Box eine normale Scheme-Box, wäre das Ergebnis kein XHTML-Element und würde damit zu inkorrektem Code führen. (Der Unterschied zwischen den beiden Sorten Scheme-Box ist anfangs verwirrend und die korrekte Verwendung erfordert etwas Übung. Diese Übung macht sich allerdings schnell bezahlt.)