Get Child Node XML With DomDocument PHP - php

I have read instruction of DomDocument PHP, and try to get specific child from xml file, here the example
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<methods>
<index>
<title>User</title>
<meta_description>Description part</meta_description>
<meta_keywords>keywords parts</meta_keywords>
<permissions>permissions part</permissions>
<layout>layout parts</layout>
<css>css parts</css>
</index>
<update>
<title>update part</title>
<permissions>update_user</permissions>
</update>
</methods>
</configuration>
What I want is, let say I want to access permissions value tag of index tag, then how to do that? And how to check if permissions tag is exists on index tag with domdocument php.
Here, what I have done
$xml = new DOMDocument();
$xml->load(__DIR__ . DIRECTORY_SEPARATOR . 'configuration.xml');
$configurations['title'] = $xml->getElementsByTagName('index title')->nodeValue;
print_r($configurations);

The argument of DOMDocument::getElementsByTagName() is a single node name and it always returns a node list. For more complex and flexible expressions you need to use XPath:
$dom = new DOMDocument();
$dom->load(__DIR__ . DIRECTORY_SEPARATOR . 'configuration.xml');
$xpath = new DOMXPath($dom);
$configurations['title'] = $xpath->evaluate('string(//index/title)');
var_dump($configurations);
Output:
array(1) {
["title"]=>
string(4) "User"
}

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:

getElementById on dynamically created XML

The example speaks for itself, I expect getElementById to return the second element but NULL is given. How come?
$dom = new DOMDocument();
$root = $dom->createElement("root");
$el = $dom->createElement("element");
$el->setAttribute("id", "1");
$root->appendChild($el);
$el = $dom->createElement("element");
$el->setAttribute("id", "2");
$root->appendChild($el);
$dom->appendChild($root);
// OK
echo $dom->saveXML();
// NOT OK
var_dump($dom->getElementById("2"));
With XML the getElementById method returns elements for which a certain attribute has been defined in the DTD as having type ID and not those named id. So you would need to make sure you have a DTD declaring the type ID for the elements named element and the attribute named id if you want to be able to use that method:
$dom = new DOMDocument();
$dom->loadXML("<!DOCTYPE root [<!ATTLIST element id ID #IMPLIED>]><root/>");
$root = $dom->documentElement;
It seems that in the PHP DOM API doing
$el = $dom->createElement("element");
$el->setAttribute("id", "2");
$el->setIdAttribute("id", TRUE);
serves as an alternative to having a DTD.
id is only an id attribute if defined by the DTD/XSD or API. The only predefined id attribute in XML is xml:id ({http://www.w3.org/XML/1998/namespace}id).
You can use Xpath to fetch an node by its attribute value. It does not need to by an id attribute for that:
$xml = <<<'XML'
<?xml version="1.0"?>
<root>
<element id="1"/>
<element id="2"/>
<element id="3"/>
</root>
XML;
$document = new DOMDocument();
$document->loadXml($xml);
$xpath = new DOMXpath($document);
var_dump(
$xpath->evaluate('//*[#id=2]')->item(0)->getAttribute('id')
);
Output:
string(1) "2"

Set the namespace for an XML tag with PHP

I'd like to create an XML document with a very specific format. It should look similar to this:
<?xml version="1.0" encoding="UTF-8"?>
<ram:FLOW xmlns:ram=\"http://MY_LIBRARY\" xmlns:mar=\"http://ANOTHER_LIBRARY\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">
<Header>
<Source>Application1</Source>
<Time>2014-11-12T12:46:39</Time>
<Environment>TEST</Environment>
<Sequence>537</Sequence>
</Header>
<Data>
<mar:OC_DC>
<DC_elements>
<Unit>
<Unit_ID>089789</Unit_ID>
<State>active</State>
</Unit>
<Unit>
<Unit_ID>459008</Unit_ID>
<State>inactive</State>
</Unit>
</DC_elements>
</mar:OC_DC>
</Data>
</ram:FLOW>
I wrote a PHP/MySQL script to generate this document:
<?php
$xml = new SimpleXMLElement("<?xml version=\"1.0\" encoding=\"UTF-8\"?><ram:FLOW xmlns:ram=\"http://MY_LIBRARY\" xmlns:mar=\"http://ANOTHER_LIBRARY\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"></ram:FLOW>");
$header = $xml->addChild('Header');
$header->addChild('Source', $source);
$header->addChild('Time', $time);
$header->addChild('Environment', $env);
$header->addChild('Sequence', $sequence);
$data=$xml->addChild('Data');
$mar_oc_dc=$data->addChild('mar:OC_DC');
$dc_elements=$mar_oc_dc->addChild('DC_elements');
while($condition)
{
// some MySQL code here to extract unit_id and state
$unit=$dc_elements->addChild('Unit');
$unit_id=$unit->addChild('Unit_ID', $unit_id);
$state=$unit->addChild('State', $state);
}
$dom = new DOMDocument();
$dom->preserveWhiteSpace = FALSE;
$dom->formatOutput = TRUE;
$dom->loadXML($xml->asXML());
$handle = fopen("backup/" . $file_name . ".xml", "w");
fwrite($handle, $dom->saveXML());
fclose($handle);
?>
But the result was a little bit different from what I expected:
<?xml version="1.0" encoding="UTF-8"?>
<FLOW xmlns:ram=\"http://MY_LIBRARY\" xmlns:mar=\"http://ANOTHER_LIBRARY\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">
<Header>
<Source>Application1</Source>
<Time>2014-11-12T12:46:39</Time>
<Environment>TEST</Environment>
<Sequence>537</Sequence>
</Header>
<Data>
<OC_DC>
<DC_elements>
<Unit>
<Unit_ID>089789</Unit_ID>
<State>active</State>
</Unit>
<Unit>
<Unit_ID>459008</Unit_ID>
<State>inactive</State>
</Unit>
</DC_elements>
</OC_DC>
</Data>
</FLOW>
As you can see, the ram:FLOW tag became FLOW, and the mar:OC_DC tag became OC_DC.
I looked on Stack Overflow and other websites for a solution and didn't manage to find one. Could you please give me a hand with this?
Thank you in advance.
The xmlns:* attributes are namespace definitions (not libraries). The value of that attributes is a unique string that identifies the format/standard the elements belong to.
The attributes define a prefix for the unique string so that the XML document is smaller and more readable.
If you want to create an element (or attribute) inside a namespace you have to provide the namespace. In SimpleXMlElement the third argument is the namespace.
It seems to add the elements to the namespace of the parent node, if no namespace is provided. That means that you have to provide an empty string for any element without a namespace.
$root = new SimpleXMlElement('<ram:FLOW xmlns:ram="http://MY_LIBRARY" xmlns:mar="http://ANOTHER_LIBRARY"/>');
$root->addChild('header', null, '');
$data = $root->addChild('data', null, '');
$data->addChild('mar:OC_DC', null, 'http://ANOTHER_LIBRARY');
echo $root->asXml();
Output:
<?xml version="1.0"?>
<ram:FLOW xmlns:ram="http://MY_LIBRARY" xmlns:mar="http://ANOTHER_LIBRARY">
<header xmlns=""/>
<data xmlns="">
<mar:OC_DC/>
</data>
</ram:FLOW>
I haven't found a way to avoid the empty xmlns attributes.
DOM is more explicit. The create and append logic is separate.
const XMLNS_RAM = 'http://MY_LIBRARY';
const XMLNS_MAR = 'http://ANOTHER_LIBRARY';
$dom = new DOMDocument();
// appending an element with a namespace with define it if needed
$root = $dom->appendChild($dom->createElementNS(XMLNS_RAM, 'ram:FLOW'));
// setting the xmlns attribute explicit avoids the definition in descendant nodes
$root->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:mar', XMLNS_MAR);
$root->appendChild($dom->createElement('header'));
$data = $root->appendChild($dom->createElement('data'));
$data->appendChild($dom->createElementNS(XMLNS_MAR, 'mar:OC_DC'));
$dom->formatOutput = true;
echo $dom->saveXml();
Output:
<?xml version="1.0"?>
<ram:FLOW xmlns:ram="http://MY_LIBRARY" xmlns:mar="http://ANOTHER_LIBRARY">
<header/>
<data>
<mar:OC_DC/>
</data>
</ram:FLOW>

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

Get XML values using PHP

I have a XML file. Here is a small version of that.
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="fr">
<title>Liste des ebooks</title>
<updated>2012-03-01T01:23:24Z</updated>
<author>
<name>Drown Del</name>
</author>
<opensearch:totalResults>2338</opensearch:totalResults>
<opensearch:itemsPerPage>100</opensearch:itemsPerPage>
<entry>
<category term="Romans" label="Romans"/>
<category term="Aventures" label="Aventures"/>
</entry>
</feed>
First I would like to know how do we call something like opensearch:totalResults in XML terms.
And I need your help with obtaining following values with PHP.
<opensearch:totalResults>2338</opensearch:totalResults> I need to get 2338 to a PHP variable.
Thank you.
Thank you all for your answers.
I could fix it with following way.
$xml = simplexml_load_string($xmltext);
$val = $xml->xpath('opensearch:totalResults');
echo $val[0];
parse all of this information into PHP using DOM. Ex.
$doc = new DOMDocument;
$doc->loadXML($xml); //$xml is your xml string
echo $doc->getElementsByTagName("totalResults")->item(0)->nodeValue;
For your first question, opensearch:totalResults is the qualified name of a start tag. It is called a qualified name (you might come across this as QName) because it contains the namespace (opensearch) for the tag.
For your second question, you can easily parse your XML into a DOMDocument and then query it for the value of the relevant tag. There are lots of examples on SO and of course on Google; a basic one from PHP.net is here.
Important note: Your current XML document does not contain an XML namespace declaration for the opensearch namespace, and will not parse as a result. You need to add such a declaration by making a modification:
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="fr"
xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/">
If you need more powerful querying you can also use XPath. A minimal example would look like:
$doc = new DOMDocument();
$doc->loadXML($xml);
$xpath = new DOMXPath($doc);
$nodes = $xpath->query('//opensearch:totalResults');
foreach ($nodes as $node) {
echo $node->nodeValue;
}
opensearch is a namespace, so you can try to access it like:
$yourXml->children('openSearch', true)->totalResults
Hope it helps
Check this out the exact result in PHP
<?php
$xml ='<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="fr">
<title>Liste des ebooks</title>
<updated>2012-03-01T01:23:24Z</updated>
<author>
<name>Drown Del</name>
</author>
<opensearch:totalResults>2338</opensearch:totalResults>
<opensearch:itemsPerPage>100</opensearch:itemsPerPage>
<entry>
<category term="Romans" label="Romans"/>
<category term="Aventures" label="Aventures"/>
</entry>
</feed>';
$dom = new DOMDocument();
$dom->loadXML($xml);
$xmlD = simplexml_import_dom($dom);
echo $xmlD->totalResults;
?>
Read your xml file with simplexml_load_file as an object
Then get your variable like this:
$object->{'opensearch:totalResults'};

Categories