正则表达式:在捕获组中嵌套捕获组

10

介绍

(如果你对介绍感到无聊,可以跳到假设...)

这个问题并不是特别针对VBScript(我只是在这种情况下使用它):我想找到一个通用的正则表达式用法解决方案(包括编辑器)。

这起源于我想创建一个适应MS Excel的示例4,其中3个捕获组用于将数据拆分到3个单元格中。我需要捕获一个完整的模式,然后在其中捕获另外3个模式。但是,在同一个表达式中,我还需要捕获另一种类型的模式,并再次捕获其中的3个模式(是的我知道…但在指责我之前,请先读完以下内容)。

我首先考虑了命名捕获组,然后我意识到我不应该«混合命名和编号的捕获组»,因为它«不被推荐,因为各种语言如何编号组是不一致的」

然后我看了看VBScript SubMatches非捕获,我为特定情况找到了一种可行的解决方案:

For Each C In Myrange
    strPattern = "(?:^([0-9]+);([0-9]+);([0-9]+)$|^.*:([0-9]+)\s.*:([0-9]+).*:([a-zA-Z0-9]+)$)"

    If strPattern <> "" Then
        strInput = C.Value

        With regEx
            .Global = True
            .MultiLine = True
            .IgnoreCase = False
            .Pattern = strPattern
        End With

        Set rgxMatches = regEx.Execute(strInput)

        For Each mtx In rgxMatches
            If mtx.SubMatches(0) <> "" Then
                C.Offset(0, 1) = mtx.SubMatches(0)
                C.Offset(0, 2) = mtx.SubMatches(1)
                C.Offset(0, 3) = mtx.SubMatches(2)
            ElseIf mtx.SubMatches(3) <> "" Then
                C.Offset(0, 1) = mtx.SubMatches(3)
                C.Offset(0, 2) = mtx.SubMatches(4)
                C.Offset(0, 3) = mtx.SubMatches(5)
            Else
                C.Offset(0, 1) = "(Not matched)"
            End If
        Next
    End If
Next

这里是 Rubular 上的正则表达式演示

124;12;3
我的 id1:213 我的 id2:232 我的 word:ins4yanrgx
:8587459 :18254182540215 :dcpt
0;1;2

它返回前两个带数字的单元以及第三个带数字或单词的单元。 基本上,我使用了一个非捕获组和两个“父”模式(“父” = 广泛的模式,用于检测其他子模式)。 如果第一个父模式有匹配的子模式(第一个捕获组),则将其值以及此模式的其余捕获组放在这三个单元格中。 如果没有,则检查是否匹配了第二个父模式的第四个捕获组,并将剩余的子模式放在相同的三个单元格中。

如果...

不是像这样:

(?:^(\d+);(\d+);(\d+)$|^.*:(\d+)\s.*:(\d+).*:(\w+)$|what(ever))

类似这样的事情是可能的:

(#:^(\d+);(\d+);(\d+)$)|(#:^.*:(\d+)\s.*:(\d+).*:(\w+)$)|(#:what(ever))

使用(#:不是创建一个非捕获组,而是创建一个“父”编号的捕获组。

这样我就可以类似于示例4中所做的事情:

C.Offset(0, 1) = regEx.Replace(strInput, "#$1")
C.Offset(0, 2) = regEx.Replace(strInput, "#$2")
C.Offset(0, 3) = regEx.Replace(strInput, "#$3")

它会搜索父模式,直到在子模式中找到匹配项(第一个匹配项将被返回,并且理想情况下不会搜索剩余的模式)。

已经有类似的东西了吗?或者我从正则表达式中漏掉了什么可以做到这一点的东西吗?

其他可能的变化:

  • 直接引用父模式和子模式,例如:#2$3(这相当于我的示例中的$6);
  • 在其他捕获组内创建尽可能多的捕获组(我猜这将更加复杂,但也是最有趣的部分),例如:(#:^_(?:(#:(\d+):\w+-(\d))|(#:\w+:(\d+)-(\d+)))_$)|(#:^\w+:\s+(#:(\w+);\d-(\d+))$)和在模式中提取##$1

    _123:smt-4_它将匹配:123
    _ott:432-10_ 它将匹配:432
    yant: special;3-45235 它将匹配:special

请告诉我是否注意到此逻辑中的任何错误或缺陷,我会尽快进行编辑。


看起来你正在尝试让事情变得比实际更困难。你可以在捕获组内使用捕获组,它们是编号或命名的,并且你始终可以像那样访问它们。在我看来,没有实际需要创建这样的捕获组层次结构。也许.NET Captures属性——按最内侧最左侧顺序获取所有匹配捕获组的集合接近你的需求。但是,你无法以描述的方式访问它们。 - Wiktor Stribiżew
@stribizhev 谢谢,我之前确实看到过那些 .NET 捕获(我写的示例使用 SubMatches 来获取非捕获组中的 cap.groups,在这些组中再次使用 Execute 就可以无限地向下进入层次结构了)。实际需求是任何人都可以在任何支持正则表达式的 IDE/编辑器中进行搜索和替换,而不是为了相同的目的编写循环... 在这个 rubular 中,每次匹配只显示 3 个结果而不是 6 个,其中有 3 个为空,这样做更有意义,对吗? - Armfoot
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Wiktor Stribiżew
1个回答

5
这通常是捕获大部分相同数据的情况。
唯一的区别在于形式。
有一个名为Branch Reset的正则表达式构造,适用于大多数Perl兼容引擎,但不适用于Java或Dot Net。
它主要节省了正则表达式资源,并使处理匹配更容易。
你提到的替代方法不会有任何帮助,实际上只会使用更多资源。你仍然需要查看匹配内容以确定位置。
但你只需检查聚类内的一个组即可告诉哪些其他组是有效的(如果使用分支重置,则此步骤是不必要的)。
(以下内容使用RegexFormat 6构建)
这是分支重置版本:
 # (?|^(\d+);(\d+);(\d+)$|^.*:(\d+)\s.*:(\d+).*:(\w+)$|what(ever)()())

 (?|
      ^ 
      ( \d+ )                       # (1)
      ;
      ( \d+ )                       # (2)
      ;
      ( \d+ )                       # (3)
      $ 
   |  
      ^ .* :
      ( \d+ )                       # (1)
      \s .* :
      ( \d+ )                       # (2)
      .* :
      ( \w+ )                       # (3)
      $ 
   |  
      what
      ( ever )                      # (1)
      ( )                           # (2)
      ( )                           # (3)
 )

这是你的两个正则表达式。注意,'parent'捕获实际上增加了组的数量(这会减慢引擎的速度):

 # (?:^(\d+);(\d+);(\d+)$|^.*:(\d+)\s.*:(\d+).*:(\w+)$|what(ever))

 (?:
      ^ 
      ( \d+ )                       # (1)
      ;
      ( \d+ )                       # (2)
      ;
      ( \d+ )                       # (3)
      $ 
   |  
      ^ .* :
      ( \d+ )                       # (4)
      \s .* :
      ( \d+ )                       # (5)
      .* :
      ( \w+ )                       # (6)
      $ 
   |  
      what
      ( ever )                      # (7)
 )

    # (#:^(\d+);(\d+);(\d+)$)|(#:^.*:(\d+)\s.*:(\d+).*:(\w+)$)|(#:what(ever))

    (                             # (1 start)
         \#: ^ 
         ( \d+ )                       # (2)
         ;
         ( \d+ )                       # (3)
         ;
         ( \d+ )                       # (4)
         $ 
    )                             # (1 end)
 |  
    (                             # (5 start)
         \#: ^ .* :
         ( \d+ )                       # (6)
         \s .* :
         ( \d+ )                       # (7)
         .* :
         ( \w+ )                       # (8)
         $ 
    )                             # (5 end)
 |  
    (                             # (9 start)
         \#:what
         ( ever )                      # (10)
    )                             # (9 end)

1
哇!这个真棒!几乎是个漂亮的射击。在regular-expressions.info上,他们实际上解释了“分支重置组”_将备选项分组并合并它们的捕获组_。我试过RegexFormat 6,但在我的Win 7上执行时崩溃了。然而,我渴望得到更多:直接访问由其父模式标识的子模式......这就是为什么我使用那种奇怪的语法(#:用于正则表达式和#$1用于匹配的模式(实际上,在正则表达式语法中不存在...至少我不知道 :)。 - Armfoot
@Armfoot - RegexFormat 6在我的Win 7机器上安装和运行都非常完美,无论是32位还是64位。你遇到了什么问题,安装了哪个子版本?6.02是当前版本。我认识写这个软件的人,他们会在合法的软件漏洞上提供免费注册密钥。将如何重现错误的详细报告发送至support@regexformat.com,您可能会获得一个免费密钥。回到主题...如果我是正则表达式引擎设计师,我不会缓冲嵌套捕获组,只需维护一个指向源字符串的迭代器数组/链表即可。 - user557597

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接