-2

鉴于下面的代码,我想匹配第一次form出现。我发现?!可以使用负前瞻来实现这一目标,但它不起作用。我的正则表达式有什么问题?

#test
$test = "<form abc> foo </form> <form gg> bar </form>";
$test =~ m/<form[^>]*abc[^>]*>(?!.*form>.*)form>/s;
print $&;
4

1 回答 1

7

首先,在解释正则表达式之前:使用像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 正则表达式的一个很好的介绍。

于 2012-08-19T22:19:00.247 回答