【问题标题】:PHP Regular Expression - Repeating Match of a GroupPHP 正则表达式 - 组的重复匹配
【发布时间】:2010-02-06 11:34:32
【问题描述】:

我有一个可能看起来像这样的字符串:

$r = 'Filed under: <a>Group1</a>, <a>Group2</a>';

这是我目前使用的正则表达式:

preg_match_all("/Filed under: (?:<a.*?>([\w|\d|\s]+?)<\/a>)+?/", $r, $matches);

我希望() 中的正则表达式继续匹配最后指定的+?。但它只是不会这样做。 ::叹气::

任何想法。我知道必须有一种方法可以在一个正则表达式中执行此操作,而不是将其分解。

【问题讨论】:

    标签: php regex


    【解决方案1】:

    只是为了好玩,这里有一个正则表达式,它可以与单个 preg_match_all 一起使用:

    '%(?:Filed under:\s*+|\G</a>)[^<>]*+<a[^<>]*+>\K[^<>]*%`
    

    或者,以更易读的格式:

    '%(?:
          Filed under:   # your sentinel string
        |                
          \G             # NEXT MATCH POSITION
          </a>           # an end tag
      )
      [^<>]*+          # some non-tag stuff     
      <a[^<>]*+>       # an opening tag
      \K               # RESET MATCH START
      [^<>]+           # the tag's contents
    %x'
    

    \G 匹配下一次匹配尝试开始的位置,这通常是上一次成功匹配结束的位置(但如果上一次匹配的长度为零,它会再向前碰撞一次)。这意味着正则表达式不会匹配以&lt;/a&gt; 开头的子字符串,直到之后它至少匹配一个以Filed under: 开头的子字符串。

    在匹配标记字符串或结束标记后,[^&lt;&gt;]*+&lt;a[^&lt;&gt;]*+&gt; 会消耗直到并包括下一个开始标记的所有内容。然后\K 欺骗开始位置,因此匹配(如果有)似乎在&lt;a&gt; 标记之后开始(这就像一个积极的后视,但更灵活)。最后,[^&lt;&gt;]+ 匹配标签的内容并将匹配位置带到结束标签,以便\G 可以匹配。

    但是,正如我所说,这只是为了好玩。如果您没有必须在一个正则表达式中完成这项工作,那么最好使用像@codaddict 那样的多步骤方法;它更易读、更灵活、更易于维护。

    \K reference
    \G reference

    编辑:虽然我给出的参考资料是针对 Perl 文档的,但这些功能 也受到 PHP 的支持——或者更准确地说,是 PCRE 库支持的。我认为 Perl 文档要好一些,但您也可以在 PCRE manual 中阅读有关这些内容。

    【讨论】:

    • 我不知道\K。有趣的!关于\G 的一个小注释——你指的是“上一场比赛”,这很好,而“下一场比赛”有点令人困惑(特别是当你链接到的 Perl 示例完全具有误导性时——它设置代码中的下一个位置 - 与默认行为非常不同)。简单地说 - \G 指的是当前匹配尝试开始的位置。它也不准确 &lt;/a&gt; 将始终在 Filed under: 之后匹配 - 它也可以匹配字符串的开头,例如 @987654344 @:ideone.com/aTjrm.
    • (顺便说一句,我是从这里来的:stackoverflow.com/questions/5982451/…
    • @Kobi:我应该省略关于零长度匹配的部分;噪音太大,信号不够。通常我只会说\G 匹配上一场比赛结束的位置,除非它们与当前问题相关,否则不要打扰这些琐碎的细节。我的意思是,字符串以&lt;/a&gt; 开头的几率是多少?我对那个感到很安全。 ;)
    【解决方案2】:

    试试:

    <?php
    
    $r = 'Filed under: <a>Group1</a>, <a>Group2</a>, <a>Group3</a>, <a>Group4</a>';
    
    if(preg_match_all("/<a.*?>([^<]*?)<\/a>/", $r, $matches)) {
        var_dump($matches[1]); 
    }
    
    ?>
    

    输出:

    array(4) {
      [0]=>
      string(6) "Group1"
      [1]=>
      string(6) "Group2"
      [2]=>
      string(6) "Group3"
      [3]=>
      string(6) "Group4"
    }
    

    编辑:

    由于您想在搜索中包含字符串 'Filed under' 以唯一标识匹配项,您可以试试这个,我不确定是否可以通过一次调用 preg_match 来完成

    // Since you want to match everything after 'Filed under'
    if(preg_match("/Filed under:(.*)$/", $r, $matches)) {
        if(preg_match_all("/<a.*?>([^<]*?)<\/a>/", $matches[1], $matches)) {
            var_dump($matches[1]); 
        }
    }
    

    【讨论】:

    • 谢谢,但我真的需要使用“归档于:”标志。虽然我的示例文本很简陋,但我正在解析的实际文件非常复杂,并且归档在:确实是我必须使用的唯一唯一标识符。还好在文件末尾,所以我可以一直匹配到末尾。
    【解决方案3】:
    $r = 'Filed under: <a>Group1</a>, <a>Group2</a>'
    $s = explode("</a>",$r);
    foreach ($s as $k){
        if ($k){
            $k=explode("<a>",$k);
            print "$k[1]\n";
        }
    }
    

    输出

    $ php test.php
    Group1
    Group2
    

    【讨论】:

    【解决方案4】:

    我希望 () 内的正则表达式继续匹配 +? 指定的内容?最后。

    +? 是一个惰性量词 - 它会尽可能少地匹配 次。换句话说,只有一次。

    如果你想匹配多次,你需要一个贪婪的量词 - +

    另请注意,您的正则表达式不太有效 - 一旦遇到标签之间的逗号,匹配就会失败,因为您没有考虑到它。这可能需要纠正。

    【讨论】:

    • 对,我只尝试了 + 量词。那也失败了。而且我也确实考虑过 , [逗号] 恐怕我不知道如何设置它,因为第二个或第三个匹配项可能有也可能没有逗号。然而,我确实尝试了这个作为我的尝试:[code] preg_match_all("/Filed under: (?:([\w|\d|\s]+?).*? )+/", $r, $matches); [/code]
    • 嗯,cmets 看起来不太漂亮。
    • @Senica:您可以使用反引号来格式化 cmets 中的代码,就像在问题和答案中一样,但是如果代码很长或很复杂,您应该编辑您的问题并将其放在那里。您在上面包含的代码有点需要评论。
    • 但是@Anon。是对的:在正则表达式末尾不情愿的量词几乎没有意义。如果你的正则表达式是正确的,那么最后的 ? 会破坏它。
    猜你喜欢
    • 2020-04-09
    • 1970-01-01
    • 2013-05-06
    • 2018-01-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-24
    相关资源
    最近更新 更多