@@ -1,5 +1,4 @@
1{-# LANGUAGE FlexibleContexts #-}
1{-# LANGUAGE FlexibleContexts #-}
2−{-# LANGUAGE OverloadedStrings #-}
4Copyright (C) 2014-2016 Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
3Copyright (C) 2014-2016 Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
@@ -29,6 +28,8 @@ Conversion of org-mode formatted plain text to 'Pandoc' document.
30module Text.Pandoc.Readers.Org ( readOrg ) where
29module Text.Pandoc.Readers.Org ( readOrg ) where
31+import Text.Pandoc.Readers.Org.BlockStarts
32+import Text.Pandoc.Readers.Org.Inlines
32import Text.Pandoc.Readers.Org.ParserState
33import Text.Pandoc.Readers.Org.ParserState
33import Text.Pandoc.Readers.Org.Parsing
34import Text.Pandoc.Readers.Org.Parsing
@@ -38,19 +39,16 @@ import Text.Pandoc.Definition
38import Text.Pandoc.Compat.Monoid ((<>))
39import Text.Pandoc.Compat.Monoid ((<>))
39import Text.Pandoc.Error
40import Text.Pandoc.Error
40import Text.Pandoc.Options
41import Text.Pandoc.Options
41−import Text.Pandoc.Readers.LaTeX (inlineCommand, rawLaTeXInline)
42−import Text.Pandoc.Shared (compactify', compactify'DL)
42+import Text.Pandoc.Shared ( compactify', compactify'DL )
43−import Text.TeXMath (readTeX, writePandoc, DisplayType(..))
44−import qualified Text.TeXMath.Readers.MathML.EntityMap as MathMLEntityMap
46−import Control.Arrow (first)
44+import Control.Arrow ( first )
47−import Control.Monad (foldM, guard, mplus, mzero, when)
45+import Control.Monad ( foldM, guard, mzero )
48import Control.Monad.Reader ( runReader )
46import Control.Monad.Reader ( runReader )
49−import Data.Char (isAlphaNum, isSpace, toLower, toUpper)
47+import Data.Char ( toLower, toUpper)
50−import Data.List ( foldl', intersperse, isPrefixOf, isSuffixOf )
48+import Data.List ( foldl', intersperse, isPrefixOf )
51import qualified Data.Map as M
49import qualified Data.Map as M
52import Data.Maybe ( fromMaybe, isNothing )
50import Data.Maybe ( fromMaybe, isNothing )
53−import Network.HTTP (urlEncode)
51+import Network.HTTP ( urlEncode )
56-- | Parse org-mode string and return a Pandoc document.
54-- | Parse org-mode string and return a Pandoc document.
@@ -59,54 +57,6 @@ readOrg :: ReaderOptions -- ^ Reader options
59 -> Either PandocError Pandoc
57 -> Either PandocError Pandoc
60readOrg opts s = flip runReader def $ readWithM parseOrg def{ orgStateOptions = opts } (s ++ "\n\n")
58readOrg opts s = flip runReader def $ readWithM parseOrg def{ orgStateOptions = opts } (s ++ "\n\n")
63−-- Functions acting on the parser state
65−recordAnchorId :: String -> OrgParser ()
66−recordAnchorId i = updateState $ \s ->
67− s{ orgStateAnchorIds = i : (orgStateAnchorIds s) }
69−pushToInlineCharStack :: Char -> OrgParser ()
70−pushToInlineCharStack c = updateState $ \s ->
71− s{ orgStateEmphasisCharStack = c:orgStateEmphasisCharStack s }
73−popInlineCharStack :: OrgParser ()
74−popInlineCharStack = updateState $ \s ->
75− s{ orgStateEmphasisCharStack = drop 1 . orgStateEmphasisCharStack $ s }
77−surroundingEmphasisChar :: OrgParser [Char]
78−surroundingEmphasisChar =
79− take 1 . drop 1 . orgStateEmphasisCharStack <$> getState
81−startEmphasisNewlinesCounting :: Int -> OrgParser ()
82−startEmphasisNewlinesCounting maxNewlines = updateState $ \s ->
83− s{ orgStateEmphasisNewlines = Just maxNewlines }
85−decEmphasisNewlinesCount :: OrgParser ()
86−decEmphasisNewlinesCount = updateState $ \s ->
87− s{ orgStateEmphasisNewlines = (\n -> n - 1) <$> orgStateEmphasisNewlines s }
89−newlinesCountWithinLimits :: OrgParser Bool
90−newlinesCountWithinLimits = do
92− return $ ((< 0) <$> orgStateEmphasisNewlines st) /= Just True
94−resetEmphasisNewlines :: OrgParser ()
95−resetEmphasisNewlines = updateState $ \s ->
96− s{ orgStateEmphasisNewlines = Nothing }
98−addLinkFormat :: String
99− -> (String -> String)
101−addLinkFormat key formatter = updateState $ \s ->
102− let fs = orgStateLinkFormatters s
103− in s{ orgStateLinkFormatters = M.insert key formatter fs }
105−addToNotesTable :: OrgNoteRecord -> OrgParser ()
106−addToNotesTable note = do
107− oldnotes <- orgStateNotes' <$> getState
108− updateState $ \s -> s{ orgStateNotes' = note:oldnotes }
111-- Export Settings
61-- Export Settings
@@ -259,7 +209,7 @@ block = choice [ mempty <$ blanklines
259 , genericDrawer
209 , genericDrawer
260 , specialLine
210 , specialLine
262− , return <$> hline
212+ , horizontalRule
264 , latexFragment
214 , latexFragment
265 , noteBlock
215 , noteBlock
@@ -457,9 +407,6 @@ indentWith num = do
458type SwitchOption = (Char, Maybe String)
408type SwitchOption = (Char, Maybe String)
460−orgArgWord :: OrgParser String
461−orgArgWord = many1 orgArgWordChar
463-- | Parse code block arguments
410-- | Parse code block arguments
464-- TODO: We currently don't handle switches.
411-- TODO: We currently don't handle switches.
465codeHeaderArgs :: OrgParser ([String], [(String, String)])
412codeHeaderArgs :: OrgParser ([String], [(String, String)])
@@ -474,7 +421,10 @@ codeHeaderArgs = try $ do
474 , map toRundocAttrib (("language", language) : parameters)
421 , map toRundocAttrib (("language", language) : parameters)
476 else ([ pandocLang ], parameters)
423 else ([ pandocLang ], parameters)
477− where hasRundocParameters = not . null
425+ hasRundocParameters = not . null
426+ toRundocAttrib = first ("rundoc-" ++)
479switch :: OrgParser SwitchOption
429switch :: OrgParser SwitchOption
480switch = try $ simpleSwitch <|> lineNumbersSwitch
430switch = try $ simpleSwitch <|> lineNumbersSwitch
@@ -508,17 +458,6 @@ blockOption = try $ do
508 paramValue <- option "yes" orgParamValue
458 paramValue <- option "yes" orgParamValue
509 return (argKey, paramValue)
459 return (argKey, paramValue)
511−inlineBlockOption :: OrgParser (String, String)
512−inlineBlockOption = try $ do
514− paramValue <- option "yes" orgInlineParamValue
515− return (argKey, paramValue)
517−orgArgKey :: OrgParser String
519− skipSpaces *> char ':'
520− *> many1 orgArgWordChar
522orgParamValue :: OrgParser String
461orgParamValue :: OrgParser String
523orgParamValue = try $
462orgParamValue = try $
524 skipSpaces
463 skipSpaces
@@ -526,19 +465,6 @@ orgParamValue = try $
526 *> many1 (noneOf "\t\n\r ")
465 *> many1 (noneOf "\t\n\r ")
527 <* skipSpaces
466 <* skipSpaces
529−orgInlineParamValue :: OrgParser String
530−orgInlineParamValue = try $
532− *> notFollowedBy (char ':')
533− *> many1 (noneOf "\t\n\r ]")
536−orgArgWordChar :: OrgParser Char
537−orgArgWordChar = alphaNum <|> oneOf "-_"
539−toRundocAttrib :: (String, String) -> (String, String)
540−toRundocAttrib = first ("rundoc-" ++)
542commaEscaped :: String -> String
468commaEscaped :: String -> String
543commaEscaped (',':cs@('*':_)) = cs
469commaEscaped (',':cs@('*':_)) = cs
544commaEscaped (',':cs@('#':'+':_)) = cs
470commaEscaped (',':cs@('#':'+':_)) = cs
@@ -552,7 +478,10 @@ exampleCode :: String -> Blocks
552exampleCode = B.codeBlockWith ("", ["example"], [])
478exampleCode = B.codeBlockWith ("", ["example"], [])
554exampleLine :: OrgParser String
480exampleLine :: OrgParser String
555−exampleLine = try $ skipSpaces *> string ": " *> anyLine
481+exampleLine = try $ exampleLineStart *> anyLine
483+horizontalRule :: OrgParser (F Blocks)
484+horizontalRule = return B.horizontalRule <$ try hline
@@ -582,11 +511,6 @@ genericDrawer = try $ do
582 drawerDiv :: String -> F Blocks -> F Blocks
511 drawerDiv :: String -> F Blocks -> F Blocks
583 drawerDiv drawerName = fmap $ B.divWith (mempty, [drawerName, "drawer"], mempty)
512 drawerDiv drawerName = fmap $ B.divWith (mempty, [drawerName, "drawer"], mempty)
585−drawerStart :: OrgParser String
587− skipSpaces *> drawerName <* skipSpaces <* newline
588− where drawerName = char ':' *> manyTill nonspaceChar (char ':')
590drawerLine :: OrgParser String
514drawerLine :: OrgParser String
591drawerLine = anyLine
515drawerLine = anyLine
@@ -639,31 +563,38 @@ figure = try $ do
639 let attr = (mempty, mempty, figKeyVals)
563 let attr = (mempty, mempty, figKeyVals)
640 return $ (B.para . B.imageWith attr src (withFigPrefix figName) <$> figCaption)
564 return $ (B.para . B.imageWith attr src (withFigPrefix figName) <$> figCaption)
566+ withFigPrefix :: String -> String
642 withFigPrefix cs =
567 withFigPrefix cs =
643 if "fig:" `isPrefixOf` cs
568 if "fig:" `isPrefixOf` cs
645 else "fig:" ++ cs
570 else "fig:" ++ cs
572+ selfTarget :: OrgParser String
573+ selfTarget = try $ char '[' *> linkTarget <* char ']'
648-- Comments, Options and Metadata
577-- Comments, Options and Metadata
580+addLinkFormat :: String
581+ -> (String -> String)
583+addLinkFormat key formatter = updateState $ \s ->
584+ let fs = orgStateLinkFormatters s
585+ in s{ orgStateLinkFormatters = M.insert key formatter fs }
650specialLine :: OrgParser (F Blocks)
587specialLine :: OrgParser (F Blocks)
651specialLine = fmap return . try $ metaLine <|> commentLine
588specialLine = fmap return . try $ metaLine <|> commentLine
653−metaLine :: OrgParser Blocks
654−metaLine = mempty <$ metaLineStart <* (optionLine <|> declarationLine)
656-- The order, in which blocks are tried, makes sure that we're not looking at
590-- The order, in which blocks are tried, makes sure that we're not looking at
657-- the beginning of a block, so we don't need to check for it
591-- the beginning of a block, so we don't need to check for it
658−metaLineStart :: OrgParser ()
592+metaLine :: OrgParser Blocks
659−metaLineStart = try $ skipSpaces <* string "#+"
593+metaLine = mempty <$ metaLineStart <* (optionLine <|> declarationLine)
661commentLine :: OrgParser Blocks
595commentLine :: OrgParser Blocks
662commentLine = commentLineStart *> anyLine *> pure mempty
596commentLine = commentLineStart *> anyLine *> pure mempty
664−commentLineStart :: OrgParser ()
665−commentLineStart = try $ skipSpaces <* string "# "
667declarationLine :: OrgParser ()
598declarationLine :: OrgParser ()
668declarationLine = try $ do
599declarationLine = try $ do
669 key <- metaKey
600 key <- metaKey
@@ -741,23 +672,6 @@ header = try $ do
741 *> many1 tag
672 *> many1 tag
742 <* skipSpaces
673 <* skipSpaces
744−headerStart :: OrgParser Int
746− (length <$> many1 (char '*')) <* many1 (char ' ') <* updateLastPreCharPos
749−-- Don't use (or need) the reader wrapper here, we want hline to be
750−-- @show@able. Otherwise we can't use it with @notFollowedBy'@.
752−-- | Horizontal Line (five -- dashes or more)
753−hline :: OrgParser Blocks
760− return B.horizontalRule
@@ -793,9 +707,6 @@ orgToPandocTable :: OrgTable
793orgToPandocTable (OrgTable aligns heads lns) caption =
707orgToPandocTable (OrgTable aligns heads lns) caption =
794 B.table caption (zip aligns $ repeat 0) heads lns
708 B.table caption (zip aligns $ repeat 0) heads lns
796−tableStart :: OrgParser Char
797−tableStart = try $ skipSpaces *> char '|'
799tableRows :: OrgParser [OrgTableRow]
710tableRows :: OrgParser [OrgTableRow]
800tableRows = try $ many (tableAlignRow <|> tableHline <|> tableContentRow)
711tableRows = try $ many (tableAlignRow <|> tableHline <|> tableContentRow)
@@ -899,25 +810,12 @@ latexFragment = try $ do
899 , "\\end{", e, "}\n"
810 , "\\end{", e, "}\n"
902−latexEnvStart :: OrgParser String
903−latexEnvStart = try $ do
904− skipSpaces *> string "\\begin{"
909latexEnd :: String -> OrgParser ()
813latexEnd :: String -> OrgParser ()
910latexEnd envName = try $
814latexEnd envName = try $
911 () <$ skipSpaces
815 () <$ skipSpaces
912 <* string ("\\end{" ++ envName ++ "}")
816 <* string ("\\end{" ++ envName ++ "}")
913 <* blankline
817 <* blankline
915−-- | Parses a LaTeX environment name.
916−latexEnvName :: OrgParser String
917−latexEnvName = try $ do
918− mappend <$> many1 alphaNum
919− <*> option "" (string "*")
923-- Footnote defintions
821-- Footnote defintions
@@ -942,7 +840,7 @@ paraOrPlain = try $ do
942 -- is directly followed by a list item, in which case the block is read as
840 -- is directly followed by a list item, in which case the block is read as
943 -- plain text.
841 -- plain text.
944 try (guard nl
842 try (guard nl
945− *> notFollowedBy (inList *> (orderedListStart <|> bulletListStart))
843+ *> notFollowedBy (inList *> (() <$ orderedListStart <|> bulletListStart))
946 *> return (B.para <$> ils))
844 *> return (B.para <$> ils))
947 <|> (return (B.plain <$> ils))
845 <|> (return (B.plain <$> ils))
@@ -971,38 +869,21 @@ orderedList :: OrgParser (F Blocks)
971orderedList = fmap B.orderedList . fmap compactify' . sequence
869orderedList = fmap B.orderedList . fmap compactify' . sequence
972 <$> many1 (listItem orderedListStart)
870 <$> many1 (listItem orderedListStart)
974−genericListStart :: OrgParser String
976−genericListStart listMarker = try $
977− (+) <$> (length <$> many spaceChar)
978− <*> (length <$> listMarker <* many1 spaceChar)
980−-- parses bullet list marker. maybe we know the indent level
981−bulletListStart :: OrgParser Int
982−bulletListStart = bulletListStart' Nothing
984bulletListStart' :: Maybe Int -> OrgParser Int
872bulletListStart' :: Maybe Int -> OrgParser Int
985-- returns length of bulletList prefix, inclusive of marker
873-- returns length of bulletList prefix, inclusive of marker
986bulletListStart' Nothing = do ind <- length <$> many spaceChar
874bulletListStart' Nothing = do ind <- length <$> many spaceChar
987− when (ind == 0) $ notFollowedBy (char '*')
875+ oneOf (bullets $ ind == 0)
988− oneOf bullets
876+ skipSpaces1
990 return (ind + 1)
877 return (ind + 1)
991− -- Unindented lists are legal, but they can't use '*' bullets
992− -- We return n to maintain compatibility with the generic listItem
993bulletListStart' (Just n) = do count (n-1) spaceChar
878bulletListStart' (Just n) = do count (n-1) spaceChar
994− when (n == 1) $ notFollowedBy (char '*')
879+ oneOf (bullets $ n == 1)
996 many1 spaceChar
880 many1 spaceChar
883+-- Unindented lists are legal, but they can't use '*' bullets.
884+-- We return n to maintain compatibility with the generic listItem.
999−bullets :: String
885+bullets :: Bool -> String
1000−bullets = "*+-"
886+bullets unindented = if unindented then "+-" else "*+-"
1002−orderedListStart :: OrgParser Int
1003−orderedListStart = genericListStart orderedListMarker
1004− -- Ordered list markers allowed in org-mode
1005− where orderedListMarker = mappend <$> many1 digit <*> (pure <$> oneOf ".)")
1007definitionListItem :: OrgParser Int
888definitionListItem :: OrgParser Int
1008 -> OrgParser (F (Inlines, [Blocks]))
889 -> OrgParser (F (Inlines, [Blocks]))
@@ -1040,602 +921,6 @@ listContinuation markerLength = try $
1040 <*> many blankline)
921 <*> many blankline)
1041 where listLine = try $ indentWith markerLength *> anyLineNewline
922 where listLine = try $ indentWith markerLength *> anyLineNewline
924+-- | Parse any line, include the final newline in the output.
1043anyLineNewline :: OrgParser String
925anyLineNewline :: OrgParser String
1044anyLineNewline = (++ "\n") <$> anyLine
926anyLineNewline = (++ "\n") <$> anyLine
1051−inline :: OrgParser (F Inlines)
1053− choice [ whitespace
1075− ] <* (guard =<< newlinesCountWithinLimits)
1078−parseInlines :: OrgParser (F Inlines)
1079−parseInlines = trimInlinesF . mconcat <$> many1 inline
1081−-- treat these as potentially non-text when parsing inline:
1082−specialChars :: [Char]
1083−specialChars = "\"$'()*+-,./:<=>[\\]^_{|}~"
1086−whitespace :: OrgParser (F Inlines)
1087−whitespace = pure B.space <$ skipMany1 spaceChar
1088− <* updateLastPreCharPos
1089− <* updateLastForbiddenCharPos
1092−linebreak :: OrgParser (F Inlines)
1093−linebreak = try $ pure B.linebreak <$ string "\\\\" <* skipSpaces <* newline
1095−str :: OrgParser (F Inlines)
1096−str = return . B.str <$> many1 (noneOf $ specialChars ++ "\n\r ")
1097− <* updateLastStrPos
1099−-- | An endline character that can be treated as a space, not a structural
1100−-- break. This should reflect the values of the Emacs variable
1101−-- @org-element-pagaraph-separate@.
1102−endline :: OrgParser (F Inlines)
1105− notFollowedBy blankline
1106− notFollowedBy' exampleLine
1107− notFollowedBy' hline
1108− notFollowedBy' noteMarker
1109− notFollowedBy' tableStart
1110− notFollowedBy' drawerStart
1111− notFollowedBy' headerStart
1112− notFollowedBy' metaLineStart
1113− notFollowedBy' latexEnvStart
1114− notFollowedBy' commentLineStart
1115− notFollowedBy' bulletListStart
1116− notFollowedBy' orderedListStart
1117− decEmphasisNewlinesCount
1118− guard =<< newlinesCountWithinLimits
1119− updateLastPreCharPos
1120− return . return $ B.softbreak
1122−cite :: OrgParser (F Inlines)
1124− guardEnabled Ext_citations
1125− (cs, raw) <- withRaw normalCite
1126− return $ (flip B.cite (B.text raw)) <$> cs
1128−normalCite :: OrgParser (F [Citation])
1129−normalCite = try $ char '['
1135−citeList :: OrgParser (F [Citation])
1136−citeList = sequence <$> sepBy1 citation (try $ char ';' *> skipSpaces)
1138−citation :: OrgParser (F Citation)
1141− (suppress_author, key) <- citeKey
1146− return $ Citation{ citationId = key
1147− , citationPrefix = B.toList x
1148− , citationSuffix = B.toList y
1149− , citationMode = if suppress_author
1150− then SuppressAuthor
1151− else NormalCitation
1152− , citationNoteNum = 0
1156− prefix = trimInlinesF . mconcat <$>
1157− manyTill inline (char ']' <|> (']' <$ lookAhead citeKey))
1159− hasSpace <- option False (notFollowedBy nonspaceChar >> return True)
1161− rest <- trimInlinesF . mconcat <$>
1162− many (notFollowedBy (oneOf ";]") *> inline)
1163− return $ if hasSpace
1164− then (B.space <>) <$> rest
1167−footnote :: OrgParser (F Inlines)
1168−footnote = try $ inlineNote <|> referencedNote
1170−inlineNote :: OrgParser (F Inlines)
1171−inlineNote = try $ do
1173− ref <- many alphaNum
1175− note <- fmap B.para . trimInlinesF . mconcat <$> many1Till inline (char ']')
1176− when (not $ null ref) $
1177− addToNotesTable ("fn:" ++ ref, note)
1178− return $ B.note <$> note
1180−referencedNote :: OrgParser (F Inlines)
1181−referencedNote = try $ do
1184− notes <- asksF orgStateNotes'
1185− case lookup ref notes of
1186− Nothing -> return $ B.str $ "[" ++ ref ++ "]"
1187− Just contents -> do
1189− let contents' = runF contents st{ orgStateNotes' = [] }
1190− return $ B.note contents'
1192−noteMarker :: OrgParser String
1193−noteMarker = try $ do
1195− choice [ many1Till digit (char ']')
1196− , (++) <$> string "fn:"
1197− <*> many1Till (noneOf "\n\r\t ") (char ']')
1200−linkOrImage :: OrgParser (F Inlines)
1201−linkOrImage = explicitOrImageLink
1202− <|> selflinkOrImage
1205− <?> "link or image"
1207−explicitOrImageLink :: OrgParser (F Inlines)
1208−explicitOrImageLink = try $ do
1210− srcF <- applyCustomLinkFormat =<< possiblyEmptyLinkTarget
1211− title <- enclosedRaw (char '[') (char ']')
1212− title' <- parseFromString (mconcat <$> many inline) title
1216− if isImageFilename title
1217− then pure $ B.link src "" $ B.image title mempty mempty
1218− else linkToInlinesF src =<< title'
1220−selflinkOrImage :: OrgParser (F Inlines)
1221−selflinkOrImage = try $ do
1222− src <- char '[' *> linkTarget <* char ']'
1223− return $ linkToInlinesF src (B.str src)
1225−plainLink :: OrgParser (F Inlines)
1226−plainLink = try $ do
1228− returnF $ B.link src "" (B.str orig)
1230−angleLink :: OrgParser (F Inlines)
1231−angleLink = try $ do
1237−selfTarget :: OrgParser String
1238−selfTarget = try $ char '[' *> linkTarget <* char ']'
1240−linkTarget :: OrgParser String
1241−linkTarget = enclosedByPair '[' ']' (noneOf "\n\r[]")
1243−possiblyEmptyLinkTarget :: OrgParser String
1244−possiblyEmptyLinkTarget = try linkTarget <|> ("" <$ string "[]")
1246−applyCustomLinkFormat :: String -> OrgParser (F String)
1247−applyCustomLinkFormat link = do
1248− let (linkType, rest) = break (== ':') link
1250− formatter <- M.lookup linkType <$> asksF orgStateLinkFormatters
1251− return $ maybe link ($ drop 1 rest) formatter
1253−-- | Take a link and return a function which produces new inlines when given
1254−-- description inlines.
1255−linkToInlinesF :: String -> Inlines -> F Inlines
1256−linkToInlinesF linkStr =
1258− "" -> pure . B.link mempty "" -- wiki link (empty by convention)
1259− ('#':_) -> pure . B.link linkStr "" -- document-local fraction
1260− _ -> case cleanLinkString linkStr of
1261− (Just cleanedLink) -> if isImageFilename cleanedLink
1262− then const . pure $ B.image cleanedLink "" ""
1263− else pure . B.link cleanedLink ""
1264− Nothing -> internalLink linkStr -- other internal link
1266−-- | Cleanup and canonicalize a string describing a link. Return @Nothing@ if
1267−-- the string does not appear to be a link.
1268−cleanLinkString :: String -> Maybe String
1271− '/':_ -> Just $ "file://" ++ s -- absolute path
1272− '.':'/':_ -> Just s -- relative path
1273− '.':'.':'/':_ -> Just s -- relative path
1274− -- Relative path or URL (file schema)
1275− 'f':'i':'l':'e':':':s' -> Just $ if ("//" `isPrefixOf` s') then s else s'
1276− _ | isUrl s -> Just s -- URL
1279− isUrl :: String -> Bool
1281− let (scheme, path) = break (== ':') cs
1282− in all (\c -> isAlphaNum c || c `elem` (".-"::String)) scheme
1285−isImageFilename :: String -> Bool
1286−isImageFilename filename =
1287− any (\x -> ('.':x) `isSuffixOf` filename) imageExtensions &&
1288− (any (\x -> (x++":") `isPrefixOf` filename) protocols ||
1289− ':' `notElem` filename)
1291− imageExtensions = [ "jpeg" , "jpg" , "png" , "gif" , "svg" ]
1292− protocols = [ "file", "http", "https" ]
1294−internalLink :: String -> Inlines -> F Inlines
1295−internalLink link title = do
1296− anchorB <- (link `elem`) <$> asksF orgStateAnchorIds
1298− then return $ B.link ('#':link) "" title
1299− else return $ B.emph title
1301−-- | Parse an anchor like @<<anchor-id>>@ and return an empty span with
1302−-- @anchor-id@ set as id. Legal anchors in org-mode are defined through
1303−-- @org-target-regexp@, which is fairly liberal. Since no link is created if
1304−-- @anchor-id@ contains spaces, we are more restrictive in what is accepted as
1307−anchor :: OrgParser (F Inlines)
1309− anchorId <- parseAnchor
1310− recordAnchorId anchorId
1311− returnF $ B.spanWith (solidify anchorId, [], []) mempty
1313− parseAnchor = string "<<"
1314− *> many1 (noneOf "\t\n\r<>\"' ")
1318−-- | Replace every char but [a-zA-Z0-9_.-:] with a hypen '-'. This mirrors
1319−-- the org function @org-export-solidify-link-text@.
1321−solidify :: String -> String
1322−solidify = map replaceSpecialChar
1323− where replaceSpecialChar c
1325− | c `elem` ("_.-:" :: String) = c
1328−-- | Parses an inline code block and marks it as an babel block.
1329−inlineCodeBlock :: OrgParser (F Inlines)
1330−inlineCodeBlock = try $ do
1332− lang <- many1 orgArgWordChar
1333− opts <- option [] $ enclosedByPair '[' ']' inlineBlockOption
1334− inlineCode <- enclosedByPair '{' '}' (noneOf "\n\r")
1335− let attrClasses = [translateLang lang, rundocBlockClass]
1336− let attrKeyVal = map toRundocAttrib (("language", lang) : opts)
1337− returnF $ B.codeWith ("", attrClasses, attrKeyVal) inlineCode
1339−enclosedByPair :: Char -- ^ opening char
1340− -> Char -- ^ closing char
1341− -> OrgParser a -- ^ parser
1343−enclosedByPair s e p = char s *> many1Till p (char e)
1345−emph :: OrgParser (F Inlines)
1346−emph = fmap B.emph <$> emphasisBetween '/'
1348−strong :: OrgParser (F Inlines)
1349−strong = fmap B.strong <$> emphasisBetween '*'
1351−strikeout :: OrgParser (F Inlines)
1352−strikeout = fmap B.strikeout <$> emphasisBetween '+'
1354−-- There is no underline, so we use strong instead.
1355−underline :: OrgParser (F Inlines)
1356−underline = fmap B.strong <$> emphasisBetween '_'
1358−verbatim :: OrgParser (F Inlines)
1359−verbatim = return . B.code <$> verbatimBetween '='
1361−code :: OrgParser (F Inlines)
1362−code = return . B.code <$> verbatimBetween '~'
1364−subscript :: OrgParser (F Inlines)
1365−subscript = fmap B.subscript <$> try (char '_' *> subOrSuperExpr)
1367−superscript :: OrgParser (F Inlines)
1368−superscript = fmap B.superscript <$> try (char '^' *> subOrSuperExpr)
1370−math :: OrgParser (F Inlines)
1371−math = return . B.math <$> choice [ math1CharBetween '$'
1372− , mathStringBetween '$'
1373− , rawMathBetween "\\(" "\\)"
1376−displayMath :: OrgParser (F Inlines)
1377−displayMath = return . B.displayMath <$> choice [ rawMathBetween "\\[" "\\]"
1378− , rawMathBetween "$$" "$$"
1381−updatePositions :: Char
1382− -> OrgParser (Char)
1383−updatePositions c = do
1384− when (c `elem` emphasisPreChars) updateLastPreCharPos
1385− when (c `elem` emphasisForbiddenBorderChars) updateLastForbiddenCharPos
1388−symbol :: OrgParser (F Inlines)
1389−symbol = return . B.str . (: "") <$> (oneOf specialChars >>= updatePositions)
1391−emphasisBetween :: Char
1392− -> OrgParser (F Inlines)
1393−emphasisBetween c = try $ do
1394− startEmphasisNewlinesCounting emphasisAllowedNewlines
1395− res <- enclosedInlines (emphasisStart c) (emphasisEnd c)
1396− isTopLevelEmphasis <- null . orgStateEmphasisCharStack <$> getState
1397− when isTopLevelEmphasis
1398− resetEmphasisNewlines
1401−verbatimBetween :: Char
1402− -> OrgParser String
1403−verbatimBetween c = try $
1405− many1TillNOrLessNewlines 1 (noneOf "\n\r") (emphasisEnd c)
1407−-- | Parses a raw string delimited by @c@ using Org's math rules
1408−mathStringBetween :: Char
1409− -> OrgParser String
1410−mathStringBetween c = try $ do
1412− body <- many1TillNOrLessNewlines mathAllowedNewlines
1413− (noneOf (c:"\n\r"))
1414− (lookAhead $ mathEnd c)
1416− return $ body ++ [final]
1418−-- | Parse a single character between @c@ using math rules
1419−math1CharBetween :: Char
1420− -> OrgParser String
1421−math1CharBetween c = try $ do
1423− res <- noneOf $ c:mathForbiddenBorderChars
1425− eof <|> () <$ lookAhead (oneOf mathPostChars)
1428−rawMathBetween :: String
1430− -> OrgParser String
1431−rawMathBetween s e = try $ string s *> manyTill anyChar (try $ string e)
1433−-- | Parses the start (opening character) of emphasis
1434−emphasisStart :: Char -> OrgParser Char
1435−emphasisStart c = try $ do
1436− guard =<< afterEmphasisPreChar
1437− guard =<< notAfterString
1439− lookAhead (noneOf emphasisForbiddenBorderChars)
1440− pushToInlineCharStack c
1443−-- | Parses the closing character of emphasis
1444−emphasisEnd :: Char -> OrgParser Char
1445−emphasisEnd c = try $ do
1446− guard =<< notAfterForbiddenBorderChar
1448− eof <|> () <$ lookAhead acceptablePostChars
1452− where acceptablePostChars =
1453− surroundingEmphasisChar >>= \x -> oneOf (x ++ emphasisPostChars)
1455−mathStart :: Char -> OrgParser Char
1457− char c <* notFollowedBy' (oneOf (c:mathForbiddenBorderChars))
1459−mathEnd :: Char -> OrgParser Char
1460−mathEnd c = try $ do
1461− res <- noneOf (c:mathForbiddenBorderChars)
1463− eof <|> () <$ lookAhead (oneOf mathPostChars)
1467−enclosedInlines :: OrgParser a
1469− -> OrgParser (F Inlines)
1470−enclosedInlines start end = try $
1471− trimInlinesF . mconcat <$> enclosed start end inline
1473−enclosedRaw :: OrgParser a
1475− -> OrgParser String
1476−enclosedRaw start end = try $
1477− start *> (onSingleLine <|> spanningTwoLines)
1478− where onSingleLine = try $ many1Till (noneOf "\n\r") end
1479− spanningTwoLines = try $
1480− anyLine >>= \f -> mappend (f <> " ") <$> onSingleLine
1482−-- | Like many1Till, but parses at most @n+1@ lines. @p@ must not consume
1484−many1TillNOrLessNewlines :: Int
1487− -> OrgParser String
1488−many1TillNOrLessNewlines n p end = try $
1489− nMoreLines (Just n) mempty >>= oneOrMore
1491− nMoreLines Nothing cs = return cs
1492− nMoreLines (Just 0) cs = try $ (cs ++) <$> finalLine
1493− nMoreLines k cs = try $ (final k cs <|> rest k cs)
1494− >>= uncurry nMoreLines
1495− final _ cs = (\x -> (Nothing, cs ++ x)) <$> try finalLine
1496− rest m cs = (\x -> (minus1 <$> m, cs ++ x ++ "\n")) <$> try (manyTill p newline)
1497− finalLine = try $ manyTill p end
1499− oneOrMore cs = guard (not $ null cs) *> return cs
1501−-- Org allows customization of the way it reads emphasis. We use the defaults
1502−-- here (see, e.g., the Emacs Lisp variable `org-emphasis-regexp-components`
1505−-- | Chars allowed to occur before emphasis (spaces and newlines are ok, too)
1506−emphasisPreChars :: [Char]
1507−emphasisPreChars = "\t \"'({"
1509−-- | Chars allowed at after emphasis
1510−emphasisPostChars :: [Char]
1511−emphasisPostChars = "\t\n !\"'),-.:;?\\}"
1513−-- | Chars not allowed at the (inner) border of emphasis
1514−emphasisForbiddenBorderChars :: [Char]
1515−emphasisForbiddenBorderChars = "\t\n\r \"',"
1517−-- | The maximum number of newlines within
1518−emphasisAllowedNewlines :: Int
1519−emphasisAllowedNewlines = 1
1521−-- LaTeX-style math: see `org-latex-regexps` for details
1523−-- | Chars allowed after an inline ($...$) math statement
1524−mathPostChars :: [Char]
1525−mathPostChars = "\t\n \"'),-.:;?"
1527−-- | Chars not allowed at the (inner) border of math
1528−mathForbiddenBorderChars :: [Char]
1529−mathForbiddenBorderChars = "\t\n\r ,;.$"
1531−-- | Maximum number of newlines in an inline math statement
1532−mathAllowedNewlines :: Int
1533−mathAllowedNewlines = 2
1535−-- | Whether we are right behind a char allowed before emphasis
1536−afterEmphasisPreChar :: OrgParser Bool
1537−afterEmphasisPreChar = do
1539− lastPrePos <- orgStateLastPreCharPos <$> getState
1540− return . fromMaybe True $ (== pos) <$> lastPrePos
1542−-- | Whether the parser is right after a forbidden border char
1543−notAfterForbiddenBorderChar :: OrgParser Bool
1544−notAfterForbiddenBorderChar = do
1546− lastFBCPos <- orgStateLastForbiddenCharPos <$> getState
1547− return $ lastFBCPos /= Just pos
1549−-- | Read a sub- or superscript expression
1550−subOrSuperExpr :: OrgParser (F Inlines)
1551−subOrSuperExpr = try $
1552− choice [ id <$> charsInBalanced '{' '}' (noneOf "\n\r")
1553− , enclosing ('(', ')') <$> charsInBalanced '(' ')' (noneOf "\n\r")
1554− , simpleSubOrSuperString
1555− ] >>= parseFromString (mconcat <$> many inline)
1556− where enclosing (left, right) s = left : s ++ [right]
1558−simpleSubOrSuperString :: OrgParser String
1559−simpleSubOrSuperString = try $ do
1561− guard . exportSubSuperscripts . orgStateExportSettings $ state
1562− choice [ string "*"
1563− , mappend <$> option [] ((:[]) <$> oneOf "+-")
1567−inlineLaTeX :: OrgParser (F Inlines)
1568−inlineLaTeX = try $ do
1569− cmd <- inlineLaTeXCommand
1570− maybe mzero returnF $
1571− parseAsMath cmd `mplus` parseAsMathMLSym cmd `mplus` parseAsInlineLaTeX cmd
1573− parseAsMath :: String -> Maybe Inlines
1574− parseAsMath cs = B.fromList <$> texMathToPandoc cs
1576− parseAsInlineLaTeX :: String -> Maybe Inlines
1577− parseAsInlineLaTeX cs = maybeRight $ runParser inlineCommand state "" cs
1579− parseAsMathMLSym :: String -> Maybe Inlines
1580− parseAsMathMLSym cs = B.str <$> MathMLEntityMap.getUnicode (clean cs)
1581− -- drop initial backslash and any trailing "{}"
1582− where clean = dropWhileEnd (`elem` ("{}" :: String)) . drop 1
1584− state :: ParserState
1585− state = def{ stateOptions = def{ readerParseRaw = True }}
1587− texMathToPandoc :: String -> Maybe [Inline]
1588− texMathToPandoc cs = (maybeRight $ readTeX cs) >>= writePandoc DisplayInline
1590−maybeRight :: Either a b -> Maybe b
1591−maybeRight = either (const Nothing) Just
1593−inlineLaTeXCommand :: OrgParser String
1594−inlineLaTeXCommand = try $ do
1596− case runParser rawLaTeXInline def "source" rest of
1597− Right (RawInline _ cs) -> do
1598− -- drop any trailing whitespace, those are not be part of the command as
1599− -- far as org mode is concerned.
1600− let cmdNoSpc = dropWhileEnd isSpace cs
1601− let len = length cmdNoSpc
1606−-- Taken from Data.OldList.
1607−dropWhileEnd :: (a -> Bool) -> [a] -> [a]
1608−dropWhileEnd p = foldr (\x xs -> if p x && null xs then [] else x : xs) []
1610−smart :: OrgParser (F Inlines)
1612− getOption readerSmart >>= guard
1613− doubleQuoted <|> singleQuoted <|>
1614− choice (map (return <$>) [orgApostrophe, orgDash, orgEllipses])
1616− orgDash = dash <* updatePositions '-'
1617− orgEllipses = ellipses <* updatePositions '.'
1619− (char '\'' <|> char '\8217') <* updateLastPreCharPos
1620− <* updateLastForbiddenCharPos
1621− *> return (B.str "\x2019")
1623−singleQuoted :: OrgParser (F Inlines)
1624−singleQuoted = try $ do
1626− updatePositions '\''
1627− withQuoteContext InSingleQuote $
1628− fmap B.singleQuoted . trimInlinesF . mconcat <$>
1629− many1Till inline (singleQuoteEnd <* updatePositions '\'')
1631−-- doubleQuoted will handle regular double-quoted sections, as well
1632−-- as dialogues with an open double-quote without a close double-quote
1633−-- in the same paragraph.
1634−doubleQuoted :: OrgParser (F Inlines)
1635−doubleQuoted = try $ do
1637− updatePositions '"'
1638− contents <- mconcat <$> many (try $ notFollowedBy doubleQuoteEnd >> inline)
1639− (withQuoteContext InDoubleQuote $ (doubleQuoteEnd <* updateLastForbiddenCharPos) >> return
1640− (fmap B.doubleQuoted . trimInlinesF $ contents))
1641− <|> (return $ return (B.str "\8220") <> contents)