如何将元素添加到文本节点的中间?

3

鉴于以下HTML:

$content = '<html>
 <body>
  <div>
   <p>During the interim there shall be nourishment supplied</p>
  </div>
 </body>
</html>';

我该如何将它修改为以下HTML格式:
<html>
 <body>
  <div>
   <p>During the <span>interim</span> there shall be nourishment supplied</p>
  </div>
 </body>
</html>

我需要使用DomDocument完成这个任务。以下是我尝试过的方法:
$dom = new DomDocument();
$dom->loadHTML($content);
$dom->preserveWhiteSpace = false;
$xpath = new DOMXpath($dom);
$elements = $xpath->query("//*[contains(text(),'interim')]");
if (!is_null($elements)) {
 foreach ($elements as $element) {
   $text = $element->nodeValue;
   $element->nodeValue = str_replace('interim','<span>interim</span>',$text);
 }
}
echo $dom->saveHTML();

然而,这将输出实际的HTML实体,因此在浏览器中呈现如下:
During the <span>interim</span> there shall be nourishment supplied

我想应该使用createElementappendChild方法来代替直接赋值nodeValue,但我不知道如何在TextNode字符串的中间插入一个元素?


那是一般的想法。问题是“如何”呢? - geoidesic
3个回答

3

马库斯·哈里森(Marcus Harrison)使用 splitText 的答案很好,但可以简化并需要使用 mb_* 方法来处理 UTF-8 输入:

<?php

$html = <<<END
<html>
<meta charset="utf-8">
<body>
    <div>
        <p>During € the interim there shall be nourishment supplied</p>
    </div>
</body>
</html>
END;

$replace = 'interim';

$doc = new DOMDocument;
$doc->loadHTML($html);

$xpath = new DOMXPath($doc);
$nodes = $xpath->query(sprintf('//text()[contains(., "%s")]', $replace));

foreach ($nodes as $node) { 
    $start = mb_strpos($node->textContent, $replace);
    $end = $start + mb_strlen($replace);

    $node->splitText($end); // do this first
    $node->splitText($start); // do this last

    $newnode = $doc->createElement('span');
    $node->parentNode->insertBefore($newnode, $node->nextSibling);
    $newnode->appendChild($newnode->nextSibling);
}

$doc->encoding = 'UTF-8';

print $doc->saveHTML($doc->documentElement);

0
为了实现这个,你必须使用 DOMString 的 splitText 接口。它接受一个偏移量,可以通过使用 strpos 来检索:
$dom = new DomDocument();
$dom->loadHTML($content);
$dom->preserveWhiteSpace = false;
$xpath = new DOMXpath($dom);
$elements = $xpath->query("//*[contains(text(),'interim')]");
if (!is_null($elements)) {
    foreach ($elements as $element) {
        $text = $element->childNodes->item(0);
        $text->splitText(strpos($text->textContent, "interim"));
        $text2 = $element->childNodes->item(1);
        $text2->splitText(strpos($text2->textContent, " "));
        $element->removeChild($text2);
        $span = $dom->createElement("span");
        $span->appendChild($dom->createTextNode("interim"));
        $element->insertBefore($span, $element->childNodes->item(1));
    }
}
echo $dom->saveHTML();

编辑:刚刚测试了一下,我意识到我没有删除第二个文本节点中原始的“interim”。我已经编辑了这个答案来做到这一点。我还编辑了这段代码,使其尽可能兼容旧版本的PHP:因为我不运行旧版本的PHP,所以无法测试。


$text = $element->childNodes[0]; 会出现错误:"致命错误:无法将类型为DOMNodeList的对象用作数组"。 - geoidesic
DOMNode 的 textContent 属性只能在 PHP 5.6.1 及以上版本中进行写入,而我很遗憾无法访问该版本。 - geoidesic
抱歉,在我使用的系统上,DOMNodeList上的数组语法有效。在这种情况下,请将所有实例的 $element->childNodes[n] 替换为 $element->childNodes->item(n)。我会修改我的答案以反映这一点。我将调查如何修改 DOMText 对象的文本内容,但我相信这将涉及在空格字符之后执行另一个拆分,然后从树中删除中心最多的 DOMText。 - Marcus Harrison

0
创建一个新的DomDocument,带有修改后的元素,并替换旧的元素。
 foreach ($elements as $element) {
   $text = $element->nodeValue;

   $el = new DomDocument();
   $el->loadHTML('<iframe>'. str_replace('interim','<span>interim</span>',$text) . '</iframe>');
   $new = $dom->importNode($el->getElementsByTagName('iframe')->item(0), true);
   unset($el);

   $element->parentNode->replaceChild($new, $element);
 }

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