git.iko.soy/typeable/pandoc
on master help sign in
Org reader: Support footnotes
0672f58Albert Krewinkel
src/Text/Pandoc/Readers/Org.hs+64−2
@@ -44,7 +44,7 @@ import Control.Applicative ( Applicative, pure
44 , (<$>), (<$), (<*>), (<*), (*>), (<**>) )
44 , (<$>), (<$), (<*>), (<*), (*>), (<**>) )
45import Control.Arrow ((***))
45import Control.Arrow ((***))
46import Control.Monad (foldM, guard, liftM, liftM2, when)
46import Control.Monad (foldM, guard, liftM, liftM2, when)
47−import Control.Monad.Reader (Reader, runReader)
47+import Control.Monad.Reader (Reader, runReader, ask, asks)
48import Data.Char (toLower)
48import Data.Char (toLower)
49import Data.Default
49import Data.Default
50import Data.List (isPrefixOf, isSuffixOf)
50import Data.List (isPrefixOf, isSuffixOf)
@@ -59,7 +59,7 @@ readOrg opts s = readWith parseOrg def{ orgStateOptions = opts } (s ++ "\n\n")
59
59
60type OrgParser = Parser [Char] OrgParserState
60type OrgParser = Parser [Char] OrgParserState
61
61
62−parseOrg:: OrgParser Pandoc
62+parseOrg :: OrgParser Pandoc
63parseOrg = do
63parseOrg = do
64 blocks' <- parseBlocks
64 blocks' <- parseBlocks
65 st <- getState
65 st <- getState
@@ -70,6 +70,9 @@ parseOrg = do
70-- Parser State for Org
70-- Parser State for Org
71--
71--
72
72
73+type OrgNoteRecord = (String, F Blocks)
74+type OrgNoteTable = [OrgNoteRecord]
75+
73-- | Org-mode parser state
76-- | Org-mode parser state
74data OrgParserState = OrgParserState
77data OrgParserState = OrgParserState
75 { orgStateOptions :: ReaderOptions
78 { orgStateOptions :: ReaderOptions
@@ -80,6 +83,7 @@ data OrgParserState = OrgParserState
80 , orgStateLastStrPos :: Maybe SourcePos
83 , orgStateLastStrPos :: Maybe SourcePos
81 , orgStateMeta :: Meta
84 , orgStateMeta :: Meta
82 , orgStateMeta' :: F Meta
85 , orgStateMeta' :: F Meta
86+ , orgStateNotes' :: OrgNoteTable
83 }
87 }
84
88
85instance HasReaderOptions OrgParserState where
89instance HasReaderOptions OrgParserState where
@@ -104,6 +108,7 @@ defaultOrgParserState = OrgParserState
104 , orgStateLastStrPos = Nothing
108 , orgStateLastStrPos = Nothing
105 , orgStateMeta = nullMeta
109 , orgStateMeta = nullMeta
106 , orgStateMeta' = return nullMeta
110 , orgStateMeta' = return nullMeta
111+ , orgStateNotes' = []
107 }
112 }
108
113
109updateLastStrPos :: OrgParser ()
114updateLastStrPos :: OrgParser ()
@@ -146,6 +151,11 @@ resetEmphasisNewlines :: OrgParser ()
146resetEmphasisNewlines = updateState $ \s ->
151resetEmphasisNewlines = updateState $ \s ->
147 s{ orgStateEmphasisNewlines = Nothing }
152 s{ orgStateEmphasisNewlines = Nothing }
148
153
154+addToNotesTable :: OrgNoteRecord -> OrgParser ()
155+addToNotesTable note = do
156+ oldnotes <- orgStateNotes' <$> getState
157+ updateState $ \s -> s{ orgStateNotes' = note:oldnotes }
158+
149
159
150--
160--
151-- Adaptions and specializations of parsing utilities
161-- Adaptions and specializations of parsing utilities
@@ -157,6 +167,12 @@ newtype F a = F { unF :: Reader OrgParserState a
157runF :: F a -> OrgParserState -> a
167runF :: F a -> OrgParserState -> a
158runF = runReader . unF
168runF = runReader . unF
159
169
170+askF :: F OrgParserState
171+askF = F ask
172+
173+asksF :: (OrgParserState -> a) -> F a
174+asksF f = F $ asks f
175+
160instance Monoid a => Monoid (F a) where
176instance Monoid a => Monoid (F a) where
161 mempty = return mempty
177 mempty = return mempty
162 mappend = liftM2 mappend
178 mappend = liftM2 mappend
@@ -191,6 +207,7 @@ block = choice [ mempty <$ blanklines
191 , return <$> hline
207 , return <$> hline
192 , list
208 , list
193 , table
209 , table
210+ , noteBlock
194 , paraOrPlain
211 , paraOrPlain
195 ] <?> "block"
212 ] <?> "block"
196
213
@@ -500,6 +517,16 @@ setAligns :: [Alignment]
500 -> F OrgTable
517 -> F OrgTable
501setAligns aligns t = return $ t{ orgTableAlignments = aligns }
518setAligns aligns t = return $ t{ orgTableAlignments = aligns }
502
519
520+--
521+-- Footnote defintions
522+--
523+noteBlock :: OrgParser (F Blocks)
524+noteBlock = try $ do
525+ ref <- noteMarker
526+ content <- skipSpaces *> paraOrPlain
527+ addToNotesTable (ref, content)
528+ return mempty
529+
503-- Paragraphs or Plain text
530-- Paragraphs or Plain text
504paraOrPlain :: OrgParser (F Blocks)
531paraOrPlain :: OrgParser (F Blocks)
505paraOrPlain = try $
532paraOrPlain = try $
@@ -587,6 +614,7 @@ inline :: OrgParser (F Inlines)
587inline =
614inline =
588 choice [ whitespace
615 choice [ whitespace
589 , linebreak
616 , linebreak
617+ , footnote
590 , linkOrImage
618 , linkOrImage
591 , str
619 , str
592 , endline
620 , endline
@@ -632,6 +660,7 @@ endline = try $ do
632 notFollowedBy blankline
660 notFollowedBy blankline
633 notFollowedBy' exampleLine
661 notFollowedBy' exampleLine
634 notFollowedBy' hline
662 notFollowedBy' hline
663+ notFollowedBy' noteMarker
635 notFollowedBy' tableStart
664 notFollowedBy' tableStart
636 notFollowedBy' drawerStart
665 notFollowedBy' drawerStart
637 notFollowedBy' headerStart
666 notFollowedBy' headerStart
@@ -644,6 +673,39 @@ endline = try $ do
644 updateLastPreCharPos
673 updateLastPreCharPos
645 return . return $ B.space
674 return . return $ B.space
646
675
676+footnote :: OrgParser (F Inlines)
677+footnote = try $ inlineNote <|> referencedNote
678+
679+inlineNote :: OrgParser (F Inlines)
680+inlineNote = try $ do
681+ string "[fn:"
682+ ref <- many alphaNum
683+ char ':'
684+ note <- fmap B.para . trimInlinesF . mconcat <$> many1Till inline (char ']')
685+ when (not $ null ref) $
686+ addToNotesTable ("fn:" ++ ref, note)
687+ return $ B.note <$> note
688+
689+referencedNote :: OrgParser (F Inlines)
690+referencedNote = try $ do
691+ ref <- noteMarker
692+ return $ do
693+ notes <- asksF orgStateNotes'
694+ case lookup ref notes of
695+ Nothing -> return $ B.str $ "[" ++ ref ++ "]"
696+ Just contents -> do
697+ st <- askF
698+ let contents' = runF contents st{ orgStateNotes' = [] }
699+ return $ B.note contents'
700+
701+noteMarker :: OrgParser String
702+noteMarker = try $ do
703+ char '['
704+ choice [ many1Till digit (char ']')
705+ , (++) <$> string "fn:"
706+ <*> many1Till (noneOf "\n\r\t ") (char ']')
707+ ]
708+
647linkOrImage :: OrgParser (F Inlines)
709linkOrImage :: OrgParser (F Inlines)
648linkOrImage = explicitOrImageLink <|> selflinkOrImage <?> "link or image"
710linkOrImage = explicitOrImageLink <|> selflinkOrImage <?> "link or image"
649
711
tests/Tests/Readers/Org.hs+4
@@ -98,6 +98,10 @@ tests =
98 "line \\\\ \nbreak" =?>
98 "line \\\\ \nbreak" =?>
99 para ("line" <> linebreak <> "break")
99 para ("line" <> linebreak <> "break")
100
100
101+ , "Inline note" =:
102+ "[fn::Schreib mir eine E-Mail]" =?>
103+ para (note $ para "Schreib mir eine E-Mail")
104+
101 , "Markup-chars not occuring on word break are symbols" =:
105 , "Markup-chars not occuring on word break are symbols" =:
102 unlines [ "this+that+ +so+on"
106 unlines [ "this+that+ +so+on"
103 , "seven*eight* nine*"
107 , "seven*eight* nine*"