简体   繁体   中英

Iterating through XML file with php

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.

Something like this perhaps:

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

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:

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM