Simplexml is giving me the wrong results - php

I have a simple xml below:
<?xml version="1.0" encoding="utf-8"?>
<catalogue>
<category name="textbook" id="100" parent="books">
<product id="20000">
<author>Gambardella, Matthew</author>
<title>XML Developer's Guide</title>
<genre>Computer</genre>
<price>44.95</price>
<publish_date>2000-10-01</publish_date>
<description>An in-depth look at creating applications
with XML.</description>
</product>
<product id="20001">
<author>Gambardellas, Matthew</author>
<title>XML Developer's Guide</title>
<genre>Computer</genre>
<price>44.95</price>
<publish_date>2000-10-01</publish_date>
<description>An in-depth look at creating applications
with XML.</description>
</product>
</category>
<category name="fiction" id="101" parent="books">
<product id="2001">
<author>Ralls, Kim</author>
<title>Midnight Rain</title>
<genre>Fantasy</genre>
<type>Fiction</type>
<price>5.95</price>
<publish_date>2000-12-16</publish_date>
<description>A former architect battles corporate zombies, an evil sorceress, and her own childhood to become queen
of the world.</description>
</product>
</category>
</catalogue>
I am using php simplexml library to parse it as follows: (note there are two category nodes. The first category contains two 'product' children. My aim is to get an array that contains those two children of first 'category'
$xml = simplexml_load_file($xml_file) or die ("unable to load XML File!".$xml_file);
//for each product, print out info
$cat = array();
foreach($xml->category as $category)
{
if($category['id'] == 100)
{
$cat = $category;
break;
}
}
$prod_arr = $category->product;
Here is the problem. I am expecting an array with two products children but its only returning one product. What am I doing wrong or is this a php bug? Please help!

You can use SimpleXMLElement::xpath() to get all product elements that are children of a specific category element. E.g.
// $catalogue is your $xml
$products = $catalogue->xpath('category[#id="100"]/product');
foreach($products as $p) {
echo $p['id'], ' ', $p->title, "\n";
}
prints
20000 XML Developer's Guide
20001 XML Developer's Guide

For start, your XML file is not well defined. You should probably start and end it with <categories> tag.
Replace the last assignment with the following:
$prod_array = array();
foreach ($cat->product as $p) {
$prod_array[] = $p;
}

$cat = array();
foreach ($xml->category as $category)
{
$attributes = $category->attributes();
if(isset($attributes['id']) && $attributes['id'] == 100)
{
$cat = $category;
break;
}
}

Related

Parsing throuth complex xml with PHP

I have a xml file with structure like this:
<categories>
<category>
<id></id>
<name></name>
</category>
...
</categories>
<products>
<product>
<id></id>
<name></name>
</product>
...
</products>
<params>
<param>
<id></id>
<name></name>
</param>
...
</params>
<product_params>
<product_param>
<param_id></param_id>
<product_id></product_id>
</product_param>
...
</product_params>
How do I display product nodes, right category (that matches id) and all params for that product?
I tried doing something like this:
$xml = simplexml_load_file('file.xml');
$products = $xml->products;
$product_params = $xml->product_params->product_param;
foreach ($products->product as $product) {
echo '<p>'.$product->name.'</p>';
for($i=0; $i < count($product_params) ;$i++) {
if($product->id == $product_params[$i]->product_id) {
}
}
}
The file is too big and script crashes. Plus with my "solution" I would need at least one more loop nested in there.

how i can store xml dom object in array

i want to store xml Dom object in array and retrieve them back from array using array index
for example
arrayoftags[index] = $this->dom->createElement("plist");
index++;
// and retrive back
$dict = $this->dom->createElement("dict");
arrayoftags[index]->appendChild($dict);
/* some thing like that
<plist>
<dict>
</dict>
</plist>
*/
what i am doing wrong please guide me in right direction and thanks in advance
I am not sure why you want to use an array. So the answer is a little more generic. But yes you can store XML nodes in variables including arrays.
$dom = new DOMDocument();
$created = [];
$created['plist'] = $dom->appendChild($dom->createElement('plist'));
$created['dict'] = $dom->createElement('dict');
$created['plist']->appendChild($created['dict']);
echo $dom->saveXml();
Output:
<?xml version="1.0"?>
<plist><dict/></plist>
appendChild() returns the node it appended. So it is possible to use it directly on a createElement() (or other create* call) and assign the result to a variable. So if the parent node is just stored in a variable the example will be cleaner.
$dom = new DOMDocument();
$plist = $dom->appendChild($dom->createElement('plist'));
$plist->appendChild($dom->createElement('dict'));
echo $dom->saveXml();
Now the DOM already is a data structure, you can use Xpath to fetch some nodes from it, why store the nodes in a second structure (the array)?
$dom = new DOMDocument();
$plist = $dom->appendChild($dom->createElement('plist'));
$plist->appendChild($dom->createElement('dict'));
$xpath = new DOMXpath($dom);
foreach ($xpath->evaluate('//*') as $node) {
var_dump($node->nodeName);
}
Output:
string(5) "plist"
string(4) "dict"
Please refer this code, I think this will help you.
<!-- suppose this is book.xml file -->
<?xml version="1.0"?>
<catalog>
<book id="bk101">
<author>Gambardella, Matthew</author>
<title>XML Developer's Guide</title>
<genre>Computer</genre>
<price>44.95</price>
<publish_date>2000-10-01</publish_date>
<description>An in-depth look at creating applications
with XML.</description>
</book>
<book id="bk102">
<author>Ralls, Kim</author>
<title>Midnight Rain</title>
<genre>Fantasy</genre>
<price>5.95</price>
<publish_date>2000-12-16</publish_date>
<description>A former architect battles corporate zombies,
an evil sorceress, and her own childhood to become queen
of the world.</description>
</book>
<catalog>
//PHP file
$dom = new DOMDocument();
$dom->loadXml('book.xml');
$xpath = new DOMXpath($dom);
$result = [];
foreach ($xpath->evaluate('//book') as $book) {
$result[] = [
'id' => $xpath->evaluate('string(#id)', $book),
'Author' => $xpath->evaluate('string(author)', $book),
'Title' => $xpath->evaluate('string(title)', $book),
'Genre' => $xpath->evaluate('string(genre)', $book),
'Price' => $xpath->evaluate('number(price)', $book),
'Publish Date' => $xpath->evaluate('string(publish_date)', $book),
'Description' => $xpath->evaluate('string(description)', $book)
];
}
var_dump($result);

XML count elements, If id exists increment by one

What i am trying to do is count the elements under the root element. Then check if one id on that same level has the id value. When this occurs it needs to increment by one.
The code
public function _generate_id()
{
$id = 0;
$xpath = new DOMXPath($this->_dom);
do{
$id++;
} while($xpath->query("/*/*[#id=$id]"));
return $id;
}
example xml
<?xml version="1.0"?>
<catalog>
<book id="0">
<author>Gambardella, Matthew</author>
<title>XML Developer's Guide</title>
<genre>Computer</genre>
<price>44.95</price>
<publish_date>2000-10-01</publish_date>
<description>An in-depth look at creating applications
with XML.</description>
</book>
<book id="1">
<author>Ralls, Kim</author>
<title>Midnight Rain</title>
<genre>Fantasy</genre>
<price>5.95</price>
<publish_date>2000-12-16</publish_date>
<description>A former architect battles corporate zombies,
an evil sorceress, and her own childhood to become queen
of the world.</description>
</book>
</catalog>
You can use the following xpath query to get the maximum value of the id attribute:
$result = $xpath->query('/*/*[not(../*/#id > #id)]/#id');
In your function you can return this value incremented by 1:
return intval($result->item(0)->nodeValue) + 1;
Update: You can do the increment operation using XPath as well. Note DOMXPath::evaluate():
return $xpath->evaluate('/*/*[not(../*/#id > #id)]/#id + 1');
|------- +1 in xpath
This will give you 2 - but as a double. I would suggest to convert to integer before returning the result:
return (integer) $xpath->evaluate('/*/*[not(../*/#id > #id)]/#id + 1');
I suggest you create an array of all existing ID values first (which is a single xpath query) and then you check against it:
$id = 0;
while(isset($ids[$id])) {
$id++;
}
echo $id; # 2
Creating such a list is trivial running the xpath on SimpleXML, however this can be easily ported to DOMXPath as well with iterator_to_array:
<?php
$buffer = <<<BUFFER
<?xml version="1.0"?>
<catalog>
<book id="0">
<author>Gambardella, Matthew</author>
<title>XML Developer's Guide</title>
<genre>Computer</genre>
<price>44.95</price>
<publish_date>2000-10-01</publish_date>
<description>An in-depth look at creating applications
with XML.</description>
</book>
<book id="1">
<author>Ralls, Kim</author>
<title>Midnight Rain</title>
<genre>Fantasy</genre>
<price>5.95</price>
<publish_date>2000-12-16</publish_date>
<description>A former architect battles corporate zombies,
an evil sorceress, and her own childhood to become queen
of the world.</description>
</book>
</catalog>
BUFFER;
$xml = simplexml_load_string($buffer);
$ids = array_flip(array_map('intval', $xml->xpath("/*/*/#id")));
Interactive Demo
Additionally I suggest you to not use 0 (zero) as ID value.
Use simplexml, try this
$xml = simplexml_load_string($this->_dom);
$id = is_array($xml->book) ? $xml->book[count($xml->book)-1]->attributes()->id : 0;
return $id;

is it possible to receive more information from a query by using Xpath in PHP than just one? (-nodeValue)

Hoi,
for example, i have a xml file :
<bookstore>
<book category="COOKING">
<title lang="en">Everyday Italian</title>
<author>Giada De Laurentiis</author>
<year>2005</year>
<price>30.00</price>
</book>
<book category="WEB">
<title lang="en">XQuery Kick Start</title>
<author>James McGovern</author>
<author>Per Bothner</author>
<author>Kurt Cagle</author>
<author>James Linn</author>
<author>Vaidyanathan Nagarajan</author>
<year>2003</year>
<price>49.99</price>
</book>
</bookstore>
And I want all the titles and prices
I could write this code
<php
$res = $xpath->query('/bookstore/book/title');
foreach ($res as $item) {
echo "{$item->nodeValue}";
}
$res = $xpath->query('/bookstore/book/price');
foreach ($res as $item) {
echo "{$item->nodeValue}";
}
?>
But this looks very ugly,
Is there another possibility so that I can combine those 2 blocks of code?
something like this?
<php
$res = $xpath->query('/bookstore/book');
foreach ($res as $item) {
echo "{$item->title} <br/> {$item->price}";
}
?>
Kind regards
You could adjust your XPATH with concatenate to read '/bookstore/book/title | /bookstore/book/price'
This would give you a node list as such:
<title lang="en">Everyday Italian</title>
<price>30.00</price>
<title lang="en">XQuery Kick Start</title>
<price>49.99</price>
...
...
Then just pull values 1 and 2 in each foreach loop cycle for what you like.
Hope this helps!

XML/PHP to Array Issues

I've been tearing my hair out with this now for a few hours and thought I'd post it up here to see if anybody had any suggestions.
Basically I am receving some XML date via SOAP/Curl call which looks like this:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<LocationAvailabilityResponse xmlns="">
<getAvailabilityReturn>
<errors />
<requestID>389851</requestID>
<hotels>
<hotels>
<hotel>
<apt>false</apt>
<distance>0</distance>
<fromPrice>18.5</fromPrice>
<hotelName>Britannia Hotel Stockport</hotelName>
<id>5165</id>
<images>
<images>
<hasThumbnail>true</hasThumbnail>
<height>187</height>
<thumbnailHeight>50</thumbnailHeight>
<thumbnailURL>http://static.superbreak.net/content/images/Hotel/thumbs/britannia_hotel_stockport_swimming_pool_1_swi_5165.JPG</thumbnailURL>
<thumbnailWidth>68</thumbnailWidth>
<title>Britannia Hotel Stockport</title>
<url>http://static.superbreak.net/content/images/Hotel/britannia_hotel_stockport_swimming_pool_1_swi_5165.JPG</url>
<width>257</width>
</images>
<images>
<hasThumbnail>false</hasThumbnail>
<height>187</height>
<thumbnailHeight>0</thumbnailHeight>
<thumbnailURL>http://static.superbreak.net/content/images/Hotel/thumbs/britannia_hotel_stockport_swimming_pool_2_swi_5165.JPG</thumbnailURL>
<thumbnailWidth>0</thumbnailWidth>
<title>Swimming Pool</title>
<url>http://static.superbreak.net/content/images/Hotel/britannia_hotel_stockport_swimming_pool_2_swi_5165.JPG</url>
<width>257</width>
</images>
<images>
<hasThumbnail>false</hasThumbnail>
<height>187</height>
<thumbnailHeight>0</thumbnailHeight>
<thumbnailURL>http://static.superbreak.net/content/images/Hotel/thumbs/britannia_hotel_stockport_hotel_entrance_1_ent_5165.JPG</thumbnailURL>
<thumbnailWidth>0</thumbnailWidth>
<title>Hotel Entrance</title>
<url>http://static.superbreak.net/content/images/Hotel/britannia_hotel_stockport_hotel_entrance_1_ent_5165.JPG</url>
<width>257</width>
</images>
<images>
<hasThumbnail>false</hasThumbnail>
<height>187</height>
<thumbnailHeight>0</thumbnailHeight>
<thumbnailURL>http://static.superbreak.net/content/images/Hotel/thumbs/britannia_hotel_stockport_hotel_gym_1_gym_5165.JPG</thumbnailURL>
<thumbnailWidth>0</thumbnailWidth>
<title>Hotel Gym</title>
<url>http://static.superbreak.net/content/images/Hotel/britannia_hotel_stockport_hotel_gym_1_gym_5165.JPG</url>
<width>257</width>
</images>
<images>
<hasThumbnail>false</hasThumbnail>
<height>187</height>
<thumbnailHeight>0</thumbnailHeight>
<thumbnailURL>http://static.superbreak.net/content/images/Hotel/thumbs/britannia_hotel_stockport_hotel_lounge_1_lou_5165.JPG</thumbnailURL>
<thumbnailWidth>0</thumbnailWidth>
<title>Hotel Lounge</title>
<url>http://static.superbreak.net/content/images/Hotel/britannia_hotel_stockport_hotel_lounge_1_lou_5165.JPG</url>
<width>257</width>
</images>
<images>
<hasThumbnail>false</hasThumbnail>
<height>187</height>
<thumbnailHeight>0</thumbnailHeight>
<thumbnailURL>http://static.superbreak.net/content/images/Hotel/thumbs/britannia_hotel_stockport_four_poster_bedroom_1_pst_5165.JPG</thumbnailURL>
<thumbnailWidth>0</thumbnailWidth>
<title>Four Poster Bedroom</title>
<url>http://static.superbreak.net/content/images/Hotel/britannia_hotel_stockport_four_poster_bedroom_1_pst_5165.JPG</url>
<width>257</width>
</images>
</images>
<latitude>53.398941</latitude>
<location>Stockport</location>
<longitude>-2.13463</longitude>
<starRating>3</starRating>
</hotel>
<roomUnits>
<roomUnits>
<allocation>1</allocation>
<boardCode>RO</boardCode>
<boardDescription>Room only</boardDescription>
<maxOccupancy>2</maxOccupancy>
<minOccupancy>1</minOccupancy>
<price>18.5</price>
<stdOccupancy>2</stdOccupancy>
<unitDescription>Double For 1-2</unitDescription>
<unitID>162</unitID>
</roomUnits>
<roomUnits>
<allocation>1</allocation>
<boardCode>RO</boardCode>
<boardDescription>Room only</boardDescription>
<maxOccupancy>2</maxOccupancy>
<minOccupancy>1</minOccupancy>
<price>18.5</price>
<stdOccupancy>2</stdOccupancy>
<unitDescription>Twin For 1-2</unitDescription>
<unitID>161</unitID>
</roomUnits>
<roomUnits>
<allocation>1</allocation>
<boardCode>RO</boardCode>
<boardDescription>Room only</boardDescription>
<maxOccupancy>2</maxOccupancy>
<minOccupancy>2</minOccupancy>
<price>23.5</price>
<stdOccupancy>2</stdOccupancy>
<unitDescription>Executive Double Room</unitDescription>
<unitID>65</unitID>
</roomUnits>
<roomUnits>
<allocation>1</allocation>
<boardCode>RO</boardCode>
<boardDescription>Room only</boardDescription>
<maxOccupancy>2</maxOccupancy>
<minOccupancy>2</minOccupancy>
<price>23.5</price>
<stdOccupancy>2</stdOccupancy>
<unitDescription>Executive Twin Room</unitDescription>
<unitID>64</unitID>
</roomUnits>
</roomUnits>
</hotels>
I'm attempting to iterate through each hotels hotels result and turn each result into a multi dimensional array. The code I'm using which isn't working as I'd like is below:
$doc = new DOMDocument();
if ($doc->loadXML($result)) {
$items = $doc->getElementsByTagName('hotels');
$hotelnames = array();
foreach($items as $item) {
$hotelname = array();
$hotelimages = array();
if($item->childNodes->length) {
foreach($item->childNodes as $i) {
$hotelname[$i->nodeName] = $i->nodeValue;
if($i->childNodes->length){
foreach($i->childNodes as $z) {
if($z->childNodes->length){
foreach($z->childNodes as $x) {
$hotelimage[$x->nodeName] = $x->nodeValue;
}
}
}
}
$hotelimages[] = $hotelimage;
}
}
$hotelnames[] = $hotelname;
}
}
I'm guessing the issues I'm facing are mostly caused by the fact that the child and parent nodes are named the same for hotels and for the images.
Any help or a nod in the right direction will be much appreciated.
I suggest you using xpath (for example in SimpleXML implementation http://php.net/manual/en/simplexmlelement.xpath.php) for loading the values you need.
Or if you need whole XML parsed to array, you can always use PEAR XML_Serializer package (http://pear.php.net/package/XML_Serializer) to unserialize your XML.
Instead of working directly with the DOM I would recommend that you, unless you actually do need access to the DOM, perform these tasks using SimpleXML
It makes it very easy to work with XML data and you can act on it almost like a normal array.
Example
<?php
$url = 'http://www.flickr.com/services/feeds/photos_public.gne';
foreach(simplexml_load_file($url)->entry as $entry) {
echo $entry->content;
}
?>
Quite few lines for that functionality :)
Good luck!
I am looking in to the XML and I noticed some XML tags are not correct
Ex: <images><images></images><images></images></images> same is the case with roomunit.
I think it should be like <images><image></image><image></image></images> this will help to iterate over XML tag in php.

Categories