Looping through simpleXML nodes

Hi

I have been trying to loop through the image nodes relative to the id in this XML. Although the “live” version will contain an unknown number of image url’s I have limited it to 3 for simplicity.

<?xml version="1.0" encoding="UTF-8"?>
<root>

<property>
<id>153</id>

<images>
<image id="1">
<url>http://www.mysite.com/product/images/1322.jpg</url>
<title>
<en>Title 1</en>
</title>
</image>

<image id="2">
<url>http://www.mysite.com/product/images/1321.jpg</url>
<title>
<en>Title 2</en>
</title>
</image>

<image id="3">
<url>http://www.mysite.com/product/images/1316.jpg</url>
<title>
<en>Title 3</en>
</title>
</images>
</property>

<property>
<id>154</id>

<images>
<image id="1">
<url>http://www.mysite.com/product/images/1322.jpg</url>
<title>
<en>Title 1</en>
</title>
</image>

<image id="2">
<url>http://www.mysite.com/product/images/1321.jpg</url>
<title>
<en>Title 2</en>
</title>
</image>

<image id="3">
<url>http://www.mysite.com/product/images/1316.jpg</url>
<title>
<en>Title 3</en>
</title>
</images>
</property>

</root>

I want to insert the url values relevant to the id in my DB then loop around to the next id and so on.

Nothing I have tried works, can some one help me please!!

$xml = 'example.xml'; // URL for feed.

try{
  $feed = new SimpleXMLElement($xml, null, true);
}catch(Exception $e){
  echo $e-&gt;getMessage();
  exit;
}

$sql = 'INSERT INTO images (`id`, `url`) VALUES ';


foreach($feed-&gt;property as $property){
  $sql .= sprintf(
    "\
('%d', '%s'),",
   $property-&gt;id,
   mysql_real_escape_string($property-&gt;images-&gt;image-&gt;url)
  );
}

$sql = rtrim($sql, ',') . ';';


echo $sql;

I have fallen back to the original code, that at least inserts one url, as a starting point. :confused:

Thanks in advance

Colin

Probably because the element “Images” has more than one child named “image”, so $property->images->image is an array.
check with ‘print_r’.

Yes. $property->images->image has an id attached to it as can be seen from the XML example. It is this that I need to loop around.

Here is the array structure for the images section:

                                        )

                                    [1] => Array
                                        (
                                            [@attributes] => Array
                                                (
                                                    [id] => 2
                                                )

                                             => http://www.mysite.com/product/images/1321.jpg
                                            [title] => Array
                                                (
                                                    [en] => Title 2
                                                )

                                        )

                                    [2] => Array
                                        (
                                            [@attributes] => Array
                                                (
                                                    [id] => 3
                                                )

                                             => http://www.mysite.com/product/images/1316.jpg
                                            [title] => Array
                                                (
                                                    [en] => Title 3
                                                )

                                        )

                                    [3] => Array
                                        (
                                            [@attributes] => Array
                                                (
                                                    [id] => 4
                                                )

                                             => http://www.mysite.com/product/images/1317.jpg
                                            [title] => Array
                                                (
                                                    [en] => Title 4
                                                )

                                        )

                                    [4] => Array
                                        (
                                            [@attributes] => Array
                                                (
                                                    [id] => 5
                                                )

                                             => http://www.mysite.com/product/images/1319.jpg
                                            [title] => Array
                                                (
                                                    [en] => Title 5
                                                )

                                        )

                                    [5] => Array
                                        (
                                            [@attributes] => Array
                                                (
                                                    [id] => 6
                                                )

                                             => http://www.mysite.com/product/images/1320.jpg
                                            [title] => Array
                                                (
                                                    [en] => Title 6
                                                )

                                        )

                                    [6] => Array
                                        (
                                            [@attributes] => Array
                                                (
                                                    [id] => 7
                                                )

                                             => http://www.mysite.com/product/images/1323.jpg
                                            [title] => Array
                                                (
                                                    [en] => Title 7
                                                )

                                        )

                                    [7] => Array
                                        (
                                            [@attributes] => Array
                                                (
                                                    [id] => 8
                                                )

                                             => http://www.mysite.com/product/images/1324.jpg
                                            [title] => Array
                                                (
                                                    [en] => Title 8
                                                )

                                        )

What I would really like to know is how to get simpleXML to loop through these for each id result.

Thanks

Colin

For each property in the loop, use:


$xpath="images/image";
$image_elements = $property->xpath($xpath);

Now, $image_elements is as array of “SimpleXMLElement” objects.

If you loop through them using

foreach ($image_elements as $image)

The value of the id is $image->attributes()->id.
The url is $image->url

BTW, I found an error with your input xml file: the ‘image’ tag with id=3 is not matched by ‘</image>’.

Thanks for your reply

I think I may not have explained this very well. It is the ID contained within the <id> tag that I need to insert into my DB along with all the images relevant to that ID. I think your reply assumes I want to insert the id attached to each image??

This is my first time using simple XML and it is not proving to be too simple :slight_smile:

Perhaps I should rewrite the way the query is constructed?

Ok it is a bit scruffy at the moment but I have a result that i can work on.

$xml = 'example.xml'; // URL for feed.

try{
  $feed = new SimpleXMLElement($xml, null, true);
}catch(Exception $e){
  echo $e->getMessage();
  exit;
}


foreach($feed->property as $property){

$id = $property->id;
echo "<br />" .$id ."<br />";
$xpath="images/image";
$image_elements = $property->xpath($xpath);



foreach ($image_elements as $image){
$url = $image->url;
$image_title = $image->title;



echo "<br />" .$url;
echo "<br />" .$image_title;
      }

}

Thanks very much

Colin

AARGH!!

Can somebody please help me to put this new found solution into my insert. I think I have tried almost every combination but always end up with an error if I attempt to include another for each loop.

The example below is as far as I have got but the loop is in the wrong place :eye:

$sql = 'INSERT INTO images (`id`, `url`) VALUES ';


foreach($feed-&gt;property as $property)
{

$xpath="images/image";
$image_elements = $property-&gt;xpath($xpath);
foreach ($image_elements as $image){
  $sql .= sprintf(
    "\
('%d', '%s'),",
   $property-&gt;id,
   $image-&gt;url
  );
}
}
$sql = rtrim($sql, ',') . ';';


echo $sql;

Thank you

Hi Colin.

Let me start off by apologising in advance for the length of this post; there is a lot to read, far more than you really need.

To begin, I’d like to introduce some concepts that you might or might not be familiar with, which are vital to understanding SimpleXML and how it does its magic (in a very real sense of the word!).

One of those concepts is that of the iterable object. That is a fancy word for being able to loop (iterate) over a collection of things, like you would with an array (the term applies for arrays, “boring” objects and iterable ones too). The foreach loop is easy to make sense of: it runs the code inside it for each item in the thing being looped. That thing can be an array, in which case the code runs once for each item in the array. It can be what I’ll call a “boring” object, though this is less used, and the code runs for each (visible) property of that object. And finally, it can be an iterable object, and the code runs for each item within it (what exactly an “item” is, can vary wildly).

Bringing this into the world of SimpleXML, a SimpleXMLElement object is one of those fancy objects! There is a whole world of magic happening for this particular class and I won’t pretend to list it all, just some key points. With reference to iterable objects, SimpleXMLElements most certainly are! A good point to keep in mind is that in XML, there can be multiple elements at any one level with the same name, so how does that translate into SimpleXML-land? Lets have an example:


<?php

$xml = "
<album>
  <song>A Kind of Magic</song>
  <song>Under Pressure</song>
  <song>Radio Ga Ga</song>
</album>
";
$sxe = new SimpleXMLElement($xml);
echo $sxe->song;
// Outputs: A Kind of Magic

So, we access the song information by its tag name. $sxe is the main album element, and the first song can be accessed via a fake song property. As if this wasn’t enough of a cheat (to make life “simple”!) what that really does is tells SimpleXML to return the first song belonging to $sxe. So how to get at the other songs? With a loop? Well, of course that is one way but lets hold that thought for a minute.

That same, first, song element could be pulled from the album by accessing it with array-like notation (aside: the same way as accessing attributes for the album): like, $sxe->song[0].


echo $sxe->song[0] . " " . $sxe->song[2];
// Outputs: A Kind of Magic Radio Ga Ga

Hopefully, this can give a clue as to why looping over the songs would work. With a for each loop like below, the code is executed on the first, second and then third song in turn.


// This might look weird, but think about it.
$songs = $sxe->song;

foreach ($songs as $song) {
    echo $song . "\
";
}
// Outputs:
// A Kind of Magic
// Under Pressure
// Radio Ga Ga

Huh, what? How can accessing the first song work within the foreach loop? This where you have to remember that the basic $parent->child style of code accesses all of the elements (belonging to $parent) with that name when you want to loop (or count(), or do anything with the collection of elements) and only the first element if you want to do things like echo the content, or an attribute, or add another element to it.

So how does all this relate to the topic? Lets take a look. The basic outline is that there are two property elements within the top-level root element. Each of those property elements has both a single id and images element. So, it looks like the example below (the contents of images are removed so that you can see the described structure).


<root>
  <property>
    <id>153</id>
    <images>...</images>
  </property>
  <property>
    <id>154</id>
    <images>...</images>
  </property>
</root>

To loop over each of the property elements and access their respective id, as has already been posted in the thread, we can do:


foreach ($feed->property as $property) {
    echo $property->id . "\
"; // $property->id[0] would work too
}
// Outputs:
// 153
// 154

To loop over the collection of images (within the images element) for each property, you can do the following:


foreach ($feed->property as $property) {
    foreach ($property->images->image as $image) {
        // Do something useful here
    }
}

Each image looks like the snippet below, with different values of course.


<image id="1">
  <url>http://www.mysite.com/product/images/1322.jpg</url>
  <title>
    <en>Title 1</en>
  </title>
</image>

To get at the url element, you can simply use $image->url but remember that that returns the SimpleXMLElement object not the text within it. To get at the text, some more magic can be employed by asking the object for its string value.


// Automagically echoes the string value
echo $image->url;

// or, see http://php.net/string#language.types.string.casting
$string_url = (string) $image->url;

Lets get to a stage where your loops are in place and you can access the values that you want (the property id and image url).


foreach($feed->property as $property) {
    $id = (int) $property->id[0];
    foreach ($property->images->image as $image) {
        $url = (string) $image->url;
        // Do something with each url and id!
        echo $id . " " . $url . "\
";
    }
}
// Outputs:
// 153 http://www.mysite.com/product/images/1322.jpg
// 153 http://www.mysite.com/product/images/1321.jpg
// 153 http://www.mysite.com/product/images/1316.jpg
// 154 http://www.mysite.com/product/images/1322.jpg
// 154 http://www.mysite.com/product/images/1321.jpg
// 154 http://www.mysite.com/product/images/1316.jpg

All that is really left is first of all, to go back and read all of this again! Then, plug in the building of your $sql string at the right place. Good luck.

P.S. If all I’ve done is confuddle you, then I apologise! :eye:

Thank you so much for this excellent response, I wish everybody would answer questions as well as this, I have learnt something today :slight_smile:

I will now take this information and digest it and I feel certain come up with a working solution.

Thanks again

Colin