为什么这两个正则表达式不一样?

Why are these two regexes not the same?

我最近回答了 this question,这让我开始考虑模拟可变宽度后视。

无效:

(?:(?<=<\/)|(?<=<))[^ >]+

https://regex101.com/r/1wQz5E/1/

作品:

(?:(?<=<\/)|(?<=<))[^ >\/]+

https://regex101.com/r/1wQz5E/2

期望从匹配中省略结束标记的正斜杠。

我正在努力思考为什么我的第一个示例无法按预期运行。


源字符串:

<!DOCTYPE html>

<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title></title>
</head>
<body>
    <a></a>
</body>
</html> 

首先,您的第一个正则表达式可以缩短为 (?<=<\/|<)[^ >]+,使用更短的模式更容易。

请记住,正则表达式引擎从左到右解析字符串。 (?<=<\/|<) 后视匹配 位置紧接在 </< 之前,即 <</.

之后的位置

当输入为</a>时,<之后的位置首先用(?<=<\/|<)模式找到(因为它比[=15=之后的位置更靠左) ]), 然后 [^ >]+ 消耗 / 和它可以匹配的所有内容,并且 [^ >]+ 可以 匹配 /<, lookbehind 模式匹配的所有字符。

交替中先匹配的获胜,this rule这里并没有被破坏,只是交替在这里恰好是非消耗模式,这一点也应该记住。

这里的经验法则是:交替,或未知长度量词,如 +/*/{1,x} 在 lookbehind 模式的末尾 紧随其后的消费模式也匹配那些尾随的 lookbehind 模式,使 lookbehind 模式匹配最少的字符,随后的消费模式往往会尽早开始抓取内容尽他们所能。

在这种情况下,最安全的解决方案 是使用 消耗 模式而不是无限长度的回溯以及 捕获组 以捕获匹配的所需部分:

(?:<\/|<)([^ >]+)
<\/?([^ >]+)

或者,在 PCRE/Onigmo/PyPi Python 正则表达式中,您也可以使用 \K:

<\/?\K[^ >]+

这里,非捕获(?:<\/|<)组或<\/?模式将匹配并消耗<</,而([^ >]+)捕获组将产生预期的子字符串,因为它没有机会在 < 之后立即获取文本。 \K 只是从整个匹配内存缓冲区中省略了到目前为止匹配的所有文本,因此返回的唯一文本是与 [^ >]+.

匹配的文本

但是,在这种情况下,您还可以通过要求与 [^ >]+ 匹配的第一个字符是除 /:

以外的字符来解决 lookbehinds 的问题
(?<=<|</)[^ >/][^ >]*

所以,基本上展开 [^ >/]+[^ >/][^ >]*

对于当前的示例,在这种模式中 (?:(?<=<\/)|(?<=<))[^ >]+ 从当前位置向后看,直接向左的只能是 </< 因此只有 1 个断言可以同时为真。

当它在 </title> 中遇到 < 时,第二个断言为真。当尝试匹配它不能时,由于 [^ >\/] 不允许 /,然后它移动到下一个位置,因为没有更多的模式要处​​理。

接下来是 </,第一个断言为真,[^ >\/]+ 可以匹配以下字符。

所以它没有产生预期匹配的原因是因为在这个模式中 (?:(?<=<\/)|(?<=<))[^ >]+ 后视是相同的,但是否定字符 class [^ >]+ 可以匹配</title>

中的/