Programmieren

ARexx - Textformatierung

(C) 1999 by Sven Drieling

ARexx verfügt über eine Vielzahl von Befehlen zur Verarbeitung von Zeichenketten. So reichen ein paar Zeilen, um einen ASCII-Text links-, rechtsbündig, zentriert oder als Blocksatz zu formatieren.

Als Beispiel zur Formatierung dient folgendes Zitat von Rainer Gellrich aus AmigaGadget #32:

"Was denken sie, warum hat sich dieser Mann mit einer Kaliber .98 Plastiksplittersprengmonsterkanone mit coolem Granatwerfertm erschossen?"

"Hmmm.... Gute Frage. Seine Frau ist vors Auto gelaufen, seine zehn Kinder wurden in den letzten vier Wochen alle von zwanzig unabhängigen Psychopathen vergewaltigt und in 90 Wäldern der USA verstreut, seine Firma hat seine Schweizer Nummernkonten gepfändet und den Ferkeln Jehovas gespendet, in seinem Haus spukt der Geist von Roy Black, seine Corvette wird von allen Tauben westlich von Timbuktu vollgeschissen und seine Schwester trägt seit gestern Latzhosen.

Wenn Sie mich fragen, ich hab echt keine Ahnung...."

Zunächst soll nur der erste Satz linksbündig mit 50 Zeichen pro Zeile formatiert werden. Um sich die Aufgabe möglichst einfach zu machen, wird der Satz zunächst in eine einzige lange Zeichenkette gelegt

   line = '"Was denken sie, warum hat sich dieser Mann mit einer Kaliber ' ||
          'Plastiksplittersprengmonsterkanone mit coolem Granatwerfertm ' ||
          ....
  
und in die einzelnen Wörter zerlegt. Hierfür bieten sich folgende Befehle an:
   Anzahl  = WORDS(String)   - Anzahl der Wörter des Strings
   String2 = WORD(String, n) - Liefert das n-te Wort
  
An Trennzeichen, zwischen den Wörtern, benutzt ARexx das Leerzeichen und den Tabulator.

Zur Zerlegung der Zeile in die einzelnen Wörter reichen somit folgende drei Zeilen

    DO n = 1 TO WORDS(line)
      SAY WORD(line, n)
    END

    4.DH1:PRG/rexx> rx agSplitText.rexx
    "Was
    denken
    sie,
    warum
    hat
    sich
    dieser
    Mann
    mit
    ...
  
Als nächstes sollen die einzelnen Wörter wieder zu Zeilen mit maximal 50 Zeichen zusammengsetzt und ausgegeben werden. Dazu wird die Zeichenkette "out" erstellt. An ihr wird jeweils das nächste Wort angehängt, solange die Zeichenkette dadurch nicht die Länge von 50 Zeichen überschreitet.
    out = ""

    DO n = 1 TO WORDS(line)
      wort = WORD(line, n)

      IF LENGTH(out) + LENGTH(wort) < 50 THEN DO
        out = out wort
      END
      ELSE DO
        SAY out
        out = wort
      END
    END

    SAY out
  
Auf < 50 wird dabei überprüft, weil in der IF-Abfrage nur die Wortlänge berücksichtigt wird. In der Zeile
    out = out wort
  
kommt aber zusätzlich mit dem Leerzeichen ein weiteres Zeichen hinzu, das das neue Wort vom bisherigen Text trennt.

Da innerhalb der Schleife eine Zeile nur bei einen Überlauf ausgegeben wird, wird nach Ablauf der Schleife mit "SAY out" die letzte noch verbliebene Zeile ausgegeben, die zwangsläufig immer < 50 ist.

Wenn man sich die Ausgabe anschaut, kann man einen Schönheitsfehler entdecken.

    4.DH1:PRG/rexx/ag> rx agLeftText.rexx
  ->  "Was denken sie, warum hat sich dieser Mann mit
      einer Kaliber .98
      Plastiksplittersprengmonsterkanone mit coolem
      Granatwerfertm erschossen?"
  
Am Anfang wurde ein zusätzliches Leerzeichen eingefügt. Das liegt daran, dass "out" zu Anfang auf "" gesetzt wird und die Zeile
    out = out wort
  
ja immer ein Leerzeichen einfügt. Während dies bei allen nachfolgenden Wörtern genau das ist, was gebraucht wird, ist dies beim ersten Wort unschön. Verhindern kann man es leicht durch eine bessere Initialisierung der Schleife. "out" wird dazu am Anfang auf das erste Wort gesetzt und die Schleife beginnt daher mit dem 2. Wort.
    out = WORD(line, 1)  /* out aufs erste Wort setzen */

    DO n = 2 TO WORDS(line)  /* Schleife mit 2. Wort beginnen */
      wort = WORD(line, n)
      ....
  
Außerdem kann "out" am Ende der Schleife ein Leerstring sein, weshalb dieser Fall mittels IF-Abfrage abgefangen wird, um die Ausgabe zusätzlicher Leerzeilen zu vermeiden. Der Rest des Programms bleibt unverändert und schon stimmt die Ausgabe:
    4.DH1:PRG/rexx/ag> rx agLeftText_2.rexx
    "Was denken sie, warum hat sich dieser Mann mit
    einer Kaliber .98
    Plastiksplittersprengmonsterkanone mit coolem
    Granatwerfertm erschossen?"
  
Da ARexx die Schleife bei "DO n = 2 TO 1 oder DO n = 2 TO 0" nicht ausführt und WORD("", 1) einen Leerstring liefert, funktioniert das Ganze auch problemlos bei nur einem oder keinem Wort in einer Zeile.

rechtsbündig und zentriert

Diese Zeilen rechtsbündig oder zentriert auszugeben ist kein großes Problem. Dazu müssen nur die Ausgabezeilen "SAY out" entsprechend geändert werden.

Bei der rechtsbündigen Ausgabe müssen am Anfang der Zeile soviele Leerzeichen eingefügt werden, daß der letzte Buchstabe an Position 50 landet.

    numLeer = 50 - LENGTH(out)
    SAY COPIES(" ", numLeer) || out
  
Die ARexx-Funktion COPIES() erzeugt eine Zeichenketten mit der Anzahl der gewünschten Zeichen und "||" sorgt dafür, daß ARexx nicht - wie sonst üblich - selbst noch ein zusätzliches Leerzeichen einfügt.
    4.DH1:PRG/rexx/ag> rx agRightText.rexx
       "Was denken sie, warum hat sich dieser Mann mit
                                     einer Kaliber .98
         Plastiksplittersprengmonsterkanone mit coolem
                           Granatwerfertm erschossen?"
  
Zentrieren ist noch einfacher, da ARexx hierfür die Funktion CENTER() bietet:
   String2 = CENTER(String, Länge, [Füllzeichen])
  
Damit braucht im Programm nur
    SAY CENTER(out, 50)
  
zu stehen. Das optionale Füllzeichen kann dazu benutzt werden, um den Text mit anderen Zeichen als den Leerzeichen zu umschließen. Einfach mal selbst ausprobieren.
    4.DH1:PRG/rexx/ag> rx agCenterText.rexx
     "Was denken sie, warum hat sich dieser Mann mit
                    einer Kaliber .98
      Plastiksplittersprengmonsterkanone mit coolem
               Granatwerfertm erschossen?"
  
Problem ist dabei, dass die Programmschleife auch Zeilen erzeugen kann, die länger als 50 Zeichen sind. Dies passiert, wenn ein einzelnes Wort diese Länge überschreitet.

In diesem Fall wird "numLeer" negativ, was zum Abbruch das Programms führt und CENTER() schneidet soviel von der Zeile weg, bis sie nur noch 50 Zeichen enthält.

Als einfache Lösung erkennen die folgenden Programme diesen Fall und geben eine erklärende Fehlermeldung aus:

    IF LENGTH(line) > 50 THEN DO
      SAY "FEHLER!: Die erzeugte Zeile ist länger als 50 Zeichen. "
      SAY "Sie könnten die Breite des Textes erhöhen oder "
      SAY "das überlange Wort kürzen bzw. "
      SAY "durch Trennungen aufteilen: " out
      EXIT(10)
    END
  
Die bisherigen Funktionen sind in agFormatText_1.rexx zusammengefaßt.

[ Die Beispielskripten sind in der Online-Version nicht enthalten. (an) ]

Blocksatz

Fehlt noch die Erzeugung des Blocksatzes. Als Grundlage dient die linksbündig formatierte Zeile. Zwischen den einzelnen Wörtern werden soviele Leerzeichen eingefügt, bis die Enden bündig mit den beiden Seitenrändern abschließen.
    0                                                50
    |                                                |
    "Was denken sie, warum hat sich dieser Mann mit

    "Was  denken  sie,  warum hat sich dieser Mann mit
  
Dazu wird wie bei der rechtsbündigen Formatierung die Anzahl der zum Auffüllen benötigten Leerzeichen berechnet. Dann wird die Zeile wieder Wort für Wort zerlegt und solange ein zusätzliches Leerzeichen eingefügt bis der Leerzeichenzähler 0 erreicht. Das alles aber nur, wenn die Zeile mehr als ein Wort enthält.
    IF WORDS(line) > 1 THEN DO
      block = WORD(line, 1)       /* block aufs erste Wort setzen         */
      numLeer = 50 - LENGTH(line) /* Anzahl der einzufügenden Leerzeichen */

      DO n = 2 TO WORDS(line)  /* Schleife mit 2. Wort beginnen */
        wort = WORD(line, n)

        IF numLeer > 0 THEN DO
          block   = block || " " wort   /* zusätzliches Leerzeichen */
          numLeer = numLeer - 1
        END
        ELSE DO
          block   = block wort
        END
      END

      SAY block
    END
    ELSE DO
      SAY line
    END
  
Diese Erweiterung ist in agFormatText_2.rexx zu finden.
    4.DH1:PRG/rexx/ag> rx agFormatText_2.rexx BLOCK
 1. "Was  denken  sie,  warum hat sich dieser Mann mit
 2. einer  Kaliber  .98
 3. Plastiksplittersprengmonsterkanone  mit  coolem
 4. Granatwerfertm  erschossen?"

    ^                                                ^
    0                                                50
  
In diesem Fall kann das Ergebnis aber noch nicht überzeugen. Die Zeilen sind so unglücklich lang, dass teilweise mehr als ein zusätzliches Leerzeichen zwischen den einzelnen Wörtern eingefügt werden muß. Diese Anzahl wird nun in die "plus"-Zeichenkette gelegt. WORDS(line) - 1 ist dabei die Anzahl der Wortzwischenräume. Bei zwei Wörtern ist dies einer, bei drei Wörter sind dies zwei, bei vier gibt es drei Zwischenräume usw.
    1. spaces  = 50 - LENGTH(line)
    2. numPlus = spaces %  (WORDS(line) - 1)
    3. numLeer = spaces // (WORDS(line) - 1)
    4. plus    = COPIES(" ", numPlus)
  
In der ersten Zeile werden die benötigten Leerzeichen berechnet.

In den beiden folgenden Zeichen wird jeweils eine Ganzzahldivision durchgeführt und 'numPlus' das Ergebnis vor dem Komma und 'numLeer' der ganzzahlige Rest zugewiesen.

Beipiel:

    einer Kaliber .98
    ^                                                ^
    0                                                50
  
Hier fehlen 33 Leerzeichen und es gibt zwei Wortzwischenräume:
    33 %  2 ergibt 16 für numPlus
    33 // 2 ergibt  1 für numLeer
  
Die Erzeugung von "block" wird entsprechend erweitert:
    IF numLeer > 0 THEN DO
      block   = block || plus || " " wort
      numLeer = numLeer - 1
    END
    ELSE DO
      block   = block || plus wort
    END
  
Jetzt werden mindestens 16 Leerzeichen zwischen den Wörtern eingesetzt und zwischen dem 1. und 2. Wort kommt ein zusätzliches Leerzeichen hinzu.
    4.DH1:PRG/rexx/ag> rx agFormatText_3.rexx BLOCK
    "Was  denken  sie,  warum hat sich dieser Mann mit
    einer                  Kaliber                 .98
    Plastiksplittersprengmonsterkanone    mit   coolem
    Granatwerfertm                        erschossen?"
    ^                                                ^
    0                                                50
  
Hier sind, Dank des "Plastiksplittersprengmonsterkanone"-Wortungetüms, die Zwischenräume zwar sehr groß, aber der Text schließt bündig mit den Rändern ab.

Die großen Lücken, insbesonders bei kleinen Textbreiten, kann man nur mit einer automatischen Trennung vermeiden. Aber das ist ein anderes Thema.

In einem abschließendesm Programm ist nochmal alles zusammengefaßt. Dieses liest einen ASCII-Text ein und gibt die einzelnen Blöcke formatiert aus. Getrennt werden diese Blöcke innerhalb des Textes mit einer Leerzeile. Siehe "agFormatText_4.rexx" und "zitat.txt". Als zusätzliche Parameter kommt der Dateiname und die nun einstellbare Textbreite hinzu.

   4.DH1:PRG/rexx/ag> rx agFormatText_4.rexx BLOCK 70 zitat.txt
   "Was  denken  sie,  warum  hat  sich dieser Mann mit einer Kaliber .98
   Plastiksplittersprengmonsterkanone     mit    coolem    Granatwerfertm
   erschossen?"
   

"Hmmm.... Gute Frage. Seine Frau ist vors Auto gelaufen, seine zehn Kinder wurden in den letzten vier Wochen alle von zwanzig unabhängigen Psychopathen vergewaltigt und in 90 Wäldern der USA verstreut, seine Firma hat seine Schweizer Nummernkonten gepfändet und den Ferkeln Jehovas gespendet, in seinem Haus spukt der Geist von Roy Black, seine Corvette wird von allen Tauben westlich von Timbuktu vollgeschissen und seine Schwester trägt seit gestern Latzhosen.

Wenn Sie mich fragen, ich hab echt keine Ahnung...."

Zumindest einen Fehler besitzt das Programm aber. Bei ARexx ist derzeit die Länge einer Zeichenkette auf 65 535 Zeichen beschränkt. Wenn ein Block diese Länge überschreitet, dann bricht ARexx mit einer Fehlermeldung ab. Da Absätze mit 65 535 Zeichen aber relativ selten sind verzichte ich an dieser Stelle auf eine entsprechende Korrektur.

tschuess
     [|8:)

Zurück