How to change root of a node with DomDocument methods?

眉间皱痕 提交于 2020-01-11 07:31:10

问题


How to only change root's tag name of a DOM node?

In the DOM-Document model we can not change the property documentElement of a DOMElement object, so, we need "rebuild" the node... But how to "rebuild" with childNodes property?


NOTE: I can do this by converting to string with saveXML and cuting root by regular expressions... But it is a workaround, not a DOM-solution.


Tried but not works, PHP examples

PHP example (not works, but WHY?):

Try-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");
  }

The appendChild($node) cause an error:

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

Try-2

From @can suggestion (only pointing link) and my interpretation of the poor dom-domdocument-renamenode manual.

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

The renameNode() method caused an error,

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

Try-3

From PHP manual, comment 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; 
 }

The replaceChild() method caused an error,

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

回答1:


As this has not been really answered yet, the error you get about not found is because of a little error in the renameNode() function you've copied.

In a somewhat related question about renaming different elements in the DOM I've seen this problem as well and used an adoption of that function in my answer that does not have this error:

/**
 * 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);
}

You might have spotted it in the last line of the function body: This is using ->parentNode instead of ->ownerDocument. As $node was not a child of the document, you did get the error. And it also was wrong to assume that it should be. Instead use the parent element to search for the child in there to replace it ;)

This has not been outlined in the PHP manual usernotes so far, however, if you did follow the link to the blog-post that originally suggested the renameNode() function you could find a comment below it offering this solution as well.

Anyway, my variant here uses a slightly different variable naming and is more distinct about the types. Like the example in the PHP manual it misses the variant that deals with namespace nodes. I'm not yet booked what would be best, e.g. creating an additional function dealing with it, taking over namespace from the node to rename or changing the namespace explicitly in a different function.




回答2:


First, you need to understand that the DOMDocument is only the hierarchical root of the document-tree. It's name is always #document. You want to rename the root-element, which is the $document->documentElement.

If you want to copy nodes form a document to another document, you'll need to use the importNode() function: $document->importNode($nodeInAnotherDocument)

Edit:

renameNode() is not implemented yet, so you should make another root, and simply replace it with the old one. If you use DOMDocument->createElement() you don't need to use importNode() on it later.

$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); 



回答3:


This is an variation of my "Try-3" (see question), and works fine!

  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;
  }    

Of course, if you show how to use DOMDocument::renameNode (without errors!), the bounty goes for you!




回答4:


ISTM in your approach you attempt to import nodes from another DOMDocument, so you need to use the importNode() method:

$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);

However, it is far easier to append the child nodes to a new parent element (thereby moving them), and replace the old root with that parent element:

$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();



回答5:


I hope I am not missing anything but I happened to have the similar problem and was able to solve it by using use 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;

What threw me off initially was that $doc->documentElement was readonly, but the above worked and seems to be much simpler solution IF the $newRoot was created with the same DomDocument, otherwise you'll need do the importNode solution as described above. From your question is appears that $newRoot could be created from the same $doc.

Let us know if this worked out for you. Cheers.

EDIT: Noticed in version 20031129 that the DomDocument::$formatOutput, if set, does not format $newRoot output when you finally call $doc->saveXML()



来源:https://stackoverflow.com/questions/15646757/how-to-change-root-of-a-node-with-domdocument-methods

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!