Append DOMDocument root element to another DOMDocument - php

I have 2 "DOMDocument" objects - $original and $additional. What I want is to take all children from $additional DOMDocument and append it to the end of $original document.
My plan was to take root element of the $additional document. I tried to use:
$root = $additional->documentElement;
$original->appendChild($root)
But I receive error that appendChild expect DOMNode object as an argument.
I tried to access each children of the document through:
$additional->childNodes->item(0);
But it returns object of DOMElement. Can you advice how to get object of DOMNode class? What is the most convenient way to provide this import operation?
$original XML looks like:
<?xml version="1.0" encoding="utf-8"?>
<Product>
<RecordReference>345345</RecordReference>
<NotificationType>03</NotificationType>
<NumberOfPages>100</NumberOfPages
</Product>
$additional XML looks like:
<?xml version="1.0" encoding="utf-8"?>
<MainSubject>
<SubjectScheme>10</SubjectScheme>
</MainSubject>
What I want to have:
<?xml version="1.0" encoding="utf-8"?>
<Product>
<RecordReference>345345</RecordReference>
<NotificationType>03</NotificationType>
<NumberOfPages>100</NumberOfPages>
<MainSubject>
<SubjectScheme>10</SubjectScheme>
</MainSubject>
</Product>

A DOMElement is a DOMNode, DOMNode is the superclass. Here are several child classes for element, text and other nodes. Just iterate, import and append them.
$targetDom = new DOMDocument();
$targetDom->loadXML('<root/>');
$sourceDom = new DOMDocument();
$sourceDom->loadXml('<items><child/>TEXT</items>');
foreach ($sourceDom->documentElement->childNodes as $child) {
$targetDom->documentElement->appendChild(
$targetDom->importNode($child, TRUE)
);
}
This works with the document element, too.
$targetDom = new DOMDocument();
$targetDom->loadXML('<root/>');
$sourceDom = new DOMDocument();
$sourceDom->loadXml('<items><child/>TEXT</items>');
$targetDom->documentElement->appendChild(
$targetDom->importNode($sourceDom->documentElement, TRUE)
);
echo $targetDom->saveXml();
DOMDocument::importNode() creates a copy of the provided node in the context of the document. Only nodes belonging to a document can be appended to it.

Related

How can I append new xml nodes to an existing .xml file with php class DOMDocument?

Let's say I have the following .xml file:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<item>
<name>Foo</name>
</item>
<item>
<name>Bar</name>
</item>
</root>
In this sample file, I'm trying to append new nodes <item> to node <root> after the last node <item>.
I'm trying to append newly created <item> nodes after the last <item> node in the <root> node in the .xml file.
<?php
$file = new DOMDocument;
$file->load("xml.xml");
$file->loadXML($file->saveXML());
$root = $file->getElementsByTagName('root')->item(0);
foreach (["Foo_1", "Bar_2", "Foo_3", "Bar_4"] as $val) {
$item = new DOMElement('item');
$item->appendChild(new DOMElement('name', $val));
$root->appendChild(item);
}
?>
But I'm getting an error:
Fatal error: Uncaught Error: Call to a member function appendChild() on null in C:\Users\pfort\Desktop\p.php:12
Stack trace:
#0 {main}
thrown in C:\Users\user_acer\Desktop\p.php on line 12
What am I doing wrong?
There's multiple issues with your example code. I will address the error you received first:
There is no element <terminy> in your example XML, so
$root = $file->getElementsByTagName('terminy')->item(0);
will return null. That's why you are receiving the
Call to a member function appendChild() on null
error at
$root->appendChild(item);
Also, item is a typo, because it's not a valid variable name (but a name for a non-existent constant); you meant $item.
I'm assuming "terminy" means something similar to "root" in your native language and that you actually meant to write
$root = $file->getElementsByTagName('root')->item(0);
By the way: if you want a reference to the root node of an XML document, you can also use $file->docomentElement.
However, there are other issues with your example code:
$file->load("xml.xml");
$file->loadXML($file->saveXML()); // why are you reloading it in this way?
The last line is unnecessary. You are reloading the same XML again. Is it for formatting purposes? If so, there's a better option available:
$file->preserveWhiteSpace = false;
$file->formatOutput = true;
$file->load("xml.xml");
Lastly: you cannot append children to a node that has not been associated with a document yet. So, to create a new item and associate it with the document, you either do (recommended):
// automatically associate new nodes with document
$item = $file->createElement('item');
$item->appendChild($file->createElement('name', $val));
or (more cumbersome):
// import nodes to associate them with document
$item = $file->importNode(new DOMElement('item'));
$item->appendChild($file->importNode(new DOMElement('name', $val)));
So, putting it all together it becomes:
<?php
$xml = <<<'XML'
<?xml version="1.0" encoding="UTF-8"?>
<root>
<item>
<name>Foo</name>
</item>
<item>
<name>Bar</name>
</item>
</root>
XML;
$file = new DOMDocument;
$file->preserveWhiteSpace = false;
$file->formatOutput = true;
$file->loadXML($xml); // (for demo purpose loading above XML) replace this with $file->load("xml.xml"); in your actual code
$root = $file->documentElement;
foreach (["Foo_1", "Bar_2", "Foo_3", "Bar_4"] as $val) {
$item = $file->createElement('item');
$item->appendChild($file->createElement('name', $val));
$root->appendChild($item);
}
echo $file->saveXML();
**PROBLEM SOLVED**
I lost too much time on this problem. The good news is, I already know how to get what I need. Here I offer a solution - for everyone who will need to solve the same problem.
Perhaps this solution will be useful for anyone who needs it.
<?php
// snippet of xml temple
$xml = <<<XML
<item date="%s" status="%s">
<name>%s</name>
</item>
XML;
// prepare snippet
$xmlSnippet = sprintf($xml, "2022-11-21", 0, "Foo Bar");
// new DOMDocument
$dom = new DOMDocument;
$dom->preserveWhiteSpace = 0;
$dom->formatOutput = 1;
// load of .xml file content and load to DOMDocument object
$file = simplexml_load_file("xml.xml");
$dom->loadXML($file->asXML());
// creating of fragment from snippet
$fragment = $dom->createDocumentFragment();
$fragment->appendXML($xmlSnippet);
//append the snippet to the DOMDocument
// and save it to the xml.xml file
$dom->documentElement->appendChild($fragment);
$dom->save("xml.xml");
?>
Result:

How to get into into another child than 'firstChild' of XML file in PHP?

How can I get through another child of XML file than firstChild in PHP?
I have a code like this:
$root = $xmldoc->firstChild;
Can I simply get into second child or another?
A possible solution to your problem could be something like this. First your XML structure. You asked how to add an item node to the data node.
$xml = <<< XML
<?xml version="1.0" encoding="utf-8"?>
<xmldata>
<data>
<item>item 1</item>
<item>item 2</item>
</data>
</xmldata>
XML;
In PHP one possible solution is the DomDocument object.
$doc = new \DomDocument();
$doc->loadXml($xml);
// fetch data node
$dataNode = $doc->getElementsByTagName('data')->item(0);
// create new item node
$newItemNode = $doc->createElement('item', 'item 3');
// append new item node to data node
$dataNode->appendChild($newItemNode);
// save xml node
$doc->saveXML();
This code example is not tested. Have fun. ;)

PHP DOM Cut xml in to pieces and save each child with parent separately

I have next type of XML:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE test SYSTEM "dtd">
<root>
<tag1>
<1>Name</1>
<2>Num1</2>
<3>NumOrder</3>
<4>test</5>
<6>line</6>
<7>HTTP </7>
<8>1</8>
<9></9>
</tag1>
<tag2>
<1>Name</1>
<2>Num1</2>
<3>NumOrder</3>
<4>test</5>
<6>line</6>
<7>HTTP </7>
<8>1</8>
<9></9>
</tag2>
...
<tagN>
<1>Name</1>
<2>Num1</2>
<3>NumOrder</3>
<4>test</5>
<6>line</6>
<7>HTTP </7>
<8>1</8>
<9></9>
</tagN>
</root>
And i need to get root with each child element separately in array saved as HTML:
array = [rootwithchild1,rootwithchild2...N];
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE test SYSTEM "dtd">
<root>
<tagN>
<1>Name</1>
<2>Num1</2>
<3>NumOrder</3>
<4>test</5>
<6>line</6>
<7>HTTP </7>
<8>1</8>
<9></9>
</tagN>
</root>
For now i make 2 doms, in one i get all child separately, in another i have deleted all child and left only root. At these step i wanted to add each child to root, save as html, delete child, and so on with each child, but this doesn't work.
$bodyNode = $copydoc->getElementsByTagName('root')->item(0);
foreach ($mini as $value) {
$bodyNode->appendChild($value);
$result[] = $copydoc->saveHTML();
$bodyNode->removeChild($value);
}
Error on $bodyNode->appendChild($value);
Mini is array of cut child.
Lib: $doc = new DOMDocument();
Can anyone advice how to do this right, maybe better to use xpath or something else..?
Thanks
I would simply create a new document that contains only the root element and a “fake” initial child:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE test SYSTEM "dtd">
<root>
<fakechild />
</root>
After that, loop over the child elements of the original document – and for each of those perform the following steps:
import the child node from the original document into the new document using DOMDocument::importNode
replace the current child node of the root element of the new document with the imported node using DOMNode::replaceChild with the firstChild of the root element as second parameter
save the new document
(Having the <fakechild /> in the root element to begin with is not technically necessary, a simple whitespace text node should do as well – but with an empty root element this would not work in such a straight fashion, because the firstChild would give you NULL in the first loop iteration, so you would not have a node to feed to DOMNode::replaceChild as second parameter. Of course you could do additional checks for that and use appendChild instead of replaceChild for the first item … but why complicate stuff more than necessary.)
DOMNode::getElemementsByTagName() returns a live result. So if you remove the node from the DOM it is removed from the node list as well.
You can iterate the list backwards...
for ($i = $nodes->length - 1; $i >= 0; $i--) {
$node = $nodes->item($i);
...
}
... or copy it to an array:
foreach (iterator_to_array($nodes) as $node) {
...
}
Node lists from DOMXpath::evaluate() are not affected that way. XPath allows a more specific selection of nodes, too.
$xpath = new DOMXpath($domDocument);
$nodes = $xpath->evaluate('/root/*');
foreach (iterator_to_array($nodes) as $node) {
...
}
But I wonder why are you modifying (destroying) the original XML source?
If would create a new document to act as a template and. Never removing nodes, only creating new documents and importing them:
// load the original source
$source= new DOMDocument();
$source->loadXml($xml);
$xpath = new DOMXpath($source);
// create a template dom
$template = new DOMDocument();
$parent = $template;
// add a node and all its ancestors to the template
foreach ($xpath->evaluate('/root/part[1]/ancestor-or-self::*') as $node) {
$parent = $parent->appendChild($template->importNode($node, FALSE));
}
// for each of the child element nodes
foreach ($xpath->evaluate('/root/part/*') as $node) {
// create a new target
$target = new DOMDocument();
// import the nodes from the template
$target->appendChild($target->importNode($template->documentElement, TRUE));
// find the first element node that has no child element nodes
$targetXpath = new DOMXpath($target);
$targetNode = $targetXpath->evaluate('//*[count(*) = 0]')->item(0);
// append the child node from the original xml
$targetNode->appendChild($target->importNode($node, TRUE));
echo $target->saveXml(), "\n\n";
}
Demo: https://eval.in/191304

Adding in new XML root node

I need to add in a new root node to the following XML
<?xml version="1.0"?>
<unit>
<source>
<id>ANCH02</id>
<uri>http://www.hamiltonisland.biz/tabid/339/Default.aspx</uri>
</source>
</unit>
to become
<?xml version="1.0"?>
<units>
<unit>
<source>
<id>ANCH02</id>
<uri>http://www.hamiltonisland.biz/tabid/339/Default.aspx</uri>
</source>
</unit>
</units>
How could I do this? It doesn't seem like SimpleXMLElement has this functionality. I have also looked at this DomNode example http://php.net/manual/en/domnode.insertbefore.php but it doesnt seem to be able to add in a new root node.
This seem to work
$units = $dom->createElement('units');
$units->appendChild($dom->documentElement);
$dom->appendChild($units);
DEMO
DOMDocument:
$yourDOMDOMDocument ... <--- already loaded XML
$doc = new DOMDocument();
$doc->appendChild($doc->createElement('Units'));
$doc->documentElement->appendChild($doc->importNode($yourDOMDocument->documentElement));
Or. if you have your XML as SimpleXMLElement already:
$yourSimpleXML ... <--- already loaded XML
$doc = new DOMDocument();
$doc->appendChild($doc->createElement('Units'));
$domnode = dom_import_simplexml($yourSimpleXML);
$doc->documentElement->appendChild($doc->importNode($domnode));
//if you want it back as SXE:
$newSimpleXMLElement = simplexml_import_dom($doc);

Adding a block of XML as child of a SimpleXMLElement object

I have this SimpleXMLElement object with a XML setup similar to the following...
$xml = <<<EOX
<books>
<book>
<name>ABCD</name>
</book>
</books>
EOX;
$sx = new SimpleXMLElement( $xml );
Now I have a class named Book that contains info. about each book. The same class can also spit out the book info. in XML format akin the the above (the nested block).. example,
$book = new Book( 'EFGH' );
$book->genXML();
... will generate
<book>
<name>EFGH</name>
</book>
Now I'm trying to figure out a way by which I can use this generated XML block and append as a child of so that now it looks like... for example..
// Non-existent member method. For illustration purposes only.
$sx->addXMLChild( $book->genXML() );
...XML tree now looks like:
<books>
<book>
<name>ABCD</name>
</book>
<book>
<name>EFGH</name>
</book>
</books>
From what documentation I have read on SimpleXMLElement, addChild() won't get this done for you as it doesn't support XML data as tag value.
Two solutions. First, you do it with the help of libxml / DOMDocument / SimpleXML: you have to import your $sx object to DOM, create a DOMDocumentFragment and use DOMDocumentFragment::appendXML():
$doc = dom_import_simplexml($sx)->ownerDocument;
$fragment = $doc->createDocumentFragment();
$fragment->appendXML($book->genXML());
$doc->documentElement->appendChild($fragment);
// your original $sx is now already modified.
See the Online Demo.
You can also extend from SimpleXMLElement and add a method that is providing this. Using this specialized object then would allow you to create the following easily:
$sx = new MySimpleXMLElement($xml);
$sx->addXML($book->genXML());
Another solution is to use an XML library that already has this feature built-in like SimpleDOM. You grab SimpleDOM and you use insertXML(), which works like the addXMLChild() method you were describing.
include 'SimpleDOM.php';
$books = simpledom_load_string(
'<books>
<book>
<name>ABCD</name>
</book>
</books>'
);
$books->insertXML(
'<book>
<name>EFGH</name>
</book>'
);
Have a look at my code:
$doc = new DOMDocument();
$doc->loadXML("<root/>");
$fragment = $doc->createDocumentFragment();
$fragment->appendXML("<foo>text</foo><bar>text2</bar>");
$doc->documentElement->appendChild($fragment);
echo $doc->saveXML();
This modifies the XML document by adding an XML fragment. Online Demo.

Categories