2010-01-27 14 views
37

Sto provando a trovare tutto il testo citato su una singola riga.Trovare stringhe tra virgolette con virgolette di escape in C# usando un'espressione regolare

Esempio:

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

ho bisogno di ottenere:

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

\"[^\"\r]*\" mi dà tutto tranne l'ultimo, a causa delle citazioni sfuggite.

Ho letto di \"[^\"\\]*(?:\\.[^\"\\]*)*\" di lavoro, ma ottengo un errore in fase di esecuzione:

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

Come posso risolvere questo problema?

risposta

76

Quello che hai qui è un esempio della tecnica di "loop srotolato" di Friedl, ma sembra che tu abbia qualche problema fusione su come esprimerla come stringa letterale. Ecco come dovrebbe apparire al compilatore regex:

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

L'iniziale "[^"\\]* corrisponde a un segno di virgolette seguito da zero o più caratteri diversi da virgolette o backslash. Quella parte da sola, insieme all'ultima ", corrisponderà a una semplice stringa quotata senza sequenze di escape incorporate, come "this" o "".

Se fa incontro un backslash, \\. consuma la barra rovesciata e tutto ciò segue, e [^"\\]* (di nuovo) consuma tutto fino al prossimo backslash o virgolette. Quella parte viene ripetuta tutte le volte necessarie fino a quando non viene visualizzata una virgoletta senza escape (o raggiunge la fine della stringa e il tentativo di corrispondenza fallisce).

Si noti che questo corrisponderà a "foo\"- in \"foo\"-"bar". Questo potrebbe sembrare un difetto nella regex, ma non lo è; è l'input non valido. L'obiettivo era quello di far corrispondere le stringhe tra virgolette, opzionalmente contenenti virgolette rovesciate, incorporate in altro testo - perché ci sarebbero citazioni sfuggite al di fuori dello delle stringhe tra virgolette? Se hai davvero bisogno di sostenerlo, hai un problema molto più complesso, che richiede un approccio molto diverso.

Come ho detto, quanto sopra è il modo in cui la regex dovrebbe guardare al compilatore regex. Ma lo stai scrivendo sotto forma di stringa letterale, e quelli tendono a trattare determinati caratteri in particolare - cioè, barre rovesciate e virgolette. Fortunatamente, le stringhe letterali di C# ti risparmiano il fastidio di dover evitare i backslash; non resta che fuggire ogni virgoletta con un altro marchio citazione:

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

Quindi la regola è virgolette doppie per il compilatore C# e doppi backslash per il compilatore regex - piacevole e facile. Questo particolare espressione regolare può sembrare un po 'imbarazzante, con i tre virgolette alle due estremità, ma prendere in considerazione l'alternativa:

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

In Java, è sempre deve scrivere in quel modo. :-(

+0

Mi piace di più questa spiegazione. –

+0

è stata una buona risposta – motevalizadeh

+0

Navigare attraverso alcune delle risposte che ti hanno reso famoso ... Sconfiggere questo per fare una spiegazione così chiara della peggiore zuppa di backslash! :) – zx81

1

So che questo non è il metodo più pulito, ma con il tuo esempio vorrei controllare il carattere prima del " per vedere se è un \. Se lo è, ignorerei la citazione.

0

C'è qualche possibilità che devi fare: \"[^\"\\\\]*(?:\\.[^\"\\\\]*)*\"

+0

Questo mi dà: "Some Text"; "Qualche altro testo"; "" –

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

dovrebbe funzionare. Abbina una citazione di escape, una barra rovesciata di escape o qualsiasi altro carattere ad eccezione di una citazione o di un carattere di barra rovesciata. Ripetere.

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: Aggiunto sfuggito backslash alla lista per gestire correttamente "This is a test\\".

Spiegazione:

primo incontro un carattere preventivo.

Quindi le alternative vengono valutate da sinistra a destra. Il motore prima cerca di abbinare una citazione sfuggita. Se ciò non corrisponde, prova una barra rovesciata di escape. In questo modo, è in grado di distinguere tra "Hello \" string continues" e "String ends here \\".

Se uno dei due non corrisponde, allora è consentito qualsiasi altro eccetto che per una citazione o un carattere di barra rovesciata. Quindi ripetere.

Infine, corrisponde alla quotazione di chiusura.

+0

Ci scusiamo per aver modificato questo post così tanto. Ma ora penso di averlo abbastanza elegante. E anche corretto. Io spero. –

+0

Questa espressione regolare non funziona con questo testo: \ "Some Text \" Some Text "Some Text", e "Some more Text" an "" d "Ancora più testo su \" this text \ "" – Kamarey

+0

Questo è eccellente! Penso che parte del problema fosse che non stavo usando la @, che aggiungeva più complessità con il dover tagliare tutto il posto. –

3

Mi consiglia di ottenere RegexBuddy. Ti permette di giocarci fino a quando non ti assicuri che tutto nel tuo set di test coincida.

Per quanto riguarda il tuo problema, vorrei provare a quattro/s 'invece di due:

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

Uno dei punti di forza di RegexBuddy è che può convertire automaticamente la regex in codice sorgente in qualsiasi lingua specificata. In questo caso converte la regex "raw" "[^" \\] * (?: \\. [^ "\\] *) *" 'a' @ "" "[^" "\\] * (:? \\ [^ "" \\] *. *) "" "'. –

2

L'espressione regolare

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

sarà anche gestire il testo che inizia con un preventivo sfuggito:

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

C'è un modo in cui questo potrebbe funzionare per più stringhe quotate su linee? –

+0

Questo non gestisce i backslash di escape alla fine delle stringhe: '" Hello \\ "'. –

12

Regex per catturare le stringhe (con \ per il personaggio in fuga), per il motore NET:

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

Qui, una versione "friendly":

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

Sulla base di http://tomkaminski.com/conditional-constructs-net-regular-expressions esempi.Si basa sul bilanciamento delle citazioni. Lo uso con grande successo. Usalo con il flag Singleline.

Per giocare con espressioni regolari, suggerisco Rad Software Regular Expression Designer, che ha una bella scheda "Elementi della lingua" con accesso rapido ad alcune istruzioni di base. È basato sul motore regex di .NET.

+0

Interessante ripartizione. –

1

Simile a RegexBuddy pubblicato da @Blankasaurus, RegexMagic aiuta anche.

1

Una risposta semplice, senza l'uso di ?, è

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

o, come una stringa verbatim

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

Significa solo:

  • trovare la prima "
  • trova un numero qualsiasi di caratteri s che non sono \ o "
  • trovare un qualsiasi numero di citazioni fuggiti \"
  • trovare qualsiasi numero di caratteri di escape, che non sono citazioni
  • Ripetere gli ultimi tre comandi, fino a trovare "

I Credo che funzioni come la risposta di @Alan Moore, ma per me è più facile da capire. Accetta anche quotazioni ineguagliate ("sbilanciate").

+1

Posso vedere che questa risposta è un po 'buggata, per qualche ragione. Si prega di fare riferimento a http://stackoverflow.com/questions/20196740/regex-matching-doesnt-finish –

1

Bene, la risposta di Alan Moore è buona, ma vorrei modificarla un po 'per renderla più compatta. Per il compilatore regex:

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

Confronta con l'espressione di Alan Moore:

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

La spiegazione è molto simile a Alan Moore di uno:

La prima parte " corrisponde a un segno di virgolette.

La seconda parte [^"\\]* corrisponde a zero o più caratteri diversi da virgolette o barre retroverse.

E l'ultima parte (\\.)* corrisponde al backslash e qualunque sia il singolo carattere che lo segue. Presta attenzione al simbolo *, dicendo che questo gruppo è facoltativo.

Le parti descritte, insieme con la finale " (cioè "[^"\\]*(\\.)*"), corrisponderà: "Un testo" e "Ancora più Text \" "ma non corrisponderanno: 'Ancora più testo su \' questo testo \" ."

Per rendere possibile, abbiamo bisogno della parte:. [^"\\]*(\\.)* ottiene ripetuto tante volte quanto necessario fino a quando un segno di virgolette senza caratteri di escape gira in su (o raggiunge la fine della stringa e il tentativo match fallisce) Così ho avvolto quella parte tra parentesi e aggiunge un asterisco. Ora corrisponde a: "Alcuni testo", "Ancora più testo \" "," Ancora testo su \ "questo testo \" "e" Ciao \\ ".

Nel codice C# che sarà del tipo:

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

BTW, l'ordine delle due parti principali: [^"\\]* e (\\.)* non importa. È possibile scrivere:

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

o

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

Il risultato sarà lo stesso.

Ora dobbiamo risolvere un altro problema: \"foo\"-"bar". L'espressione corrente corrisponderà a "foo\"-", ma vogliamo abbinarla a "bar". Non so

virgolette perché ci sarebbe sfuggito fuori di stringhe tra virgolette

ma possiamo implementare facilmente aggiungendo la seguente parte per l'inizio: (\G|[^\\]). Dice che vogliamo che la partita inizi nel punto in cui è terminata la partita precedente o dopo qualsiasi carattere tranne il backslash. Perché abbiamo bisogno di \G? Questo è per il seguente caso, ad esempio: "a""b".

Si noti che (\G|[^\\])"([^"\\]*(\\.)*)*" corrisponde a -"bar" in \"foo\"-"bar". Quindi, per ottenere solo "bar", dobbiamo specificare il gruppo e facoltativamente dargli un nome, ad esempio "MyGroup". Quindi il codice C# sarà simile a:

[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]); 
} 
Problemi correlati