【问题标题】:How to capture nested named groups when referencing outer group by name?按名称引用外部组时如何捕获嵌套命名组?
【发布时间】:2021-10-29 19:54:36
【问题描述】:

在以逗号分隔的整数列表中,我需要捕获(通过 PCRE 正则表达式)第一次出现的 12*(如果有)和第一次出现的 45*(如果有)。我怎么做? 我尝试了以下方法,但它只能捕获序列中的第一个数字:(

(?P<number>(?P<n12>12\d)|(?P<n45>45\d)|\d+)(?:,(?P>number))*

这是一个要测试的示例字符串:11,222,123,444,456,7。我希望在这里捕获 n12=123 和 n45=456。

UPD

作为一种解决方法,我自己的解决方案是将分隔符声明为可选(它不是),如下所示:

(?:,?(?P<number>(?P<n12>12\d)|(?P<n45>45\d)|\d+))*

- 这对我有用,但不是在所有情况下(例如 ,1234123,41234,123,4 的解析方式相同),如果可能的话,我想避免这种情况。

UPD2

注意来吧,这不是我面临的真正任务——这只是一个简化的例子。这是另一个,以便您更好地理解我的想法:

(?P<animal>(?P<cat>pussy|cat)|(?P<dog>doge|dog)|\w+)(?:,(?P>animal))*

pussy,mouse,dog,bird - 必须捕获:cat=pussy, dog=dog

【问题讨论】:

  • 如果你不使用全局标志,你可以寻找\b12\d*\b45\d* 作为两个不同的查询。这对你来说是一个选择吗?
  • 或者可能没有命名组:^.*?\b((12|45)\d*)(?:(?:,\d+)*?,(?!\2)((12|45)\d+))? 并使用第一组和第三组。
  • @anubhava 上面的示例是我正在尝试做的尽可能简化的示例。在实际示例中(我不能在此处发布 b/c nda)子模式更复杂,但同样的事情仍然存在:我需要捕获不同子模式的单次出现(如果有),它们都是分隔符分隔列表中项目的更改- 这个列表只是解析数据的一部分,由几个部分组成
  • @JvdV 这只是一个例子。在实际情况下,我不希望复制子模式的代码(你做了)并一次性解析它们
  • @anubhava 有几个这样的子模式,我更愿意一次性捕获它们。我的解决方法允许我这样做,所以到目前为止,我坚持使用它,但如果可能的话,我正在寻找更好的解决方案

标签: regex pcre regex-group


【解决方案1】:

如果没有命名组,您可以在组 1 中捕获 12 或 45,并且对于第二个捕获组,使用 (?1) 递归第一个子模式,然后断言它与组 1 中已经捕获的不同使用带有反向引用 (?!\1)

的负前瞻
^(?:\d+,)*?(12|45)(?:\d*(?:,\d+)*?,(?!\1)((?1)))?

说明

  • ^ 字符串开始
  • (?:\d+,)*? 尽可能少匹配 1+ 位和 , 的可选重复
  • (12|45)\d* 捕获第 1 组中的 12 或 45
  • (?:非捕获组
    • (?:,\d+)*?, 尽可能少地匹配 , 和 1+ 个数字的可选重复并匹配 ,
    • (?!\1) 负前瞻,断言不是第 1 组中捕获的内容
    • ((?1))捕获组2,重复第一个子模式
  • )?关闭非捕获组并使其可选以允许匹配 1 个捕获组

Regex demo


如果您想要为单个或 2 个组值命名捕获组,您可以使用带有 J 标志的替代项来允许重复的子模式名称。

该模式匹配第一次出现的 12 然后 45,或仅匹配 12 或仅匹配 45。

^(?:(?:\d+,)*?(?P<n12>12)\d*(?:,\d+)*?,(?P<n45>45)|(?:\d+,)*?(?P<n45>45)\d*(?:,\d+)*?,(?P<n12>12)|(?:\d+,)*?(?P<n12>12)|(?:\d+,)*?(?P<n45>45))

Regex demo

【讨论】:

  • ++ for J 标志解决方案
  • 我可以使用 J 标志而不复制子模式吗?复制所有子模式不是很方便,但我需要捕获每个子模式一次(如果它出现在主题文本中)
  • 请看我刚刚附在问题上的另一个例子。在我看来,您建议的解决方案并不能解决我的问题,是吗?我需要捕获每个子模式,而不是在正则表达式中复制它们,但我不知道它们出现在 delimiter-separated-list-of-values 中的顺序
【解决方案2】:

看起来 PCRE 不允许捕获嵌套在通过引用调用的命名模式中的命名子模式。所以这个问题的确切答案是“没办法。对不起”。

但是对于这种特定情况有一个解决方法:而不是引用子模式:

(?P&lt;animal&gt;...)(?:,(?P&gt;animal))*

- 你可以避免引用它:

(?:,(?P&lt;animal&gt;...))*

- 但这需要主题在开头有一个前导分隔符,而它没有。

一个不好的解决方法是将分隔符标记为可选:

(?:,?(?P&lt;animal&gt;...))*

- 但它允许匹配奇怪的序列。

更好的解决方案是,将分隔符标记为条件需要:如果子模式已经至少匹配一次,则需要分隔符,否则必须省略:

(?:(?(animal),)(?P&lt;animal&gt;...))*

(?:(?(animal),)(?P&lt;animal&gt;(?P&lt;cat&gt;pussy|cat)|(?P&lt;dog&gt;doge|dog)|\w+))*

注意这只会捕获每个子模式的最后一个匹配项(如果有)。

【讨论】:

  • 这是一个很好的解决方案++。您可以使用^(?:(?(1),)((?P&lt;cat&gt;pussy|cat)|(?P&lt;dog&gt;doge|dog)|\w+))+$ 进行更新。但是请补充一点,它只会匹配命名子组的最后一个匹配项,例如。 doge,rat,cat,dog
猜你喜欢
  • 2020-01-22
  • 1970-01-01
  • 2016-06-20
  • 1970-01-01
  • 1970-01-01
  • 2019-12-04
  • 2014-09-11
  • 2014-06-26
相关资源
最近更新 更多