packages feed

fmark-0.1.1: src/Backend/Xml.hs

-- | 'Backend.Xml' is the XML generator backend for 'Document's.
module Backend.Xml where

import Control.Monad.State (State, evalState, get, modify)
import Data.Functor ((<$>))
import Data.List (intercalate)

import Data.Document
import Data.Text


-- | 'XmlState' is the XML generator state that records the current
-- indentation level and a prefix values that decides whether XML tags
-- should be indented.
data XmlState = XmlState Int Bool

-- | 'XmlM' is the type of the XML generator 'Monad'.
type XmlM a = State XmlState a


-- | 'getIdn' is a 'Monad' with the current indentation level.
getIdn :: XmlM Int
getIdn =
    do XmlState idn _ <- get
       return idn


-- | 'putIdn' @idn@ is a 'Monad' that modifies the current indentation
-- level.
putIdn :: Int -> XmlM ()
putIdn idn =
    modify $ \(XmlState _ pre) -> XmlState idn pre


-- | 'withIdn' @m@ is a 'Monad' that temporarily creates a deeper
-- indentation level for 'Monad' @m@.
withIdn :: XmlM a -> XmlM a
withIdn m =
    do idn <- getIdn
       putIdn $ idn + 2
       val <- m
       putIdn idn
       return val


-- | 'getPrefix' is a 'Monad' with the current prefix.
getPrefix :: XmlM Bool
getPrefix =
    do XmlState _ pre <- get
       return pre


-- | 'putPrefix' @pre@ is a 'Monad' that modified the current prefix.
putPrefix :: Bool -> XmlM ()
putPrefix pre =
    modify $ \(XmlState idn _) -> XmlState idn pre


-- | 'withPrefix' @pre m@ is a 'Monad' that modified the current
-- prefix for 'Monad' @m@.
withPrefix :: Bool -> XmlM a -> XmlM a
withPrefix pre m =
    do pre' <- getPrefix
       putPrefix pre
       val <- m
       putPrefix pre'
       return val


-- | 'docToXml' @mstyle doc@ formats a styled 'Document' @doc@ into a
-- XML 'String', where @mstyle@ specifies the style 'Document' used to
-- stylize @doc@.
docToXml :: Maybe Document -> Document -> String
docToXml _ doc =
    intercalate "\n" ["<xml>", str, "</xml>"]
    where str = evalState (loop doc) (XmlState 2 True)

          xmlIndent :: XmlM String -> XmlM String
          xmlIndent m =
              do idn <- getIdn
                 pre <- getPrefix
                 if pre then
                     do str <- m
                        return $ replicate idn ' ' ++ str
                 else
                     m

          xmlAttribute Nothing = []
          xmlAttribute (Just val) = [("style", val)]

          xmlAttributes [] = ""
          xmlAttributes attrs = ' ':unwords (map (\(id, val) -> id ++ "=\"" ++ val ++ "\"") attrs)

          xmlStr = xmlIndent

          xmlShortTag :: [(String, String)] -> String -> XmlM String -> XmlM String
          xmlShortTag attrs tag m =
              concat <$> sequence [xmlIndent (return ("<" ++ tag ++ xmlAttributes attrs ++ ">")),
                                   withPrefix False m,
                                   return ("</" ++ tag ++ ">")]

          xmlLongTag attrs tag m =
              do pre <- getPrefix
                 if pre then
                     concat <$> sequence [xmlIndent (return ("<" ++ tag ++ xmlAttributes attrs ++ ">\n")),
                                          withIdn m,
                                          return "\n",
                                          xmlIndent (return ("</" ++ tag ++ ">"))]
                 else
                     xmlShortTag attrs tag m

          xmlLongTags :: [(String, String)] -> String -> [XmlM String] -> XmlM String
          xmlLongTags attrs tag ms =
              do pre <- getPrefix
                 if pre then
                     xmlLongTag attrs tag $ intercalate "\n" <$> sequence ms
                 else
                     xmlLongTag attrs tag $ concat <$> sequence ms

          loopText :: Text -> XmlM String
          loopText (Footnote str) = withPrefix False $ xmlShortTag [] "footnote" $ return str
          loopText (Plain str) = xmlStr $ return str

          loop :: Document -> XmlM String
          loop (Heading _ [txts]) = xmlShortTag [] "heading" $ concat <$> mapM loopText txts
          loop (Heading _ lns) = xmlLongTags [] "heading" [ concat <$> mapM loopText txts | txts <- lns ]
          loop (Paragraph _ txts) = xmlShortTag [] "paragraph" $ concat <$> mapM loopText txts
          loop (Content [doc]) = xmlShortTag [] "content" $ loop doc
          loop (Content docs) = xmlLongTags [] "content" $ map loop docs
          loop (Section doc) = xmlLongTag [] "section" $ loop doc
          loop (Style _ sty txt) = xmlShortTag [] sty $ loopText txt