Récupération et parsing 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

Haskell Logo

Alors, voyons voir… Premièrement, on importe les modules dont on a besoin, évidemment.

?View Code HASKELL
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.

?View Code HASKELL
url = "http://planet.ocamlcore.org/atom.xml"

Puis on se lance dans main !

?View Code HASKELL
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…

?View Code HASKELL
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) ?

?View Code HASKELL
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 !

?View Code HASKELL
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 :

?View Code HASKELL
(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 :

?View Code HASKELL
\(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.

?View Code HASKELL
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 :

?View Code HASKELL
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.

, , , ,

  1. #1 by credit loans - mars 6th, 2010 at 02:52

    People in the world get the loan from different banks, just because it’s comfortable and fast.

(will not be published)
  1. No trackbacks yet.