Flickr feeds and feedparser

Gunnar asked this morning on #swig how to handle Flickr feeds, and especially images and thumbnails with feedparser. I remembered I hacked it once to make it work, but lost my changes with a server crash … hopefully I submitted a bug report about it.

Adding these 2 methods make it works as needed[1]:

def _start_media_content(self, attrsD):
  url = attrsD.get('url')
  if url:
    self._save('media_content', url)
def _start_media_thumbnail(self, attrsD):
  url = attrsD.get('url')
  if url:
    self._save('media_thumbnail', url)

Then, you can get thumbnails from Flickr feeds:

 
>>> import feedparser 
>>> d = feedparser.parse("http://api.flickr.com/services/feeds/photos_public.gne?id=33669349@N00&format=rss_200")
>>> print d['entries'][0]['media_thumbnail']
http://farm1.static.flickr.com/83/268382663_a5102bc6dd_s.jpg

Notes

[1] In case you don’t have any Python XML parser you will need to rename the second one _start_thumbnail, yet I didn’t check how to make the first one work in that case.

Fil RSS 1.0 avec Dotclear2

DC2 installé depuis quelques jourt, et damned, pas de flux RSS 1.0 ! Premier plongeon dans ses entrailles (bien pensées, pour ce que j’en ai vu) pour l’ajouter. Le patch complet est disponible ici et l’explication pas à pas ci-dessous, les liens vers des fichiers correspondant aux fichiers modifiés (à partir de ceux de la 2.0beta2[1]).

Concernant l’écriture du flux en lui-même, il s’agit d’un “banal” template à réaliser – en se basant sur les exports RSS 2.0 et Atom existants afin de récupérer les noms de variables. Les 2 templates rdf.xml et rdf-comments.xml sont à placer dans le répertoire inc/public/default-templates/. Je n’ai pas ajouté de feuille de style pour afficher ces flux en HTML comme c’est le cas pour RSS 2.0 et Atom, notamment puisque Firefox2 s’en charge très bien – et un peu par flemme aussi, il faut bien l’avouer.

La seconde partie consiste à faire accepter à DC un flux nommé ‘rdf’ et non pas ‘rss2’ ou ‘atom’, et se fait en 2 étapes:

  • Dans inc/public/class.dc.template.php, il faut modifier les fonctions BlogFeedURL (l.542) et CategoryFeedURL (l.637) de façon à pouvoir passer l’argument ‘rdf’ aux fonctions de template qui vont générer les liens de syndication, sans qu’il soit remplacé par ‘rss2’, la valeur par défaut;
  • Dans inc/public/lib.urlhandlers.php, il faut maintenant gérer les URL correspondantes (l.346 et l.353) et en profiter pour définir un mime-type application/rdf+xml (l.400) pour le flux en question.

Ces étapes sont à répéter pour les flux par tags, mais tout ce fait cette fois-ci dans le fichier plugins/metadata/_public.php.

Troisième partie, intégrer ce flux dans les templates. Dans les fichiers category.html, post.html et tag.html de votre template (ou du template par défaut si vous voulez ensuite que la modification s’applique à tous vos thèmes qui ne surchargent pas ces fichiers), remplacer respectivement dans les appels de fonctions de template CategoryFeedURL, BlogFeedURL et TagFeedURL l’argument type="rss2" par type = "rdf". Ne modifiez pas ceux des entêtes mais ajoutez la ligne suivante dans _head.html:

 <link rel="alternate" type="application/rdf+xml" title="RSS 1.0" href="http://apassant.net/blog/feed/rdf" />

Dernière étape, facultative, modifier les liens de syndication du widget dedié. Pour cela, dans widgets/_defauls_widgets.php, ajoutez RDF aux options l.42, et dans widgets/_widgets_functions.php, modifiez la fonction subscribe pour authoriser ‘rdf’, avec le bon mime-type. D’ailleurs ici, ca serait pas mal que le flux choisi soit récupéré par défaut dans les différentes fonctions XXFeedURL, pour éviter d’avoir à changer les templates – ou alors j’ai loupé un truc[2] ?

Et voila, vous avez maintenant un DC2 avec un flux RSS1.0 ‘de base’, accessible via l’URI /feed/rdf que vous pourrez enrichir avec FOAF par ex, ou d’autres vocabulaires RDF (en attendant un plugin dedié Web Sémantique ;)

Notes

[1] La 2.0beta3 vient de sortir pendant que je finissais ce billet.

[2] Et tant qu’à faire, un flux RDF en natif dans DC2, ça serait également sympa.

Migration réussie

Ce blog tourne maintenant sous Dotclear2.

Migration sans soucis (a part un cafouillage en migrant du répertoire temporaire vers la prod), tags conservés, et une rewrite rule temporaire pour conserver les anciennes URLs en attendant de voir (ou pas) comment indiquer à DC de conserver l’ancien schéma d’adresses pour les nouveaux billets.

Le flux RSS1.0 est également disponible à la même adresse qu’auparavant. DC2 ne fournit qu’un flux Atom ou RSS2, la méthode de template est très intuitive et remettre un flux RSS1.0 – donc RDF – a été très rapide. Je fais la partie pour les commentaires, et je mets ça en ligne.

Edit 01h10: Je viens de voir que mes billets ont été réagrégés sur planete web-sémantique (puisque nouvelle URL, chose à laquelle je n’avais pas pensé), desolé pour ceux qui suivent le fil de la planète et qui vont ravoir les billets en double !

From RSS to SIOC using SPARQL

Recently, Danny Ayers asked on sioc-dev:

My blog has RDF inside, core stuff is RSS 1.0 vocab. Does anyone happen to a SPARQL CONSTRUCT for RSS 1.0 to SIOC?

I’ve never looked at the CONSTRUCT feature of SPARQL before, so I thought it was a good motivation to look at it. Basically, the goal of CONSTRUCT is to create a RDF graph from a SPARQL query (instead of getting the XML / JSON formatted-results). So it can be used to translate RDF data from one format to another, as soon as you can get data from source using a SPARQL query.

Regarding RSS 1.0 to SIOC, the mappings can be defined as:

  • rss:channel is a sioc:Forum (I think that’s the broader concept, as it’s the one that contains the posts), and rss:title, rss:link and rss:description can be mapped to dc:title, sioc:link and dc:description;
  • rss:item is a sioc:Post. The previously mentionned properties are mapped the same way.

So, the RSS1.0 “core” to SIOC CONSTRUCT query is:

PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX sioc: <http://rdfs.org/sioc/ns#>
PREFIX rss: <http://purl.org/rss/1.0/>
PREFIX dc: <http://purl.org/dc/elements/1.1/>

CONSTRUCT {
  ?channel rdf:type sioc:Forum .
  ?channel sioc:link ?channel_url .
  ?channel dc:title ?channel_title .
  ?channel dc:description ?channel_description .
  ?channel sioc:container_of ?item .
  ?item rdf:type sioc:Post .
  ?item sioc:link ?item_url .
  ?item dc:title ?item_title .
  ?item sioc:content ?item_content .
} WHERE {
  ?channel rdf:type rss:channel .
  ?channel rss:link ?channel_url .
  ?channel rss:title ?channel_title .
  ?channel rss:description ?channel_description .
  ?channel rss:items ?items .
  ?items ?li ?item .
  ?item rdf:type rss:item .
  ?item rss:link ?item_url .
  ?item rss:title ?item_title .
  ?item rss:description ?item_content .
}

As an example, here’s my RSS feed (core only) translated to SIOC, and rendered in the SIOC browser.

Yet, most blogs also use DC and content RSS extensions to add more information to each rss:item, so I mapped dc:date to dcterms:created, and kept content:encoded, both using OPTIONAL in the query [1].

Finally, I also wanted to map dc:creator, but this is more tricky as there’s different use cases:

  • If querying the posts feed or if your blog needs registration to comment, then dc:creator is a registered user of the system, so he’s a sioc:User, using dc:creator as a sioc:name. He can also be linked to a foaf:User, where dc:creator could be used as foaf:name (or maybe foaf:nick or rdf:label, I’m still confused with this). Yet, as nothing can clearly define a dc:creator, since this is just a simple string, it will create one sioc:User / foaf:User for each post, and I’m not sure it really makes sense, but here’s the part to add to make it work:
?item foaf:maker _:foaf .
_:foaf foaf:name ?item_creator .
_:foaf foaf:holdsAccount _:sioc .
_:foaf rdf:type foaf:Person .
?item sioc:has_creator _:sioc .
_:sioc rdf:type sioc:User .
_:sioc sioc:name ?item_creator .

in the CONSTRUCT part, and

?item dc:creator ?item_creator

in the WHERE clause. (eg: my RSS feed + browing it);

  • If you translate comments feed – allowing not-registered users -, as it was decided earlier, just use foaf:Person here, with this CONSTRUCT part:
 
?item foaf:maker _:foaf .
_:foaf rdf:type foaf:Person .
_:foaf foaf:name ?item_creator .

(eg: my comments feed + browsing it)

  • Finally, if your RSS feed uses dc:creator and foaf:maker as this one, you can get more information about foaf:Person:
 
?item foaf:maker _:foaf .
_:foaf rdf:type foaf:Person .
_:foaf foaf:name ?item_creator .
_:foaf foaf:holdsAccount _:sioc .
_:foaf foaf:nick ?foaf_nick .
_:foaf foaf:mbox_sha1sum ?foaf_sha1 .
_:foaf foaf:homepage ?foaf_homepage .
_:foaf rdfs:seeAlso ?foaf_seealso .
?item sioc:has_creator _:sioc .
_:sioc rdf:type sioc:User .
_:sioc sioc:name ?item_creator .

in the CONSTRUCT part, and

?item foaf:maker ?foaf .
?foaf rdf:type foaf:Person .
?foaf foaf:nick ?foaf_nick .
?foaf foaf:mbox_sha1sum ?foaf_sha1 .
?foaf foaf:homepage ?foaf_homepage .
?foaf rdfs:seeAlso ?foaf_seealso

in the WHERE clause, see result here + browsing it. Yet, I still can’t see how to merge all foaf:User into only one using only CONSTRUCT. If someone knows, I’ll be happy to get it.

So, basically, as most feeds contain only RSS, DC, and content vocabularies, here’s a SPARQL CONSTRUCT that should fits most of it (remove the SIOC part in CONSTRUCT for comments feeds):

PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX sioc: <http://rdfs.org/sioc/ns#>
PREFIX rss: <http://purl.org/rss/1.0/>
PREFIX content: <http://purl.org/rss/1.0/modules/content/>
PREFIX dc: <http://purl.org/dc/elements/1.1/>
PREFIX dcterms: <http://purl.org/dc/terms/>
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
CONSTRUCT {
  ?channel rdf:type sioc:Forum .
  ?channel sioc:link ?channel_url .
  ?channel dc:title ?channel_title .
  ?channel dc:description ?channel_description .
  ?channel sioc:container_of ?item .
  ?item rdf:type sioc:Post .
  ?item sioc:link ?item_url .
  ?item dc:title ?item_title .
  ?item dcterms:created ?item_created .
  ?item sioc:content ?item_content .
  ?item content:encoded ?item_content_encoded .
  ?item dc:subject ?item_subject .
  ?item foaf:maker _:foaf .
  _:foaf foaf:name ?item_creator .
  _:foaf foaf:holdsAccount _:sioc .
  _:foaf rdf:type foaf:Person .
  ?item sioc:has_creator _:sioc .
  _:sioc rdf:type sioc:User .
  _:sioc sioc:name ?item_creator .
} WHERE {
  ?channel rdf:type rss:channel .
  ?channel rss:link ?channel_url .
  ?channel rss:title ?channel_title .
  ?channel rss:description ?channel_description .
  ?channel rss:items ?items .
  ?items ?li ?item .
  ?item rdf:type rss:item .
  ?item rss:link ?item_url .
  ?item rss:title ?item_title .
  ?item rss:description ?item_content .
  OPTIONAL {
    ?item dc:date ?item_created
  } . OPTIONAL {
    ?item content:encoded ?item_content_encoded
  } . OPTIONAL {
    ?item dc:subject ?item_subject
  } . OPTIONAL {
    ?item dc:creator ?item_creator
  }
}

My translated RSS feed there, and the same one in the browser.

Notes

[1] I fist struggled with CONSTRUCT / OPTIONAL, before realising thanks to #swig guys that it was a librdf bug, not a SPARQL one, and it runs fine with Jena/ARQ which runs in these examples with sparql.org service.

Timeline for RSS feeds

When playing with Timeline, I thaught it could be a nice interface for RSS feeds, especially for weblogs or planets.

So, I wrote an ”RSS to Timeline” service, that takes any RSS/Atom feed as an input, and translates it into the correct JSON / Timeline format. Just put the correct URL as a data source for your Timeline, and you’ll get it !

Eg:

As you can see, I’ve also setup a demo service where you can see your feed in action. Everything is described in details here.

Regarding the implementation, the script is written in Python, using feedparser and mod_python. I first started in PHP with MagpieRSS, but it doesn’t provide universal methods to access feed/items informations, so the way to access content depends on the feed format. Yet, with feedparser, methods and properties are the same whatever your feed: is RSS 0.9, RSS 1.0, Atom … which is really interesting for writing universal agregators / translators.

It was also the first time I used mod_python, and I must say the Publisher handler is also very easy to use, with a templating system and interaction between the interface and the script.