反向引用需要放在它们所引用的组后面吗?

4

在运行这个答案的一些测试时,我注意到以下意外的行为。这将在第一个出现后删除所有<tag>的实例:

var input = "<text><text>extra<words><text><words><something>";
Regex.Replace(input, @"(<[^>]+>)(?<=\1.*\1)", "");
// <text>extra<words><something>

但这样做是不行的:
Regex.Replace(input, @"(?<=\1.*)(<[^>]+>)", "");
// <text><text>extra<words><text><words><something>

同样地,这将会删除最后一个之前所有 <tag> 的出现。
Regex.Replace(input, @"(<[^>]+>)(?=.*\1)", "");
// extra<text><words><something>

但这个不会:
Regex.Replace(input, @"(?=\1.*\1)(<[^>]+>)", "");
// <text><text>extra<words><text><words><something>

这让我有所思考...

在.NET正则表达式引擎中,反向引用需要出现在其引用的组之后吗?或者这些模式中还有其他导致它们无法工作的问题?


从逻辑上讲,你需要先捕获某些内容,然后将其用作反向引用,否则递归正则表达式(例如此处)将失败 :) PS:这不仅适用于 .net,我认为在所有版本中都是如此。在 php pcre 中查看演示。 - HamZa
1
@HamZa 谢谢,这是个好点子。我原本以为环视断言可能不同,因为(据我所知),它们必须在字符串的匹配部分之后进行评估。换句话说,在它可以检查断言之前,它必须找到<tag> - 但我对正则表达式的内部了解不太多,所以我可能是错的。此外,我想大多数情况下,如果引擎的行为取决于评估它的顺序而不是出现在模式中的顺序,那么它将更为令人惊讶。 - p.s.w.g
1个回答

4
您的问题让我也开始思考了起来,所以我用RegexBuddy做了一些测试,令我惊讶的是你说不起作用的第二个正则表达式(?<=\1.*)(<[^>]+>)实际上起作用了,其他的都和你说的一样。然后我尝试在C#代码中使用同样的表达式- 第二个表达式 -,但它并没有像你遇到的那样工作。
这让我感到困惑,然后我注意到我的RegexBuddy版本可以追溯到2008年,因此.NET引擎的工作方式肯定有所改变,但这揭示了一个我认为是合理的事实,在2008年之前,回顾后面的内容似乎是在整个表达式匹配之后才进行评估的。我觉得这种行为在回顾后面的内容时还是可以接受的,因为您需要在查找之前先匹配一些内容。
尽管如此,现在的引擎似乎在遇到环视时会立即评估它们,我通过使用以下表达式发现了这一点,这类似于您的情况的反向情况:
(?<=(\w))\1

正如您所看到的,我在正则表达式中捕获了一个单词字符,并在外部引用它。我在字符串hello上进行了测试,并在第二个l字符处得到了预期的匹配结果,这证明了回顾先于尝试匹配表达式的其余部分执行。

结论:是的,反向引用需要出现在所引用的组后面,否则它将没有匹配语义。


1
错误信息告诉你(?<=\1)\w是无效的语法,因为它包含对不存在的捕获组的引用。(?<=\1)(\w)是有效的语法,但它永远不会成功,因为它试图匹配该组之前的内容,而该组尚未参与匹配。 - Alan Moore
@AlanMoore 当然,你是正确的,错误已经被纠正了。谢谢。 - Ibrahim Najjar
感谢抽出时间回复。实际上,如果它出现在组前面,我不会得到任何异常;它只是没有匹配任何内容。我曾考虑过,可能后向引用会与尚未捕获其引用的组匹配一个空字符串,但显然根本不匹配任何内容,例如Regex.Matches("hello", @"\1()")不匹配任何内容,但是Regex.Matches("hello", @"(?!\1)()")会匹配每个字符周围的0长度字符串。 - p.s.w.g
@p.s.w.g 对于异常语句我很抱歉,Alan在他的评论中提到了这一点,但我可能忘记修复结论部分。如果引用的组尚未匹配,则反向引用不会匹配任何内容。然而,关于论点的主要观点,即lookaround是否在表达式的其余部分之前或之后匹配,可以肯定地说,它在遇到时立即匹配。 - Ibrahim Najjar

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