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


Why this regex doesn't work?

我有这个正则表达式:

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 个空数组的数组,但如果我用这个站点测试它,它说结果是:

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
        )
)

怎么了?是配置问题吗?

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);

经过测试并正常工作。

编辑

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

  1. 您已使用双引号字符串声明了正则表达式。这通常会导致您意想不到的事情,并且并不完全明显,因为双引号字符串会在传递给 PCRE 之前插入某些转义序列本身。在您的情况下,这造成的问题是'1被解释为八进制字符定义(如此处定义),因此您的表达式中包含文字0x01(标题开头)字符,而不是您希望 PCRE 用作反向引用的'1字符串。

    我发现当我遇到这样的问题时,一个好的起点是简单地echo表达式进行筛选,以查看 PHP 如何插入您在脚本中声明的字符串。这是该特定问题的演示。

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

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

  • <'s*?img - 这里的非贪婪*毫无意义,因为's只匹配空格,下一个序列将是 alpha,只需<'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';

。顺便说一下,这很好用。

现在,我仍然认为即使考虑额外的代码,DOM 方法也更好,因为它可能会捕获我的正则表达式技能忘记的边缘情况。尽管不可否认,正则表达式似乎确实更快一些。