Regex-Objekte verwenden

(Auszug aus "Reguläre Ausdrücke" von Jeffrey E. F. Friedl)

Es ist kaum sinnvoll, ein Regex-Objekt nur zu erzeugen, es soll auch angewendet werden.

RegexObj.IsMatch(Suchtext)                  Typ des Rückgabewerts: Boolean
RegexObj.IsMatch(Suchtext, Offset)

Mit der IsMatch-Methode wird die Regex des Objekts auf den Suchtext angewandt und gibt einen einfachen Wert vom Typ Boolean zurück, der einen Erfolg oder Fehlschlag anzeigt. Ein Beispiel:

Dim R as RegexObj = New Regex("^\s*$")
   ...
If R.IsMatch(Zeile) Then
   ' Leerzeile ...
      ...
Endif

Wenn ein Offset (eine ganze Zahl) angegeben wird, wird erst ab dieser Position im Suchtext gesucht.

RegexObj.Match(Suchtext)                        Typ des Rückgabewerts: Match-Objekt
RegexObj.Match(Suchtext, Offset)
RegexObj.Match(Suchtext, Offset, Maxlänge)

Die Match-Methode wendet die Regex aus dem Objekt auf den Suchtext an und erzeugt ein Match-Objekt, das alle Resultate enthält. Diese Resultate (ob die Suche erfolgreich war, eingefangenen Text usw.) kann man abfragen. Man kann mit diesem Objekt auch die Mustersuche nach dem nächsten Treffer mit der gleichen Regex im gleichen String auslösen. Die Einzelheiten zum Match-Objekt folgen unter Match-Objekte verwenden.

Wenn ein Offset (eine ganze Zahl) angegeben wird, wird erst ab dieser Position im Suchtext gesucht.

Wenn das Argument Maxlänge angegeben wird, wird nicht eigentlich im Suchtext gesucht, sondern viel eher in einem Substring der Länge Maxlänge davon, der bei Offset beginnt. Die Zeichen davor und danach werden für die Mustersuche ignoriert. Das bedeutet, dass ˹^˼ an der Position Offset des ursprünglichen Suchtexts passt und ˹$˼ nach weiteren Maxlänge Zeichen. Auch ein Lookaround-Konstrukt »sieht« die Zeichen davor und danach nicht. Dies ist ein großer Unterschied zum Verhalten, wenn nur Offset angegeben wird – das beeinflusst nur den Ort, an dem das Getriebe die Regex-Maschine ansetzt, aber die Maschine »sieht« dennoch den ganzen Suchtext.

Diese kleine Tabelle soll die Bedeutung von Offset und Maxlänge illustrieren:

Resultate mit der Regex ...
Methodenaufruf ˹\d\d˼ ˹^\d\d˼ ˹^\d\d$˼
RegexObj.Match("May 16, 1998") Treffer ›16 Kein Treffer Kein Treffer
RegexObj.Match("May 16, 1998", 9) Treffer ›99 Kein Treffer Kein Treffer
RegexObj.Match("May 16, 1998", 9, 2) Treffer ›99 Treffer ›99 Treffer ›99
RegexObj.Matches(Suchtext)               Typ des Rückgabewerts: MatchCollection
RegexObj.Matches(Suchtext, Offset)

Die Matches-Methode ähnelt der Match-Methode sehr, hier wird allerdings eine Collection von Match-Objekten zurückgegeben, die allen Treffern der Regex im Suchstring entspricht, während Match ein Objekt zurückgibt, das nur dem ersten oder einem einzigen Treffer entspricht. Das zurückgegebene Objekt ist vom Typ MatchCollection.

Wenn wir das folgende Regex-Objekt und den Suchtext aufgesetzt haben:

Dim R as New Regex("\w+")
Dim Suchtext as String = "ein paar Worte"

bekommen wir mit diesem Programmstück

Dim AlleTreffer as MatchCollection = R.Matches(Suchtext)
Dim I as Integer
For I = 0 to AlleTreffer.Count - 1
    Dim MatchObj as Match = AlleTreffer.Item(I)
    Console.WriteLine("Treffer: " & MatchObj.Value)
Next

die folgende Ausgabe:

Treffer: ein
Treffer: paar
Treffer: Worte

Das folgende Beispiel macht genau das Gleiche und zeigt, dass man auf das MatchCollection-Objekt auch verzichten kann:

Dim MatchObj as Match
For Each MatchObj in R.Matches(Suchtext)
    Console.WriteLine("Treffer: " & MatchObj.Value)
Next

Zum Vergleich die gleiche Aufgabe mit einem einzigen Match-Objekt und der NextMatch-Methode (statt mit Matches):

Dim MatchObj as Match = R.Match(Suchtext)
While MatchObj.Success
    Console.WriteLine("Treffer: " & MatchObj.Value)
    MatchObj = MatchObj.NextMatch()
End While
RegexObj.Replace(Suchtext, Ersatztext)                  Typ des Rückgabewerts: String
RegexObj.Replace(Suchtext, Ersatztext, Anzahl)
RegexObj.Replace(Suchtext, Ersatztext, Anzahl, Offset)

Zum »Suchen und Ersetzen« verwendet man in .NET die Replace-Methode. Sie wendet den regulären Ausdruck des Regex-Objekts auf den Suchstring an und gibt statt einem Match-Objekt eine möglicherweise veränderte Kopie dieses Strings zurück. Was ersetzt wird, hängt vom Ersatztext-Argument ab.

Dieses Ersatztext-Argument ist überladen; es kann ein String oder ein MatchEvaluator-Delegate sein. Wenn es ein String ist, wird dieser nach den Regeln aus dem folgenden Kasten interpretiert. Das folgende Programmbeispiel packt Wörter mit großen Anfangsbuchstaben in <B>...</B> ein:

Dim R_GrossAnf as New Regex("\b[A-Z]\w*")
   ...
Text = R_GrossAnf.Replace(Text, "<B>$0</B>")

Spezielle »Dollar-Sequenzen« im Ersatztext

Die Methoden Regex.Replace und Match.Result haben ein »Ersatztext«-Argument, das auf besondere Weise interpretiert wird. Die darin vorkommenden, von Perl inspirierten »Dollar-Sequenzen« werden durch Textstücke ersetzt, die bei der Mustersuche gefunden wurden:

Sequenz Wird ersetzt durch ...
$& den Treffertext (auch $0)
$1, $2, .. den Text, auf den die entsprechende einfangende Klammer gepasst hat
${Name} den Text, auf den die entsprechend benannte Klammer gepasst hat
$` den Text aus dem Suchstring vor dem aktuellen Treffer
$' den Text aus dem Suchstring nach dem aktuellen Treffer
$$ ein einzelnes Dollarzeichen
$ den ganzen ursprünglichen Suchstring
$+ (siehe Text)

Die Sequenz $+ ist in der vorliegenden Implementation ziemlich unnütz. Sie sollte wohl die Variable $+ von Perl nachahmen, die dort sehr wohl nützlich ist und den Text aus der letzten (höchstnummerierten) Klammer zurückgibt, die Teil des Treffers ist. (Unter Einen HTML-Link erkennen finden Sie ein Programmbeispiel dazu.) In der .NET-Variante dagegen wird einfach der Text aus der Klammer mit der höchsten Nummer zurückgegeben, gleichgültig, ob diese Klammer Anteil am gefundenen Treffer hat oder nicht. Besonders unnütz ist diese Dollar-Sequenz, wenn zu den normalen noch benannte einfangende Klammern in der Regex dazukommen, die anders nummeriert werden (siehe Benannte Klammerausdrücke).

Alle weiteren Dollar-Kombinationen haben im Ersatztext keine besondere Bedeutung und bleiben unverändert erhalten.

Wenn eine Anzahl angegeben wird, werden maximal so viele Ersetzungen vorgenommen (normalerweise werden alle Treffer ersetzt). Wenn nur der erste Treffer ersetzt werden soll, gibt man für die Anzahl 1 an. Wenn bekannt ist, dass nur ein Treffer möglich ist, kann es effizienter sein, diese Anzahl explizit anzugeben; die Regex-Maschine muss dann nicht vergeblich nach weiteren Treffern suchen. Eine Anzahl von -1 bedeutet das Gleiche, wie wenn das Anzahl-Argument weggelassen wird: Es werden alle Treffer ersetzt.

Wenn ein Offset (eine ganze Zahl) angegeben wird, wird die Regex erst nach so vielen Zeichen nach dem Anfang des Suchtextes angesetzt; die dabei übersprungenen Zeichen werden unverändert in den Resultat-String kopiert.

Das folgende Beispiel ersetzt alle Whitespace-Sequenzen durch genau ein Leerzeichen:

Dim WS_Seq as New Regex("\s+")
   ...
Suchtext = WS_Seq.Replace(Suchtext, " ")

Das verändert den String ›viel leerer Raum‹ zu ›viel leerer Raum‹. Das folgende Beispiel macht das Gleiche, lässt aber den Whitespace am Anfang des Suchtexts unberührt:

Dim WS_Seq  as New Regex("\s+")
Dim WS_vorn as New Regex("^\s+")
   ...
Suchtext = WS_Seq.Replace(Suchtext, " ", -1, WS_vorn.Match(Suchtext).Length)

Damit wird ›vielleererRaum‹ zu ›vielleererRaum‹.

Das Programmbeispiel benutzt die Länge des Treffers der WS_vorn-Regex als Offset für die Replace-Methode. Es werden also vor dem »Suchen und Ersetzen« so viele Zeichen übersprungen, wie Whitespace am Anfang des Suchtextes gefunden wurde. Dabei wird eine praktische Eigenschaft des Match-Objekts ausgenutzt: Wenn kein Treffer (mit WS_vorn.Match(Suchtext)) gefunden wird, bekommt die Length-Eigenschaft dennoch einen definierten Wert, nämlich null. Dieser Wert ist auch bei einem Fehlschlag genau das, was wir für den Offset benötigen, damit WS_Seq auf den kompletten Suchstring angewandt wird.

Gebrauch von Ersatztext-Delegates

Das Ersatztext-Argument braucht kein String zu sein, es kann auch ein Delegate sein – das ist im Wesentlichen ein Zeiger auf eine Funktion. Die Delegate-Funktion wird nach dem Matching aufgerufen und erzeugt den Ersatztext. Da diese Funktion irgendwelche Dinge tun kann, ist dies ein sehr mächtiger Mechanismus.

Das Delegate ist vom Typ MatchEvaluator und wird einmal pro Treffer aufgerufen. Die Funktion, auf die das Delegate zeigt, soll ein Match-Objekt als Parameter haben und muss einen String zurückgeben, der als Ersatztext gebraucht wird.

Zum Vergleich: Die folgenden zwei Programmstücke ergeben das gleiche Resultat:

Suchtext = R.Replace(Suchtext, "<<$&>>"))

Function MatchFunc(ByVal M as Match) as String
    return M.Result("<<$&>>")
End Function
Dim Evaluator as MatchEvaluator = New MatchEvaluator(AddressOf MatchFunc)
   ...
Suchtext = R.Replace(Suchtext, Evaluator)

Beide Programmstücke packen den Treffer in <<...>> ein. Mit der Delegate-Funktion besteht die Möglichkeit, beliebig komplexen Code zur Berechnung des Ersatztextes zu benutzen. Im nächsten Beispiel rechnet die Funktion Celsius-Temperaturen in Fahrenheit um:

Function MatchFunc(ByVal M as Match) as String
    'Temperatur als Zahl aus $1 herausholen, dann in Fahrenheit umrechnen.
    Dim Celsius    as Double = Double.Parse(M.Groups(1).Value)
    Dim Fahrenheit as Double = Celsius * 9/5 + 32
    Return Fahrenheit & "F" 'Ein »F« anhängen und zurückgeben.
End Function
Dim Evaluator as MatchEvaluator = New MatchEvaluator(AddressOf MatchFunc)
   ...
Dim R_Temp as Regex = New Regex("(\d+)C\b", RegexOptions.IgnoreCase)
Suchtext = R_Temp.Replace(Suchtext, Evaluator)

Wenn der Suchtext ›Temperatur ist 37C.‹ lautet, wird er durch ›Temperatur ist 98.6F.‹ ersetzt.

RegexObj.Split(Suchtext)
RegexObj.Split(Suchtext, Anzahl)
RegexObj.Split(Suchtext, Anzahl, Offset)
Typ des Rückgabewerts: Array von String

Bei der Split-Methode wird die Regex aus dem Objekt auf den Suchtext angewandt, und es wird ein Array von Textstücken zurückgegeben, die zwischen den gefundenen Treffern im Suchtext liegen. Hier sehen Sie ein ganz einfaches Beispiel:

Dim R as New Regex("\.")
Dim Teile as String() = R.Split("209.204.146.22")

Der Methodenaufruf R.Split gibt ein Array mit vier Strings zurück (›209‹, ›204‹, ›146‹ und ›22 ‹), die im Suchtext durch die Treffer (die drei Punkte) getrennt waren.

Wenn eine Anzahl angegeben wird, werden maximal so viele Bruchstücke zurückgegeben (es sei denn, die Regex enthält einfangende Klammern – mehr dazu in Kürze). Ohne Anzahl werden so viele Teile wie möglich zurückgegeben. Wenn eine Anzahl angegeben wird, hält die Regex-Maschine möglicherweise an, bevor alle Treffer gefunden wurden; wenn das geschieht, erhält das letzte zurückgegebene Element den ganzen Rest des Suchtextes:

Dim R as New Regex("\.")
Dim Teile as String() = R.Split("209.204.146.22", 2)

Diesmal landen in Teile nur zwei Strings: ›209‹ und ›204.146.22‹.

Wenn ein Offset (eine ganze Zahl) angegeben wird, wird die Regex erst nach so vielen Zeichen nach dem Anfang des Suchtextes angesetzt; die dabei übersprungenen Zeichen werden in das erste zurückgegebene Element übernommen (außer wenn RegexOptions.RightToLeft verwendet wird, dann werden die übersprungenen Zeichen Teil des letzten Elements).

Gebrauch von Split mit einfangenden Klammern

Wenn einfangende Klammern jeder Art verwendet werden, werden normalerweise zusätzliche Elemente in das Rückgabe-Array eingesetzt. (Die außergewöhnlichen Fälle besprechen wir in Kürze.) Wenn beispielsweise Strings wie ›2006-12-31‹ oder ›04/12/2007‹ in Komponenten zerlegt werden sollen, könnte man mit ˹[-/]˼ splitten:

Dim R as New Regex("[-/]")
Dim Teile as String() = R.Split(IrgendeinDatum)

Wir bekommen eine Liste mit drei Zahlen in Stringdarstellung. Wenn wir einfangende Klammern benutzen und ˹([-/,])˼ als Regex verwenden, dann bekommen wir fünf Strings.

Wenn IrgendeinDatum den String ›2006-12-31‹ enthält, sind dies die Elemente ›2006‹, ›-‹, ›12‹, ›-‹ und ›31‹. Die zusätzlichen ›-‹ rühren von den einfangenden Klammern her, sie entsprechen dem $1 jedes Treffers.

Wenn die Regex mehrere einfangende Klammerpaare enthält, wird für jedes Klammerpaar in der üblichen numerischen Reihenfolge ein zusätzliches Element erzeugt (d.h., die benannten Klammern kommen nach den normalen einfangenden Klammern, siehe Benannte Klammerausdrücke).

Split funktioniert gut und zuverlässig mit einfangenden Klammern, solange alle Klammern am Treffertext teilhaben. Wenn dem nicht so ist, zeigt sich in der geprüften Version der Regex-Routinen in .NET ein Fehler. Wenn ein geklammerter Ausdruck nicht Teil des Treffers ist, wird für ihn und für alle Klammern mit höherer Nummer kein zusätzliches Element zurückgegeben.

Nehmen wir an – ein etwas gesuchtes Beispiel –, Sie möchten bei einem Komma und optionalem Whitespace davor und danach aufteilen. Sie möchten den eventuell gefundenen Whitespace aber auch als Element im zurückgegebenen Array haben. Die Regex ˹(\s+)?, (\s+)?˼ erledigt dies. Wenn die Regex mit Split auf den String ›dies● das‹ angewendet wird, werden vier Strings zurückgegeben, ›dies‹, ›‹, ›‹ und ›das‹. Beim Suchtext ›dies, das‹ aber ist die erste Klammer nicht Teil des Treffers, und wegen dieses Fehlers wird auch für das von der zweiten Klammer erkannte Leerzeichen kein Element zurückgegeben. Es werden nur zwei Strings zurückgegeben, ›dies‹ und ›das‹. Da man aber nicht voraussehen kann, welche Klammern zum Treffer gehören werden und welche nicht, kann man dieses Feature in der gegenwärtigen Form kaum sinnvoll verwenden.

In diesem besonderen Beispiel könnte man das Problem mit ˹(\s*),(\s*)˼ vermeiden – dabei nehmen beide Klammern am Treffer teil, auch wenn sie vielleicht leere Strings einfangen. Kompliziertere reguläre Ausdrücke kann man aber nicht immer so einfach umschreiben.

RegexObj.GetGroupNames()
RegexObj.GetGroupNumbers()
RegexObj.GroupNameFromNumber(Zahl)
RegexObj.GroupNumberFromName(Name)

Diese Methoden liefern Informationen über die Nummern und (wenn benannte Klammerausdrücke verwendet werden) die Namen von einfangenden Klammern in der Regex. Sie beziehen sich nicht auf eine bestimmte Mustersuche, sondern nur auf die Nummerierung der Klammern und die Namen in der Regex. Im folgenden Kasten werden sie in einem Beispielprogramm verwendet.


Informationen über ein Regex-Objekt abfragen

Das folgende Programm gibt alle bekannten Informationen über das Regex-Objekt in R aus:

'Informationen über das Regex-Objekt in R ausgeben.
Console.WriteLine("Regex: " & R.ToString())
Console.WriteLine("Optionen: " & R.Options)

If R.RightToLeft
   Console.WriteLine("Von rechts nach links.")
Else
   Console.WriteLine("Von links nach rechts.")
End If
Dim S as String
For Each S in R.GetGroupNames()
    Console.WriteLine("Name """ & S & """ ist Nr. " & R.GroupNumberFromName(S))
Next
Console.WriteLine("---")
Dim I as Integer
For Each I in R.GetGroupNumbers()
    Console.WriteLine("Nr. " & I & " hat den Namen """ & _
                      R.GroupNameFromNumber(I) & """")
Next
Das Programm wird mit zwei verschiedenen Regex-Objekten ausgeführt:
New Regex("^(\w+)://([^/]+)(/\S*)")
New Regex("^(?<proto>\w+)://(?<host>[^/]+)(?<page>/\S*)",
          RegexOptions.Compiled)

Das Programm wird mit zwei verschiedenen Regex-Objekten ausgeführt:

New Regex("^(\w+)://([^/]+)(/\S*)")

New Regex("^(?<proto>\w+)://(?<host>[^/]+)(?<page>/\S*)", RegexOptions.Compiled)</page></host></proto>

Es gibt dann Folgendes aus (die Regex rechts ist aus Platzgründen abgeschnitten):

Regex: ^(\w+)://([^/]+)(/\S*)
Optionen: 0
Von links nach rechts.
Name "0" ist Nr. 0
Name "1" ist Nr. 1
Name "2" ist Nr. 2
Name "3" ist Nr. 3
---
Nr. 0 hat den Namen "0"
Nr. 1 hat den Namen "1"
Nr. 2 hat den Namen "2"
Nr. 3 hat den Namen "3"
Regex: ^(?<proto>\w+)://(?<host>[^/]+)...
Optionen: 8
Von rechts nach links.
Name "0" ist Nr. 0
Name "proto" ist Nr. 1
Name "host" ist Nr. 2
Name "page" ist Nr. 3
---
Nr. 0 hat den Namen "0"
Nr. 1 hat den Namen "proto"
Nr. 2 hat den Namen "host"
Nr. 3 hat den Namen "page"

RegexObj.ToString()
RegexObj.RightToLeft
RegexObj.Options

Mit diesen Methoden und Eigenschaften erhält man Informationen über das Regex-Objekt (im Gegensatz zum Regex-String). Mit der ToString()-Methode wird aus dem Objekt wieder die Stringdarstellung der Regex erzeugt. Die Eigenschaft RightToLeft gibt an, ob beim Aufbau des Regex-Objekts die Option RegexOptions.RightToLeft verwendet wurde. Die Eigenschaft Options gibt alle am Aufbau der Regex beteiligten Optionen zurück, wobei die einzelnen Optionen mit OR zu einem einzigen numerischen Wert codiert werden:

0 None 16 Singleline
1 IgnoreCase 32 IgnorePatternWhitespace
2 Multiline 64 RightToLeft
4 ExplicitCapture
8 Compiled 256 ECMAScript

Der fehlende Wert 128 dient Microsoft-intern als Debugging-Option und ist im ausgelieferten Produkt nicht mehr enthalten.

Im obigen Kasten (Informationen über ein Regex-Objekt abfragen) werden diese Methoden an einem Beispiel illustriert.

  

<< zurück vor >>

 

 

 

Tipp der data2type-Redaktion:
Zum Thema Reguläre Ausdrücke bieten wir auch folgende Schulungen zur Vertiefung und professionellen Fortbildung an:
   

Copyright der deutschen Ausgabe © 2008 by O’Reilly Verlag GmbH & Co. KG
Für Ihren privaten Gebrauch dürfen Sie die Online-Version ausdrucken.
Ansonsten unterliegt dieses Kapitel aus dem Buch "Reguläre Ausdrücke" denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

O’Reilly Verlag GmbH & Co. KG, Balthasarstr. 81, 50670 Köln