I wrote a filter for pandas that converts :-) to but not in inline code or code blocks. Pandoc makes it so the applyExceptOnCodeBlock smileyfy function trickles through the whole tree. But since I operate on Blocks and Inlines I need to handle every data constructor for Block that takes Inlines.

import Text.Pandoc.JSON
import Text.Pandoc.Walk

import Data.List (intercalate)
import Data.List.Split (splitOn)

main :: IO ()
main = toJSONFilter $ applyExceptOnCodeBlock smileyfy

smileyfy :: String -> String
smileyfy = intercalate "☺" . splitOn ":-)"

applyExceptOnCodeInline :: (String -> String) -> Inline -> Inline
applyExceptOnCodeInline f (Str s)       = Str (f s)
applyExceptOnCodeInline f (Code attr s) = Code attr s -- for code nothing shall happen
applyExceptOnCodeInline _ x = x

applyExceptOnCodeBlock :: (String -> String) -> Block -> Block
applyExceptOnCodeBlock f (Plain is) = Plain $ applyExceptOnCodeInline f <$> is
applyExceptOnCodeBlock f (Para  is) = Para  $ applyExceptOnCodeInline f <$> is
applyExceptOnCodeBlock f (LineBlock iss) = LineBlock $ (fmap . fmap) (applyExceptOnCodeInline f) iss
applyExceptOnCodeBlock f (RawBlock format s)  = RawBlock format $ f s
applyExceptOnCodeBlock f (DefinitionList [(is, bss)]) = DefinitionList [(applyExceptOnCodeInline f <$> is
                                                                        ,fmap (fmap (applyExceptOnCodeBlock f)) bss)]
applyExceptOnCodeBlock f (Header i attr is) = Header i attr $ applyExceptOnCodeInline f <$> is
applyExceptOnCodeBlock f (Table is as ds tcs tcss) = Table (applyExceptOnCodeInline f <$> is) as ds tcs tcss
applyExceptOnCodeBlock _ x = x --this covers CodeBlock as well

And thus it looks very convoluted. Is there a clearer way to express this?

  • \$\begingroup\$ For possible future visitors, I'd like to add that a small Lua filter is easier to apply, as you don't need a fully Haskell stack installed. \$\endgroup\$
    – Zeta
    Commented Sep 7, 2019 at 5:43

1 Answer 1


There's a much shorter way to express the same functionality, but I'm not sure you'll immediately find it to be clearer.

import Data.List (intercalate)
import Data.List.Split (splitOn)
import Text.Pandoc.JSON

main :: IO ()
main = toJSONFilter smile

smile :: Inline -> Inline
smile (Str s) = Str . intercalate "☺" . splitOn ":-)" $ s
smile x = x

I think the insight you missed is that the Block constructor CodeBlock contains no Inlines, so you can trust the Walkable typeclass machinery to ignore them if your smiley making code only works on Inlines. That eliminates the entire applyExceptOnCodeBlock function.

Similarly it isn't necessary to include a case for Inline's Code constructor, constructors like Code and Math contain their own raw strings and are safely ignored. Pandoc's datatypes are already structured to divide prose text from markup making it easy to define transformations by only matching on the things you do want to modify.

  • \$\begingroup\$ Thanks! I originally wanted the filter to ignore BlockQuote but when I switched to Code I didn't realize that I no longer had a problem. \$\endgroup\$
    – user2740
    Commented Sep 7, 2019 at 11:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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