【问题标题】:Regex replace text but exclude when text is between specific tag正则表达式替换文本但在文本位于特定标签之间时排除
【发布时间】:2012-09-19 10:44:01
【问题描述】:

我有以下字符串:

Lorem ipsum Test dolor sit amet, consetetur sadipscing elitr, sed diam nonumy <a href="http://Test.com/url">Test</a> eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd sed Test dolores et ea rebum. Stet clita kasd gubergren, no sea <a href="http://url.com">Test xyz</a> takimata sanctus est Lorem ipsum dolor sit amet.

现在我将替​​换标签之外的字符串“Test”而不是标签之间的字符串(例如,替换为“1234”)。

Lorem ipsum 1234 dolor sit amet, consetetur sadipscing elitr, sed diam nonumy <a href="http://Test.com/url">Test</a> eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd sed 1234 dolores et ea rebum. Stet clita kasd gubergren, no sea <a href="http://url.com">Test xyz</a> takimata sanctus est Lorem ipsum dolor sit amet.

我从这个正则表达式开始:(?!&lt;a[^&gt;]*&gt;)(Test)([^&lt;])(?!&lt;/a&gt;)

但是有两个问题没有解决:

  1. 标签中的文本“测试”也被替换(例如&lt;a href="http://Test.com/url"&gt;
  2. 标签之间的文本是否与搜索的文本不完全匹配,也会被替换(例如&lt;a href="http://url"&gt;Test xyz&lt;/a&gt;

我希望有人有办法解决这个问题。

【问题讨论】:

    标签: regex


    【解决方案1】:

    回答

    使用

    (Test)(?!(.(?!<a))*</a>)
    

    说明

    让我提醒你一些符号的含义:

    1) ?!negative lookahead,例如r(?!d) 选择所有r 后面没有直接跟d 的@:

    2) 因此,永远不要在没有字符的情况下开始负前瞻。只是(?!d) 没有意义:

    3) ? 可以用作惰性匹配。例如.+E 会选择

    123EEE

    整个字符串123EEE。但是,.+?E 根据需要选择尽可能少的“任何字符”(.+)。它只会选择123E

    答案:

    Protist 的答案是你应该使用(?!&lt;a[^&gt;]*?&gt;)(Test)(?![^&lt;]*?&lt;/a&gt;)。让我先解释一下如何缩短它。

    如 2) 中所述,在比赛前进行前瞻是没有意义的。所以以下相当于原生答案:

    (Test)(?![^<]*?</a>)
    

    同样由于&lt;不被允许,懒惰匹配?是多余的,所以它也等价于

    (Test)(?![^<]*</a>)
    

    这将选择所有 Test 后面没有 &lt;/a&gt; 且两者之间没有符号 &lt;。这就是出现在任何&lt;a ...&gt; .. &lt;/a&gt; 之前或之后的Test 将被替换的原因。

    但是,请注意

    Lorem Test dolor <a href="http://Test.com/url">Test <strong>dolor</strong></a> eirmod
    

    将改为

    Lorem 1234 dolor <a href="http://1234.com/url">1234 <strong>dolor</strong></a> eirmod 
    

    为了捕捉到你可以将你的正则表达式更改为

    (Test)(?!(.(?!<a))*</a>)
    

    执行以下操作:

    选择每个单词Test 后面没有字符串***&lt;/a&gt;,其中*** 中的每个字符后面都没有&lt;a

    请注意,点 . 很重要(参见 2))。

    请注意,像 (Test)(?!(.(?!&lt;a))*?&lt;/a&gt;) 这样的惰性匹配是不相关的,因为嵌套链接在 HTML4 和 HTML5 中是非法的(比如 &lt;a href="#"&gt;..&lt;a href="#"&gt;...&lt;/a&gt;..&lt;/a&gt;)

    原生生物说

    此外,不建议在原始 HTML 上使用正则表达式。

    我同意这一点。一个问题是,如果标签没有关闭或打开,就会导致问题。例如这里提到的所有解决方案都会改变

    Lorem Test dolor Test <strong>dolor</strong></a> eirmod
    

    Lorem Test dolor Test <strong>dolor</strong></a> eirmod 1234 dolores sea 1234 takimata 
    

    【讨论】:

    • 这是一个绝妙的解决方案。我会在 (Test)(?!(.(?!<a ))*<\/a>)
    • 这是一个解决方案,也是一个很好的解释。你的赢家!!!
    【解决方案2】:
    (?!<a[^>]*?>)(Test)(?![^<]*?</a>)
    

    与 zb226 相同,但使用惰性匹配进行了优化

    此外,不建议在原始 HTML 上使用正则表达式。

    【讨论】:

    • 我还添加了 \b 标志以匹配单词边界: (?!]*?>)(\bTest\b)(?![^)
    • 这应该给正则表达式优化器更多的工作。只要_Test_, _Test, or Test_ 不在您的文档中,它也不会对您的匹配产生不利影响(假设如果它们存在,您不会关心匹配它们)。
    • Test 之前的lookaheaded 和lazy match 是没有意义的。看我的回答。
    • 这不适用于 regexpr.com 为什么会被接受?
    【解决方案3】:

    这应该可以解决问题:

    (?!<a[^>]*>)(Test)(?![^<]*</a>)
    

    自己试试on regexr.

    跟进:正如Adam解释above,第一部分没有效果,可以完全放弃:

    (Test)(?![^<]*</a>)
    

    【讨论】:

    • 比赛前先看是没有意义的
    • @Adam 这当然是正确的,感谢您的提醒:)
    • 这不适用于 regexpr.com 为什么会被接受?
    • @Rualark:a)这个答案不被接受,b)我不知道它在一些我从未听说过的正则表达式页面上“不起作用”这一事实的严重性,这会立即使我公司的网络防火墙因恶意内容而跳闸。
    • @Adam 重新审视这一点后,我发现模式开头的前瞻确实至关重要。这很奇怪,因为我记得当时测试了你的说法,而且它是真的!打算为此想出一个解释。
    【解决方案4】:

    复活这个古老的问题,因为它有一个没有被提及的简单解决方案。

    关于使用正则表达式解析 html 的所有免责声明,这里有一个简单的方法。

    Perl / PCRE 方法

    <a[^>]*>[^<]*<\/a(*SKIP)(*F)|Test
    

    demo

    一般解决方案

    <a[^>]*>[^<]*<\/a|(Test)
    

    在这个版本中,要替换的文本被捕获在第 1 组中,并通过简单的回调或 lambda 执行替换。

    demo

    参考

    1. How to match pattern except in situations s1, s2, s3
    2. 有关代码实现,请参见How to match a pattern unless... 中的代码示例

    【讨论】:

    • 对我来说最重要的部分是知道$replaced = preg_replace_callback( $regex, function($m) { if(empty($m[1])) return $m[0]; else return "Superman";}, $subject);。所以如果m[1] 为空,我需要返回m[0]。真的很高兴知道。谢谢!
    【解决方案5】:

    调整@protist 提出的解决方案,在这种情况下搜索短语并排除脚本标记内的任何匹配项:

    (?!<script[^>]*?>)(\bTest Phrase\b)(?![^<]*?<\/script>)
    

    Demo

    Adam 提供的答案虽然更简洁,但执行起来需要更长的时间。这可以通过编辑此评论中已经提到的演示来证明。

    【讨论】:

    • 你在回答什么问题?
    • 原始问题提到“当文本在特定标签之间时”,我的回答只是在有人需要匹配短语而不是单个单词的情况下扩大解决方案.
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多