Pour illustrer la notion de contexte d’une BOUCLE dans un squelette SPIP, le tutoriel suivant propose l’élaboration d’un squelette de podcast.
Un podcast est un simple fichier XML publié par un site web et qui se conforme à une structure lui permettant d’être lu et "compris" (i.e. d’être compatible) avec les logiciels de podcast tels que Juice et SongBird (open source) ou iTunes et Winamp (propriétaires) : voir ce tutoriel winamp et podcast
Pour plus de précisions sur le XML en général voir : Le langage XML : structuration, validation et transformations.
Pour plus de précisions sur la structure d’un fichier de podcast voir Les flux RSS, podcast et vidéocast et Créer un podcast
Structure du fichier de podcast attendu
L’examen d’un fichier XML de podcast (code du fichier type) montre que le squelette du podcast doit contenir 2 parties :
- la partie qui débute le flux et qui permet de décrire le site émetteur et le podcast dans son ensemble. Cette partie est "fixe" : son contenu est indépendant des items diffusés, les informations quelle contient sont spécifique du site émetteur.
- la liste des items : dans le SPIP chaque item correspondra à un article, le fichier média "podcasté" (i.e. associé à l’item) sera un document attaché. Cette liste est donc constituée par les n derniers articles publiés. Chaque item est structuré de façon identique.
Un squelette de podcast basique
Pour générer le podcast, dans le dossier /squelettes, on va créer un fichier de squelette spécifique : podcast.html. Selon la règle générale de fonctionnement de SPIP, l’URL de ce fichier de squelette sera donc de la forme : http://site.../spip.php?page=podcast.
Le contenu de ce fichier sera donc un mélange de code SPIP et des balises XML typiques du format podcast :
la première partie du fichier (informations concernant le site et le podcast) va donc faire appel aux balises SPIP se rapportant au site (cf Les balises propres au site). Pour chaque élément descriptif on va insérer dans le XML la balise SPIP correspondant au contenu recherché : par exemple pour le titre et l’url du podcast :
<title>#NOM_SITE_SPIP</title>
donnera <title>Lycee Truc</title>
et <link>#URL_SITE_SPIP</link>
retourne <link>http://lyc-truc.ac-aix-marseille.fr</link>
Remarque : les problèmes de validation du XML imposent un traitement des contenus pour éviter les caractères interdits : pour cela on applique l’enchaînement de filtres |textebrut|entites_html|entites_unicode sur les textes ou |texte_backend.
On obtient donc le code suivant pour la première partie :
- #CACHE{3600}[(#HTTP_HEADER{Content-type: text/xml[; charset=(#CHARSET)]})]<?xml version="1.0"[ encoding="(#CHARSET)"]?>
- <rss xmlns:itunes="http://www.itunes.com/DTDs/Podcast-1.0.dtd" version="2.0">
- <channel>
- <title>[(#NOM_SITE_SPIP|textebrut|entites_html|entites_unicode)]</title>
- <link>[(#URL_SITE_SPIP)]</link>
- [<description>
- (#DESCRIPTIF_SITE_SPIP|textebrut|entites_html|entites_unicode)
- </description>]
- <generator>SPIP</generator>
- <docs>http://blogs.law.harvard.edu/tech/rss</docs>
- <language>#LANG</language>
- <copyright>
- [(#NOM_SITE_SPIP|textebrut|entites_html|entites_unicode)]
- [(#DATE_annee|textebrut|entites_html|entites_unicode)]
- </copyright>
- <managingEditor>#EMAIL_WEBMASTER</managingEditor>
- <BOUCLE_webmaster(AUTEURS){id_auteur=1}>
- <webMaster>[(#NOM|texte_backend)]</webMaster>
- </BOUCLE_webmaster>
- <pubDate>[(#DATE|affdate{'r'})]</pubDate>
- <lastBuildDate>
- [(#DATE_NOUVEAUTES|affdate)] [(#DATE_NOUVEAUTES|heures)]:
- [(#DATE_NOUVEAUTES|minutes)]:[(#DATE_NOUVEAUTES|secondes)]
- </lastBuildDate>
- <image>
- <url>[href="(#LOGO_SITE_SPIP|
>extraire_attribut{src}|url_absolue|texte_backend)"]</url> - <title>#NOM_SITE_SPIP</title>
- <link>#URL_SITE_SPIP</link>
- <title>#NOM_SITE_SPIP</title>
- </image>
- <itunes:author>Nom du site</itunes:author>
- <itunes:link rel="image" type="video/png" href="http://www.url-du-site.org/adresse_images_site.png">Nom du site</itunes:link>
- [<itunes:image href="(#LOGO_SITE_SPIP|
>extraire_attribut{src}|url_absolue|texte_backend)" />] - [<itunes:category text="(#DESCRIPTIF_SITE_SPIP|textebrut|entites_html|entites_unicode)" />]
- <itunes:category text="#LANG" />
- <itunes:explicit>no</itunes:explicit>
pour la partie dynamique du podcast (la liste des items), dans une première approche, on utilise une boucle ARTICLES qui permet de lister les derniers articles publiés (BOUCLE_article dans la suite de cet article) qui contient une boucle DOCUMENTS (BOUCLE_podcast) pour afficher les documents attachés (fichiers qui vont être placés dans une balise XML <enclosure>
) :
- <BOUCLE_article(ARTICLES){par date} {inverse} {0,12}>
- <item>
- <title>[(#TITRE|texte_backend)]</title>
- <link>[(#URL_ARTICLE|url_absolue|unique)]</link>
- <description>[(#TEXTE|liens_absolus|texte_backend)]</description>
- <BOUCLE_categorie(RUBRIQUES){id_rubrique}{0,1}>
- <category>#TITRE</category>
- </BOUCLE_categorie>
- <pubDate>[(#DATE|affdate{'r'})]</pubDate>
- <BOUCLE_podcast(DOCUMENTS){id_article}{mode=document}>
- [<enclosure url="(#URL_DOCUMENT|url_absolue|unique)" length="[(#TAILLE)]" type="#MIME_TYPE" />]
- </BOUCLE_podcast>
- <guid isPermaLink="false">[(#URL_ARTICLE|url_absolue|unique)]</guid>
- <itunes:author>
- <BOUCLE_auteursb(AUTEURS){id_article}{", "}>
- [(#NOM|texte_backend)]
- </BOUCLE_auteursb>
- </itunes:author>
- [<itunes:subtitle>(#SOUS_TITRE|texte_backend)</itunes:subtitle>]
- <itunes:summary>[(#TEXTE|liens_absolus|texte_backend)]</itunes:summary>
- <itunes:keywords>
- <BOUCLE_articles_mots(ARTICLES){id_mot} {par hasard} {0,4}>
- #TITRE
- </BOUCLE_articles_mots>
- </itunes:keywords>
- </item>
- </BOUCLE_article>
...et pour que le fichier obtenu soit complet, il ne faut pas oublier de fermer les balises rss et channel ouvertes au départ du fichier :
- </channel>
- </rss>
Ce squelette va donc retourner les 12 derniers articles publiés, avec pour chacun d’entre eux les documents attachés placés dans des balises enclosure.
Filtrer les documents mis dans <enclosure>
La première chose à faire est de ne sélectionner que les fichiers mp3 dans la balise enclosure : pour cela il faut modifier la BOUCLE_podcast de la façon suivante :
- <BOUCLE_podcast(DOCUMENTS){id_article}{mode=document}{extension=mp3}>
Exclure les articles n’ayant pas de fichier mp3 attaché
- Squelette "mp3 uniquement"
- Sélectionne parmi les 12 derniers articles ceux ayant un mp3 en document attaché
L’amélioration la plus notable (à combiner avec les critères ci-dessous), consiste à ne sélectionner que les articles ayant au moins un fichier mp3 en document attaché. Pour cela on utilise la BOUCLE_podcast(DOCUMENTS) en étendant son champ d’action à l’ensemble de l’item.
Cette modification nécessite une réorganisation complète pour que la BOUCLE_podcast englobe l’ensemble de la balise <item>...</item>
: le principe est d’utiliser le fait que si il n’existe pas de fichier mp3 renvoyé par cette boucle, on n’affiche pas la balise <item>...</item>
i.e. l’article ne sera donc pas intégré dans le flux du podcast.
- <BOUCLE_article(ARTICLES){id_rubrique ?} {par date} {inverse} {0,12}>
- <BOUCLE_podcast(DOCUMENTS){id_article}{mode=document}{extension=mp3}>
- <item>
- ...
- </item>
- </BOUCLE_podcast>
- </BOUCLE_article>
Le problème engendré par cette organisation de boucles imbriquées est que les éléments à afficher dans l’item peuvent êtres soit dépendant de la boucle DOCUMENTS (le <enclosure>...</enclosure>
principalement) qui est la boucle en cours, soit de la boucle ARTICLES englobante. Pour appeler les balises SPIP de la boucle englobante BOUCLE_article on va donc utiliser la syntaxe suivante : #_article:NOM_BALISE
pour remplacer les balises #NOM_BALISE
que l’on utiliserait sans cette imbrication supplémentaire. La partie dynamique du squelette devient donc :
- <BOUCLE_article(ARTICLES){id_rubrique ?} {par date} {inverse} {0,12}>
- <BOUCLE_podcast(DOCUMENTS){id_article}{mode=document}{extension=mp3}>
- <item>
- <title>[(#_article:TITRE|texte_backend)]</title>
- <link>[(#_article:URL_ARTICLE|url_absolue|unique)]</link>
- <description>[(#_article:TEXTE|liens_absolus|texte_backend)]</description>
- <BOUCLE_categorie(RUBRIQUES){id_rubrique}{0,1}>
- <category>#TITRE</category>
- </BOUCLE_categorie>
- <pubDate>[(#_article:DATE|affdate{'r'})]</pubDate>
- [<enclosure url="(#URL_DOCUMENT|url_absolue|unique)" length="[(#TAILLE)]" type="#MIME_TYPE" />]
- <guid isPermaLink="false">[(#_article:URL_ARTICLE|url_absolue|unique)]</guid>
- <itunes:author>
- <BOUCLE_auteursb(AUTEURS){id_article}{", "}>[(#NOM|texte_backend)]</BOUCLE_auteursb>
- </itunes:author>
- [<itunes:subtitle>(#_article:SOUS_TITRE|texte_backend)</itunes:subtitle>]
- <itunes:summary>[(#_article:TEXTE|liens_absolus|texte_backend)]</itunes:summary>
- <itunes:keywords><BOUCLE_mots(MOTS){id_article=#_article:ID_ARTICLE} {par hasard} {0,4}{", "}>#TITRE,</BOUCLE_mots></itunes:keywords>
- </item>
- </BOUCLE_podcast>
- </BOUCLE_article>
Améliorer la sélection des articles intégrés dans le flux
A partir des critères disponibles dans la boucle ARTICLES, plusieurs améliorations sont possibles / souhaitables à partir de la BOUCLE_article de base :
Ne sélectionner que les articles ayant un mot clé : pour cela on va créer un mot clé spécifique ("podcast" par exemple) qui sera attribués aux articles que l’on veut voit apparaitre dans le flux. La boucle sera alors modifiée de la façon suivante :
- <BOUCLE_article(ARTICLES){titre_mot = podcast} {par date} {inverse} {0,12}>
Dans le même ordre d’idée, on peut vouloir utiliser plusieurs mots clés (par exemple ici "podcast" et "sons") : le code de la boucle sera alors :
- <BOUCLE_article(ARTICLES){titre_mot IN (podcast,sons)}{par date} {inverse} {0,12}>
Toujours dans la même lignée on peut utiliser tous les mots clés d’un groupe de mots clés (par exemple ici le groupe multimédia) :
- <BOUCLE_article(ARTICLES){type_mot=multimédia}{par date} {inverse} {0,12}>
Il est également possible de faire une sélection par mots clés sur les rubriques (par ex, ne podcaster que sur les rubriques "Anglais", "Italien" et "Allemand"). Pour ce faire on attribue le mot clé "podcast" aux rubriques en question, puis on modifie le squelette de façon à trier sur les rubriques avant de boucler dans les articles. Notez le {id_rubrique}
de la BOUCLE_article : il est nécessaire pour préciser que cette boucle ne prend que les articles de la rubrique sélectionnée par la BOUCLE_tri_rub englobante.
- <BOUCLE_tri_rub(RUBRIQUES){titre_mot=podcast}>
- <BOUCLE_article(ARTICLES){id_rubrique} {par date} {inverse} {0,12}>
- <BOUCLE_podcast(DOCUMENTS){id_article}{mode=document}{extension=mp3}>
- <item>
- <title>[(#_article:TITRE|texte_backend)]</title>
- ....
- </item>
- </BOUCLE_podcast>
- </BOUCLE_article>
- </BOUCLE_tri_rub>
Il est possible de combiner les différents niveaux de sélection : par exemple si on veut ne podcaster que les rubriques ayant le mot clé "podcast" en ne sélectionnant que les articles ayant le mot clé "sons", on fera alors :
- <BOUCLE_tri_rub(RUBRIQUES){titre_mot=podcast}>
- <BOUCLE_article(ARTICLES){titre_mot=sons} {id_rubrique} {par date} {inverse} {0,12}>
- Squelette "par mots articles"
- Sélectionne les articles ayant le mot clé "podcast" dans toutes les rubriques
- Squelette "par mots rubriques"
- Sélectionne les articles dans les rubriques ayant le mot clé "podcast"
- Squelette "par mots rubriques et articles"
- Sélectionne les articles ayant le mot clé "podcast" uniquement dans les rubriques ayant le mot clé "podcast"
Générer un flux pour chaque rubrique
Jusque là le squelette de podcast émet un flux général (à l’échelle de l’ensemble du site) unique. On peut vouloir émettre un flux de podcast par rubrique (il semble légitime de vouloir un flux différent pour l’anglais, l’italien et l’allemend par exemple) : il va donc falloir récupérer le contexte dans lequel se trouve appelé le squelette. Ce contexte de rubrique sera donné par l’url d’appel de la page : de la façon suivante : .../spip.php?id_rubrique=12
ou .../?id_rubrique=12
. Dans tous les cas cela permet de pouvoir récupérer un id_rubrique dans le squelette... et de l’utiliser pour limiter la BOUCLE_article de la façon suivante :
- <BOUCLE_article(ARTICLES){id_rubrique} {par date} {inverse} {0,12}>
- <BOUCLE_podcast(DOCUMENTS){id_article}{mode=document}{extension=mp3}>
- <item>
- <title>[(#_article:TITRE|texte_backend)]</title>
- ...
- </item>
- </BOUCLE_podcast>
- </BOUCLE_article>
Ce squelette n’affichera que les articles de la rubrique n°12 si on l’appelle avec : .../spip.php ?page=podcast&id_rubrique=12 alors qu’il n’affichera que les articles de la rubrique n°14 si il est appelé par .../spip.php ?id_rubrique=14&page=podcast [1]
- Le squelette "par rubrique"
- Sélectionne les articles de la rubrique passée dans l’url ou tout le site si pas de id_rubrique=...
Le problème de ce squelette est qu’il ne retourne rien si il n’y a pas d’id_rubrique défini dans l’url... On peut améliorer la chose en faisant :
- <BOUCLE_article(ARTICLES){id_rubrique ?} {par date} {inverse} {0,12}>
Le ? du critère {id_rubrique ?}
permettant de faire une sélection sur toutes les rubriques si il n’existe pas de contexte de rubrique passé par l’url.
On se retrouve donc avec le beurre et l’argent du beurre : un flux de podcast général pour tout le site en appelant .../spip.php ?page=podcast et un flux spécifique par rubrique avec .../spip.php ?page=podcast&id_rubrique=XX !