分组命名

命名捕获组和反向引用


几乎所有现代正则表达式引擎都支持编号的捕获组和编号的反向引用。在一个很长的正则表达式中,如果具有许多组和反向引用那这个正则是很难阅读的。由于在正则表达式的中间添加或删除捕获组会使在添加或删除的组之后的所有组的数目变少,这就导致先前我们使用的反向引用的编号可能会有变化,因此维护起来特别困难。   

Python的re模块是第一个提供解决方案的模块:命名捕获组和命名反向引用。(?P<name>group)给捕获匹配的group的反向引用加一个名称“name”。name必须是以字母开头的字母数字序列。group可以是任何正则表达式。我们可以使用命名过的反向引用(?P=name)引用组的内容。?,P,<和=都是语法的一部分。尽管命名后向引用的语法也是使用小括号,但这只是一个后向引用,不进行任何捕获或分组。在之前对HTML标记匹配的栗子中,可以使用命名的反向引用重写。可以写为<(?P<tag>[A-Z][A-Z0-9]*)\b[^>]*>.*?</(?P=tag)>

在.NET框架中也支持命名捕获组。微软的开发人员发明了自己的语法,而不是遵循Python率先开发并由PCRE复制的语法(当时只有两个正则表达式引擎支持命名捕获组)。(?<name>group)(?“name”group)捕获匹配的group成反向引用“name”。命名反向引用为\k<name>\k'name'。与Python相比,命名组的语法中没有P。命名反向引用的语法与编号反向引用的语法更相似,而不是Python使用的语法。我们可以在名称周围使用单引号或尖括号。这在正则表达式中是没有区别的。我们可以使用这两种样式。使用<的语法在使用单引号分隔字符串的编程语言中更可取,而在将正则表达式添加到XML文件时,使用单引号的语法更可取,因为这可以在将正则表达式作为文本字符串或者是XML内容的时候,最小化格式化正则表达式所需的转义量。

由于Python和.NET引入了自己的语法,因此我们将这两种变体称为“ Python语法”和“ .NET语法”,用于命名捕获和命名后向引用。如今,许多其他正则表达式版本都复制了此语法。

Perl 5.10添加了对命名捕获和反向引用的Python和.NET语法的支持。它还为命名反向引用添加了两个更多的语法变体:\k{one}\g{two} 。Perl中用于命名反向引用的五种语法之间没有区别。所有这些都可以互换使用。在替换文本中,可以插入变量$ + {name}用以插入与命名捕获组匹配的文本。      

PCRE 7.2和更高版本支持Perl 5.10支持的命名捕获和反向引用的所有语法。旧版本的PCRE支持Python语法,即使当时还不兼容Perl。使用PCRE语法实现其regex支持的还有PHP,Delphi和R之类的语言。不幸的是,PHP或R在替换文本中均不支持命名引用。我们将必须使用对已命名组的编号引用。PCRE完全不支持搜索和替换。     

Java 7和XRegExp复制了.NET语法,但仅复制了带有尖括号的变体。Ruby 1.9和Ruby支持.NET语法的两种变体。该JGsoft支持Python语法和.NET语法的两种变型。       

Boost 1.42和更高版本支持使用带尖括号或引号的.NET语法命名的捕获组,以及使用来自Perl 5.10的带有花括号的\g语法的命名反向引用。Boost 1.47还使用\k语法,尖括号和.NET中的引号来支持反向引用。Boost 1.47允许使用\g或\k以及花括号,尖括号<>或引号指定命名和编号的反向引用。因此,Boost 1.47和更高版本在基于\1语法的基础上具有六个反向引用语法。这使Boost与Ruby,PCRE,PHP,R和JGsoft冲突,后者将\g放在尖括号或引号中作为子例程调用。              

命名捕获组的编号


不建议混合使用已命名和编号的捕获组,因为各种正则表达式在组的编号方式上不一致。如果组不需要名称,请使用(?:group)语法使其不被捕获。在.NET中,可以通过设置RegexOptions.ExplicitCapture使所有未命名的组都无法捕获。在Delphi中,设置roExplicitCapture 。对于XRegExp,请使用/n标志。Perl从Perl 5.22开始支持/ n 。使用PCRE,设置PCRE_NO_AUTO_CAPTURE 。该JGsoft和.NET支持(?n)的模式修饰符。如果使所有未命名的组不被捕获,则可以跳过此部分,省去头疼的麻烦。                  

大多数正则表达式引擎通过从左到右计数其开头括号来对命名和未命名的捕获组进行编号。但是,在.NET中,未命名的捕获组首先被分配了编号,从左到右计算它们的开始括号,跳过所有已命名的组。之后,通过从左到右计算命名组的开头括号,为命名组分配编号。

当只有Python和PCRE使用Python语法,而只有.NET使用.NET语法时,JGsoft regex引擎会复制Python和.NET语法。因此,它还复制了Python和.NET的编号行为。像Python一样,它将未命名的组命名为Python样式的命名组。像.NET一样,它随后为.NET样式的命名组编号。即使将两种方式混合在同一个正则表达式中,这些规则也适用。  

例如,正则表达式(a)(?P<x>b)(c)(?P<y>d)符合预期的abcd 。如果使用此正则表达式以及替换项\1\2\3\4$1$2$3$4进行搜索替换,我们将得到abcd 。所有四个组从左到右,从一到四编号。         

.NET框架使事情变得更加复杂。正则表达式(a)(?<x>b)(c)(?<y>d)再次与abcd匹配。但是,如果用$1$2$3$4作为替换进行搜索替换,我们将得到acbd 。首先,未命名的组(a)(c)得到数字1和2。然后,已命名的组“ x”和“ y”得到数字3和4。          

在复制.NET语法的所有其他版本中,正则表达式(a)(?<x>b)(c)(?<y>d)仍与abcd匹配。但是在所有这些正则中,除了JGsoft,替换\1\2\3\4$1$2$3$4(取决于正则表达式引擎)都会使我们获得abcd 。         

具有相同名称的多个组


在.NET框架和JGsoft中允许在正则表达式中对多个捕获组命名相同的名称。具有相同名称的所有组都为它们匹配的文本共享相同的存储空间。因此,对该名称的反向引用将匹配该组与该名称最近一次捕获了的匹配的内容。在替换文本中对该名称的引用会插入与该名称匹配的组匹配的文本,该名称是最后一个捕获内容的名称。    

Perl和Ruby也允许具有相同名称的组。但是这些正则只是看起来像所有的组具有相同名称一样。实际上,这些组是分开的。在Perl中,反向引用将正则表达式中最左边的组捕获的文本与匹配某些内容的名称匹配。在Ruby中,反向引用将匹配任何具有该名称的组捕获的文本。正则表达式引擎的回溯功能使Ruby尝试所有组。   

因此,在Perl和Ruby中,如果它们在正则表达式中位于不同的选择项中,则使用具有相同名称的组是有意义的,以便只有具有该名称的组中的任何一个都可以捕获任何文本。然后,对该组的反向引用将合理地匹配该组捕获的文本。 例如,如果要匹配“ a”后跟数字0..5或“ b”后跟数字4..7,并且只关心数字,则可以使用正则表达式a(?<digit>[0-5])|b(?<digit>[4-7])。在这四种正则中,名为“ digit”的组将为我们提供匹配的数字0..7,与字母无关。如果希望此匹配项后跟c并输入完全相同的数字,则可以使用

``` (?:a(?[0-5])|b(?[4-7]))c\k

```  默认情况下,PCRE不允许重复的命名组。如果打开该选项或使用模式修饰符(?J),则在PCRE 6.7和更高版本中是允许的。但是在PCRE 8.36之前,它并不是很有用,因为反向引用始终指向正则表达式中具有该名称的第一个捕获组,而不管它是否参与了匹配。从PCRE 8.36(以及PHP 5.6.9和R 3.1.3)开始,在PCRE2中,反向引用指向具有该名称的第一个组,该组实际上参与了匹配。尽管PCRE和Perl在相反的方向上处理重复的组,但是如果我们遵循建议仅在不同的选择项中使用具有相同名称的组,则最终结果是相同的。  

Boost允许重复的命名组。在Boost 1.47之前,它没有用,因为反向引用始终指向正则表达式中出现在反向引用之前的最后一个具有该名称的组。在Boost 1.47和更高版本中,反向引用指向具有该名称的第一个组,该组实际上参与了匹配,就像在PCRE 8.36和更高版本中一样。

Python,Java和XRegExp 3不允许多个组使用相同的名称。这样做会产生正则表达式编译错误。XRegExp 2虽然允许了这样使用,但是确未能正确处理。

在Perl 5.10,PCRE 8.00,PHP 5.2.14和Boost 1.42(或这些版本的更高版本)中,如果希望不同选项中的组具有相同的名称如(?|a(?<digit>[0-5])|b(?<digit>[4-7]))c\k<digit>,则最好使用分支重置组。这种特殊的语法是-(?|而不是(?:——名称为’digit’的两个组实际上是同一组。对该组的引用始终是正确的,并且在所有的正则中都是一致的。(旧版本PCRE和PHP可能支持分支重置组,但不能正确处理分支重置组中的重复名称。)        

查看笔记

扫码一下
查看教程更方便