    {-# LANGUAGE CPP #-}
    --------------------------------------------------------------------
    -- |
    -- Module    : Text.Feed.Import
    -- Copyright : (c) Galois, Inc. 2008,
    --             (c) Sigbjorn Finne 2009-
    -- License   : BSD3
    --
    -- Maintainer: Sigbjorn Finne <sof@forkIO.com>
    -- Stability : provisional
    --
    -- Convert from XML to Feeds.
    --
    --------------------------------------------------------------------

    module Text.Feed.Import
            ( parseFeedFromFile -- :: FilePath -> IO Feed
            , parseFeedString   -- :: String -> Maybe Feed
            , parseFeedSource   -- :: XmlSource s => s -> Maybe Feed

              -- if you know your format, use these directly:
            , readRSS2          -- :: XML.Element -> Maybe Feed
            , readRSS1          -- :: XML.Element -> Maybe Feed
            , readAtom          -- :: XML.Element -> Maybe Feed
            ) where

    import Text.Atom.Feed.Import as Atom
    import Text.RSS.Import       as RSS
    import Text.RSS1.Import      as RSS1

    import Text.Feed.Types
    import Text.XML.Light as XML
    import Text.XML.Light.Lexer ( XmlSource )

    import Control.Monad

    #if MIN_VERSION_utf8_string(1,0,0)
    import Codec.Binary.UTF8.String (decodeString)
    import System.IO (IOMode(..), hGetContents, openBinaryFile )
    utf8readFile :: FilePath -> IO String
    utf8readFile fp = fmap decodeString (hGetContents =<< openBinaryFile fp ReadMode)
    #else
    import System.IO.UTF8 as UTF8 ( readFile )
    utf8readFile :: FilePath -> IO String
    utf8readFile = UTF8.readFile
    #endif

    -- | 'parseFeedFromFile fp' reads in the contents of the file at @fp@;
    -- the assumed encoding is UTF-8.
    parseFeedFromFile :: FilePath -> IO Feed
    parseFeedFromFile fp = do
      ls <- utf8readFile fp
      case parseFeedString ls of
        Nothing -> fail ("parseFeedFromFile: not a well-formed XML content in: " ++ fp)
        Just f  -> return f

    -- | 'parseFeedWithParser tries to parse the string @str@
    -- as one of the feed formats. First as Atom, then RSS2 before
    -- giving RSS1 a try. @Nothing@ is, rather unhelpfully, returned
    -- as an indication of error.
    parseFeedWithParser :: XmlSource s => (s -> Maybe Element) -> s -> Maybe Feed
    parseFeedWithParser parser str =
      case parser str of
        Nothing -> Nothing
        Just e ->
          readAtom e `mplus`
          readRSS2 e `mplus`
          readRSS1 e `mplus`
          Just (XMLFeed e)

    parseFeedString :: String -> Maybe Feed
    parseFeedString = parseFeedSource

    -- | 'parseFeedSource s' tries to parse the source @s@ as
    -- one of the feed formats. First as Atom, then RSS2 before giving
    -- RSS1 a try. @Nothing@ is, rather unhelpfully, returned as an
    -- indication of error.
    parseFeedSource :: XmlSource s => s -> Maybe Feed
    parseFeedSource = parseFeedWithParser parseXMLDoc

    -- | 'readRSS2 elt' tries to derive an RSS2.x, RSS-0.9x feed document
    -- from the XML element @e@.
    readRSS2 :: XML.Element -> Maybe Feed
    readRSS2 e = fmap RSSFeed  $ RSS.elementToRSS e

    -- | 'readRSS1 elt' tries to derive an RSS1.0 feed document
    -- from the XML element @e@.
    readRSS1 :: XML.Element -> Maybe Feed
    readRSS1 e = fmap RSS1Feed $ RSS1.elementToFeed e

    -- | 'readAtom elt' tries to derive an Atom feed document
    -- from the XML element @e@.
    readAtom :: XML.Element -> Maybe Feed
    readAtom e = fmap AtomFeed $ Atom.elementFeed e