@@ -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,117 @@ 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
124+--
247−-- of the state saved and restored.
125+-- Export Settings
248−parseFromString :: OrgParser a -> String -> OrgParser a
126+--
249−parseFromString parser str' = do
127+exportSetting :: OrgParser ()
250− oldLastPreCharPos <- orgStateLastPreCharPos <$> getState
128+exportSetting = choice
251− updateState $ \s -> s{ orgStateLastPreCharPos = Nothing }
129+ [ booleanSetting "^" setExportSubSuperscripts
252− result <- P.parseFromString parser str'
130+ , ignoredSetting "'"
253− updateState $ \s -> s{ orgStateLastPreCharPos = oldLastPreCharPos }
131+ , ignoredSetting "*"
254− return result
132+ , ignoredSetting "-"
133+ , ignoredSetting ":"
134+ , ignoredSetting "<"
135+ , ignoredSetting "\\n"
136+ , ignoredSetting "arch"
137+ , ignoredSetting "author"
138+ , ignoredSetting "c"
139+ , ignoredSetting "creator"
140+ , ignoredSetting "d"
141+ , ignoredSetting "date"
142+ , ignoredSetting "e"
143+ , ignoredSetting "email"
144+ , ignoredSetting "f"
145+ , ignoredSetting "H"
146+ , ignoredSetting "inline"
147+ , ignoredSetting "num"
148+ , ignoredSetting "p"
149+ , ignoredSetting "pri"
150+ , ignoredSetting "prop"
151+ , ignoredSetting "stat"
152+ , ignoredSetting "tags"
153+ , ignoredSetting "tasks"
154+ , ignoredSetting "tex"
155+ , ignoredSetting "timestamp"
156+ , ignoredSetting "title"
157+ , ignoredSetting "toc"
158+ , ignoredSetting "todo"
159+ , ignoredSetting "|"
160+ ] <?> "export setting"
162+booleanSetting :: String -> ExportSettingSetter Bool -> OrgParser ()
163+booleanSetting settingIdentifier setter = try $ do
164+ string settingIdentifier
166+ value <- many nonspaceChar
167+ let boolValue = case value of
171+ updateState $ modifyExportSettings setter boolValue
173+ignoredSetting :: String -> OrgParser ()
174+ignoredSetting s = try (() <$ string s <* char ':' <* many nonspaceChar)
258−-- Adaptions and specializations of parsing utilities
177+-- Parser
179+parseOrg :: OrgParser Pandoc
181+ blocks' <- parseBlocks
183+ let meta = runF (orgStateMeta' st) st
184+ let removeUnwantedBlocks = dropCommentTrees . filter (/= Null)
185+ return $ Pandoc meta $ removeUnwantedBlocks (B.toList $ runF blocks' st)
261−newtype F a = F { unF :: Reader OrgParserState a
187+-- | Drop COMMENT headers and the document tree below those headers.
262− } deriving (Monad, Applicative, Functor)
188+dropCommentTrees :: [Block] -> [Block]
189+dropCommentTrees [] = []
190+dropCommentTrees (b:bs) =
191+ maybe (b:dropCommentTrees bs)
192+ (dropCommentTrees . flip dropUntilHeaderAboveLevel bs)
193+ (commentHeaderLevel b)
264−runF :: F a -> OrgParserState -> a
195+-- | Return the level of a header starting a comment or :noexport: tree and
265−runF = runReader . unF
196+-- Nothing otherwise.
197+commentHeaderLevel :: Block -> Maybe Int
198+commentHeaderLevel blk =
200+ (Header level _ ((Str "COMMENT"):_)) -> Just level
201+ (Header level _ title) | hasNoExportTag title -> Just level
204+ hasNoExportTag :: [Inline] -> Bool
205+ hasNoExportTag = any isNoExportTag
267−askF :: F OrgParserState
207+ isNoExportTag :: Inline -> Bool
268−askF = F ask
208+ isNoExportTag (Span ("", ["tag"], [("data-tag-name", "noexport")]) []) = True
209+ isNoExportTag _ = False
270−asksF :: (OrgParserState -> a) -> F a
211+-- | Drop blocks until a header on or above the given level is seen
271−asksF f = F $ asks f
212+dropUntilHeaderAboveLevel :: Int -> [Block] -> [Block]
213+dropUntilHeaderAboveLevel n = dropWhile (not . isHeaderLevelLowerEq n)
273−instance Monoid a => Monoid (F a) where
215+isHeaderLevelLowerEq :: Int -> Block -> Bool
274− mempty = return mempty
216+isHeaderLevelLowerEq n blk =
275− mappend = liftM2 mappend
217+ case blk of
276− mconcat = fmap mconcat . sequence
218+ (Header level _ _) -> n >= level
278−trimInlinesF :: F Inlines -> F Inlines
279−trimInlinesF = liftM trimInlines
281−returnF :: a -> OrgParser (F a)
222+--
282−returnF = return . return
223+-- Adaptions and specializations of parsing utilities
226+-- The version Text.Pandoc.Parsing cannot be used, as we need additional parts
227+-- of the state saved and restored.
228+parseFromString :: OrgParser a -> String -> OrgParser a
229+parseFromString parser str' = do
230+ oldLastPreCharPos <- orgStateLastPreCharPos <$> getState
231+ updateState $ \s -> s{ orgStateLastPreCharPos = Nothing }
232+ result <- P.parseFromString parser str'
233+ updateState $ \s -> s{ orgStateLastPreCharPos = oldLastPreCharPos }
285-- | Like @Text.Parsec.Char.newline@, but causes additional state changes.
236-- | Like @Text.Parsec.Char.newline@, but causes additional state changes.
286newline :: OrgParser Char
237newline :: OrgParser Char
@@ -691,8 +642,9 @@ optionLine :: OrgParser ()
691optionLine = try $ do
642optionLine = try $ do
692 key <- metaKey
643 key <- metaKey
693 case key of
644 case key of
694− "link" -> parseLinkFormat >>= uncurry addLinkFormat
645+ "link" -> parseLinkFormat >>= uncurry addLinkFormat
646+ "options" -> () <$ sepBy spaces exportSetting
695− _ -> mzero
647+ _ -> mzero
697parseLinkFormat :: OrgParser ((String, String -> String))
649parseLinkFormat :: OrgParser ((String, String -> String))
698parseLinkFormat = try $ do
650parseLinkFormat = try $ do
@@ -1561,7 +1513,9 @@ subOrSuperExpr = try $
1561 where enclosing (left, right) s = left : s ++ [right]
1513 where enclosing (left, right) s = left : s ++ [right]
1563simpleSubOrSuperString :: OrgParser String
1515simpleSubOrSuperString :: OrgParser String
1564−simpleSubOrSuperString = try $
1516+simpleSubOrSuperString = try $ do
1518+ guard . exportSubSuperscripts . orgStateExportSettings $ state
1565 choice [ string "*"
1519 choice [ string "*"
1566 , mappend <$> option [] ((:[]) <$> oneOf "+-")
1520 , mappend <$> option [] ((:[]) <$> oneOf "+-")
1567 <*> many1 alphaNum
1521 <*> many1 alphaNum