鉴于下面的代码,我想匹配第一次form
出现。我发现?!
可以使用负前瞻来实现这一目标,但它不起作用。我的正则表达式有什么问题?
#test
$test = "<form abc> foo </form> <form gg> bar </form>";
$test =~ m/<form[^>]*abc[^>]*>(?!.*form>.*)form>/s;
print $&;
首先,在解释正则表达式之前:使用像HTML::TreeBuilder
创建文档树这样的模块,然后从那里获取您的信息。使用正则表达式解析 HTML 太容易出错,无法在现实世界中使用。
这是你的字符串:
"<form abc> foo </form> <form gg> bar </form>"
还有你的正则表达式(为了可读性而写扩展,与/x
标志一样):
<form [^>]* abc [^>]* > (?! .* form> .* ) form>
<form
找到文字字符序列时锚定
[^>]*
搜索多个非>
字符。最初它匹配 abc
abc
匹配文字字符序列abc
。但是因为正则表达式引擎当前看到 a >
它必须回溯,直到[^>]*
匹配
。
[^>]*
将不匹配,因为引擎看到>
>
匹配>
当表达式.* form .*
不匹配时,否定前瞻匹配。
将.*
消耗所有字符,直到字符串结束。
form>
导致引擎回溯直到.*
匹配foo </form> <form gg> bar </
。
什么都不匹配,.*
但这没关系。
所以前瞻成功,但它是负前瞻,所以断言失败。Regex 的最后一部分甚至不会被执行。
在我们的.*
例子中消耗了太多的字符。这称为贪心匹配。
非贪心匹配是用一个尾随的?
like编写的.*?
。此版本最初使用零个字符,并首先检查模式的下一部分。如果这不起作用,它会迭代地消耗另一个字符,直到匹配。
<form [^>]* > .*? </form>
在开始标签内,只>
允许使用非字符。在标签之间,允许任何字符。我们进行非贪婪匹配,因此第一个结束标记匹配并结束正则表达式。
但是,这个解决方案有点问题。宽容的 HTML 解析器不会在attr="val<u>e"
. 我们会。此外,第一个</form>
是匹配的,如果我们有嵌套表单,这是不可取的。虽然在这个用例中没有问题,但这个正则表达式在匹配<div>
s 等时完全没用。
Perl 正则表达式非常强大,允许您声明递归语法。内置语法有点笨拙,但我建议该Regexp::Grammars
模块轻松做到这一点。更好的是,只需使用已经存在的成熟的 HTML 解析器。
不鼓励使用$&
(and $`
and $'
),因为它使 perl 非常低效。这不会在一个小脚本中体现出来,但无论如何它的风格很糟糕。而是用括号将整个正则表达式括起来以捕获匹配项
m{ ( <form [^>]* > .*? </form> ) }
然后使用$1
.
本perlretut
教程可能是理解 Perl 正则表达式的一个很好的介绍。