Как изменить корень узла с помощью методов DomDocument?

Как изменить только имя корневого тега узла DOM?

В модели DOM-Document мы не можем изменить свойство documentElement из DOMElement объект, поэтому нам нужно "перестроить" узел... Но как "перестроить" с помощью childNodes имущество?


ПРИМЕЧАНИЕ: я могу сделать это путем преобразования строки в saveXML и вырезания корня регулярными выражениями... Но это обходной путь, а не DOM-решение.


Пробовал, но не работает, примеры PHP

Пример PHP (не работает, но ПОЧЕМУ?):

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

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

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

Метод replaceChild() вызвал ошибку,

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

5 ответов

Решение

Поскольку на этот вопрос еще не получен ответ, ошибка, о которой вы узнаете, не найдена, из-за небольшой ошибки в 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); 

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

Это вариация моего "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::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()

Другие вопросы по тегам