Inhaltsverzeichnis

2.17 Textfiles

Die im letzten Abschnitt vorgestellten Files besitzen Komponenten eines beliebigen skalaren oder zusammengesetzten Typs. Dadurch können effizient und kompakt alle Werte der Komponenten auf einem Hintergrundspeicher dargestellt werden. Jedoch werden die Werte in codierter Form (als Bytefolgen) gespeichert. Diese Codierung der Daten ist eine für den Menschen oder Programme in anderen Programmiersprachen ungeeignete Darstellungsform. Da zur Ein- und Ausgabe bevorzugt Zeichenfolgen, wie

Dies ist ein Beispiel für
einen Text, der sich
über drei Zeilen erstreckt.

verwendet werden, spielen Files mit dem Komponententyp CHAR eine besondere Rolle. In Pascal existiert deshalb ein vordefinierter Typbezeichner:

TYPE TEXT = FILE OF CHAR;

Files des Typs TEXT bestehen also aus einer (beliebig) langen Folge von Zeichen. Sie kennen bereits zwei Filevariablen, die den Typ TEXT besitzen. Es sind dies die im Programmkopf genannten Namen INPUT und OUTPUT, welche folgendermaßen vordefiniert sind:

VAR INPUT, OUTPUT: TEXT;

In Abschnitt 2.2 wurde erklärt, daß INPUT und OUTPUT die Standardeingabe von der Tastatur bzw. die Standardausgabe an den Bildschirm symbolisieren. Eingaben von der Tastatur und Ausgaben an den Bildschirm stellen nichts anderes als Folgen von Zeichen des Typs CHAR dar, die nur sequentiell lesend oder schreibend bearbeitet werden können.

Aber auch alle anderen Peripheriegeräte am C-128 kommunizieren mit dem Computer über Zeichenfolgen, die vom Drucker gedruckt, auf der Floppy gespeichert, und von einem Modem eingelesen werden. Zwar könnte man eine zeichenweise Aus- und Eingabe auch auf Textfiles mit den Standardfileoperationen PUT und GET vornehmen, jedoch existieren einige spezielle Prozeduren und Funktionen in Pascal, mit denen komplette Zeichenfolgen bearbeitet werden können:

In Abschnitt 2.5 wurde nämlich nur eine Kurzform der Prozeduren READ(LN) und WRITE(LN) vorgestellt, in der alle Ausgaben am Bildschirm und alle Eingaben von der Tastatur erfolgen. In der allgemeinen Form dieser Prozeduren muß als erster Parameter in der Parameterliste ein File vom Typ TEXT (also FILE OF CHAR) genannt werden, auf dem die auszugebenden Daten zu speichern sind, oder von dem die Eingabedaten stammen:

WRITE(Filevariable, Ausdruck, Ausdruck, ..., Ausdruck)
WRITELN(Filevariable)
WRITELN(Filevariable, Ausdruck, Ausdruck, ...., Ausdruck)

Die obigen Prozeduren speichern Zeichenfolgen in dem durch Filevariable spezifizierten File. Die Ausdrücke können wie in Abschnitt 2.5 beschrieben um Formatierungsparameter erweitert werden. Mit der Prozedur WRITELN können Sie den ausgegebenen Text (wie es ebenfalls in Abschnitt 2.5 für den Bildschirm erläutert wurde) in Zeilen gliedern.

PROGRAM TEXTFILE(INPUT,OUTPUT);
  VAR F: TEXT;
      S: STRING;
BEGIN
  OPEN(F,8,3, 'TEXT,SEQ,WRITE');
  WRITELN(F,'Dies ist ein Beispiel für');
  WRITELN(F,'einen Text, der sich');
  WRITELN(F,'über drei Zeilen erstreckt.');
  CLOSE(F);
 
  OPEN(F,8,3, 'TEXT,SEQ,READ');
  WHILE NOT EOF(F) DO
    BEGIN
      READLN(F,S); WRITELN(S);
    END;
  CLOSE(F);
END.

Bild 81: Zeilenstrukturen in Textfiles

Das Ende einer Zeile wird in Files (und am Bildschirm) mit einem Steuerzeichen markiert. In Pascal 2.0 ist dies das Zeichen CHR(13) (carriage return im ASCII). Jedoch müssen Sie sich nicht um solche Realisierungsdetails kümmern, da spezielle Funktionen und Prozeduren zur Behandlung der Zeilenstruktur in Textfiles existieren.

Ist der erste Parameter einer WRITE-Anweisung keine Filevariable, so wird das Standardfile OUTPUT zur Ausgabe benutzt:

WRITELN('^':50 , 'ERROR', ERRNUM:3)    entspricht
WRITELN(OUTPUT,'^':50, 'ERROR', ERRNUM:3)

Wie alle anderen Files auch, müssen Files mit dem Komponententyp CHAR vor Schreiboperationen mit RESET (OPEN in Pascal 2.0) zum Schreiben eröffnet werden. In dem in Bild 82 angegebenen Programm wird wieder das Muster für eine zeilenweise Ausgabe verwendet, wie es Ihnen bereits in Abschnitt 2.5 und bei der Ausgabe von Matrizen in Abschnitt 2.9.3 begegnet ist.

Das Programm gibt eine ASCII-Tabelle (also eine Liste aller darstellbaren Zeichen in der Reihenfolge ihrer Codierung) auf einem Drucker aus. Die Variable I gibt die momentan ausgebene Zeile an. In der inneren For-Anweisung für die Variable J wird der ASCII-Code mit der Schrittweite 15 berechnet. Jede Zeile wird mit WRITELN(D) beendet. Bei der Ausgabe werden die Zeichen mit den Codes zwischen 0 und 31 sowie 127 und 159 nicht gedruckt, da sie am Drucker nur Steuerfunktionen (Schriftartwechsel, Papiervorschub etc.) besitzen.

PROGRAM ASCII (INPUT,OUTPUT);
  VAR I,J: 0..15;
      D  : TEXT; (* Filevariable zur Ausgabe *)
BEGIN
  OPEN(D,4,0); (* Eröffne den Druckerkanal *)
  FOR I:= 0 TO 15 DO
    BEGIN
      FOR J:= 0 TO 15 DO
        IF J IN [0,1,8,9] THEN 
          (* Steuerzeichen auslassen *)
          WRITE(D,' ':3)
        ELSE
          WRITE(D,' ',CHR(I+16*J),' ');
      WRITELN(D);
    END;
  CLOSE(D)
END.

Bild 82: Ausgabe einer ASCII-Tabelle

Sollten Sie keinen Drucker besitzen, so können Sie die Ausgabe auch auf einem anderen Peripheriegerät vornehmen. Dazu müssen Sie nur im OPEN-Befehl den Geräteparameter (4 für Druckerausgabe) ändern. So besitzt z.B. der Bildschirm die Geräteadresse 3. In diesem Buch können natürlich nicht alle Eigenheiten des Betriebssystems des C-128 besprochen werden. Solche Informationen finden Sie einerseits im BASIC-Handbuch und andererseits bei den Bedienungsanleitungen der Geräte (Drucker, Floppy). Dort wird auch die Bedeutung der Sekundäradresse (0) für jedes Gerät erläutert.

OPEN(D,4,7)                   Beim Drucker MPS-802 Ausgabe in Kleinschrift
OPEN(D,3,0)                   Ausgabe auf den Bildschirm
OPEN(D,1,2,'ASCII')           Ausgabe auf ein Kassetten-File mit dem Namen ASCII
OPEN(D,8,3,'ASCII,SEQ,WRITE')

Die letzte Angabe erzeugt eine sequentielle Datei mit dem Namen »ASCII« auf der Diskette mit der Geräteadresse 8. Solche Textdateien können Sie übrigens mit dem Editor des Pascal-Systems lesen und schreiben (primary-commands INPUT und OUTPUT s. 4.2.10 und 4.2.11).

Eine sehr nützliche Möglichkeit von Pascal 2.0 besteht darin, die Standardausgabe (OUTPUT) auf ein anderes Gerät als den Bildschirm umzuleiten. Das folgende kleine Programm druckt eine gedämpfte Schwingung auf dem Bildschirm. Bei Bedarf können Sie die Ausgabe jedoch mit dem Prozeduraufruf OPEN(OUTPUT,4,0) auf den Drucker umlenken. Um wieder zur Bildschirmausgabe zurückzuschalten genügt der Prozeduraufruf CLOSE(OUTPUT).

PROGRAM SIMPLECURVE (INPUT,OUTPUT);
  CONST D   = 0.00624;
        S   = 32;
        H   = 34;
        C   = 6.28318; (* 2 * PI *)
        LIM = 32;
  VAR X,Y: REAL;
      I  : INTEGER;
BEGIN
  WRITE('Ausgabe auf den Drucker? N' #157); READLN(C);
  IF C<>'N' THEN
    OPEN(OUTPUT,4,0); (* Umleitung auf den Drucker *)
  FOR I:= 0 TO LIM DO
    BEGIN
      X:= D*I; Y:= EXP(-X)* SIN(C*X);
      N:= ROUND(S*Y) + H;
      WRITE('*':N);
    END;
  CLOSE(OUTPUT);  (* OUTPUT wieder zum Bildschirm *)
END.

Bild 83: Umleitung der Standardausgabe

Natürlich gelten analog die Prozeduren READ und READLN nicht nur für das Standard-Eingabetextfile INPUT. Die allgemeine Syntax der Eingabeoperationen lautet nämlich:

READ(Filevariable, Variable, Variable, ..., Variable)
READLN(Filevariable)
READLN(Filevariable, Variable, Variable, ..., Variable)

Die Eingabe erfolgt wie in Abschnitt 2.5 zeilenweise. Damit lassen sich also Werte der Typen CHAR, INTEGER, REAL und STRING einlesen. Die Regeln bezüglich der Syntax der eingelesenen Zahlen wurden bereits in Abschnitt 2.5 beschrieben. Wird bei den Prozeduren als erster Parameter keine Filevariable angegeben, so erfolgt die Eingabe vom Standard-Eingabemedium INPUT:

READ(A,X,S)entspricht
READ(INPUT,A,X,S)

Sowohl bei der Eingabe von der Tastatur als auch bei jedem anderen Textfile besteht die Möglichkeit zu prüfen, ob bei der Eingabe das Ende einer Zeile erreicht wurde. Die Funktion

EOLN(Filevariable)

(end of line, Zeilenende) liefert ein boolesches Ergebnis. Wurde beim Einlesen einer Zahl oder eines Zeichens vom File Filevariable das Zeilenende erreicht, so ist EOLN(Filevariable) = TRUE.

PROGRAM EOLTEST(INPUT,OUTPUT);
  VAR I,ZAHL: INTEGER;
BEGIN
  WRITELN('Geben Sie eine Folge von 5 Zahlen ');
  WRITELN('in verschiedenen Zeilen ein:');
  FOR I:= 1 TO 5 DO
    BEGIN
      READ(ZAHL); 
      IF EOLN(INPUT) THEN 
        WRITE('Zeilenende nach', ZAHL);
    END;
END.

Bild 84: Erkennen von Zeilenwechseln

In Bild 84 wurde die Funktion EOLN(INPUT) verwendet, um das Ende einer Eingabezeile beim File INPUT (Benutzer betätigt die RETURN-Taste) zu erkennen. Wie bei READ(LN) kann auch bei EOLN die Angabe INPUT entfallen, so daß man in Bild 84 die If-Anweisung abkürzen könnte:

IF EOLN THEN 
  WRITE('Zeilenende nach', ZAHL);

Die Prozedur READLN(F) läßt sich formal durch folgende Anweisungsfolge definieren (die Variable CH ist vom Typ CHAR):

READLN(F)                         entspricht
WHILE NOT EOLN(F) DO READ(F,CH)

Das folgende Programm (Bild 85) demonstriert die Eingabe von Files mit READ. Das sequentielle File mit dem Namen »DATA« auf der Diskette beinhaltet in jeder Zeile mehrere reelle Zahlen. Die Zeilensumme über diese Zahlen soll am Bildschirm angezeigt werden. Hat das File folgenden Inhalt

3.142     22         -0.345 0.33
1 2 3 4
             3
-8   -8                  -8

soll also die Ausgabe

25.127 10 3 -24

erzeugt werden. Da die Länge einer Zeile und die Länge des Files nicht a priori bekannt sind, werden While- und Repeat-Anweisungen mit den Funktionen EOLN(DATEN) und EOF(DATEN) als Abbruchkriterien verwendet.

PROGRAM ZEILENSUMME (INPUT, OUTPUT);
  VAR DATEN: TEXT;
 
  PROCEDURE ADD(VAR F: TEXT);
  (* ADDIERE DIE WERTE IN DEN ZEILEN VON F    *)
  (* DRUCKE DIE SUMMEN                        *)
    VAR R, SIGMA: REAL;
  BEGIN
    WHILE NOT EOF(F) DO
      BEGIN SIGMA:=0.0;
        REPEAT
          READ(F,R); SIGMA:= SIGMA + R;
        UNTIL EOLN(F);
        WRITE(SIGMA:10:3);
      END;
  END; (* ADD *)
 
BEGIN
  OPEN(DATEN, 8, 3, 'DATA,SEQ,READ');
  ADD(DATEN); WRITELN;
  CLOSE(DATEN)
END.

Bild 85: Schleifen mit EOLN und EOF

Bemerkenswert ist vielleicht noch die Übergabe von Filevariablen als Parameter an Funktionen und Prozeduren, da in diesem Fall Files nur als Variablenparameter übergeben werden dürfen. Die Prozedur ADD hätte also nicht folgendermaßen vereinbart werden dürfen:

PROCEDURE ADD(F: TEXT);       FALSCH !

Das Programm in Bild 86 zeigt abschließend, wie man eine Textdatei zeichenweise bearbeitet und gleichzeitig ein File der gleichen Zeilenstruktur erzeugt. Es wird eine Datei gelesen und alle Kleinbuchstaben oder Grafikzeichen (mit Ordinalwerten ORD(CH) >=128) in Großbuchstaben umgewandelt.

PROGRAM KONVERT (INPUT, OUTPUT);
  VAR EINGABE,AUSGABE: TEXT;
      NAME           : STRING[16];
      CH             : CHAR;
BEGIN
  WRITE('FILENAMEN :'); READLN(NAME);
  OPEN(EINGABE, 8, 3, NAME + 'SEQ,READ');
  OPEN(AUSGABE, 8, 4, NAME + '.G,SEQ,WRITE');
  WHILE NOT EOF(EINGABE) DO
  BEGIN
    READ(EINGABE, CH);
    WHILE NOT EOLN(EINGABE) DO
      BEGIN
        IF ORD(CH)>127 THEN CH:= CHR(ORD(CH)-128);
        WRITE(AUSGABE,CH);
        READ(EINGABE,CH);
      END;
    WRITELN(AUSGABE);
  END;
  CLOSE(EINGABE); CLOSE(AUSGABE);
END.

Bild 86: Umwandlung einer Datei in Großbuchstaben

Natürlich können Sie das Programm auch mit Files auf anderen Speichermedien testen, indem Sie die Parameter bei OPEN geeignet wählen.

Da jeder Aufruf der Standardprozeduren READ und WRITE neben der eigentlichen Zeichenein- und ausgabe zunächst das Peripheriegerät adressieren muß, kann man die Verarbeitungsgeschwindigkeit des Programmes dadurch erhöhen, daß man jeweils vollständige Zeilen (Strings) einliest und ausgibt und diese im Hauptspeicher bearbeitet:

PROGRAM STRINGKONVERT (INPUT, OUTPUT);
  VAR EINGABE,AUSGABE: TEXT;
      NAME           : STRING[16];
      I              : INTEGER;
      LINE           : STRING[255];
BEGIN
  WRITE('FILENAMEN :'); READLN(NAME);
  OPEN(EINGABE, 8, 3, NAME + 'SEQ,READ');
  OPEN(AUSGABE, 8, 4, NAME + '.G,SEQ,WRITE');
  WHILE NOT EOF(EINGABE) DO
  BEGIN
    READLN(EINGABE, LINE);
    FOR I:= 1 TO LENGTH(LINE) DO
      IF ORD(LINE[I])>127 THEN 
        LINE[I]:= CHR(ORD(LINE[I])-128);
        WRITELN(AUSGABE, LINE);
      END;
  CLOSE(EINGABE); CLOSE(AUSGABE);
END.

Bild 87: Umwandlung mit Zeilenpuffer

In aller Deutlichkeit soll nochmals auf die Unterschiede zwischen der Speicherung von Werten in Textfiles und »normalen« Files hingeweisen werden. Möchten Sie z.B. 100 Zahlen des Typs REAL speichern, so könnten Sie folgende Programmstücke verwenden:

PROGRAM WRITEREAL1 (INPUT,OUTPUT);
  VAR REALS: FILE OF REAL;
      I    : INTEGER;
BEGIN
  OPEN(REALS,8,3,'ZAHLEN,SEQ,WRITE');
  FOR I:= 1 TO 100 DO
    BEGIN REALS^:= 1 / I; PUT(REALS) END;
  CLOSE(REALS)
END.
 
PROGRAM WRITEREAL2 (INPUT,OUTPUT);
  VAR T    : TEXT;
      I    : INTEGER;
BEGIN
  OPEN(T,8,3,'ZAHLEN,SEQ,WRITE');
  FOR I:= 1 TO 100 DO
    WRITE(T, 1/I:15);
  CLOSE(T)
END.

Bild 88: Speicherung reelier Zahlen

  1. Bei der Verwendung eines FILE OF REAL werden die reellen Zahlen so gespeichert, wie sie im Speicher des Computers codiert sind (5 Bytes pro Zahl). Daher erfolgt die Ausgabe extrem schnell und die Daten sind sehr kompakt gespeichert.
  2. Bei einem File des Typs TEXT muß bei jeder Ausgabe eine reelle Zahl in eine formatierte Folge von Zeichen umgewandelt werden (»0.10000000E-01«). Dies erfordert einen nicht unerheblichen Aufwand. Jedoch können die so gespeicherten Daten z.B. auf einen Drucker ausgegeben werden und von einem Menschen gelesen werden. In der Sprache BASIC werden ebenfalls alle Zahlen als Zeichenfolgen gespeichert. Möchten Sie also die in Pascal erstellten Daten in BASIC weiterverwenden, so müssen Sie das Schema aus Programm WRITEREAL2 verwenden.

Aufgaben

1. Erstellen Sie ein Druckprogramm, das den Inhalt eines Datenfiles, wie es zum Beispiel in Aufgabe 1 in Abschnitt 2.16 beschrieben wurde, formatiert als Liste ausgibt. Finden Sie ein möglichst allgemein verwendbares Verfahren, um jede Seite mit einem Listenkopf (Titel und Seitennummer) zu versehen.

2. Gegeben ist ein Datenfile, das die Umsätze von Vertretern im Bundesgebiet für ein Jahr enthält. Dieses File ist nach dem Feld Postleitzahl aufsteigend sortiert. Drucken Sie eine Liste, die alle Umsätze im Bundesgebiet enthält. Außerdem sollen Zwischensummen gebildet werden, aus denen die Gesamtumsätze in jedem PLZ-Bereich (also z.B. 6000-6999 , 7000-7999 etc.) hervorgehen:

PLZ Stadt Vertreter
Monat
Umsatz
6000 Frankfurt/M Müller
3
323000.00
6200 Wiesbaden Meier
5
43000.00
Mogel
6
12100000.00
6991 Wildentierbach Schulze
12
3.00
--------------
SUMME:
426003.00
7000 Stuttgart Müller
7
4500.00
7006 ...  

3. Eine solche Gruppenkontrolle wie in Aufgabe 2 läßt sich auch mehrstufig anwenden. Innerhalb jedes PLZ-Gebietes könnte man (bei einer entsprechenden Sortierung der Ausgangsdaten) auch eine zusätzliche Aufschlüsselung nach Monatsumsätzen vornehmen. Im Programm muß also ein Vergleich des laufenden mit dem nachfolgenden (Teil-) Schlüssel vorgenommen werden. Das generelle Schema einer zweistufigen Gruppenkontrolle ist also folgendes:

Dateien eröffnen
Ersten Satz lesen
While NOT Dateiende erreicht DO
BEGIN
  Vorlauf Stufe 2
  REPEAT
    Vorlauf Stufe 1
    REPEAT
      Bearbeite Einzelposten
      Neuen Satz lesen
    UNTIL Wechsel 1
    Gruppenabschluß
  UNTIL Wechsel 2
  Gruppenabschluß 2
END
Dateien schließen

Wechsel 1 bezeichnet im oben skizzierten Beispiel einen Wechsel des Monats, während Wechsel 2 eine Änderung des übergeordneten Gruppenkriteriums (PLZ-Bereich) bedeutet. Wechsel 1 muß natürlich auch durch ein Dateiende und Wechsel 2 hervorgerufen werden. Ein Wechsel 2 wird auch durch das Dateiende provoziert.

Der Vorlauf für eine Gruppe enthält das Löschen von Summenfeldern, den Druck von Überschriften etc., während der Gruppenabschluß z.B. den Druck einer Summenzeile unter einer Gruppe bedeutet.

Inhaltsverzeichnis