2010-01-27 5 views
37

Ich versuche, den gesamten zitierten Text in einer einzigen Zeile zu finden.Suche in Anführungszeichenfolgen mit Escapezeichen in C# mit einem regulären Ausdruck

Beispiel:

"Some Text" 
"Some more Text" 
"Even more text about \"this text\"" 

Ich brauche zu bekommen:

  • "Some Text"
  • "Some more Text"
  • "Even more text about \"this text\""

\"[^\"\r]*\" gibt mir alles außer dem letzten, wegen der gemieteten Zitate.

Ich habe über \"[^\"\\]*(?:\\.[^\"\\]*)*\" Arbeits gelesen, aber ich einen Fehler zur Laufzeit erhalten:

parsing ""[^"\]*(?:\.[^"\]*)*"" - Unterminated [] set. 

Wie dieses Problem beheben?

Antwort

76

Was Sie dort haben, ist ein Beispiel für Friedls "entrollt" -Technik, aber Sie scheinen etwas zu haben Fusion darüber, wie man es als String-Literal ausdrücken kann. Hier ist, wie es zu der Regex Compiler aussehen sollte:

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

Die anfängliche "[^"\\]* entspricht ein Anführungszeichen, gefolgt von null oder mehr von irgendwelchen anderen Zeichen als Anführungszeichen oder Schrägstriche. Dieser Teil allein, zusammen mit der endgültigen ", wird eine einfache Zeichenfolge in Anführungszeichen ohne eingebettete Escape-Sequenzen wie "this" oder "" übereinstimmen.

Wenn es tut Begegnung ein Backslash verbraucht \\. den umgekehrten Schrägstrich und was folgt, und [^"\\]* (wieder) verbraucht alles bis zum nächsten Backslash oder Anführungszeichen. Dieser Teil wird so oft wie nötig wiederholt, bis ein nicht verzahntes Anführungszeichen auftaucht (oder das Ende der Zeichenfolge erreicht und der Abgleichversuch fehlschlägt).

Beachten Sie, dass dies "foo\"- in \"foo\"-"bar" entspricht. Das mag einen Fehler in der Regex aufdecken, aber das tut es nicht; Es ist der Eingang, der ungültig ist. Das Ziel bestand darin, in Anführungszeichen gesetzte Zeichenfolgen zu finden, die optional umgekehrte Schrägstriche enthalten, die in anderen Text eingebettet sind - warum würde es außerhalb von von Strings in Anführungszeichen geben? Wenn Sie das wirklich unterstützen müssen, haben Sie ein viel komplexeres Problem, das einen ganz anderen Ansatz erfordert.

Wie gesagt, das obige ist, wie die Regex auf den Regex-Compiler aussehen sollte. Aber Sie schreiben es in Form eines String-Literals, und diese neigen dazu, bestimmte Zeichen speziell zu behandeln - d. H. Umgekehrte Schrägstriche und Anführungszeichen. Glücklicherweise ersparen Ihnen C# 'wörtliche Strings die Mühe, Backslashes doppelt zu entkommen; Sie müssen nur jedes Anführungszeichen mit einem anderen Anführungszeichen entkommen:

Regex r = new Regex(@"""[^""\\]*(?:\\.[^""\\]*)*"""); 

So ist die Regel doppelte Anführungszeichen für den C# -Compiler und doppelte Schrägstriche für den Regex Compiler - schön und einfach. Diese besondere regex ein wenig umständlich, mit den drei Anführungszeichen an jedem Ende aussehen, sondern betrachtet die Alternative:

Regex r = new Regex("\"[^\"\\\\]*(?:\\\\.[^\"\\\\]*)*\""); 

In Java Sie immer haben sie auf diese Weise zu schreiben. :-(

+0

Ich mag diese Erklärung am besten. –

+0

war eine gute Antwort – motevalizadeh

+0

Cruising durch einige der Antworten, die dich berühmt gemacht haben ... Upvoting dieses für eine so klare Erklärung aus der schlimmsten Backslash-Suppe! :) – zx81

1

Ich weiß, das ist nicht die sauberste Methode, aber mit Ihrem Beispiel würde ich das Zeichen vor der " überprüfen, um zu sehen, ob es ein \ ist. Wenn ja, würde ich das Zitat ignorieren.

0

Jede Chance, was Sie tun müssen: \"[^\"\\\\]*(?:\\.[^\"\\\\]*)*\"

+0

Das gibt mir: "Etwas Text"; "Etwas mehr Text"; "" –

4
"(\\"|\\\\|[^"\\])*" 

funktionieren sollte. Passen Sie entweder ein Escapezeichen, einen Escaped-Backslash oder ein anderes Zeichen außer einem Anführungszeichen oder einem umgekehrten Schrägstrich an. Wiederholen.

In C#:

StringCollection resultList = new StringCollection(); 
Regex regexObj = new Regex(@"""(\\""|\\\\|[^""\\])*"""); 
Match matchResult = regexObj.Match(subjectString); 
while (matchResult.Success) { 
    resultList.Add(matchResult.Value); 
    matchResult = matchResult.NextMatch(); 
} 

Edit: Added entkam in die Liste Backslash richtig "This is a test\\" zu handhaben.

Erläuterung:

Geben Sie zuerst ein Anführungszeichen ein.

Dann werden die Alternativen von links nach rechts ausgewertet. Die Engine versucht zuerst, ein gestrichenes Zitat zu finden. Wenn das nicht übereinstimmt, versucht es einen umgekehrten Backslash. Auf diese Weise kann zwischen "Hello \" string continues" und "String ends here \\" unterschieden werden.

Wenn beide nicht übereinstimmen, ist alles außer einem Anführungszeichen oder Backslash-Zeichen erlaubt. Dann wiederhole es.

Schließlich passen Sie das schließende Zitat an.

+0

Entschuldigung für die Bearbeitung dieses Beitrags. Aber jetzt denke ich, ich habe es elegant genug. Und auch richtig. Ich hoffe. –

+0

Diese Regex nicht mit diesem Text: \ "Einige Text \" Einige Text "Einige Text" und "Einige mehr Text" ein "" d "Noch mehr Text über \" this text \ "" – Kamarey

+0

Dies ist ausgezeichnet! Ich denke, ein Teil des Problems war, dass ich das @ nicht verwendete, was mehr Komplexität mit sich brachte, als ich überall Platz nehmen musste. –

3

Ich empfehle bekommen RegexBuddy. Sie können damit herumspielen, bis Sie sicher sind, dass alles in Ihrem Testset übereinstimmt.

Wie für Ihr Problem, würde ich versuchen, vier/'s statt zwei:

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

Eines der Verkaufsargumente von RegexBuddy ist, dass es die Regex automatisch in den Quellcode in der von Ihnen angegebenen Sprache konvertieren kann. In diesem Fall wird der "rohe" Regex "[^" \\] * (?: \\. [^ "\\] *) *" in '@" "[^" "\\] konvertiert. * (?: \\. [^ "" \\] *) * "" "'. –

2

Der reguläre Ausdruck

(?<!\\)".*?(?<!\\)" 

auch Text behandeln, die mit einem entflohenen Zitat beginnt:

\"Some Text\" Some Text "Some Text", and "Some more Text" an""d "Even more text about \"this text\"" 
+0

Gibt es eine Möglichkeit, dies könnte für mehrere Zeilen in Anführungszeichenfolgen funktionieren? –

+0

Dies behandelt keine maskierten Backslashes am Ende von Strings: '" Hallo \\ "'. –

12

Regex für Streicher Erfassung (mit \ für Charakter zu entkommen), für den .NET-Motor:

(?>(?(STR)(?(ESC).(?<-ESC>)|\\(?<ESC>))|(?!))|(?(STR)"(?<-STR>)|"(?<STR>))|(?(STR).|(?!)))+ 

hier eine "freundliche" Version:

(?>       | especify nonbacktracking 
    (?(STR)      | if (STRING MODE) then 
     (?(ESC)    |  if (ESCAPE MODE) then 
       .(?<-ESC>)  |   match any char and exits escape mode (pop ESC) 
       |    |  else 
       \\(?<ESC>)  |   match '\' and enters escape mode (push ESC) 
     )      |  endif 
     |      | else 
     (?!)     |  do nothing (NOP) 
    )       | endif 
    |       | -- OR 
    (?(STR)      | if (STRING MODE) then 
     "(?<-STR>)   |  match '"' and exits string mode (pop STR) 
     |      | else 
     "(?<STR>)    |  match '"' and enters string mode (push STR) 
    )       | endif 
    |       | -- OR 
    (?(STR)      | if (STRING MODE) then 
     .      |  matches any character 
     |      | else 
     (?!)     |  do nothing (NOP) 
    )       | endif 
)+        | REPEATS FOR EVERY CHARACTER 

Basierend auf http://tomkaminski.com/conditional-constructs-net-regular-expressions Beispiele.Es beruht auf Anführungszeichen. Ich benutze es mit großem Erfolg. Verwenden Sie es mit Singleline Flagge.

Um mit Regex zu spielen, empfehle ich Rad Software Regular Expression Designer, die eine nette "Language Elements" Registerkarte mit schnellen Zugriff auf einige grundlegende Anweisungen hat. Es basiert auf der Regex-Engine von .NET.

+0

Interessante Aufschlüsselung. –

1

Ähnlich wie RegexBuddy von @Blankasaurus, hilft RegexMagic auch.

1

Eine einfache Antwort, ohne die Verwendung von ? ist

"([^\\"]*(\\")*)*\" 

oder als wörtliche Zeichenfolge

@"^""([^\\""]*(\\"")*(\\[^""])*)*""" 

Es bedeutet nur:

  • finden die ersten "
  • finden Sie eine beliebige Anzahl von Zeichen s, die nicht \ oder "
  • finden eine beliebige Anzahl von entgangen Zitate \"
  • eine beliebige Anzahl von Escape-Zeichen finden, die nicht zitiert sind
  • wiederholen die letzten drei Befehle, bis Sie finden "

I glaube, es funktioniert so gut wie @Alan Moores Antwort, aber für mich ist es leichter zu verstehen. Es akzeptiert auch unübertroffene ("unausgeglichene") Zitate.

+1

Ich kann sehen, dass diese Antwort aus irgendeinem Grund ein bisschen fehlerhaft ist. Bitte beachten Sie http://stackoverflow.com/questions/20196740/regex-matching-doesnt-finish –

1

Nun, Alan Moores Antwort ist gut, aber ich würde sie etwas modifizieren, um sie kompakter zu machen. Für die Regex Compiler:

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

mit Alan Moores Ausdruck vergleichen:

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

Die Erklärung ist sehr ähnlich wie Alan Moores ein:

Der erste Teil " passt ein Anführungszeichen.

Der zweite Teil [^"\\]* entspricht null oder mehr Zeichen außer Anführungszeichen oder Backslashes.

Und der letzte Teil (\\.)* entspricht Backslash und was auch immer einzelnes Zeichen folgt. Achten Sie auf das *, dass diese Gruppe optional ist.

Die Teile beschrieben, zusammen mit der endgültigen " (dh "[^"\\]*(\\.)*"), wird übereinstimmen: "Einige Text" und "Noch mehr Text \" ", aber wird nicht übereinstimmen:" Noch mehr Text über \ "dieser Text \" ".

Um es zu ermöglichen, brauchen wir den Teil: [^"\\]*(\\.)* wird so oft wie nötig wiederholt, bis ein unespaniertes Anführungszeichen auftaucht (oder es das Ende der Zeichenfolge erreicht und der Abgleichversuch fehlschlägt). So wickelte ich dieser Teil durch Klammern und fügte ein Sternchen hinzu. Jetzt stimmt es überein: "Etwas Text", "Noch mehr Text \" "," Noch mehr Text über \ "this text \" "und" Hello \\ ".

In C# -Code wird es wie folgt aussehen:

var r = new Regex("\"([^\"\\\\]*(\\\\.)*)*\""); 

BTW, die Reihenfolge der zwei Hauptteilen: [^"\\]* und (\\.)* keine Rolle spielt. Sie können schreiben:

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

oder

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

Das Ergebnis wird das gleiche sein.

Nun müssen wir ein anderes Problem lösen: \"foo\"-"bar". Der aktuelle Ausdruck wird mit "foo\"-" übereinstimmen, aber wir möchten ihn mit "bar" vergleichen. Ich weiß nicht,

warum es

entgangen Zitate außerhalb von Strings in Anführungszeichen werden, aber wir können es leicht implementieren, indem Sie den folgenden Teil an den Anfang hinzu: (\G|[^\\]). Es besagt, dass der Match-Start an dem Punkt erfolgen soll, an dem die vorherige Übereinstimmung beendet wurde oder nach einem beliebigen Zeichen außer Backslash. Warum brauchen wir \G? Dies ist für den folgenden Fall, zum Beispiel: "a""b".

Beachten Sie, dass (\G|[^\\])"([^"\\]*(\\.)*)*" mit -"bar" in \"foo\"-"bar" übereinstimmt. Um nur "bar" zu erhalten, müssen wir die Gruppe angeben und ihr optional einen Namen geben, zum Beispiel "MyGroup". Dann wird C# -Code wie folgt aussehen:

[TestMethod] 
public void RegExTest() 
{ 
    //Regex compiler: (?:\G|[^\\])(?<MyGroup>"(?:[^"\\]*(?:\.)*)*") 
    string pattern = "(?:\\G|[^\\\\])(?<MyGroup>\"(?:[^\"\\\\]*(?:\\\\.)*)*\")"; 
    var r = new Regex(pattern, RegexOptions.IgnoreCase); 

    //Human readable form:  "Some Text" and "Even more Text\""  "Even more text about \"this text\""  "Hello\\"  \"foo\" - "bar" "a" "b" c "d" 
    string inputWithQuotedText = "\"Some Text\" and \"Even more Text\\\"\" \"Even more text about \\\"this text\\\"\" \"Hello\\\\\" \\\"foo\\\"-\"bar\" \"a\"\"b\"c\"d\""; 
    var quotedList = new List<string>(); 
    for (Match m = r.Match(inputWithQuotedText); m.Success; m = m.NextMatch()) 
     quotedList.Add(m.Groups["MyGroup"].Value); 

    Assert.AreEqual(8, quotedList.Count); 
    Assert.AreEqual("\"Some Text\"", quotedList[0]); 
    Assert.AreEqual("\"Even more Text\\\"\"", quotedList[1]); 
    Assert.AreEqual("\"Even more text about \\\"this text\\\"\"", quotedList[2]); 
    Assert.AreEqual("\"Hello\\\\\"", quotedList[3]); 
    Assert.AreEqual("\"bar\"", quotedList[4]); 
    Assert.AreEqual("\"a\"", quotedList[5]); 
    Assert.AreEqual("\"b\"", quotedList[6]); 
    Assert.AreEqual("\"d\"", quotedList[7]); 
} 
Verwandte Themen