feed-1.3.2.1: src/Text/Feed/Constructor.hs
{-# OPTIONS -fno-warn-incomplete-patterns #-}
--------------------------------------------------------------------
-- |
-- Module : Text.Feed.Constructor
-- Copyright : (c) Galois, Inc. 2008,
-- (c) Sigbjorn Finne 2009-
-- License : BSD3
--
-- Maintainer: Sigbjorn Finne <sof@forkIO.com>
-- Stability : provisional
-- Description: Module for an abstraction layer between different kinds of feeds.
--
--------------------------------------------------------------------
module Text.Feed.Constructor
( FeedKind(..)
, newFeed -- :: FeedKind -> Feed
, feedFromRSS -- :: RSS -> Feed
, feedFromAtom -- :: Atom.Feed -> Feed
, feedFromRDF -- :: RSS1.Feed -> Feed
, feedFromXML -- :: Element -> Feed
, getFeedKind -- :: Feed -> FeedKind
, FeedSetter -- type _ a = a -> Feed -> Feed
, addItem -- :: FeedSetter Item
, withFeedTitle -- :: FeedSetter Text
, withFeedHome -- :: FeedSetter URLString
, withFeedHTML -- :: FeedSetter URLString
, withFeedDescription -- :: FeedSetter Text
, withFeedPubDate -- :: FeedSetter DateString
, withFeedLastUpdate -- :: FeedSetter DateString
, withFeedDate -- :: FeedSetter DateString
, withFeedLogoLink -- :: FeedSetter URLString
, withFeedLanguage -- :: FeedSetter Text
, withFeedCategories -- :: FeedSetter [(Text, Maybe Text)]
, withFeedGenerator -- :: FeedSetter Text
, withFeedItems -- :: FeedSetter [Item]
, newItem -- :: FeedKind -> Item
, getItemKind -- :: Item -> FeedKind
, atomEntryToItem -- :: Atom.Entry -> Item
, rssItemToItem -- :: RSS.Item -> Item
, rdfItemToItem -- :: RSS1.Item -> Item
, ItemSetter -- type _ a = a -> Item -> Item
, withItemTitle -- :: ItemSetter Text
, withItemLink -- :: ItemSetter URLString
, withItemPubDate -- :: ItemSetter DateString
, withItemDate -- :: ItemSetter DateString
, withItemAuthor -- :: ItemSetter Text
, withItemCommentLink -- :: ItemSetter Text
, withItemEnclosure -- :: Text -> Maybe Text -> ItemSetter Integer
, withItemFeedLink -- :: Text -> ItemSetter Text
, withItemId -- :: Bool -> ItemSetter Text
, withItemCategories -- :: ItemSetter [(Text, Maybe Text)]
, withItemDescription -- :: ItemSetter Text
, withItemRights -- :: ItemSetter Text
) where
import Prelude.Compat
import Text.Feed.Types as Feed.Types
import Text.Atom.Feed as Atom
import Text.DublinCore.Types
import Text.RSS.Syntax as RSS
import Text.RSS1.Syntax as RSS1
import Data.XML.Compat
import Data.XML.Types as XML
import Data.Char (toLower)
import Data.Maybe (fromMaybe, mapMaybe)
import Data.Text (Text, pack)
-- ToDo:
--
-- - complete set of constructors over feeds
-- - provide a unified treatment of date string reps.
-- (i.e., I know they differ across formats, but ignorant what
-- the constraints are at the moment.)
-- | Construct an empty feed document, intending to output it in
-- the 'fk' feed format.
newFeed :: FeedKind -> Feed.Types.Feed
newFeed fk =
case fk of
AtomKind ->
AtomFeed
(Atom.nullFeed
"feed-id-not-filled-in"
(TextString "dummy-title")
"dummy-and-bogus-update-date")
RSSKind mbV ->
let def = RSS.nullRSS "dummy-title" "default-channel-url"
in RSSFeed $ maybe def (\v -> def {RSS.rssVersion = v}) mbV
RDFKind mbV ->
let def = RSS1.nullFeed "default-channel-url" "dummy-title"
in RSS1Feed $ maybe def (\v -> def {RSS1.feedVersion = v}) mbV
feedFromRSS :: RSS.RSS -> Feed.Types.Feed
feedFromRSS = RSSFeed
feedFromAtom :: Atom.Feed -> Feed.Types.Feed
feedFromAtom = AtomFeed
feedFromRDF :: RSS1.Feed -> Feed.Types.Feed
feedFromRDF = RSS1Feed
feedFromXML :: XML.Element -> Feed.Types.Feed
feedFromXML = XMLFeed
getFeedKind :: Feed.Types.Feed -> FeedKind
getFeedKind f =
case f of
Feed.Types.AtomFeed {} -> AtomKind
Feed.Types.RSSFeed r ->
RSSKind
(case RSS.rssVersion r of
"2.0" -> Nothing
v -> Just v)
Feed.Types.RSS1Feed r ->
RDFKind
(case RSS1.feedVersion r of
"1.0" -> Nothing
v -> Just v)
Feed.Types.XMLFeed {} -> RSSKind (Just "2.0") -- for now, just a hunch..
addItem :: Feed.Types.Item -> Feed.Types.Feed -> Feed.Types.Feed
addItem it f =
case (it, f) of
(Feed.Types.AtomItem e, Feed.Types.AtomFeed fe) ->
Feed.Types.AtomFeed fe {Atom.feedEntries = e : Atom.feedEntries fe}
(Feed.Types.RSSItem e, Feed.Types.RSSFeed r) ->
Feed.Types.RSSFeed
r {RSS.rssChannel = (RSS.rssChannel r) {RSS.rssItems = e : RSS.rssItems (RSS.rssChannel r)}}
(Feed.Types.RSS1Item e, Feed.Types.RSS1Feed r)
-- note: do not update the channel item URIs at this point;
-- will delay doing so until serialization.
-> Feed.Types.RSS1Feed r {RSS1.feedItems = e : RSS1.feedItems r}
_ ->
error "addItem: currently unable to automatically convert items from one feed type to another"
withFeedItems :: FeedSetter [Feed.Types.Item]
withFeedItems is fe =
foldr
addItem
(case fe of
Feed.Types.AtomFeed f -> Feed.Types.AtomFeed f {Atom.feedEntries = []}
Feed.Types.RSSFeed f -> Feed.Types.RSSFeed f {rssChannel = (rssChannel f) {rssItems = []}}
Feed.Types.RSS1Feed f -> Feed.Types.RSS1Feed f {feedItems = []})
is
newItem :: FeedKind -> Feed.Types.Item
newItem fk =
case fk of
AtomKind ->
Feed.Types.AtomItem $
Atom.nullEntry
"entry-id-not-filled-in"
(TextString "dummy-entry-title")
"dummy-and-bogus-entry-update-date"
RSSKind {} -> Feed.Types.RSSItem $ RSS.nullItem "dummy-rss-item-title"
RDFKind {} ->
Feed.Types.RSS1Item $ RSS1.nullItem "dummy-item-uri" "dummy-item-title" "dummy-item-link"
getItemKind :: Feed.Types.Item -> FeedKind
getItemKind f =
case f of
Feed.Types.AtomItem {} -> AtomKind
Feed.Types.RSSItem {} -> RSSKind (Just "2.0") -- good guess..
Feed.Types.RSS1Item {} -> RDFKind (Just "1.0")
Feed.Types.XMLItem {} -> RSSKind (Just "2.0")
type FeedSetter a = a -> Feed.Types.Feed -> Feed.Types.Feed
withFeedTitle :: FeedSetter Text
withFeedTitle tit fe =
case fe of
Feed.Types.AtomFeed f -> Feed.Types.AtomFeed f {feedTitle = TextString tit}
Feed.Types.RSSFeed f -> Feed.Types.RSSFeed f {rssChannel = (rssChannel f) {rssTitle = tit}}
Feed.Types.RSS1Feed f ->
Feed.Types.RSS1Feed f {feedChannel = (feedChannel f) {channelTitle = tit}}
Feed.Types.XMLFeed f ->
Feed.Types.XMLFeed $
mapMaybeChildren
(\e ->
if elementName e == "channel"
then Just
(mapMaybeChildren
(\e2 ->
if elementName e2 == "title"
then Just (unode "title" tit)
else Nothing)
e)
else Nothing)
f
withFeedHome :: FeedSetter URLString
withFeedHome url fe =
case fe of
Feed.Types.AtomFeed f -> Feed.Types.AtomFeed f {feedLinks = newSelf : Atom.feedLinks f}
-- ToDo: fix, the <link> element is for the HTML home of the channel, not the
-- location of the feed itself. Struggling to find if there is a common way
-- to represent this outside of RSS 2.0 standard elements..
Feed.Types.RSSFeed f -> Feed.Types.RSSFeed f {rssChannel = (rssChannel f) {rssLink = url}}
Feed.Types.RSS1Feed f ->
Feed.Types.RSS1Feed f {feedChannel = (feedChannel f) {channelURI = url}}
Feed.Types.XMLFeed f ->
Feed.Types.XMLFeed $
mapMaybeChildren
(\e ->
if elementName e == "channel"
then Just
(mapMaybeChildren
(\e2 ->
if elementName e2 == "link"
then Just (unode "link" url)
else Nothing)
e)
else Nothing)
f
where
newSelf = (nullLink url) {linkRel = Just (Left "self"), linkType = Just "application/atom+xml"}
-- | 'withFeedHTML' sets the URL where an HTML version of the
-- feed is published.
withFeedHTML :: FeedSetter URLString
withFeedHTML url fe =
case fe of
Feed.Types.AtomFeed f -> Feed.Types.AtomFeed f {feedLinks = newAlt : Atom.feedLinks f}
Feed.Types.RSSFeed f -> Feed.Types.RSSFeed f {rssChannel = (rssChannel f) {rssLink = url}}
Feed.Types.RSS1Feed f ->
Feed.Types.RSS1Feed f {feedChannel = (feedChannel f) {channelLink = url}}
Feed.Types.XMLFeed f ->
Feed.Types.XMLFeed $
mapMaybeChildren
(\e ->
if elementName e == "channel"
then Just
(mapMaybeChildren
(\e2 ->
if elementName e2 == "link"
then Just (unode "link" url)
else Nothing)
e)
else Nothing)
f
where
newAlt = (nullLink url) {linkRel = Just (Left "alternate"), linkType = Just "text/html"}
-- | 'withFeedHTML' sets the URL where an HTML version of the
-- feed is published.
withFeedDescription :: FeedSetter Text
withFeedDescription desc fe =
case fe of
Feed.Types.AtomFeed f -> Feed.Types.AtomFeed f {feedSubtitle = Just (TextString desc)}
Feed.Types.RSSFeed f ->
Feed.Types.RSSFeed f {rssChannel = (rssChannel f) {rssDescription = desc}}
Feed.Types.RSS1Feed f ->
Feed.Types.RSS1Feed f {feedChannel = (feedChannel f) {channelDesc = desc}}
Feed.Types.XMLFeed f ->
Feed.Types.XMLFeed $
mapMaybeChildren
(\e ->
if elementName e == "channel"
then Just
(mapMaybeChildren
(\e2 ->
if elementName e2 == "description"
then Just (unode "description" desc)
else Nothing)
e)
else Nothing)
f
withFeedPubDate :: FeedSetter Text
withFeedPubDate dateStr fe =
case fe of
Feed.Types.AtomFeed f -> Feed.Types.AtomFeed f {feedUpdated = dateStr}
Feed.Types.RSSFeed f ->
Feed.Types.RSSFeed f {rssChannel = (rssChannel f) {rssPubDate = Just dateStr}}
Feed.Types.RSS1Feed f ->
Feed.Types.RSS1Feed $
case break isDate $ RSS1.channelDC (RSS1.feedChannel f) of
(as, dci:bs) ->
f
{ RSS1.feedChannel =
(RSS1.feedChannel f) {RSS1.channelDC = as ++ dci {dcText = dateStr} : bs}
}
(_, []) ->
f
{ RSS1.feedChannel =
(RSS1.feedChannel f)
{ RSS1.channelDC =
DCItem {dcElt = DC_Date, dcText = dateStr} :
RSS1.channelDC (RSS1.feedChannel f)
}
}
Feed.Types.XMLFeed f ->
Feed.Types.XMLFeed $
mapMaybeChildren
(\e ->
if elementName e == "channel"
then Just
(mapMaybeChildren
(\e2 ->
if elementName e2 == "pubDate"
then Just (unode "pubDate" dateStr)
else Nothing)
e)
else Nothing)
f
where
isDate dc = dcElt dc == DC_Date
withFeedLastUpdate :: FeedSetter DateString
withFeedLastUpdate dateStr fe =
case fe of
Feed.Types.AtomFeed f -> Feed.Types.AtomFeed f {feedUpdated = dateStr}
Feed.Types.RSSFeed f ->
Feed.Types.RSSFeed f {rssChannel = (rssChannel f) {rssLastUpdate = Just dateStr}}
Feed.Types.RSS1Feed f ->
Feed.Types.RSS1Feed $
case break isDate $ RSS1.channelDC (RSS1.feedChannel f) of
(as, dci:bs) ->
f
{ RSS1.feedChannel =
(RSS1.feedChannel f) {RSS1.channelDC = as ++ dci {dcText = dateStr} : bs}
}
(_, []) ->
f
{ RSS1.feedChannel =
(RSS1.feedChannel f)
{ RSS1.channelDC =
DCItem {dcElt = DC_Date, dcText = dateStr} :
RSS1.channelDC (RSS1.feedChannel f)
}
}
Feed.Types.XMLFeed f ->
Feed.Types.XMLFeed $
mapMaybeChildren
(\e ->
if elementName e == "channel"
then Just
(mapMaybeChildren
(\e2 ->
if elementName e2 == "lastUpdate"
then Just (unode "lastUpdate" dateStr)
else Nothing)
e)
else Nothing)
f
where
isDate dc = dcElt dc == DC_Date
-- | 'withFeedDate dt' is the composition of 'withFeedPubDate'
-- and 'withFeedLastUpdate', setting both publication date and
-- last update date to 'dt'. Notice that RSS2.0 is the only format
-- supporting both pub and last-update.
withFeedDate :: FeedSetter DateString
withFeedDate dt f = withFeedPubDate dt (withFeedLastUpdate dt f)
withFeedLogoLink :: URLString -> FeedSetter URLString
withFeedLogoLink imgURL lnk fe =
case fe of
Feed.Types.AtomFeed f ->
Feed.Types.AtomFeed f {feedLogo = Just imgURL, feedLinks = newSelf : Atom.feedLinks f}
Feed.Types.RSSFeed f ->
Feed.Types.RSSFeed
f
{ rssChannel =
(rssChannel f) {rssImage = Just $ RSS.nullImage imgURL (rssTitle (rssChannel f)) lnk}
}
Feed.Types.RSS1Feed f ->
Feed.Types.RSS1Feed $
f
{ feedImage = Just $ RSS1.nullImage imgURL (RSS1.channelTitle (RSS1.feedChannel f)) lnk
, feedChannel = (feedChannel f) {channelImageURI = Just imgURL}
}
Feed.Types.XMLFeed f ->
Feed.Types.XMLFeed $
mapMaybeChildren
(\e ->
if elementName e == "channel"
then Just
(mapMaybeChildren
(\e2 ->
if elementName e2 == "image"
then Just
(unode
"image"
[unode "url" imgURL, unode "title" title, unode "link" lnk])
else Nothing)
e)
else Nothing)
f
where title =
case fmap (findChild "title") (findChild "channel" f) of
Just (Just e1) -> strContent e1
_ -> "feed_title" -- shouldn't happen..
where
newSelf = (nullLink lnk) {linkRel = Just (Left "self"), linkType = Just "application/atom+xml"}
withFeedLanguage :: FeedSetter Text
withFeedLanguage lang fe =
case fe of
Feed.Types.AtomFeed f -> Feed.Types.AtomFeed f {Atom.feedAttrs = langAttr : Atom.feedAttrs f}
where langAttr = (name, [ContentText lang])
name = Name {nameLocalName = "lang", nameNamespace = Nothing, namePrefix = Just "xml"}
Feed.Types.RSSFeed f ->
Feed.Types.RSSFeed f {rssChannel = (rssChannel f) {rssLanguage = Just lang}}
Feed.Types.RSS1Feed f ->
Feed.Types.RSS1Feed $
case break isLang $ RSS1.channelDC (RSS1.feedChannel f) of
(as, dci:bs) ->
f
{ RSS1.feedChannel =
(RSS1.feedChannel f) {RSS1.channelDC = as ++ dci {dcText = lang} : bs}
}
(_, []) ->
f
{ RSS1.feedChannel =
(RSS1.feedChannel f)
{ RSS1.channelDC =
DCItem {dcElt = DC_Language, dcText = lang} :
RSS1.channelDC (RSS1.feedChannel f)
}
}
Feed.Types.XMLFeed f ->
Feed.Types.XMLFeed $
mapMaybeChildren
(\e ->
if elementName e == "channel"
then Just
(mapMaybeChildren
(\e2 ->
if elementName e2 == "language"
then Just (unode "language" lang)
else Nothing)
e)
else Nothing)
f
where
isLang dc = dcElt dc == DC_Language
withFeedCategories :: FeedSetter [(Text, Maybe Text)]
withFeedCategories cats fe =
case fe of
Feed.Types.AtomFeed f ->
Feed.Types.AtomFeed
f
{ Atom.feedCategories =
map (\(t, mb) -> (Atom.newCategory t) {Atom.catScheme = mb}) cats ++ feedCategories f
}
Feed.Types.RSSFeed f ->
Feed.Types.RSSFeed
f
{ rssChannel =
(rssChannel f)
{ RSS.rssCategories =
map (\(t, mb) -> (RSS.newCategory t) {RSS.rssCategoryDomain = mb}) cats ++
RSS.rssCategories (rssChannel f)
}
}
Feed.Types.RSS1Feed f ->
Feed.Types.RSS1Feed
f
{ feedChannel =
(feedChannel f)
{ RSS1.channelDC =
map (\(t, _) -> DCItem {dcElt = DC_Subject, dcText = t}) cats ++
RSS1.channelDC (feedChannel f)
}
}
Feed.Types.XMLFeed f ->
Feed.Types.XMLFeed $
mapMaybeChildren
(\e ->
if elementName e == "channel"
then Just
(foldr
(\(t, mb) acc ->
addChild
(unode
"category"
(maybe (: []) (\v x -> [mkAttr "domain" v, x]) mb (mkAttr "term" t)))
acc)
e
cats)
else Nothing)
f
withFeedGenerator :: FeedSetter (Text, Maybe URLString)
withFeedGenerator (gen, mbURI) fe =
case fe of
Feed.Types.AtomFeed f ->
Feed.Types.AtomFeed $
f {Atom.feedGenerator = Just ((Atom.nullGenerator gen) {Atom.genURI = mbURI})}
Feed.Types.RSSFeed f ->
Feed.Types.RSSFeed f {rssChannel = (rssChannel f) {rssGenerator = Just gen}}
Feed.Types.RSS1Feed f ->
Feed.Types.RSS1Feed $
case break isSource $ RSS1.channelDC (RSS1.feedChannel f) of
(as, dci:bs) ->
f
{ RSS1.feedChannel =
(RSS1.feedChannel f) {RSS1.channelDC = as ++ dci {dcText = gen} : bs}
}
(_, []) ->
f
{ RSS1.feedChannel =
(RSS1.feedChannel f)
{ RSS1.channelDC =
DCItem {dcElt = DC_Source, dcText = gen} : RSS1.channelDC (RSS1.feedChannel f)
}
}
Feed.Types.XMLFeed f ->
Feed.Types.XMLFeed $
mapMaybeChildren
(\e ->
if elementName e == "channel"
then Just
(mapMaybeChildren
(\e2 ->
if elementName e2 == "generator"
then Just (unode "generator" gen)
else Nothing)
e)
else Nothing)
f
where
isSource dc = dcElt dc == DC_Source
-- Item constructors (all the way to the end):
atomEntryToItem :: Atom.Entry -> Feed.Types.Item
atomEntryToItem = Feed.Types.AtomItem
rssItemToItem :: RSS.RSSItem -> Feed.Types.Item
rssItemToItem = Feed.Types.RSSItem
rdfItemToItem :: RSS1.Item -> Feed.Types.Item
rdfItemToItem = Feed.Types.RSS1Item
type ItemSetter a = a -> Feed.Types.Item -> Feed.Types.Item
-- | 'withItemPubDate dt' associates the creation\/ publication date 'dt'
-- with a feed item.
withItemPubDate :: ItemSetter DateString
withItemPubDate dt fi =
case fi of
Feed.Types.AtomItem e -> Feed.Types.AtomItem e {Atom.entryUpdated = dt}
Feed.Types.RSSItem i -> Feed.Types.RSSItem i {RSS.rssItemPubDate = Just dt}
Feed.Types.RSS1Item i ->
case break isDate $ RSS1.itemDC i of
(as, dci:bs) -> Feed.Types.RSS1Item i {RSS1.itemDC = as ++ dci {dcText = dt} : bs}
(_, []) ->
Feed.Types.RSS1Item
i {RSS1.itemDC = DCItem {dcElt = DC_Date, dcText = dt} : RSS1.itemDC i}
Feed.Types.XMLItem i ->
Feed.Types.XMLItem $
addChild (unode "pubDate" dt) $ filterChildren (\e -> elementName e /= "pubDate") i
where
isDate dc = dcElt dc == DC_Date
-- | 'withItemDate' is a synonym for 'withItemPubDate'.
withItemDate :: ItemSetter DateString
withItemDate = withItemPubDate
-- | 'withItemTitle myTitle' associates a new title, 'myTitle',
-- with a feed item.
withItemTitle :: ItemSetter Text
withItemTitle tit fi =
case fi of
Feed.Types.AtomItem e -> Feed.Types.AtomItem e {Atom.entryTitle = TextString tit}
Feed.Types.RSSItem i -> Feed.Types.RSSItem i {RSS.rssItemTitle = Just tit}
Feed.Types.RSS1Item i -> Feed.Types.RSS1Item i {RSS1.itemTitle = tit}
Feed.Types.XMLItem i ->
Feed.Types.XMLItem $
addChild (unode "title" tit) $ filterChildren (\e -> elementName e /= "title") i
-- | 'withItemAuthor auStr' associates new author info
-- with a feed item.
withItemAuthor :: ItemSetter Text
withItemAuthor au fi =
case fi of
Feed.Types.AtomItem e ->
Feed.Types.AtomItem
e {Atom.entryAuthors = [nullPerson {personName = au, personURI = Just au}]}
Feed.Types.RSSItem i -> Feed.Types.RSSItem i {RSS.rssItemAuthor = Just au}
Feed.Types.RSS1Item i ->
case break isAuthor $ RSS1.itemDC i of
(as, dci:bs) -> Feed.Types.RSS1Item i {RSS1.itemDC = as ++ dci {dcText = au} : bs}
(_, []) ->
Feed.Types.RSS1Item
i {RSS1.itemDC = DCItem {dcElt = DC_Creator, dcText = au} : RSS1.itemDC i}
Feed.Types.XMLItem i ->
Feed.Types.XMLItem $
addChild (unode "author" au) $ filterChildren (\e -> elementName e /= "author") i
where
isAuthor dc = dcElt dc == DC_Creator
-- | 'withItemFeedLink name myFeed' associates the parent feed URL 'myFeed'
-- with a feed item. It is labelled as 'name'.
withItemFeedLink :: Text -> ItemSetter Text
withItemFeedLink tit url fi =
case fi of
Feed.Types.AtomItem e ->
Feed.Types.AtomItem
e
{ Atom.entrySource =
Just Atom.nullSource {sourceId = Just url, sourceTitle = Just (TextString tit)}
}
Feed.Types.RSSItem i -> Feed.Types.RSSItem i {RSS.rssItemSource = Just (RSS.nullSource url tit)}
Feed.Types.RSS1Item i -> Feed.Types.RSS1Item i {RSS1.itemTitle = tit}
Feed.Types.XMLItem i ->
Feed.Types.XMLItem $
addChild (unode "source" ([mkAttr "url" url], tit)) $
filterChildren (\e -> elementName e /= "source") i
-- | 'withItemCommentLink url' sets the URL reference to the comment page to 'url'.
withItemCommentLink :: ItemSetter Text
withItemCommentLink url fi =
case fi of
Feed.Types.AtomItem e ->
Feed.Types.AtomItem
e {Atom.entryLinks = ((nullLink url) {linkRel = Just (Left "replies")}) : Atom.entryLinks e}
Feed.Types.RSSItem i -> Feed.Types.RSSItem i {RSS.rssItemComments = Just url}
Feed.Types.RSS1Item i ->
case break isRel $ RSS1.itemDC i of
(as, dci:bs) -> Feed.Types.RSS1Item i {RSS1.itemDC = as ++ dci {dcText = url} : bs}
(_, []) ->
Feed.Types.RSS1Item
i {RSS1.itemDC = DCItem {dcElt = DC_Relation, dcText = url} : RSS1.itemDC i}
Feed.Types.XMLItem i ->
Feed.Types.XMLItem $
addChild (unode "comments" url) $ filterChildren (\e -> elementName e /= "comments") i
where
isRel dc = dcElt dc == DC_Relation
-- | 'withItemEnclosure url mbTy len' sets the URL reference to the comment page to 'url'.
withItemEnclosure :: Text -> Maybe Text -> ItemSetter (Maybe Integer)
withItemEnclosure url ty mb_len fi =
case fi of
Feed.Types.AtomItem e ->
Feed.Types.AtomItem
e
{ Atom.entryLinks =
((nullLink url)
{ linkRel = Just (Left "enclosure")
, linkType = ty
, linkLength = fmap (pack . show) mb_len
}) :
Atom.entryLinks e
}
Feed.Types.RSSItem i ->
Feed.Types.RSSItem
i {RSS.rssItemEnclosure = Just (nullEnclosure url mb_len (fromMaybe "text/html" ty))}
Feed.Types.RSS1Item i ->
Feed.Types.RSS1Item
i
{ RSS1.itemContent =
nullContentInfo {contentURI = Just url, contentFormat = ty} : RSS1.itemContent i
}
Feed.Types.XMLItem i ->
Feed.Types.XMLItem $
addChild
((unode "enclosure" url)
{elementAttributes = [mkAttr "length" "0", mkAttr "type" (fromMaybe "text/html" ty)]}) $
filterChildren (\e -> elementName e /= "enclosure") i
-- | 'withItemId isURL id' associates new unique identifier with a feed item.
-- If 'isURL' is 'True', then the id is assumed to point to a valid web resource.
withItemId :: Bool -> ItemSetter Text
withItemId isURL idS fi =
case fi of
Feed.Types.AtomItem e -> Feed.Types.AtomItem e {Atom.entryId = idS}
Feed.Types.RSSItem i ->
Feed.Types.RSSItem
i {RSS.rssItemGuid = Just (nullGuid idS) {rssGuidPermanentURL = Just isURL}}
Feed.Types.RSS1Item i ->
case break isId $ RSS1.itemDC i of
(as, dci:bs) -> Feed.Types.RSS1Item i {RSS1.itemDC = as ++ dci {dcText = idS} : bs}
(_, []) ->
Feed.Types.RSS1Item
i {RSS1.itemDC = DCItem {dcElt = DC_Identifier, dcText = idS} : RSS1.itemDC i}
Feed.Types.XMLItem i ->
Feed.Types.XMLItem $
addChild (unode "guid" ([mkAttr "isPermaLink" (showBool isURL)], idS)) $
filterChildren (\e -> elementName e /= "guid") i
where
showBool x = pack $ map toLower (show x)
isId dc = dcElt dc == DC_Identifier
-- | 'withItemDescription desc' associates a new descriptive string (aka summary)
-- with a feed item.
withItemDescription :: ItemSetter Text
withItemDescription desc fi =
case fi of
Feed.Types.AtomItem e -> Feed.Types.AtomItem e {Atom.entrySummary = Just (TextString desc)}
Feed.Types.RSSItem i -> Feed.Types.RSSItem i {RSS.rssItemDescription = Just desc}
Feed.Types.RSS1Item i -> Feed.Types.RSS1Item i {RSS1.itemDesc = Just desc}
Feed.Types.XMLItem i ->
Feed.Types.XMLItem $
addChild (unode "description" desc) $ filterChildren (\e -> elementName e /= "description") i
-- | 'withItemRights rightStr' associates the rights information 'rightStr'
-- with a feed item.
withItemRights :: ItemSetter Text
withItemRights desc fi =
case fi of
Feed.Types.AtomItem e -> Feed.Types.AtomItem e {Atom.entryRights = Just (TextString desc)}
-- Note: per-item copyright information isn't supported by RSS2.0 (and earlier editions),
-- you can only attach this at the feed/channel level. So, there's not much we can do
-- except dropping the information on the floor here. (Rolling our own attribute or
-- extension element is an option, but would prefer if someone else had started that
-- effort already.
Feed.Types.RSSItem {} -> fi
Feed.Types.RSS1Item i ->
case break ((== DC_Rights) . dcElt) $ RSS1.itemDC i of
(as, dci:bs) -> Feed.Types.RSS1Item i {RSS1.itemDC = as ++ dci {dcText = desc} : bs}
(_, []) ->
Feed.Types.RSS1Item
i {RSS1.itemDC = DCItem {dcElt = DC_Rights, dcText = desc} : RSS1.itemDC i}
-- Since we're so far assuming that a shallow XML rep. of an item
-- is of RSS2.0 ilk, pinning on the rights info is hard (see above.)
Feed.Types.XMLItem {} -> fi
-- | 'withItemTitle myLink' associates a new URL, 'myLink',
-- with a feed item.
withItemLink :: ItemSetter URLString
withItemLink url fi =
case fi of
Feed.Types.AtomItem e ->
Feed.Types.AtomItem e {Atom.entryLinks = replaceAlternate url (Atom.entryLinks e)}
Feed.Types.RSSItem i -> Feed.Types.RSSItem i {RSS.rssItemLink = Just url}
Feed.Types.RSS1Item i -> Feed.Types.RSS1Item i {RSS1.itemLink = url}
Feed.Types.XMLItem i ->
Feed.Types.XMLItem $
addChild (unode "link" url) $ filterChildren (\e -> elementName e /= "link") i
where
replaceAlternate _ [] = []
replaceAlternate x (lr:xs)
| toStr (Atom.linkRel lr) == "alternate" = lr {Atom.linkHref = x} : xs
| otherwise = lr : replaceAlternate x xs
toStr Nothing = ""
toStr (Just (Left x)) = x
toStr (Just (Right x)) = x
withItemCategories :: ItemSetter [(Text, Maybe Text)]
withItemCategories cats fi =
case fi of
Feed.Types.AtomItem e ->
Feed.Types.AtomItem
e
{ Atom.entryCategories =
map (\(t, mb) -> (Atom.newCategory t) {Atom.catScheme = mb}) cats ++ entryCategories e
}
Feed.Types.RSSItem i ->
Feed.Types.RSSItem
i
{ RSS.rssItemCategories =
map (\(t, mb) -> (RSS.newCategory t) {RSS.rssCategoryDomain = mb}) cats ++
rssItemCategories i
}
Feed.Types.RSS1Item i ->
Feed.Types.RSS1Item
i
{ RSS1.itemDC =
map (\(t, _) -> DCItem {dcElt = DC_Subject, dcText = t}) cats ++ RSS1.itemDC i
}
Feed.Types.XMLItem i ->
Feed.Types.XMLItem $
foldr
(\(t, mb) acc ->
addChild
(unode "category" (maybe (: []) (\v x -> [mkAttr "domain" v, x]) mb (mkAttr "term" t)))
acc)
i
cats
-- helpers..
filterChildren :: (XML.Element -> Bool) -> XML.Element -> XML.Element
filterChildren pre e =
case elementNodes e of
[] -> e
cs -> e {elementNodes = mapMaybe filterElt cs}
where
filterElt xe@(XML.NodeElement el)
| pre el = Just xe
| otherwise = Nothing
filterElt xe = Just xe
addChild :: XML.Element -> XML.Element -> XML.Element
addChild a b = b {elementNodes = XML.NodeElement a : elementNodes b}
mapMaybeChildren :: (XML.Element -> Maybe XML.Element) -> XML.Element -> XML.Element
mapMaybeChildren f e =
case elementNodes e of
[] -> e
cs -> e {elementNodes = map procElt cs}
where
procElt xe@(XML.NodeElement el) =
case f el of
Nothing -> xe
Just el1 -> XML.NodeElement el1
procElt xe = xe