@@ -1,6 +1,5 @@
1+{-# LANGUAGE FlexibleContexts #-}
1{-# LANGUAGE OverloadedStrings #-}
2{-# LANGUAGE OverloadedStrings #-}
2−{-# LANGUAGE GeneralizedNewtypeDeriving #-}
3−{-# LANGUAGE MultiParamTypeClasses, FlexibleContexts, FlexibleInstances #-}
5Copyright (C) 2014-2016 Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
4Copyright (C) 2014-2016 Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
@@ -31,8 +30,7 @@ Conversion of org-mode formatted plain text to 'Pandoc' document.
31module Text.Pandoc.Readers.Org ( readOrg ) where
30module Text.Pandoc.Readers.Org ( readOrg ) where
33import qualified Text.Pandoc.Builder as B
32import qualified Text.Pandoc.Builder as B
34−import Text.Pandoc.Builder ( Inlines, Blocks, HasMeta(..),
33+import Text.Pandoc.Builder ( Inlines, Blocks )
36import Text.Pandoc.Definition
34import Text.Pandoc.Definition
37import Text.Pandoc.Compat.Monoid ((<>))
35import Text.Pandoc.Compat.Monoid ((<>))
38import Text.Pandoc.Error
36import Text.Pandoc.Error
@@ -43,153 +41,33 @@ import Text.Pandoc.Parsing hiding ( F, unF, askF, asksF, runF
43 , parseFromString, blanklines
41 , parseFromString, blanklines
45import Text.Pandoc.Readers.LaTeX (inlineCommand, rawLaTeXInline)
43import Text.Pandoc.Readers.LaTeX (inlineCommand, rawLaTeXInline)
44+import Text.Pandoc.Readers.Org.ParserState
46import Text.Pandoc.Shared (compactify', compactify'DL)
45import Text.Pandoc.Shared (compactify', compactify'DL)
47import Text.TeXMath (readTeX, writePandoc, DisplayType(..))
46import Text.TeXMath (readTeX, writePandoc, DisplayType(..))
48import qualified Text.TeXMath.Readers.MathML.EntityMap as MathMLEntityMap
47import qualified Text.TeXMath.Readers.MathML.EntityMap as MathMLEntityMap
50import Control.Arrow (first)
49import Control.Arrow (first)
51−import Control.Monad (foldM, guard, liftM, liftM2, mplus, mzero, when)
50+import Control.Monad (foldM, guard, mplus, mzero, when)
52−import Control.Monad.Reader (Reader, runReader, ask, asks, local)
51+import Control.Monad.Reader ( Reader, runReader )
53import Data.Char (isAlphaNum, isSpace, toLower)
52import Data.Char (isAlphaNum, isSpace, toLower)
55import Data.List (intersperse, isPrefixOf, isSuffixOf)
53import Data.List (intersperse, isPrefixOf, isSuffixOf)
56import qualified Data.Map as M
54import qualified Data.Map as M
57−import qualified Data.Set as Set
58import Data.Maybe (fromMaybe, isJust)
55import Data.Maybe (fromMaybe, isJust)
59import Network.HTTP (urlEncode)
56import Network.HTTP (urlEncode)
61-- | Parse org-mode string and return a Pandoc document.
59-- | Parse org-mode string and return a Pandoc document.
62readOrg :: ReaderOptions -- ^ Reader options
60readOrg :: ReaderOptions -- ^ Reader options
63 -> String -- ^ String to parse (assuming @'\n'@ line endings)
61 -> String -- ^ String to parse (assuming @'\n'@ line endings)
64 -> Either PandocError Pandoc
62 -> Either PandocError Pandoc
65readOrg opts s = flip runReader def $ readWithM parseOrg def{ orgStateOptions = opts } (s ++ "\n\n")
63readOrg opts s = flip runReader def $ readWithM parseOrg def{ orgStateOptions = opts } (s ++ "\n\n")
67−data OrgParserLocal = OrgParserLocal { orgLocalQuoteContext :: QuoteContext }
65+-- | The parser used to read org files.
69type OrgParser = ParserT [Char] OrgParserState (Reader OrgParserLocal)
66type OrgParser = ParserT [Char] OrgParserState (Reader OrgParserLocal)
71−instance HasIdentifierList OrgParserState where
72− extractIdentifierList = orgStateIdentifiers
73− updateIdentifierList f s = s{ orgStateIdentifiers = f (orgStateIdentifiers s) }
75−instance HasHeaderMap OrgParserState where
76− extractHeaderMap = orgStateHeaderMap
77− updateHeaderMap f s = s{ orgStateHeaderMap = f (orgStateHeaderMap s) }
79−parseOrg :: OrgParser Pandoc
81− blocks' <- parseBlocks
83− let meta = runF (orgStateMeta' st) st
84− let removeUnwantedBlocks = dropCommentTrees . filter (/= Null)
85− return $ Pandoc meta $ removeUnwantedBlocks (B.toList $ runF blocks' st)
87−-- | Drop COMMENT headers and the document tree below those headers.
88−dropCommentTrees :: [Block] -> [Block]
89−dropCommentTrees [] = []
90−dropCommentTrees (b:bs) =
91− maybe (b:dropCommentTrees bs)
92− (dropCommentTrees . flip dropUntilHeaderAboveLevel bs)
93− (commentHeaderLevel b)
95−-- | Return the level of a header starting a comment or :noexport: tree and
97−commentHeaderLevel :: Block -> Maybe Int
98−commentHeaderLevel blk =
100− (Header level _ ((Str "COMMENT"):_)) -> Just level
101− (Header level _ title) | hasNoExportTag title -> Just level
104− hasNoExportTag :: [Inline] -> Bool
105− hasNoExportTag = any isNoExportTag
107− isNoExportTag :: Inline -> Bool
108− isNoExportTag (Span ("", ["tag"], [("data-tag-name", "noexport")]) []) = True
109− isNoExportTag _ = False
111−-- | Drop blocks until a header on or above the given level is seen
112−dropUntilHeaderAboveLevel :: Int -> [Block] -> [Block]
113−dropUntilHeaderAboveLevel n = dropWhile (not . isHeaderLevelLowerEq n)
115−isHeaderLevelLowerEq :: Int -> Block -> Bool
116−isHeaderLevelLowerEq n blk =
118− (Header level _ _) -> n >= level
122−-- Parser State for Org
69+-- Functions acting on the parser state
125−type OrgNoteRecord = (String, F Blocks)
126−type OrgNoteTable = [OrgNoteRecord]
128−type OrgBlockAttributes = M.Map String String
130−type OrgLinkFormatters = M.Map String (String -> String)
132−-- | Org-mode parser state
133−data OrgParserState = OrgParserState
134− { orgStateOptions :: ReaderOptions
135− , orgStateAnchorIds :: [String]
136− , orgStateBlockAttributes :: OrgBlockAttributes
137− , orgStateEmphasisCharStack :: [Char]
138− , orgStateEmphasisNewlines :: Maybe Int
139− , orgStateLastForbiddenCharPos :: Maybe SourcePos
140− , orgStateLastPreCharPos :: Maybe SourcePos
141− , orgStateLastStrPos :: Maybe SourcePos
142− , orgStateLinkFormatters :: OrgLinkFormatters
143− , orgStateMeta :: Meta
144− , orgStateMeta' :: F Meta
145− , orgStateNotes' :: OrgNoteTable
146− , orgStateParserContext :: ParserContext
147− , orgStateIdentifiers :: Set.Set String
148− , orgStateHeaderMap :: M.Map Inlines String
151−instance Default OrgParserLocal where
152− def = OrgParserLocal NoQuote
154−instance HasReaderOptions OrgParserState where
155− extractReaderOptions = orgStateOptions
157−instance HasMeta OrgParserState where
158− setMeta field val st =
159− st{ orgStateMeta = setMeta field val $ orgStateMeta st }
160− deleteMeta field st =
161− st{ orgStateMeta = deleteMeta field $ orgStateMeta st }
163−instance HasLastStrPosition OrgParserState where
164− getLastStrPos = orgStateLastStrPos
165− setLastStrPos pos st = st{ orgStateLastStrPos = Just pos }
167−instance HasQuoteContext st (Reader OrgParserLocal) where
168− getQuoteContext = asks orgLocalQuoteContext
169− withQuoteContext q = local (\s -> s{orgLocalQuoteContext = q})
171−instance Default OrgParserState where
172− def = defaultOrgParserState
174−defaultOrgParserState :: OrgParserState
175−defaultOrgParserState = OrgParserState
176− { orgStateOptions = def
177− , orgStateAnchorIds = []
178− , orgStateBlockAttributes = M.empty
179− , orgStateEmphasisCharStack = []
180− , orgStateEmphasisNewlines = Nothing
181− , orgStateLastForbiddenCharPos = Nothing
182− , orgStateLastPreCharPos = Nothing
183− , orgStateLastStrPos = Nothing
184− , orgStateLinkFormatters = M.empty
185− , orgStateMeta = nullMeta
186− , orgStateMeta' = return nullMeta
187− , orgStateNotes' = []
188− , orgStateParserContext = NullState
189− , orgStateIdentifiers = Set.empty
190− , orgStateHeaderMap = M.empty
193recordAnchorId :: String -> OrgParser ()
71recordAnchorId :: String -> OrgParser ()
194recordAnchorId i = updateState $ \s ->
72recordAnchorId i = updateState $ \s ->
195 s{ orgStateAnchorIds = i : (orgStateAnchorIds s) }
73 s{ orgStateAnchorIds = i : (orgStateAnchorIds s) }
@@ -243,44 +121,65 @@ addToNotesTable note = do
243 oldnotes <- orgStateNotes' <$> getState
121 oldnotes <- orgStateNotes' <$> getState
244 updateState $ \s -> s{ orgStateNotes' = note:oldnotes }
122 updateState $ \s -> s{ orgStateNotes' = note:oldnotes }
246−-- The version Text.Pandoc.Parsing cannot be used, as we need additional parts
247−-- of the state saved and restored.
248−parseFromString :: OrgParser a -> String -> OrgParser a
249−parseFromString parser str' = do
250− oldLastPreCharPos <- orgStateLastPreCharPos <$> getState
251− updateState $ \s -> s{ orgStateLastPreCharPos = Nothing }
252− result <- P.parseFromString parser str'
253− updateState $ \s -> s{ orgStateLastPreCharPos = oldLastPreCharPos }
258−-- Adaptions and specializations of parsing utilities
125+-- Parser
127+parseOrg :: OrgParser Pandoc
129+ blocks' <- parseBlocks
131+ let meta = runF (orgStateMeta' st) st
132+ let removeUnwantedBlocks = dropCommentTrees . filter (/= Null)
133+ return $ Pandoc meta $ removeUnwantedBlocks (B.toList $ runF blocks' st)
261−newtype F a = F { unF :: Reader OrgParserState a
135+-- | Drop COMMENT headers and the document tree below those headers.
262− } deriving (Monad, Applicative, Functor)
136+dropCommentTrees :: [Block] -> [Block]
137+dropCommentTrees [] = []
138+dropCommentTrees (b:bs) =
139+ maybe (b:dropCommentTrees bs)
140+ (dropCommentTrees . flip dropUntilHeaderAboveLevel bs)
141+ (commentHeaderLevel b)
264−runF :: F a -> OrgParserState -> a
143+-- | Return the level of a header starting a comment or :noexport: tree and
265−runF = runReader . unF
144+-- Nothing otherwise.
145+commentHeaderLevel :: Block -> Maybe Int
146+commentHeaderLevel blk =
148+ (Header level _ ((Str "COMMENT"):_)) -> Just level
149+ (Header level _ title) | hasNoExportTag title -> Just level
152+ hasNoExportTag :: [Inline] -> Bool
153+ hasNoExportTag = any isNoExportTag
267−askF :: F OrgParserState
155+ isNoExportTag :: Inline -> Bool
268−askF = F ask
156+ isNoExportTag (Span ("", ["tag"], [("data-tag-name", "noexport")]) []) = True
157+ isNoExportTag _ = False
270−asksF :: (OrgParserState -> a) -> F a
159+-- | Drop blocks until a header on or above the given level is seen
271−asksF f = F $ asks f
160+dropUntilHeaderAboveLevel :: Int -> [Block] -> [Block]
161+dropUntilHeaderAboveLevel n = dropWhile (not . isHeaderLevelLowerEq n)
273−instance Monoid a => Monoid (F a) where
163+isHeaderLevelLowerEq :: Int -> Block -> Bool
274− mempty = return mempty
164+isHeaderLevelLowerEq n blk =
275− mappend = liftM2 mappend
165+ case blk of
276− mconcat = fmap mconcat . sequence
166+ (Header level _ _) -> n >= level
278−trimInlinesF :: F Inlines -> F Inlines
279−trimInlinesF = liftM trimInlines
281−returnF :: a -> OrgParser (F a)
170+--
282−returnF = return . return
171+-- Adaptions and specializations of parsing utilities
174+-- The version Text.Pandoc.Parsing cannot be used, as we need additional parts
175+-- of the state saved and restored.
176+parseFromString :: OrgParser a -> String -> OrgParser a
177+parseFromString parser str' = do
178+ oldLastPreCharPos <- orgStateLastPreCharPos <$> getState
179+ updateState $ \s -> s{ orgStateLastPreCharPos = Nothing }
180+ result <- P.parseFromString parser str'
181+ updateState $ \s -> s{ orgStateLastPreCharPos = oldLastPreCharPos }
285-- | Like @Text.Parsec.Char.newline@, but causes additional state changes.
184-- | Like @Text.Parsec.Char.newline@, but causes additional state changes.
286newline :: OrgParser Char
185newline :: OrgParser Char