为什么这个正则表达式不起作用?

时间:2012-06-08 12:25:21

标签: php regex

我有这个正则表达式:

preg_match_all("/<\s*?img\s[^>]*?src=([\"']??)([^\"' >]*?)\1[^>]*?>/si", $content, $m);

我们的想法是在一段HTML中找到所有图像链接。鉴于此内容:

<p>
    <img alt="" src="/emailimg/interdigital_old.jpg" style="width: 377px; height: 245px; " />Some text here.</p><a href="site.html">test</a>

执行正则表达式后,$ m是一个包含3个空数组的数组,但如果我用this site测试它,结果是:

Array
(
    [0] => Array
        (
            [0] => <img alt="" src="/emailimg/interdigital_old.jpg" style="width: 377px; height: 245px; " />
        )

    [1] => Array
        (
            [0] => "
        )

    [2] => Array
        (
            [0] => /emailimg/interdigital_old.jpg
        )

)

有什么问题?这是配置问题吗?

1 个答案:

答案 0 :(得分:4)

DOM / XPath(即正确)方式:

<?php

  $html = '
<p>
    <img alt="" src="/emailimg/interdigital_old.jpg" style="width: 377px; height: 245px; " />Some text here.</p><a href="site.html">test</a>
';

  $dom = new DOMDocument('1.0');
  $dom->loadHTML($html);

  $xpath = new DOMXPath($dom);

  $links = array();
  foreach ($xpath->query('//img/@src') as $img) $links[] = $img->value;
  print_r($links);

Tested and working

修改

你的正则表达式不起作用的原因有两个:

  1. 您已使用双引号字符串声明了正则表达式。这通常会导致您不期望并且不完全明显的事情,因为双引号字符串将在传递给PCRE之前插入某些转义序列。在您的情况下导致的问题是\1被解释为八进制字符定义(定义为here),因此您的表达式具有文字0x01(标题的开头)字符在其中,而不是您希望PCRE用作后向参考的\1字符串。

    我发现当我遇到这样的问题时,一个好的起点就是简单echo屏幕上的表达式,以查看PHP如何插入您在脚本中声明的字符串。 Here是对该特定问题的证明。

  2. ([\"']??) - 第二个问号是打破它。我真的不确定你要用这个来完成什么,这只是一个错误的类型吗?我很难确定PCRE究竟是如何解释这一点的,以及为什么它会破坏它,但足以说它确实如此,第二个问号需要去。 FTR,它具有的效果是表达式仍然匹配<img>标签,但是下面的捕获组(您实际想要的数据)是空的。

  3. 现在让我们分解正则表达式,看看如何改进它:

    • <\s*?img - 这里的非贪婪*毫无意义,因为\s只匹配空格,下一个序列将是alphas,只需<\s*img即可。我实际上并不确定HTML标记是否允许在开头<和标记名称之间有前导空格,但我认为它不会造成任何伤害,因为正确的解析器可能会。
    • \s[^>]*?src=(["']??) - 如前所述,捕获组中的??打破了表达式,我不确定您首先尝试使用它做什么。另外,我认为非贪婪的*毫无意义,因为标记将以>结尾,如果我们还没有找到src,那么反正不是一场比赛。另外,如果我们允许在不应该是解析器可能允许的地方使用空格,我们应该允许它在=附近。我将其重写为\s[^>]*src\s*=\s*(["']?)
    • ([^"' >]*?)\1 - 假设您担心能够处理不带引号的属性,请不要在此处投诉。当然,如果你知道将始终引用属性,你可以简单地使用([^\1]*?)\1并从前面的捕获组中删除?,我们确定使用的报价类型
    • [^>]*?> - 此处没有投诉。
    • /si - s修饰符毫无意义,因为表达式中的任何位置都没有.。它没有任何伤害,但它也没有帮助,所以它是多余的。

    所以,把所有这些放在一起,在这里我将如何编写正则表达式:

    /<\s*img\s[^>]*src\s*=\s*(["']?)([^"' >]*?)\1[^>]*>/i
    

    ...当转换为带有正确转义引号的PHP字符串声明时,如下所示:

    $expr = '/<\s*img\s[^>]*src\s*=\s*(["\']?)([^"\' >]*?)\1[^>]*>/i';
    

    ......顺便说一下works nicely

    现在,我仍然认为即使考虑额外的代码,DOM方法也更好,因为它可能会捕获我的正则表达式技巧已经忘记的边缘情况。虽然可以肯定正则表达式确实是somewhat faster