【问题标题】:Regex to match words or phrases in string but NOT match if part of a URL or inside <a> </a> tags. (php)正则表达式匹配字符串中的单词或短语,但如果是 URL 的一部分或在 <a> </a> 标记内则不匹配。 (php)
【发布时间】:2011-05-15 15:43:59
【问题描述】:

我知道正则表达式不适合与 HTML 字符串一起使用,我已经查看了 PHP Simple HTML DOM Parser,但仍然相信这是要走的路。所有的 HTML 标签都将由我的论坛软件生成,因此它们将是一致且有效的 HTML。

我想做的是制作一个插件,它会在 HTML 字符串中找到关键字(或短语)列表,并用我指定的链接替换它们。例如,如果有人键入:

I use Amazon for that.

它将替换为:

I use <a href="http://www.amazon.com">Amazon</a> for that.

问题当然是如果 URL 中有“amazon”,它也会被替换。我用这个网站上的回调函数解决了这个问题,稍作修改。

但现在我还有一个问题,它仍然会替换开始和结束标签之间的单词。

<a href="http://www.amazon.com">My Amazon Link</a>

它将匹配“我的亚马逊链接”中的“亚马逊”

我真正需要的是一个正则表达式,可以匹配除&lt;a href&lt;/a&gt; 之外的任何地方的“amazon”

有什么想法吗?

【问题讨论】:

  • 搜索一下这个问题已经被回答了无数次
  • fwiw 我进行了搜索,并查看了我输入主题时收到的每条建议。我可能措辞不佳,但我搜索了 2 天。
  • 只是一个后续。在测试中,我发现如果有人将图像标签 包含到亚马逊资源中,它也会尝试将 标签内的单词转换为链接。我也修改了正则表达式以忽略 标记(以及 XHTML 图像标记,实际上是所有 XHTML 标记): (?![^|" />))

标签: php html regex preg-replace


【解决方案1】:

不要这样做。无论您的 HTML 有多一致,您都无法使用 Regex 可靠地做到这一点。

然而,这样的事情应该可以工作:

<?php
$dom = new DOMDocument;
$dom->load('test.xml');
$x = new DOMXPath($dom);

$nodes = $x->query("//text()[contains(., 'Amazon')][not(ancestor::a)]");

foreach ($nodes as $node) {
    while (false !== strpos($node->nodeValue, 'Amazon')) {
        $word = $node->splitText(strpos($node->nodeValue, 'Amazon'));
        $after = $word->splitText(6);

        $link = $dom->createElement('a');
        $link->setAttribute('href', 'http://www.amazon.com');

        $word->parentNode->replaceChild($link, $word);
        $link->appendChild($word);

        $node = $after;
    }
}

$html = $dom->saveHTML();
echo $html;

这很冗长,但它确实有效。

【讨论】:

  • 当我有时间我会玩这个来学习 DOM。我实际上是在使用字符串数组代替“Amazon”,所以我更倾向于使用我知道会起作用的正则表达式函数。但是谢谢你的时间,它不会浪费的。 :)
  • 是的,这是一个更好的解决方案,但像 Joe D. 一样,我需要匹配一组关键字(目前正在使用管道内爆到正则表达式中)。所以上面的正则表达式解决方案目前正在解决,但我很想知道是否有办法用 DOM 方法做到这一点。
  • @KevinCogill 是的——这很容易实现。您必须遍历所有文本节点,而不仅仅是包含Amazon 的节点,并更改while 循环以检查不止一件事。这应该不会太难。
【解决方案2】:

使用 DOM 肯定会更好。

但是,您可能会侥幸逃脱:

$result = preg_replace('%Amazon(?![^<]*</a>)%i', '<a href="http://www.amazon.com">Amazon</a>', $subject);

仅当

匹配 Amazon
  1. 后面没有结束&lt;/a&gt;标签,
  2. 它本身不是标签的一部分,
  3. 没有中间标签,即。 e.如果标签可以嵌套在&lt;a&gt; 标签中,它将被丢弃。

因此会改变这一点:

I use Amazon for that.
I use <a href="http://www.amazon.com">Amazon</a> for that.
<a href="http://www.amazon.com">My Amazon Link</a>
It will match the "Amazon" in "My Amazon Link"

进入这个:

I use <a href="http://www.amazon.com">Amazon</a> for that.
I use <a href="http://www.amazon.com">Amazon</a> for that.
<a href="http://www.amazon.com">My Amazon Link</a>
It will match the "<a href="http://www.amazon.com">Amazon</a>" in "My <a href="http://www.amazon.com">Amazon</a> Link"

【讨论】:

  • 这实际上对我来说非常有效。非常感谢。我确实希望尽快学习 DOM,但我很确定我现在可以使用正则表达式“逃脱”。也感谢其他所有人。 @anubhava 我先尝试了你的代码,但它仍然会干扰现有的标签。
【解决方案3】:

Joe,重新提出这个问题,因为它有一个未提及的简单解决方案。 (在对有关how to exclude patterns in regex 的一般问题进行一些研究时发现了您的问题。)

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

这是我们的简单正则表达式:

<a.*?</a>(*SKIP)(*F)|amazon

交替的左侧匹配完整的&lt;a... &lt;/a&gt; 标签,然后故意失败。右边匹配amazon,我们知道这是右边的amazon,因为它没有被左边的表达式匹配。

这个程序展示了如何使用正则表达式(见online demo底部的结果):

<?php
$target = "word1 <a stuff amazon> </a> word2 amazon";
$regex = "~(?i)<a.*?</a>(*SKIP)(*F)|amazon~";
$repl= '<a href="http://www.amazon.com">Amazon</a>';
$new=preg_replace($regex,$repl,$target);
echo htmlentities($new);

参考

How to match (or replace) a pattern except in situations s1, s2, s3...

【讨论】:

    【解决方案4】:

    在这里试试

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

    这将搜索亚马逊,负前瞻确保后面没有结束标签。而且我只在那里搜索不是&lt;,这样我就不会意外读取开始标签。

    http://regexr.com

    【讨论】:

      【解决方案5】:

      不幸的是,我认为您需要的逻辑仍然比文本模式匹配更复杂:-/

      我知道这不是您想听到的答案,但使用 DOM 模型可能会得到更好的结果。

      这是其他地方对此主题的讨论:http://coderzone.org/forum/index.php?topic=84.0

      是否可以只运行一次过滤器,这样您就不会受到欺骗?还是原始语料库也可以包含链接?

      【讨论】:

        【解决方案6】:

        使用此代码:

        $p = '~((<a\s)(?(2)[^>]*?>))?(amazon)~smi';
        
        $str = '<a href="http://www.amazon.com">Amazon</a>';
        
        $s = preg_replace($p, "$1My $3 Link", $str);
        var_dump($s);
        

        输出

        String(50) "<a href="http://www.amazon.com">My Amazon Link</a>"
        

        【讨论】:

          【解决方案7】:

          即兴创作。只有当它是一个完整的单词“Amazon”而不是像 AmazonWorld 这样的单词时,它才应该链接。

          $result = preg_replace('%\bAmazon(?![^<]*</a>)\b%i', '<a href="http://www.amazon.com">Amazon</a>', $subject);
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2016-10-14
            • 1970-01-01
            • 1970-01-01
            • 2016-12-06
            • 2021-09-26
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多