@@ -44,7 +44,7 @@ import Control.Applicative ( Applicative, pure
4444 , (<$>), (<$), (<*>), (<*), (*>), (<**>) )
4545import Control.Arrow ((***))
4646import Control.Monad (foldM, guard, liftM, liftM2, when)
47−import Control.Monad.Reader (Reader, runReader)
47+import Control.Monad.Reader (Reader, runReader, ask, asks)
4848import Data.Char (toLower)
4949import Data.Default
5050import Data.List (isPrefixOf, isSuffixOf)
@@ -59,7 +59,7 @@ readOrg opts s = readWith parseOrg def{ orgStateOptions = opts } (s ++ "\n\n")
5959
6060type OrgParser = Parser [Char] OrgParserState
6161
62−parseOrg:: OrgParser Pandoc
62+parseOrg :: OrgParser Pandoc
6363parseOrg = do
6464 blocks' <- parseBlocks
6565 st <- getState
@@ -70,6 +70,9 @@ parseOrg = do
7070-- Parser State for Org
7171--
7272
73+type OrgNoteRecord = (String, F Blocks)
74+type OrgNoteTable = [OrgNoteRecord]
75+
7376-- | Org-mode parser state
7477data OrgParserState = OrgParserState
7578 { orgStateOptions :: ReaderOptions
@@ -80,6 +83,7 @@ data OrgParserState = OrgParserState
8083 , orgStateLastStrPos :: Maybe SourcePos
8184 , orgStateMeta :: Meta
8285 , orgStateMeta' :: F Meta
86+ , orgStateNotes' :: OrgNoteTable
8387 }
8488
8589instance HasReaderOptions OrgParserState where
@@ -104,6 +108,7 @@ defaultOrgParserState = OrgParserState
104108 , orgStateLastStrPos = Nothing
105109 , orgStateMeta = nullMeta
106110 , orgStateMeta' = return nullMeta
111+ , orgStateNotes' = []
107112 }
108113
109114updateLastStrPos :: OrgParser ()
@@ -146,6 +151,11 @@ resetEmphasisNewlines :: OrgParser ()
146151resetEmphasisNewlines = updateState $ \s ->
147152 s{ orgStateEmphasisNewlines = Nothing }
148153
154+addToNotesTable :: OrgNoteRecord -> OrgParser ()
155+addToNotesTable note = do
156+ oldnotes <- orgStateNotes' <$> getState
157+ updateState $ \s -> s{ orgStateNotes' = note:oldnotes }
158+
149159
150160--
151161-- Adaptions and specializations of parsing utilities
@@ -157,6 +167,12 @@ newtype F a = F { unF :: Reader OrgParserState a
157167runF :: F a -> OrgParserState -> a
158168runF = runReader . unF
159169
170+askF :: F OrgParserState
171+askF = F ask
172+
173+asksF :: (OrgParserState -> a) -> F a
174+asksF f = F $ asks f
175+
160176instance Monoid a => Monoid (F a) where
161177 mempty = return mempty
162178 mappend = liftM2 mappend
@@ -191,6 +207,7 @@ block = choice [ mempty <$ blanklines
191207 , return <$> hline
192208 , list
193209 , table
210+ , noteBlock
194211 , paraOrPlain
195212 ] <?> "block"
196213
@@ -500,6 +517,16 @@ setAligns :: [Alignment]
500517 -> F OrgTable
501518setAligns aligns t = return $ t{ orgTableAlignments = aligns }
502519
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+
503530-- Paragraphs or Plain text
504531paraOrPlain :: OrgParser (F Blocks)
505532paraOrPlain = try $
@@ -587,6 +614,7 @@ inline :: OrgParser (F Inlines)
587614inline =
588615 choice [ whitespace
589616 , linebreak
617+ , footnote
590618 , linkOrImage
591619 , str
592620 , endline
@@ -632,6 +660,7 @@ endline = try $ do
632660 notFollowedBy blankline
633661 notFollowedBy' exampleLine
634662 notFollowedBy' hline
663+ notFollowedBy' noteMarker
635664 notFollowedBy' tableStart
636665 notFollowedBy' drawerStart
637666 notFollowedBy' headerStart
@@ -644,6 +673,39 @@ endline = try $ do
644673 updateLastPreCharPos
645674 return . return $ B.space
646675
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+
647709linkOrImage :: OrgParser (F Inlines)
648710linkOrImage = explicitOrImageLink <|> selflinkOrImage <?> "link or image"
649711