How to iterate through an XML element node with dynamic children - php

I currently have the following XML structure:
<root>
<maininfo>
<node>
<tournament_id>3100423</tournament_id>
<games>
<a_0>
<id>23523636</id>
<type>
<choice_4>
<choice_id>345</choice_id>
<choice_4>
<choice_9>
<choice_id>345</choice_id>
<choice_9>
... etc
</type>
</a_0>
<a_1></a_1>
<a_2></a_2>
...etc
</games>
</info>
</node>
</root>
I can easily get the id of the first node element "a_0" by just doing:
maininfo[0]->a_3130432[0]->games[0]->a_1[0]->id;
My issue is:
How do I automatically iterate (with a foreach) through all a_0, a_1, a_2 and get the values of each of these node elements and all of their children like "345" in <choice_id>345</choice_id>?
The ending numbers of a_0, a_1 + the children of choice_4, choice_9, are dynamically created and there are no logic in the _[number] counting up with +1 for each next element.

As it has been outlined previously on Stackoverflow (for example in Read XML dynamic PHP) and as well generally in the PHP manual (for example in Basic SimpleXML usage), you can iterate over all child elements by using foreach.
For example to go over all a_* elements, it's just
foreach ($xml->maininfo->node->games[0] as $name => $a) {
echo $name, "\n";
}
Output:
a_0
a_1
a_2
You then want to iterate over these their ->type children again. This is possible in pure PHP by putting one foreach into a another:
foreach ($xml->maininfo->node->games[0] as $name => $a) {
echo $name, "\n";
if (!$a->type[0]) {
continue;
}
foreach ($a->type[0] as $name => $choice) {
echo ' +- ', $name, "\n";
}
}
This now outputs:
a_0
+- choice_4
+- choice_9
a_1
a_2
This starts to get a bit complicated. As you can imagine since XML is famous for it's tree structures, you're not the first one running into this problem. Therefore a query-language to get elements from an XML document has been invented: Xpath.
With Xpath you can access XML data as if it was a file-system. As I know that each a_* element is a child of games and each choice_* element a child of type, it's pretty straight forward:
/*/maininfo/node/games/*/type/*
^ ^ ^
| | choice_*
root |
a_*
In PHP Simplexml this looks like:
$choices = $xml->xpath('/*/maininfo/node/games/*/type/*');
foreach ($choices as $choice) {
echo $choice->getName(), ': ', $choice->choice_id, "\n";
}
Output:
choice_4: 345
choice_9: 345
As this example shows, the data is now retrieved with a single foreach.
If you as well need access to the <a_*> elements, you need to have multiple foreach's or your own iteration but that is even a more advanced topic which I'd say would extend over the limits of your question.
I hope this is helpful so far. See as well SimpleXMLElement::children() which also gives all children (like ->games[0] in the first example). All example codes are as well available as a working, interactive online-demo.

If I understand it well, you can do something like:
for($i = 0; $i < $max; ++$i){
$a = $parentNode->{'a_'.$i};
}

You can do this very easily using SimpleXML :
<?php
$xmlStr = "<?xml version='1.0' standalone='yes'?>
<root>
<maininfo>
<node>
<tournament_id>3100423</tournament_id>
<games>
<a_0>
<id>23523636</id>
<type>
<choice_4>
<choice_id>345</choice_id>
</choice_4>
<choice_9>
<choice_id>345</choice_id>
</choice_9>
</type>
</a_0>
<a_1></a_1>
<a_2></a_2>
</games>
</node>
</maininfo>
</root>";
$xmlRoot = new SimpleXMLElement($xmlStr);
$i = 0;
foreach($xmlRoot->maininfo[0]->node[0]->games[0] as $a_x)
{
echo $i++ . " - " . htmlentities($a_x->asXML()) . "<br/>";
}
?>
I have modified some parts of your XML string to make it syntactically correct. You can view the results at http://phpfiddle.org/main/code/56q-san

Related

Check if child exists? - SimpleXML (PHP)

I have different XML files where I renamed for each XML file all individual tags, so that every XML file has the same tag name. That was easy because the function was customized for the XML file.
But instand of writing 7 new functions for each XML file now I want to check if a XML file has a specidifed child or not. Because if I want to say:
foreach ($items as $item) {
$node = dom_import_simplexml($item);
$title = $node->getElementsByTagName('title')->item(0)->textContent;
$price = $node->getElementsByTagName('price')->item(0)->textContent;
$url = $node->getElementsByTagName('url')->item(0)->textContent;
$publisher = $node->getElementsByTagName('publisher')->item(0)->textContent;
$category = $node->getElementsByTagName('category')->item(0)->textContent;
$platform = $node->getElementsByTagName('platform')->item(0)->textContent;
}
I get sometimes: PHP Notice: Trying to get property of non-object in ...
For example. Two different XML sheets. One contains publisher, category and platform, the other not:
XML 1:
<products>
<product>
<desc>This is a Test</desc>
<price>11.69</price>
<price_base>12.99</price_base>
<publisher>Stackoverflow</publisher>
<category>PHP</category>
</packshot>
<title>Check if child exists? - SimpleXML (PHP)</title>
<url>http://stackoverflow.com/questions/ask</url>
</product>
</products>
XML 2:
<products>
<product>
<image></image>
<title>Questions</title>
<price>23,90</price>
<url>google.de/url>
<platform>Stackoverflow</platform>
</product>
</products>
You see, sometimes one XML file contains publisher, category and platform but sometimes not. But it could also be that not every node of a XML file contains all attributes like in the first!
So I need to check for every node of a XML file individual if the node is containing publisher, category or/and platform.
How can I do that with SimpleXML?
I thought about switch case but at first I need to check which childs are contained in every node.
EDIT:
Maybe I found a solution. Is that a solution or not?
if($node->getElementsByTagName('platform')->item(0)){
echo $node->getElementsByTagName('platform')->item(0)->textContent . "\n";
}
Greetings and Thank You!
One way to rome... (working example)
$xml = "<products>
<product>
<desc>This is a Test</desc>
<price>11.69</price>
<price_base>12.99</price_base>
<publisher>Stackoverflow</publisher>
<category>PHP</category>
<title>Check if child exists? - SimpleXML (PHP)</title>
<url>http://stackoverflow.com/questions/ask</url>
</product>
</products>";
$xml = simplexml_load_string($xml);
#set fields to look for
foreach(['desc','title','price','publisher','category','platform','image','whatever'] as $path){
#get the first node
$result = $xml->xpath("product/{$path}[1]");
#validate and set
$coll[$path] = $result?(string)$result[0]:null;
#if you need here a local variable do (2 x $)
${$path} = $coll[$path];
}
#here i do array_filter() to remove all NULL entries
print_r(array_filter($coll));
#if local variables needed do
extract($coll);#this creates $desc, $price
Note </packshot> is an invalid node, removed here.
xpath syntax https://www.w3schools.com/xmL/xpath_syntax.asp
Firstly, you're over-complicating your code by switching from SimpleXML to DOM with dom_import_simplexml. The things you're doing with DOM can be done in much shorter code with SimpleXML.
Instead of this:
$node = dom_import_simplexml($item);
$title = $node->getElementsByTagName('title')->item(0)->textContent;
you can just use:
$title = (string)$item->title[0];
or even just:
$title = (string)$item->title;
To understand why this works, take a look at the SimpleXML examples in the manual.
Armed with that knowledge, you'll be amazed at how simple it is to see if a child exists or not:
if ( isset($item->title) ) {
$title = (string)$item->title;
} else {
echo "There is no title!";
}

Two dimensional array

i've tried to find this out by myself before asking but cant really figure it out.
What I have is a loop, it's actually a loop which reads XML data with simplexml_load_file
Now this XML file has data which I want to read and put into an array.. a two dimensional array actually..
So the XML file has a child called Tag and has a child called Amount.
The amount is always differnt, but the Tag is usually the same, but can change sometimes too.
What I am trying to do now is:
Example:
This is the XML example:
<?xml version="1.0"?>
<Data>
<Items>
<Item Amount="9,21" Tag="tag1"/>
<Item Amount="4,21" Tag="tag1"/>
<Item Amount="6,21" Tag="tag2"/>
<Item Amount="1,21" Tag="tag1"/>
<Item Amount="6,21" Tag="tag2"/>
</Data>
</Items>
Now i have a loop which reads this, sees what tag it is and adds up the amounts.
It works with 2 loops and two different array, and I would like to have it all in one array in single loop.
I tried something like this:
$tags = array();
for($k = 0; $k < sizeof($tags); $k++)
{
if (strcmp($tags[$k], $child['Tag']) == 0)
{
$foundTAG = true;
break;
}
else
$foundTAG = false;
}
if (!$foundTAG)
{
$tags[] = $child['Tag'];
}
and then somewhere in the code i tried different variations of adding to the array ($counter is what counts the Amounts together):
$tags[$child['Tag']][$k] = $counter;
$tags[$child['Tag']][] = $counter;
$tags[][] = $counter;
i tried few other combinations which i already deleted since it didnt work..
Ok this might be a really noob question, but i started with PHP yesterday and have no idea how multidimensional arrays work :)
Thank you
this is how you can iterate over the returned object from simple xml:
$xml=simplexml_load_file("/home/chris/tmp/data.xml");
foreach($xml->Items->Item as $obj){
foreach($obj->Attributes() as $key=>$val){
// php will automatically cast each of these to a string for the echo
echo "$key = $val\n";
}
}
so, to build an array with totals for each tag:
$xml=simplexml_load_file("/home/chris/tmp/data.xml");
$tagarray=array();
// iterate over the xml object
foreach($xml->Items->Item as $obj){
// reset the attr vars.
$tag="";
$amount=0;
// iterate over the attributes setting
// the correct vars as you go
foreach($obj->Attributes() as $key=>$val){
if($key=="Tag"){
// if you don't cast this to a
// string php (helpfully) gives you
// a psuedo simplexml_element object
$tag=(string)$val[0];
}
if($key=="Amount"){
// same as for the string above
// but cast to a float
$amount=(float)$val[0];
}
// when we have both the tag and the amount
// we can store them in the array
if(strlen($tag) && $amount>0){
$tagarray[$tag]+=$amount;
}
}
}
print_r($tagarray);
print "\n";
This will break horribly should the schema change or you decide to wear blue socks (xml is extremely colour sensitive). As you can see dealing with the problem child that is xml is tedious - yet another design decision taken in a committee room :-)

xml and php getting tag elements with certain element and outputting

I am have two xml files.. I first get one and loop through it then I need to take an id from the first xml file and find it in the second one and echo out the results associated with that id. If I were to do this with SQL I would simply do this:
$query = (SELECT * FROM HotelSummary WHERE roomTypeCode = '$id') or die();
while($row=mysql_fetch_array($query)){
$name = $row['Name'];
}
echo $name;
How can I do this is in xml and php??
I recommend you to read the DOMDocument documentation.
It's quite heavy but also powerful (not always clear what happens, but the Internet shold always give you a solution)
You can simply walk through your first document, finding your Id and then find your DOMElement via an XPath.
<?php
$dom = new DOMDocument();
$dom->load('1.xml');
foreach ($dom->getElementsByTagName('article') as $node) {
// your conditions to find out the id
$id = $node->getAttribute('id');
}
$dom = new DOMDocument();
$dom->load('2.xml');
$xpath = new DOMXPath($dom);
$element = $xpath->query("//*[#id='".$id."']")->item(0);
// would echo "top_2" based on my example files
echo $element->getAttribute('name');
Based on following test files:
1.xml
<?xml version="1.0" encoding="UTF-8"?>
<articles>
<article id="foo_1">
<title>abc</title>
</article>
<article id="foo_2">
<title>def</title>
</article>
</articles>
2.xml
<?xml version="1.0" encoding="UTF-8"?>
<tests>
<test id="foo_1" name="top_1">
</test>
<test id="foo_2" name="top_2">
</test>
</tests>
Use SimpleXML to create an object representation of the file. You can then loop through the elements of the Simple XML object.
Depending on the format of the XML file:
Assuming it is:
<xml>
<roomTypeCode>
<stuff>stuff</stuff>
<name>Skunkman</name>
</roomTypeCode>
<roomTypeCode>
<stuff>other stuff</stuff>
<name>Someone Else</name>
</roomTypeCode>
</xml>
It would be something like this:
$xml = simplexml_load_file('xmlfile.xml');
for($i = 0; $i < count($xml->roomTypeCode); $i++)
{
if($xml->roomTypeCode[$i]->stuff == "stuff")
{
$name = $xml->roomTypeCode[$i]->name;
}
}
That connects to the XML file, finds how many roomTypeCode entries there are, searches for the value of "stuff" within and when it matches it correctly, you can access anything having to do with that XML entry.

How to snag highest numbered XML item with PHP?

I've basically got an XML file full of product information for use in an ecommerce system. I've been creating a script that converts these XML files into a .CSV with the data structured in a format the ecommerce system can handle (So I don't need to copy/paste columns over every time the vendor provides new XML files). The category of each product is defined like this:
<web_category1>3</web_category1>
<web_category2>1</web_category2>
<web_category3>6</web_category3>
web_category3 being the category of the item and 1 and 2 being the parent categories of the product's category. The thing is that some items are nested under 2 categories..or sometimes 5. So I need to figure out a way for PHP to grab the web_category with the highest number after it since that's always going to be the product's category.
Thanks!
#ben's answer is correct, but is a little intense for me. SimpleXMLElement objects are nice because you can easily cast them to an array. So, a simpler solution would be to cast it to an array and use max to determine the highest value in the resulting array:
$str = '
<item>
<web_category1>3</web_category1>
<web_category2>1</web_category2>
<web_category3>6</web_category3>
</item>
';
$xml = new SimpleXMLElement($str);
echo max((array)$xml); // outputs: 6
UPDATE
Based on your comment below, let's assume you need to get the max of all the <item> elements that occur in an XML file and not just one (like above). To handle this you could use SimpleXMLElement::xpathdocs to get an array of all the occurrences of <item> then execute the same casting trick inside a loop over the xpath result:
$str = '
<xml>
<product1>
<item>
<web_category1>3</web_category1>
<web_category2>1</web_category2>
<web_category3>6</web_category3>
</item>
</product1>
<product2>
<item>
<web_category4>17</web_category4>
<web_category5>0</web_category5>
</item>
</product2>
</xml>
';
$xml = new SimpleXMLElement($str);
$allItems = array();
$items = $xml->xpath('//item');
foreach($items as $item) {
$allItems = array_merge($allItems, (array)$item);
}
echo max($allItems); // outputs: 17
UPDATE 2
Okay, last time. If this isn't exactly what you're trying to do, you should at least have enough examples to figure it out from here. Consider:
$str = '
<xml>
<product1>
<web_category1>3</web_category1>
<web_category2>1</web_category2>
<web_category3>6</web_category3>
</product1>
<product2>
<web_category4>17</web_category4>
<web_category5>0</web_category5>
</product2>
<product3>
<web_category6>17</web_category6>
<web_category7>21</web_category7>
</product3>
</xml>
';
$xml = new SimpleXMLElement($str);
// assumes that product node names start with "product"
$products = $xml->xpath("//*[starts-with(name(),'product')]");
foreach ($products as $p) {
$catNames = array_keys((array)$p);
$catNums = preg_replace("/[^\d]/", "", $catNames);
echo $p->getName() . ' - highest category: ' . max($catNums) . "\n";
}
The above code outputs the following:
product1 - highest category: 3
product2 - highest category: 5
product3 - highest category: 7
assuming your XML is something like this:
<item>
<web_category1>3</web_category1>
<web_category2>1</web_category2>
<web_category3>6</web_category3>
</item>
And you have a SimpleXMLElement object for <item>, this should do it:
$highest_web_category_number = -1;
$value_of_highest_web_category_number = -1;
foreach($item->getChildren() as $name => $data) {
if(strpos($name, 'web_category') === 0) {
$web_category_number = substr($name, strlen('web_category'));
if($web_category_number > $highest_web_category_number) {
$highest_web_category_number = $web_category_number
$value_of_highest_web_category_number = $data;
}
}
}

How does one use SimpleDOM sortedXPath to sort on node value?

XML newbie here!
I have a file containing only the following XML:
<tags>
<tag>orange</tag>
<tag>apple</tag>
<tag>banana</tag>
</tags>
I want to ouput the tags alphabetically.
I am trying to use SimpleDOM library and its sortedXPath method. Here's what I have so far, which outputs the tags unsorted.
$allTags = simpledom_load_file("tags.xml");
foreach ($allTags->sortedXPath("//tags/tag", "tag") as $i => $item)
{
echo($item);
}
Could someone tell me how to write this correctly so it works? Cheers!
In XPath, you can refer the current node (called "context node") using a single dot . so if you're accessing //tags/tag you have to use . to get the value of tag. Your example becomes:
$allTags = simpledom_load_file("tags.xml");
foreach ($allTags->sortedXPath("//tags/tag", ".") as $i => $item)
{
echo($item);
}

Categories