Die Arbeit mit regulären Ausdrücken teilt sich in zwei Kategorien ein: Zum
Einen ist da das Suchen, bei dem der Text durchsucht, aber nicht verändert
wird. Zum Anderen gibt es das Suchen & Ersetzen, das bereits im vorigen
Kapitel angesprochen wurde. In beiden Fällen braucht man ein Suchmuster, im
zweiten Fall auch ein Ersetzungsmuster.
Zunächst werden wir uns mit dem Suchmuster befassen und ein paar einfache
Regeln kennenlernen. Damit das Ganze nachvollziehbar wird, schlage ich vor,
die folgenden kleinen Übungen direkt auf der AutoCAD-Kommandozeile
nachzuvollziehen. Dazu muss natürlich die PCRE-Funktionalität erst einmal in
Lisp geladen sein. Das ist ganz einfach: Laden Sie die Datei
pcre.arx
(33 kB) hier herunter und legen Sie sie irgendwo ab, wo AutoCAD sie findet.
Dann muss sie nur noch mit dem AutoCAD-Befehl APPLOAD oder der Lisp-Funktion
(arxload "pcre2006.arx") geladen werden. Die Datei lässt sich übrigens auch
problemlos wieder entladen, was ja bei ARX-Anwendungen oft nicht der Fall ist.
Nach dem Laden steht uns eine einzige neue Lisp-Funktion namens (pcre-match
...) zur Verfügung. Diese Funktion erwartet 2 oder 3 Argumente:
- das Suchmuster
- die zu durchsuchende Zeichenkette
- optionales Argument: eine weitere Zeichenkette für die Suchoptionen
Um das dritte Argument machen wir uns erst einmal keine Gedanken, wir lassen
es einfach weg. Tippen wir doch einmal eine ganz kleine Suchanfrage ein. Als
Suchstring verwenden wir das Wort "Donaudampfschifffahrtsgesellschaft", und
wir wollen wissen, ob der Buchstabe m in diesem Wort vorkommt:
(pcre-match "m" "Donaudampfschifffahrtsgesellschaft") => ((7 1))
So eine einfache Frage hätte sich auch mit wcmatch klären lassen:
(wcmatch "Donaudampfschifffahrtsgesellschaft" "*m*") => T
Man beachte, dass wcmatch immer nur zwei vollständige Zeichenketten
miteinander vergleicht, von denen die zweite Stellvertreterzeichen enthalten
kann - deshalb die Sternchen vor und nach dem Buchstaben m. Das PCRE-Matching
ist völlig anders: Es prüft nicht, ob der gesamte String und das Suchmuster
'matchen', sondern es sucht passende Stellen und gibt deren Position zurück.
Die Rückgabe ((7 1)) ist so zu interpretieren: Die Position im String ist 7,
und die Fundstelle ist 1 Zeichen lang.
Nehmen wir einmal an, es soll ein Text daraufhin untersucht werden,
ob er mit der alten oder der neuen Rechtschreibung konform ist (gemäß der
alten Rechtschreibung schrieb man ja "Schiffahrt" mit nur zwei f). Natürlich
ist eines auf Anhieb klar, dass man nach 3 f so suchen kann:
(pcre-match "fff" "Donaudampfschifffahrtsgesellschaft") => ((14 3))
Ach ja, hier wird immer ab 0 gezählt! Der 15. Buchstabe ist also das erste der
3 f. Sucht man jedoch nach 2 f, passiert folgendes: Logischerweise enthalten 3
f immer 2 f, deswegen wird eine Suche nach einem Doppel-f die gleiche
Fundstelle anzeigen:
(pcre-match "ff" "Donaudampfschifffahrtsgesellschaft") => ((14 2))
Um ganz explizit ein Doppel-f zu suchen, müssen wir eine andere Technik
anwenden und das Suchmuster präzisieren:
(pcre-match "[^f]ff[^f]" "Donaudampfschifffahrtsgesellschaft") => nil
Die eckigen Klammern bedeuten normalerweise "ein Zeichen aus dieser Menge",
ist allerdings ein Caret ^ am Anfang, wirkt dieses als Negationszeichen, es
heisst nun "ein Zeichen, das nicht in dieser Menge ist". [aeiou] bedeutet also
"ein Vokal in Kleinbuchstaben", [^AEIOU] heisst dann, bei Licht betrachtet,
"ein großgeschriebener Nicht-Vokal". Das muss noch nicht unbedingt ein
Konsonant sein, denn es könnte ja eine Ziffer, ein Satzzeichen oder sonst
etwas sein. Kurzum, [^f] bedeutet also "alles, nur kein kleines f". Wir suchen
also nach einem Zeichen, das kein f ist, dann zwei f, dann wieder ein anderes
Zeichen. Natürlich enthält das Riesenwort in neuer Rechtschreibung kein
Doppel-f, sondern ein Dreifach-f. Deswegen bekommen wir eine leere Liste (nil)
zurück. Die Gegenprobe mit der alten Schreibweise:
(pcre-match "[^f]ff[^f]" "Donaudampfschiffahrtsgesellschaft") => ((13 4))
Die nächste Übung ist wieder etwas komplexer: Es soll in diesem Wort (wieder
mit 3 f gemäß der neuen Rechtschreibung) eine Stelle gesucht werden, an der
6 Konsonanten aufeinander folgen. Das lässt sich so realisieren:
(pcre-match "[bcdfghjklmnpqrstvwxyz]{6}" "Donaudampfschiffahrtsgesellschaft")
Die Angabe {n} nach einem Ausdruck beutet also n-mal, das erspart es uns, den
Ausdruck n-mal zu schreiben. Es gibt aber noch weitere Abkürzungen, z.B. die
Schreibweise [0-9] für eine Ziffer, [a-z] für Kleinbuchstaben. [a-zA-Z] für
alle Buchstaben usw. Ferner gibt es dann noch folgende Zeichen:
- \d steht für eine Ziffer (entspr. [0-9])
- \s steht für ein Whitespace-Zeichen, also Leerzeichen, Tabulator,
Zeilenumbruch usw. (entspr. [ \t\n\r\f])
- \w steht für ein Wort-Zeichen (entspr. [a-zA-Z0-9_])
Diese Abkürzungen können durch das Verwenden eines Großbuchstabens negiert
werden:
- \D ist also eine Nicht-Ziffer [^0-9]
- \S steht für ein Nicht-Whitespace-Zeichen [^ \t\n\r\f]
- \W steht für ein Nicht-Wort-Zeichen [^a-zA-Z0-9_]
Noch wesentlicher ist es aber, dass Sie die Bedeutung der folgenden Zeichen
verstehen:
- . irgendein Zeichen
- * das Zeichen kann 0 mal vorkommen, aber auch öfters
- + das Zeichen muss mindestens einmal vorkommen
- ? das Zeichen kann 0 oder 1 mal vorhanden sein
- \ das folgende Zeichen als Literal verwenden
Ein paar Beispiele:
- a+ sucht nach mindestens einem a, also a oder aa oder aaa ...
- a.b.c sucht nach abc mit irgendwas dazwischen, z.B. axbxc oder a_b_c
- A.*E.*G würde z.B. "Ausschalten, Einschalten, Garantiefall" finden, aber auch AEG
- A.+E.+G würde zwar "Ausschalten, Einschalten, Garantiefall" finden, aber nicht AEG
- \d sucht nach genau einer Ziffer
- \d+ sucht nach mindestens einer Ziffer
- \d* sucht nach mindestens keiner Ziffer (!)
- r e sucht nach einer Stelle im Text, wo ein Wort mit r aufhört und das nächste mit e anfängt
- r +e dasselbe, aber es können auch mehrere Leerstellen dazwischen sein
- r *e das reagiert auch auf 'reagieren', weil zwischen dem r und dem e mindestens keine Leerstelle ist
Kehren wir zurück zur eigentlichen Aufgabe, die im vorigen Kapitel formuliert
wurde: Es soll eine Dezimalzahl innerhalb einer Zeichenkette gefunden werden.
Wenn wir als Beispiel den String "Ein Text, in dem die Zahl 123,45 zu finden
ist", dann können wir das Suchmuster so in Worten definieren:
Zunächst eine beliebige Anzahl von Zeichen, dann eine Leerstelle, dann
mindestens eine Ziffer, anschließend ein Komma, dann wieder mindestens eine
Ziffer, eine Leerstelle und schließlich wieder beliebige Zeichen bis zum Ende.
Und so sieht unser Suchmuster nun in der Praxis aus:
.* \d+,\d+ .*
Wir müssen allerdings, um dieses Muster austesten, wie immer in Lisp die
Backslashs doppelt schreiben (der Backslash wird sonst mit dem darauf
folgenden Zeichen als Steuerzeichen gewertet).
(pcre-match ".* \\d+,\\d+ .*" "Ein Text, in dem die Zahl 123,45 zu
finden ist") => ((0 46))
Die Rückgabe zeigt uns an, dass unser String von Anfang bis Ende dem
Suchmuster entspricht. Damit wissen wir aber noch immer nicht, *wo* sich die
Zahl befindet. Um das zu klären, gruppieren wir mit runden Klammern:
(pcre-match "(.* )(\\d+,\\d+)( .*)" "Ein Text, in dem die Zahl 123,45
zu finden ist") => ((0 43) (0 26) (26 6) (32 11))
Für jede Gruppierung, also jede von uns eingefügte runde Klammer,
erhalten wir eine weitere Unterliste mit einer Position und einer Länge. Man
kann das Ergebnis also so interpretieren: Von Pos 0 bis 25 (Länge 26) wurden
die beliebigen Zeichen sowie die Leerstelle vor der Zahl gefunden. Die 6
Zeichen ab Pos. 26 sind die Zahl einschl. Komma, und ab Pos. 32 folgen wieder
eine Leerstelle und beliebige Zeichen.
Die Anzahl der Klammerpaare ist übrigens in der hier vorliegenden
Implementation auf 9 begrenzt, das Ergebnis enthält maximal 10 Unterlisten
(die erste ist immer der 'whole match', weitere 9 können die
Gruppen-Positionen und -längen beinhalten). Denken Sie immer daran, dass sich
der 'whole match' an der Länge des ausgewerteten Suchmusters orientiert,
niemals aber an der Länge der zu durchsuchenden Zeichenkette! Allerdings kann
der 'whole match' aber niemals länger als die Zeichenkette werden.
Damit haben Sie nun einen kleinen Einblick in das Suchen mit regulären
Ausdrücken bekommen. Im nächsten Kapitel werden wir uns mit dem Ersetzen
befassen.