PHP正则表达式 - 重复匹配一个组

10

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

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

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

preg_match_all("/Filed under: (?:<a.*?>([\w|\d|\s]+?)<\/a>)+?/", $r, $matches);
我希望正则表达式在()内继续按照末尾的+?匹配,但它却无法实现。有什么想法吗?我知道必须有一种方法可以在一个正则表达式中完成,而不是分开来做。
4个回答

12

仅供娱乐,这里有一个正则表达式,可以在单个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匹配下一次匹配尝试开始的位置,通常是上一次成功匹配结束的地方(但如果上一次匹配长度为零,则向前移动一个)。这意味着正则表达式不会匹配以</a>开头的子字符串,直到至少匹配了一个以Filed under:开头的子字符串。

在匹配了哨兵字符串或结束标记之后,[^<>]*+<a[^<>]*+>会消耗掉直到下一个起始标记的所有内容。然后\K欺骗起始位置,使得匹配(如果有的话)看起来是在<a>标签之后开始的(就像一个正向后行断言,但更加灵活)。最后,[^<>]+匹配标签的内容,并将匹配位置提升到结束标记,以便\G可以匹配。

但是,正如我所说,这只是为了好玩。如果你不必使用一个正则表达式完成任务,那么你最好使用@codaddict使用的多步方法;它更易读、更灵活和更易维护。

\K 参考
\G 参考

编辑:虽然我提供的参考资料是关于Perl文档的,但这些功能也被PHP支持,更准确地说是由PCRE库支持。我认为Perl文档更好一些,但你也可以在PCRE手册中阅读有关这些内容的信息。


我不知道\K。有趣!关于\G的一个小注释-您提到了“前一个匹配”,这很好,还有“下一个匹配”,这有点令人困惑(特别是当您链接的Perl示例是完全误导性的-它设置了代码中的下一个位置-这与默认行为非常不同)。简单地说-\G指的是尝试启动当前匹配的位置。 </a>始终匹配于“Filed under:”后面并不准确-它也可以匹配字符串的开头,例如</a>,<a>Group2</a>:http://ideone.com/aTjrm。 - Kobi
(顺便提一句,我来自这里:http://stackoverflow.com/questions/5982451/regex-capturing-a-repeated-group/7135730#7135730) - Kobi
@Kobi:我应该省略有关零长度匹配的部分;太多噪音,不够清晰。通常我只说\G匹配上一个匹配结束的位置,除非这些琐碎的细节与当前问题相关,否则就不必费心了。我的意思是,字符串以</a>开头的可能性有多大呢?我对这个问题感到相当安全。 ;) - Alan Moore

8

尝试:

<?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]); 
    }
}

谢谢,但我真的需要使用“Filed under:”标志。虽然我的示例文本很简单,但我要解析的实际文件非常复杂,“Filed under:”确实是我唯一可以使用的唯一标识符。幸运的是,它在文件末尾,所以我可以一直匹配到末尾。 - Senica Gonzalez

2
$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

1
最好与否,取决于个人。如果可以不使用复杂的正则表达式来完成,那对我和维护者来说都是最好的选择。 - ghostdog74
正如我在上面的评论中解释的那样,我不能使用explode函数...首先,有些情况下没有逗号,只有一个组。其次,虽然我的示例很简单,但这是一个复杂的文件。 <a>标签也不是那么简单。第三,我需要Filed under:属性,使用explode函数肯定会返回不想要的值。 - Senica Gonzalez

1
我想让圆括号内的正则表达式继续按照末尾的+?进行匹配。
+?是一种懒惰量词-它将尽可能少地匹配。换句话说,仅匹配一次。
如果您想多次匹配,那么您需要贪婪量词-+。
还要注意,您的正则表达式不完全正确-一旦遇到标签之间的逗号,匹配就会失败,因为您没有考虑到它。这可能需要更正。

好的,我尝试过只使用+量词。但是这也失败了。我也考虑了逗号“,”,但不知道如何设置,因为第二个或第三个匹配项可能有逗号,也可能没有。不过,我尝试了以下代码: [code] preg_match_all("/Filed under: (?:<a.?>([\w|\d|\s]+?)</a>.?)+/", $r, $matches); [/code] - Senica Gonzalez
哦,注释看起来不太好看。 - Senica Gonzalez
@Senica:您可以使用反引号在评论中格式化代码,就像在问题和答案中一样,但如果代码很长或复杂,您应该编辑您的问题并将其放在那里。您上面包含的代码对于评论来说有点太多了。 - Alan Moore
但是@Anon.是正确的:在正则表达式的末尾使用一个勉强量词几乎没有意义。如果你的正则表达式本来就是正确的,那么最后的?会破坏它。 - Alan Moore

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