通过PHP DOM在HTML片段中查找和替换关键词为超链接。

6
我正在尝试使用 simple_html_dom PHP 类创建查找和替换功能,用于查找关键词并将其替换为指向该关键词定义的链接,同时使关键词成为链接文本。
如何使用此类在字符串中查找并替换“Dexia”为 <a href="info.php?tag=dexia">Dexia</a> ,例如在字符串:<div><p>The CEO of the Dexia bank has just decided to retire.</p></div> 中?

你必须使用simple_html_dom吗?这似乎可以通过使用preg_replace函数来实现正则表达式。 - Nathan Hess
@threendib HTML不是规则的。 - Gordon
1个回答

5
这有点棘手,但你可以这样做:
$html = <<< HTML
<div><p>The CEO of the Dexia bank <em>has</em> just decided to retire.</p></div>
HTML;

我添加了一个强调元素,只是为了说明它也适用于行内元素。

设置

$dom = new DOMDocument;
$dom->formatOutput = TRUE;
$dom->loadXML($html);
$xpath = new DOMXPath($dom);
$nodes = $xpath->query('//text()[contains(., "Dexia")]');

上面有趣的事情当然是XPath。它查询已加载的DOM中所有包含“Dexia”关键字的DOMText节点,结果通常是一个DOMNodeList

更换内容

foreach($nodes as $node) {
    $link     = '<a href="info.php?tag=dexia">Dexia</a>';
    $replaced = str_replace('Dexia', $link, $node->wholeText);
    $newNode  = $dom->createDocumentFragment();
    $newNode->appendXML($replaced);
    $node->parentNode->replaceChild($newNode, $node);
}
echo $dom->saveXML($dom->documentElement);

找到的$node将包含字符串德克萨斯银行的CEOwholeText,尽管它在P元素中。这是因为$node有一个强调标记银行后面的兄弟DOMElement。我将链接创建为字符串而不是节点,并用它替换wholeText中所有出现的“Dexia”(不考虑单词边界-这对于正则表达式来说是个好选择)。然后,我从结果字符串创建一个DocumentFragment并用它替换DOMText节点。

W3C vs PHP

使用DocumentFragement::applyXML()是非标准的方法,因为该方法不是W3C DOM规范的一部分。

如果您想使用标准的API进行替换,您需要首先将A元素创建为新的DOMElement。然后,您需要查找DOMTextnodeValue中“Dexia”的偏移量,并在该位置将DOMText节点拆分为两个节点。从返回的兄弟节点中删除Dexia并在第二个节点之前插入链接元素。重复此过程,直到在节点中找不到更多的Dexia字符串。以下是如何处理一个Dexia出现的方法:

foreach($nodes as $node) {
    $link = $dom->createElement('a', 'Dexia');
    $link->setAttribute('href', 'info.php?tag=dexia');
    $offset  = strpos($node->nodeValue, 'Dexia');
    $newNode = $node->splitText($offset);
    $newNode->deleteData(0, strlen('Dexia'));
    $node->parentNode->insertBefore($link, $newNode);
}

最后的输出

<div>
  <p>The CEO of the <a href="info.php?tag=dexia">Dexia</a> bank <em>has</em> just decided to retire.</p>
</div>

@gordon:非常有趣。我简化了我的问题,因为我期望得到更简单的答案;但实际上我有大约500个关键词。这种方法是否可以正确扩展?我想我需要一个双重foreach循环。 - pixeline
@pixeline 嗯,一开始很棘手,但只是因为DOM非常冗长,你必须考虑节点而不是文本。我理解为什么人们想用正则表达式来做它,但一旦你进入它,它真的不那么困难。我的意思是,这只是15行代码。将其放入适当的Service类中,您就拥有了一个很酷的可重复使用的工具。不过我不知道它的性能如何,所以您需要对其进行基准测试,并查看它是否可接受。 - Gordon
1
它运行得相当不错(我不得不更改为loadHTML,因为我传递的HTML来自CMS,可能格式不正确)。 - pixeline

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接