PHP 正则表达式 preg_match_all 在当前匹配之前重复匹配的单词

PHP RegEx preg_match_all Reiterate Matched Words Before the Current Match

我有以下 RegEx 代码

$str = 'word1 word2 word3 keyword word4 word5 word6 keyword word7 word8 word9 word10';
$matches = array();
preg_match_all('/(\w* ){1,3}keyword( \w*){1,3}/u', $str, $matches);

我希望比赛包括:

word1 word2 word3 keyword word4 word5 word6

word4 word5 word6 keyword word7 word8 word9

但实际上,我得到了这些:

word1 word2 word3 keyword word4 word5 word6

keyword word7 word8 word9

换句话说,由于第一个匹配,第二个匹配被裁剪。

这是一个测试: https://regex101.com/r/EPp14b/1/

如果您不想交叉单词 keyword,您可以在重复 1-3 个单词时使用否定前瞻来断言它们不是关键字。

匹配后,您可以使用带捕获组的正向先行断言,匹配 1-3 个再次不是 keyword

的词

句子将是完整匹配项和组 1 的串联。

(?<!\S)(?:(?!keyword\b)\w+\h+){1,3}keyword\b(?=((?:\h+(?!keyword\b)\w+){1,3}))

模式匹配:

  • (?<!\S) 断言左侧空白边界
  • (?:非捕获组
    • (?!keyword\b)\w+\h+ 否定前瞻,如果不是 keyword
    • ,则匹配一个单词和空格
  • ){1,3}关闭非捕获组重复1-3次
  • keyword\b 匹配 keyword
  • (?= 正面前瞻
    • ( 捕获 组 1
      • (?:\h+(?!keyword\b)\w+){1,3}匹配1-3个不以keyword
      • 开头的单词
    • ) 关闭组 1
  • ) 关闭前瞻

Regex demo | Php demo

$re = '/(?<!\S)((?:(?!keyword\b)\w+\h+){1,3}keyword\b)(?=((?:\h+(?!keyword\b)\w+){1,3}))/u';

$strings = [
    "word1 word2 word3 keyword word4 word5 word6 keyword word7 word8 word9 word10",
    "word2 keyword word4 word5 word6 keyword word7 word8",
    "word2 word3 keyword word4 word5 word6 keyword word7 keyword word10",
];

foreach ($strings as $str) {
    preg_match_all($re, $str, $matches, PREG_SET_ORDER, 0);
    $matches = array_map(function($m) {
        return $m[1] . $m[2];
    }, $matches);
    print_r($matches);
}

输出

Array
(
    [0] => word1 word2 word3 keyword word4 word5 word6
    [1] => word4 word5 word6 keyword word7 word8 word9
)
Array
(
    [0] => word2 keyword word4 word5 word6
    [1] => word4 word5 word6 keyword word7 word8
)
Array
(
    [0] => word2 word3 keyword word4 word5 word6
    [1] => word4 word5 word6 keyword word7
    [2] => word7 keyword word10
)

另一种选择是将完整匹配放入前瞻中的捕获组中,以便能够获得重叠匹配:

(?=((\b(?:\w+\h+){1,3}keyword)(?:\h+\w+){1,3}))(?2)

RegEx Demo

代码:

$s = 'word1 word2 word3 keyword word4 word5 word6 keyword word7 word8 word9 word10';
$re = '/(?=((\b(?:\w+\h+){1,3}keyword)(?:\h+\w+){1,3}))(?2)/u';
preg_match_all($re, $s, $m);
print_r($m[1]);

/* Output
Array
(
    [0] => word1 word2 word3 keyword word4 word5 word6
    [1] => word4 word5 word6 keyword word7 word8 word9
)
*/

正则表达式详细信息:

  • (?=: 开始前瞻
    • (: 开始捕获组#1
      • (: 开始捕获组#2
        • \b: 字边界
        • (?:\w+\h+){1,3}: 匹配1到3个词
        • keyword:
      • ): 结束捕获组#2
      • (?:\h+\w+){1,3}: 匹配1到3个词
    • ): 结束捕获组#1
  • ):结束先行
  • (?2):递归捕获组#2

您需要的整个部分(关键字 + 周围的词)都在前瞻断言中的捕获组(结果)内,这样字符就不会被消耗,并且可以成为稍后最终下一次匹配的一部分。 但是为了避免多次匹配同一个关键词,需要到达这个之后的位置,消耗所有的字符,直到包含这个关键词。这就是为什么我定义了一个名为 consume 的组并且我引用了他的内容:\g{consume}.

$pattern = '~
\b
(?=
    (?<result>
        (?<consume>
           (?> \w+ \h+ ){0,3}?
           keyword \b
        )
        (?: \h+ (?! keyword \b ) \w+ ){0,3}
    )
) \g{consume}
~ux';

demo

使用此模式,您不必重新构造结果,所有结果都存储在命名组 result:

preg_match_all($pattern, $str, $matches);

print_r($matches['result']);