这是一个执行 preg_replace
的示例多次查找嵌套/重叠的匹配项:
$text = '[foo][foo][/foo][/foo]';
//1st: ^^^^^ ^^^^^^
//2nd: ^^^^^ ^^^^^^
//3rd: fails
do {
$text = preg_replace('~\[foo](.*?)\[/foo]~', '[bar]$1[/bar]', $text, -1, $replace_count);
} while ($replace_count);
echo $text; //'[bar][bar][/bar][/bar]'
我对结果和行为感到满意。但是,如上例所示,将整个字符串扫描 3 次似乎效率很低。是否有任何正则表达式魔术可以在单个替换中执行此操作?
条件:
- 我不能简单地替换
~\[(/)?foo]~
与[$1bar]
,我需要确保有一个匹配的关闭[/foo]
打开后标记[foo]
标记并一次更换它们。它们是否嵌套并不重要。未配对[foo]
和[/foo]
不应被替换。
在 JS 中我可以设置 Regex 对象的 lastIndex
属性添加到匹配的开头,以便它从上一个匹配的开头再次开始匹配。我找不到任何 startIndex
在 PHP 中替换正则表达式并使用 substr()
的选项ing 也可能效率低下。我四处查看了 PCRE 是否会有“在这个位置开始下一场比赛”或类似的 anchor ,但我没有运气。
有没有更好的方法?
为了澄清未配对的标签,给定输入:
[foo][foo][/foo]
我对 [bar][foo][/bar]
都满意或 [foo][bar][/bar]
作为输出。前者是遗留行为。
最佳答案
对于这种特定情况,完整的正则表达式解决方案是不可能的。
您的解决方案适用于匹配配对标签(常识):
$pattern = '~\[foo]((?>[^[]++|\[(?!/?foo]))*)\[/foo]~';
$result = $text;
do {
$result = preg_replace($pattern, '[bar]$1[/bar]', $result, -1, $count);
} while ($count);
另一种只解析一次字符串的方法:
$arr = preg_split('~(\[/?foo])~', $text, -1, PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY);
$stack = array();
foreach ($arr as $key=>$item) {
if ($item == '[foo]') $stack[] = $key;
else if ($item == '[/foo]' && !empty($stack)) {
$arr[array_pop($stack)] = '[bar]';
$arr[$key] = '[/bar]';
}
}
$result = implode($arr);
第二个脚本的性能与深度无关。
要回答标题问题,是的,可以找到与单个正则表达式重叠的匹配项,但是,您不能使用这种模式执行替换,例如:
$pattern = '~(?=(\[foo]((?>[^[]++|\[(?!/?foo)|(?1))*)\[/foo]))~';
preg_match_all($pattern, $text, $matches);
诀窍是使用前瞻和捕获组。请注意,整个匹配始终是一个空字符串,这就是为什么不能将此模式与 preg_replace 一起使用的原因。
关于php - 是否有可能找到与单个正则表达式重叠的匹配项?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22121286/