git.iko.soy/typeable/pandoc
on master help sign in
Markdown writer: Allow footnotes/refs at the end of blocks, sections
ca50deeJesse Rosenthal
src/Text/Pandoc/Writers/Markdown.hs+64−21
@@ -70,23 +70,27 @@ evalMD md env st = evalState (runReaderT md env) st
70data WriterEnv = WriterEnv { envInList :: Bool
70data WriterEnv = WriterEnv { envInList :: Bool
71 , envPlain :: Bool
71 , envPlain :: Bool
72 , envRefShortcutable :: Bool
72 , envRefShortcutable :: Bool
73+ , envBlockLevel :: Int
73 }
74 }
74
75
75instance Default WriterEnv
76instance Default WriterEnv
76 where def = WriterEnv { envInList = False
77 where def = WriterEnv { envInList = False
77 , envPlain = False
78 , envPlain = False
78 , envRefShortcutable = True
79 , envRefShortcutable = True
80+ , envBlockLevel = 0
79 }
81 }
80
82
81data WriterState = WriterState { stNotes :: Notes
83data WriterState = WriterState { stNotes :: Notes
82 , stRefs :: Refs
84 , stRefs :: Refs
83 , stIds :: Set.Set String
85 , stIds :: Set.Set String
86+ , stNoteNum :: Int
84 }
87 }
85
88
86instance Default WriterState
89instance Default WriterState
87 where def = WriterState{ stNotes = []
90 where def = WriterState{ stNotes = []
88 , stRefs = []
91 , stRefs = []
89 , stIds = Set.empty
92 , stIds = Set.empty
93+ , stNoteNum = 1
90 }
94 }
91
95
92-- | Convert Pandoc to Markdown.
96-- | Convert Pandoc to Markdown.
@@ -238,9 +242,11 @@ keyToMarkdown opts (label, (src, tit), attr) = do
238
242
239-- | Return markdown representation of notes.
243-- | Return markdown representation of notes.
240notesToMarkdown :: WriterOptions -> [[Block]] -> MD Doc
244notesToMarkdown :: WriterOptions -> [[Block]] -> MD Doc
241−notesToMarkdown opts notes =
245+notesToMarkdown opts notes = do
246+ n <- gets stNoteNum
242− mapM (\(num, note) -> noteToMarkdown opts num note) (zip [1..] notes) >>=
247+ notes' <- mapM (\(num, note) -> noteToMarkdown opts num note) (zip [n..] notes)
248+ modify $ \st -> st { stNoteNum = stNoteNum st + length notes }
243− return . vsep
249+ return $ vsep notes'
244
250
245-- | Return markdown representation of a note.
251-- | Return markdown representation of a note.
246noteToMarkdown :: WriterOptions -> Int -> [Block] -> MD Doc
252noteToMarkdown :: WriterOptions -> Int -> [Block] -> MD Doc
@@ -335,15 +341,35 @@ beginsWithOrderedListMarker str =
335blockToMarkdown :: WriterOptions -- ^ Options
341blockToMarkdown :: WriterOptions -- ^ Options
336 -> Block -- ^ Block element
342 -> Block -- ^ Block element
337 -> MD Doc
343 -> MD Doc
338−blockToMarkdown _ Null = return empty
344+blockToMarkdown opts blk =
345+ local (\env -> env {envBlockLevel = envBlockLevel env + 1}) $
346+ do doc <- blockToMarkdown' opts blk
347+ blkLevel <- asks envBlockLevel
348+ if writerReferenceLocation opts == EndOfBlock && blkLevel == 1
349+ then do st <- get
350+ notes' <- notesToMarkdown opts (reverse $ stNotes st)
351+ modify $ \s -> s { stNotes = [] }
352+ st' <- get -- note that the notes may contain refs
353+ refs' <- refsToMarkdown opts (reverse $ stRefs st')
354+ modify $ \s -> s { stRefs = [] }
355+ return $ doc <>
356+ (if isEmpty notes' then empty else blankline <> notes') <>
357+ (if isEmpty refs' then empty else blankline <> refs') <>
358+ (if (isEmpty notes' && isEmpty refs') then empty else blankline)
359+ else return doc
360+
361+blockToMarkdown' :: WriterOptions -- ^ Options
362+ -> Block -- ^ Block element
363+ -> MD Doc
364+blockToMarkdown' _ Null = return empty
339−blockToMarkdown opts (Div attrs ils) = do
365+blockToMarkdown' opts (Div attrs ils) = do
340 contents <- blockListToMarkdown opts ils
366 contents <- blockListToMarkdown opts ils
341 return $ if isEnabled Ext_raw_html opts &&
367 return $ if isEnabled Ext_raw_html opts &&
342 isEnabled Ext_markdown_in_html_blocks opts
368 isEnabled Ext_markdown_in_html_blocks opts
343 then tagWithAttrs "div" attrs <> blankline <>
369 then tagWithAttrs "div" attrs <> blankline <>
344 contents <> blankline <> "</div>" <> blankline
370 contents <> blankline <> "</div>" <> blankline
345 else contents <> blankline
371 else contents <> blankline
346−blockToMarkdown opts (Plain inlines) = do
372+blockToMarkdown' opts (Plain inlines) = do
347 contents <- inlineListToMarkdown opts inlines
373 contents <- inlineListToMarkdown opts inlines
348 -- escape if para starts with ordered list marker
374 -- escape if para starts with ordered list marker
349 isPlain <- asks envPlain
375 isPlain <- asks envPlain
@@ -360,11 +386,11 @@ blockToMarkdown opts (Plain inlines) = do
360 else contents
386 else contents
361 return $ contents' <> cr
387 return $ contents' <> cr
362-- title beginning with fig: indicates figure
388-- title beginning with fig: indicates figure
363−blockToMarkdown opts (Para [Image attr alt (src,'f':'i':'g':':':tit)]) =
389+blockToMarkdown' opts (Para [Image attr alt (src,'f':'i':'g':':':tit)]) =
364 blockToMarkdown opts (Para [Image attr alt (src,tit)])
390 blockToMarkdown opts (Para [Image attr alt (src,tit)])
365−blockToMarkdown opts (Para inlines) =
391+blockToMarkdown' opts (Para inlines) =
366 (<> blankline) `fmap` blockToMarkdown opts (Plain inlines)
392 (<> blankline) `fmap` blockToMarkdown opts (Plain inlines)
367−blockToMarkdown opts (RawBlock f str)
393+blockToMarkdown' opts (RawBlock f str)
368 | f == "markdown" = return $ text str <> text "\n"
394 | f == "markdown" = return $ text str <> text "\n"
369 | f == "html" && isEnabled Ext_raw_html opts = do
395 | f == "html" && isEnabled Ext_raw_html opts = do
370 plain <- asks envPlain
396 plain <- asks envPlain
@@ -379,9 +405,25 @@ blockToMarkdown opts (RawBlock f str)
379 then empty
405 then empty
380 else text str <> text "\n"
406 else text str <> text "\n"
381 | otherwise = return empty
407 | otherwise = return empty
382−blockToMarkdown opts HorizontalRule = do
408+blockToMarkdown' opts HorizontalRule = do
383 return $ blankline <> text (replicate (writerColumns opts) '-') <> blankline
409 return $ blankline <> text (replicate (writerColumns opts) '-') <> blankline
384−blockToMarkdown opts (Header level attr inlines) = do
410+blockToMarkdown' opts (Header level attr inlines) = do
411+ -- first, if we're putting references at the end of a section, we
412+ -- put them here.
413+ blkLevel <- asks envBlockLevel
414+ refs <- if writerReferenceLocation opts == EndOfSection && blkLevel == 1
415+ then do st <- get
416+ notes' <- notesToMarkdown opts (reverse $ stNotes st)
417+ modify $ \s -> s { stNotes = [] }
418+ st' <- get -- note that the notes may contain refs
419+ refs' <- refsToMarkdown opts (reverse $ stRefs st')
420+ modify $ \s -> s { stRefs = [] }
421+ return $
422+ (if isEmpty notes' then empty else blankline <> notes') <>
423+ (if isEmpty refs' then empty else blankline <> refs') <>
424+ (if (isEmpty notes' && isEmpty refs') then empty else blankline)
425+ else return empty
426+
385 plain <- asks envPlain
427 plain <- asks envPlain
386 -- we calculate the id that would be used by auto_identifiers
428 -- we calculate the id that would be used by auto_identifiers
387 -- so we know whether to print an explicit identifier
429 -- so we know whether to print an explicit identifier
@@ -402,8 +444,7 @@ blockToMarkdown opts (Header level attr inlines) = do
402 then capitalize inlines
444 then capitalize inlines
403 else inlines
445 else inlines
404 let setext = writerSetextHeaders opts
446 let setext = writerSetextHeaders opts
405− return $ nowrap
406− $ case level of
447+ hdr = nowrap $ case level of
407 1 | plain -> blanklines 3 <> contents <> blanklines 2
448 1 | plain -> blanklines 3 <> contents <> blanklines 2
408 | setext ->
449 | setext ->
409 contents <> attr' <> cr <> text (replicate (offset contents) '=') <>
450 contents <> attr' <> cr <> text (replicate (offset contents) '=') <>
@@ -416,11 +457,13 @@ blockToMarkdown opts (Header level attr inlines) = do
416 _ | plain || isEnabled Ext_literate_haskell opts ->
457 _ | plain || isEnabled Ext_literate_haskell opts ->
417 contents <> blankline
458 contents <> blankline
418 _ -> text (replicate level '#') <> space <> contents <> attr' <> blankline
459 _ -> text (replicate level '#') <> space <> contents <> attr' <> blankline
460+
461+ return $ refs <> hdr
419−blockToMarkdown opts (CodeBlock (_,classes,_) str)
462+blockToMarkdown' opts (CodeBlock (_,classes,_) str)
420 | "haskell" `elem` classes && "literate" `elem` classes &&
463 | "haskell" `elem` classes && "literate" `elem` classes &&
421 isEnabled Ext_literate_haskell opts =
464 isEnabled Ext_literate_haskell opts =
422 return $ prefixed "> " (text str) <> blankline
465 return $ prefixed "> " (text str) <> blankline
423−blockToMarkdown opts (CodeBlock attribs str) = return $
466+blockToMarkdown' opts (CodeBlock attribs str) = return $
424 case attribs == nullAttr of
467 case attribs == nullAttr of
425 False | isEnabled Ext_backtick_code_blocks opts ->
468 False | isEnabled Ext_backtick_code_blocks opts ->
426 backticks <> attrs <> cr <> text str <> cr <> backticks <> blankline
469 backticks <> attrs <> cr <> text str <> cr <> backticks <> blankline
@@ -442,7 +485,7 @@ blockToMarkdown opts (CodeBlock attribs str) = return $
442 else case attribs of
485 else case attribs of
443 (_,(cls:_),_) -> " " <> text cls
486 (_,(cls:_),_) -> " " <> text cls
444 _ -> empty
487 _ -> empty
445−blockToMarkdown opts (BlockQuote blocks) = do
488+blockToMarkdown' opts (BlockQuote blocks) = do
446 plain <- asks envPlain
489 plain <- asks envPlain
447 -- if we're writing literate haskell, put a space before the bird tracks
490 -- if we're writing literate haskell, put a space before the bird tracks
448 -- so they won't be interpreted as lhs...
491 -- so they won't be interpreted as lhs...
@@ -451,7 +494,7 @@ blockToMarkdown opts (BlockQuote blocks) = do
451 else if plain then " " else "> "
494 else if plain then " " else "> "
452 contents <- blockListToMarkdown opts blocks
495 contents <- blockListToMarkdown opts blocks
453 return $ (prefixed leader contents) <> blankline
496 return $ (prefixed leader contents) <> blankline
454−blockToMarkdown opts t@(Table caption aligns widths headers rows) = do
497+blockToMarkdown' opts t@(Table caption aligns widths headers rows) = do
455 caption' <- inlineListToMarkdown opts caption
498 caption' <- inlineListToMarkdown opts caption
456 let caption'' = if null caption || not (isEnabled Ext_table_captions opts)
499 let caption'' = if null caption || not (isEnabled Ext_table_captions opts)
457 then empty
500 then empty
@@ -485,10 +528,10 @@ blockToMarkdown opts t@(Table caption aligns widths headers rows) = do
485 $ Pandoc nullMeta [t]
528 $ Pandoc nullMeta [t]
486 | otherwise -> return $ (id, text "[TABLE]")
529 | otherwise -> return $ (id, text "[TABLE]")
487 return $ nst $ tbl $$ blankline $$ caption'' $$ blankline
530 return $ nst $ tbl $$ blankline $$ caption'' $$ blankline
488−blockToMarkdown opts (BulletList items) = do
531+blockToMarkdown' opts (BulletList items) = do
489 contents <- inList $ mapM (bulletListItemToMarkdown opts) items
532 contents <- inList $ mapM (bulletListItemToMarkdown opts) items
490 return $ cat contents <> blankline
533 return $ cat contents <> blankline
491−blockToMarkdown opts (OrderedList (start,sty,delim) items) = do
534+blockToMarkdown' opts (OrderedList (start,sty,delim) items) = do
492 let start' = if isEnabled Ext_startnum opts then start else 1
535 let start' = if isEnabled Ext_startnum opts then start else 1
493 let sty' = if isEnabled Ext_fancy_lists opts then sty else DefaultStyle
536 let sty' = if isEnabled Ext_fancy_lists opts then sty else DefaultStyle
494 let delim' = if isEnabled Ext_fancy_lists opts then delim else DefaultDelim
537 let delim' = if isEnabled Ext_fancy_lists opts then delim else DefaultDelim
@@ -501,7 +544,7 @@ blockToMarkdown opts (OrderedList (start,sty,delim) items) = do
501 mapM (\(item, num) -> orderedListItemToMarkdown opts item num) $
544 mapM (\(item, num) -> orderedListItemToMarkdown opts item num) $
502 zip markers' items
545 zip markers' items
503 return $ cat contents <> blankline
546 return $ cat contents <> blankline
504−blockToMarkdown opts (DefinitionList items) = do
547+blockToMarkdown' opts (DefinitionList items) = do
505 contents <- inList $ mapM (definitionListItemToMarkdown opts) items
548 contents <- inList $ mapM (definitionListItemToMarkdown opts) items
506 return $ cat contents <> blankline
549 return $ cat contents <> blankline
507
550
@@ -1009,7 +1052,7 @@ inlineToMarkdown opts img@(Image attr alternate (source, tit))
1009inlineToMarkdown opts (Note contents) = do
1052inlineToMarkdown opts (Note contents) = do
1010 modify (\st -> st{ stNotes = contents : stNotes st })
1053 modify (\st -> st{ stNotes = contents : stNotes st })
1011 st <- get
1054 st <- get
1012− let ref = text $ writerIdentifierPrefix opts ++ show (length $ stNotes st)
1055+ let ref = text $ writerIdentifierPrefix opts ++ show (stNoteNum st + (length $ stNotes st) - 1)
1013 if isEnabled Ext_footnotes opts
1056 if isEnabled Ext_footnotes opts
1014 then return $ "[^" <> ref <> "]"
1057 then return $ "[^" <> ref <> "]"
1015 else return $ "[" <> ref <> "]"
1058 else return $ "[" <> ref <> "]"