module Text.OPML.Conduit.Render
(
renderOpmlHead
, renderOpmlOutline
, renderOpml
) where
import Control.Monad
import Data.Conduit
import Data.List.NonEmpty hiding (filter, map)
import Data.Monoid
import Data.NonNull
import Data.String
import Data.Text (Text, intercalate, pack, toLower)
import Data.Text.Encoding
import Data.Time.Clock
import Data.Time.LocalTime
import Data.Time.RFC822
import Data.Tree
import Data.Version
import Data.XML.Types
import Lens.Simple
import Prelude hiding (foldr, lookup, show)
import qualified Prelude (show)
import Text.OPML.Lens
import Text.OPML.Types
import Text.XML.Stream.Render
import URI.ByteString
show :: (Show a, IsString t) => a -> t
show = fromString . Prelude.show
empty :: (Eq s, Monoid s) => s -> Bool
empty t = t == mempty
toMaybe :: (Eq s, Monoid s) => s -> Maybe s
toMaybe s | s == mempty = mempty
| otherwise = Just s
formatTime :: UTCTime -> Text
formatTime = formatTimeRFC822 . utcToZonedTime utc
formatCategories :: [NonEmpty (NonNull Text)] -> Maybe Text
formatCategories = toMaybe . intercalate "," . map (intercalate "/" . toList . fmap toNullable)
formatBool :: Bool -> Text
formatBool = toLower . show
formatURI :: URI -> Text
formatURI = decodeUtf8 . serializeURIRef'
renderOpmlHead :: (Monad m) => OpmlHead -> Source m Event
renderOpmlHead input = tag "head" mempty $ do
forM_ (input^.opmlCreatedL) $ tag "dateCreated" mempty . content . formatTime
forM_ (input^.modifiedL) $ tag "dateModified" mempty . content . formatTime
forM_ (input^.docsL) $ tag "docs" mempty . content . formatURI
unless (null es) $ tag "expansionState" mempty . content . intercalate "," $ show <$> es
unless (empty email) $ tag "ownerEmail" mempty $ content email
forM_ (input^.ownerIdL) $ tag "ownerId" mempty . content . formatURI
unless (empty name) $ tag "ownerName" mempty $ content name
unless (empty title) $ tag "title" mempty $ content title
forM_ (input^.vertScrollStateL) $ tag "vertScrollState" mempty . content . show
forM_ (input^.windowBottomL) $ tag "windowBottom" mempty . content . show
forM_ (input^.windowLeftL) $ tag "windowLeft" mempty . content . show
forM_ (input^.windowRightL) $ tag "windowRight" mempty . content . show
forM_ (input^.windowTopL) $ tag "windowTop" mempty . content . show
where es = input ^.. expansionStateL
email = input ^. ownerEmailL
name = input ^. ownerNameL
title = input ^. opmlTitleL
renderOpmlOutline :: (Monad m) => Tree OpmlOutline -> Source m Event
renderOpmlOutline (Node outline subOutlines) = tag "outline" attributes $ mapM_ renderOpmlOutline subOutlines
where attributes = case outline of
OpmlOutlineGeneric b t -> baseAttr b <> optionalAttr "type" (toMaybe t)
OpmlOutlineLink b uri -> baseAttr b <> attr "type" "link" <> attr "url" (formatURI uri)
OpmlOutlineSubscription b s -> baseAttr b <> subscriptionAttr s
baseAttr b = attr "text" (toNullable $ b^.textL)
<> optionalAttr "isComment" (formatBool <$> b^.isCommentL)
<> optionalAttr "isBreakpoint" (formatBool <$> b^.isBreakpointL)
<> optionalAttr "created" (formatTime <$> b^.outlineCreatedL)
<> optionalAttr "category" (formatCategories $ b^.categoriesL)
subscriptionAttr s = attr "type" "rss"
<> attr "xmlUrl" (formatURI $ s^.xmlUriL)
<> optionalAttr "htmlUrl" (formatURI <$> s^.htmlUriL)
<> optionalAttr "description" (toMaybe $ s^.descriptionL)
<> optionalAttr "language" (toMaybe $ s^.languageL)
<> optionalAttr "title" (toMaybe $ s^.subscriptionTitleL)
<> optionalAttr "version" (toMaybe $ s^.subscriptionVersionL)
renderOpml :: (Monad m) => Opml -> Source m Event
renderOpml opml = tag "opml" (attr "version" . pack . showVersion $ opml^.opmlVersionL) $ do
renderOpmlHead $ opml^.opmlHeadL
tag "body" mempty . mapM_ renderOpmlOutline $ opml^.opmlOutlinesL