I wrote a Pandoc filter that allows me to embed arbitrary HTML snippets in LaTeX documents, which are then printed out as-is when the document is converted to HTML. It is slightly more generic than it needs to be right now, in case I want to embed other types of things in the future.

It can be used like:

    %%% html
    <iframe width="560" height="315" src="https://www.youtube.com/embed/dQw4w9WgXcQ" frameborder="0" allowfullscreen></iframe>

In particular, I think the chain of >>= in convertCode could be improved some how, possibly using Applicatives. runReader basically does nothing except rearrange its arguments and call a thing.

import Data.Char ( isSpace )
import Data.List ( dropWhileEnd )
import Data.Maybe ( fromMaybe )

import Text.Pandoc.JSON ( Block(CodeBlock, RawBlock), Format(Format), toJSONFilter )

main = toJSONFilter convertCodeBlocks

type Reader = String -> Maybe [Block]

convertCodeBlocks :: Block -> [Block]
convertCodeBlocks b = case b of
    (CodeBlock _ code) -> fromMaybe [b] $ convertCode code
    _                  -> [b]

-- Given a string of code, try to convert it into something nicer
convertCode :: String -> Maybe [Block]
convertCode code =
    codeBlockType codeHead >>= codeBlockReader >>= runReader codeTail
  where (codeHead, codeTail) = splitWhen (== '\n') $ strip code

-- Get the reader name from the magic header
codeBlockType :: String -> Maybe String
codeBlockType line = if percent == "%%% "
    then Just $ strip typeName
    else Nothing
  where (percent, typeName) = splitAt 4 $ strip line

-- All the known readers and their name from the magic header
codeBlockReader :: String -> Maybe Reader
codeBlockReader "html" = Just rawHtml
codeBlockReader _      = Nothing

-- Make a raw HTML block from some string
rawHtml html = Just [RawBlock (Format "html") html]

-- A mostly useless function, only here because I can't make Applicatives work
runReader :: String -> Reader -> Maybe [Block]
runReader code reader = reader code

-- Strip whitespace from the start and end of a string
strip = dropWhileEnd isSpace . dropWhile isSpace

-- Split an array at a predicate, returning (before, after)
splitWhen :: (a -> Bool) -> [a] -> ([a], [a])
splitWhen pred [] = ([], [])
splitWhen pred (x:xs) = if pred x
    then ([], xs)
    else let (before, after) = splitWhen pred xs in (x:before, after)

I initially implemented something for this as a modification to Pandoc itself (See this PR on Github), but the maintainer suggested a filter instead.


I think it's clearer to just merge all those functions into one since you only really care about one case:

convertCode :: String -> Maybe [Block]
convertCode code =
  case strip codeHead of
    ('%':'%':'%':' ':typeName) -> matchType $ strip typeName
    _                          -> Nothing
   (codeHead, codeTail) = splitWhen (== '\n') $ strip code

   matchType "html" = Just [RawBlock (Format "html") codeTail]
   matchType _      = Nothing

Does this do the same thing? I cant test it without too much effort on my part.


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.