Syndication : Atom 1.0  RSS 2.0
Blogs des développeurs   »   Alp Mestan :: Blog

Article complet: Récupération et traitement d'un flux Atom XML en Haskell

16/06/2009

Permalink 04:20:10, Catégories: Récapitulatif OpenSource, Récapitulatif, Programmation Fonctionnelle, 1006 mots   French (FR) , Alp Mestan

[OpenSource] Récupération et traitement d'un flux Atom XML en Haskell

Bonsoir,

Il y a peu, on m'a demandé d'écrire un programme qui récupère un flux Atom XML et qui ne récupère que le titre et l'url des éléments (qui en l'occurence sont des billets aggrégés sur Planet OCaml).

J'ai hésité entre OCaml, C++ et Haskell. Plutôt tenté par du fonctionnel, mon choix s'est vite porté sur Haskell grâce au nombre impressionnant de paquets présents sur Hackage.
J'ai donc opté pour le paquet feed pour la gestion d'Atom et download pour la récupération du XML distant, qui se situe ici).

Haskell Logo

[Suite:]

Alors, voyons voir... Premièrement, on importe les modules dont on a besoin, évidemment.
 
import Network.Download 
import Text.Atom.Feed 
import Text.Feed.Import 
import Text.Feed.Types 

Ensuite, on définit l'adresse du fichier atom.xml... Toujours rien de bien sorcier.
 
url = "http://planet.ocamlcore.org/atom.xml" 

Puis on se lance dans main !
 
main = do 
  putStrLn "*** Recent blog posts ***" 
  Right src <- openURIString url 

Ici, on affiche simplement un message dans la console, puis on récupère le contenu du fichier dans la chaîne de caractères src.
Right est l'un des constructeurs du type Either, ne vous en préoccupez pas trop.

Ensuite, on s'occupe à proprement parler du flux Atom...
 
let Just (AtomFeed is) = parseFeedString src 

Just est un constructeur du type Maybe, qui permet de gérer les erreurs (Just mavaleur si pas eu d'erreur, Nothing si une erreur). AtomFeed est lui un constructeur pour le type Feed précisant que c'est un flux Atom, et non RSS1 ou RSS2 par exemple (qui sont eux aussi gérés par ce même paquet). La fonction parseFeedString prend donc une chaîne (ici src) et retourne un flux Atom... C'est là, en utilisant is, que l'on va pouvoir récupérer les différents éléments du XML au format Atom.

Bon, et si on récupérait les différentes entrées de notre flux (ici ce sont des billets de blog) ?
 
let entries = feedEntries is 

Ceci nous retourne donc une liste d'Entry. Et c'est parti, on va récupérer les informations qu'il nous faut, puis les afficher !
 
let infos = map (\e -> (entryTitle e, entryId e)) entries 
mapM_ (\(x,y) -> putStrLn $ (stringize x) ++ ": " ++ y) infos 

Oui, oui, ça se complique un peu.
Tout d'abord, qu'est-ce que infos ? map transforme chaque élément en l'image de l'élément donné par son premier argument, qui est donc une fonction. Ici, on va transformer chaque Entry en un couple (titre, id) associé à notre entrée, où id se trouve être l'URL originale du billet.
Bon infos est donc la liste des couples (titre, url). Ah ? Ce n'étant pas tellement cette ligne qui vous faisait peur mais la suivante ?
Bon, sans rentrer en détail dans les monades (la page Monad du HaskellWiki le faisant mieux que moi, et surtout donnant d'excellents liens pour comprendre de quoi il s'agit, où c'est utilisé, etc), on se trouve dans la monade IO. mapM_ se retrouve donc avec le type :
 
(a -> IO b) -> [a] -> IO () 

Notre [a], c'est infos, donc notre liste de couples...
(a -> IO b) est effectivement le type de notre fonction anonyme, que je me permets de vous montrer à part ici :
 
\(x,y) -> putStrLn $ (stringize x) ++ ": " ++ y 

A un couple (titre,url), elle associe un appel à putStrLn, qui retourne IO (). Donc le type b est en fait (). mapM_ ne faut qu'effectuer des actions dans une monade donnée, en ignorant le résultat de chacune au lieu de les placer des une liste comme le fait son homologue mapM

Ah oui, j'oubliais, stringize, qui est définie juste après fonction main.
 
stringize :: TextContent -> String 
stringize (TextString s) = s 
stringize _ = error "shoud not be called on something else than TextString" 

Elle me permet juste de passer d'une valeur construite avec TextString, donc de type TextContent, à la valeur qui est en fait englobée par ce type, de type String.

Voilà donc le code complet :
 
import Network.Download 
import Text.Atom.Feed 
import Text.Feed.Import 
import Text.Feed.Types 
 
url = "http://planet.ocamlcore.org/atom.xml" 
 
main = do 
  putStrLn "*** Recent blog posts ***" 
  Right src <- openURIString url 
  let Just (AtomFeed is) = parseFeedString src 
  let entries = feedEntries is 
  let infos = map (\e -> (entryTitle e, entryId e)) entries 
  mapM_ (\(x,y) -> putStrLn $ (stringize x) ++ ": " ++ y) infos 
 
stringize :: TextContent -> String 
stringize (TextString s) = s 
stringize _ = "stringize Error" 

La compilation :

$ ghc -package download -o cwn cwn.hs

Et un exemple d'execution :

$ ./cwn
*** Recent blog posts ***
ocaml-text: http://forge.ocamlcore.org/projects/ocaml-text/
0.1.3 sources now in subversion: http://forge.ocamlcore.org/forum/forum.php?forum_id=355
Sudoku in ocamljs, part 2: RPC over HTTP: tag:blogger.com,1999:blog-1445545651031573301.post-3490486535879812384
Caml Weekly News, 28 Apr 2009: http://alan.petitepomme.net/cwn/2009.04.28.html
Bouncing Ball in OCaml with OCamlSDL: http://blog.mestan.fr/?p=31
Sudoku in ocamljs, part 1: DOM programming: tag:blogger.com,1999:blog-1445545651031573301.post-4574121943207730951
Using OCaml’s module functors to provide monadic contexts for Batteries: http://blog.mestan.fr/?p=30
Lastfm no longer free as in free beer (and some bits about xml in OCaml): http://blog.rastageeks.org/spip.php?article34
Last lecture: http://dutherenverseauborddelatable.wordpress.com/?p=571
Liquidsoap now supports AAC+ encoding.: http://blog.rastageeks.org/spip.php?article33

Alors, pas si "académique" que ça le fonctionnel, non ? ;)
Tout ça en 18 lignes, lignes vides comprises, 15 non comprises.

Enjoy.

PS : on pourrait raccourcir et rendre le code moins compréhensible en utilisant des opérateurs et moins de valeurs intermédiaires, mais ce n'est pas l'objectif ici.

Social Bookmarking:

                                     

Commentaires, Pingbacks:

Connectez-vous pour vous abonner à cet article:

Flux de commentaires pour cet article : Atom 1.0  RSS 2.0
Commentaire de: SpiceGuid [Membre]
Encore une bonne initiative.

C'est tellement compact qu'un programmeur impératif pourrait croire que tu ne traite pas le cas où openURIString et parseFeedString échouent (ces cas sont traités silencieusement, il y a un filtrage partiel qui s'il échoue met fin au programme en libérant les ressources).

Tu as eu la bonne idée de fournir la ligne de commande pour la compilation. C'est un détail qui a son importance, j'y penserai à l'avenir.

Permalien 16/06/2009 @ 16:42
Commentaire de: Alp Mestan [Membre] · http://alp.developpez.com/
Oui, j'ai oublié de mentionner que les cas d'erreurs (non présence du fichier là où je le veux, mauvais parsing, etc) provoquent l'arrêt du programme. Ca n'aurait pas de sens d'essayer de faire autre chose si quelque chose échoue durant l'exécution. Une exception, par exemple, est assez explicite ici pour nous dire ce qui n'a pas marché.

Pour faire un équivalent en OCaml, ça sera un peu plus long/tricky, étant donné qu'il n'y a si je ne m'abuse pas de bibliothèques qui mâchent autant le travail.
Permalien 16/06/2009 @ 17:37
Commentaire de: SpiceGuid [Membre]
Désolé de décevoir tes attentes mais je n'ai pas vraiment l'intention de faire l'équivalent en OCaml. Si je le faisais ça serait en étendant mon module Lex pour lui permettre de parser du xml. Sauter à la balise fermante, capturer le contenu d'une balise, c'est à peu près les seules fonctions qui me manquent pour parser du xml en LL(1).

Permalien 16/06/2009 @ 22:59

Vous devez être identifié pour poster un commentaire.

Liste des blogs

Alp Mestan :: Blog


Powered by Caml
Powered by Haskell
Mon blog en anglais.

Blog de Alp Mestan

Rechercher

<  Février 2012  >
Lun Mar Mer Jeu Ven Sam Dim
    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29        

Syndiquez ce blog XML

Articles :

Commentaires :

 
 
 
 
Partenaires

Hébergement Web