web-dev-qa-db-fra.com

Recherche de chaînes entre guillemets avec des guillemets échappés en C # à l'aide d'une expression régulière

J'essaie de trouver tout le texte cité sur une seule ligne.

Exemple:

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

J'ai besoin d'avoir:

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

\"[^\"\r]*\" Me donne tout sauf le dernier, à cause des citations échappées.

J'ai lu que \"[^\"\\]*(?:\\.[^\"\\]*)*\" fonctionne, mais j'obtiens une erreur au moment de l'exécution:

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

Comment puis-je réparer ça?

40
Joshua Lowry

Ce que vous avez là-bas est un exemple de la technique de "boucle déroulée" de Friedl, mais vous semblez avoir une certaine confusion sur la façon de l'exprimer comme un littéral de chaîne. Voici à quoi cela devrait ressembler pour le compilateur d'expressions régulières:

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

La première "[^"\\]* correspond à un guillemet suivi de zéro ou plusieurs caractères autres que des guillemets ou des barres obliques inverses. Cette partie seule, avec la finale ", correspondra à une simple chaîne entre guillemets sans séquence d'échappement intégrée, comme "this" ou "".

S'il rencontre rencontre une barre oblique inverse, \\. consomme la barre oblique inverse et tout ce qui suit, et [^"\\]* (à nouveau) consomme tout jusqu'à la barre oblique inverse ou le guillemet suivant. Cette partie est répétée autant de fois que nécessaire jusqu'à ce qu'un guillemet non échappé apparaisse (ou atteigne la fin de la chaîne et la tentative de correspondance échoue).

Notez que cela correspondra à "foo\"- dans \"foo\"-"bar". Cela peut sembler révéler une faille dans l'expression régulière, mais ce n'est pas le cas; c'est le entrée qui n'est pas valide. Le but était de faire correspondre les chaînes entre guillemets, contenant éventuellement des guillemets avec barre oblique inverse, incorporés dans un autre texte - pourquoi y aurait-il des guillemets échappés extérieur des chaînes entre guillemets? Si vous avez vraiment besoin de soutenir cela, vous avez un problème beaucoup plus complexe, nécessitant une approche très différente.

Comme je l'ai dit, ce qui précède est la façon dont l'expression régulière doit ressembler au compilateur d'expression régulière. Mais vous l'écrivez sous la forme d'un littéral de chaîne, et ceux-ci ont tendance à traiter certains caractères spécialement - à savoir les barres obliques inverses et les guillemets. Heureusement, les chaînes textuelles de C # vous évitent d'avoir à double-échapper les barres obliques inverses; il vous suffit d'échapper à chaque guillemet avec un autre guillemet:

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

Donc, la règle est des guillemets doubles pour le compilateur C # et des doubles barres obliques inverses pour le compilateur regex - Nice and easy. Cette expression régulière particulière peut sembler un peu maladroite, avec les trois guillemets à chaque extrémité, mais envisagez l'alternative:

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

En Java, vous toujours devez les écrire de cette façon. :-(

79
Alan Moore

Regex pour la capture de chaînes (avec \ pour l'échappement de caractères), pour le moteur .NET:

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

Ici, une version "conviviale":

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

Basé sur http://tomkaminski.com/conditional-constructs-net-regular-expressions exemples. Il repose sur l'équilibrage des devis. Je l'utilise avec beaucoup de succès. Utilisez-le avec le drapeau Singleline.

Pour jouer avec les regex, je recommande Rad Software Regular Expression Designer , qui a un bel onglet "Language Elements" avec un accès rapide à quelques instructions de base. Il est basé sur le moteur d'expression régulière de .NET.

12
Ricardo Nolde
"(\\"|\\\\|[^"\\])*"

devrait marcher. Faites correspondre une citation échappée, une barre oblique inversée ou tout autre caractère à l'exception d'une citation ou d'une barre oblique inversée. Répéter.

En 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: Ajout d'une barre oblique inversée à la liste pour gérer correctement "This is a test\\".

Explication:

Faites d'abord correspondre un caractère de citation.

Ensuite, les alternatives sont évaluées de gauche à droite. Le moteur essaie d'abord de faire correspondre une citation échappée. Si cela ne correspond pas, il essaie une barre oblique inversée. De cette façon, il peut faire la distinction entre "Hello \" string continues" et "String ends here \\".

Si l'un ou l'autre ne correspond pas, alors tout le reste est autorisé à l'exception d'un guillemet ou d'une barre oblique inverse. Répétez ensuite.

Enfin, faites correspondre le devis de clôture.

4
Tim Pietzcker

Je recommande d'obtenir RegexBuddy . Il vous permet de jouer avec jusqu'à ce que vous vous assuriez que tout dans votre ensemble de tests correspond.

Quant à votre problème, j'essaierais quatre/au lieu de deux:

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

L'expression régulière

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

gérera également le texte commençant par une citation échappée:

\"Some Text\" Some Text "Some Text", and "Some more Text" an""d "Even more text about \"this text\""
2
Kamarey

Je sais que ce n'est pas la méthode la plus propre, mais avec votre exemple, je vérifierais le caractère avant le " pour voir s'il s'agit d'un \. Si c'est le cas, j'ignorerais la citation.

1
Krill

Semblable à RegexBuddy publié par @Blankasaurus, RegexMagic aide aussi.

1
Emre

Eh bien, la réponse d'Alan Moore est bonne, mais je la modifierais un peu pour la rendre plus compacte. Pour le compilateur d'expressions régulières:

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

Comparez avec l'expression d'Alan Moore:

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

L'explication est très similaire à celle d'Alan Moore:

La première partie " Correspond à un guillemet.

La deuxième partie [^"\\]* Correspond à zéro ou plusieurs caractères quelconques autres que les guillemets ou les barres obliques inverses.

Et la dernière partie (\\.)* Correspond à la barre oblique inverse et à n'importe quel caractère unique qui la suit. Faites attention au *, en disant que ce groupe est facultatif.

Les parties décrites, ainsi que le " Final (c'est-à-dire "[^"\\]*(\\.)*"), correspondront à: "Un peu de texte" et "Encore plus de texte \" ", mais ne correspondront pas à:" Encore plus de texte sur\"ce texte \" ".

Pour le rendre possible, nous avons besoin de la partie: [^"\\]*(\\.)* est répétée autant de fois que nécessaire jusqu'à ce qu'un guillemet non échappé apparaisse (ou atteigne la fin de la chaîne et la tentative de correspondance échoue). J'ai donc enveloppé cette partie entre crochets et ajouté un astérisque. Maintenant, il correspond à: "Du texte", "Encore plus de texte \" "," Encore plus de texte sur\"ce texte \" "et" Bonjour \\ ".

En code C #, cela ressemblera à:

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

BTW, l'ordre des deux parties principales: [^"\\]* Et (\\.)* N'a pas d'importance. Tu peux écrire:

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

ou

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

Le résultat sera le même.

Nous devons maintenant résoudre un autre problème: \"foo\"-"bar". L'expression actuelle correspondra à "foo\"-", Mais nous voulons la faire correspondre à "bar". Je ne sais pas

pourquoi y aurait-il des guillemets échappés extérieur de chaînes entre guillemets

mais nous pouvons l'implémenter facilement en ajoutant la partie suivante au début: (\G|[^\\]). Il dit que nous voulons que le match commence au point où le match précédent s'est terminé ou après n'importe quel caractère sauf la barre oblique inverse. Pourquoi avons-nous besoin de \G? C'est pour le cas suivant, par exemple: "a""b".

Notez que (\G|[^\\])"([^"\\]*(\\.)*)*" Correspond à -"bar" Dans \"foo\"-"bar". Donc, pour obtenir seulement "bar", Nous devons spécifier le groupe et éventuellement lui donner un nom, par exemple "MyGroup". Le code C # ressemblera alors à:

[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]);
}
1
Alex

Une réponse simple, sans utiliser de ?, est

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

ou, sous forme de chaîne textuelle

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

Cela signifie simplement:

  • trouver le premier "
  • trouver n'importe quel nombre de caractères qui ne sont pas \ ou "
  • trouver n'importe quel nombre de guillemets échappés \"
  • trouver n'importe quel nombre de caractères échappés, qui ne sont pas des guillemets
  • répétez les trois dernières commandes jusqu'à ce que vous trouviez "

Je crois que cela fonctionne aussi bien que la réponse de @Alan Moore, mais pour moi, c'est plus facile à comprendre. Il accepte également les citations inégalées ("non équilibrées").

1
Piotr Zierhoffer

Toute chance que vous devez faire: \"[^\"\\\\]*(?:\\.[^\"\\\\]*)*\"

0
Fried Hoeben