PHP - 不贪婪的正则表达式还有点'greedy'

PHP - ungreedy regular expression still a little 'greedy'

对于我的 CMS,我需要替换 [?][/?] 标签之间的多行内容,如果它包含字符串 %empty%,如果没有找到 %empty% 标记则保持不变。

$a='
[?]<h1>%empty%</h1>
<p>text</p>
[/?]  
text        
[?]<h1>%empty%</h1>
<p>text</p>
[/?]  
text';

$r= preg_replace (
  '/(\[\?\]).*?%empty%.*?(\[\/\?\])/s',           
  "REPLACED",   
  $a )   ;
echo $r;

正确结果:

REPLACED  
text        
REPLACED  
text        

它几乎适用于所有组合,除非 first 行不匹配。在这种情况下,将替换第一个 [?] 和最后一个 [/?]

之间的所有内容
$a='
[?]<h1>%!empty%</h1>
<p>text</p>
[/?]  
text        
[?]<h1>%empty%</h1>
<p>text</p>
[/?]  
text';

Wrong result:

REPLACED  
text        

Expected:

[?]
<h1>%!empty%</h1>
<p>test</p>
[/?] 
text  
REPLACED  
text

我同时使用了非贪婪异常和 'lazy' 常规异常,结果相同。我想我需要在正则表达式中明确定义第二个 [/?],但没有成功。

对于您当前的示例数据,如果您要匹配从 [?][/?] 之间不能有 [?] 而必须有 %empty%,你可以使用 .

使用 /s 修饰符使点匹配换行符:

\[\?\](?:(?!\[/?\?\]).)*%empty%(?:(?!\[\?\]).)*\[/\?]

说明

  • \[\?\] 匹配 [?]
  • (?:非捕获组
    • (?!\[/?\?\]).断言右边的不是[?][/?]。然后匹配任何字符。
  • )*关闭非捕获组并重复0+次
  • %empty%字面匹配
  • (?:非捕获组
    • (?!\[\?\]).断言直接右边的不是[?]。然后匹配任何字符。
  • )*关闭非捕获组并重复0+次
  • \[/\?] 匹配 [/?]

Regex demo

编辑

@Casimir et Hippolyte suggests a more performant pattern using a Unrolled Star Alternation Solution方法:

\[\?\][^[%]*+(?:\[(?!\?])[^[%]*|%(?!empty%)[^[{%]*)*+%empty%[^[]*+(?:\[(?!/?\?])[^[]*)*+\[/\?]

说明

  • \[\?\] 匹配 [?]
  • [^[%]*+ 否定字符 class,匹配除 [ ] %
  • 之外的任何字符
  • (?:非捕获组
    • \[(?!\?])匹配[,断言直接右边的不是?]
    • [^[%]*如果是这种情况,匹配 0+ 次除 [ %
    • 之外的任何字符
    • |
    • %(?!empty%)匹配%,断言直接右边的不是empty%
    • [^[{%]* 如果是这种情况,匹配 0+ 次除 [ {
    • 之外的任何字符
  • )*+ 关闭非捕获组并使用 possessive quantifier
  • 重复 0+ 次
  • %empty%[^[]*+ 匹配 %empty% 和 1+ 次除 [ ]
  • 之外的任何字符
  • (?:非捕获组
    • \[(?!/?\?])匹配[,断言直接右边的不是可选的/?]
    • [^[]* 如果是这种情况,匹配 0+ 次除 [
    • 之外的任何字符
  • )*+关闭非捕获组并重复0+次
  • \[/\?] 匹配 [/?]

Regex demo