Quelle est la différence entre les «groupes» et les «captures» dans les expressions régulières .NET?

Je suis un peu confus quant à la différence entre un “groupe” et une “capture” en ce qui concerne le langage d’expression régulière de .NET. Considérez le code C # suivant:

MatchCollection matches = Regex.Matches("{Q}", @"^\{([AZ])\}$"); 

Je m’attends à ce que cela se traduise par une capture unique pour la lettre «Q», mais si MatchCollection les propriétés du MatchCollection renvoyé, je vois:

 matches.Count: 1 matches[0].Value: {Q} matches[0].Captures.Count: 1 matches[0].Captures[0].Value: {Q} matches[0].Groups.Count: 2 matches[0].Groups[0].Value: {Q} matches[0].Groups[0].Captures.Count: 1 matches[0].Groups[0].Captures[0].Value: {Q} matches[0].Groups[1].Value: Q matches[0].Groups[1].Captures.Count: 1 matches[0].Groups[1].Captures[0].Value: Q 

Qu’est-ce qui se passe exactement ici? Je comprends qu’il y a aussi une capture pour tout le match, mais comment les groupes entrent-ils? Et pourquoi pas les matches[0].Captures incluent la capture pour la lettre ‘Q’?

Vous ne serez pas le premier à le comprendre. Voici ce que le célèbre Jeffrey Friedl a à dire à ce sujet (pages 437+):

Selon votre sharepoint vue, cela ajoute une nouvelle dimension intéressante aux résultats de la correspondance, ou ajoute de la confusion et des ballonnements.

Et plus loin:

La principale différence entre un object Groupe et un object Capture est que chaque object Groupe contient une collection de Captures représentant toutes les correspondances intermédiaires du groupe pendant la correspondance, ainsi que le texte final correspondant au groupe.

Et quelques pages plus tard, voici sa conclusion:

Après avoir dépassé la documentation de .NET et compris réellement ce que ces objects ajoutent, j’ai des sentiments mitigés à leur sujet. D’une part, c’est une innovation intéressante [..] d’autre part, elle semble append un fardeau d’efficacité [..] d’une fonctionnalité qui ne sera pas utilisée dans la majorité des cas.

En d’autres termes: ils sont très similaires, mais occasionnellement et en l’occurrence, vous les trouverez utiles. Avant de faire pousser une autre barbe grise, vous pouvez même aimer les captures …


Étant donné que ni ce qui précède, ni ce qui est dit dans l’autre article ne semble vraiment répondre à votre question, considérez ce qui suit. Considérez Captures comme une sorte de tracker d’histoire. Lorsque la regex fait sa correspondance, elle passe de gauche à droite (sans tenir compte du retour en arrière pendant un moment) et quand elle rencontre des parenthèses, elle les stocke dans $x (x étant n’importe quel chiffre), disons $1 .

Les moteurs de regex normaux, lorsque les parenthèses de capture doivent être répétées, jetteront le $1 actuel et le remplaceront par la nouvelle valeur. Pas .NET, qui gardera cet historique et le place dans Captures[0] .

Si nous changeons votre regex pour regarder comme suit:

 MatchCollection matches = Regex.Matches("{Q}{R}{S}", @"(\{[AZ]\})+"); 

vous remarquerez que le premier Group aura une Captures (le premier groupe étant toujours la correspondance complète, c’est-à-dire égal à $0 ) et le second groupe contiendra {S} , c’est-à-dire uniquement le dernier groupe correspondant. Cependant, et voici le problème, si vous voulez trouver les deux autres captures, elles se trouvent dans Captures , qui contient toutes les captures intermédiaires pour {Q} {R} et {S} .

Si vous vous êtes déjà demandé comment obtenir de la capture multiple, qui ne montre que la dernière correspondance avec les captures individuelles clairement présentes dans la chaîne, vous devez utiliser Captures .

Un dernier mot sur votre dernière question: le total du match a toujours une capture totale, ne mélangez pas cela avec les groupes individuels. Les captures ne sont intéressantes qu’à l’intérieur des groupes .

De la documentation MSDN:

L’utilité réelle de la propriété Captures se produit lorsqu’un quantificateur est appliqué à un groupe de capture afin que le groupe capture plusieurs sous-chaînes dans une seule expression régulière. Dans ce cas, l’object Group contient des informations sur la dernière sous-chaîne capturée, tandis que la propriété Captures contient des informations sur toutes les sous-chaînes capturées par le groupe. Dans l’exemple suivant, l’expression régulière \ b (\ w + \ s *) +. correspond à une phrase entière qui se termine par une période. Le groupe (\ w + \ s *) + capture les mots individuels de la collection. Comme la collection de groupes ne contient que des informations sur la dernière sous-chaîne capturée, elle capture le dernier mot de la phrase “phrase”. Cependant, chaque mot capturé par le groupe est disponible dans la collection renvoyée par la propriété Captures.

Un groupe est ce que nous avons associé aux groupes dans les expressions régulières

 "(a[zx](b?))" Applied to "axb" returns an array of 3 groups: group 0: axb, the entire match. group 1: axb, the first group matched. group 2: b, the second group matched. 

sauf que ce ne sont que des groupes «capturés». Groupes non capturés (en utilisant la syntaxe ‘(?:’ Ne sont pas représentés ici.

 "(a[zx](?:b?))" Applied to "axb" returns an array of 2 groups: group 0: axb, the entire match. group 1: axb, the first group matched. 

Une capture est également ce que nous avons associé aux «groupes capturés». Mais lorsque le groupe est appliqué avec un quantificateur plusieurs fois, seule la dernière correspondance est conservée en tant que correspondance du groupe. Le tableau de capture stocke toutes ces correspondances.

 "(a[zx]\s+)+" Applied to "ax az ax" returns an array of 2 captures of the second group. group 1, capture 0 "ax " group 1, capture 1 "az " 

En ce qui concerne votre dernière question – j’aurais pensé avant de regarder cela que Captures serait un tableau des captures ordonnées par le groupe auquel elles appartiennent. C’est plutôt un alias des groupes [0] .Captures. Assez inutile ..

Cela peut être expliqué par un exemple simple (et des images).

Correspondant à 3:10pm avec l’expression régulière ((\d)+):((\d)+)(am|pm) , et en utilisant Mono interactive csharp :

 csharp> Regex.Match("3:10pm", @"((\d)+):((\d)+)(am|pm)"). > Groups.Cast(). > Zip(Enumerable.Range(0, int.MaxValue), (g, n) => "[" + n + "] " + g); { "[0] 3:10pm", "[1] 3", "[2] 3", "[3] 10", "[4] 0", "[5] pm" } 

Alors, où est le 1? entrer la description de l'image ici

Comme il y a plusieurs chiffres qui correspondent au quasortingème groupe, nous n’atteignons que la dernière correspondance si nous référençons le groupe (avec un ToSsortingng() implicite). Afin d’exposer les correspondances intermédiaires, nous devons approfondir et référencer la propriété Captures sur le groupe en question:

 csharp> Regex.Match("3:10pm", @"((\d)+):((\d)+)(am|pm)"). > Groups.Cast(). > Skip(4).First().Captures.Cast(). > Zip(Enumerable.Range(0, int.MaxValue), (c, n) => "["+n+"] " + c); { "[0] 1", "[1] 0" } 

entrer la description de l'image ici

Avec la permission de cet article .

Imaginez que vous ayez l’entrée de texte suivante dogcatcatcat et un pattern comme dog(cat(catcat))

Dans ce cas, vous avez 3 groupes, le premier ( groupe majeur ) correspond au match.

Match == dogcatcatcat et Group0 == dogcatcatcat

Group1 == catcatcat

Group2 == catcat

Alors, de quoi s’agit-il?

Considérons un petit exemple écrit en C # (.NET) en utilisant la classe Regex .

 int matchIndex = 0; int groupIndex = 0; int captureIndex = 0; foreach (Match match in Regex.Matches( "dogcatabcdefghidogcatkjlmnopqr", // input @"(dog(cat(...)(...)(...)))") // pattern ) { Console.Out.WriteLine($"match{matchIndex++} = {match}"); foreach (Group @group in match.Groups) { Console.Out.WriteLine($"\tgroup{groupIndex++} = {@group}"); foreach (Capture capture in @group.Captures) { Console.Out.WriteLine($"\t\tcapture{captureIndex++} = {capture}"); } captureIndex = 0; } groupIndex = 0; Console.Out.WriteLine(); } 

Sortie :

 match0 = dogcatabcdefghi group0 = dogcatabcdefghi capture0 = dogcatabcdefghi group1 = dogcatabcdefghi capture0 = dogcatabcdefghi group2 = catabcdefghi capture0 = catabcdefghi group3 = abc capture0 = abc group4 = def capture0 = def group5 = ghi capture0 = ghi match1 = dogcatkjlmnopqr group0 = dogcatkjlmnopqr capture0 = dogcatkjlmnopqr group1 = dogcatkjlmnopqr capture0 = dogcatkjlmnopqr group2 = catkjlmnopqr capture0 = catkjlmnopqr group3 = kjl capture0 = kjl group4 = mno capture0 = mno group5 = pqr capture0 = pqr 

Analysons juste la première correspondance ( match0 ).

Comme vous pouvez le voir, il y a trois groupes mineurs : group3 , group4 et group5

  group3 = kjl capture0 = kjl group4 = mno capture0 = mno group5 = pqr capture0 = pqr 

Ces groupes (3-5) ont été créés à cause du ‘ subpattern(...)(...)(...) du pattern principal (dog(cat(...)(...)(...)))

La valeur de group3 correspond à sa capture ( capture0 ). (Comme dans le cas du groupe group4 et du groupe group5 ). C’est parce qu’il n’y a pas de répétition de groupe comme (...){3} .


Ok, considérons un autre exemple où il y a une répétition de groupe .

Si l’on modifie le modèle d’expression régulière à faire correspondre (pour le code ci-dessus) à partir de (dog(cat(...)(...)(...))) à (dog(cat(...){3})) , vous remarquerez qu’il y a la répétition de groupe suivante: (...){3} .

Maintenant, la sortie a changé:

 match0 = dogcatabcdefghi group0 = dogcatabcdefghi capture0 = dogcatabcdefghi group1 = dogcatabcdefghi capture0 = dogcatabcdefghi group2 = catabcdefghi capture0 = catabcdefghi group3 = ghi capture0 = abc capture1 = def capture2 = ghi match1 = dogcatkjlmnopqr group0 = dogcatkjlmnopqr capture0 = dogcatkjlmnopqr group1 = dogcatkjlmnopqr capture0 = dogcatkjlmnopqr group2 = catkjlmnopqr capture0 = catkjlmnopqr group3 = pqr capture0 = kjl capture1 = mno capture2 = pqr 

Encore une fois, analysons juste la première correspondance ( match0 ).

Il n’y a plus de groupes mineurs group4 et group5 raison de (...){3} répétition ( {n}n> = 2 ) ils ont été fusionnés en un seul groupe de group3 .

Dans ce cas, la valeur group3 correspond à sa capture2 ( la dernière capture , en d’autres termes).

Ainsi, si vous avez besoin des 3 captures internes ( capture0 , capture1 , capture2 ), vous devrez parcourir la collection Captures du groupe.

La conclusion est: faites attention à la manière dont vous concevez les groupes de votre modèle. Vous devriez penser dès le départ à quel comportement provoque la spécification du groupe, comme (...)(...) , (...){2} ou (.{3}){2} etc.


Espérons que cela aidera à faire la lumière sur les différences entre les captures , les groupes et les matchs .