Below is the piece of code that I am using to create a bunch of xml nodes and trying to attach to parent that is passed to it.
Not sure whats going wrong, Parent node is not getting augmented.
input.xml
<?xml version="1.0"?>
<root>
<steps> This is a step </steps>
<steps> This is also a step </steps>
</root>
output.xml should be
<?xml version="1.0">
<root>
<steps>
<step>
<step_number>1</step_number>
<step_detail>Step detail goes here</step_detail>
</step>
</steps>
<steps>
<step>
<step_number>2</step_number>
<step_detail>Step detail of the 2nd step</step_detail>
</step>
</steps>
</root>
<?php
$xml = simplexml_load_file( 'input.xml' );
$domxml = dom_import_simplexml( $xml );
//Iterating through all the STEPS node
foreach ($steps as $stp ){
createInnerSteps( &$stp, $stp->nodeValue , 'Expected Result STring' );
echo 'NODE VAL :-----' . $stp->nodeValue;// Here it should have the new nodes, but it is not
}
function createInnerSteps( &$parent )
{
$dom = new DOMDocument();
// Create Fragment, where all the inner childs gets appended
$frag = $dom->createDocumentFragment();
// Create Step Node
$stepElm = $dom->createElement('step');
$frag->appendChild( $stepElm );
// Create Step_Number Node and CDATA Section
$stepNumElm = $dom->createElement('step_number');
$cdata = $dom->createCDATASection('1');
$stepNumElm->appendChild ($cdata );
// Append Step_number to Step Node
$stepElm->appendChild( $stepNumElm );
// Create step_details and append to Step Node
$step_details = $dom->createElement('step_details');
$cdata = $dom->createCDATASection('Details');
$step_details->appendChild( $cdata );
// Append step_details to step Node
$stepElm->appendChild( $step_details );
// Add Parent Node to step element
// I get error PHP Fatal error:
// Uncaught exception 'DOMException' with message 'Wrong Document Error'
$parent->appendChild( $frag );
//echo 'PARENT : ' .$parent->saveXML();
}
?>
You are getting the Wrong Document Error because you create a new DOMDocument object within createInnerSteps(), each time it is called.
When the code reaches the line $parent->appendChild($frag), the $frag belongs to the document created in the function and the $parent belongs to the main document that you are trying to manipulate. You cannot move nodes (or fragments) between documents like this.
The simplest solution is to only ever use the one document by replacing:
$dom = new DOMDocument();
with
$dom = $parent->ownerDocument;
See a running example.
Finally, to get your desired output, you will need to remove the existing text within each <steps> element.
Related
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 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. ;)
I am building up an xml file and need to include a segment of xml saved in a database (yeah, I wish that wasn't the case too).
// parent element
$parent = $dom->createElement('RecipeIngredients');
// the xml string I want to include
$xmlStr = $row['ingredientSectionXml'];
// load xml string into domDocument
$dom->loadXML( $xmlStr );
// add all Ingredient Sections from xmlStr as children of $parent
$xmlList = $dom->getElementsByTagName( 'IngredientSection' );
for ($i = $xmlList->length; --$i >= 0; ) {
$elem = $xmlList->item($i);
$parent->appendChild( $elem );
}
// add the parent to the $dom doc
$dom->appendChild( $parent );
Right now, I get the following error when I hit the line $parent->appendChild( $elem );
Fatal error: Uncaught exception 'DOMException' with message 'Wrong Document Error'
The XML in the string might look something like the following example. An important point is that there may be multiple IngredientSections, all of which need to be appended to the $parent element.
<IngredientSection name="Herbed Cheese">
<RecipeIngredient>
<Quantity>2</Quantity>
<Unit>cups</Unit>
<Item>yogurt cheese</Item>
<Note>(see Tip)</Note>
<MeasureType/>
<IngredientBrand/>
</RecipeIngredient>
<RecipeIngredient>
<Quantity>2</Quantity>
<Unit/>
<Item>scallions</Item>
<Note>, trimmed and minced</Note>
<MeasureType/>
<IngredientBrand/>
</RecipeIngredient>
<IngredientSection name="Cracked-Wheat Crackers">
</IngredientSection>
<RecipeIngredient>
<Quantity>2</Quantity>
<Unit>teaspoon</Unit>
<Item>salt</Item>
<Note/>
<MeasureType/>
<IngredientBrand/>
</RecipeIngredient>
<RecipeIngredient>
<Quantity>1 1/4</Quantity>
<Unit>cups</Unit>
<Item>cracked wheat</Item>
<Note/>
<MeasureType/>
<IngredientBrand/>
</RecipeIngredient>
</IngredientSection>
Here a two possible solutions:
Import From A Source Document
This works only if the XML string is a valid document. You need to import the document element, or any descendant of it. Depends on the part you would like to add to the target document.
$xml = "<child>text</child>";
$source = new DOMDocument();
$source->loadXml($xml);
$target = new DOMDocument();
$root = $target->appendChild($target->createElement('root'));
$root->appendChild($target->importNode($source->documentElement, TRUE));
echo $target->saveXml();
Output:
<?xml version="1.0"?>
<root><child>text</child></root>
Use A Document Fragment
This works for any valid XML fragment. Even if it has no root node.
$xml = "text<child>text</child>";
$target = new DOMDocument();
$root = $target->appendChild($target->createElement('root'));
$fragment = $target->createDocumentFragment();
$fragment->appendXml($xml);
$root->appendChild($fragment);
echo $target->saveXml();
Output:
<?xml version="1.0"?>
<root>text<child>text</child></root>
You need to use ->importNode() instead of ->appendChild(). Your XML snippets are coming from a completely different XML document, and appendChild will only accept nodes which are part of the SAME xml tree. importNode() will accept "foreign" nodes and incorporate them into the main tree.
I'm creating XML response for the one of our clients with the namespace URLs in that using PHP. I'm expecting the output as follows,
<?xml version="1.0" encoding="UTF-8"?>
<ns3:userResponse xmlns:ns3="http://www.w3.org/2001/XMLSchema-instance" xmlns:ns2="http://www.w3.org/2001/XMLSchema">
<Content>
<field1>fieldvalue1</field1>
</Content>
</ns3:userResponse>
But by using the following code,
<?php
// create a new XML document
$doc = new DomDocument('1.0', 'UTF-8');
// create root node
$root = $doc->createElementNS('http://www.w3.org/2001/XMLSchema-instance', 'ns3:userResponse');
$root = $doc->appendChild($root);
$root->setAttributeNS('http://www.w3.org/2001/XMLSchema-instance', 'ns1:schemaLocation','');
$root->setAttributeNS('http://www.w3.org/2001/XMLSchema', 'ns2:schemaLocation','');
// add node for each row
$occ = $doc->createElement('Content');
$occ = $root->appendChild($occ);
$child = $doc->createElement("field1");
$child = $occ->appendChild($child);
$value = $doc->createTextNode('fieldvalue1');
$value = $child->appendChild($value);
// get completed xml document
$xml_string = $doc->saveXML();
echo $xml_string;
DEMO:
The demo is here, http://codepad.org/11W9dLU9
Here the problem is, the third attribute is mandatory attribute for the setAttributeNS PHP function. So, i'm getting the output as,
<?xml version="1.0" encoding="UTF-8"?>
<ns3:userResponse xmlns:ns3="http://www.w3.org/2001/XMLSchema-instance" xmlns:ns2="http://www.w3.org/2001/XMLSchema" ns3:schemaLocation="" ns2:schemaLocation="">
<Content>
<field1>fieldvalue1</field1>
</Content>
</ns3:userResponse>
So, is there anyway to remove that ns3:schemaLocation and ns2:schemaLocation which is coming with empty value? I googled a lot but couldn't able to find any useful answers.
Any idea on this would be so great. Please help.
You create this attributes:
$root->setAttributeNS('http://www.w3.org/2001/XMLSchema-instance', 'ns1:schemaLocation','');
$root->setAttributeNS('http://www.w3.org/2001/XMLSchema', 'ns2:schemaLocation','');
remove this lines and they will be removed.
If you want to add some xmlns without using it in code is:
$attr_ns = $doc->createAttributeNS( 'http://www.w3.org/2001/XMLSchema', 'ns2:attr' );
Read this comment: http://php.net/manual/pl/domdocument.createattributens.php#98210
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