PHP: documentElement->childNodes warning - php

$xml = file_get_contents(example.com);
$dom = new DomDocument();
$dom->loadXML($xml);
$items = $dom->documentElement;
foreach($items->childNodes as $item) {
$childs = $item->childNodes;
foreach($childs as $i) {
echo $i->nodeValue . "<br />";
}
}
Now I get this warning in every 2nd foreach:
Warning: Invalid argument supplied for foreach() in file_example.php on line 14
Please help guys. Thanks!

Some nodes don't have children, so you're passing a null (invalid) argument to the foreach (just like the warning says).
To avoid the warnings you need to check if the current node has any children. For that you can use the DOMNode::hasChildNodes() method:
foreach($items->childNodes as $item) {
if ($item->hasChildNodes()) {
$childs = $item->childNodes;
foreach($childs as $i) {
echo $i->nodeValue . "<br />";
}
}
}

Related

Getting XML node names without duplicating in simplexml_load_file

I'm getting the node names of an XML using this code:
$url = 'https://www.toptanperpa.com/xml.php?c=shopphp&xmlc=e7ef2a0122';
$xml = simplexml_load_file($url) or die("URL Read Error");
echo $xml->getName() . "<br>";
foreach ($xml->children() as $child) {
echo $child->getName() . "<br>";
foreach ($child->children() as $child2) {
echo $child2->getName() . "<br>";
foreach ($child2->children() as $child3) {
echo $child3->getName() . "<br>";
foreach ($child3->children() as $child4) {
echo $child4->getName() . "<br>";
}
}
}
}
I'm getting the nodes and children correctly, however, it's duplicating.
Result is as below:
urunler
urun
urun_aktif
urun_metaKeywords
urun_metaDescription
urun_url
urun
urun_aktif
urun_metaKeywords
urun_metaDescription
urun_url
Should I just use array_unique or is there a better method?
Thanks
i used recursive function this is simple
function getChildrens($x) {
$children = array();
array_push($children, $x->getName());
foreach ($x->children() as $chld) {
$children = array_merge($children, getChildrens($chld));
}
return array_unique($children);
}
echo implode("<br>", getChildrens($xml));

dom document to get the href and nodeValue

I need to fetch the nodeValue and the HREF from this following snippet
<a class="head_title" href="/automotive/pr?sid=0hx">Automotive</a>
To achieve this I have done the following:
foreach($dom->getElementsByTagName('a') as $p) {
if($p->getAttribute('class') == 'head_title') {
foreach($p->childNodes as $child) {
$name = $child->nodeValue;
echo $name ."<br />";
echo $child->hasAttribute('href');
}
}
}
It returns me an error:
PHP Fatal error: Call to undefined method DOMText::hasAttribute()
Can anyone please help me with this.
hasAttribute is valid method for DOMElements but you cannot use it for text nodes. Can you check the type of node and then try to extract the value is its not a 'text' node. The following code might help you
foreach($p->childNodes as $child) {
$name = $child->nodeValue;
echo $name ."<br />";
if ($child->nodeType == 1) {
echo $child->hasAttribute('href');
}
}
It checks if the node is of type 'DOMElement' and invokes hasAttribute method only if it is a DOMElement.
Yes...I did the changes in my coding like the following:
foreach($dom->getElementsByTagName('a') as $link) {
if($link->getAttribute('class') == 'head_title') {
$link2 = $link->nodeValue;
$link1 = $link->getAttribute('href');
echo "".$link2."<br/>";
}
}
And it works for me!

PHP how to count xml elements in object returned by simplexml_load_file(),

I have inherited some PHP code (but I've little PHP experience) and can't find how to count some elements in the object returned by simplexml_load_file()
The code is something like this
$xml = simplexml_load_file($feed);
for ($x=0; $x<6; $x++) {
$title = $xml->channel[0]->item[$x]->title[0];
echo "<li>" . $title . "</li>\n";
}
It assumes there will be at least 6 <item> elements but sometimes there are fewer so I get warning messages in the output on my development system (though not on live).
How do I extract a count of <item> elements in $xml->channel[0]?
Here are several options, from my most to least favourite (of the ones provided).
One option is to make use of the SimpleXMLIterator in conjunction with LimitIterator.
$xml = simplexml_load_file($feed, 'SimpleXMLIterator');
$items = new LimitIterator($xml->channel->item, 0, 6);
foreach ($items as $item) {
echo "<li>{$item->title}</li>\n";
}
If that looks too scary, or not scary enough, then another is to throw XPath into the mix.
$xml = simplexml_load_file($feed);
$items = $xml->xpath('/rss/channel/item[position() <= 6]');
foreach ($items as $item) {
echo "<li>{$item->title}</li>\n";
}
Finally, with little change to your existing code, there is also.
$xml = simplexml_load_file($feed);
for ($x=0; $x<6; $x++) {
// Break out of loop if no more items
if (!isset($xml->channel[0]->item[$x])) {
break;
}
$title = $xml->channel[0]->item[$x]->title[0];
echo "<li>" . $title . "</li>\n";
}
The easiest way is to use SimpleXMLElement::count() as:
$xml = simplexml_load_file($feed);
$num = $xml->channel[0]->count();
for ($x=0; $x<$num; $x++) {
$title = $xml->channel[0]->item[$x]->title[0];
echo "<li>" . $title . "</li>\n";
}
Also note that the return of $xml->channel[0] is a SimpleXMLElement object. This class implements the Traversable interface so we can use it directly in a foreach loop:
$xml = simplexml_load_file($feed);
foreach($xml->channel[0] as $item {
$title = $item->title[0];
echo "<li>" . $title . "</li>\n";
}
You get count by count($xml).
I always do it like this:
$xml = simplexml_load_file($feed);
foreach($xml as $key => $one_row) {
echo $one_row->some_xml_chield;
}

First and Second last xml nodes

I only want the first and the second last Area nodes - how would I do that here?
$url = "http://developer.multimap.com/API/geocode/1.2/OA10081917657704697?qs=Byker&countryCode=GB";
$results = simplexml_load_file($url);
foreach($results->Location as $location) {
echo "<hr />";
foreach($location->Address as $address) {
foreach($address->Areas as $areas) {
foreach($areas->Area as $area) {
echo $area;
echo "<br />";
}
}
}
}
Update: If you have those foreach-loops anyway you can simply use:
$url = "http://developer.multimap.com/API/geocode/1.2/OA10081917657704697?qs=Byker&countryCode=GB";
$results = simplexml_load_file($url);
foreach($results->Location as $location) {
foreach($location->Address as $address) {
foreach( $address->Areas as $areas) {
// <-- todo: add test if those two elements exist -->
echo $areas->Area[0], ' - ', $areas->Area[count($areas->Area)-1], "\n";
}
}
}
You can use XPath for this.
<?php
$doc = new SimpleXMLElement('<foo>
<bar>a</bar>
<bar>b</bar>
<bar>c</bar>
<bar>x</bar>
<bar>y</bar>
<bar>z</bar>
</foo>');
$nodes = $doc->xpath('bar[position()=1 or position()=last()-1]');
foreach( $nodes as $n ) {
echo $n, "\n";
}
prints
a
y
see also:
PHP Manual: SimpleXMLElement::xpath()
XPath: predicates
XPath: position()
XPath: last()
Here it is:
<?php
$url = 'http://developer.multimap.com/API/geocode/1.2/OA10081917657704697?qs=Byker&countryCode=GB';
$results = simplexml_load_file($url);
$areas = array();
foreach ($results->Location->Address->Areas->Area as $area)
{
$areas[] = (string) $area;
}
$first = $areas[0];
$second_last = $areas[count($areas)-2];
?>

Inject XML into a node using a string

I am rebuilding a nodes children by saving them out to an array as strings, trashing them in the XML, inserting a new child node into the array as a string... now I want to loop through the array and write them back out to the original node. The problem is I can't find anything on how to add a child node using a string.
See below for my code. Thanks!!!
$xml = simplexml_load_file($url);
$questionGroup = $xml->qa[intval($id)];
$children = array(); // create empty array
foreach ($questionGroup->children() as $element) { // loop thru children
array_push($children, $element->asXML()); // save XML into array
}
//unset($questionGroup->answer);
//unset($questionGroup->question);
//create new node
$newNode = '<answer><title>'.$title.'</title><description>'.$description.'</description><subName>'.$subName.'</subName><date>'.$date.'</date><timestamp>'.$timestamp.'</timestamp></answer>';
echo "children count: ".count($children);
echo "<br /><br />";
print_r($children);
echo "<br /><br />";
// insert new
array_splice($children,intval($elementIndex),0,$newNode);
echo "children count: ".count($children);
echo "<br /><br />";
print_r($children);
echo "<br /><br />";
echo $questionGroup->asXML();
foreach ($children as $element) { // loop thru array
echo "<br /><br />";
echo $element;
//$questionGroup->addChild(simplexml_load_string($element)); // add array element to the empty questionGroup
}
echo "<br /><br />";
echo "questionGroup: ".$questionGroup;
UPDATE:
I found a function that I modified and was able to get working:
function append_simplexml(&$simplexml_to, &$simplexml_from)
{
$childNode = $simplexml_to->addChild($simplexml_from->getName(), "");
foreach ($simplexml_from->children() as $simplexml_child)
{
$simplexml_temp = $childNode->addChild($simplexml_child->getName(), (string) $simplexml_child);
foreach ($simplexml_child->attributes() as $attr_key => $attr_value)
{
$simplexml_temp->addAttribute($attr_key, $attr_value);
}
// append_simplexml($simplexml_temp, $simplexml_child);
}
}
With this usage in my foreach() loop:
foreach ($children as $element) { // loop thru array
append_simplexml($questionGroup, new SimpleXMLElement($element));
}
You can't do that with SimpleXML alone. There is a nice way to do this with the DOM extension and the DOMDocumentFragment class. (Please note that I didn't try to understand your logic in the example provided, but you should be able to implement the simple sample below into your code).
$xml = simplexml_load_string('<root><parent/></root>');
// get the parent node under which you want to insert your XML fragement
$parent = dom_import_simplexml($xml->parent);
// create the XML fragment
$fragment = $parent->ownerDocument->createDocumentFragment();
// append the XML literal to your fragment
$fragment->appendXML('<child id="1"/><child id="2"><grandchild/></child>');
// append the fragment to the parent node
$parent->appendChild($fragment);
echo $xml->asXML();
/*
* <?xml version="1.0"?>
* <root><parent><child id="1"/><child id="2"><grandchild/></child></parent></root>
*/
Links:
dom_import_simplexml()
DOMDocument::createDocumentFragment()
DOMDocumentFragment::appendXML()
DOMNode::appendChild()
SimpleXMLElement::asXML()
I found a function that I modified and was able to get working:
function append_simplexml(&$simplexml_to, &$simplexml_from)
{
$childNode = $simplexml_to->addChild($simplexml_from->getName(), "");
foreach ($simplexml_from->children() as $simplexml_child)
{
$simplexml_temp = $childNode->addChild($simplexml_child->getName(), (string) $simplexml_child);
foreach ($simplexml_child->attributes() as $attr_key => $attr_value)
{
$simplexml_temp->addAttribute($attr_key, $attr_value);
}
// append_simplexml($simplexml_temp, $simplexml_child);
}
}
With this usage in my foreach() loop:
foreach ($children as $element) { // loop thru array
append_simplexml($questionGroup, new SimpleXMLElement($element));
}
You should be able to do something like this:
$child = new SimpleXMLElement($newNode);
$yourxmlobject->addChild($child);

Categories