如何使用DOMDocument方法更改节点的根


How to change root of a node with DOMDocument methods?

如何仅更改DOM节点的根标记名称?

在DOM-Document模型中,我们不能改变DOMElement对象的属性documentElement,因此,我们需要"重建"节点…但如何"重建"与childNodes属性?


注意:我可以通过使用saveXML转换为字符串并通过正则表达式切割根来做到这一点…但这是一种变通方法,而不是dom的解决方案。


尝试但不工作,PHP示例

PHP示例(不工作,但为什么?):

尝试1

 // DOMElement::documentElement can not be changed, so... 
 function DomElement_renameRoot1($ele,$ROOTAG='newRoot') { 
    if (gettype($ele)=='object' && $ele->nodeType==XML_ELEMENT_NODE) {
   $doc = new DOMDocument();
   $eaux = $doc->createElement($ROOTAG); // DOMElement
       foreach ($ele->childNodes as $node)  
       if ($node->nodeType == 1)  // DOMElement 
               $eaux->appendChild($node);  // error!
       elseif ($node->nodeType == 3)  // DOMText
               $eaux->appendChild($node); // error!
       return $eaux;
    } else
        die("ERROR: invalid DOM object as input");
  }

appendChild($node)导致错误:

 Fatal error: Uncaught exception 'DOMException' 
 with message 'Wrong Document Error'

Try-2

来自@can建议(仅指向链接)和我对可怜的dom-domdocument-renamenode手册的解释

 function DomElement_renameRoot2($ele,$ROOTAG='newRoot') {
$ele->ownerDocument->renameNode($ele,null,"h1");
    return $ele;
 }

renameNode()方法导致错误,

Warning: DOMDocument::renameNode(): Not yet implemented

3

来自PHP手册,注释1

 function renameNode(DOMElement $node, $newName)
 {
     $newNode = $node->ownerDocument->createElement($newName);
     foreach ($node->attributes as $attribute)
        $newNode->setAttribute($attribute->nodeName, $attribute->nodeValue);
     while ($node->firstChild)
        $newNode->appendChild($node->firstChild); // changes firstChild to next!?
     $node->ownerDocument->replaceChild($newNode, $node); // changes $node?
     // not need return $newNode; 
 }

replacecchild()方法导致错误,

Fatal error: Uncaught exception 'DOMException' with message 'Not Found Error'

由于这还没有得到真正的回答,您得到的关于未找到的错误是由于您复制的renameNode()函数中的一个小错误。

在DOM中重命名不同元素的相关问题中,我也看到了这个问题,并在我的答案中使用了该函数,没有此错误:

/**
 * Renames a node in a DOM Document.
 *
 * @param DOMElement $node
 * @param string     $name
 *
 * @return DOMNode
 */
function dom_rename_element(DOMElement $node, $name) {
    $renamed = $node->ownerDocument->createElement($name);
    foreach ($node->attributes as $attribute) {
        $renamed->setAttribute($attribute->nodeName, $attribute->nodeValue);
    }
    while ($node->firstChild) {
        $renamed->appendChild($node->firstChild);
    }
    return $node->parentNode->replaceChild($renamed, $node);
}

您可能已经在函数体的最后一行发现了它:这是使用->parentNode而不是->ownerDocument。由于$node不是文档的子节点,因此您确实得到了错误。认为它应该如此也是错误的。而是使用父元素来搜索其中的子元素来替换它;)

到目前为止,这还没有在PHP手册用户说明中概述,但是,如果您遵循最初建议renameNode()函数的博客文章的链接,您可以在其下方找到提供此解决方案的评论。

无论如何,我这里的变体使用了稍微不同的变量命名,并且在类型上更明显。与PHP手册中的示例一样,它遗漏了处理名称空间节点的变体。我还没有预定什么是最好的,例如创建一个额外的函数来处理它,从节点接管命名空间以重命名或在不同的函数中显式更改命名空间。

首先,您需要理解DOMDocument只是文档树的层次根。它的名字总是#document。您想重命名根元素,它是$document->documentElement .

如果要将节点从一个文档复制到另一个文档,则需要使用importNode()函数:$document->importNode($nodeInAnotherDocument)

编辑:

renameNode()尚未实现,因此您应该创建另一个根,并简单地将其替换为旧的根。如果你使用DOMDocument->createElement(),以后就不需要使用importNode()了。

$oldRoot = $doc->documentElement;
$newRoot = $doc->createElement('new-root');
foreach ($oldRoot->attributes as $attr) {
  $newRoot->setAttribute($attr->nodeName, $attr->nodeValue);
}
while ($oldRoot->firstChild) {
  $newRoot->appendChild($oldRoot->firstChild);
}
$doc->replaceChild($newRoot, $oldRoot); 

这是我的"Try-3"(见问题)的一个变体,工作良好!

  function xml_renameNode(DOMElement $node, $newName, $cpAttr=true) {
      $newNode = $node->ownerDocument->createElement($newName);
      if ($cpAttr && is_array($cpAttr)) {
        foreach ($cpAttr as $k=>$v)
             $newNode->setAttribute($k, $v);
      } elseif ($cpAttr)
        foreach ($node->attributes as $attribute)
             $newNode->setAttribute($attribute->nodeName, $attribute->nodeValue);
      while ($node->firstChild)
          $newNode->appendChild($node->firstChild);
      return $newNode;
  }    

当然,如果您展示了如何使用DOMDocument::renameNode(没有错误!),那么奖励将为您服务!

ISTM在您的方法中,您尝试另一个 DOMDocument导入节点,因此您需要使用importNode()方法:

$d = new DOMDocument();
/* Make a `foo` element the root element of $d */
$root = $d->createElement("foo");
$d->appendChild($root);
/* Append a `bar` element as the child element of the root of $d */
$child = $d->createElement("bar");
$root->appendChild($child);
/* New document */
$d2 = new DOMDocument();
/* Make a `baz` element the root element of $d2 */
$root2 = $d2->createElement("baz");
$d2->appendChild($root2);
/* 
 * Import a clone of $child (from $d) into $d2,
 * with its child nodes imported recursively
 */
$child2 = $d2->importNode($child, true);
/* Add the clone as the child node of the root of $d2 */
$root2->appendChild($child2);

但是,将子节点附加到新的父元素(从而移动它们),并用父元素替换旧的根元素要容易得多:

$d = new DOMDocument();
/* Make a `foo` element the root element of $d */
$root = $d->createElement("foo");
$d->appendChild($root);
/* Append a `bar` element as the child element of the root of $d */
$child = $d->createElement("bar");
$root->appendChild($child);
/* <?xml version="1.0"?>
   <foo><bar/></foo> */
echo $d->saveXML();
$root2 = $d->createElement("baz");
/* Make the `bar` element the child element of `baz` */
$root2->appendChild($child);
/* Replace `foo` with `baz` */
$d->replaceChild($root2, $root);
/* <?xml version="1.0"?>
   <baz><bar/></baz> */
echo $d->saveXML();

我希望我没有错过任何东西,但我碰巧有类似的问题,并能够通过使用DomDocument::replaceChild(...)来解决它。

   /* @var $doc DOMDocument */
   $doc = DOMImplementation::createDocument(NULL, 'oldRoot');
   /* @var $newRoot DomElement */
   $newRoot = $doc->createElement('newRoot');
   /* all the code to create the elements under $newRoot */
   $doc->replaceChild($newRoot, $doc->documentElement);
   $doc->documentElement->isSameNode($newRoot) === true;

最初让我感到困惑的是$doc->documentElement是只读的,但如果$newRoot是用相同的DomDocument创建的,那么上面的工作似乎是更简单的解决方案,否则你需要执行上面描述的importNode解决方案。从你的问题看来,$newRoot可以从相同的$doc创建。

让我们知道这是否适合你。欢呼。

编辑:注意在20031129版本,DomDocument::$formatOutput,如果设置,不格式化$newRoot输出当你最终调用$doc->saveXML()