How to get attribute of node with namespace using SimpleXML? [closed]

youtube.xml

<feed xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" xmlns:openSearch="http://a9.com/-/spec/opensearchrss/1.0/" xmlns:gd="http://schemas.google.com/g/2005" xmlns:yt="http://gdata.youtube.com/schemas/2007">  

    <entry>
        ...
        <yt:duration seconds="1870"/>
        ...
    </entry>

</feed>

update_videos.php

$source = 'youtube.xml';

// load as file
$youtube = new SimpleXMLElement($source, null, true);

foreach($youtube->entry as $item){
    //title works
    echo $item->title;

    //now how to get seconds? My attempt...
    $namespaces = $item->getNameSpaces(true);
    $yt = $item->children($namespaces['yt']);
    $seconds = $yt->duration->attributes();
    echo $seconds['seconds'];
    //but doesn't work :(
}   

Solution 1:

The code you've got in your question does work. You have correctly written that you can access an attribute from a namespaced-element that is not in the default namespace as the root-element by making use of the SimpleXMLElement::children() method with the XML-namespace related parameters $ns and $is_prefix:

$youtube->entry->children('yt', TRUE)->duration->attributes()->seconds; // is "1870"

As this is basically the same as you did in your question, one could say that you have answered your own question. Compare with the extended Online Demo #2.


Long answer: The code you've got in your question does work. You can find your example XML and code in an interactive example online here: Online Demo #1 - it shows the result with different PHP and LIBXML versions.

Code:

$buffer = '<feed xmlns="http://www.w3.org/2005/Atom" xmlns:yt="http://gdata.youtube.com/schemas/2007">
    <entry>
        <yt:duration seconds="1870"/>
    </entry>
</feed>';

$xml = new SimpleXMLElement($buffer);

echo "ibxml version: ", LIBXML_DOTTED_VERSION, "\n";

foreach ($xml->entry as $item)
{
    //original comment: how to get seconds?
    $namespaces = $item->getNameSpaces(true);
    $yt         = $item->children($namespaces['yt']);
    $seconds    = $yt->duration->attributes();

    echo $seconds['seconds'], "\n"; // original comment: but doesn't work.
}

echo "done. should read 1870 one time.\n";

Results:

Output for 5.3.26, 5.4.16 - 5.5.0

ibxml version: 2.9.1
1870
done. should read 1870 one time.

Output for 5.3.15 - 5.3.24, 5.4.5 - 5.4.15

ibxml version: 2.8.0
1870
done. should read 1870 one time.

Output for 5.1.2 - 5.3.14, 5.4.0 - 5.4.4

ibxml version: 2.7.8
1870
done. should read 1870 one time.

From this perspective everything looks fine. As you have not given any concrete error description it's hard to say what went wrong on your case. You were probably using a PHP version that was outdated the time you asked your question, for example getting a fatal error:

Fatal error: Call to undefined method SimpleXMLElement::getNameSpaces()

Probably also due to an outdated libxml version. According to the test, the following libxml versions work fine with PHP 5.1.2-5.5.0:

  • ibxml version: 2.9.1
  • ibxml version: 2.8.0
  • ibxml version: 2.7.8

Solution 2:

So I found a way to do it using xpath, is this the best way or is there a way that's consistent with my code in the question? Just out of curiosity.

$source = 'youtube.xml';

// load as file
$youtube = new SimpleXMLElement($source, null, true);
$youtube->registerXPathNamespace('yt', 'http://gdata.youtube.com/schemas/2007');

$count = 0;
foreach($youtube->entry as $item){

    //title works
    echo $item->title;

    $attributes = $item->xpath('//yt:duration/@seconds');
    echo $attributes[$count]['seconds'];
    $count++;
}

Solution 3:

I was struggling with this situation as well then I found the solution. It's so simple on how to read a complex XML attribute namespace.

foreach($media->group->content as $content) {
    $attrs = $content->attributes();
    $ytformat = $content->attributes("yt","format");

    echo(" attr = " . $ytformat . "; ");

    if ($ytformat == 5) {
        $url = $attrs['url'];
        $type = $attrs['type'];

        echo ($url . " - " . $type);
    }
}

Hope it helps...:)

Solution 4:

The following is another solution using attributes() and children() only, tested and working OK! :)

$data=<<<XML

<rss version="2.0" xmlns:media="http://search.yahoo.com/mrss/">
 <channel>
   <item>
    <title>Title: 0</title>
    <link test="test" test2="This is a test">Link:0</link>
    <media:thumbnail url="Thumbnail URL: 0"/>
    <media:content url="Content URL: 0" type="video/mp4" />
  </item>
    <item>
    <title>Title: 1</title>
    <link>Link:1</link>
    <media:thumbnail url="1"/>
    <media:content url="1" type="video/mp4" />
  </item>
 </channel>
</rss>

XML;



$xml=simplexml_load_string($data);

//reading simple node
echo $xml->channel[0]->item[0]->title;

echo "<br>----------------------------------<br>";

//reading/updating simple node's attribute
echo $xml->channel[0]->item[0]->link->attributes()->test2="This is a NEW test!.";

echo "<br>----------------------------------<br>";

//reading/updating namespaced node's attribute
echo $xml->channel[0]->item[0]->children('media',TRUE)->content->attributes()->type="VIDEO/MP6";

//saving...
$xml->asXml('updated.xml');