如何在PHP中用DOMNodeList替换DOMNode?

时间:2014-08-08 22:30:56

标签: php dom domdocument xmldom

到目前为止,我已经能够非常轻松地替换节点,因为我只需要1:1替换它们,因为它们只是文本。我正在使用这样的东西:

$element->parentNode->replaceChild($element->ownerDocument->createTextNode($value),$element);

现在的问题是我需要接受可能包含或不包含某些HTML的字符串。例如,我不能再使用createTextNode()字符串:

This is some <span style="font-weight:bold;"></span> text.

因为我在实际的html中最终会混合使用html实体。我也不能这样做:

<p>Paragraph 1</p>
<p>&nbsp;</p>
<p>Paragraph 3</p>

我已经将我的代码修改为以下内容,第一部分通过导入text / html mix创建一个新的dom节点,我可以使用它作为节点将其拉出来,第二部分导入新的<fubar> DOMNode,并用它替换原始节点:

$temp = new DOMDocument('1.0','UTF-8');
$temp->loadHTML('<fubar id="replacement">'.$val.'</fubar>');
$replacement = $temp->getElementById('replacement');

$replacement = $element->ownerDocument->importNode($replacement, TRUE);
$element->parentNode->replaceChild($replacement,$element);

我无法解决的问题是,该文档现在包含所有新节点,包括 <fubar>元素,但它&# 39; s是进行1:1替换的唯一方法,因为replaceChild()要求参数是DOMNode,所以我不能直接使用子节点DOMNodeList。

删除<fubar>节点但保留其子节点(我想要的实际内容)或直接用多个节点替换原始节点的最简单的解决方案是什么? < / p>


编辑:完整的意图是:

<html>
    <body>
        <p>Opening content....<placeholder>REPLACE_ME_FIRST</placeholder></p>
        <placeholder>REPLACE_ME_SECOND</placeholder>
        <p>Closing content....</p>
    </body>
</html>

然后将<placeholder>REPLACE_ME_FIRST</placeholder>替换为...

This is some <span style="font-weight:bold;"></span> text.

并将<placeholder>REPLACE_ME_SECOND</placeholder>替换为...

<p>Paragraph 1</p>
<p>&nbsp;</p>
<p>Paragraph 3</p>

导致:

<html>
    <body>
        <p>Opening content....This is some <span style="font-weight:bold;"></span> text.</p>
        <p>Paragraph 1</p>
        <p>&nbsp;</p>
        <p>Paragraph 3</p>
        <p>Closing content....</p>
    </body>
</html>

...在我原来的问题中,在代码示例中,$element代表<placeholder>节点。

2 个答案:

答案 0 :(得分:0)

感谢OP评论中的一些对话,我能够提出以下替代策略,该策略仍然具有高效性并与我提出的所有示例兼容。

$temp = new DOMDocument('1.0', 'UTF-8');
$temp->loadHTML('<fubar id="replacement">'.$val.'</fubar>');
$replacement = $temp->getElementById('replacement');

// If element is a text node just add a new node with the value, otherwise if it's an element with child nodes, iterate over them adding them to a fragment which can be imported as a whole.
if ($replacement->nodeType === XML_TEXT_NODE || ($replacement->nodeValue && $replacement->childNodes->length === 1 && $replacement->childNodes->item(1) === NULL)) {
    // Text Node
    $new_node = $element->ownerDocument->createTextNode($replacement->nodeValue);
} else {
    // Node List
    $new_node = $element->ownerDocument->createDocumentFragment();
    $children = $replacement->childNodes->length - 1;
    for ($i = 0; $i <= $children; $i++) {
        $child = $element->ownerDocument->importNode($replacement->childNodes->item($i), TRUE);
        $new_node->appendChild($child);
    }
}
$element->parentNode->replaceChild($new_node,$element);
unset($replacement);
unset($temp);

--- N.B. ---

我通过对childNodes的迭代努力了很多。我能够在$replacement中看到childNodes,但它们似乎总是空的。

直到我意识到需要在原始元素的doc而不是temp元素中创建documentFragment,并且在导入到doc之后附加新的子元素。

根本原因是子节点($replacement->childNodes->item($i))无法附加到已存在的文档中。

答案 1 :(得分:0)

感谢您的输入,这就是我解决类似问题的方式,我需要剥离所有//span[@class="scayt-misspell-word"]并将其替换为内容(可以是文本,节点或它们的任意组合)。 请注意,我使用HTML5DOMDocument来保存自定义标记:https://github.com/ivopetkov/html5-dom-document-php

$doc = new HTML5DOMDocument('1.0', 'UTF-8');
$doc->loadHTML($oldText);

$xpath = new DOMXPath($doc);

$body = $xpath->query('//body')->item(0);
while ($span = $xpath->query('//span[@class="scayt-misspell-word"]')
  ->item(0)) {
  $frag = $doc->createDocumentFragment();
  foreach ($span->childNodes as $child) {
    $frag->appendChild($child);
  }
  $span->parentNode->replaceChild($frag, $span);
}

$newText = $doc->saveHTML($body);