Stringverarbeitung mit Delphi

Mal ein paar anspruchsvollere Beispiele

Beispiel 1

Das dritte Element einer kommaseparierten Liste(also alles zwischen dem 2. und dem 3. Komma) soll ausgegeben werden

1
2
3
4
5
6
7
8
9
10
var
  s, s2: string;
  Pos1, Pos2, Pos3: Integer;
begin
  s := 'Apfel, Birne, Kirsche, Banane, Waschmaschine';
  Pos1 := Pos(',', s);  // erstes Komma
  Pos2 := PosEx(',', s, Pos1+1);  // zweites Komma
  Pos3 := PosEx(',', s, Pos2+1);  // drittes Komma
  s2 := Copy(s, Pos2+1, Pos3 - Pos2 -1); // Text zwischen 2. und 3. Komma
  ShowMessage(Trim(s2));  // Leerzeichen entfernen und ausgeben

Erklärung

Erklärungsbedürftig dürfte höchstens die Bestimmung der Länge des herauszukopierenden Teilstrings sein:
Pos3 - Pos2 -1
Die Länge des Teilstrings errechnet sich aus den Position der Kommas. Soweit sollte das klar sein. Und das -1 ist nötig, damit das letzte Komma nicht mitkopiert wird. Also eigentlich ganz einfach.

Exkurs: Fehlerbehandlung

Wenn man etwas programmiert, dann muss man sich Gedanken darüber machen, ob es Situationen gibt, in denen die geschriebenen Funktionen nicht so funktionieren wie sie sollen. Wir sehen uns also erst einmal die Funktion an und insbesondere die Eingabedaten.

Die einzige Eingabe ist der String s. Das, was bei dem String eine Rolle spielt, ist eigentlich nur die Anzahl und Position der Kommas. Was wir jetzt machen müssen, ist das Verhalten der Funktion in „Extremsituationen“ zu untersuchen. Als Eingabe müssen wir dabei Folgendes testen:
– Leerstring
– String ohne Kommas
– String mit nur einem Komma
– String mit nur 2 Kommas
– Strings mit Komma am Ende
– Strings mit aufeinanderfolgenden Kommas

Das ergibt folgende Eingabestrings:
– “
– ‚Apfel‘
– ‚Apfel, Birne‘
– ‚Apfel, Birne, Kirsche‘
– ‚Apfel, Birne,‘
– ‚Apfel,‘
– ‚Apfel,, Birne,, Kirsche‘
– ‚Apfel,,, Birne‘

Wir könnten uns jetzt jeweils überlegen, wo Fehler oder unerwünschtes Verhalten auftauchen könnten. Weitaus einfacher und sicherer ist es aber, es einfach auszuprobieren.

Zuerst einmal müssen wir uns aber überlegen, was wir eigentlich in diesen Fällen erwarten. In keinem dieser Fälle existiert ein Text zwischen dem 2. und 3. Komma. Logisch wäre also ein Leerstring als Rückgabewert. Diesen erhalten wir auch bei fast allen Teststrings. Lediglich bei ‚Apfel,‘ und ‚Apfel, Birne‘ – also dann, wenn sich nur ein Komma im String befindet – erhalten wir einen offensichtlich falschen Wert (nämlich ‚Apfel‘). Um zu untersuchen, wie dieses Fehlverhalten zustande kommt, sehen wir uns die Funktion im Debugger an:
– Pos1 erhält den Wert 6 –> klar
– Pos2 erhält den Wert 0, da PosEx nix mehr findet
– Pos3 erhält dann wieder den Wert 6, da wieder bei Pos2+1 = 0+1=1 angefangen wird zu suchen
– Copy liefert dann den Text zwischen 1(ganz vorne) und dem ersten Komma, was nicht unsere Absicht ist

Wie aber gehen wir dagegen vor? Eine Möglichkeit wäre, einfach abzufragen, ob nur ein Komma vorhanden ist. Das hat aber den Nachteil, dass a) keinem, der den Code list klar wird, warum nur das abgefragt wird, und b) man nur diesen einen Spezialfall abgesichert hätte (es könnte ja sein, dass wir welche übersehen haben). Die bessere Möglichkeit ist, das Problem an der Wurzel zu packen. Die Situation ist ja nur entstanden, weil kein zweites und drittes Komma gefunden wurde. Pos2 war also 0. Wir können sogar soweit gehen und sagen, dass, wenn Pos1, Pos2 oder Pos3 0 wird, ein Ausnahmefall vorliegt:

1
2
3
4
5
6
7
8
9
10
11
12
13
var
  s, s2: string;
  Pos1, Pos2, Pos3: Integer;
begin
  s := 'Apfel, Birne, Kirsche, Banane, Waschmaschine';
  Pos1 := Pos(',', s);  // erstes Komma
  Pos2 := PosEx(',', s, Pos1+1);  // zweites Komma
  Pos3 := PosEx(',', s, Pos2+1);  // drittes Komma
  if (Pos1 <> 0) and (Pos2 <> 0) and (Pos3 <> 0) then // alle Kommas gefunden?
    s2 := Copy(s, Pos2+1, Pos3 - Pos2 -1) // Text zwischen 2. und 3. Komma
  else
    s2 := ''; // Leerstring zurückgeben
  ShowMessage(Trim(s2));  // Leerzeichen entfernen und ausgeben

Somit haben wir zwar auch andere, schon durch die Copy-Funktion abgefangene Ausnahmefälle behandelt, aber a) stört das nicht und b) ist nun für jeden, der die Funktion sieht klar, warum da eine Prüfung stattfindet…

Beispiel 2

Es soll eine Stringkette der Form ‚Key1=Value1;Key2=Value2;…‘ so aufgeteilt werden, dass man einfach auf die einzelnen Key und Value-Wertze zugreifen kann.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
var
  input: string;
  sl: TStringList;
  Item: string;

  // Holt das nächste Element aus dem String
  function GetNext(var s: string): string;
  var
    PosSemikolon: Integer;
  begin
    PosSemikolon := Pos(';', s);   // Semikolon finden
    if PosSemikolon <> 0 then
    begin
      Result := Copy(s, 1, PosSemikolon -1);  // nächstes Element zurückgeben
      Delete(s, 1, PosSemikolon);    // Element aus String entfernen
    end
    else
    begin
      Result := '';    // keine Elemente mehr im String
    end;
  end;

  // liefert den Teil vor dem '=' in einer gegebenen Zeile der StringList
  function GetName(AStrings: TStringList; AIndex: Integer): string;
  var
    s: string;
    PosGleich: Integer;
  begin
    if AIndex < AStrings.Count then   // existiert die Zeile überhaupt?
    begin
      s := AStrings[AIndex];
      PosGleich := Pos('=', s);
      Result := Trim(Copy(s, 1, PosGleich -1));  // Teil vor dem '='
    end
    else
    begin
      Result := '';
    end;
  end;

  // liefert den Value-Teil einer Zeile mit einem gegebenen Namen
  function GetValue(AStrings: TStringList; AName: string): string;
  var
    i: Integer;
    PosGleich: Integer;
    s: string;
  begin
    Result := '';
    for i := 0 to AStrings.Count -1 do   // alle Elemente durchgehen
    begin
      if GetName(AStrings, i) = AName then    // ist das das gesuchte Element?
      begin
        s := AStrings[i];
        PosGleich := Pos('=', s);
        Result := Trim(Copy(s, PosGleich +1, Length(s) - PosGleich));  // Teil nach dem '='
      end;
    end;
  end;

begin
  // Verschiedene Programme und deren Hersteller
  input := 'Delphi=Borland; VisualStudio=Microsoft; C++Builder=Borland;'
    + ' TurboPascal=Borland; SharpDevelop=ICSharpCode; Acrobat=Adobe; OpenOffice=Sun;'
    + 'Office2007=Microsoft;';
  sl := TStringList.Create;  // StringList erstellen
  try
    Item := GetNext(input);  // das erste Element holen
    while Item <> '' do      // solange noch Elemente vorhanden sind
    begin
      sl.Add(Item);          // in StringList aufnehmen
      Item := GetNext(input);
    end;
    Memo1.Lines.Assign(sl);
    ShowMessage(GetName(sl, 3));  // Ausgabe: TurboPascal
    ShowMessage(GetValue(sl, 'SharpDevelop')); // Ausgabe: ICSharpCode
  finally
    sl.Free;  // Speicher freigeben
  end;
end;

An diesem Beispiel kann man sehen, wie uns Delphi in manchen Fällen sehr viel Arbeit ersparen kann. Das obige Beispiel lässt sich nämlich unter Zuhilfenahme einiger TStringList-Features deutlich kürzer schreiben:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var
  input: string;
  sl: TStringList;
begin
  // Verschiedene Programme und deren Hersteller
  input := 'Delphi=Borland; VisualStudio=Microsoft; C++Builder=Borland;'
    + ' TurboPascal=Borland; SharpDevelop=ICSharpCode; Acrobat=Adobe; OpenOffice=Sun;'
    + ' Office2007=Microsoft;';
  sl := TStringList.Create;  // StringList erstellen
  try
    sl.Delimiter := ';';   // Trennzeichen einstellen
    sl.DelimitedText := input; // Delphi die Arbeit machen lassen
    Memo1.Lines.Assign(sl);
    ShowMessage(sl.Names[3]);  // Ausgabe: TurboPascal
    ShowMessage(sl.Values['SharpDevelop']); // Ausgabe: ICSharpCode
  finally
    sl.Free;  // Speicher freigeben
  end;
end;

Mit Delimiter legt man das Trennzeichen fest, das die einzelnen Elemente voneinender trennt. Das Zuweisen des gegebenen Strings Input an die Eigenschaft DelimitedText bewirkt, dass der Inhalt von Input automatisch an den Stellen Delimiter aufgetrennt und in die StringList eingefügt wird. Über die Properties Names und Values kann man dann auch bequem auf die Schlüssel-Wert-Paare zugreifen, ohne sich darum kümmern zu müssen, wie das aufgetrennt wird…
Dabei ist aber zu beachten, dass TStringList ein Leerzeichen immer als Delimiter ansieht, egal, was als Delimiter angegeben wird. Damit Werte, die Leerzeichen enthalten, nicht getrennt werden, müssen sie in Anführungszeichen stehen. Die Funktion von Anführungszeichen können auch andere Chars übernehmen. Dazu muss die Eigenschaft QuoteChar entsprechend gesetzt werden.

Kapitel: | Zurück | 1 | 2 | 3 | 4 | Weiter |

3 Kommentare


  1. @Embarcadero (falls das jemand liest): Wär doch mal was, das direkt in die System.pas zu packen; könntet ihr auch gleich in eure elendslange Changelog-Datei schreiben und als neues Feature anpreisen 😉


  2. @Embarcadero (falls das jemand liest)

    Meinst du ehrlich?

    Wär doch mal was, das direkt in die System.pas zu packen

    Ein Tutorial in der System.pas? Ob das so sinnvoll ist… *kopfkratz*


  3. Hmm wollt das eigentlich zu der LastPos()-Funktion dazuschreiben, allerdings wird das dann zum gesamten Tutorial kommentiert.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert