为什么这两个正则表达式不一样?
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>
中的/
我最近回答了 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
只是从整个匹配内存缓冲区中省略了到目前为止匹配的所有文本,因此返回的唯一文本是与 [^ >]+
.
但是,在这种情况下,您还可以通过要求与 [^ >]+
匹配的第一个字符是除 /
:
(?<=<|</)[^ >/][^ >]*
所以,基本上展开 [^ >/]+
和 [^ >/][^ >]*
。
对于当前的示例,在这种模式中 (?:(?<=<\/)|(?<=<))[^ >]+
从当前位置向后看,直接向左的只能是 </
或 <
因此只有 1 个断言可以同时为真。
当它在 </title>
中遇到 <
时,第二个断言为真。当尝试匹配它不能时,由于 [^ >\/]
不允许 /
,然后它移动到下一个位置,因为没有更多的模式要处理。
接下来是 </
,第一个断言为真,[^ >\/]+
可以匹配以下字符。
所以它没有产生预期匹配的原因是因为在这个模式中 (?:(?<=<\/)|(?<=<))[^ >]+
后视是相同的,但是否定字符 class [^ >]+
可以匹配</title>
/