Überladen von Regex-Literalen

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

Mit Overloading kann man die Elemente eines Regex-Literals in jeder denkbaren Weise vorverarbeiten. In den nächsten Abschnitten werden dazu Beispiele vorgestellt.

Metazeichen für Wortanfang und Wortende einbauen

In Perl gibt es keine speziellen Metazeichen wie ˹\<˼ und ˹\>˼ für den Wortanfang und das Wortende, denn in den meisten Fällen kommt man auch mit ˹\b˼ zurecht. Wenn man sie trotzdem benötigt, kann man die Metazeichen ›\<‹ und ›\>‹ überladen und dafür die Konstrukte ˹(?<!\w)(?=\w)˼ und ˹(?<=\w)(?!\w)˼ einsetzen.

Zuerst schreiben wir eine Subroutine RegexLiteralVerarbeiten, die diese Ersetzung vornimmt:

sub RegexLiteralVerarbeiten($)
{
   my ($RegexLiteral) = @_; # Argument ist ein String.
   $RegexLiteral =~ s/\\</(?<!\\w)(?=\\w)/g; # \< für Wortanfang imitieren.
   $RegexLiteral =~ s/\\>/(?<=\\w)(?!\\w)/g; # \> für Wortende imitieren.
   return $RegexLiteral; # Möglicherweise veränderten String zurückgeben.
}

Wenn man dieser Funktion einen String wie ›...\<...‹ übergibt, wird er in ›...(?<!\w)(?=\w)...‹ verwandelt. Zur Erinnerung: Der Ersatztext der Substitution wird wie ein String in Anführungszeichen behandelt, daher muss man ›\\w‹ schreiben, um den Wert ›\w‹ zu erhalten.

Damit diese Ersetzung bei jedem Regex-Literal vorgenommen wird, schreiben wir die Routine in eine Datei namens MyRegexStuff.pm und benutzen den Overload-Mechanismus:

package MyRegexStuff; # Eindeutiger Name.
use strict;           # Gute Gewohnheit.
use warnings;         # Gute Gewohnheit.
use overload;         # Wir benutzen den Overload-Mechanismus.
# Unsere Regex-Verarbeitung wird installiert, wenn das Modul mit ›use‹ geladen wird.
sub import { overload::constant qr => \&RegexLiteralVerarbeiten }

sub RegexLiteralVerarbeiten($)
{
   my ($RegexLiteral) = @_;  # Argument ist ein String.
   $RegexLiteral =~ s/\\</(?<!\\w)(?=\\w)/g;  # \< für Wortanfang imitieren.
   $RegexLiteral =~ s/\\>/(?<=\\w)(?!\\w)/g;  # \> für Wortende imitieren.
   return $RegexLiteral;     # Möglicherweise veränderten String zurückgeben.
}

1; # Standardformulierung: Ein Package muss ›wahr‹ zurückgeben.

Wenn MyRegexStuff.pm im Bibliothekssuchpfad (PERLLIB, siehe die Perl-Dokumentation) installiert wird, können wir das Modul in unseren Perl-Skripten verwenden. Zu Testzwecken lassen wir es im aktuellen Verzeichnis wie das folgende Testskript:

use lib '.';          # Sicherstellen, dass Module im aktuellen Verzeichnis gesucht werden.
use MyRegexStuff;     # Jetzt können wir Wortanfang und -ende benutzen!
  ...
$text =~ s/\s+\</ /g; # Whitespace vor einem Wort in ein einzelnes Leerzeichen verwandeln.
  ...

Wenn wir die neuen Metazeichen in Regex-Literalen benutzen wollen, müssen wir das Modul mit use MyRegexStuff einbinden. (Die neuen Metazeichen sind in MyRegexStuff.pm selbst natürlich nicht verfügbar, weil man im Modul MyRegexStuff besser nicht use MyRegexStuff verwendet.)

Possessive Quantoren in Perl nachbilden

Wir wollen MyRegexStuff.pm erweitern und darin die possessiven Quantoren wie ˹x++˼ (siehe Possessive Quantoren) unterstützen. Possessive Quantoren verhalten sich zunächst wie normale, gierige Quantoren, geben aber Zeichen, die sie einmal »gefressen« haben, nie wieder zurück. Mit atomaren Gruppen kann man das Verhalten von possessiven Quantoren nachbilden; man entfernt dazu das letzte ›+‹ und packt das Ganze in eine atomare Klammer, d.h., man transformiert ˹Regex*+˼ in ˹(?>Regex*)˼ (siehe Possessive Quantoren: ?+, *+, ++ und {m,n}+).

Der Regex-Teil kann ein geklammerter Unterausdruck sein, eine Metasequenz wie ˹\w˼ oder ˹\x{1234}˼ oder auch ein einfaches Zeichen. Es ist nicht so einfach, alle möglichen Fälle richtig zu behandeln, wir konzentrieren uns der Einfachheit halber im Moment auf ?+, *+˼ oder ++˼, die einen geklammerten Unterausdruck quantifizieren. Mit der Regex $Tiefe_N aus Verschachtelte Paare mit dynamischen regulären Ausdrücken erkennen geht das auch mit Unterausdrücken, die verschachtelte Klammern enthalten. Wir fügen unserer RegexLiteralVerarbeiten-Funktion diese Zeile hinzu:

$RegexLiteral =~ s/(  \( $Tiefe_N \)[*+?]  )\+/(?>$1)/gx;

Das ist schon alles. Mit unserem neuen MyRegexStuff-Modul können wir in Regex-Literalen possessive Quantoren benutzen, wie etwa in diesem Beispiel von Geschützte Anführungszeichen in Strings in Anführungszeichen zulassen:

$text =~ s/"(\\.|[^"])*+"//; # Strings in Anführungszeichen entfernen.

Es ist gar nicht so einfach, auch alle anderen Gebilde außer geklammerten Unterausdrücken zuzulassen. Es gibt eine ganze Reihe von möglichen Dingen in regulären Ausdrücken, auf die ein Quantor folgen kann. Hier ein möglicher Ansatz:

$RegexLiteral =~ s{
 (
   # Finde etwas, das quantifiziert sein kann ...
   (?:  \\[\\abCdDefnrsStwWX] # \n, \w usw.
     |  \\c.                  # \cA
     |  \\x[\da-fA-F]{1,2}    # \xFF
     |  \\x\{[\da-fA-F]*\}    # \x{1234}
     |  \\[pP]\{[^{}]+\}      # \p{Letter}
     |  \[\]?[^]]+\]          # Zeichenklasse
     |  \\\W                  # \*
     |  \( $Tiefe_N \)        # (...), beliebig verschachtelt
     |  [^()*+?\\]            # Fast alles andere
    )
    # ... und auf das tatsächlich ein Quantor folgt ...
    (?: [*+?] | \{\d+(?:,\d*)?\} )
 )
 \+  # ... mit einem zusätzlichen possessiven ›+‹ nach dem Quantor.
}{(?>$1)}gx;

Die Regex ist so aufgebaut wie die einfachere vorhin: ein Gebilde finden, auf das ein possessiver Quantor folgt, das Pluszeichen entfernen und in ˹(?>...)˼ einpacken. Auch hier werden nicht alle der komplexen Möglichkeiten der Syntax von Perl behandelt. Insbesondere die Alternative mit der Zeichenklasse lässt zu wünschen übrig, weil geschützte Zeichen in der Zeichenklasse nicht korrekt behandelt werden. Schlimmer noch: Eigentlich ist der ganze Ansatz falsch, weil unsere Substitutionen nicht alle verzwickten Möglichkeiten von regulären Ausdrücken abdecken. Bei einem Ausdruck wie ›\(blah\)++‹ etwa werden die Backslashes ignoriert, der Code nimmt an, dass sich der Quantor ˹++˼ auf mehr als nur die ˹\)˼ bezieht.

Diese Probleme kann man nur mit großem Aufwand korrekt behandeln. Man könnte dazu die ganze Regex vom Anfang bis zum Ende durchgehen (ähnlich wie in dem HTML-Beispiel im Kasten Ein Beispiel für den Gebrauch von \G in Perl). Ich wäre damit zufrieden, nur den Zeichenklassen-Teil zu reparieren, alles Weitere ist wohl kaum der Mühe wert. Die Fälle außer den Zeichenklassen, in denen die gezeigte Lösung nicht funktioniert, sind bereits sehr spitzfindig; wenn also Zeichenklassen korrekt behandelt würden, taugte die Lösung in der Praxis. Vor allem aber hat der Overload-Mechanismus von Perl in der aktuellen Version einen gravierenden Fehler, der das ganze Unterfangen ziemlich unnütz macht.

  

<< 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