【问题标题】:How to change root of a node with DOMDocument methods?如何使用 DOMDocument 方法更改节点的根?
【发布时间】:2013-03-16 19:41:09
【问题描述】:

如何只更改 DOM 节点的根标签名称?

在 DOM-Document 模型中,我们无法更改 DOMElement 对象的属性 documentElement,因此,我们需要“重建”节点......但是如何使用 childNodes 属性“重建”?


注意:我可以通过 converting to string with saveXML and cuting root by regular expressions 执行此操作...但这是一种解决方法,而不是 DOM 解决方案。


尝试过但不起作用,PHP 示例

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

试一试

 // 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 manual 的解释。

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

renameNode() 方法导致错误,

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

Try-3

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

replaceChild() 方法导致错误,

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

【问题讨论】:

  • 我添加了一个答案,该答案显示了如何执行此操作的函数,并且还轻轻指出了问题代码中的错误位置。一个更大的例子是我在其中链接的另一个答案:stackoverflow.com/a/16315039/367456
  • 根标签通常被认为是文档元素或根节点。因此,您在问题中使用的措辞可能有点误导。您想重命名文档根元素的标记名还是只想重命名 domelements 标记名? (澄清问题)

标签: php xml domdocument appendchild removechild


【解决方案1】:

由于这个问题还没有真正得到解答,你得到的错误 not found 是因为你复制的 renameNode() 函数中的一个小错误。

a somewhat related question about renaming different elements in the DOM 我也看到了这个问题,used an adoption of that function in my answer 没有这个错误:

/**
 * 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 手册中的示例一样,它错过了处理命名空间节点的变体。我还没有预订最好的,例如创建一个处理它的附加函数,从节点接管命名空间以重命名或在不同的函数中显式更改命名空间。

【讨论】:

    【解决方案2】:

    首先,您需要了解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); 
    

    【讨论】:

    • 非常感谢您提供的线索,也许是一个解决方案(!),但我还没有看到DOMDocument::importNode() 可以在我的xml_renameNode() 函数中使用,没有循环,如果像@ 987654333@无法使用。
    • 这是两种不同的解决方案:如果你想重命名根标签的名字,你应该使用$document->documentElement->renameNode();如果你想用另一个根创建另一个文档,请使用$document->importNode
    • 是的,很好的做法...而且,现在我了解您和@PointedEars 将importNode 视为重命名问题的解决方案的上下文...但即使是“另一个文件与另一个根”你需要一个循环。
    • 关于您的编辑:谢谢,但它正是same algorithm than the mine,具有相同的循环。不同之处在于,在函数范围内我不需要 replaceChild。
    • 是的,但它不会引发 DOMException,所以它可以工作。这个概念是您需要更改 documentElement,而不是文档本身。并且:没有循环就无法实现,直到 renameNode() 没有实现。
    【解决方案3】:

    这是我的“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(没有错误!),赏金会为您服务!

    【讨论】:

    • 您被误导了,并且指的是伪造的文档。 DOMDocument::renameNode() as of DOM Level 3 Corenot implemented in PHP as of yet,所以任何使用它的方法都必须失败(或者至少不可靠,因为它只在主干构建中)。但是,您不需要DOMDocument::renameNode()DOM Level 2 Core methods 就够了,看我的回答。
    • 我使用了 Kan 的回答所指出的 renameNode-link,在 Methai 使用 PHP 标记编辑我的问题之前,它向我们展示了一个有效的解决方案。 Methai 缩小范围后,@kan 显示 PHP have a 2-yers-old bug。没有 DOM 级别 2 核心方法就足够了(!),只使用循环。我首先在这里展示了一个循环解决方案。
    • 您和@pozs 没有为此提供替代解决方案,在循环中使用 appendChild。
    • @kan 表明他们不知道他们在说什么,而你爱上了巨魔。 不是 renameNode() 尚未在 PHP 中实现的错误。考虑到 DOM Level 3 中的 textContent 被实现为 DOMNode::textContent,你可以称之为疏忽。您的“解决方案”不仅过于复杂,而且不是DOMDocument::renameNode() 的正确实现。我已经向您提供了有关如何正确实施DOMDocument::renameNode() 的提示,并且您让赏金到期。 ISTM 你太自信了。
    • +1,我认为这是一个很好的镜头,即使不是“正确”的方式。 @PointedEars 你对这里的很多 cmets 有点苛刻,只是不需要。
    【解决方案4】:

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

    【讨论】:

    • 非常感谢,您提供了新的线索和可能的解决方案!你能用 xml_renameNode() 函数来表示吗?我的问题是,如果 child 是节点列表,我要了解如何做 $root-&gt;appendChild($child)... 看看我的“while ($node->firstChild)”实现。你可以在没有循环的情况下(使用replaceChild)吗?
    • 我不认为你可以在不循环调用appendChild() 的情况下附加多个子节点(我的客户端脚本库包含一个appendChildren() 方法)。至于 PHP 中的 renameNode() 实现,您只需要实例化 DOMDocument 的子类即可。 (太糟糕了,你让赏金到期。)
    【解决方案5】:

    我希望我没有遗漏任何东西,但我碰巧遇到了类似的问题,并且能够通过使用 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-&gt;documentElement 是只读的,但是如果$newRoot 是用相同的DomDocument 创建的,那么上面的方法似乎是更简单的解决方案,否则你需要做@987654326 @如上所述的解决方案。从您的问题看来,$newRoot 可以从相同的$doc 创建。

    让我们知道这是否适合您。干杯。

    编辑:在版本 20031129 中注意到 DomDocument::$formatOutput(如果设置)在您最终调用 $doc-&gt;saveXML() 时不会格式化 $newRoot 输出

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-08-25
      • 1970-01-01
      • 1970-01-01
      • 2021-12-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多