1

I have an XML file with the following:

<property>
  <id>1</id>
  <type>type</type>
  <town>town</town>
  <province>province</province>
  <images>
    <image id="1">
    <url>
      http://www.test.com
    </url>
    <image id="2">
    <url>
      http://www.test.com
    </url>
    <image id="3">
    <url>
      http://www.test.com
    </url>
  </image>

I can iterate through the file and get the value except the image url. I am struggling with the element after the element with an attribute.

$count=0;
$id=0;
foreach($xml->children() as $properties) {
    echo "<h1>" . $xml->property[$count]->type . " for sale in " .$xml->property[$count]->town . ", " . $xml->property[$count]->province . "</h1>" . "<br>";
    echo $xml->property[$count]->id . "<br>";
    echo $xml->property[$count]->desc->en . "<br>";

    foreach($xml->property[$count]->children() as $images) {
        echo $xml->property[$count]->images -> image[$id++] -> url;
        $id++;
}
    $count++;
}

but the 2nd loop isn´t close to being right. I would greatly appreciate some help.

sharon
  • 55
  • 7
  • 2
    whats the end result you want? – Parag Tyagi Mar 08 '15 at 17:12
  • my apologies, it would help! I want to display each image. I just need the code that returns the value of for images -> image [id..] -> url . – sharon Mar 08 '15 at 17:34
  • I don't know the correct xml terminology but each element contains a element with a sub element and that as a element. I want the url of each image. http:// http:// http:// – sharon Mar 08 '15 at 17:49

2 Answers2

0

Something like this perhaps:

foreach($xml->property[$count]->images as $image) {
    echo $image->url;
}
Jimmy Bernljung
  • 429
  • 2
  • 8
0

You have not shared the complete XML structure so it's unclear where the root of the document is (the document element).

Assuming that the root element is not <property> but all <property> elements are children of the root element you can iterate over all those <property> elements by simply just iterating over them:

$xml = simplexml_load_file('example.xml');

foreach ($xml->property as $property) {
    printf(
        "<h1>%s for sale in %s, %s</h1>\n", htmlspecialchars($property->id), 
        htmlspecialchars($property->town), htmlspecialchars($property->province)
    );

As you can see, you don't need to make use of a $count variable explicitly. You can, but you don't need to. Just saying.

Now you're looking for the image URLs. Each <property> element has a single child element named <images> and these again have mulitple <url> elements you're interested in.

This can be done with an xpath query (as you go down deeper more than one level):

$urls = $property->xpath('images/image/url');
foreach ($urls as $url) {
    printf(" - %s\n", htmlspecialchars(trim($url)));
}

If you don't use xpath here, you would have needed to make a foreach for every single level that contains more than a single child. Just as a counter-example:

foreach ($property->images->image as $image) {
    foreach ($image->url as $url) {
        printf(" - %s\n", htmlspecialchars(trim($url)));
    }
}

I hope this shows you more well how the traversal works in simplexml. There is some more great material in the PHP manual entitled Basic SimpleXML usage which shows basic traversal and also links to the more advanced xpath topic.

And don't forget when you output dynamic data into HTML to properly HTML encode it. I've used the htmlspecialchars function for that, you need to provide the correct encoding parameters which I've left out for brevity in my answer.

Example in full:

<?php
/**
 * @link https://stackoverflow.com/questions/28929239/iterating-through-xml-file-with-php
 */

$buffer = <<<XML
<root>
    <property>
        <id>1</id>
        <type>type</type>
        <town>town</town>
        <province>province</province>
        <images>
            <image id="1">
                <url>
                    http://www.test.com
                </url>
            </image>
            <image id="2">
                <url>
                    http://www.test.com
                </url>
            </image>
            <image id="3">
                <url>
                    http://www.test.com
                </url>
            </image>
        </images>
    </property>
    <property>
        <id>1</id>
        <images>
            <image id="1">
                <url>
                    http://www.test.com
                </url>
            </image>
         </images>
    </property>
</root>
XML;


$xml = simplexml_load_string($buffer);

foreach ($xml->property as $property) {
    printf(
        "<h1>%s for sale in %s, %s</h1>\n",
        htmlspecialchars($property->id), htmlspecialchars($property->town), htmlspecialchars($property->province)
    );

    $urls = $property->xpath('images/image/url');
    foreach ($urls as $url) {
        printf(" - %s\n", htmlspecialchars(trim($url)));
    }
}

Exemplary output (plain-text):

<h1>1 for sale in town, province</h1>
 - http://www.test.com
 - http://www.test.com
 - http://www.test.com
<h1>1 for sale in , </h1>
 - http://www.test.com

Transliteration and Output Encoding with SimpleXML

One way to globally apply transliteration (removal of accents) and HTML encoding (htmlspecialchars) for every string-value of the SimpleXMLElement can be achieved by extending it. There is a shortcomming: a SimpleXMLElement can't have private properties (because the properties are all magic), however you can create static global variables. For keeping the instance of a Transliterator this is enough. And for manipulating the string values, the __toString() magic method works well with SimpleXMLElement:

/**
 * Transliterate and HTML encode
 *
 * Class XMLTransliterated
 */
class XMLTransliterated extends SimpleXMLElement
{
    public static $transliterator;

    public function __toString()
    {
        $transliterator = &self::$transliterator;
        $transliterator || $transliterator = Transliterator::create("Latin-ASCII");

        $transliterated = $transliterator->transliterate($this);

        return htmlspecialchars(trim($transliterated), ENT_QUOTES | ENT_HTML5, 'UTF-8');
    }
}

All you need to do to benefit from the string manipulations is to either use this classname XMLTransliterated (or whichever you would name the class) when creating the SimpleXMLElement:

$xml = simplexml_load_string($buffer, 'XMLTransliterated');

- or - and this is very special to simplexml, you can change the class later on with a little conversion trick:

$xml = simplexml_import_dom(dom_import_simplexml($xml), 'XMLTransliterated');

This will make the following code use an XMLTransliterated instead of the previous less specific SimpleXMLElement so the code largely remains the same (please note that the htmlspecialchars and trim calls could be safely removed as they are now automatically called when accessing the string-values of the SimpleXMLElement):

$xml = simplexml_load_string($buffer, 'XMLTransliterated');

foreach ($xml->property as $property) {
    printf(
        "<h1>%s (%s) for sale in %s, %s</h1>\n",
        $property->id, $property->type, $property->town, $property->province
    );

    $urls = $property->xpath('images/image/url');
    foreach ($urls as $url) {
        printf(" - %s\n", $url);
    }
}

But the output will turn "Schloß" into "schloss", "tôwn" into "town" and "provincé" into "province".

Transliterator requires PHP 5.4 and you having the Intl extension enabled (which you should have, if not enable it).

Alternatively you can also make use of transliteration from the iconv library. But beware, this produces slightly different output:

$transliterated = iconv('UTF-8', 'ASCII//IGNORE//TRANSLIT', $this);

More related transliteration questions:

Community
  • 1
  • 1
hakre
  • 193,403
  • 52
  • 435
  • 836
  • many thanks this works. I tried to add a comment but couldn't. I've also tried to mark it as correct. – sharon Mar 09 '15 at 11:41
  • The encoding part is difficult for me. Do you happen to know how I can change special characters like ó and á as "o" and "a" or if not, at least display them correctly. $output = "property_for_sale_in_" . $property->town. "_" . $property->province."_".$count.".html"; I tried with various methods including headers and not succeeded. – sharon Mar 09 '15 at 12:38
  • I've managed to ó to display as ó using charset=ISO-8859-1 but can't manage to format ó as o. – sharon Mar 09 '15 at 12:50
  • @sharon: What is the character encoding of the output you create? – hakre Mar 09 '15 at 13:43
  • Thanks again for your help hakre. To finalize the thread for others who come across it, I have settled for using utf-8 and then formatting with a replacement function $prop_province = removeAccents($property->province); that I found here http://www.pontikis.net/tip/?id=22 . I couldn't find any other fail safe method. – sharon Mar 09 '15 at 19:36
  • @sharon: Use `strtr` of `str_replace` for that function, also make the translation array static. However what you're looking for is called *transliteration* I'll update the answer with an example. Which PHP version are you using? – hakre Mar 09 '15 at 20:57
  • @sharon: Extended the answer with an example on how to transparently transliterate and html-encode the values a SimpleXMLElement provides by extending from it and changing the behavior of `__toString()`. – hakre Mar 09 '15 at 22:37