这是一个多次执行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
属性设置为匹配的开头,以便从最后一次匹配的开头再次开始匹配。我在PHP中找不到正则表达式替换的任何startIndex
选项,并且使用substr()
ing也可能效率低下。我已经四处查看PCRE是否会为“在这个位置开始下一场比赛”或类似事件做出让步,但我没有运气。
有更好的方法吗?
在未配对的标签上澄清,给出输入:
[foo][foo][/foo]
我可以使用[bar][foo][/bar]
或[foo][bar][/bar]
作为输出。前者是遗留行为。
答案 0 :(得分:2)
对于这种特定情况,无法使用完整的正则表达式解决方案。
您的解决方案适合匹配配对标签(在常识中):
$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一起使用的原因。
答案 1 :(得分:1)
更好的方法是找到结束[/foo]
并回溯,直到找到开始[foo]
或[foo(space).*]
。将匹配区域替换为其他内容并继续执行,直到找不到结尾。但是使用常规strpos/stripos
或普通substr
,而不是regex
。
使用regex
可能是可以实现的,但我总是通过定期搜索来做这种事情,因为它也更快。