module Text.Pandoc.Writers.ZimWiki ( writeZimWiki ) where
import Text.Pandoc.Definition
import Text.Pandoc.Options ( WriterOptions(writerTableOfContents, writerStandalone, writerTemplate, writerWrapText), WrapOption(..) )
import Text.Pandoc.Shared ( escapeURI, removeFormatting, trimr, substitute )
import Text.Pandoc.Writers.Shared ( defField, metaToJSON )
import Text.Pandoc.ImageSize
import Text.Pandoc.Templates ( renderTemplate' )
import Data.List ( intercalate, isPrefixOf, transpose, isInfixOf )
import Data.Text ( breakOnAll, pack )
import Data.Default (Default(..))
import Network.URI ( isURI )
import Control.Monad ( zipWithM )
import Control.Monad.State ( modify, State, get, evalState )
data WriterState = WriterState {
stItemNum :: Int,
stIndent :: String
}
instance Default WriterState where
def = WriterState { stItemNum = 1, stIndent = "" }
writeZimWiki :: WriterOptions -> Pandoc -> String
writeZimWiki opts document = evalState (pandocToZimWiki opts document) (WriterState 1 "")
pandocToZimWiki :: WriterOptions -> Pandoc -> State WriterState String
pandocToZimWiki opts (Pandoc meta blocks) = do
metadata <- metaToJSON opts
(fmap trimr . blockListToZimWiki opts)
(inlineListToZimWiki opts)
meta
body <- blockListToZimWiki opts blocks
let main = body
let context = defField "body" main
$ defField "toc" (writerTableOfContents opts)
$ metadata
if writerStandalone opts
then return $ renderTemplate' (writerTemplate opts) context
else return main
escapeString :: String -> String
escapeString = substitute "__" "''__''" .
substitute "**" "''**''" .
substitute "~~" "''~~''" .
substitute "//" "''//''"
blockToZimWiki :: WriterOptions -> Block -> State WriterState String
blockToZimWiki _ Null = return ""
blockToZimWiki opts (Div _attrs bs) = do
contents <- blockListToZimWiki opts bs
return $ contents ++ "\n"
blockToZimWiki opts (Plain inlines) = inlineListToZimWiki opts inlines
blockToZimWiki opts (Para [Image attr txt (src,'f':'i':'g':':':tit)]) = do
capt <- if null txt
then return ""
else (" " ++) `fmap` inlineListToZimWiki opts txt
let opt = if null txt
then ""
else "|" ++ if null tit then capt else tit ++ capt
prefix = if isURI src then "" else ":"
return $ "{{" ++ prefix ++ src ++ imageDims opts attr ++ opt ++ "}}\n"
blockToZimWiki opts (Para inlines) = do
indent <- stIndent <$> get
contents <- inlineListToZimWiki opts inlines
return $ contents ++ if null indent then "\n" else ""
blockToZimWiki opts (RawBlock f str)
| f == Format "zimwiki" = return str
| f == Format "html" = do cont <- indentFromHTML opts str; return cont
| otherwise = return ""
blockToZimWiki _ HorizontalRule = return "\n----\n"
blockToZimWiki opts (Header level _ inlines) = do
contents <- inlineListToZimWiki opts $ removeFormatting inlines
let eqs = replicate ( 7 level ) '='
return $ eqs ++ " " ++ contents ++ " " ++ eqs ++ "\n"
blockToZimWiki _ (CodeBlock (_,classes,_) str) = do
return $ case classes of
[] -> "'''\n" ++ cleanupCode str ++ "\n'''\n"
(x:_) -> "{{{code: lang=\"" ++ x ++ "\" linenumbers=\"True\"\n" ++ str ++ "\n}}}\n"
blockToZimWiki opts (BlockQuote blocks) = do
contents <- blockListToZimWiki opts blocks
return $ unlines $ map ("> " ++) $ lines contents
blockToZimWiki opts (Table capt aligns _ headers rows) = do
captionDoc <- if null capt
then return ""
else do
c <- inlineListToZimWiki opts capt
return $ "" ++ c ++ "\n"
headers' <- if all null headers
then zipWithM (tableItemToZimWiki opts) aligns (rows !! 0)
else zipWithM (tableItemToZimWiki opts) aligns headers
rows' <- mapM (zipWithM (tableItemToZimWiki opts) aligns) rows
let widths = map (maximum . map length) $ transpose (headers':rows')
let padTo (width, al) s =
case (width length s) of
x | x > 0 ->
if al == AlignLeft || al == AlignDefault
then s ++ replicate x ' '
else if al == AlignRight
then replicate x ' ' ++ s
else replicate (x `div` 2) ' ' ++
s ++ replicate (x x `div` 2) ' '
| otherwise -> s
let borderCell (width, al) _ =
if al == AlignLeft
then ":"++ replicate (width1) '-'
else if al == AlignDefault
then replicate width '-'
else if al == AlignRight
then replicate (width1) '-' ++ ":"
else ":" ++ replicate (width2) '-' ++ ":"
let underheader = "|" ++ intercalate "|" (zipWith borderCell (zip widths aligns) headers') ++ "|"
let renderRow sep cells = sep ++ intercalate sep (zipWith padTo (zip widths aligns) cells) ++ sep
return $ captionDoc ++
(if null headers' then "" else renderRow "|" headers' ++ "\n") ++ underheader ++ "\n" ++
unlines (map (renderRow "|") rows')
blockToZimWiki opts (BulletList items) = do
indent <- stIndent <$> get
modify $ \s -> s { stIndent = stIndent s ++ "\t" }
contents <- (mapM (listItemToZimWiki opts) items)
modify $ \s -> s{ stIndent = indent }
return $ vcat contents ++ if null indent then "\n" else ""
blockToZimWiki opts (OrderedList _ items) = do
indent <- stIndent <$> get
modify $ \s -> s { stIndent = stIndent s ++ "\t", stItemNum = 1 }
contents <- (mapM (orderedListItemToZimWiki opts) items)
modify $ \s -> s{ stIndent = indent }
return $ vcat contents ++ if null indent then "\n" else ""
blockToZimWiki opts (DefinitionList items) = do
contents <- (mapM (definitionListItemToZimWiki opts) items)
return $ vcat contents
definitionListItemToZimWiki :: WriterOptions -> ([Inline],[[Block]]) -> State WriterState String
definitionListItemToZimWiki opts (label, items) = do
labelText <- inlineListToZimWiki opts label
contents <- mapM (blockListToZimWiki opts) items
indent <- stIndent <$> get
return $ indent ++ "* **" ++ labelText ++ "** " ++ concat contents
indentFromHTML :: WriterOptions -> String -> State WriterState String
indentFromHTML _ str = do
indent <- stIndent <$> get
itemnum <- stItemNum <$> get
if isInfixOf "<li>" str then return $ indent ++ show itemnum ++ "."
else if isInfixOf "</li>" str then return "\n"
else if isInfixOf "<li value=" str then do
let val = drop 10 $ reverse $ drop 1 $ reverse str
modify $ \s -> s { stItemNum = read val }
return ""
else if isInfixOf "<ol>" str then do
let olcount=countSubStrs "<ol>" str
modify $ \s -> s { stIndent = stIndent s ++ replicate olcount '\t', stItemNum = 1 }
return ""
else if isInfixOf "</ol>" str then do
let olcount=countSubStrs "/<ol>" str
modify $ \s -> s{ stIndent = drop olcount (stIndent s) }
return ""
else
return ""
countSubStrs :: String -> String -> Int
countSubStrs sub str = length $ breakOnAll (pack sub) (pack str)
cleanupCode :: String -> String
cleanupCode = substitute "<nowiki>" "" . substitute "</nowiki>" ""
vcat :: [String] -> String
vcat = intercalate "\n"
listItemToZimWiki :: WriterOptions -> [Block] -> State WriterState String
listItemToZimWiki opts items = do
contents <- blockListToZimWiki opts items
indent <- stIndent <$> get
return $ indent ++ "* " ++ contents
orderedListItemToZimWiki :: WriterOptions -> [Block] -> State WriterState String
orderedListItemToZimWiki opts items = do
contents <- blockListToZimWiki opts items
indent <- stIndent <$> get
itemnum <- stItemNum <$> get
return $ indent ++ show itemnum ++ ". " ++ contents
tableItemToZimWiki :: WriterOptions -> Alignment -> [Block] -> State WriterState String
tableItemToZimWiki opts align' item = do
let mkcell x = (if align' == AlignRight || align' == AlignCenter
then " "
else "") ++ x ++
(if align' == AlignLeft || align' == AlignCenter
then " "
else "")
contents <- blockListToZimWiki opts item
return $ mkcell contents
blockListToZimWiki :: WriterOptions -> [Block] -> State WriterState String
blockListToZimWiki opts blocks = vcat <$> mapM (blockToZimWiki opts) blocks
inlineListToZimWiki :: WriterOptions -> [Inline] -> State WriterState String
inlineListToZimWiki opts lst = concat <$> (mapM (inlineToZimWiki opts) lst)
inlineToZimWiki :: WriterOptions -> Inline -> State WriterState String
inlineToZimWiki opts (Emph lst) = do
contents <- inlineListToZimWiki opts lst
return $ "//" ++ contents ++ "//"
inlineToZimWiki opts (Strong lst) = do
contents <- inlineListToZimWiki opts lst
return $ "**" ++ contents ++ "**"
inlineToZimWiki opts (Strikeout lst) = do
contents <- inlineListToZimWiki opts lst
return $ "~~" ++ contents ++ "~~"
inlineToZimWiki opts (Superscript lst) = do
contents <- inlineListToZimWiki opts lst
return $ "^{" ++ contents ++ "}"
inlineToZimWiki opts (Subscript lst) = do
contents <- inlineListToZimWiki opts lst
return $ "_{" ++ contents ++ "}"
inlineToZimWiki opts (Quoted SingleQuote lst) = do
contents <- inlineListToZimWiki opts lst
return $ "\8216" ++ contents ++ "\8217"
inlineToZimWiki opts (Quoted DoubleQuote lst) = do
contents <- inlineListToZimWiki opts lst
return $ "\8220" ++ contents ++ "\8221"
inlineToZimWiki opts (Span _attrs ils) = inlineListToZimWiki opts ils
inlineToZimWiki opts (SmallCaps lst) = inlineListToZimWiki opts lst
inlineToZimWiki opts (Cite _ lst) = inlineListToZimWiki opts lst
inlineToZimWiki _ (Code _ str) = return $ "''" ++ str ++ "''"
inlineToZimWiki _ (Str str) = return $ escapeString str
inlineToZimWiki _ (Math mathType str) = return $ delim ++ str ++ delim
where delim = case mathType of
DisplayMath -> "$$"
InlineMath -> "$"
inlineToZimWiki opts (RawInline f str)
| f == Format "zimwiki" = return str
| f == Format "html" = do cont <- indentFromHTML opts str; return cont
| otherwise = return ""
inlineToZimWiki _ (LineBreak) = return "\n"
inlineToZimWiki opts SoftBreak =
case writerWrapText opts of
WrapNone -> return " "
WrapAuto -> return " "
WrapPreserve -> return "\n"
inlineToZimWiki _ Space = return " "
inlineToZimWiki opts (Link _ txt (src, _)) = do
label <- inlineListToZimWiki opts txt
case txt of
[Str s] | "mailto:" `isPrefixOf` src -> return $ "<" ++ s ++ ">"
| escapeURI s == src -> return src
_ -> if isURI src
then return $ "[[" ++ src ++ "|" ++ label ++ "]]"
else return $ "[[" ++ src' ++ "|" ++ label ++ "]]"
where src' = case src of
'/':xs -> xs
_ -> src
inlineToZimWiki opts (Image attr alt (source, tit)) = do
alt' <- inlineListToZimWiki opts alt
let txt = case (tit, alt) of
("", []) -> ""
("", _ ) -> "|" ++ alt'
(_ , _ ) -> "|" ++ tit
prefix = if isURI source then "" else ":"
return $ "{{" ++ prefix ++ source ++ imageDims opts attr ++ txt ++ "}}"
inlineToZimWiki opts (Note contents) = do
contents' <- blockListToZimWiki opts contents
return $ "((" ++ contents' ++ "))"
imageDims :: WriterOptions -> Attr -> String
imageDims opts attr = go (toPx $ dimension Width attr) (toPx $ dimension Height attr)
where
toPx = fmap (showInPixel opts) . checkPct
checkPct (Just (Percent _)) = Nothing
checkPct maybeDim = maybeDim
go (Just w) Nothing = "?" ++ w
go (Just w) (Just h) = "?" ++ w ++ "x" ++ h
go Nothing (Just h) = "?0x" ++ h
go Nothing Nothing = ""