packages feed

hakyll-convert-0.3.0.1: src/Hakyll/Convert/Wordpress.hs

{-# LANGUAGE OverloadedStrings #-}

module Hakyll.Convert.Wordpress (readPosts, distill) where

import Control.Monad
import Data.Maybe
import qualified Data.Text as T
import Data.Time.Format (defaultTimeLocale, parseTimeM, rfc822DateFormat)
import Data.XML.Types (Element (..), Name (..), elementChildren, elementText)
import Hakyll.Convert.Common
import Text.RSS.Import
import Text.RSS.Syntax
import qualified Text.XML as XML

-- | Returns only public posts
readPosts :: FilePath -> IO (Maybe [RSSItem])
readPosts f = do
  doc <- XML.readFile (XML.def :: XML.ParseSettings) f
  let root = XML.toXMLElement $ XML.documentRoot doc
  return $ fmap select (elementToRSS root)
  where
    select = filter isPublished . rssItems . rssChannel

isPublished :: RSSItem -> Bool
isPublished i = "publish" `elem` getStatus i

distill :: Bool -> RSSItem -> DistilledPost
distill extractComments item =
  DistilledPost
    { dpTitle = rssItemTitle item,
      dpBody = body,
      dpUri = link,
      dpTags = tags,
      dpCategories = categories,
      dpDate = date
    }
  where
    body =
      if extractComments
        then
          T.intercalate
            "\n"
            [ content,
              "",
              "<h3 id='hakyll-convert-comments-title'>Comments</h3>",
              comments
            ]
        else content
    link = fromMaybe "" (rssItemLink item)
    content = T.unlines (map strContent contentTags)
    categories = rssCategoriesOfType "category"
    tags = rssCategoriesOfType "post_tag"
    contentTags = concatMap (findElements contentTag) (rssItemOther item)
    rssCategoriesOfType ty =
      [ rssCategoryValue c
        | c <- rssItemCategories item,
          rssCategoryDomain c == Just ty
      ]
    contentTag =
      Name
        { nameLocalName = "encoded",
          nameNamespace = Just "http://purl.org/rss/1.0/modules/content/",
          namePrefix = Just "content"
        }
    comments = T.intercalate "\n" $ map formatComment commentTags
    commentTags = rssItemOther item >>= findElements commentTag
    commentTag = wordpressTag "comment"
    --
    date = case parseTime' =<< rssItemPubDate item of
      Nothing -> fromJust $ parseTime' "Thu, 01 Jan 1970 00:00:00 UTC"
      Just d -> d
    parseTime' d =
      msum $
        map
          (\f -> parseTimeM True defaultTimeLocale f (T.unpack d))
          [ rfc822DateFormat
          ]

-- ---------------------------------------------------------------------
-- helpers
-- ---------------------------------------------------------------------

formatComment :: Element -> T.Text
formatComment commentElement =
  T.intercalate
    "\n"
    [ "<div class='hakyll-convert-comment'>",
      T.concat
        [ "<p class='hakyll-convert-comment-date'>",
          "On ",
          pubdate,
          ", ",
          author,
          " wrote:",
          "</p>"
        ],
      "<div class='hakyll-convert-comment-body'>",
      comment,
      "</div>",
      "</div>"
    ]
  where
    pubdate = fromMaybe "unknown date" $ findField "comment_date"
    author = fromMaybe "unknown author" $ findField "comment_author"
    comment = fromMaybe "" $ findField "comment_content"
    findField name =
      strContent <$> findChild (wordpressTag name) commentElement

wordpressTag :: T.Text -> Name
wordpressTag name =
  Name
    { nameLocalName = name,
      nameNamespace = Just "http://wordpress.org/export/1.2/",
      namePrefix = Just "wp"
    }

getStatus :: RSSItem -> [T.Text]
getStatus item =
  map strContent statusTags
  where
    statusTags = concatMap (findElements (wordpressTag "status")) (rssItemOther item)

-- | Find all non-nested elements which are named `name`, starting with `root`.
-- ("Non-nested" means we don't search sub-elements of an element that's named
-- `name`.)
findElements :: Name -> Element -> [Element]
findElements name element =
  if elementName element == name
    then [element]
    else concatMap (findElements name) (elementChildren element)

-- | Find first immediate child of `root` which is named `name`.
findChild :: Name -> Element -> Maybe Element
findChild name element =
  let subelements = elementChildren element
      matching = filter (\child -> elementName child == name) subelements
   in listToMaybe matching

-- | The contents of the element (ignoring non-text sub-elements).
strContent :: Element -> T.Text
strContent element = T.concat $ elementText element