【问题标题】:PHP - ungreedy regular expression still a little 'greedy'PHP - 不贪婪的正则表达式仍然有点“贪婪”
【发布时间】:2019-07-26 12:28:56
【问题描述】:

对于我的 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

我同时使用不贪婪和“懒惰”的常规异常,结果相同。我需要在正则表达式中明确定义第二个 [/?],但没有成功。

【问题讨论】:

  • 问题是即使是非贪心搜索也能从左到右工作。看到[?] 后,贪婪说“我可以从这里采取最长的延续”,非贪婪说“最短”,但都没有说“有没有以后的[?] 我可以从这里开始?”因此,您需要将.* 替换为与下一个[?] 不匹配的东西,这不是微不足道的,但应该是可能的。
  • 如果[] 在文本中被允许但必须被转义,用([^][]|\\\[|\\\]) 替换.* 会起作用。演示:regex101.com/r/Cgx19h/2 但是,如果您通常允许使用方括号,我不确定该怎么做。
  • 也许使用 2 次回火的贪婪令牌 regex101.com/r/2UWj23/1
  • @Axis 我不知道出了什么问题 - regex101.com/r/Cgx19h/6

标签: php regex regex-negation


【解决方案1】:

对于您当前的示例数据,如果您想匹配从 [?][/?] 之间不能有 [?] 并且必须有 %empty%,您可以使用 tempered greedy token

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

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

说明

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

Regex demo

编辑

@Casimir et Hippolyte 建议使用Unrolled Star Alternation Solution 方法的性能更高的模式:

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

说明

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

Regex demo

【讨论】:

  • Speechless。感谢您的完美解决方案和学习点。
  • 第二个否定前瞻应该是:(?!\[/\?\])(根据你的解释)。就个人而言,我更喜欢这样写:regex101.com/r/0C8DQe/2
  • @CasimiretHippolyte 太棒了!但我的意思是在第二个负面环视中匹配\[\?\],而不是在两者之间匹配[?]。我选择了this approach,我认为您选择了this approach 我对您的版本进行了细微更改,以与regex101.com/r/6H1DYc/1 之间的[?] 不匹配如果您同意并征得您的许可,我可以将其添加到答案中或执行你要发布吗?
  • 我这么说是因为你在解释中写道:断言右边的不是[/?]。抱歉,在我之前链接的模式中,这显然是 [ 而不是 {。关于方法,更多的是下一章Unrolled Star Alternation Solution。随意添加或不添加到答案中,我不会发布答案。
  • @CasimiretHippolyte 我现在明白你的意思了,我已经更新了。感谢您的反馈,非常感谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-04
  • 2011-09-01
相关资源
最近更新 更多