Sogenannte „reguläre Ausdrücke" stammen ursprünglich aus Programmiersprachen
wie Perl oder PHP, stehen aber inzwischen in zahlreichen Programmiersprachen zur
Verfügung. Für Reguläre Ausdrücke gibt es daher auch eigene Fachbücher.
Sie ermöglichen Textuntersuchungen anhand von Mustern, die weit über das hinausgehen,
was mit den in VBA integrierten Zeichenfolgefunktionen
möglich ist. Für VBA gibt es für Reguläre Ausdrücke die
Microsoft VBScript Regular Expressions
-Objektbibliothek, die natürlich
für dieses Kapitel eingebunden sein muss.
Bekanntlich kann man mit den
Platzhalterzeichen
des Like
-Operators in einem Text ein Muster suchen. Darüber hinaus
kennt VBA einige Zeichenfolgefunktionen,
mit denen in gewissem Umfang auch Zeichenfolgen durchsucht oder verändert werden
können. Schauen wir uns zunächst einen einfachen Fall an, der mit dem
Like
-Operator lösbar ist:
Debug.Print "Mayer" Like "Ma?er" Wahr
Nun stellen wir diesen Vergleich mit einem regulären Ausdruck nach.
Public Function vergleiche(str As String, muster As String) As Boolean Dim regex As New RegExp regex.Pattern = muster vergleiche = regex.Test(str) End Function
Debug.Print vergleiche("Mayer", "Ma.er") Wahr
Es wird also ein Objekt vom Typ RegExp
angelegt. Das Vergleichsmuster
- also der eigentliche reguläre Ausdruck - wird in der Eigenschaft Pattern
gesetzt. Wir sehen, dass die Vergleichsmuster eines regulären Ausdrucks anders sind
als die Platzhalterzeichen des Like
-Operators. Während allerdings damit
die Möglichkeiten von Like
allmählich ausgeschöpft sind, fängt
RegExp
hier erst an. Schon für die Suche nach einem einzelnen Zeichen
stehen verschiedene Suchmuster zur Verfügung:
Reguläre Ausdrücke für einzelne Zeichen | |||
---|---|---|---|
Symbol | Beispiel | Ergebnis | Verwendung |
. | Ma.er | findet Maier, Majer, Mayer | Ein beliebiges Zeichen |
[ ] | Ma[iy]er | findet Maier, Mayer | Eines der Zeichen in den Klammern |
[ - ] | b[a-z]d | findet bad, bbd, bcd, bzd | Ein Zeichen im angegebenen Bereich (Reihenfolge gemäß Zuordnungstabelle) |
[^ ] | Ma[^iy]er | findet Majer, aber nicht Maier oder Mayer | Ein nicht in den Klammern aufgelistetes Zeichen |
\d | Nr \d | findet Nr 1, Nr 9 | Eine Ziffer (entspricht [0-9]) |
\D | Nr \D | findet Nr A, aber nicht Nr 1 | Keine Ziffer (entspricht [^0-9]) |
\w | Anhang \w | findet Anhang 1, Anhang A | Buchstabe, Ziffer oder Unterstrich (entspricht [a-zA-Z0-9_]) |
\W | abc\Wefg | findet abc efg, aber nicht abcdefg | kein Buchstabe, Ziffer oder Unterstrich (entspricht [^a-zA-Z0-9_]) |
\t | abc\txyz | findet abc & vbTab & xyz | Steuerzeichen „Tabulator“ |
\n | abc\nxyz | findet abc & vbNewLine & xyz | Steuerzeichen für Zeilenumbruch |
\f | abc\fxyz | findet abc & vbFormFeed & xyz | Steuerzeichen für vbFormFeed |
\r | abc\rxyz | findet abc & vbLf & xyz | Steuerzeichen für vbLf |
\x | \x41BC | findet ABC | Auf „x“ folgt der hexadezimale Zeichenwert eines Zeichens |
Es gibt auch Möglichkeiten, die Plätze des Suchmusters innerhalb der zu durchsuchenden Zeichenkette anzugeben.
Reguläre Ausdrücke für den Gesamtausdruck | |||
---|---|---|---|
Symbol | Beispiel | Ergebnis | Verwendung |
^ | ^Wort | findet Wort am Anfang | markiert den Anfang der Zeichenkette/Zeile |
$ | Wort$ | findet Wort am Ende | markiert das Ende der Zeichenkette/Zeile |
\b | ung\b | findet ung am Wortende | bezeichnet eine Wortgrenze (zwischen \w und \W) |
\B | \Baus | findet Haus oder Maus, aber nicht aus | bezeichnet alles außer einer Wortgrenze |
Will man nach einem Zeichen suchen, das in einem regulären Ausdruck eine
Bedeutung hat, wie z. B. [
oder ^
,
muss man einen Backslash (\
) voranstellen. Das nennt man
„das Zeichen maskieren“. Um nach dem Backslash selbst zu suchen, entsprechend zweimal
den Backslash.
Debug.Print vergleiche("F:\Data", "\\") Wahr
Mit regulären Ausdrücken kann man auch nach längeren Zeichenketten suchen. In aller Regel muss dafür diese Zeichenkette mit (runden Klammern) zu einem Teilausdruck gruppiert werden. In der folgenden Tabelle werden die entsprechenden Möglichkeiten anhand einzelner Zeichen aufgeführt, genauso könnten dort aber auch Teilausdrücke stehen.
Reguläre Ausdrücke für (Teil)ausdrücke | |||
---|---|---|---|
Symbol | Beispiel | Ergebnis | Verwendung |
* | Han*s | findet Has, Hans, Hanns | der vorhergehende Ausdruck nicht oder beliebig oft |
+ | Han+s | findet Hans, Hanns | der vorhergehende Ausdruck einmal oder beliebig oft |
? | Han?s | findet Has oder Hans | der vorhergehende Ausdruck nicht oder einmal |
{ } | A{3} | findet AAA | der vorhergehende Ausdruck exakt so oft, wie in der Klammer angegeben |
{ , } | A{2,4} | findet AA, AAA und AAAA | der vorhergehende Ausdruck so oft, wie in der Klammer angegeben |
{ ,} | A{2,} | findet AAA, AAAA, AAAAA, ... | der vorhergehende Ausdruck mindestens so oft, wie in der Klammer angegeben |
{, } | A{,4} | findet A, AA, AAA und AAAA | der vorhergehende Ausdruck höchstens so oft, wie in der Klammer angegeben |
Um nach Teilausdrücken zu suchen, ist also beispielsweise auch
Ich gehe( langsam)? zu dir
möglich, wenn man nicht weiß,
ob „langsam“ an der entsprechenden Stelle im String vorkommt.
In den Klammern können mit |
auch Alternativen eingebaut
werden, z. B.
And(i|reas)
, für Andi oder Andreas,
Wir gehen zu (dir|mir|uns|euch)
.
Bei Suchmustern mit *
oder +
ist zu beachten, dass sie im Normalfall besonders „gierig“ sind:
Debug.Print vergleiche("Mein Haus! Mein Auto! Mein Boot!", "Mein .+!")
Wahr
Zur besseren Übersichtlichkeit ist in diesem Beispiel markiert, was der
reguläre Ausdruck Mein .+!
alles erkennt: Wegen des
.+
nämlich alles zwischen dem ersten „Mein “ bis zum
letzten Ausrufezeichen. Soll der Ausdruck statt dessen nur bis zum ersten
Ausrufezeichen gelten, muss auf das +
ein
?
folgen, um nicht so gierig zu sein:
Debug.Print vergleiche("Mein Haus! Mein Auto! Mein Boot!", "Mein .+?!")
Wahr
Bisher haben wir uns lediglich die Test
-Methode sowie die
Pattern
-Eigenschaft des RegExp
-Objekts angesehen.
Dieses Objekt hat allerdings noch weitere Eigenschaften und Methoden: Mit der
IgnoreCase
-Eigenschaft kann bestimmt werden, ob zwischen Groß- und
Kleinschreibung unterschieden wird, mit MultiLine
wird bestimmt, ob
Zeilenumbrüche im Suchstring dazu führen, dass jede Zeile als eigener Ausdruck
behandelt werden soll, und die Global
-Eigenschaft bestimmt, ob nur
die erste gefundene Suchstelle zurückgegeben werden soll, wenn der Ausdruck
mehrfach matcht.
Insbesondere die letzten beiden Eigenschaften werden erst interessant, wenn nicht
nur einfach die Test
-Methode zum Einsatz kommt. Test
gibt
nur zurück, ober der Ausdruck matcht oder nicht. Dagegen wendet die
Execute
-Methode den Ausdruck zunächst an und speichert das Ergebnis
in Objekten vom Typ Match
, die wiederum in einem
MatchCollection
-Objekt zusammengefasst sind.
Public Sub suchInZeile() Dim regex As New RegExp, Fundstellen As MatchCollection, Fund As Match Dim myString As String myString = "Zeile A" & vbNewLine & "Zeile B" regex.Pattern = "\w$" regex.MultiLine = True regex.Global = True Set Fundstellen = regex.Execute(myString) Debug.Print Fundstellen.Count For Each Fund In Fundstellen Debug.Print Fund Next End Sub
In diesem Beispiel wird zuerst ein zweizeiliger String definiert. Als Suchmuster
wird \w$
definiert; es wird also nach Buchstaben
am Ende gesucht. Während aber üblicherweise die Suche beendet wird, sobald ein erster
Treffer gefunden wurde, wird hier mit Global
nach allen Vorkommen gesucht.
Da auch noch MultiLine
gesucht wird, bekommt das $
im Suchmuster eine etwas andere Bedeutung: Nun heißt es nicht mehr „Ende des gesamten
zu durchsuchenden Strings", sondern „Ende einer jeden Zeile“. Abschließend wird
ausgegeben, wie viele Treffer gefunden wurden, und dann noch alle einzelnen Treffer.
suchInZeile 2 A B
Wenn nicht sowohl Global
als auch MultiLine
aktiviert
sind, wird immer nur eines der beiden Zeilenenden gefunden, und zwar entweder „A“
oder „B“.
Wie schon erwähnt, kann man Teilausdrücke mit (runden Klammern) gruppieren.
Diese Teilausdrücke werden zugleich auch in eigenen Objekten gespeichert. Während
der gesamte gefundene Ausdruck, wie eben beschrieben, im Match
-Objekt
gespeichert wird, finden sich Teilausdrücke darin in SubMatch
-Objekten:
Public Sub suchKlammern() Dim regex As New RegExp, Fundstellen As MatchCollection, Fund As Match Dim myString As String myString = "Lukas ist ein bisschen eigenartig" regex.Global = True regex.Pattern = "\b(\w)(\w+)" Set Fundstellen = regex.Execute(myString) Debug.Print Fundstellen.Count For Each Fund In Fundstellen Debug.Print Fund.SubMatches(0), Fund.SubMatches(1) Next End Sub
Dieses Beispiel ähnelt dem vorigen. Im Unterschied dazu werden nun im Suchmuster
Klammern verwendet, nämlich einmal für den ersten Buchstaben eines Worts sowie für
den Rest des Wortes. Wegen der Global
-Eigenschaft kommt es je Wort
zu zwei Funden, und die For-Each-Schleife gibt die jeweiligen Inhalte der Klammern
mit Hilfe des Submatches
-Objekts aus.
suchKlammern 5 L ukas i st e in b isschen e igenartig
Mit der Replace
-Methode sind auch Ersetzungen im Suchstring möglich.
Public Function ersetze(myString As String, Muster As String, Ersatz As String) Dim regex As New RegExp regex.Global = True regex.Pattern = Muster Set Fundstellen = regex.Execute(myString) ersetze = regex.Replace(myString, Ersatz) End Function
Dieser einfachen Prozedur werden der zu durchsuchende String und ein Suchmuster
übergeben. Mit Replace
werden die gefundenen Suchmuster durch strErsatz
ersetzt.
Debug.Print ersetze("überflüssig", "ü", "ue") ueberfluessig
Enthält der Suchstring geklammerte Ausdrücke, kann man im Ersetzungsstring auf
den Inhalt der Klammern zugreifen. Der Inhalt der Klammern wird in „Variablen“ mit
den vordefinierten Namen $1
, $2
usw. gespeichert, in der
Reihenfolge der Klammern.
Debug.Print ersetze("Maierhuber, Dieter", "(.+), (.+)", "$2 $1") Dieter Maierhuber