2016-05-22 11 views
1

Ich möchte eine Eingabezeichenfolge analysieren und bestimmen, ob sie eine Folge von Zeichen enthält, die von doppelten Anführungszeichen umgeben sind ("). Die Zeichenfolge selbst darf keine weiteren doppelten Anführungszeichen enthalten, es sei denn, sie werden durch einen umgekehrten Schrägstrich wie folgt geschützt: \".Wie wird nach einem Zeichenfolgenliteral gesucht, das Escapezeichen erlaubt?

Um die Dinge komplizierter zu machen, können die Backslashes selbst gemieden werden: \\. Ein doppeltes Anführungszeichen, dem zwei (oder eine gerade Anzahl) Backslashes vorangestellt sind (\\"), ist daher nicht maskiert. Und um es noch schlimmer zu machen, sind einzelne nicht entkommene Backslashes (d. H. Weder gefolgt von " noch \) erlaubt.

Ich versuche, das mit Pythons re Modul zu lösen. Die module documentation erzählt uns von dem Rohr Operator A|B:

Da die Zielzeichenfolge abgetastet wird, durch '|' getrennt REs versucht werden von links nach rechts. Wenn ein Muster vollständig übereinstimmt, wird dieser Zweig akzeptiert. Dies bedeutet, dass A, wenn B nicht mehr getestet wird, auch wenn es eine längere Gesamtübereinstimmung ergeben würde. Mit anderen Worten, der Operator '|' ist niemals gierig.

jedoch dies nicht funktioniert, wie ich erwartete:

>>> import re 
>>> re.match(r'"(\\[\\"]|[^"])*"', r'"a\"') 
<_sre.SRE_Match object; span=(0, 4), match='"a\\"'> 

Die Idee dieser regex ist, zuerst Prüfung für ein Escape-Zeichen (\\ oder \") und nur, wenn diese nicht gefunden wird, überprüfen Sie für jedes Zeichen, das nicht " ist (aber es könnte ein einzelnes sein \). Dies kann beliebig oft geschehen und muss von Literalen " Zeichen umgeben sein.

Ich würde erwarten, dass die Zeichenfolge "a\" überhaupt nicht übereinstimmen, aber anscheinend tut es. Ich würde erwarten, \", um die A Teil übereinstimmen und die B Teil nicht getestet werden, aber anscheinend ist es.

Ich weiß nicht wirklich, wie das Backtracking in diesem Fall funktioniert, aber gibt es eine Möglichkeit, es zu vermeiden?

Ich denke, es würde funktionieren, wenn ich zuerst für das ursprüngliche " Zeichen prüfen (und es von der Eingabe entfernen) in einem separaten Schritt. Ich kann dann den folgenden regulären Ausdruck verwendet, den Inhalt der Zeichenfolge zu erhalten:

>>> re.match(r'(\\[\\"]|[^"])*', r'a\"') 
<_sre.SRE_Match object; span=(0, 3), match='a\\"'> 

Dies würde das entwichene Zitat enthält. Da es kein abschließendes Zitat geben würde, würde ich wissen, dass die angegebene Zeichenfolge insgesamt nicht übereinstimmt.

Muss ich es so machen oder ist es möglich, dies mit einem einzigen regulären Ausdruck und ohne zusätzliche manuelle Überprüfung zu lösen?

In meiner realen Anwendung ist die " -geschlossene Zeichenfolge nur ein Teil eines größeren Musters, also denke ich, es wäre einfacher, alles auf einmal in einem einzigen regulären Ausdruck zu tun.

fand ich ähnliche Fragen, aber die nicht berücksichtigen, dass ein einzelner nicht-Flucht Backslash Teil der Zeichenfolge sein kann: regex to parse string with escaped characters, Parsing for escape characters with a regular expression.

+0

Siehe: '" (?: [^ \\ "] | \\.) *" '->' "[^" \\] * (?: \\. [^ "\\] *) * "' –

+0

Danke, das funktioniert super! Was meinst du mit dem Pfeil? Ist eine der Alternativen besser als die andere? Sind sie genau gleichwertig? – Matthias

+0

Ja, es gibt einen Unterschied. Okay, lassen Sie mich das als Antwort betrachten, denn das funktioniert für Sie. –

Antwort

1

Wenn Sie "(\\[\\"]|[^"])*", Sie " von 0+ Sequenzen von \ entweder durch \ oder " oder nicht ", und dann gefolgt von einem „Schließen“ " gefolgt gefolgt entsprechen. Beachten Sie, dass die Eingabe "a\"\ durch den zweiten alternativen Zweig [^"] übereinstimmt (da der umgekehrte Schrägstrich ein gültiger Nicht-" ist).

Sie müssen die \ aus dem nicht " auszuschließen:

"(?:[^\\"]|\\.)*" 
     ^^ 

Also, wir " übereinstimmen, dann entweder nicht " und nicht \ (mit [^\\"]) oder einer Escape-Sequenz (mit \\.) , 0 oder öfter.

Diese Regex ist jedoch nicht effizient genug, da viel Backtracking stattfindet (verursacht durch den Wechsel und den Quantifizierer). Abgerollt Version ist:

"[^"\\]*(?:\\.[^"\\]*)*" 

Siehe regex demo

Das letzte Muster entspricht:

  • " - doppelte Anführungszeichen
  • [^"\\]* - null oder mehr Zeichen andere als \ und "
  • (?:\\.[^"\\]*)* - ze ro oder mehrere Sequenzen von
    • \\. - einem umgekehrten Schrägstrich, gefolgt mit einem beliebigen Zeichen als ein Neue-Zeile-
    • [^"\\]* - null oder mehr andere Zeichen als \ und "
  • " - einem doppelten Anführungszeichen
+0

Das ist definitiv der Weg zu gehen, aber ich frage mich immer noch, warum mein ursprünglicher Ausdruck '' '' '\'' entspricht, wobei letzterer '' 'ist (auf der linken Seite von' '' '' ') sagen wir: "Wenn' A' passt, wird 'B' nicht weiter getestet." Wie kommt es, dass 'B' in diesem Fall übereinstimmt? – Matthias

+1

Die Antwort ist: backtracking ermöglicht' '' '' '' ' \ ''' '' '' '' '' '' '' '' '' '' \ "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' ' werden von der ersten Alternative gepackt. ABER, das letzte '" ist * obligatorisch *, bedeutet es, dass die Regex-Engine versucht, die erfassten Werte, die sie gefunden hat, zu gruppieren, um Platz für die '" 'zu schaffen und findet ein '' '' '' vor '' ', das mit dem zweiten alternativen Zweig' [^ "]' abgeglichen werden kann.Siehe [Ihre Regex in Aktion] (https://regex101.com/r/dZ4bB2/2), Schritte 12, 13 auf der Seite * regex debugger *. –

+1

Danke für die Erklärung, jetzt glaube ich, ich verstehe! Ich bin neu in diesem ganzen Regex-Ding und ich kannte den * Regex-Debugger * nicht, der ein wirklich großartiges Werkzeug ist, um zu verstehen, wie diese Regexe funktionieren. – Matthias