Soweit ich weiß, sind Ausgleichsgruppen für die Regex-Variante von .NET einzigartig.
Nebenbei: Wiederholte Gruppen
Zunächst müssen Sie wissen, dass .NET (soweit ich weiß) die einzige Regex-Variante ist, mit der Sie auf mehrere Erfassungen einer einzelnen Erfassungsgruppe zugreifen können (nicht in Rückreferenzen, sondern nach Abschluss des Spiels).
Um dies anhand eines Beispiels zu veranschaulichen, betrachten Sie das Muster
(.)+
und die Zeichenfolge "abcd"
.
Bei allen anderen Regex-Varianten führt die Erfassungsgruppe 1
lediglich zu einem Ergebnis: d
(Beachten Sie, dass die vollständige Übereinstimmung natürlich abcd
wie erwartet erfolgt.) Dies liegt daran, dass bei jeder neuen Verwendung der Erfassungsgruppe die vorherige Erfassung überschrieben wird.
.NET hingegen erinnert sich an alle. Und das in einem Stapel. Nach dem Abgleichen der obigen Regex wie
Match m = new Regex(@"(.)+").Match("abcd");
Sie werden das finden
m.Groups[1].Captures
Ist ein, CaptureCollection
dessen Elemente den vier Erfassungen entsprechen
0: "a"
1: "b"
2: "c"
3: "d"
Dabei ist die Nummer der Index in der CaptureCollection
. Grundsätzlich wird jedes Mal, wenn die Gruppe erneut verwendet wird, eine neue Erfassung auf den Stapel verschoben.
Interessanter wird es, wenn wir benannte Erfassungsgruppen verwenden. Da .NET die wiederholte Verwendung des gleichen Namens ermöglicht, können wir einen regulären Ausdruck wie schreiben
(?<word>\w+)\W+(?<word>\w+)
um zwei Wörter in derselben Gruppe zu erfassen. Jedes Mal, wenn eine Gruppe mit einem bestimmten Namen angetroffen wird, wird eine Erfassung auf ihren Stapel verschoben. Wenden Sie diesen regulären Ausdruck also auf die Eingabe an "foo bar"
und überprüfen Sie ihn
m.Groups["word"].Captures
Wir finden zwei Aufnahmen
0: "foo"
1: "bar"
Auf diese Weise können wir sogar Dinge aus verschiedenen Teilen des Ausdrucks auf einen einzelnen Stapel verschieben. Dies ist jedoch nur die Funktion von .NET, mit der mehrere in dieser Liste aufgeführte Aufnahmen verfolgt werden können CaptureCollection
. Aber ich sagte, diese Sammlung ist ein Stapel . So können wir knallen Dinge von ihm?
Geben Sie ein: Gruppen ausgleichen
Es stellt sich heraus, dass wir es können. Wenn wir eine Gruppe wie verwenden (?<-word>...)
, wird die letzte Erfassung vom Stapel entfernt, word
wenn der Unterausdruck ...
übereinstimmt. Wenn wir also unseren vorherigen Ausdruck in ändern
(?<word>\w+)\W+(?<-word>\w+)
Dann wird die zweite Gruppe die Aufnahme der ersten Gruppe platzen lassen und wir werden CaptureCollection
am Ende eine leere erhalten . Natürlich ist dieses Beispiel ziemlich nutzlos.
Die Minus-Syntax enthält jedoch noch ein weiteres Detail: Wenn der Stapel bereits leer ist, schlägt die Gruppe fehl (unabhängig von ihrem Untermuster). Wir können dieses Verhalten nutzen, um Verschachtelungsebenen zu zählen - und hier kommt die Namensausgleichsgruppe her (und dort wird es interessant). Angenommen, wir möchten Zeichenfolgen abgleichen, die korrekt in Klammern stehen. Wir schieben jede öffnende Klammer auf den Stapel und fügen für jede schließende Klammer eine Aufnahme hinzu. Wenn eine schließende Klammer zu viele enthält, wird versucht, einen leeren Stapel zu platzieren, und das Muster schlägt fehl:
^(?:[^()]|(?<Open>[(])|(?<-Open>[)]))*$
Wir haben also drei Alternativen in einer Wiederholung. Die erste Alternative verbraucht alles, was keine Klammer ist. Die zweite Alternative entspricht (
s, während sie auf den Stapel geschoben werden. Die dritte Alternative stimmt mit )
s überein, während Elemente vom Stapel entfernt werden (falls möglich!).
Hinweis: Zur Verdeutlichung überprüfen wir nur, dass keine nicht übereinstimmenden Klammern vorhanden sind! Dies bedeutet, dass Zeichenfolgen, die überhaupt keine Klammern enthalten , übereinstimmen, da sie syntaktisch noch gültig sind (in einer Syntax, in der Ihre Klammern übereinstimmen müssen). Wenn Sie mindestens einen Satz Klammern sicherstellen möchten, fügen Sie einfach (?=.*[(])
direkt nach dem einen Lookahead hinzu ^
.
Dieses Muster ist jedoch nicht perfekt (oder völlig korrekt).
Finale: Bedingte Muster
Es gibt noch einen Haken: Dies stellt nicht sicher, dass der Stapel am Ende der Zeichenfolge leer ist (daher (foo(bar)
gültig). .NET (und viele andere Varianten) haben ein weiteres Konstrukt, das uns hier hilft: bedingte Muster. Die allgemeine Syntax lautet
(?(condition)truePattern|falsePattern)
wo das falsePattern
optional ist - wenn es weggelassen wird, stimmt der falsche Fall immer überein. Die Bedingung kann entweder ein Muster oder der Name einer Erfassungsgruppe sein. Ich werde mich hier auf den letzteren Fall konzentrieren. Wenn es sich um den Namen einer Erfassungsgruppe handelt, truePattern
wird dieser nur dann verwendet, wenn der Erfassungsstapel für diese bestimmte Gruppe nicht leer ist. Das heißt, ein bedingtes Muster wie (?(name)yes|no)
"Wenn name
etwas übereinstimmt und erfasst wurde (das sich noch auf dem Stapel befindet), verwenden Sie ein Muster, yes
andernfalls verwenden Sie ein Muster no
".
Am Ende unseres obigen Musters könnten wir also so etwas hinzufügen, (?(Open)failPattern)
was dazu führt, dass das gesamte Muster fehlschlägt, wenn der Open
-stack nicht leer ist. Das Einfachste, um das Muster bedingungslos zum Scheitern zu bringen, ist (?!)
(ein leerer negativer Lookahead). Wir haben also unser letztes Muster:
^(?:[^()]|(?<Open>[(])|(?<-Open>[)]))*(?(Open)(?!))$
Beachten Sie, dass diese bedingte Syntax per se nichts mit dem Ausgleich von Gruppen zu tun hat, sondern dass die volle Leistung genutzt werden muss.
Von hier aus ist der Himmel die Grenze. In Kombination mit anderen .NET-Regex-Funktionen wie Lookbehinds mit variabler Länge ( die ich selbst auf die harte Tour lernen musste) sind viele sehr anspruchsvolle Anwendungen möglich, und es gibt einige Fallstricke . Die Hauptfrage ist jedoch immer: Ist Ihr Code bei Verwendung dieser Funktionen noch wartbar? Sie müssen es wirklich gut dokumentieren und sicherstellen, dass jeder, der daran arbeitet, diese Funktionen auch kennt. Andernfalls ist es möglicherweise besser, wenn Sie die Zeichenfolge nur manuell Zeichen für Zeichen durchlaufen und die Verschachtelungsebenen in einer Ganzzahl zählen.
Nachtrag: Was ist mit der (?<A-B>...)
Syntax?
Credits für diesen Teil gehen an Kobi (siehe seine Antwort unten für weitere Details).
Mit all dem können wir nun überprüfen, ob eine Zeichenfolge korrekt in Klammern steht. Aber es wäre viel nützlicher, wenn wir tatsächlich (verschachtelte) Captures für alle Inhalte dieser Klammern erhalten könnten. Natürlich können wir uns daran erinnern, Klammern in einem separaten Erfassungsstapel geöffnet und geschlossen zu haben, der nicht geleert wird, und dann in einem separaten Schritt eine Teilzeichenfolgenextraktion basierend auf ihren Positionen durchführen.
Aber .NET bietet hier noch eine weitere Komfortfunktion: Wenn wir verwenden (?<A-B>subPattern)
, wird nicht nur ein Capture vom Stapel genommen B
, sondern auch alles zwischen diesem Popup von B
und dieser aktuellen Gruppe wird auf den Stapel verschoben A
. Wenn wir also eine solche Gruppe für die schließenden Klammern verwenden, während wir Verschachtelungsebenen von unserem Stapel entfernen, können wir den Inhalt des Paares auch auf einen anderen Stapel verschieben:
^(?:[^()]|(?<Open>[(])|(?<Content-Open>[)]))*(?(Open)(?!))$
Kobi hat diese Live-Demo in seiner Antwort bereitgestellt
Wenn wir all diese Dinge zusammen nehmen, können wir:
- Erinnere dich an beliebig viele Aufnahmen
- Überprüfen Sie verschachtelte Strukturen
- Erfassen Sie jede Verschachtelungsebene
Alles in einem einzigen regulären Ausdruck. Wenn das nicht aufregend ist ...;)
Einige Ressourcen, die ich hilfreich fand, als ich zum ersten Mal davon erfuhr: