Как изменить только имя тега корня узла DOM?
В модели DOM-Document мы не можем изменить свойство documentElement
объекта DOMElement
, поэтому нам нужно «перестроить» узел … Но как «перестроить» с childNodes
свойства childNodes
?
ПРИМЕЧАНИЕ. Я могу сделать это, преобразовывая в строку с помощью saveXML и вырезая корень с помощью регулярных выражений … Но это обходное решение, а не DOM-решение.
Пример 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'
Из предложения @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
Из руководства по 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'
Поскольку на этот вопрос пока еще не ответил, ошибка, о которой вы не нашли, renameNode()
с небольшой ошибкой в функции 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
импортировать узлы из другого 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
.
Сообщите нам, если бы это сработало для вас. Приветствия.
EDIT: замечено в версии 20031129, что параметр DomDocument::$formatOutput
, если он установлен, не форматирует вывод $newRoot
когда вы, наконец, вызываете $doc->saveXML()