Verschachtelte Regex Lookahead und Lookbehind

8

Ich habe Probleme mit dem verschachtelten '+' / '-' Lookahead / Lookbehind in Regex.

Nehmen wir an, ich möchte '*' in einer Zeichenkette mit '%' ändern und sagen wir, dass '\' das nächste Zeichen verlässt. (Eine Regex zu SQL wie Befehl ^^).

Also die Zeichenfolge

  • '*test*' sollte in '%test%' ,
  • geändert werden
  • '\*test\*' - & gt; '\%test\%' , aber
  • '\*test\*' und '\\*test\\*' sollten gleich bleiben.

Ich habe es versucht:

%Vor%

Was ist die richtige Regex, die mit den '*' in den oben angegebenen Beispielen übereinstimmt?

Was ist der Unterschied zwischen (?<!\(?=\\)*)\* und (?=(?<!\)(?=\\)*)\* oder wenn diese im Wesentlichen falsch sind, der Unterschied zwischen Regex, die eine solche visuelle Konstruktion haben?

    
bliof 23.10.2011, 15:45
quelle

5 Antworten

11

Um ein nicht-gescanntes Zeichen zu finden, suchen Sie nach einem Zeichen, dem eine gerade Anzahl von (oder null) Escape-Zeichen vorangestellt ist. Das ist relativ einfach.

%Vor%

Leider unterstützen viele Regex-Engines kein Look-Back mit variabler Länge, daher müssen wir Look-Ahead ersetzen:

%Vor%

Ersetzen Sie dies durch den Inhalt der Gruppe 1 und ein % -Zeichen.

Erklärung

%Vor%

Die Vorausschau stellt sicher, dass nur gerade Zahlen von Backslashes berücksichtigt werden. Wie auch immer, es gibt keinen Weg, sie in einer Gruppe zusammenzufassen, da die Vorausschau die Position in der Zeichenkette nicht vorrückt.

    
Tomalak 23.10.2011, 16:16
quelle
9

Ok, da Tim beschlossen hat, seine Regex nicht mit meinen vorgeschlagenen Mods zu aktualisieren (und Tomalaks Antwort ist nicht so gestrafft), hier ist meine empfohlene Lösung:

Ersetzen: ((?<!\)(?:\\)*)\* mit %

Hier ist es in Form eines kommentierten PHP-Snippets:

%Vor%

Addendum: Nicht-Lookaround-JavaScript-Lösung

Die obige Lösung erfordert Lookbehind, so dass es in JavaScript nicht funktioniert. Die folgende JavaScript-Lösung verwendet nicht lookbehind:

%Vor%

Diese Lösung ersetzt jede Instanz von Backslash-irgendetwas durch sich selbst und jede Instanz von * asterisk durch ein % Prozentzeichen.

Bearbeiten 2011-10-24: Javascript-Version korrigiert, um Fälle wie: **text** korrekt zu behandeln. (Danke an Alan Moore für den Hinweis auf den Fehler in der vorherigen Version.)

    
ridgerunner 23.10.2011 16:46
quelle
5

Andere haben gezeigt, wie dies mit einem Lookbehind gemacht werden kann, aber ich würde gerne dafür plädieren, keine Lookarounds zu benutzen. Betrachten Sie diese Lösung ( Demo hier ):

%Vor%

Der Großteil der Regex, [^*\]*(?:\.[^*\]*)* , ist ein Beispiel für Friedls "entrolled loop" -Idiom. Es verbraucht so viele Zeichen wie möglich für einzelne Zeichen außer Sternchen oder umgekehrten Schrägstrich oder Zeichenpaare, die aus einem umgekehrten Schrägstrich gefolgt von einem beliebigen Zeichen bestehen. Dadurch kann vermieden werden, dass nicht verzerrte Sterne verbraucht werden, unabhängig davon, wie viele Backslashes (oder andere Zeichen) ihnen vorangehen.

Die \G -Anker passen jeweils zu der Position, an der die vorherige Übereinstimmung endete, oder zum Anfang der Eingabe, wenn dies der erste Übereinstimmungsversuch ist. Dies verhindert, dass die Regex-Engine übersprungene Backslashes einfach überspringt und die nicht-gesperrten Sternchen trotzdem abgleicht. Daher verbraucht jede Iteration der /g controlled match alles bis zum nächsten nicht-ascendenen Stern, wobei alle bis auf das Sternchen in Gruppe 1 erfasst werden. Dann ist das wieder angeschlossen und das * wird durch % ersetzt.

Ich denke, das ist mindestens so leserlich, wie sich der Lookaround nähert und leichter zu verstehen ist. Es erfordert Unterstützung für \G , also wird es nicht in JavaScript oder Python funktionieren, aber es funktioniert gut in Perl.

    
Alan Moore 23.10.2011 23:39
quelle
3

Sie möchten also * nur dann abgleichen, wenn eine gerade Anzahl von Backslashes vorangestellt ist (oder anders ausgedrückt, wenn es nicht maskiert ist)? Dann brauchst du überhaupt kein Lookahead, da du nur zurückblickst, nicht wahr?

Suche nach

%Vor%

und ersetzen durch % .

Erläuterung:

%Vor%     
Tim Pietzcker 23.10.2011 16:01
quelle
0

Das Problem, entkommete Backslashes in Regex zu entdecken, hat mich eine Zeit lang fasziniert, und erst kürzlich wurde mir klar, dass ich es komplett überkomplizierte. Es gibt ein paar Dinge, die es einfacher machen, und soweit ich es sagen kann, hat es noch niemand bemerkt:

  • Mit Backslashes wird jedes Zeichen nach ihnen und nicht nur mit anderen umgekehrten Schrägstrichen zurückgeblättert. Also wird (\.)* eine ganze Kette von Escape-Zeichen essen, egal ob Backslashes oder nicht. Sie müssen sich keine Gedanken über gerade oder ungerade Schrägstriche machen; Überprüfen Sie einfach am Anfang oder Ende der Kette nach einem einzelnen \ ( ridgerunner JavaScript-Lösung nutzt dies aus).

  • Lookarounds sind nicht die einzige Möglichkeit, um sicherzustellen, dass Sie mit dem ersten Backslash in einer Kette beginnen. Sie können nur nach einem umgekehrten Schrägstrich (oder dem Anfang der Zeichenfolge) suchen.

Das Ergebnis ist ein kurzes, einfaches Muster, das keine Lookarounds oder Callbacks benötigt, und es ist kürzer als alles, was ich bisher gesehen habe.

%Vor%

Und die Ersatzzeichenfolge:

%Vor%

Dies funktioniert in .NET , was Lookbehinds erlaubt, und es sollte funktionieren für dich in Perl. Es ist möglich, dies in JavaScript zu tun, aber ohne Lookbehinds oder den \G -Anker kann ich keinen Weg sehen, dies in einem Einzeiler zu tun. Ridgerunner Callback sollte funktionieren, ebenso wie eine Schleife:

%Vor%

Es gibt viele Namen, die ich aus anderen Regex-Fragen kenne, und ich weiß, dass einige von euch schlauer sind als ich. Wenn ich einen Fehler gemacht habe, sag es bitte.

    
Justin Morgan 16.10.2012 20:38
quelle