Flickr RSS feed inlezen met SimpleXML
Net zoals zovelen heb ook ik een account bij Flickr voor foto's die ik zo af en toe maak. Aangezien Flickr zo ongeveer het poster-child voor alles web2.0 is, is het alleen uploaden van foto's naar Flickr eigenlijk not-done.
Dus in dit artikeltje kijken we naar hoe we de RSS feeds van Flickr kunnen gebruiken om onze eigen Flickr overzicht te maken voor op bijv. je persoonlijke blog. Hiervoor zullen we PHP5's SimpleXML gebruiken.
Een van de dingen die werd aangepakt voor PHP 5 was het verwerken van XML. In PHP4 had je hier al twee methodes voor (SAX parsing en DOM parsing) maar deze waren ofwel enigzins ingewikkeld of gewoon niet zo stabiel. Voor PHP5 werd daarom de SimpleXML extensie in het leven geroepen.
Zoals je van de naam SimpleXML mag verwachten is het verwerken van XML met deze extensie behoorlijk eenvoudig. Om een XML bestand in te laden gebruiken we slechts één functie: simplexml_load_file. Deze functie geeft een object terug welke we kunnen gebruiken om het document verder te verwerken:
-
$xmlObj = simplexml_load_file("http://api.flickr.com/services/feeds/photos_public.gne?id=82896416@N00&lang=en-us&format=rss_200")
De individuele XML elementen kunnen we vervolgens benaderen als eigenschappen van het $xmlObj object. Neem bijvoorbeeld deze voorbeeld RSS file:
-
<?xml version="1.0" encoding="utf-8"?>
-
<rss version="2.0">
-
<channel>
-
<title>Test RSS feed</title>
-
<link>http://scriptorama.nl/</link>
-
-
<item>
-
<title>Flickr RSS feed inlezen met SimpleXML</title>
-
<link>http://scriptorama.nl/flickr-rss-feed-inlezen-met-simplexml</link>
-
<description>Leer werken met SimpleXML</description>
-
</item>
-
-
<item>
-
<title>Webconferences in 2008</title>
-
<link>http://www.scriptorama.nl/events/webdeveloper-conferences-in-2008</link>
-
<description>Overzicht van conferences voor Webdevelopers in Nederland</description>
-
</item>
-
-
</channel>
-
</rss>
Om de titel van het eerste item te lezen gebruik je simpelweg:
Zoals je ziet slaan we het eerste element van het XML document, het zogenaamde document element, over. Dit komt omdat $xmlObj in feite al de verwijzing naar het eerste element is. Later zullen we zien hoe je de details van het document element kunt uitlezen.
Vervolgens benaderen we het channel element en daarin het item element. Zoals je ziet corresponderen deze namen exact met de XML element namen in de RSS feed. Er is maar een ding speciaal en dat is $xmlObj->channel->item. Deze ontstaat doordat er meerdere item elementen op hetzelfde niveau staan. Je kunt de individuele item element benaderen door simpel wel door $xmlObj->channel->item te lopen.
Met SimpleXML kunnen we dus met een paar regels een RSS feed verwerken:
-
$xmlObj = simplexml_load_file("http://api.flickr.com/services/feeds/photos_public.gne?id=82896416@N00&lang=en-us&format=rss_200");
-
-
foreach ($xmlObj->channel->item as $item)
-
{
-
}
Maar, we hebben het hier over een Flickr RSS feed en dus moeten we wel de foto's die in deze RSS feed staan gebruiken. Om de foto's beschikbaar te maken gebruikt Flickr de Media RSS uitbreiding en om deze te verwerken zullen we het nog iets anders moeten aan pakken.
De Media RSS namespace in Flickr RSS feeds
De Media RSS specificatie is een door Yahoo! ontwikkelde toevoeging op de RSS standaard waarbij media, zoals fotos en video's, duidelijk beschreven kunnen worden toegevoegd aan een RSS feed.
In de Flickr RSS feed ziet dat er als volgt uit. Het voorbeeld is ingekort om het duidelijk te houden:
-
<?xml version="1.0" encoding="utf-8"?>
-
<rss version="2.0" xmlns:media="http://search.yahoo.com/mrss/"
-
xmlns:dc="http://purl.org/dc/elements/1.1/"
-
>
-
<channel>
-
<title>Photos from Mathieu K</title>
-
<link>http://www.flickr.com/photos/hoppa/</link>
-
<description></description>
-
-
<item>
-
<title>Sunbeds, 3 euro</title>
-
<link>http://www.flickr.com/photos/hoppa/1243277966/</link>
-
-
<pubDate>Sun, 26 Aug 2007 13:40:24 -0800</pubDate>
-
<author>nobody@flickr.com (Mathieu K)</author>
-
<guid isPermaLink="false">tag:flickr.com,2004:/photo/1243277966</guid>
-
<media:content url="http://farm2.static.flickr.com/1428/1243277966_6e87d19813_o.jpg" type="image/jpeg" height="1944" width="2592"/>
-
<media:title>Sunbeds, 3 euro</media:title>
-
<media:thumbnail url="http://farm2.static.flickr.com/1428/1243277966_2f111f0457_s.jpg" height="75" width="75" />
-
<media:credit role="photographer">Mathieu K</media:credit>
-
<media:category scheme="urn:flickr:tags">greece crete preveli</media:category>
-
-
</item>
-
</channel>
-
</rss>
In het document element worden de namespaces media en dc gedefinieerd. In dit geval gaat het ons alleen om de media namespace. In het eerste item element zie je vervolgens enkele elementen waarvan de naam begint met media:. Dit zijn elementen uit de Media RSS namespace.
In ons voorbeeld zullen we een lijst van thumbnails tonen. In dit geval hebben we dus interesse in het <media:thumbnail /> element. Om deze te kunnen benaderen zullen we een trucje moeten uithalen want in PHP kunnen object eigenschappen eigenlijk geen dubbele punt in hun naam hebben.
Om namespace elementen te verwerken gebruiken we de children methode en aan deze methode geven we de namespace waaruit we graag de children willen hebben. Om alle media RSS children te krijgen schrijven we dan:
-
foreach ($xmlObj->channel->item as $item)
-
{
-
$mediaChildren = $item->children('http://search.yahoo.com/mrss/');
-
}
Als namespace identifier geven we de URL op die bij de media namespace staat gedeclareerd in het document element. Deze aanroep geeft een object met alle elementen onder het item element die in de media namespace zitten. Deze elementen zijn weer te benaderen als object eigenschappen.
Zoals gezegd zijn wij nu voornamelijk geinteresseerd in het media:thumbnail element en specifiek daaruit willen we graag de URL weten. Hiervoor gebruiken we nog een andere methode: SimpleXMLElement::attributes().
Deze methode geeft ons alle attributen van het huidige xml element in een handige associatieve array. Dus om alle thumbnails weer te geven schrijven we:
-
foreach ($xmlObj->channel->item as $item)
-
{
-
$mediaChildren = $item->children('http://search.yahoo.com/mrss/');
-
$thumbnailAttr = $mediaChildren->thumbnail->attributes();
-
-
echo '<img src="', $thumbnailAttr['url'], '" title="', htmlspecialchars($item->title), '" /><br />';
-
}
Het is belangrijk om je te realiseren dat $thumbnailAttr['url'] nu wel als string gebruikt wordt, maar dat de waarde ervan eigenlijk een SimpleXMLElement instantie is. Doordat $thumbnailAttr['url'] in een echo statement gebruikt wordt, roept PHP intern direct de magic method __toString() aan op het object waardoor we toch het gewenste effect krijgen.
Om de waarde toch als string in handen te krijgen zul je $thumbnailAttr['url'] moeten casten naar een string:
-
foreach ($xmlObj->channel->item as $item)
-
{
-
$mediaChildren = $item->children('http://search.yahoo.com/mrss/');
-
$thumbnailAttr = $mediaChildren->thumbnail->attributes();
-
-
$thumbnails[] = (string) $thumbnailAttr['url'];
-
}
Controleren of een XML document wel een RSS feed is
Eerder in dit artikel schreef ik dat het eerst element van een XML feed, het document element, niet wordt opgenomen als apart object eigenschap. Maar in sommige gevallen wil je toch graag dit document element benaderen, in ons geval bijvoorbeeld om te bepalen of het XML document wel een RSS feed is.
Hiervoor kun je het $xmlObj object direct aanspreken:
-
$xmlObj = simplexml_load_file("http://api.flickr.com/services/feeds/photos_public.gne?id=82896416@N00&lang=en-us&format=rss_200");
-
-
$docElAttr = $xmlObj->attributes();
-
Maar dat is natuurlijk nog niet alles. We willen ook weten of deze RSS feed wel de Media RSS extensie gebruikt. Om dit te doen moeten we controleren of de Media RSS namespace definieert wordt in het document element en dit kunnen we doen met de methode getDocNamespaces():
Het volledige script
Ter volledigheid nog even een compleet script, inclusief controle of het document wel een RSS feed is:
-
<?php
-
$xmlObj = simplexml_load_file("http://api.flickr.com/services/feeds/photos_public.gne?id=82896416@N00&lang=en-us&format=rss_200");
-
-
$docElAttr = $xmlObj->attributes();
-
-
-
$namespaces = $xmlObj->getDocNamespaces();
-
-
-
foreach ($xmlObj->channel->item as $item)
-
{
-
$mediaChildren = $item->children('http://search.yahoo.com/mrss/');
-
$thumbnailAttr = $mediaChildren->thumbnail->attributes();
-
-
'thumbnail' => (string) $thumbnailAttr['url'],
-
'link' => (string) $item->link
-
);
-
}
-
-
?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
-
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-
<html>
-
<head>
-
<title>Flickr RSS overzicht</title>
-
<style type="text/css">
-
body { font-family: helvetica; font-size: 0.8em; }
-
ul li { list-style-type: none; border: 1px dashed #c0c0c0; width: 500px; margin: 5px 0; }
-
ul li.odd { background-color: #ebebeb; }
-
ul li img { vertical-align: middle; padding-right: 15px; border: 0;}
-
</style>
-
-
</head>
-
<body>
-
<ul>
-
<?php
-
$i = 0;
-
foreach ($thumbnails as $title => $data)
-
{
-
$class = ($i++ % 2) == 0 ? 'even' : 'odd';
-
echo '</li>';
-
-
-
}
-
?>
-
</ul>
-
</body>
-
</html>
Conclusie
Met SimpleXML zijn XML documenten erg simpel te verwerken. Zelfs als het document in kwestie gebruik maakt van wat geavanceerdere features, zoals bijvoorbeeld XML namespaces.
Een grappig demo-tje vind je hier. Deze moet bekeken worden met een recente WebKit nightly, aangezien deze CSS Transforms, een nieuwe webkit specifieke feature bevat. Of, als je daar geen zin in hebt, kun je ook gewoon dit screenshot bekijken:

Happy parsing!
Volg Scriptorama via RSS!
Reageer ook!
Nice artikel :) zelf ben ik op het moment druk bezig met SOAP koppelingen ed. Als er nog iemand een artikel heeft om een SOAP connectie zo min mogelijk te belasten houd ik me aanbevolen.
Door Marten
op 02.27.08 @ 8:49 am | Permalink
Uh, cachen? :P
Door Mathieu Kooiman
op 02.27.08 @ 9:23 am | Permalink
Relaxed artikel, just what I needed =D
Door Joost van Velzen
op 02.27.08 @ 9:46 am | Permalink
Nog wel voor zorgen dat je thumbnail linked naar de flickr foto pagina. Dit is verplicht bij flickr.
Door Joost van Velzen
op 02.27.08 @ 10:14 am | Permalink
Cachen gaat moeilijk omdat er een externe bron is die de gegevens ook kan aanpassen. Hierdoor zal ik steeds het request moeten sturen (per onderdeel zijn dat 6 requests)
Door Marten
op 02.27.08 @ 4:31 pm | Permalink
Je geeft hier de namespace url aan children() mee, en dus is de check of de prefix wel 'media' is overbodig. Als die prefix op beide punten gewijzigd wordt, kan je script in nog prima werken.
Ik denk dat als je in je main code enkel met namespace urls werkt, in_array($namespace_url, $namespaces) een voldoende check is. :)
Marten: Er zijn genoeg manieren te bedenken, maar zonder meer informatie over het systeem, de requests en de houdbaarheid van de data, kan dat niet gericht gebeuren. Hoewel ik je graag wil helpen, is dit niet wellicht niet de beste plek om je probleem uitgebreid te tackelen. ;)
Door Maarten
op 02.28.08 @ 9:10 am | Permalink
@ Maarten: je hebt helemaal gelijk. Ik ga het even aanpassen.
Door Mathieu Kooiman
op 02.28.08 @ 9:14 am | Permalink
Wederom net artikel!
persoonlijk zou ik dan wel denken aan:
http://www.php.net/~helly/php/ext/spl/classSimpleXMLIterator.html
Door Lode
op 03.03.08 @ 12:49 am | Permalink
SimpleXMLElement is vaak al een iterator, $xmlObj->channel->item uit het artikel is eigenlijk geen array, maar een iterator.
Door Mathieu Kooiman
op 03.03.08 @ 7:29 am | Permalink
@ Maarten: Jouw methode werkt overigens niet altijd. Sommige implementaties gebruiken http://search.yahoo.com/mrss als namespace, terwijl anderen http://search.yahoo.com/mrss/ gebruiken.
Door Mathieu Kooiman
op 03.04.08 @ 9:29 am | Permalink
Dat probleem was al aanwezig omdat je juist al op basis van url werkte. Je kan ook werken children($namespace_prefix, true), als de prefix tenminste wel constant is. :)
Door Maarten
op 03.04.08 @ 9:31 am | Permalink
Mooi simpel artikel!
Door Jimmy
op 10.01.08 @ 7:19 pm | Permalink
simplexml_load_file werkt bij mij niet, en misschien bij meerdere, gebruik daarvoor gwoon cURL
Door Sander
op 01.09.09 @ 11:07 pm | Permalink
Line and paragraph breaks automatic, e-mail address never displayed, HTML allowed:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>