Revision #1 Autor

Text in ProcessingVerarbeitung von Text-basierten Daten

Kern der Lesson ist die Verarbeitung von Text in Processing. Dabei stehen vor allem einfache Methodiken wie Suchen und Aufteilen von längeren Textpassagen im Mittelpunkt, die in der Datenanalyse und Informationsvisualisierung Verwendung finden.

Seit dem Kapitel Variablen I sind uns Zeichenketten (in Processing mit String betitelt) bekannt. Sie beinhalten Informationen über Schriftzeichen die wir mit text() abbilden können. Anders als bei int und float handelt es sich bei String um einen komplexen Datentyp. Dies erkennen wir an der Großschreibung und dem nicht orange eingefärbt werden im Processing Texteditor. Komplexe Datentypen besitzen einen erweiterten Umfang von Speicher- und/oder Funktionsmöglichkeiten. Beispielsweise erlauben uns Variablen vom Typ PImage mittels der Punktschreibweise und des Befehls width (myPImage.width) die Breite von Bilddokumenten auszulesen. Primitive Datentypen hingegen haben nur die Funktion einen Wert zu speichern.
Zeichenketten (String Variablen) sind ähnlich wie Arrays aufgebaut. Alle Schriftzeichen sind in einer Art Liste abgelegt die durchnummeriert bei 0 beginnt und bei der Anzahl der Zeichen minus 1 endet. Da wir nicht davon ausgehen können den abzubildenden Text in gewünschte Form vorliegen zu haben, bzw. der Text nur analysiert und die Ergebnisse visualisiert werden sollen, bietet Processing ein Set an Werkzeugen.

Texteigenschaften abfragen/auslesen

Zum Auslesen von Texten sind drei Funktionen essentiell.
  1. Die Länge von Texten; definiert durch die Anzahl von Zeichen die dieser enthält.
  2. Das Zeichen an einer bestimmten Position im Text. Da Strings, wie oben erwähnt, eine Liste aus Einzelzeichen sind, hat jede Position im Text den Inhalt von einem Character (char). Die Nummerierung beginnt, wie bei Arrays, bei dem ersten Zeichen mit 0.
  3. Die Position des ersten Vorkommes eines bestimmten Zeichens im Text. Meistens wird dies jedoch zur Überprüfung der Existenz genutzt. Wenn ein Text das gesuchte Zeichen nicht enthalten sollte, dann ist das Resultat minus 1, ein sonst unmöglicher Index. Alle drei Funktionen kombiniert mit den uns bekannten Bedingungen und Schleifen erlauben Lösungen für nahezu jedes Problem.
  • myText.charAt() gibt das Zeichen einer Zeichenkette an einer bestimmten Position zurück. Das erste Zeichen ist dabei an der Stelle 0, das Letzte an der Stelle length() - 1. siehe Referenz
    String s = "abc123ABC";
    println (s.charAt (1)); // b
    println (s.charAt (7)); // B
  • myText.equals() vergleicht zwei Zeichenketten und gibt das Ergebnis als true oder false zurück. siehe Referenz
    String s1 = "abc";
    String s2 = "ABC";
    
    if (s1.equals (s2) == true) {
        println ("s1 ist gleich s2");
    }else{
        println ("s1 ist nicht gleich s2"); // <-- wird ausgeführt
    }
  • myText.length() gibt die Länge, Anzahl an Zeichen, eines Textes zurück. siehe Referenz
    String s = "abc123ABC";
    println (s.length ()); // 9
  • myText.indexOf() sucht nach dem Vorkommen einer Zeichenkette innerhalb einer anderen Zeichenkette. Das Ergebnis ist die Position des ersten Treffers. Wird der gesuchte Textschnippsel nicht im Text gefunden, ist das Resultat -1, eine unmögliche Position im Text. siehe Referenz
    String s = "abc123ABC";
    println (s.indexOf ("1")); // 3
Bsp.: Länge von Texten
Bei der Länge handelt es sich immer um die Gesamtanzahl aller Schriftzeichen. Das beinhaltet neben Buchstaben, Zahlen und Sonderzeichen ebenfalls Leerzeichen, Tabs und weitere unsichtbare Zeichen und Symbole. Die Funktion length(), angewandt auf eine Variable vom Typ String, zählt alle und teilt uns ihr Ergebnis mit. Dabei handelt es sich immer um eine Ganzzahl (int).
String s1 = " -$- ";
String s2 = "3 Mark";
println (s1.length ()); // 5
println (s2.length ()); // 6

String s3 = s1 + " " + s2;
println (s3.length ()); // 12
Bsp.: Auslesen von Text (lückenhaft)
In diesem Beispiel lesen wir mittels der for-Schleife jedes zweite Zeichen aus unserem satz aus. Bei Index 0, dem ersten Zeichen, befindet sich das L. Wir erhöhen den Wert von i nach jedem Durchlauf um zwei und sind im zweiten Schritt bei dem Buchstaben r. Unsere Schleife läft solange i kleiner ist als unser Satz Zeichen hat - in diesem Fall 27.
String satz = "Lorem ipsum dolor sit amet.";

for (int i=0; i < satz.length (); i += 2) {
  print (satz.charAt (i)); // Lrmismdlrstae
}
Bsp.: Suchen
Basierend auf dem vorherigen Beispiel suchen wir nun in unserem Textschnippsel nach dem Ausdruck "ipsum". Wir erhalten von dem Aufruf s.indexOf ("ipsum") die Zahl (int) 6. Das Resultat beschreibt die Position der ersten Zeichenkombination "ipsum" in unserem Text. Es kann sich bei der Suche auch um einzelne Zeichen, Zahlen, Sonderzeichen oder Kombinationen dieser handeln.
Ausgehend von dieser Position starten wir mit unserer for-Schleife und lesen den Text Zeichen für Zeichen aus. Dabei wird unsere Zählvariable i immer um 1 erhöht, wir lassen kein Zeichen aus.
String s = "Lorem ipsum dolor sit amet.";

// Die Startposition wird durch das Suchergebnis 
// nach "ipsum" definiert. Das erste Vorkommen zählt!
int start = s.indexOf ("ipsum");

// Ausgabe aller Zeichen ab der durch "start" 
// beschriebenen Position im Text.
for (int i=start; i < s.length (); i++) {
  print (s.charAt (i));
}

Text modifizieren

Wie beim Auslesen bietet uns Processing ein Set an Tools Texte zu verändern. Angefangen bei der Funktionalität Inhalte von überschüssigen Leerzeichen am Anfang & Ende zu bereinigen bis hin zu komplexen Extrahiermethoden. Die Vorgehensweisen teilen sich in die Gruppe der Exakten, mit Regelanwendungen anhand genauer Positionsangaben, und die Unexakten. Meist ist die Struktur bzw. der Aufbau des Textes unbekannt. In diesem Fall kommt es zur Anwendung der unexakten Methoden. Beispielsweise benötigt man immer die ersten drei Sätze, ohne deren Länge zu berücksichtigen. Als Lösung teilen wir unseren Text in Textschnippsel, getrennt wird immer am Satzende - der Position im Text wo ein Punkt gesetzt ist.
Exakte Funktionen erfordern qualitativ hochwertige Daten/Texte. Das Resultat des Programms hängt von der Struktur des Texten, der genauen Einhaltung der Zeichenabfolge, ab.
  • split() zerteilt eine Zeichenkette an vordefinierten Zeichen in separate Zeichenketten. Das Resultat ist ein Array aus String-Objekten. Das zum Zerteilen benutzte Zeichen (auch split token genannt) wird aus den resultierenden Teilzeichenketten entfernt. siehe Referenz
    String names = "Ursula,Uwe Kowalski,Beate,Peggy Schmidt,Max";
    String[] arr = split (names, ",");
    
    println (arr[1]); // Uwe Kowalski
    println (arr[2]); // Peggy
  • join() fügt ein String-Array zu einem String zusammen. Beginnend bei dem Ersten (Index von 0) werden alle folgenden Zeichenketten im Array angehangen. siehe Referenz
    String[] arr = new String[3];
    arr[0] = "Ursula";
    arr[1] = "Uwe Kowalski";
    arr[2] = "Beate";
    String names = join (arr, ",");
    
    println (names); // Ursula,Uwe Kowalski,Beate
  • substring() gibt einen Teilabschnitt eines bestehenden Textes zurück. Der Bereich kann durch zwei Formulierungen bestimmt werden: (1) Anfangsposition; Extrakt läuft bis zum Ende des Textes (2) Anfangs- und Endposition; Einschränkung in beiden Richtungen. siehe Referenz
    String s = "abc123ABC";
    println (s.substring (2)); // c123ABC
    println (s.substring (3, 6)); // 123
  • trim() entfernt unnötige Leerzeichen am Anfang und Ende einer Zeichnkette. siehe Referenz
    String s = "   My tiny Text ";
    println ("|" + s + "|"); // |   My tiny Text |
    s = trim (s);
    println ("|" + s + "|"); // |My tiny Text|
Bsp.: Text - Satz - Wort
Vom Großen ins Kleinteilige. Das Beispiel zerlegt den zu Beginn erstellten Text snippet in einzelne Sätze bzw. im zweiten Schritt diese in einzelne Wörter. Die Anzahl der Sätze und die Satzlänge sind dabei nicht von Bedeutung. Die Trennung erfolgt dabei:
  1. in Sätze mit split("mein Text", "."), Auftrennung des Textes an jedem Punk
  2. in Wörter mit split("mein Text", " "), Auftrennung des Satzes an jedem Leerzeichen
In beiden Fällen erhalten wir ein Array aus Strings zurück, welches wir mit zwei Zählschleifen auslesen.
String snippet;

snippet += "Dort! sagte der Bieber zum Esel.";
snippet += " In diesem Augenblick ging das Feuer aus.";
snippet += " Die Tankstelle hatte schon zu.";

// den Text "snippet" in einzelne Sätze zerlegen
String[] satz = split (snippet, ".");

// für jeden Satz
for (int s = 0; s < satz.length; s++) {
  // Leerzeichen am Anfang entfernen
  satz[s] = satz[s].trim ();
  // kompletten Satz ausgeben
  println (satz[s]);
  
  // wenn der Satz mehr als null Zeichen hat
  if (satz[s].length () > 0) {
    // den Satz in Worte zerlegen
    String[] wort = split (satz[s], " ");
    
    // für jedes Wort im Satz
    for (int w = 0; w < wort.length; w++) {
      // Wort inklusive Position im Satz ausgeben
      println (" (" + w + ") " + wort[w]);
    }
  }
}
Bsp.: Ausschneiden 1
Mit diesem Beispiel bedienen wir den Bereich des exakten Arbeitens mit Strings. Exakt, weil wir mit genauen Positionsangaben arbeiten. Mit einer for-Schleife durchlaufen wir unser Array das mit Namen gefüllt ist. Jedem Namen ist eine einstellige Ziffer, gefolgt von Punkt und Leerzeichen, vorangestellt. Diese drei Zeichen sind uns in unserem Programm unpassend - wir bevorzugen die freigestellten Namen. Um dies zu Realisieren nutzen wir die Funktion substring() mit einem Parameter. Die übergebene drei gibt das erste Zeichen an, welches unser Ergebnis enthalten soll. Da wir kein Ende zur Begrenzung angegeben haben, gibt uns Processing den Rest der Zeichenkette ab dem vierten Element zurück.
Das Programm ist in dieser Form strikt an den Aufbau der Zeichenketten gebunden. Mehrstellige Zahlen zu Beginn würden das Leerzeichen, den Punkt bzw. möglicherweise Ziffern in das Ergebnis rutschen lassen.
String[] name = new String[3];

name[0] = "1. Peggy Meier";
name[1] = "2. Rudolf Schmidt";
name[2] = "3. Eckhard Karsten";

for (int i=0; i < name.length; i++) {
  println (name[i].substring (3));
}
Bsp.: Ausschneiden 2
Um die Anfälligkeit zu verringern kann eine Kombination aus exakten und unexakten Funktionen benutzt werden. Wenn wir davon aus gehen, dass hinter jeder Zahl ein Punkt folgt, erlaubt uns die unexakte Funktion split() im ersten Schritt Zahl und Name zu trennen. Im zweiten Schritt entfernen wir das überschüssige Leerzeichen mittels der exakten Funktion substring().
String[] name = new String[3];

name[0] = "1. Peggy Meier";
name[1] = "98. Rudolf Schmidt";
name[2] = "11124. Eckhard Karsten";

for (int i=0; i < name.length; i++) {
  String[] arr = split (name[i], ".");
  println (arr[1].substring (1));
}

Laden von Texten

Processing gibt uns eine Funktion an die Hand, mit der wir Textdokument auf einfache Art und Weise auslesen können. Die einzige Vorraussetzung ist die Ablage der Datei im Ordner SketchOrdner/data. Eine Textdatei wird von Processing Zeile für Zeile analysiert. Hat Processing eine Zeile eines Textes gelesen, speichert es diese in einem String-Objekt ab. All diese String-Objekte werden am Ende in einem String-Array zusammengeführt und uns zur Verfügung gestellt.
  • loadStrings() öffnet ein bestehendes .txt Dokument auf der Festplatte und läd deren Inhalt in ein String-Array (String[]). Array, da jede Textzeile einem eigenen Feld im Array zugewiesen wird. Zeile 1 im Feld 0, Zeile 2 im Feld 1, usw. . Das Textdokument muss sich im Ordner data des Sketchordners befinden.
Bsp.: Textdokument auslesen
String[] myText = loadStrings ("source.txt");

for (int i=0; i < myText.length; i++) {
  println (myText[i]);
}
Bsp.: Auslesen und Zusammenführen
Wenn alle Textschnippsel, die jeweil in einem Feld des Text-Arrays liegen, zusammengeführt werden, verliert man die Zeilenumbrüche. Im unteren Beispiel geben wir die Zeilen innerhalb der for-Schleife nicht aus, sondern hängen sie an unseren Text "s" an. Um einen Zeilenumbruch nach jeder Zeile (wie im Dokument) zu erhalten, wird der Inhalt von myText[i] um den Ausdruck \n erweitert. Dies steht für new line.
  • \n neue Zeile
  • \t Tabulator (Texteinzug)
String[] myText = loadStrings ("source.txt");
String s = "";

for (int i=0; i < myText.length; i++) {
  s += myText[i] + "\n";
}
println (s);