/* Textformatierprogramm ===================== © Torsten Buller-117894 Roman Czyborra-127221 */ #include typedef char * Text; typedef enum { nein, ja } Praedikat; /* Globalmachung der Kommandozeilenargumente vermindert die Zahl der zu übergebenden Argumente an Hilfsfunktionen */ int Argumente, Index, Markierung; Text Token, * Kommandozeile, woher, wohin; Praedikat weiter (void) /* ist wahr, wenn noch ein weiteres Argument folgt, und schiebt Index und Token weiter */ { return ( ++ Index < Argumente && (Token = Kommandozeile [Index] )); } Praedikat zurueck (void) /* hebt den Seiteneffekt eines 'weiter()' auf und liefert immer nein */ { Token = Kommandozeile [-- Index]; return nein; } /* Definitionen zur Erkennung der Schlüsselwörter */ typedef enum { links, rechts, oben, unten, Rand, Warnungen, linksbuendig, rechtsbuendig, ausgeglichen, auf, die, in, bei, mit, ohne, Seiten, der,Laenge, Spalten,dazwischen, Papierbreite, Datei} Schluesselwort; struct { Text abgekuerzt, ausgeschrieben; } Schluessel [] = { "l", "links", "r", "rechts", "o", "oben", "u", "unten", "R", "Rand", "W", "Warnungen", "lb","linksbuendig", "rb","rechtsbuendig", "as","ausgeglichen", "a", "auf", "d", "die", "i", "in", "b", "bei", "m", "mit", "o", "ohne", "S", "Seiten", "d", "der", "L", "Laenge", "Sp","Spalten", "zw","dazwischen", "P", "Papierbreite", "D", "Datei" }; /* Vorsicht bei Änderungen: 1. die Reihenfolge der enum 'Schluesselwort' muß stets mit der Schlüsselworttabelle übereinstimmen, 2. sämtliche Woerter, die direkt nach tippe erscheinen dürfen, (linksbuendig bis ohne) müssen zusammenstehen, 3. Die Richtungen links bis unten müssen am Anfang bleiben. */ Praedikat ist (Schluesselwort zupruefen) /* Die Funktion ist(zupruefen) überprüft, ob das aktuelle Token, das zu prüfende Schlüsselwort ist, ausgeschrieben oder abgekürzt. */ { return (!strcmp (Token, Schluessel[zupruefen].abgekuerzt) || !strcmp (Token, Schluessel[zupruefen].ausgeschrieben)); } Schluesselwort Fund; Praedikat unter (Schluesselwort von, Schluesselwort bis) /* untersucht, ob das aktuelle Token unter von-bis ist, liefert ja, wenn gefunden, und die Erkennung in Fund */ { while (von <= bis) if (ist (Fund = von ++)) return ja; return nein; } int Wert; Praedikat Zahl (void) /* Zahl () ist wahr, wenn das aktuelle Token nur aus Ziffern besteht. Wert ist dann der Zahlenwert. */ { register char Ziffer; for (woher = Token, Wert = 0; Ziffer = * woher ++; Wert = 10 * Wert + Ziffer) if ((Ziffer -= '0') < 0 || Ziffer > 9) return nein; return ja; } void Syntax (void) { fprintf (stderr, "%s%s%s%s%s", "Zulässige Optionen: tippe [d(ie) D(atei) X] [a(uf) Y]\n", "[m(it)|o(hne) W(arnungen)] [i(n) [2] Sp(alten) [(da)zw(ischen) 4]]\n", "[a(uf) [10] S(eiten) [d(er) L(aenge) 66]] [b(ei) P(apierbreite) 80]\n", "[m(it)|o(hne) R(and) [[l(inks)|r(echts)|o(ben)|u(nten)] [4]]...]\n", "[l(inks)b(uendig)|r(echts)b(uendig)|a(u)s(geglichen)]\n"); exit (1); } void unverstaendlich (void) /* bricht mit Fehlermeldung ab */ { if (Index == Argumente) fprintf (stderr, "Noch mehr Argumente erwartet!\n"); else fprintf (stderr, "Das Argument '%s' ist unverständlich!\n", Token); Syntax (); } void Zahlerwartet (void) { fprintf (stderr, "Zahl erwartet nach '%s\' anstelle von '%s'!\n", Kommandozeile [Index - 1], Token); Syntax (); } /* Globale einstellbare Parameter */ Schluesselwort Satzrichtung = ausgeglichen; Praedikat Warnungsanzeige = ja; int Gesamtbreite = 80, Spaltenzahl = 1, Zwischenraum = 4, Seitenzahl = 1, Seitenlaenge = 1000, Randbreite [4] = {0,0,0,0}; Text Eingabedateiname = NULL, Ausgabedateiname = NULL; /* abhängige Globalparameter */ int bedruckbareZeilen, Zeilenhoechstzahl, Spaltenbreite, Maximaleinrueckung, kritischeFuellmenge; typedef struct Zeile { int Laenge; Text Wortlaut; struct Zeile * naechste; } Zeile; /* Laufvariablen */ int gelesen = 0, Zeilenzaehler = 0, Zeichenzaehler = 0, inWort = 0, Einrueckung = 0, Fuellrichtung = 1, fertigeWorte = 0, fertigeZeilen = 0, Raeume, zufuellen, fuell, Rest, Wort, Sp, zaehl, rechtestevolle; Zeile * * Z, * ersteZeile, * aktuelle; Praedikat amAnfang = ja, war_mit, spezielleKanten, WortZahlFolge; Text Puffer, * Wortlaut; Schluesselwort Richtung; int lies (void) { gelesen = getchar(); ++ Zeichenzaehler; if (gelesen == '\n') ++ Zeilenzaehler; return gelesen; } Praedikat druckbar (void) { /* prüft, ob es sich, bei dem gelesenen Zeichen um ein druckbares Zeichen des ISO-8859-Latin-1-Vorrats handelt. */ return (gelesen>32 && gelesen<127 || gelesen>160); } void Umlenkung (FILE * Kanal, Text Dateiname) { if (Dateiname && ! freopen (Dateiname, Kanal == stdin ? "r":"a", Kanal)) { fprintf (stderr, "Die Datei '%s' kann nicht geöffnet werden: ", Dateiname); perror (); exit (2); } } Text Feld (int Bytes) /* hole Speicherplatz, ggf. Abbruch mit Fehlermeldung */ { if (wohin = (Text) malloc (Bytes)) return wohin; perror ("Speicherbedarf"); exit (3); } void Leer (int wieviele) { while (wieviele --) *wohin++ = ' '; } void schreite (int wieviele) { while (wieviele --) putchar (' '); } enum Ausdehnung {leer, halbvoll, voll}; void speichereZeile (enum Ausdehnung Ausdehnung) /* Diese Funktion speichert die aktuelle Zeile unter Berücksichtigung der Satzart und Einrückung. Bei Ausdehnung == voll wird ggf. blockgesetzt. Index zeige dabei stets hinter das Ende des letzten Wortes. Leere Zeilen werden nur mit Ausdehnung == leer erzeugt. */ { if (Ausdehnung == leer) aktuelle->Laenge = 0; else { if (! fertigeWorte) return; if (Ausdehnung == voll && Satzrichtung == ausgeglichen && fertigeWorte > 1) { /* hier wird die Zahl der Füllraeume für Blocksatz berechnet */ zufuellen = Spaltenbreite - Index + 1; Raeume = fertigeWorte - 1; fuell = zufuellen / Raeume; aktuelle -> Laenge = Spaltenbreite; /* wenn es nicht geht, die Füllräume ganz gleichmäßig zu verteilen, dann wird abwechselnd von links und von rechts gefüllt */ if (Rest = zufuellen % Raeume) if ((Fuellrichtung *= -1) < 0) fuell ++; else Rest = Raeume - Rest; else Rest = -1; } else zufuellen = 0, aktuelle -> Laenge = Index - 1; aktuelle -> Wortlaut = Feld (aktuelle -> Laenge + 1); if (Satzrichtung != rechtsbuendig) Leer (Einrueckung); for (Wort = 0;;) { /* Die Worte werden mit Zwischenräumen kopiert */ for (woher = Wortlaut [Wort++]; * wohin = * woher ++; ++ wohin); if (!--fertigeWorte) break; if (zufuellen) Leer (fuell += Rest -- ? 0 : Fuellrichtung); Leer (1); } if (Satzrichtung == rechtsbuendig) Leer (Einrueckung); *wohin = '\0'; } if (++ fertigeZeilen > Zeilenhoechstzahl) { Markierung = Zeichenzaehler; while (lies () != EOF); fprintf (stderr, "Schon nach %d %% des Textes sind %d %s voll!\n", 100 * Markierung / Zeichenzaehler, Seitenzahl > 1 ? Seitenzahl : Seitenlaenge, Seitenzahl > 1 ? "Seiten" : "Zeilen"); exit (4); } Einrueckung = 0; aktuelle = aktuelle->naechste = (Zeile *) Feld (sizeof (Zeile)); } void beendeWort (void) /* schließt während des Einlesens das Wort ab */ { if (! inWort) return; Puffer [Index ++] = inWort = 0; ++ fertigeWorte; if (Index >= Spaltenbreite) speichereZeile (voll); } void retteWort (void) /* speichert die fertigen Worte und rettet das angefangene in die nächste Zeile */ { Index = Wortlaut [Markierung = fertigeWorte] - Puffer; speichereZeile(voll); strcpy (Puffer, Wortlaut [Markierung]); Index = inWort; Wortlaut [0] = Puffer; } int main (int argc, Text argv[]) { /* Zuerst werden die Aufrufargumente interpretiert: */ Argumente = argc; Kommandozeile = argv; while (weiter()) { if (! unter (linksbuendig, ohne)) unverstaendlich (); switch (Fund) { case linksbuendig: case rechtsbuendig: case ausgeglichen: /* Blocksatz */ Satzrichtung = Fund; break; case auf: /* auf [n] Seiten ... | auf /dev/ttyp2 */ if (! weiter()) unverstaendlich(); if (ist (Seiten)) Seitenzahl = 10; else if (Zahl() && weiter() && (ist (Seiten) || zurueck())) Seitenzahl = Wert; else { Ausgabedateiname = Token; break; } Markierung = Index; if (weiter() && ist (der) && weiter() && ist (Laenge)) if (weiter() && Zahl()) Seitenlaenge = Wert; else Zahlerwartet (); else Seitenlaenge = 66, Index = Markierung; break; case die: /* tippe die Datei eingabe.txt */ if (weiter() && ist (Datei) && weiter()) Eingabedateiname = Token; else unverstaendlich(); break; case in: /* in [n] Spalten */ if (! weiter()) unverstaendlich(); if (ist (Spalten)) Spaltenzahl = 2; else if (Zahl() && weiter() && ist (Spalten)) Spaltenzahl = Wert; else unverstaendlich(); if (weiter() && (ist (dazwischen) || zurueck())) if (weiter() && Zahl()) Zwischenraum = Wert; else Zahlerwartet (); break; case bei: /* bei Papierbreite n */ if (weiter() && ist (Papierbreite) && weiter()) if (Zahl()) Gesamtbreite = Wert; else Zahlerwartet(); else unverstaendlich(); break; case mit: case ohne: /* mit Rand | ohne Warnungen ... */ war_mit = Fund == mit; if (! weiter()) unverstaendlich(); if (ist (Warnungen)) { Warnungsanzeige = war_mit; break; } if (! ist (Rand)) unverstaendlich (); WortZahlFolge = nein; do { spezielleKanten = nein; while (weiter() && (unter (links, unten) || zurueck())) { Markierung = Index; if (ist (ohne) && weiter() && (ist (Warnungen) || ist (Rand))) { Index = Markierung - 1; break; } Index = Markierung; spezielleKanten = ja; Randbreite [Fund] = -1; } if (WortZahlFolge) { if (!spezielleKanten) break; if (!weiter () || !Zahl()) Zahlerwartet (); } else if (war_mit) if (weiter() && (Zahl() || zurueck())) WortZahlFolge = spezielleKanten; else Wert = 4; else Wert = 0; for (Richtung = links; Richtung <= unten; ++ Richtung) if (! spezielleKanten || Randbreite [Richtung] == -1) Randbreite [Richtung] = Wert; } while (WortZahlFolge); } } /* Dann werden die abhängigen Parameter berechnet */ bedruckbareZeilen = Seitenlaenge - Randbreite [oben] - Randbreite [unten]; Zeilenhoechstzahl = bedruckbareZeilen * Spaltenzahl * Seitenzahl; Spaltenbreite = Gesamtbreite - Randbreite [links] - Randbreite [rechts]; Spaltenbreite -= (Spaltenzahl - 1) * Zwischenraum; Spaltenbreite /= Spaltenzahl; Maximaleinrueckung = Spaltenbreite < 24 ? Spaltenbreite / 3 : 8; kritischeFuellmenge = Spaltenbreite / 2 + 1; /* Ab dieser Menge Leerstand soll nach Bindestrichen gesucht werden und, wenn keine vorhanden, soll eine Warnung ausgegeben werden */ if (! bedruckbareZeilen || Spaltenbreite < 1) { fprintf (stderr, "Da bleibt kein Platz zum Tippen übrig!\n"); exit (2); } Umlenkung (stdin, Eingabedateiname); Umlenkung (stdout, Ausgabedateiname); Puffer = (Text) Feld (Spaltenbreite + 2); /* ein Byte extra, um zu sehen, ob das letzte Wort zu Ende ist und ein Trenner kommt, ein Byte extra, das stets Null ist und den String abschließt */ Puffer[Spaltenbreite + 1] = '\0'; Wortlaut = (Text *) Feld ((Spaltenbreite / 2 + 1) * sizeof (Text)); aktuelle = ersteZeile = (Zeile *) Feld (1 * sizeof (Zeile)); /* Nun folgt das formatierende Einlesen in den Speicher */ while (gelesen != EOF) switch (lies ()) { case EOF: beendeWort(); speichereZeile(halbvoll); break; case '\n': beendeWort(); if (amAnfang) speichereZeile (halbvoll), speichereZeile (leer); amAnfang = ja; break; case '\t': case ' ': beendeWort(); if (amAnfang) { speichereZeile(halbvoll); if (gelesen == '\t' || ++ Einrueckung > Maximaleinrueckung) Einrueckung = Maximaleinrueckung; } break; default: if (! druckbar()) /* ignorieren */ break; amAnfang = nein; if (! inWort ++) { /* Wortanfang */ if (! fertigeWorte) Index = Einrueckung; Wortlaut[fertigeWorte]= Puffer + Index; } Puffer[Index++] = gelesen; if (Index <= Spaltenbreite) break; if (! fertigeWorte) { /* ein einziges Wort füllt die ganze Spalte */ if (Warnungsanzeige) fprintf (stderr, "In Eingabezeile %d mußte %s... abgeschnitten werden!\n", Zeilenzaehler, Wortlaut[0]); Puffer[Index-1] = inWort = 0; fertigeWorte = 1; speichereZeile(voll); while (druckbar()) lies (); if (gelesen == '\n') amAnfang = ja; continue; } if (inWort < kritischeFuellmenge) { retteWort(); break; } for (-- Index; (gelesen = Puffer[--Index]) && gelesen != '-'; ); if (gelesen) { /* Trennstrich gefunden */ Markierung = Puffer [++ Index]; Puffer [Index++] = '\0'; ++ fertigeWorte; speichereZeile(voll); Puffer [0] = Markierung; strcpy (Puffer + 1, Puffer + Index + 1); Wortlaut [0] = Puffer; Index = inWort = Spaltenbreite - Index + 1; break; } /* Trennstrich vergeblich gesucht */ retteWort(); if (Warnungsanzeige) fprintf (stderr, "In Ausgabezeile %d gibt es großen Leerstand (%d %s)!\n", fertigeZeilen, inWort - 1, "zusätzliche Leerzeichen"); } /* Schließlich wird der Speicherinhalt mehrspaltig ausgegeben: */ Z = (Zeile **) Feld (Spaltenzahl * sizeof (Zeile *)); Z [Spaltenzahl - 1] = ersteZeile; Seitenzahl = 1 + (fertigeZeilen - 1) / (bedruckbareZeilen * Spaltenzahl); while (Seitenzahl--) { for (zaehl = Randbreite [oben]; zaehl --;) putchar ('\n'); if (Seitenzahl) Rest = 0; else { bedruckbareZeilen = fertigeZeilen / Spaltenzahl; Rest = fertigeZeilen % Spaltenzahl; } for (Z [0] = Z [Spaltenzahl - 1], Sp = 1; Sp < Spaltenzahl; ++ Sp) { Z [Sp] = Z [Sp-1]; zaehl = bedruckbareZeilen; if (Rest && Sp <= Rest) ++ zaehl; while (zaehl --) Z [Sp] = Z [Sp] -> naechste; } zaehl = bedruckbareZeilen; if (Rest) ++ zaehl; while (zaehl--) { if (! zaehl && Rest) Spaltenzahl = Rest; for (rechtestevolle = Sp = 0; Sp < Spaltenzahl;) if (Z [Sp ++] -> Laenge) rechtestevolle = Sp; if (rechtestevolle) schreite (Randbreite[links]); for (Sp = 0; Sp < rechtestevolle; Sp ++) { if (Sp) schreite (Zwischenraum); if (Satzrichtung == rechtsbuendig) schreite (Spaltenbreite - Z[Sp]->Laenge); if (Z[Sp] -> Laenge) fputs (Z [Sp] -> Wortlaut, stdout); if (Sp + 1 < rechtestevolle && Satzrichtung != rechtsbuendig) schreite (Spaltenbreite - Z[Sp]->Laenge); } putchar ('\n'); for (Sp = 0; Sp < Spaltenzahl; Sp ++, --fertigeZeilen) Z[Sp] = Z[Sp]->naechste; } putchar ('\f'); } exit (0); }