Comment réaliser un lecteur de flux universel

Nous allons détailler les étapes de construction d'un lecteur de flux reconnaissant tous les formats, en utilisant les possibilités XML de PHP 5.
La connaissance de la structure d'un fichier RSS est indispensable pour cette étude.

Cette page extensible est réalisée avec Ajax Extensible Page de Xul.fr et du framework Anaa.

Structure d'un fichier RSS

Tout fichier de syndication contient une liste d'éléments, les articles, billets ou autres documents, et la description du site qui en est la source, que l'on appelle le canal.
Pour le canal aussi bien que pour les éléments, on donnera un titre et une description, ainsi qu'une URL.

Articles ou documents

Dans tous les formats, des données de bases sont reprises: le lien sur l'article, le titre, et un résumé.

<item> 
    <title>Tutoriels RSS</title>
    <link>http://www.scriptol.fr/lecteur-de-flux.php</link>     
    <description>Tutoriels sur la construction et l'utilisation de flux RSS </description> 
</item>

Le nom des balises est différent selon le format utilisé. D'autres données peuvent être fournies comme la date de parution, l'auteur, un logo, etc.

Le canal, ou site fournissant le contenu

Le flux comprend une description de la source, donc le site sur lequel sont publiés les documents. Son URL, le titre de la page d'accueil, une description du site.

<channel>
    <title></title>
    <link>http://www.scriptol.fr/</link>     
    <description></description> 
<channel>

Ici aussi, le nom des balises dépend du format utilisé.

Les éléments représentant les articles sont placés après le description du canal, comme on le voit dans les différents formats ci-dessous.

Différences entre les formats

Une différence globale entre RSS 2.0 et Atom est que RSS utilise le conteneur rss, et atom uniquement le canal. Les autres différences concernent les noms des balises.
En ce qui concerne RSS 1.0 qui se base sur RDF, la syntaxe s'éloigne nettement de celles des deux autres formats.

Format RSS 2.0

L'exemple est basé sur celui de la spécification du standard RSS 2.0 de Harvard.

<?xml version="1.0"?>
<rss version="2.0">
   <channel>
      <title>Xul News</title>
      <link>http://www.scriptol.fr/</link>
      <description>Réaliser un lecteur de flux.</description>
      <language>fr-FR</language>
      <pubDate>Tue, 10 Jun 2003 04:00:00 GMT</pubDate>
      <item>
         <title>Tutoriel</title>        
         <link>http://www.scriptol.fr/rss/</link>
         <description></description>
         <pubDate>Jeu, 28 Sep 2007 09:39:21 GMT</pubDate>
      </item>
   </channel>
</rss>

Format RSS 1.0 basé sur RDF

Le format 1.0 utilise les mêmes noms de balise que le 2.0 ce qui facilitera la construction d'un lecteur universel. Il y a cependant des différences de structures. En premier lieu, le conteneur rdf appartient à un espace de nom homonyme. La structure est définie dans la balise channel, mais les descriptifs des éléments sont ajoutés après cette balise.

L'exemple ci-dessous basé sur la spécification du standard RSS 1.0.

<?xml version="1.0"?>
<rdf:RDF 
  xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
  xmlns="http://purl.org/rss/1.0/"
>
 <channel rdf:about="http://www.xml.com/xml/news.rss">
    <title>scriptol.fr</title>
    <link>http://www.scriptol.fr</link>
    <description>   </description>
    <image rdf:resource="http://www.scriptol.fr/images/logo.gif" />
    <items>
        <rdf:Seq>
            <rdf:li resource="http://www.scriptol.fr/rss/" />
             ...autres articles...
         </rdf:Seq>
    </items>
  </channel>
  <image rdf:about="http://www.scriptol.fr/images/logo.gif">
       <title>scriptol.fr</title>
       <link>http://www.scriptol.fr</link>
       <url>http://www.scriptol.fr/universal/images/logo.gif</url>
 </image>
 <item rdf:about="http://www.scriptol.fr/rss/l">
      <title>RSS</title>
      <link>http://www.scriptol.fr/rss/</link>
      <description>   </description>
 </item>...autres items...
</rdf:RDF>

Même si le format est plus complexe, l'utilisation restera simple avec les fonctions XML de PHP et le DOM.

Format Atom, structure générale

Le format Atom utilise directement le canal comme conteneur racine. La balise de canal est feed et les éléments sont des entry.

<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.scriptol.fr">
  <title>Exemple de flux</title> 
  <link href="http://www.scriptol.fr/rss/"/>
  <updated></updated>
  <author> 
    <name>Denis Sureau</name>
  </author> 
  <entry>
    <title>Réaliser un lecteur de flux</title>
    <link href="http://www.scriptol.fr/rss/lecteur-de-flux.php"/>
    <updated></updated>
    <summary>Une description.</summary>
  </entry>
</feed>

On voit qu'Atom utilise des noms de balises propres tandis que les deux formats RSS partagent les mêmes, ce dont nous tirerons profit pour identifier le format d'un fichier de flux.

Utilisation de DOM avec PHP 5

Le modèle objet de documents permet d'extraire des balises dans un document XML ou HTML. On utilisera la fonction getElementsByTagName pour obtenir la liste des balises dont le nom est donné en paramètre. Cette fonction retourne une liste au format DOMNodeList, qui contient des éléments au format DOMNode.
Elle s'applique au document en entier, ou à un élément DOMNode et permet donc d'extraire des parties du fichier, le canal ou un élément et dans cette partie trouve une liste de balises.

Extraction du canal RSS
DOMDocument $doc = new DOMDocument("1.0");
DOMNodeList $canal = $doc->getElementsByTagName("channel");

On utilisera la paramètre "feed" pour un flux Atom. Noter que les noms de classes sont ici à titre informatif, le code PHP ne les utilise pas.

Extraction du premier élément
DOMElement $element = $canal.item(0);

On peut assigner un DOMElement à un DOMNode donc directement à l'appel de la méthode item() qui retourne un DOMNode.
L'avantage est que DOMElement dispose d'attributs et méthodes utilisables pour accéder au contenu de l'élément.

Extraction de tous les éléments
for($i = 0; $i < $canal->length; i++)
{
    $element = $canal->item(i);
}
Utilisation des données de l'élément

Pour chaque élément, comme pour le canal, on extrait les composants avec la même méthode et avec l'attribut firstChild. Par exemple pour le titre:

$title = $element.getElementsByTagName("title");   // obtenir la liste des balises title
$title = $title->item(0);  // obtenir la balise
$title = $title->firstChild->textContent;  // obtenir le contenu de la balise

A défaut de méthode pour extraire un élément seul, on utilise getElementsByTagName pour extraire une liste qui contiendra en fait un seul élément, et avec la méthode item, on récupère cet élément.
En XML, le contenu d'une balise est traité comme un sous-élément, donc on utilise la propriété firstChild pour obtenir l'élément XML du contenu, et data pour le contenu texte.

Il reste alors à appliquer ces méthodes sur le canal et sur chaque élément du flux pour en récupérer le contenu.

Pour une utilisation plus générale du lecteur de flux, la fonction implémentée retourne le contenu dans un tableau à deux dimensions. On aura ensuite le choix sur la façon de l'utiliser: l'afficher directement dans une page Web, ou effectuer un traitement sur ce tableau.

Comment identifier le format

Identifier le format est très simple si l'on sait que RSS 1.0 et 2.0 utilisent les mêmes balises, et donc que la même fonction pourrait s'appliquer à ces deux formats. On reconnaîtra ainsi le format Atom au conteneur feed tandis que RSS 2.0 utilise channel et 1.0 utilise rdf.
Comme les deux versions RSS utilisent la balise channel, c'est la présence de la balise feed qui permet d'identifier ce format.

DOMDocument $doc = new DOMDocument("1.0");
DOMNodeList $canal = $doc->getElementsByTagName("feed");
$isAtom = ($canal != false);

On essaie d'extraire le canal selon la balise "feed". Si l'interpréteur trouve cette balise, la liste de type DOMNodeList contiendra donc un élément. Le drapeau isAtom est placé à true quand l'objet n'est pas vide parceque la balise est présente, sinon on traitera le flux comme étant au format RSS, sans distinction.

Lecture des données du canal

On sait donc comment extraire le canal. La même fonction peut être utilisée avec en paramètre les chaînes "feed" ou "channel".
On suppose que le pointeur du document est une variable globale, $doc.

function extractChannel($chan)
{
   DOMNodeList $canal = $doc->getElementsByTagName($chan);
   return $canal->item(0);
}

On peut alors avec la fonction suivante, appelée avec le nom de chaque balise en paramètre, lire le titre et chaque élément descriptif du canal.

function getTag($tag)
{
   $content = $canal->getElementsByTagName($tag);
   $content = $content->item(0);
   return($content->firstChild->textContent);
}

On appelera alors successivement la fonction avec en paramètre "title", "link", "description"...

Les noms dépendront du format, ce sera "summary" ou "subtitle" pour Atom et "description" pour les autres.

Lecture des données des éléments

Le principe sera le même, mais on devra traiter en boucle une liste d'éléments tandis qu'il il n'y a qu'un seul canal.

Il faut aussi prendre en compte le fait que RSS 1.0 place les descriptions des éléments hors de la balise de canal alors qu'elles y sont contenues pour les autres formats. Les éléments sont contenus dans feed en Atom, dans channel en RSS 2.0 mais dans rdf:RDF en RSS 1.0.

La fonction extractItems extrait la liste des éléments, on lui passe en paramètre "item" pour RSS et "entry" pour Atom:

function extractItems($tag)
{
   DOMNodeList $dnl = $doc->getElementsByTagName($tag);
   return $dnl;
}

On utilise la liste retournée pour accéder à chaque élément. Celui-ci est ajouté dans le tableau $a.
Example avec le format RSS.

$a = array();
$items = extractItems("item");
for($i = 0; $i < $items->length; i++)
{
    array_push($a, $items->item($i));
}

On peut aussi directement créer un tableau des balises d'un élément, title, link, description pour chaque élément et le placer dans un tableau à deux dimensions.
Pour ce faire on utilisera une version générique de la fonction getTag définie précédemment:

function getTag($item, $tag)
{
   $content = $item->getElementsByTagName($tag);
   $content = $content->item(0);
   return($content->firstChild->textContent);
}

for($i = 0; $i < $items->length; i++)
{
    $a = array();
    $item = $items->item($i);
    array_push($a,  getTag($item, "title"));
   ... même chose pour chaque balise d'un élément ...

    array_push($FeedArray, $a);
}

Nous avons ainsi placé chaque article dans un tableau à deux dimensions que l'on peut simplement afficher ou utiliser à sa guise. On placera la boucle ci-dessus dans la fonction getTags.

Les fonctions du lecteur complet

Nous avons maintenant la liste de toutes les fonctions utiles pour le lecteur universel.

Avec les paramètres adéquats, ces fonctions s'utilisent pour tous les formats.

Chargement du flux

Dans le cas le plus basique, le flux est destiné à être intégré dans une page Web, soit avant le chargement, soit ultérieurement à la demande de l'utilisateur.

Quel que soit le format, et surtout pour les flux en français, il faut prendre garde à la compatibilité du format d'encodage, qui est le plus souvent UTF-8 pour le flux, et quelquefois ISO-8159 ou windows-1252 pour la page où s'affichera le flux.
Il est préférable de donner l'encodage UTF-8 à la page pour éviter un mauvais affichage des caractères accentués.

L'encodage est donné par la meta de type de contenu avec une ligne au format suivant:

<meta http-equiv="content-type" content="text/html; charset=UTF-8">

Chargement avec la page

Lorsque l'on veut afficher une page qui intègre un flux, on insère le code suivant dans le code HTML:

<?php
include("universal-reader.php");
Universal_Reader("http://www.scriptol.fr/rss.xml");
echo Universal_Display();
?>

Voir la démonstration donnée plus loin.

Chargement à la demande

Ce cas s'impose lorsque le visiteur choisit un flux dans une liste ou entre lui-même le nom du flux.
Le chargement peut alors se faire en Ajax pour un affichage asynchrone ou en PHP seul, en réaffichant la page.
On utilisera un formulaire avec un champ d'entrée de texte pour donner l'URL du flux ou un simple lien (ou un choix de liens) sur lequel(s) cliquer pour afficher un flux.

Téléchargement

Télécharger les fonctions Universal Reader dans une archive Zip.

Elle contient deux démonstrations.

Plus d'informations