Extended String: Gekonnte Formatierung

Wer Programme schreibt, die Texte bzw. Strings in irgendeiner Art und Weise formatieren - sei es nun eine Textverarbeitung oder ein kleiner Editor, ein Druckprogramm mit integrierter Textformatierung, oder was auch immer - der hat sich bestimmt schon einmal gefragt, wie die „großen“ Programme das anstellen; das rechts- oder linksbündig formatieren oder den Blocksatz etc.

Nun ja, es ist eigentlich ganz einfach, man muß sich halt nur (sehr) intensiv mit dem Thema auseinandersetzen Um und eine Menge Zeit investieren. Aufgaben wie links bündig formatieren sind noch relativ einfach zu lösen, schwieriger wird es z.B. beim Blocksatz oder beim Zentrieren einer Textzeile. Die vorliegende Routinensammlung EXTSTR.C (EXTended STRing) nimmt einem eine Menge Arbeit ab und erledigt (fast) alle Arbeiten, die bei der Textformatierung so anfallen können.

Die Funktionen

Die erste zu besprechende Funktion ist str_rjust(). Sie formatiert den über Zeiger übergebenen String rechtsbündig, d.h. das letzte Wort steht am rechten Rand, links wird mit Leerzeichen aufgefüllt. Die Länge des Strings wird dabei nicht verändert. Wenn Sie also einen 60 Zeichen langen String haben, ihn aber mit 68 Zeichen Breite rechtsbündig formatieren wollen, hängen Sie einfach noch acht Leerzeichen dran; sie werden dann automatisch an den linken Rand geschoben.

Zuerst wird die Länge des Strings festgestellt, da sie ja nicht verändert werden soll. Dann werden die Leerzeichen am rechten Ende mit str_rrm() entfernt und die neue Länge festgestellt. Nun wird der String an den rechten Rand „geschoben“ und die Differenz der beiden Längen als Leerzeichen am linken Rand eingefügt.

Die nächste Funktion, str_ljust(), formatiert den übergebenen String linksbündig. Dazu werden einfach alle Leerzeichen am linken Rand entfernt und am rechten Rand wiedereingefügt, so daß die Länge dieselbe bleibt.

Das Zentrieren eines Strings übernimmt str_center(). Diese Funktion benötigt Speicherplatz für einen temporären Arbeits-String. Wenn der Speicherplatz mittels strdup() (aus string.h) nicht reserviert werden kann, wird NULL zurückgegeben, ansonsten werden alle Leerzeichen am linken und rechten Rand entfernt und die neue Länge des Strings festgestellt. Daraufhin wird die Differenz der beiden Längen (vorher und nachher) durch Zwei geteilt, wobei mit einem kleinen Trick (Stichwort Modulu-Operator) gewährleistet wird, daß nur ganze Zahlen geliefert werden. Nun werden zuerst die Leerzeichen für den linken Rand in den String hereinkopiert, dann der eigentliche Text aus dem temporären Arbeitsstring und zuletzt die Leerzeichen am rechten Rand. Nun muß nur noch der Speicherplatz wieder zurückgegeben werden und die Funktion ist mit ihrer Arbeit fertig.

Blocksatz

... ist die sicherlich schwerste Übung in der Routinensammlung, dadurch aber auch eine der Interessantesten. Was ist Blocksatz den nun eigentlich? Nun ja, alle Leerzeichen am Anfang und am Ende des Strings werden entfernt und zwischen den Wörtern eingefügt, wobei dabei eine möglichst zufällige und gleichmäßige Verteilung erreicht werden sollte. Zuerst müssen wir uns wieder die Länge des Strings besorgen, da sie nicht verändert werden soll und zum Zählen der Wörter und Leerzeichen, was danach geschieht, benötigt wird. Wenn es weniger als zwei Wörter oder ein Leerzeichen in dem String gibt, ist eine Weiterverarbeitung unmöglich und die Funktion wird beendet. Nachdem die minimale Anzahl Leerzeichen berechnet worden ist, wird ein Speicherbereich beim Betriebssystem angefordert, der den temporären String pad enthalten soll. Wenn kein Speicher zur Verfügung steht, gibt die Funktion NULL zurück und wird beendet. Ansonsten wird der String mit der minimalen Anzahl Leerzeichen zwischen den Wörtern gefüllt. Nun wird die Anzahl der zusätzlich benötigten Leerzeichen berechnet und ein String namens tmp angefordert. Wenn kein Speicher vorhanden ist, wird NULL zurückgegeben, andernfalls wird der String mit Einsen und Nullen gefüllt, um zusätzliche Leerzeichen zu kennzeichnen. Die Reihenfolge der Zeichen in tmp wird dann von str_shuffle(), einer ebenfalls in EXTSTR.C enthaltenen Funktion, geändert, da man ja eine möglichst gleichmäßige Verteilung der Leerzeichen zwischen den Wörtern wünscht. Nachdem dies geschehen ist, wird eine Arbeitskopie des übergebenen Strings angefertigt und das erste Wort aus der Arbeitskopie in den übergebenen String kopiert, dann die minimale Anzahl Leerzeichen, und, wenn angegeben, ein zusätzliches Leerzeichen usw., bis kein Wort mehr vorhanden ist. Jetzt wird schnell nur noch der reservierte Speicherplatz zurückgegeben und die Funktion ist fertig.

Die oben angesprochene Funktion str_shuffle() ändert die Reihenfolge der Zeichen in dem übergebenen String. Die Funktionsweise ist so trivial, so daß ich auf eine Beschreibung hier verzichte. Die Funktion wird eigentlich nur für str_bjust() benötigt, ich habe sie aber trotzdem als Funktion geschrieben und im Beispielprogramm aufgeführt, weil man sie vielleicht mal gebrauchen kann.

Die beiden Funktionen str_rrm() und str_lrm() entfernen jeweils die Leerzeichen am rechten bzw. linken Rand des übergebenen Strings. Die Funktionsweise kann der geneigte Leser sicherlich am Listing erkennen.

Die Funktion str_arm(), die für str_center benötigt wird, macht nur Funktionsaufrufe von str_rrm() und str_lrm(), so daß man auch sie nicht weiter erklären muß.

Interessanter ist da wieder die Funktion str_split(, die einen String nach einer zu übergebenen Anzahl von Zeichen in zwei Strings aufteilt. Nützlich ist das z.B. beim Textumbruch, wenn eine Zeile nicht mehr als n Zeichen haben soll. Der Funktionsprototyp lautet wie folgt (siehe auch EXTSTR.FI):

BYTE *str_split(BYTE *string_1, BYTE *string_2, WORD n);

Der String Nummer 1 wird nach höchstens n Zeichen umgebrochen, d.h. nur nach ganzen Worten wird umgebrochen. Wenn z.B. der String „Hallo, alle ST-USER“ nach n = 15 umgebrochen werden soll, enthält string_1 nach der Funktion den String „Hallo, alle“ und string_2 enthält den String „ST-USER“, jeweils mit abschließendem Null-Byte. Dazu wird zuerst die Länge des umzubrechenden Strings bestimmt, und wenn dieser bereits kurz genug ist. wird das Null-Byte in den zweiten String geschrieben und die Funktion beendet. Jetzt wird das letzte Leerzeichen vor der Stelle gesucht. Wenn keines gefunden wird, kann auch nicht gesplittet werden, und das Null-Byte wird in den zweiten String geschrieben und die Funktion beendet. Ansonsten wird an dieser Stelle gesplittet und der Rest des ersten Strings, in den zweiten geschrieben und das zurückgebliebene Leerzeichen mit dem Null-Byte überschrieben und die Funktion beendet. Die Funktion *str_fill() füllt den ersten String bis zu n Zeichen mit dem Inhalt von String Nummer 2. Ein Beispiel soll das verdeutlichen: Der Aufruf von

str_fill(string, "Hallo", 10);

liefert für string den Inhalt „Hallo Hall“, plus zusätzlichem, abschließendem Null-Byte.

Das Demoprogramm ...

... bedarf eigentlicher keiner Erklärung; es werden nur die Funktionen aufgerufen und die Ergebnisse invers auf dem Bildschirm ausgegeben, damit man sieht, ob und wenn ja wie viele Leerzeichen am String-Ende noch vorhanden sind.

Die Einbindung ...

... in eigene Programme ist ganz einfach: EXTSTR.C mit in die Project-Datei aufnehmen und EXTSTR.H in das Programm mittels #include einbinden, und schon können Sie die Routinensammlung benutzen. Ich wünschen Ihnen auf jeden Fall viel Erfolg bei der Benutzung von EXTSTR.C und schöne, Texte formatierende Programme, die auch anderen Leuten durch die vielfältigen Formatierungsmöglichkeiten positiv auffallen. Alle Routinen geben übrigens einen Zeiger auf den ersten übergebenen String zurück, also ist z.B. folgender Aufruf von str_center() möglich:

puts(str_center(string));

/* EXTSTRLIB.C */ /* Erweiterte Routinen zur Stringbehandlung, * / /* u.a. Zentrieren eines Strings, Ab- */ /* schneiden von Leerzeichen am Zeilenanfang * / /* und -ende. Links- und/oder Rechtsbündig */ /* machen. Zentrieren und Splitten in */ /* zwei Strings nach einer best. Anzahl von */ /* Zeichen. */ /* Version : 1.07 */ /* Datum : 17.06.1992 */ /* Autor : Andreas Papula */ /* Copyright 1993 by MAXON Computer GmbH. */ /* include-Files einbinden. */ #include <portab.h> #include <stdlib.h> #include <string.h> #include "extstr.h" /* BYTE *str_rjust(BYTE *string) */ /* Justiert einen String rechtsbündig. */ /* Die Länge des Strings wird dabei nicht */ /* verändert. */ /* Parameter:Ein Zeiger auf den zu */ /* bearbeitenden String. */ /* Rückgabe :Ein Zeiger auf den String. */ BYTE *str_rjust(BYTE *string) { ULONG i = 0; ULONG len_1 = 0; ULONG len_2 = 0; ULONG len_diff = 0; /* Anfangslänge feststellen */ len_1 = strlen(string); /* Leerzeichen am Ende entfernen */ str_rrm(string) ; /* Neue Stringlänge feststellen */ len_2 = strlen(string); /* Differenz der Längen berechnen */ len_diff = len_1 - len_2; /* String nach rechts verschieben */ while(len_2) string[--len_1] = string[--len_2]; /* Am linken Rand mit Leerzeichen auffüllen */ for(i = 0; i< len_diff; i++) string[i] = ''; return string; } /* BYTE *str_ljust(BYTE *string) */ /* Justiert einen String linksbündig. */ /* die Länge des Strings wird dabei nicht */ /* verändert. */ /* Parameter:Ein Zeiger auf den zu */ /* bearbeitenden String. */ /* Rückgabe :Ein Zeiger auf den String. */ BYTE *str_ljust(BYTE *string) { ULONG i = 0; ULONG len_1 = 0; ULONG len_2 = 0; /* Länge des Strings am Anfang */ len_1 = strlen(string); /* Leerzeichen am Anfang entfernen */ str_lrm(string); /* Neue Länge in Erfahrung bringen */ len_2 = strlen (string); /* Und bis zur alten Länge mit Leerzeichen auffüllen */ for(i = len_2; i < len_1; i++)string[i] = ''; return string; } /* BYTE *str_center(BYTE *string) */ /* Zentriert einen String. Die ursprüngliche Länge wird */ /* dabei nicht verändert. */ /* Parameter:Ein Zeiger auf den zu bearbeitenden String. */ /* Rückgabe: Ein Zeiger auf den String, NULL, wenn kein Speicherplatz für den temporären Arbeitsstring vorhanden ist. */ BYTE *str_center(BYTE *string) { ULONG len_1 = 0; ULONG len_2 = 0; ULONG len_diff = 0; ULONG space_r = 0; ULONG space_l = 0; ULONG i = 0; ULONG j = 0; BYTE *tmp_str; /* Anfangslänge des Strings ermitteln */ len_1 = strlen(string); /* Arbeitskopie des Strings anlegen */ if((tmp_str = strdup(string)) == NULL)return NULL; /* Leerzeichen am Anfang und Ende entfernen */ str_arm(tmp_str); /* Länge ermitteln */ len_2 = strlen(tmp_str); /* Differenz der Längen der beiden Strings ermitteln */ len_diff = len_1 - len_2; /* Anzahl der einzufügenden Leerzeichen berechnen */ if((len_diff % 2) == 1) { space_l = (len_diff - 1) / 2; space_r = (len_diff + 1) / 2; } else space_l = space_r = len_diff / 2; /* Erst die Leerzeichen am linken Rand */ for(i = 0; i < space_l; string[i] = ' ', i++); /* Dann der Text */ for(i = space_l, j = 0; i < space_l + len.2; string[i] = trap_str[j], i++, j++); /* Und jetzt die Leerzeichen am rechten Rand */ for(i = space_l + len_2; i < space_l + len_2 + space_r; string[i] = ' ', i++); /* Speicherplatz wieder freigeben */ free(tmp_str); return string; } /* BYTE *str_bjust(BYTE *String) */ /* Formatiert einen String im Blocksatz, a.h. am Anfang und am */ /* Ende werden alle Leerzeichen entfernt und zwischen den Wörtern */ /* wird der String dann wieder auf die ursprüngliche Länge */ /* aufgefüllt */ /* Parameter: Ein Zeiger auf den zu bearbeitenden String. */ /* Rückgabe : Ein Zeiger auf den String oder NULL, wenn kein */ /* Speicherplatz für die temporaren Arbeitsstrings vorhanden ist. */ BYTE *str_bjust(BYTE *String) { ULONG i = 0; ULONG len = 0; ULONG spaces = 0; ULONG words = 0; ULONG space_flag = 1; ULONG min_spaces = 0; ULONG extra_spaces = 0; BYTE *tmp; BYTE *wrk; BYTE *pad; /* Länge des Strings besorgen */ len = strlen(string); /* Wörter und Leerzeichen zählen */ for(i = 0; i < len; i++) { if(string[i] == ' ') { spaces++; space_flag = 1; } else if(space_flag) { words++; space_flag = 0; } } /* Wenn weniger als zwei Wörter oder keine Leerzeichen, Abbruch */ if(words <2 || spaces == 0) return string; /* Minimale Anzahl Leerzeichen zwischen den Wörtern berechnen */ min_spaces = spaces / (words - 1); /* String für die minimale Anzahl Leerzeichen besorgen */ if((pad = (BYTE *)malloc((min spaces + 1)) ) == NULL) return null; /* pad-String mit Leerzeichen füllen */ pad[min__spaces] = '\0'; for(i = 0; i < min_spaces; i++) pad[i] = ''; /* Anzahl der zusätzlichen Leerzeichen berechnen */ extra_spaces = spaces - (words - 1) * min_spaces; /* String für zusätzliche Leerzeichen besorgen */ if((tmp = (BYTE *)malloc(words)) == NULL) return NULL; /* tmp mit Einsen und Nullern füllen, um zusätzliche */ /* Leerzeichen zu kennzeichnen */ tmp[words - 1] = '\0'; for(i = 0; i < words - 1; i++) if (i < extra_spaces) tmp[i] = '1'; else tmp[i] = '0'; /* Reihenfolge der Einsen und Nullen zufällig 'machen' */ str_shuffle(tmp); /* Arbeitskopie des übergebenen Strings anfertigen */ if((wrk = strdup(string)) == NULL) return NULL; /* Erstes Wort in den String kopieren */ strcpy(string, strtok(wrk, " ")); /* Rest des Strings zusammenbauen */ for(i = 0; i < words - 1; i++) { /* Minimale Anzahl Leerzeichen hinzufügen */ strcat(string, pad); /* Zusätzliches Leerzeichen hinzufügen, wenn angezeigt */ if(tmp[i] == '1') strcat(string, " "); /* Nächstes Wort besorgen */ strcat(string, strtok(NULL, " ")); } /* Arbeitsspeicher zurückgeben */ free(wrk); free(tmp); free(pad); return string; } /* BYTE *str_shuffle (BYTE *string) */ /* Ändert die Reihenfolge der Zeichen in dem übergebenen String. */ /* Parameter:Ein Zeiger auf den zu bearbeitenden String. */ /* Rückgabe :Ein Zeiger auf den String. */ BYTE *str_shuffle(BYTE *string) { ULONG i = 0; ULONG j = 0; ULONG k = 0; ULONG len = 0; /* Länge des Strings feststellen */ len = strlen(string); /* Reihenfolge andern */ for(i = 0; i < len; i++) { j = rand() % len; k = (ULONG)string[i]; string[i] = string[j]; string[j] = (BYTE)k; } return string; } /* BYTE *str_arm(BYTE *string) */ /* Entfernt Leerzeichen am Anfang und am Ende eines Strings. */ /* Es werden str_rrm() und str_lrm() benutzt. */ /* Parameter:Ein Zeiger auf den zu bearbeitenden String. */ /* Rückgabe :Ein Zeiger auf den String. */ BYTE *str_arm(BYTE *string) { /* Leerzeichen am Ende entfernen */ str_rrm(string); /* Leerzeichen am Anfang entfernen */ str_lrm(string); return string; } /* BYTE *str_rrm(BYTE *string) »/ /* Entfernt Leerzeichen am Ende eines Strings. */ /* Parameter:Ein Zeiger auf den zu bearbeitenden String. */ /* Rückgabe :Ein Zeiger auf den String. */ BYTE *str_rrm(BYTE *string) { ULONG len = 0; /* Länge des Strings feststellen */ len = strlen(string); /* Leerzeichen am Ende des Strings mit '\0' überschreiben */ while(string[--len] == ' ') string[len] = '\0'; return string; } /* BYTE *str_lrm(BYTE *string) */ /* Entfernt Leerzeichen am Anfang eines Strings. */ /* Parameter:Ein Zeiger auf den zu bearbeitenden String. */ /* Rückgabe :Ein Zeiger auf den String. */ BYTE *str_lrm(BYTE *string) { UWORD i = 0; UWORD j = 0; ULONG len = 0; /* Zuerst die Länge des Strings besorgen */ len = strlen(string); /* Erstes Zeichen, daß kein Leerzeichen ist, suchen */ for(i = 0; i < len && string[i] == ' '; i++); /* Zeichen vertauschen */ for(j = 0; i <= len; string[j++] = string[i++]); return string; } /* BYTE *str_split(BYTE *string_1, BYTE *string 2, WORD n) */ /* Die Funktion splittet den String string_1 in zwei /* Teilstrings auf. */ /* Parameter: Ein Zeiger auf den zu splittenden String und ein */ /* Zeiger auf den zweiten String, der den Rest des */ /* ersten Strings enthält und die Anzahl der Zeichen, */ /* nach, denen spätestens gesplittet werden soll. */ /* Rückgabe :Ein Zeiger auf den ersten String. */ BYTE *str_split(BYTE *string_1, BYTE *string_2, WORD n) { ULONG len = 0; /* Länge des Strings besorgen */ len = strlen(string_1); /* Wenn der String bereits kurz genug ist, dann zurückgeben */ if(len <= n) { string_2[0] = '\0'; return string_2; } /* Letztes Leerzeichen vor der Stelle suchen * / for(;string_1[n] !=' ' && n; n--); /* Wenn kein Leerzeichen gefunden, auch nicht splitten */ if(!n) { string_2[0] = '\0'; return string_1; } /* Splitten ! */ strcpy(string_2, &string_1[n+1]); /* Leerzeichen vom Ende des ersten Strings entfernen */ while(string_1[n] == ' ') string_1[n--] = '\0'; return string_1; } /* BXTE *str_fill (BYTE *string_1, BYTE *string_2, WORD n) */ /* Füllt string_1 bis zur Länge len_1 mit len Zeichen aus string_2 */ /* und hängt das Null-Zeichen an. */ /* Parameter:Ein Zeiger auf den zu bearbeitenden String und ein */ /* Zeiger auf den Fullstring, sowie die gewünschte Länge */ /* des zu füllenden Strings. */ /* Rückgabe :Ein Zeiger auf den gefüllten string. */ BYTE *str_fill(BYTE *string_1, BYTE *string_2, WORD n) { ULONG i = 0; ULONG j=0; ULONG len_2 = 0; /* Länge des Füllstrings besorgen */ len_2 = strlen(string_2); /* Und jetzt füllen */ for(i = j =0; i < n; i++) { string_1[i] = string_2[j++]; if(j == len_2) j = 0; } /* Null-Zeichen dranhängen */ string_1[n] = '\0'; return string_1; } /* Ende der Datei */

/* EXTSTRLIB.H */ /* Include-Datei zu EXTSTRLIB.C. */ /* Revision: 1 */ /* Datum : 15.06.1992 */ /* Autor : Andreas Papula */ /* Copyright 1992 by MAXON Computer GmbH. */ #include <portab.h> BYTE *str_rjust(BYTE *string); BYTE *str_ljust(BYTE *string); BYTE *str_center(BYTE *string); BYTE *str_bjust(BYTE *string); BYTE *str_shuffle(BYTE *string); BYTE *str_arm(BYTE *string); BYTE *str_lrm(BYTE *string); BYTE *str_rrm(BYTE *string); BYTE *str_split(BYTE *string_1, BYTE *string_2, WORD n); BYTE *str_fill(BYTE *string_1, BYTE *string_2, WORD n); /* Ende der Datei */
/* STRTEST.C */
/* Testprogramm für die EXTended-STRing-Library. */
/* Version  : 1.10 */
/* Datum    : 17.06.1992 */
/* Autor    : Andreas Papula */
/* Copyright 1992 by MAXON Computer GmbH. */

/* Include-Files einbinden. */

#include <ext.h>
#include <stdio.h»
#include <string.h>
#include "extstr.h"

/* Konstantendeklarationen und Makros */

#define INVERS_ON   printf(”\33p")
#define INVERS_OFF  printf("\33g")

/* Das Hauptprogramm. */

int main(void)
{
    char string[80] = "   Dies ist ein Test!!!    ";
    char string2[80];
    puts("\33E----- Demo zu EXTSTR.C von A. Papula -----");
    /* ursprünglichen String ausgeben */ 
    INVERS_OFF;
    puts("Der ursprüngliche String:");
    INVERS_ON; 
    puts(string); 
    strcpy(string2, string);
    /* Leerzeichen am linken Rand entfernen */ 
    INVERS_OFF;
    puts("Leerzeichen am linken Rand entfernen:");
    INVERS_ON;
    puts(str_lrm(string2)); 
    strcpy(string2, string};
    /* Leerzeichen am rechten Rand entfernen */
    INVERS_OFF;
    puts("Leerzeichen am rechten Rand entfernen:");
    INVERS_ON;
    puts(str_rrm(string2)); 
    strcpy(string2, string);
    /* Leerzeichen am Anfang und Ende entfernen */
    INVERS_OFF;
    puts("Leerzeichen am Anfang und Ende entfernen:");
    INVERS_ON;
    puts(str_arm(string2)); 
    strcpy(string2, string);
    /* String linksbündig formatieren */
    INVERS_OFF;
    puts("String linksbündig formatieren:"); 
    INVERS_ON;
    puts(str_ljust(string2)); 
    strcpy(string2, string);
    /* String rechtsbündig formatieren */ 
    INVERS_OFF;
    puts("String rechtsbündig formatieren:"); 
    INVERS_ON;
    puts(str_rjust(string2)); 
    strcpy(string2, string);
    /* String zentrieren */
    INVERS_OFF;
    puts("String zentrieren:");
    INVERS_ON;
    puts(str_center(string2)); 
    strcpy(string2, string);
    /* String im Blocksatz formatieren */ 
    INVERS OFF;
    puts("String im Blocksatz formatieren:"); 
    INVERS_ON;
    puts(str_bjust(string2)); 
    strcpy(string2, string);
    /* Reihenfolge der Zeichen ändern */
    INVERS_OFF;
    puts("Reihenfolge der Zeichen verändern: ");
    INVERS_ON;
    puts(str_shuffle(string2));
    /* String nach 17 Zeichen splitten */ 
    INVERS_OFF;
    puts("String nach 17 Zeichen splitten:"); 
    INVERS_ON;
    puts(str_split(string, string2, 17)); 
    puts(string2);
    /* String bis 70 Zeichen mit "hallo" füllen */
    INVERS_OFF;
    puts("String bis 7 0 Zeichen mit \"hallo \" füllen:");
    INVERS_ON;
    puts(str_fill(string, "hallo ", 70));
    /* Invers-Schrift aufheben */
    INVERS_OFF ;
    /* Auf Taste warten */ 
    getch();
return 0;
}

Andreas Papula
Links

Copyright-Bestimmungen: siehe Über diese Seite