web-dev-qa-db-fra.com

Comment les groupes de capture imbriqués sont-ils numérotés dans les expressions régulières?

Existe-t-il un comportement défini pour la manière dont les expressions régulières doivent gérer le comportement de capture des parenthèses imbriquées? Plus précisément, pouvez-vous raisonnablement vous attendre à ce que différents moteurs capturent les parenthèses externes dans la première position et les parenthèses imbriquées dans les positions suivantes?

Considérez le code PHP code (en utilisant des expressions régulières PCRE)

<?php
  $test_string = 'I want to test sub patterns';
  preg_match('{(I (want) (to) test) sub (patterns)}', $test_string, $matches);
  print_r($matches);
?>

Array
(
    [0] => I want to test sub patterns  //entire pattern
    [1] => I want to test           //entire outer parenthesis
    [2] => want             //first inner
    [3] => to               //second inner
    [4] => patterns             //next parentheses set
)

L'expression entre parenthèses entière est capturée en premier (je veux tester), puis les motifs parenthèses internes sont capturés ensuite ("veulent" et "à"). Cela a un sens logique, mais je pouvais voir un argument tout aussi logique pour la première capture des sous-parenthèses, puis pour la capture du motif entier.

Donc, est-ce que "capturer la chose entière en premier" comportement défini dans les moteurs d'expression régulière, ou cela va-t-il dépendre du contexte du modèle et/ou du comportement du moteur (PCRE étant différent de C # étant différent de Java étant différent que etc.)?

72
Alan Storm

De perlrequick

Si les regroupements dans une expression régulière sont imbriqués, $ 1 obtient le groupe avec la parenthèse ouvrante la plus à gauche, $ 2 la parenthèse ouvrante suivante, etc.

Avertissement : à l'exclusion des parenthèses d'ouverture de groupe non capturé (? =)

Mise à jour

Je n'utilise pas beaucoup PCRE, car j'utilise généralement la vraie chose;), mais les documents de PCRE montrent la même chose que Perl:

SOUS-MOTIFS

2. Il configure le sous-modèle en tant que sous-modèle de capture. Cela signifie que, lorsque le modèle entier correspond, la partie de la chaîne d'objet correspondant au sous-modèle est renvoyée à l'appelant via l'argument ovector de pcre_exec(). Les parenthèses ouvrantes sont comptées de gauche à droite (à partir de 1) pour obtenir le numéro des sous-modèles de capture.

Par exemple, si la chaîne "le roi rouge" est comparée au motif

the ((red|white) (king|queen))

les sous-chaînes capturées sont "red king", "red" et "king", et sont numérotées 1, 2 et 3, respectivement.

Si PCRE s'éloigne de la compatibilité avec les expressions rationnelles Perl, peut-être que l'acronyme devrait être redéfini - "Perl Cognate Regular Expressions", "Perl Comparable Regular Expressions" ou quelque chose. Ou tout simplement se départir des lettres de sens.

54
daotoad

Oui, tout cela est assez bien défini pour toutes les langues qui vous intéressent:

  • Java - http://Java.Sun.com/javase/6/docs/api/Java/util/regex/Pattern. html # cg
    "Les groupes capturés sont numérotés en comptant leurs parenthèses ouvrantes de gauche à droite. ... Le groupe zéro représente toujours l'expression entière."
  • . Net - http://msdn.Microsoft.com/en-us/library/bs2twtah (VS.71) .aspx
    "Les captures utilisant () sont numérotées automatiquement en fonction de l'ordre des parenthèses ouvrantes, en commençant par un. La première capture, l'élément de capture numéro zéro, est le texte correspondant à l'ensemble du modèle d'expression régulière.")
  • PHP (fonctions PCRE) - http://www.php.net/manual/en/function.preg-replace.php# function.preg-replace.parameters
    "\ 0 ou $ 0 fait référence au texte correspondant à l'ensemble du modèle. Les parenthèses ouvrantes sont comptées de gauche à droite (à partir de 1) pour obtenir le numéro du sous-modèle de capture." (C'était également le cas des fonctions POSIX obsolètes)
  • [~ # ~] pcre [~ # ~] - http://www.pcre.org/pcre.txt
    Pour ajouter à ce qu'a dit Alan M, recherchez "Comment pcre_exec () renvoie les sous-chaînes capturées" et lisez le cinquième paragraphe qui suit:

     La première paire d'entiers, ovector [0] et ovector [1], identifie la portion 
     De la chaîne du sujet correspondant au motif entier. La paire 
     Suivante est utilisée pour le premier sous-modèle de capture, etc. La valeur 
     Renvoyée par pcre_exec () est une de plus que la paire numérotée la plus élevée que 
     A été définie. Par exemple, si deux sous-chaînes ont été capturées, la valeur renvoyée 
     Est 3. S'il n'y a aucun sous-modèle de capture, la valeur de retour 
     D'une correspondance réussie est 1, indiquant que seule la première paire 
     de décalages a été défini. 
    
  • Perl est différent - http://perldoc.Perl.org/perlre.html#Capture-buffers
    .

Vous trouverez plus que probablement des résultats similaires pour d'autres langues (Python, Ruby et autres).

Vous dites qu'il est tout aussi logique d'énumérer d'abord les groupes de capture internes et vous avez raison - il s'agit simplement d'indexer la fermeture, plutôt que d'ouvrir, les parens. (si je vous comprends bien). Cela est cependant moins naturel (par exemple, il ne suit pas la convention de direction de lecture) et rend donc plus difficile (probablement pas de manière significative) de déterminer, par inspection, quel groupe de capture sera à un indice de résultat donné.

Mettre la chaîne de correspondance entière en position 0 est également logique - principalement pour la cohérence. Il permet à toute la chaîne correspondante de rester au même index quel que soit le nombre de groupes de capture de regex à regex et quel que soit le nombre de groupes de capture qui correspondent réellement à quelque chose (Java par exemple réduira la longueur du tableau des groupes correspondants pour chaque capture groupe ne correspond à aucun contenu (pensez par exemple à quelque chose comme "un motif (. *)"). Vous pouvez toujours inspecter capturing_group_results [capturing_group_results_length - 2], mais cela ne se traduit pas bien en langues vers Perl qui créent dynamiquement des variables ($ 1 , $ 2 etc.) (Perl est un mauvais exemple bien sûr, car il utilise $ & pour l'expression correspondante, mais vous avez l'idée :).

16
Alan Donnelly

Chaque saveur regex que je connais numérote les groupes selon l'ordre dans lequel les parenthèses d'ouverture apparaissent. Le fait que les groupes externes soient numérotés avant leurs sous-groupes contenus n'est qu'un résultat naturel, pas une politique explicite.

Là où cela devient intéressant, c'est avec les groupes nommés . Dans la plupart des cas, ils suivent la même politique de numérotation par les positions relatives des parens - le nom n'est qu'un alias pour le numéro. Cependant, dans les expressions régulières .NET, les groupes nommés sont numérotés séparément des groupes numérotés. Par exemple:

Regex.Replace(@"one two three four", 
              @"(?<one>\w+) (\w+) (?<three>\w+) (\w+)",
              @"$1 $2 $3 $4")

// result: "two four one three"

En effet, le nombre est un alias pour le nom ; les numéros attribués aux groupes nommés commencent là où les "vrais" groupes numérotés s'arrêtent. Cela peut sembler une politique bizarre, mais il y a une bonne raison à cela: dans les expressions régulières .NET, vous pouvez utiliser le même nom de groupe plus d'une fois dans une expression régulière. Cela rend possible des expressions rationnelles comme celle de ce fil pour faire correspondre les nombres à virgule flottante de différents paramètres régionaux:

^[+-]?[0-9]{1,3}
(?:
    (?:(?<thousand>\,)[0-9]{3})*
    (?:(?<decimal>\.)[0-9]{2})?
|
    (?:(?<thousand>\.)[0-9]{3})*
    (?:(?<decimal>\,)[0-9]{2})?
|
    [0-9]*
    (?:(?<decimal>[\.\,])[0-9]{2})?
)$

S'il y a un séparateur de milliers, il sera enregistré dans le groupe "mille", quelle que soit la partie de l'expression rationnelle qui lui correspond. De même, le séparateur décimal (s'il y en a un) sera toujours enregistré dans le groupe "décimal". Bien sûr, il existe des moyens d'identifier et d'extraire les séparateurs sans groupes nommés réutilisables, mais cette méthode est tellement plus pratique, je pense que cela justifie plus que le schéma de numérotation étrange.

Et puis il y a Perl 5.10+, qui nous donne plus de contrôle sur la capture de groupes que je ne sais quoi en faire. :RÉ

8
Alan Moore

L'ordre de capture dans l'ordre du paren gauche est standard sur toutes les plateformes sur lesquelles j'ai travaillé. (Perl, php, Ruby, egrep)

4
Devin Ceartas