Woche 1

# Tag 7

#
Ein paar längere Beispiele

Widmen wir den letzten Tag der Woche ein paar weiteren Beispielen. Sie werden in diesem Kapitel nicht viel Neues lernen, auch Übungen und Quiz gibt es heute nicht. Betrachten Sie es als kurze Verschnaufpause, in der Sie sich vollständige Perl-Skripts ganz in Ruhe ansehen können. Insgesamt hat das Buch drei dieser Beispiellektionen (immer am Ende der Wochen), die Ihr neues Wissen zementieren sollen.

Wir besprechen heute drei Perl-Skripts:

- Eine weitere Version von stats.pl mit einem komplexeren Histogramm
- Ein Skript zum Buchstabieren von Zahlen
- Ein Skript zum Konvertieren von Textdateien in einfache Webseiten

## Statistik mit verbessertem Histogramm

Verändern wir also noch einmal das Statistikprogramm, an dem wir die ganze Woche lang gearbeitet haben. Die Version von gestern erzeugte ein horizontales Histogramm, das etwa so aussah:

    Haeufigkeit der einzelnen Zahlen:
    1  | *****
    2  | *************
    3  | *******************
    4  | ****************
    5  | ***********
    6  | ****
    43 | *
    62 | *

Heute wollen wir ein Diagramm mit vertikalen Balken ausgeben:

         *
         *
         *
         *
         *
         * *
         * * *
         * * *       *     *     *
         * * *   * * *     *     *
       * * * *   * * *     *     *
       * * * *   * * *     *     *
     * * * * * * * * *     *     *  *
     * * * * * * * * *  *  *  *  *  *  *  *
    ---------------------------------------
     1 2 3 4 5 6 7 8 9 12 23 25 34 37 39 42

Diese Diagrammform ist sehr viel schwieriger zu erstellen als die horizontale. Wir erstellen sie mit zwei verschachtelten `for`-Schleifen und großer Sorgfalt beim Zählen, damit auch alles an der richtigen Stelle landet.

Noch etwas ist in dieser Version von stats.pl`` anders: Sie holt sich die Daten aus einer Datei, anstatt sie am Prompt vom Benutzer eintippen zu lassen. Wie bei allen Skripts, die aus einer Datei lesen, müssen Sie diese Datei beim Aufruf des Skripts in der Kommandozeile angeben:

    % statsfinal.pl daten.txt

In der Datei (hier daten.txt) steht jede Zahl in einer einzelnen Zeile.

> Sie finden daten.txt auch auf der beiliegenden CD, wenn Sie keine Lust haben, die Zahlen selbst einzutippen.

Listing 7.1 zeigt den Code unseres endgültigen Statistikskripts. So viel, wie wir bereits damit gearbeitet haben, sollte es ihnen vertraut sein. Konzentrieren Sie sich auf die beiden Teile des Skripts, die anders sind als in der letzten Version: die Eingabeschleife, die die Daten aus der Datei liest (Zeile 14 bis 21), und den Code zur Ausgabe des neuen Histogramms (Zeile 36 bis 49).

Listing 7.1: Das Skript statsfinal.pl

    1:  #!/usr/bin/perl -w
    2:
    3:  $input = '';  # Benutzereingabe: Zahl
    4:  @nums = ();   # Array: Nums;
    5:  %freq = ();   # Hash: Zahl-Haeufigkeit
    6:  $maxfreq = 0; # hoechste Haeufigkeit
    7:  $count = 0;   # Anzahl aller Nums
    8:  $sum = 0;     # Summe
    9:  $avg = 0;     # Durchschnitt
    10: $med = 0;     # Median
    11: @keys = ();   # temp Keys
    12: $totalspace = 0; # gesamte Breite des Histogramms
    13:
    14: while (defined ($input = <>)) {
    15:     chomp ($input);
    16:     $nums[$count] = $input;
    17:     $freq{$input}++;
    18:     if ($maxfreq < $freq{$input}) { $maxfreq = $freq{$input} }
    19:     $count++;
    20:     $sum += $input;
    21: }
    22: @nums = sort { $a <=> $b } @nums;
    23:
    24: $avg = $sum / $count;
    25: $med = $nums[$count /2];
    26:
    27: print "\nAnzahl der eingegebenen Nums: $count\n";
    28: print "Summe der Nums: $sum\n";
    29: print "Kleinste Zahl: $nums[0]\n";
    30: print "Groesste Zahl: $nums[$#nums]\n";
    31: printf("Durchschnitt: %.2f\n", $avg);
    32: print "Mittelwert: $med\n\n";
    33:
    34: @keys = sort { $a <=> $b } keys %freq;
    35:
    36: for ($i = $maxfreq; $i > 0; $i--) {
    37:     foreach $zahl (@keys) {
    38:         $space = (length $zahl);
    39:         if ($freq{$zahl} >= $i) {
    40:             print( (" " x $space) . "*");
    41:         } else {
    42:             print " " x (($space) + 1);
    43:         }
    44:         if ($i == $maxfreq) { $totalspace += $space + 1; }
    45:     }
    46:     print "\n";
    47: }
    48: print "-" x $totalspace;
    49: print "\n @keys\n";

Da Sie den `<>`-Operator zum Einlesen von Daten aus Dateien bereits kennen, dürften die Zeilen 14 bis 21 Sie nicht überraschen. Beachten Sie, dass wir jede Zeile der Datei (also jede Zahl) der `$input`-Variablen zuweisen und diesen Wert dann im gesamten Block verwenden.

Warum `$input` und nicht `$_`? Wir hätten auch `$_` einsetzen können, aber viele der Anweisungen in diesem Block brauchen einen wirklichen Variablenbezug (sie greifen nicht von allein auf `$_` zurück). In diesem Beispiel würde der Code durch `$_` nur wenig kürzer, aber um einiges schwieriger zu lesen. Deshalb ist es hier die wohl bessere Idee, sich zugunsten der Lesbarkeit für eine eigene Variable zu entscheiden.

> Je mehr Möglichkeiten ich in diesem Buch erkläre, desto mehr Möglichkeiten stehen Ihnen auch zur Auswahl - dass Perl ein besonderes Feature anbietet, bedeutet noch lange nicht, dass Sie es auch verwenden müssen. Man sollte immer abwägen zwischen sehr kurzem Code, den nur noch Perl-Experten entziffern können, und längerem, vielleicht gar weniger effizientem, aber dafür lesbarem Code. Betrachten Sie Ihr Perl-Skript als besonders gut gemacht, wenn jemand anderes es einfach durchlesen kann.

Abgesehen davon, dass die Daten hier aus einer Datei anstatt von der Standardeingabe gelesen werden, unterscheidet sich dieser `while`-Block nur noch in einem Punkt von der letzten Version: Die neu hinzugefügte Anweisung in Zeile 18 berechnet den Wert von `$maxfreq`, die maximale Häufigkeit. Diese gibt an, wie oft die Zahl vorkommt, die am häufigsten aufgetaucht ist. Mit diesem Wert legen wir später die Gesamthöhe des Diagramms fest. Hier vergleichen wir die maximale Häufigkeit lediglich mit der aktuellen und verändern `$maxfreq`, wenn letztere größer ist.

Weiter unten im Skript - nachdem wir sortiert, summiert und die ersten Ergebnisse ausgegeben haben - kommen wir zum Histogrammteil, der etwas einschüchternden Anhäufung von Schleifen in den Zeilen 36 bis 49.

Ein Histogramm mit horizontalen Balken ist viel einfacher zu erstellen als eins mit vertikalen. Für das horizontale Histogramm brauchten wir vorgestern nur die Schlüssel im `%freq`-Hash zu durchlaufen und die entsprechende Zahl Sternchen auszudrucken (und das Ganze etwas zu formatieren). Für ein vertikales Histogramm müssen wir von Anfang an viel mehr Informationen über das Gesamtlayout parat haben, weil die Zeilen, die wir zeichnen, in keinem direkten Zusammenhang mit einem bestimmten Schlüssel oder Wert im Hash stehen. Außerdem müssen wir auch die Leerstellen für die Formatierung beachten.

Wir bauen das Histogramm mit zwei Schleifen. Die äußere, eine `for`-Schleife, kontrolliert die Anzahl der auszugebenden Zeilen, das heißt die Höhe von oben nach unten. Die zweite, eine `foreach`-Schleife, bewegt sich innerhalb jeder Zeile von links nach rechts und schreibt entweder ein Sternchen oder ein Leerzeichen in jede Spalte. Mit diesen beiden verschachtelten Schleifen (der `for`-Schleife außen und der `foreach`- Schleife innen) können wir uns von Zeile zu Zeile und von links nach rechts bewegen und sowohl die Höhe als auch die Breite des Histogramms von unseren Daten abhängig machen.

Zuerst erstellen wir in Zeile 34 eine sortierte Liste aller Schlüssel des `%freq`-Hash, vor allem aus Bequemlichkeit und damit die `for`-Schleifen wenigstens etwas durchschaubarer werden.

In Zeile 36 startet unsere äußere `for`-Schleife. Die Gesamthöhe des Diagramms wird von der am häufigsten auftretenden Zahl in unseren Daten bestimmt. Hier kommt der beim Einlesen ermittelte Wert von `$maxfreq` zum Einsatz. Die äußere `for`-Schleife beginnt bei diesem Höchstwert und arbeitet sich hinunter bis zur 0, wo sie abbricht. So erhalten wir die richtige Anzahl Zeilen.

Die innere Schleife ist für die einzelnen Zeilen zuständig. Sie durchläuft die Schlüssel des `%freq`-Hash (also unsere Zahlen) und überprüft, ob in der aktuellen Zeile bei dieser Zahl ein `*` oder ein Leerzeichen gesetzt werden muss. Wir achten auch hier auf die Formatierung und fügen für die Spalten mit mehrstelligen Zahlen entsprechend mehr Leerstellen hinzu (`$space` wird für eine Zahl 333 einen anderen Wert haben als für 1).

Zeile für Zeile machen wir ab Zeile 38 folgendes:

- In Zeile 38 berechnen wir die für diese Spalte benötigten Leerstellen, abhängig davon, wie viele Stellen der aktuelle Wert hat.
- Zeile 39 entscheidet, ob ein Sternchen ausgegeben werden muss. Hier wird überprüft, ob die Häufigkeit der Zahl, die wir gerade betrachten, unserer vertikalen Position im Histogramm entspricht (der Häufigkeitswert also größer oder gleich der augenblicklichen Höhe des Balkens, dem aktuellen Wert von `$i`, ist`)`. Je weiter wir im Histogramm nach unten gehen, das heißt je kleiner `$i` wird, desto mehr Zahlen erfüllen dieses Kriterium.
- Wenn nach Zeile 39 ein Sternchen gesetzt werden muss, gibt Zeile 40 sowohl das Sternchen als auch die für die korrekte Spaltenbreite nötigen Leerzeichen aus.
- Wenn kein Sternchen erforderlich ist, geben wir mit den Zeilen 41 und 42 die richtige Anzahl von Füllzeichen aus: Spaltenbreite plus ein Leerzeichen extra.
- Zeile 44 ist knifflig. Diese Zeile berechnet die Gesamtbreite des Histogramms, basierend auf der Anzahl der Stellen aller Zahlen und den Leerzeichen dazwischen. Das Ergebnis brauchen wir zwar erst später, in Zeile 48, zum Ausgeben einer Trennlinie, aber wenn wir schon einmal mitten in der Schleife sind, können wir es auch gleich jetzt berechnen. Diese Schleife macht folgendes: Wenn `$i` gleich `$maxfreq` ist - das heißt, wenn wir die erste Zeile des Diagramms erstellen -, addiert diese Schleife die Anzahl der in der aktuellen Spalte erforderlichen Leerstellen zur Variablen `$totalspace`. Nach dem ersten vollständigen Durchlauf der `foreach`-Schleife enthält `$totalspace` genau die Gesamtbreite des Histogramms.
- Wenn wir mit einer Histogrammzeile fertig sind, gibt die äußere `for`-Schleife schließlich in Zeile 46 einen Zeilenvorschub aus - und beginnt alles wieder von vorn.

Wenn wir die Sternchen des Histogramms ausgegeben haben, fehlen nur noch die Bezeichnungen für die Spalten. Hier simulieren wir mit Bindestrichen eine Querlinie (die Anzahl der erforderlichen Bindestriche haben wir bereits mit dem Wert von `$totalspace` berechnet) und schreiben unsere Zahlen darunter: einen String, der die Variable `@keys` interpoliert und alle Elemente von `@keys`, durch je ein Leerzeichen getrennt, ausgibt.

Kompliziert verschachtelte Schleifen sind häufig nur schwer nachzuvollziehen. Manchmal reicht eine Erklärung wie meine einfach nicht aus. Wenn dieses Beispiel Sie auch jetzt noch verwirrt, versuchen Sie, es mit ein paar kleinen Beispielzahlen selbst »durchzuspielen«. Verfolgen Sie Schritt für Schritt und Schleife für Schleife die »Entwicklung« der verschiedenen Werte - das hilft Ihnen, die Zusammenhänge zwischen den einzelnen Variablen klarer zu erkennen.

## Ein Zahlenbuchstabierer

Unser zweites Beispiel hat im wirklichen Leben nicht besonders viel Sinn, aber es zeigt ein paar komplexere Einsatzmöglichkeiten für `if` und `while`. Dieses Skript bittet Sie um die Eingabe einer einstelligen Zahl (und fängt Strings oder mehrstellige Zahlen ab). Dann schreibt es die Zahl als Wort aus, also `2` als `zwei` und `5` als `fuenf` und so weiter. Schließlich fragt es, ob Sie eine weitere Zahl eingeben möchten, und wenn ja, wiederholt es den gesamten Prozeß. Hier ein Beispiel, wie es ablaufen könnte:

    % zahlenbuchstabierer.pl
    Geben Sie die zu buchstabierende Zahl ein: buh
    Keine Strings. Eine Zahl von 0 bis 9 bitte.
    Geben Sie die zu buchstabierende Zahl ein: 45
    Zu hoch. 0 bis 9 bitte.
    Geben Sie die zu buchstabierende Zahl ein:: 6
    6 ist sechs.
    Eine weitere Zahl versuchen (j/n)?: weiss nicht
    j oder n bitte.
    Eine weitere Zahl versuchen (j/n)?: j
    Geben Sie die zu buchstabierende Zahl ein: 3
    3 ist drei.
    Eine weitere Zahl versuchen (j/n)?: n

Listing 7.2 zeigt den kompletten Code.

Listing 7.2: Das Skript zahlenbuchstabierer.pl.

    1:  #!/usr/bin/perl -w
    2:  # zahlenbuchstabierer:  buchstabiert Zahlen
    3:  # Simple Version, nur einstellige Zahlen
    4:
    5:  $num = 0;    # Zahl
    6:  $exit = "";  # Programm verlassen? j oder n.
    7:
    8:  while ($exit ne "n") {
    9:
    10:     while () {
    11:         print 'Geben Sie die zu buchstabierende Zahl ein: ';
    12:         chomp($num = <STDIN>);
    13:         if ($num ne "0" && $num == 0) { # wenn $num ein String
    14:             print "Keine Strings. Eine Zahl von 0 bis 9 bitte.\n";
    15:             next;
    16:         }
    17:         if ($num > 9) { # wenn $num mehrstellige Zahl
    18:             print "Zu hoch. 0 bis 9 bitte.\n";
    19:             next;
    20:         }
    21:         if ($num < 0) { # wenn $num negative Zahl
    22:             print "Keine negativen Zahlen. 0 bis 9 bitte.\n";
    23:             next;
    24:         }
    25:         last;
    26:     }
    27:
    28:     print "$num ist ";
    29:     if ($num == 1) { print 'eins'; }
    30:     elsif ($num == 2) { print 'zwei'; }
    31:     elsif ($num == 3) { print 'drei'; }
    32:     elsif ($num == 4) { print 'vier'; }
    33:     elsif ($num == 5) { print 'fuenf'; }
    34:     elsif ($num == 6) { print 'sechs'; }
    35:     elsif ($num == 7) { print 'sieben'; }
    36:     elsif ($num == 8) { print 'acht'; }
    37:     elsif ($num == 9) { print 'neun'; }
    38:     elsif ($num == 0) { print 'null'; }
    39:     print "\n";
    40:
    41:     while () {
    42:         print 'Eine weitere Zahl versuchen (j/n)?: ';
    43:         chomp ($exit = <STDIN>);
    44:         $exit = lc $exit;
    45:         if ($exit ne 'j' && $exit ne 'n') {
    46:             print "j oder n bitte.\n";
    47:         }
    48:         else { last; }
    49:     }
    50: }

Beginnen wir bei der äußeren Schleife, und arbeiten wir uns nach innen vor. Die erste `while`-Schleife, von Zeile 8 bis 50, enthält fast das gesamte Skript, denn sie soll, wenn am `ja/nein`-Prompt `j` eingegeben wird, auch fast das gesamte Skript noch einmal ausführen. Weil wir sie mindestens einmal laufen lassen möchten, muss die Bedingung in Zeile 8 am Anfang wahr sein (in weiser Voraussicht haben wir die Variable `$exit` dementsprechend initialisiert).

Die zweite `while`-Schleife, von Zeile 10 bis 26, überprüft die Benutzereingaben. Das erste `if` stellt sicher, dass Sie keine Strings eingegeben haben (wenn `$num` nicht das Zeichen `0` ist, aber im numerischen Vergleich zu 0 ausgewertet wird, muss `$num` ein String sein). Das zweite `if` sieht nach, ob Sie eine Zahl größer 9 eingegeben haben (diese Version des Skripts buchstabiert nur einstellige Zahlen), und das dritte prüft auf negative Zahlen. Wenn eine dieser Bedingungen erfüllt ist, überspringen wir mit dem Schleifensteuerbefehl `next` den Rest des Blocks und gehen zurück zum Anfang der Schleife, in der wir uns unmittelbar befinden - das ist in diesem Fall immer noch die endlose `while`-Schleife in den Zeilen 10 bis 26. Wenn die Eingabe unseren Kriterien entspricht, also kein String und eine Zahl zwischen 0 und 9 ist, dann steigen wir mit `last` aus dieser `while`-Schleife aus und machen mit der ersten ihr folgenden Anweisung weiter.

Diese Anweisung steht in Zeile 28. Wir geben den ersten Teil der Ergebnismeldung aus und gehen dann durch einen Satz von `if`- und `elsif`-Anweisungen, die für die aktuelle Zahl den entsprechenden String auf den Bildschirm schreiben. Dieser Abschnitt (Zeile 29 bis 38) wäre ein klassischer Fall für eine `switch`-Anweisung, mit der wir hier nicht immer wieder das gleiche eintippen müßten (den Programmiergöttern sei Dank für Copy-and-Paste).

Sobald wir unser Zahlwort ausgegeben haben, fragen wir in der letzten `while`-Schleife (Zeile 41 bis 49), ob der gesamte Vorgang von vorn beginnen soll. Als Antwort erwarten wir ein `j` oder ein `n`. Beachten Sie den Aufruf der Funktion `lc` in Zeile 44 - wenn der Benutzer Großbuchstaben eingibt, wandelt `lc` sie vor der Überprüfung in Kleinbuchstaben um - damit akzeptieren wir also auch `J` oder `N`, ohne explizit darauf prüfen zu müssen.

Wie Sie sehen, bestimmen wir hier nicht, was bei einem `j` oder `n` passieren soll, wir überprüfen nur, ob tatsächlich `j` oder `n` eingegeben wurde. Mehr ist gar nicht nötig. Sobald `$exit` einen gültigen Wert hat, endet die äußere `while`-Schleife, und wir kommen direkt zum Anfang, zur Schleifenbedingung in Zeile 8, zurück. Hier entscheiden wir über den weiteren Ablauf: Bei einem `n` bricht die äußere `while`- Schleife wegen nicht erfüllter Schleifenbedingung ab, und das Skript ist beendet. Anderenfalls beginnen wir den Durchlauf von vorn und machen so lange weiter, bis der Anwender ein `n` eingibt.

## Simple Text-zu-HTML-Konvertierung

Beenden wir dieses Kapitel mit einem etwas nützlicheren Skript: webseite.pl liest eine einfache Textdatei, fragt vom Benutzer ein paar grundlegende Einstellungen ab und spuckt dann eine HTML-Version der Datei aus. Dieses Skript ist kein besonders raffinierter HTML-Generator. Es läßt Sie lediglich Vorder- und Hintergrundfarbe, eine Überschrift sowie Ihre E-Mail-Adresse vorgeben, und im Text selbst fügt es nur Absatz-Tags an den richtigen Stellen ein. Aber Sie erhalten immerhin eine grundlegende HTML-Vorlage, mit der Sie weiterarbeiten können.

### Der Ablauf

Bevor es Ihren Text in HTML konvertiert, bittet das webseite-Skript Sie um ein paar Angaben:

- Titel der Seite (`<TITLE>`...`</TITLE>` in HTML)
- Hintergrund- und Textfarbe. Zur Wahl stehen hier nur die von HTML unterstützten Namen der 16 Grundfarben, alles andere fangen wir ab. Hierzu geben wir außerdem etwas rudimentäre Online-Hilfe.
- eine Überschrift am Anfang der Seite (`<H1>`...`</H1>` in HTML)
- eine E-Mail-Adresse, die als Link am unteren Ende der fertigen HTML-Seite eingefügt wird.

Ein Ablauf von webseite.pl könnte zum Beispiel folgendermaßen aussehen:

    % webseite.pl heine.txt
    Geben Sie den Titel Ihrer Webseite ein: Heinrich Heine, Reisebilder, Dritter Teil
    Geben Sie die Hintergrundfarbe ein (? zeigt Auswahl): ?
    Zur Wahl stehen folgende Farben:
    black (schwarz), maroon (dunkelbraun),
    green (gruen), olive (olivgruen),
    navy (dunkelblau), purple (violett),
    teal (blaugruen), gray (grau),
    silver (silbergrau), red (rot),
    lime (hellgruen), yellow (gelb),
    blue (blau), fuchsia (pink),
    aqua (hellblau), white (weiss),
    oder Return fuer keine Farbe
    Geben Sie die Hintergrundfarbe ein (? zeigt Auswahl): white
    Geben Sie Textfarbe ein (? zeigt Auswahl): black
    Geben Sie eine Ueberschrift ein: Kapitel XXVI
    Geben Sie Ihre E-Mail-Adresse ein: lemay@lne.com
    ******************************
    <HTML>
    <HEAD>
    <TITLE>Heinrich Heine, Reisebilder, Dritter Teil</TITLE>
    </HEAD>
    <BODY BGCOLOR="white" TEXT="black">
    <H1>Kapitel XXVI</H1>
    <P>
    Die Natur wollte wissen, wie sie aussieht, und sie erschuf Goethe.

(... und so weiter, hier aus Platzgründen nicht aufgeführt).

    <P>
    Es liegt Wahrheit in diesen Worten und ich bin sogar der Meinung, dass Goethe manchmal seine Sache noch besser gemacht hätte als der liebe Gott selbst und dass er z.B. den Herrn Eckermann viel richtiger, ebenfalls mit Federn und grün erschaffen hätte.
    <P>
    <HR>
    <ADDRESS><A HREF="mailto:lemay@lne.com">lemay@lne.com</A></ADDRESS>
    </BODY>
    </HTML>

Der fertige HTML-Code wird von diesem Skript nur auf den Bildschirm ausgegeben - das nützt Ihnen herzlich wenig, ich weiß - und bitte um etwas Geduld. Wie Sie Daten in eine Datei statt an die Standardausgabe leiten, behandeln wir an Tag 15. Bis dahin könnten Sie den hier ausgegebenen HTML-Text mit Copy-and-Paste in einen Texteditor übernehmen und als HTML-Datei speichern. Wenn Sie diese Datei dann in einen Webbrowser laden, sehen Sie das Ergebnis von webseite.pl. Unser Beispiel ergäbe etwa folgendes Bild:

Abbildung 7.1: Das Ergebnis des Skripts webseite.pl

### Die Eingabedatei

Eine Bemerkung zu der Textdatei, die Sie an webseite.pl zum Konvertieren übergeben: Das Skript geht davon aus, dass diese aus Absätzen besteht, die durch Leerzeilen voneinander getrennt sind. Hier zum Beispiel der Inhalt der Datei heine.txt, die ich für das Beispiel oben verwendet habe:

    Die Natur wollte wissen, wie sie aussieht, und sie erschuf Goethe. Sogar die Gedanken, die Intentionen der Natur vermag er uns widerzuspiegeln, und es ist einem hitzigen Goethianer, zumal in den Hundstagen, nicht zu verargen, wenn er über die Identität der Spiegelbilder mit den Objekten selbst so sehr erstaunt, dass er dem Spiegel sogar Schöpfungskraft, die Kraft, ähnliche Objekte zu erschaffen, zutraut.

    Ein Herr Eckermann hat mal ein Buch über Goethe geschrieben, worin er ganz ernsthaft versichert: Hätte der liebe Gott bei Erschaffung der Welt zu Goethe gesagt: "Lieber Goethe, ich bin jetzt gottlob fertig, ich habe jetzt alles erschaffen, bis auf die Vögel und die Bäume, und du tätest mir eine Liebe, wenn du statt meiner diese Bagatellen noch erschaffen wolltest" - so würde Goethe, ebensogut wie der liebe Gott, diese Tiere und Gewächse ganz im Geiste der übrigen Schöpfung, nämlich die Vögel mit Federn und die Bäume grün, erschaffen haben.

    Es liegt Wahrheit in diesen Worten und ich bin sogar der Meinung, dass Goethe manchmal seine Sache noch besser gemacht hätte als der liebe Gott selbst und dass er z.B. den Herrn Eckermann viel richtiger, ebenfalls mit Federn und grün erschaffen hätte. Es ist wirklich ein Schöpfungsfehler, dass auf dem Kopfe des Herrn Eckermann keine grünen Federn wachsen, und Goethe hat diesem Mann wenigstens dadurch abzuhelfen gesucht, dass er ihm einen Doktorhut aus Jena verschrieben und eigenhändig aufgesetzt hat.

> Ich habe hier ausnahmsweise auf die ASCII-sichere Umschreibung von Umlauten und ß verzichtet - schließlich geht es bei Webseiten nicht zuletzt um die Ästhetik der Darstellung. Am Tag 1 habe ich erklärt, warum Ihr Perl-Interpreter damit nicht ohne weiteres umgehen kann. Wenn Sie diese Textdatei in dem Zeichensatz speichern, den Ihr Perl-Interpeter voraussetzt, werden Sie innerhalb Ihres Systems erst einmal keine Schwierigkeiten haben - beachten Sie jedoch, dass diese Datei auf dem Rechner Ihrer Brieffreundin in Kopenhagen (oder auch nur bei Ihrem Nachbarn, mit dessen Betriebssystem Sie nie arbeiten würden) zu ganz anderen, unerwünschten Ergebnissen führen kann. Übrigens haben Umlaute auch in HTML-Code eigentlich nichts zu.

### Das Skript

Listing 7.3 zeigt den Code für unser Skript.

Listing 7.3: webseite.pl

    1:  #!/usr/bin/perl -w
    2:  #
    3:  # webseite:  simple Textdatei-zu HTML-Konvertierung
    4:  # *sehr* simpel.  Keine Sonderzeichen, Links, Fett- oder
    5:  # andere Formatierungen, etc.
    6:  # Leerzeile in Textdatei == Absatz in HTML-Code
    7:
    8:  $title = '';                    # <TITLE>, Titel
    9:  $bgcolor = '';                  # BGCOLOR, Hintergrundfarbe
    10: $text = '';                     # TEXT, Textfarbe
    11: $head = '';                     # <H1>, erste Ueberschrift
    12: $mail = '';                     # E-Mail-Adresse
    13:
    14: print " Geben Sie den Titel Ihrer Webseite ein: ";
    15: chomp($title = <STDIN>);
    16:
    17: foreach $farbe ('Hintergrund', 'Text') { # laeuft zweimal: einmal
    18:                                          # je Farbe
    19:     $in = '';                   # temp. Eingabe
    20:     while () {
    21:        print "Geben Sie die ${farbe}farbe ein (? zeigt Auswahl): ";
    22:         chomp($in = <STDIN>);
    23:         $in = lc $in;
    24:
    25:         if ($in eq '?') {       # Hilfe anzeigen
    26:             print "\nZur Wahl stehen folgende Farben:\n";
    27:             print "black (schwarz), maroon (dunkelbraun),\n";
    28:             print "green (gruen), olive (olivgruen),\n";
    29:             print "navy (dunkelblau), purple (violett),\n";
    30:             print "teal (blaugruen), gray (grau),\n";
    31:             print "silver (silbergrau), red (rot),\n";
    32:             print "lime (hellgruen), yellow (gelb),\n";
    33:             print "blue (blau), fuchsia (pink),\n";
    34:             print "aqua (hellblau), white (weiss),\n";
    35:             print "oder Return fuer keine Farbe\n\n";
    36:             next;
    37:         } elsif ($in eq '' or
    38:                  $in eq 'black' or
    39:                  $in eq 'maroon' or
    40:                  $in eq 'green' or
    41:                  $in eq 'olive' or
    41:                  $in eq 'navy' or
    42:                  $in eq 'purple' or
    43:                  $in eq 'teal' or
    44:                  $in eq 'gray' or
    45:                  $in eq 'silver' or
    46:                  $in eq 'red' or
    47:                  $in eq 'lime' or
    48:                  $in eq 'yellow' or
    49:                  $in eq 'blue' or
    50:                  $in eq 'fuchsia' or
    51:                  $in eq 'aqua' or
    52:                  $in eq 'white') { last; }
    53:         else {
    54:             print "Das ist kein gueltiger Farbname.\n";
    55:         }
    56:     }
    57:
    58:     if ($farbe eq 'Hintergrund') {  # Hintergrundfarbe
    59:         $bgcolor = $in;
    60:     } else {                        # Textfarbe
    61:         $text = $in;
    62:     }
    63: }
    64:
    65: print "Geben Sie eine Ueberschrift ein: ";
    66: chomp($head = <STDIN>);
    67:
    68: print "Geben Sie Ihre E-Mail-Adresse ein: ";
    69: chomp($mail = <STDIN>);
    70:
    71: print '*' x 30;
    72:                             # jetzt geht der HTML-Code los:
    73: print "\n<HTML>\n<HEAD>\n<TITLE>$title</TITLE>\n";
    74: print "</HEAD>\n<BODY";
    75: if ($bgcolor ne '') { print qq( BGCOLOR="$bgcolor"); }
    76: if ($text ne '') { print qq( TEXT="$text"); }
    77: print ">\n";
    78: print "<H1>$head</H1>\n<P>";
    79:
    80: while (<>) {
    81:     if ($_ eq "\n") {
    82:         print "<p>\n";
    83:     } else {
    84:         print $_;
    85:     }
    86: }
    87:
    88: print qq(<HR>\n
    89:          <ADDRESS><A HREF="mailto:$mail">$mail</A></ADDRESS>\n);
    90: print "</BODY>\n</HTML>\n";

Dieses Skript ist nicht besonders komplex oder syntaktisch raffiniert. Es verwendet nicht einmal Arrays oder Hashes (Wofür auch? Es gibt nichts, was hier gespeichert oder berechnet werden müßte) - nur viele, viele Schleifen und Bedingungen.

Betrachten wir zuerst die große `foreach`-Schleife, die in Zeile 17 beginnt. Diese Schleife ist zuständig für die Frage nach Hintergrund- und Textfarbe. Weil sich diese beiden Eingabeaufforderungen exakt gleich verhalten, wollte ich denselben Code nicht für jede einzelne Aufforderung wiederholen (insbesondere, weil die `if`-Bedingungen in Zeile 37 bis 53 wirklich reichlich sind). Später werden Sie lernen, wie man sich derartig wiederholenden Code in eine Subroutine packt und dann lediglich diese Subroutine zweimal aufruft. Doch jetzt, wo wir viel über Schleifen, aber nichts über Subroutinen wissen, habe ich mich für eine `foreach`-Schleife entschieden.

Die Schleife wird zweimal durchlaufen, einmal für den String `'Hintergrund'` und einmal für den String `'Text'`. Wir benutzen diese Strings für die Eingabeaufforderung und später zur Zuweisung der eingegebenen Werte an die entsprechende Variable (`$bgcolor` oder `$text`).

Innerhalb der `foreach`-Schleife haben wir eine andere, eine unendliche `while`- Schleife, die die Eingabeaufforderungen so lange wiederholt, bis wir akzeptable Eingaben haben (Fehlerbehandlung ist immer eine gute Programmierübung). Der Benutzer kann 18 verschiedene Dinge eingeben: eine der sechzehn Grundfarben, ein Fragezeichen oder gar nichts.

Die Bedingungen in Zeile 25 bis 55 durchlaufen jede dieser Möglichkeiten, zuerst die Eingabe von `?`. Als Antwort auf ein Fragezeichen müssen wir lediglich eine hilfreiche Mitteilung ausgeben und dann mit `next` zum nächsten Durchlauf der `while`-Schleife springen (also, die Eingabeaufforderung neu anzeigen und auf weitere Eingaben warten).

Die nächste Überprüfung (ab Zeile 37) stellt sicher, dass wir korrekte Eingaben haben, nämlich entweder einen Zeilenvorschub (dann haben wir eine leere Eingabe und legen keine Farbe fest1) oder eine der sechzehn Grundfarben. Beachten Sie, dass hier immer auf kleingeschriebene Farbnamen überprüft wird. In Zeile 23 haben wir mit der Funktion `lc` die Eingabestrings komplett in Kleinbuchstaben umgewandelt; deswegen kann der Benutzer auch `BLACK` oder `Black` eingeben - wir haben alle Möglichkeiten zu einer zusammengefaßt (aber bequemerweise die Eingabe von `?` nicht berührt).

Wenn die Eingabe einer dieser Vorgaben entspricht, steigen wir in Zeile 52 mit `last` aus der `while`-Schleife aus (Sie erinnern sich, `next` und `last` beziehen sich ohne Labels auf die nächste sie umgebende Schleife - hier also auf die `while`-, nicht auf die `foreach`-Schleife). Wenn uns die Eingabe nicht paßt, springen wir zum letzten `else`- Fall in Zeile 53, geben eine Fehlermeldung aus und beginnen die `while`-Schleife von vorn.

Die letzte Überprüfung in der `foreach`-Schleife (Zeile 58 bis 62) stellt fest, ob es sich bei der Eingabe um den Wert für die Hintergrund- oder die Textfarbe handelt, und weist den Farbwert dann der entsprechenden Variablen zu.

Der Rest des Skripts, von Zeile 73 bis zum Ende, gibt den Anfang unserer HTML-Datei aus, liest und konvertiert die in der Kommandozeile angegebene Textdatei und setzt die abschließenden HTML-Tags ans Ende. Beachten Sie die Überprüfungen in Zeile 75 und 76: Wenn `$bgcolor` und `$text` keine Werte haben, schreiben wir diese Attribute gar nicht erst in den HTML-Tag `<BODY>`. (Einfacher wäre es, sie dort stehen zu lassen,`BGCOLOR=""` oder `TEXT=""`, aber das hätte nicht besonders nett ausgesehen.)

Beachten Sie auch die Verwendung der Funktion `qq`. Sie haben `qq` an Tag 2 im Vertiefungsabschnitt kennengelernt. Mit der `qq`-Funktion erstellt man einen double- quoted String, ohne doppelte Anführungszeichen zu verwenden. Wenn ich hier doppelte Anführungszeichen verwendet hätte, hätte ich vor die Anführungszeichen im String selbst einen Backslash setzen müssen. Ich finde, so sieht es besser aus.

Die Zeilen 80 bis 86 lesen (mit `<>`) die Textdatei und geben dann einfach alles wieder aus, nur mit Absatz-Tags (`<P>`) statt Leerzeilen. Eine robustere Version dieses Skripts würde nach Sonderzeichen suchen (Akzenten, Umlauten und so weiter) und sie durch ihre entsprechenden HTML-Codes ersetzen - aber diese Aufgabe läßt sich mit Pattern Matching (Mustervergleich) viel einfacher lösen; deswegen heben wir uns das für später auf.

Zum Abschluß bleibt nur noch die Ausgabe des E-Mail-Links (mit einem HTML `mailto` und Link-Tags) und der Tags zum Beenden der HTML-Datei (`</BODY></HTML>`).

## Zusammenfassung

Programmierlehrbüchern mangelt es selten an wortreichen Erklärungen, aber häufig an konkreten Beispielen. Ich möchte mich nicht um ausführliche Erklärungen drücken (wer lacht da?), aber diese Lektion - und die zwei weiteren an den Tagen 14 und 21 - zeigt Ihnen etwas längeren Code, der ohne thematische Trennung die Techniken aus den vorangegangenen Lektionen anwendet und vielleicht sogar eine mehr oder weniger nützliche Aufgabe erfüllt (obwohl ich nicht sicher bin, wie oft Sie eine Zahl werden buchstabieren müssen).

Wir haben uns heute drei Skripts angesehen: statsfinal.pl, eine weitere Neuauflage des vertrauten Statistik-Skripts, gab - nach sorgfältigem Maßnehmen - mit Hilfe zweier verschachtelter `for`-Schleifen ein vertikales Histogramm aus. Das zweite, zahlenbuchstabierer.pl, arbeitete mit sehr vielen Bedingungen, um schließlich eine einstellige Zahl zu buchstabieren. Das dritte, webseite.pl, nahm in der Kommandozeile eine Textdatei entgegen, fragte nach ein paar Angaben und konvertierte den Inhalt der Textdatei in eine Webseite.

Herzlichen Glückwunsch, Sie haben die erste Woche dieses Drei-Wochen-Buches und bereits ein mächtiges Stück der Sprache bewältigt. Darauf bauen wir weiter auf. Auf zur Woche 2!

1

Die Farbwahl bleibt dann den Standardeinstellungen des Webbrowsers überlassen.

© Markt&Technik Verlag, ein Imprint der Pearson Education Deutschland GmbH