【问题标题】:Replacing all matches except if surrounded by or only if surrounded by替换所有匹配项,除非被包围或仅被包围
【发布时间】:2015-07-31 18:41:28
【问题描述】:

给定一个文本字符串(一个降价文档),我需要实现以下两个选项之一:

  • 替换文档中所有特定表达式 ((\W)(theWord)(\W)) 的所有匹配项除了标记图像语法 ![Blah theWord blah](url) 内的匹配项。

  • 替换markdown图像中特定表达式({{([^}}]+)}}\[\[[^\]\]]+\]\])ONLY的所有匹配项,即:![Blah {{theWord}}[[1234]] blah](url)。

无论是否在 markdown 图像语法中,这两个表达式当前都匹配所有内容,并且我已经尝试了所有我能想到的。

Here is an example of the first option

And here is an example of the second option

任何帮助和/或线索将不胜感激。

提前致谢!

【问题讨论】:

  • 网址不应包含空格。所以,最简单的方法是从第二个模式中删除它们
  • 实际上,我不需要对 url 做任何事情,问题是 避免 第一个选项的 ![...] 部分内的匹配...或获取 仅那些在![...] 中的部分用于第二个部分。我认为查看发布的示例会很清楚。
  • ![Blah 总是开始自己的行吗?
  • 是的,我们认为确实如此。

标签: php regex preg-replace


【解决方案1】:

好吧,我稍微修改了第一个表达式,因为我认为有一些额外的捕获组,然后通过添加 lookahead 技巧来制作它们:

-第一个(Live demo):

\b(vitae)\b(?![^[]*]\s*\()

-第二个(Live demo):

{{([^}}]+)}}\[\[[^\]\]]+\]\](?=[^[]*]\s*\()

前瞻部分解释:

(?!            # Starting a negative lookahead
    [^[]*]     # Everything that's between brackets
    \s*        # Any whitespace
    \(         # Check if it's followed by an opening parentheses  
)              # End of lookahead which confirms the whole expression doesn't match between brackets

(?= 表示正向前瞻

【讨论】:

  • 演示 1 需要 i 修饰符。不过很好的解决方案。
  • 这些解决方案是变通方法。如果有杂散的方括号或圆括号,则可能有问题。
  • @stribizhev 这是降价!所以它带有标准。
  • @stribizhev 这很正常!因为你无缘无故地搞砸了文本,我也可以破坏你的输入。
【解决方案2】:

您可以利用对这种情况非常有用的丢弃技术。它由以下模式组成:

patternToSkip1 (*SKIP)(*FAIL)|patternToSkip2 (*SKIP)(*FAIL)| MATCH THIS PATTERN

所以,根据你的需要:

替换文档中所有特定表达式 ((\W)(theWord)(\W)) 的所有匹配项,除了 Markdown 图像语法中的匹配项

您可以在pcre 到(*SKIP)(*FAIL) 标志中轻松实现此目的,因此对于您的情况,您可以使用这样的正则表达式:

\[.*?\](*SKIP)(*FAIL)|\bTheWord\b

或者使用你的模式:

\[.*?\](*SKIP)(*FAIL)|(\W)(theWord)(\W)

这个正则表达式背后的想法是告诉正则表达式引擎跳过[...]中的内容

Working demo

【讨论】:

  • 没错,SKIP-FAIL 是第一个选项的正确技巧。但是,您应该在 theWord 周围使用 \b,因为 (\W) 是多余的开销。
  • @stribizhev FAIL 是一个更易于阅读的替代负前瞻 (?!) 而不是魔法
  • @stribizhev 你说 SKIP-FAIL 是正确的技巧,我提到它只是一种替代方法。
  • @stribizhev Gosh 我不会在替代品之间进行比较,这就像那些无法与女朋友相处的男人一样:-你知道,她是一个更好的替代品。
  • @stribizhev 请避免在 cmets 中进行长时间的讨论。 好吧,我只想再说一件事……不要合理化!
【解决方案3】:

第一个正则表达式很容易用SKIP-FAIL trick 修复:

\!\[.*?\]\(http[^)]*\)(*SKIP)(*FAIL)|\bvitae\b

替换为您选择的单词。在 PHP (PCRE) 正则表达式中,这是一种完全有效的方式来匹配 outside 某些标记。

见Demo 1

至于第二个,它更难,但可以通过\G 实现,确保我们在内部连续匹配一些标记:

(\!\[.*?|(?<!^)\G)((?>(?!\]\(http).)*?){{([^}]+?)}}\[{2}[^]]+?\]{2}(?=.*?\]\(http[^)]*?\))

替换为$1$2{{NEW_REPLACED_TEXT}}[[NEW_DIGITS]]

见Demo 2

PHP:

$re1 = "#\!\[.*?\]\(http[^)]*\)(*SKIP)(*FAIL)|\bvitae\b#i";
$re2 = "#(\!\[.*?|(?<!^)\G)((?>(?!\]\(http).)*?){{([^}]+?)}}\[{2}[^]]+?\]{2}(?=.*?\]\(http[^)]*?\))#i";

【讨论】:

  • 为了在模式上更准确,对于您的第二个正则表达式,灾难性回溯很可能发生,我有一个问题:为什么是原子组?引擎显示 4 个匹配项 - 18720 步。顺便说一句,你不觉得你在扼杀表演吗?
  • 一旦你遇到这个正则表达式的灾难性回溯,请告诉我。我总是更担心准确性和稳定性。快速正则表达式与您需要的不匹配或过火有什么好处?
  • 就像] ( 之间有空格一样。 Markdown 解析器在保留符号中对空格进行转义,您介意回答为什么在这里使用原子组吗?
  • 其实](是部分的markdown。如果之间有空格,则必须将它们添加到模式中。如果 text 内有括号,而不是 markdown,正则表达式不会失败。此外,当我们对 backtracking positions remembered by any tokens inside the group 不感兴趣时​​,原子分组很有用。
  • 忘记回溯位置 - 据我所知 - 当您在原子组中有多个管道序列而您没有任何序列时很有用。您一次只检查一个表达式。所以完全没用。不是吗?
猜你喜欢
  • 2012-07-12
  • 2023-03-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-21
  • 2020-10-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多