负回顾后断言正则表达式捕获问题

4

我尝试匹配电子邮件地址,但仅当它们没有以 "mailto:" 开头时。我尝试使用以下正则表达式:

"/(?<!mailto:)[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})/"

对这个字符串进行匹配: '<a href="mailto:someemail@domain.com">EMAIL</a> ... otheremail@domain.com '

我期望只捕获 'otheremail@domain.com',但我还收到了 'omeemail@domain.com' - 缺少了 's'。我想知道这里出了什么问题。难道在查询断言之后不能有一个普通的正则表达式吗?

我在 PHP 中的完整示例如下:

$testString = '<a href="mailto:someemail@domain.com">EMAIL</a>  ...   otheremail@domain.com ';
$pattern = "/(?<!mailto:)[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})/";
preg_match_all($pattern, $testString, $matches);
echo('<pre>');print_r($matches);echo('</pre>');

谢谢你!

1
您不想使用HTML解析器吗? - alex
你应该在正则表达式中转义“-” -> 例如 [_a-z0-9\-] - Peter
@Peter:不必要,因为它不是有效范围的一部分。 - alex
3个回答

5
因为在s之后有一个与您的正则表达式匹配的字符串someemail@domain.com,并且因为s几乎不是mailto:,所以匹配成功。在那里加入一个单词边界对大多数情况都有效:

更改为:

(?<!mailto:)

To:

(?<!mailto:)\b

顺便提一下:在示例中使用example.com,domain.com是由一家实际公司拥有的。


呃,这不是很明显吗?好的,我会做的 :P - Wrikken
\b可以防止电子邮件地址在单词中间开始。之前的someemail@domain.com是有效的,因为它前面有s而不是mailto: - Peter

2
它试图匹配"someemail@",但失败了,因为它紧接着前面有"mailto:",所以它尝试在"omeemail@"处匹配,这次成功了,因为它不是紧接着"mailto:"。

编辑:我认为将(?<!mailto:)改为(?!mailto:)效果最好。

@Wrikken:该正则表达式允许在电子邮件地址中使用".",但如果你使用(?<!mailto:)\b,那么"mailto:some.email@"将从"email@"中被匹配。

嗯...没错,\b不是很完美的解决方案。但是为什么你建议切换到负向先行断言呢?它也不是完美的。我猜最好使用某些替代方案而不是\b。 - boryn
你能举一个负向先行断言失败的例子吗? - MRAB
1
@MRAB 嗯,你说得对,我有点匆忙,而且现在已经很晚了... 总的来说,我们当然可以要求它以 (^|\s|>) 开头,但是我认为 Alex 的原始评论关于使用 HTML 解析器变得越来越有吸引力。 - Wrikken
@Wrikken:我同意。正则表达式并不总是最好的工具。 - MRAB
@Wrikken: 我没包括 |^ ,因为在 mailto: 后面,它不能出现在开头。 - boryn
显示剩余5条评论

0

所以在@Wrikken和@MRAB的提示下,我们得出了最终可用的正则表达式:
"/(?<!mailto:)(?<=^|[^A-Za-z0-9_.+@-])[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})/"

重要的是要使用一个前瞻作为“电子邮件边界”来跟随负回顾。


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