module Text.XmlTv ( Channel(..) , Program(..) , xmlToChannel , xmlToProgram , parseChannels , parsePrograms , filterChans , updateChannel , findChan , sortChans , previous , current , later ) where import Control.Monad import Data.Maybe import Text.XML.Light import Data.Time import System.Locale data Channel = Channel { cid :: String , lang :: String , name :: String , base :: String , programs :: [Program] } deriving (Show, Eq) data Program = Program { start :: UTCTime , stop :: UTCTime , title :: String , description :: String } deriving (Show, Eq) toDate :: String -> Maybe UTCTime toDate str = parseTime defaultTimeLocale "%Y%m%d%H%M%S %z" str previous, current, later :: Program -> UTCTime -> Bool previous (Program start stop _ _) now = diffUTCTime start now < 0 && diffUTCTime stop now < 0 current (Program start stop _ _) now = diffUTCTime start now < 0 && diffUTCTime stop now > 0 later (Program start stop _ _) now = diffUTCTime start now > 0 && diffUTCTime stop now > 0 xmlToChannel :: Element -> Maybe Channel xmlToChannel e = do id <- findAttr (QName "id" Nothing Nothing) e d <- findChild (QName "display-name" Nothing Nothing) e lang <- findAttr (QName "lang" Nothing Nothing) d title <- listToMaybe . map cdData . onlyText . elContent $ d b <- findChild (QName "base-url" Nothing Nothing) e base <- listToMaybe . map cdData . onlyText . elContent $ b return $ Channel id lang title base [] -- A lot of optional fields that we should parse xmlToProgram :: Element -> Maybe Program xmlToProgram e = do start <- findAttr (QName "start" Nothing Nothing) e >>= toDate stop <- findAttr (QName "stop" Nothing Nothing) e >>= toDate t <- findChild (QName "title" Nothing Nothing) e --d <- findChild (QName "desc" Nothing Nothing) e title <- listToMaybe . map cdData . onlyText . elContent $ t --desc <- listToMaybe . map cdData . onlyText . elContent $ d return (Program start stop title "") parseChannels :: String -> [Maybe Channel] parseChannels str = do case parseXMLDoc str of Just p -> let f = findElements (QName "channel" Nothing Nothing) p in map xmlToChannel f Nothing -> [] parsePrograms :: String -> [Maybe Program] parsePrograms str = do case parseXMLDoc str of Just p -> let f = findElements (QName "programme" Nothing Nothing) p in map xmlToProgram f Nothing -> [] -- Starts of by filtering empty channels and then applies another filter. filterChans :: (Channel -> Bool) -> [Maybe Channel] -> [Channel] filterChans f chans = let pure = catMaybes chans in filter f pure sortChans :: [String] -> [Channel] -> [Channel] sortChans strs chans = map (findChan chans) strs findChan :: [Channel] -> String -> Channel findChan chans str = head . filter ((==) str . name) $ chans -- takes a channel, a prefix and a fetch method; -- then etches all programs for that channel using prefix -- (often date). updateChannel :: String -> (String -> IO String) -> Channel -> IO Channel updateChannel prefix fetch c = do let url = base c ++ cid c ++ prefix tv <- liftM (catMaybes . parsePrograms) . fetch $ url return c { programs = (programs c) ++ tv}