packages feed

pandoc-stylefrommeta-0.2.0.2: styleFromMeta.hs

{-# LANGUAGE ViewPatterns, PatternGuards, PatternSynonyms #-}

import           Text.Pandoc.JSON
import           Text.Pandoc.Walk (walk)
import           Text.Pandoc.Options (def)
import           Text.Pandoc.Shared (stringify)
import qualified Data.Map as M
import           Data.String.Utils (replace)

#if MIN_VERSION_pandoc(2,0,0)
import           Text.Pandoc.Writers (Writer (..), getWriter)
import           Text.Pandoc.Class (runPure)
import qualified Data.ByteString.Lazy.Char8 as C8L
import qualified Data.Text as T
import           Control.Exception (displayException)

#define MBPLAIN Plain
#else
import           Text.Pandoc (Writer (..), getWriter)

#define MBPLAIN Para
#endif

#if MIN_TOOL_VERSION_ghc(7,10,1)
pattern Style :: String -> Inline
#endif
pattern Style x <- Math InlineMath x

#if MIN_TOOL_VERSION_ghc(7,10,1)
pattern Subst :: String -> Inline
#endif
pattern Subst x = Math InlineMath x

#if MIN_TOOL_VERSION_ghc(7,10,1)
pattern SubstVerbatim :: String -> Inline
#endif
pattern SubstVerbatim x <- Math DisplayMath x

#if MIN_TOOL_VERSION_ghc(7,10,1)
pattern Alt :: [Inline] -> [Inline]
#endif
pattern Alt x <- (dropWhile (== Space) -> x)

type MMap = M.Map String MetaValue
type PureInlineParams = ([Inline], Target)          -- style:(alt, target)
type InlineParams = (Inline, [Inline], Target)      -- (style:alt, target)
type InlineCons = [Inline] -> Target -> Inline      -- Image or Link

styleFromMeta :: Maybe Format -> Pandoc -> IO Pandoc
styleFromMeta (Just fm) (Pandoc m bs) = do
    let b = unMeta m
    return $ Pandoc m $
        walk (substInlineStyle fm b) $  -- apply styles for links and images
        walk (substBlockStyle fm b) bs  -- apply para_style to paragraphs
styleFromMeta _ p = return p

substBlockStyle :: Format -> MMap -> Block -> Block
substBlockStyle _ _ b@(Para is@(Image {} : _))
    | all isImage is = b    -- do not apply para_style to standalone images
    where isImage Image {} = True
          isImage _        = False
substBlockStyle (Format fmt) m (Para cnt)
    | Just (MetaMap mm) <- M.lookup "para_style" m
    , Just (MetaBlocks [MBPLAIN [Span attr _]]) <- M.lookup fmt mm =
        Plain [Span attr cnt]
substBlockStyle _ _ b = b

substInlineStyle :: Format -> MMap -> Inline -> Inline
substInlineStyle fm@(Format fmt) m
                 i@(toInlineParams -> Just ((Style style, alt, tgt), cons))
    | Just (MetaMap mm) <- M.lookup style m =
        let params = (alt, tgt)
            substPlainParams = Span nullAttr . map (substParams fm params)
            substInlineStyle' (Just (MetaBlocks [CodeBlock _ vb])) =
                RawInline fm $ substParamsInRawBlock fm params vb
            substInlineStyle' (Just (MetaBlocks mbs)) =
                RawInline fm $ renderInlines fm $ map substInlineStyle'' mbs
                    where substInlineStyle'' mb =
                            case mb of
                                Plain is      -> substPlainParams is
                                Para is       -> substPlainParams is
                                d@Div {}      ->
                                    RawInline fm $
                                        substParamsInRawBlock fm params $
                                            renderBlocks fm [d]
                                RawBlock bfm b ->
                                    RawInline bfm $
                                        substParamsInRawBlock bfm params b
                                _             -> i
            substInlineStyle' Nothing = cons alt tgt
            substInlineStyle' _ = i
        in substInlineStyle' $ M.lookup fmt mm
substInlineStyle _ _ i = i

toInlineParams :: Inline -> Maybe (InlineParams, InlineCons)
toInlineParams (Image attr (style@(Style _) : Alt alt) tgt) =
    Just ((style, alt, tgt), Image attr)
toInlineParams (Link attr (style@(Style _) : Alt alt) tgt) =
    Just ((style, alt, tgt), Link attr)
toInlineParams _ = Nothing

substParams :: Format -> PureInlineParams -> Inline -> Inline
substParams _   (alt, _)        (Subst "ALT")           = Span nullAttr alt
substParams fm  params          (SubstVerbatim "ALT")   = RawInline fm $
    stringify $ substParams fm params $ Subst "ALT"
substParams _   (_, (src, _))   (Subst "SRC")           = Str src
substParams fm  (_, (src, _))   (SubstVerbatim "SRC")   = RawInline fm src
substParams _   (_, (_, title)) (Subst "TITLE")         = Str title
substParams fm  (_, (_, title)) (SubstVerbatim "TITLE") = RawInline fm title
substParams _   params          (RawInline fm s)        = RawInline fm $
    substParamsInRawBlock fm params s
substParams _   _               i                       = i

substParamsInRawBlock :: Format -> PureInlineParams -> String -> String
substParamsInRawBlock fm (alt, (src, title)) s =
    foldr (\(p, is) -> replace p $ renderInlines fm is) s
          [("$ALT$",     alt                                           )
          ,("$SRC$",     [Str src]                                     )
          ,("$TITLE$",   [Str title]                                   )
          ,("$$ALT$$",   [RawInline fm $ stringify $ Span nullAttr alt])
          ,("$$SRC$$",   [RawInline fm src]                            )
          ,("$$TITLE$$", [RawInline fm title]                          )
          ]

renderBlocks :: Format -> [Block] -> String
renderBlocks fm p =
    let fmt = toWriterFormat fm
        writer = getWriter fmt
        doc = Pandoc (Meta M.empty) p
    in case writer of
        Left _ -> error $ "Unknown format " ++ fmt
#if MIN_VERSION_pandoc(2,0,0)
        Right (TextWriter w, _) ->
            case runPure $ w def doc of
                Left e -> displayException e
                Right r -> T.unpack r
        Right (ByteStringWriter w, _) ->
            case runPure $ w def doc of
                Left e -> displayException e
                Right r -> C8L.unpack r
#else
        Right (PureStringWriter w) -> w def doc
        _ -> error $ "Unsupported format " ++ fmt ++ ", try Pandoc 2.0!"
#endif

renderInlines :: Format -> [Inline] -> String
renderInlines fm p = renderBlocks fm [Plain p]

toWriterFormat :: Format -> String
toWriterFormat (Format "tex") = "latex"
toWriterFormat (Format fmt)   = fmt

main :: IO ()
main = toJSONFilter styleFromMeta