git.iko.soy/typeable/pandoc
on master help sign in
Split up T.P.Writers.Markdown...

with T.P.Writers.Markdown.Types and T.P.Writers.Markdown.Inline. The module was difficult to compile on low-memory system.s

commit60d300daaa79cb6a3365ec9a21abf80a6f7c0446 authorJohn MacFarlane <jgm@berkeley.edu> dateTue 2 Mar 2021, 21:06 (6 years ago) parent11ec5b5
4 files changed+690−591
pandoc.cabal+2 src/Text/Pandoc/Writers/Markdown.hs+6−591 src/Text/Pandoc/Writers/Markdown/Inline.hs+601 src/Text/Pandoc/Writers/Markdown/Types.hs+81
pandoc.cabal+2
@@ -665,6 +665,8 @@ library
665665 Text.Pandoc.Writers.LaTeX.Notes,
666666 Text.Pandoc.Writers.LaTeX.Table,
667667 Text.Pandoc.Writers.LaTeX.Types,
668+ Text.Pandoc.Writers.Markdown.Types,
669+ Text.Pandoc.Writers.Markdown.Inline,
668670 Text.Pandoc.Writers.Roff,
669671 Text.Pandoc.Writers.Powerpoint.Presentation,
670672 Text.Pandoc.Writers.Powerpoint.Output,
src/Text/Pandoc/Writers/Markdown.hs+6−591
@@ -22,15 +22,13 @@ module Text.Pandoc.Writers.Markdown (
2222 writePlain) where
2323import Control.Monad.Reader
2424import Control.Monad.State.Strict
25−import Data.Char (isAlphaNum, isDigit)
2625import Data.Default
27−import Data.List (find, intersperse, sortOn, transpose)
26+import Data.List (intersperse, sortOn, transpose)
2827import qualified Data.Map as M
2928import Data.Maybe (fromMaybe, mapMaybe)
3029import qualified Data.Set as Set
3130import Data.Text (Text)
3231import qualified Data.Text as T
33−import Network.HTTP (urlEncode)
3432import Text.HTML.TagSoup (Tag (..), isTagText, parseTags)
3533import Text.Pandoc.Class.PandocMonad (PandocMonad, report)
3634import Text.Pandoc.Definition
@@ -44,59 +42,11 @@ import Text.Pandoc.Templates (renderTemplate)
4442import Text.DocTemplates (Val(..), Context(..), FromContext(..))
4543import Text.Pandoc.Walk
4644import Text.Pandoc.Writers.HTML (writeHtml5String)
47−import Text.Pandoc.Writers.Math (texMathToInlines)
48−import Text.Pandoc.XML (toHtml5Entities)
49−import Data.Coerce (coerce)
50−
51−type Notes = [[Block]]
52−type Ref = (Text, Target, Attr)
53−type Refs = [Ref]
54−
55−type MD m = ReaderT WriterEnv (StateT WriterState m)
56−
57−evalMD :: PandocMonad m => MD m a -> WriterEnv -> WriterState -> m a
58−evalMD md env st = evalStateT (runReaderT md env) st
59−
60−data WriterEnv = WriterEnv { envInList :: Bool
61− , envVariant :: MarkdownVariant
62− , envRefShortcutable :: Bool
63− , envBlockLevel :: Int
64− , envEscapeSpaces :: Bool
65− }
66−
67−data MarkdownVariant =
68− PlainText
69− | Commonmark
70− | Markdown
71− deriving (Show, Eq)
72−
73−instance Default WriterEnv
74− where def = WriterEnv { envInList = False
75− , envVariant = Markdown
76− , envRefShortcutable = True
77− , envBlockLevel = 0
78− , envEscapeSpaces = False
79− }
80−
81−data WriterState = WriterState { stNotes :: Notes
82− , stPrevRefs :: Refs
83− , stRefs :: Refs
84− , stKeys :: M.Map Key
85− (M.Map (Target, Attr) Int)
86− , stLastIdx :: Int
87− , stIds :: Set.Set Text
88− , stNoteNum :: Int
89− }
90−
91−instance Default WriterState
92− where def = WriterState{ stNotes = []
93− , stPrevRefs = []
94− , stRefs = []
95− , stKeys = M.empty
96− , stLastIdx = 0
97− , stIds = Set.empty
98− , stNoteNum = 1
99− }
45+import Text.Pandoc.Writers.Markdown.Inline (inlineListToMarkdown)
46+import Text.Pandoc.Writers.Markdown.Types (MarkdownVariant(..),
47+ WriterState(..),
48+ WriterEnv(..),
49+ Ref, Refs, MD, evalMD)
10050
10151-- | Convert Pandoc to Markdown.
10252writeMarkdown :: PandocMonad m => WriterOptions -> Pandoc -> m Text
@@ -297,49 +247,6 @@ noteToMarkdown opts num blocks = do
297247 then hang (writerTabStop opts) (marker <> spacer) contents
298248 else marker <> spacer <> contents
299249
300−-- | Escape special characters for Markdown.
301−escapeText :: WriterOptions -> Text -> Text
302−escapeText opts = T.pack . go . T.unpack
303− where
304− go [] = []
305− go (c:cs) =
306− case c of
307− '<' | isEnabled Ext_all_symbols_escapable opts ->
308− '\\' : '<' : go cs
309− | otherwise -> "&lt;" ++ go cs
310− '>' | isEnabled Ext_all_symbols_escapable opts ->
311− '\\' : '>' : go cs
312− | otherwise -> "&gt;" ++ go cs
313− '@' | isEnabled Ext_citations opts ->
314− case cs of
315− (d:_)
316− | isAlphaNum d || d == '_'
317− -> '\\':'@':go cs
318− _ -> '@':go cs
319− _ | c `elem` ['\\','`','*','_','[',']','#'] ->
320− '\\':c:go cs
321− '|' | isEnabled Ext_pipe_tables opts -> '\\':'|':go cs
322− '^' | isEnabled Ext_superscript opts -> '\\':'^':go cs
323− '~' | isEnabled Ext_subscript opts ||
324− isEnabled Ext_strikeout opts -> '\\':'~':go cs
325− '$' | isEnabled Ext_tex_math_dollars opts -> '\\':'$':go cs
326− '\'' | isEnabled Ext_smart opts -> '\\':'\'':go cs
327− '"' | isEnabled Ext_smart opts -> '\\':'"':go cs
328− '-' | isEnabled Ext_smart opts ->
329− case cs of
330− '-':_ -> '\\':'-':go cs
331− _ -> '-':go cs
332− '.' | isEnabled Ext_smart opts ->
333− case cs of
334− '.':'.':rest -> '\\':'.':'.':'.':go rest
335− _ -> '.':go cs
336− _ -> case cs of
337− '_':x:xs
338− | isEnabled Ext_intraword_underscores opts
339− , isAlphaNum c
340− , isAlphaNum x -> c : '_' : x : go xs
341− _ -> c : go cs
342−
343250attrsToMarkdown :: Attr -> Doc Text
344251attrsToMarkdown attribs = braces $ hsep [attribId, attribClasses, attribKeys]
345252 where attribId = case attribs of
@@ -912,499 +819,7 @@ blockListToMarkdown opts blocks = do
912819 | otherwise = RawBlock "markdown" "&nbsp;\n"
913820 mconcat <$> mapM (blockToMarkdown opts) (fixBlocks blocks)
914821
915−getKey :: Doc Text -> Key
916−getKey = toKey . render Nothing
917−
918−findUsableIndex :: [Text] -> Int -> Int
919−findUsableIndex lbls i = if tshow i `elem` lbls
920− then findUsableIndex lbls (i + 1)
921− else i
922−
923−getNextIndex :: PandocMonad m => MD m Int
924−getNextIndex = do
925− prevRefs <- gets stPrevRefs
926− refs <- gets stRefs
927− i <- (+ 1) <$> gets stLastIdx
928− modify $ \s -> s{ stLastIdx = i }
929− let refLbls = map (\(r,_,_) -> r) $ prevRefs ++ refs
930− return $ findUsableIndex refLbls i
931−
932−-- | Get reference for target; if none exists, create unique one and return.
933−-- Prefer label if possible; otherwise, generate a unique key.
934−getReference :: PandocMonad m => Attr -> Doc Text -> Target -> MD m Text
935−getReference attr label target = do
936− refs <- gets stRefs
937− case find (\(_,t,a) -> t == target && a == attr) refs of
938− Just (ref, _, _) -> return ref
939− Nothing -> do
940− keys <- gets stKeys
941− let key = getKey label
942− let rawkey = coerce key
943− case M.lookup key keys of
944− Nothing -> do -- no other refs with this label
945− (lab', idx) <- if T.null rawkey ||
946− T.length rawkey > 999 ||
947− T.any (\c -> c == '[' || c == ']') rawkey
948− then do
949− i <- getNextIndex
950− return (tshow i, i)
951− else
952− return (render Nothing label, 0)
953− modify (\s -> s{
954− stRefs = (lab', target, attr) : refs,
955− stKeys = M.insert (getKey label)
956− (M.insert (target, attr) idx mempty)
957− (stKeys s) })
958− return lab'
959−
960− Just km -> -- we have refs with this label
961− case M.lookup (target, attr) km of
962− Just i -> do
963− let lab' = render Nothing $
964− label <> if i == 0
965− then mempty
966− else literal (tshow i)
967− -- make sure it's in stRefs; it may be
968− -- a duplicate that was printed in a previous
969− -- block:
970− when ((lab', target, attr) `notElem` refs) $
971− modify (\s -> s{
972− stRefs = (lab', target, attr) : refs })
973− return lab'
974− Nothing -> do -- but this one is to a new target
975− i <- getNextIndex
976− let lab' = tshow i
977− modify (\s -> s{
978− stRefs = (lab', target, attr) : refs,
979− stKeys = M.insert key
980− (M.insert (target, attr) i km)
981− (stKeys s) })
982− return lab'
983−
984−-- | Convert list of Pandoc inline elements to markdown.
985−inlineListToMarkdown :: PandocMonad m => WriterOptions -> [Inline] -> MD m (Doc Text)
986−inlineListToMarkdown opts lst = do
987− inlist <- asks envInList
988− go (if inlist then avoidBadWrapsInList lst else lst)
989− where go [] = return empty
990− go (x@Math{}:y@(Str t):zs)
991− | T.all isDigit (T.take 1 t) -- starts with digit -- see #7058
992− = liftM2 (<>) (inlineToMarkdown opts x)
993− (go (RawInline (Format "html") "<!-- -->" : y : zs))
994− go (i:is) = case i of
995− Link {} -> case is of
996− -- If a link is followed by another link, or '[', '(' or ':'
997− -- then we don't shortcut
998− Link {}:_ -> unshortcutable
999− Space:Link {}:_ -> unshortcutable
1000− Space:(Str(thead -> Just '[')):_ -> unshortcutable
1001− Space:(RawInline _ (thead -> Just '[')):_ -> unshortcutable
1002− Space:(Cite _ _):_ -> unshortcutable
1003− SoftBreak:Link {}:_ -> unshortcutable
1004− SoftBreak:(Str(thead -> Just '[')):_ -> unshortcutable
1005− SoftBreak:(RawInline _ (thead -> Just '[')):_ -> unshortcutable
1006− SoftBreak:(Cite _ _):_ -> unshortcutable
1007− LineBreak:Link {}:_ -> unshortcutable
1008− LineBreak:(Str(thead -> Just '[')):_ -> unshortcutable
1009− LineBreak:(RawInline _ (thead -> Just '[')):_ -> unshortcutable
1010− LineBreak:(Cite _ _):_ -> unshortcutable
1011− (Cite _ _):_ -> unshortcutable
1012− Str (thead -> Just '['):_ -> unshortcutable
1013− Str (thead -> Just '('):_ -> unshortcutable
1014− Str (thead -> Just ':'):_ -> unshortcutable
1015− (RawInline _ (thead -> Just '[')):_ -> unshortcutable
1016− (RawInline _ (thead -> Just '(')):_ -> unshortcutable
1017− (RawInline _ (thead -> Just ':')):_ -> unshortcutable
1018− (RawInline _ (T.stripPrefix " [" -> Just _ )):_ -> unshortcutable
1019− _ -> shortcutable
1020− _ -> shortcutable
1021− where shortcutable = liftM2 (<>) (inlineToMarkdown opts i) (go is)
1022− unshortcutable = do
1023− iMark <- local
1024− (\env -> env { envRefShortcutable = False })
1025− (inlineToMarkdown opts i)
1026− fmap (iMark <>) (go is)
1027− thead = fmap fst . T.uncons
1028−
1029−isSp :: Inline -> Bool
1030−isSp Space = True
1031−isSp SoftBreak = True
1032−isSp _ = False
1033−
1034−avoidBadWrapsInList :: [Inline] -> [Inline]
1035−avoidBadWrapsInList [] = []
1036−avoidBadWrapsInList (s:Str (T.uncons -> Just ('>',cs)):xs) | isSp s =
1037− Str (" >" <> cs) : avoidBadWrapsInList xs
1038−avoidBadWrapsInList [s, Str (T.uncons -> Just (c, cs))]
1039− | T.null cs && isSp s && c `elem` ['-','*','+'] = [Str $ T.pack [' ', c]]
1040−avoidBadWrapsInList (s:Str (T.uncons -> Just (c, cs)):Space:xs)
1041− | T.null cs && isSp s && c `elem` ['-','*','+'] =
1042− Str (T.pack [' ', c]) : Space : avoidBadWrapsInList xs
1043−avoidBadWrapsInList (s:Str cs:Space:xs)
1044− | isSp s && isOrderedListMarker cs =
1045− Str (" " <> cs) : Space : avoidBadWrapsInList xs
1046−avoidBadWrapsInList [s, Str cs]
1047− | isSp s && isOrderedListMarker cs = [Str $ " " <> cs]
1048−avoidBadWrapsInList (x:xs) = x : avoidBadWrapsInList xs
1049−
1050−isOrderedListMarker :: Text -> Bool
1051−isOrderedListMarker xs = not (T.null xs) && (T.last xs `elem` ['.',')']) &&
1052− isRight (runParser (anyOrderedListMarker >> eof)
1053− defaultParserState "" xs)
1054−
1055−isRight :: Either a b -> Bool
1056−isRight (Right _) = True
1057−isRight (Left _) = False
1058−
1059−-- | Convert Pandoc inline element to markdown.
1060−inlineToMarkdown :: PandocMonad m => WriterOptions -> Inline -> MD m (Doc Text)
1061−inlineToMarkdown opts (Span ("",["emoji"],kvs) [Str s]) =
1062− case lookup "data-emoji" kvs of
1063− Just emojiname | isEnabled Ext_emoji opts ->
1064− return $ ":" <> literal emojiname <> ":"
1065− _ -> inlineToMarkdown opts (Str s)
1066−inlineToMarkdown opts (Span attrs ils) = do
1067− variant <- asks envVariant
1068− contents <- inlineListToMarkdown opts ils
1069− return $ case attrs of
1070− (_,["csl-block"],_) -> (cr <>)
1071− (_,["csl-left-margin"],_) -> (cr <>)
1072− (_,["csl-indent"],_) -> (cr <>)
1073− _ -> id
1074− $ case variant of
1075− PlainText -> contents
1076− _ | attrs == nullAttr -> contents
1077− | isEnabled Ext_bracketed_spans opts ->
1078− let attrs' = if attrs /= nullAttr
1079− then attrsToMarkdown attrs
1080− else empty
1081− in "[" <> contents <> "]" <> attrs'
1082− | isEnabled Ext_raw_html opts ||
1083− isEnabled Ext_native_spans opts ->
1084− tagWithAttrs "span" attrs <> contents <> literal "</span>"
1085− | otherwise -> contents
1086−inlineToMarkdown _ (Emph []) = return empty
1087−inlineToMarkdown opts (Emph lst) = do
1088− variant <- asks envVariant
1089− contents <- inlineListToMarkdown opts lst
1090− return $ case variant of
1091− PlainText
1092− | isEnabled Ext_gutenberg opts -> "_" <> contents <> "_"
1093− | otherwise -> contents
1094− _ -> "*" <> contents <> "*"
1095−inlineToMarkdown _ (Underline []) = return empty
1096−inlineToMarkdown opts (Underline lst) = do
1097− variant <- asks envVariant
1098− contents <- inlineListToMarkdown opts lst
1099− case variant of
1100− PlainText -> return contents
1101− _ | isEnabled Ext_bracketed_spans opts ->
1102− return $ "[" <> contents <> "]" <> "{.ul}"
1103− | isEnabled Ext_native_spans opts ->
1104− return $ tagWithAttrs "span" ("", ["underline"], [])
1105− <> contents
1106− <> literal "</span>"
1107− | isEnabled Ext_raw_html opts ->
1108− return $ "<u>" <> contents <> "</u>"
1109− | otherwise -> inlineToMarkdown opts (Emph lst)
1110−inlineToMarkdown _ (Strong []) = return empty
1111−inlineToMarkdown opts (Strong lst) = do
1112− variant <- asks envVariant
1113− case variant of
1114− PlainText ->
1115− inlineListToMarkdown opts $
1116− if isEnabled Ext_gutenberg opts
1117− then capitalize lst
1118− else lst
1119− _ -> do
1120− contents <- inlineListToMarkdown opts lst
1121− return $ "**" <> contents <> "**"
1122−inlineToMarkdown _ (Strikeout []) = return empty
1123−inlineToMarkdown opts (Strikeout lst) = do
1124− contents <- inlineListToMarkdown opts lst
1125− return $ if isEnabled Ext_strikeout opts
1126− then "~~" <> contents <> "~~"
1127− else if isEnabled Ext_raw_html opts
1128− then "<s>" <> contents <> "</s>"
1129− else contents
1130−inlineToMarkdown _ (Superscript []) = return empty
1131−inlineToMarkdown opts (Superscript lst) =
1132− local (\env -> env {envEscapeSpaces = envVariant env == Markdown}) $ do
1133− contents <- inlineListToMarkdown opts lst
1134− if isEnabled Ext_superscript opts
1135− then return $ "^" <> contents <> "^"
1136− else if isEnabled Ext_raw_html opts
1137− then return $ "<sup>" <> contents <> "</sup>"
1138− else
1139− case traverse toSuperscriptInline lst of
1140− Just xs' | not (writerPreferAscii opts)
1141− -> inlineListToMarkdown opts xs'
1142− _ -> do
1143− let rendered = render Nothing contents
1144− return $
1145− case mapM toSuperscript (T.unpack rendered) of
1146− Just r -> literal $ T.pack r
1147− Nothing -> literal $ "^(" <> rendered <> ")"
1148−inlineToMarkdown _ (Subscript []) = return empty
1149−inlineToMarkdown opts (Subscript lst) =
1150− local (\env -> env {envEscapeSpaces = envVariant env == Markdown}) $ do
1151− contents <- inlineListToMarkdown opts lst
1152− if isEnabled Ext_subscript opts
1153− then return $ "~" <> contents <> "~"
1154− else if isEnabled Ext_raw_html opts
1155− then return $ "<sub>" <> contents <> "</sub>"
1156− else
1157− case traverse toSubscriptInline lst of
1158− Just xs' | not (writerPreferAscii opts)
1159− -> inlineListToMarkdown opts xs'
1160− _ -> do
1161− let rendered = render Nothing contents
1162− return $
1163− case mapM toSuperscript (T.unpack rendered) of
1164− Just r -> literal $ T.pack r
1165− Nothing -> literal $ "_(" <> rendered <> ")"
1166−inlineToMarkdown opts (SmallCaps lst) = do
1167− variant <- asks envVariant
1168− if variant /= PlainText &&
1169− (isEnabled Ext_raw_html opts || isEnabled Ext_native_spans opts)
1170− then inlineToMarkdown opts (Span ("",["smallcaps"],[]) lst)
1171− else inlineListToMarkdown opts $ capitalize lst
1172−inlineToMarkdown opts (Quoted SingleQuote lst) = do
1173− contents <- inlineListToMarkdown opts lst
1174− return $ if isEnabled Ext_smart opts
1175− then "'" <> contents <> "'"
1176− else
1177− if writerPreferAscii opts
1178− then "&lsquo;" <> contents <> "&rsquo;"
1179− else "‘" <> contents <> "’"
1180−inlineToMarkdown opts (Quoted DoubleQuote lst) = do
1181− contents <- inlineListToMarkdown opts lst
1182− return $ if isEnabled Ext_smart opts
1183− then "\"" <> contents <> "\""
1184− else
1185− if writerPreferAscii opts
1186− then "&ldquo;" <> contents <> "&rdquo;"
1187− else "“" <> contents <> "”"
1188−inlineToMarkdown opts (Code attr str) = do
1189− let tickGroups = filter (T.any (== '`')) $ T.group str
1190− let longest = if null tickGroups
1191− then 0
1192− else maximum $ map T.length tickGroups
1193− let marker = T.replicate (longest + 1) "`"
1194− let spacer = if longest == 0 then "" else " "
1195− let attrs = if isEnabled Ext_inline_code_attributes opts && attr /= nullAttr
1196− then attrsToMarkdown attr
1197− else empty
1198− variant <- asks envVariant
1199− case variant of
1200− PlainText -> return $ literal str
1201− _ -> return $ literal
1202− (marker <> spacer <> str <> spacer <> marker) <> attrs
1203−inlineToMarkdown opts (Str str) = do
1204− variant <- asks envVariant
1205− let str' = (if writerPreferAscii opts
1206− then toHtml5Entities
1207− else id) .
1208− (if isEnabled Ext_smart opts
1209− then unsmartify opts
1210− else id) .
1211− (if variant == PlainText
1212− then id
1213− else escapeText opts) $ str
1214− return $ literal str'
1215−inlineToMarkdown opts (Math InlineMath str) =
1216− case writerHTMLMathMethod opts of
1217− WebTeX url -> inlineToMarkdown opts
1218− (Image nullAttr [Str str] (url <> T.pack (urlEncode $ T.unpack str), str))
1219− _ | isEnabled Ext_tex_math_dollars opts ->
1220− return $ "$" <> literal str <> "$"
1221− | isEnabled Ext_tex_math_single_backslash opts ->
1222− return $ "\\(" <> literal str <> "\\)"
1223− | isEnabled Ext_tex_math_double_backslash opts ->
1224− return $ "\\\\(" <> literal str <> "\\\\)"
1225− | otherwise -> do
1226− variant <- asks envVariant
1227− texMathToInlines InlineMath str >>=
1228− inlineListToMarkdown opts .
1229− (if variant == PlainText then makeMathPlainer else id)
1230−inlineToMarkdown opts (Math DisplayMath str) =
1231− case writerHTMLMathMethod opts of
1232− WebTeX url -> (\x -> blankline <> x <> blankline) `fmap`
1233− inlineToMarkdown opts (Image nullAttr [Str str]
1234− (url <> T.pack (urlEncode $ T.unpack str), str))
1235− _ | isEnabled Ext_tex_math_dollars opts ->
1236− return $ "$$" <> literal str <> "$$"
1237− | isEnabled Ext_tex_math_single_backslash opts ->
1238− return $ "\\[" <> literal str <> "\\]"
1239− | isEnabled Ext_tex_math_double_backslash opts ->
1240− return $ "\\\\[" <> literal str <> "\\\\]"
1241− | otherwise -> (\x -> cr <> x <> cr) `fmap`
1242− (texMathToInlines DisplayMath str >>= inlineListToMarkdown opts)
1243−inlineToMarkdown opts il@(RawInline f str) = do
1244− let tickGroups = filter (T.any (== '`')) $ T.group str
1245− let numticks = if null tickGroups
1246− then 1
1247− else 1 + maximum (map T.length tickGroups)
1248− variant <- asks envVariant
1249− let Format fmt = f
1250− let rawAttribInline = return $
1251− literal (T.replicate numticks "`") <> literal str <>
1252− literal (T.replicate numticks "`") <> literal "{=" <> literal fmt <> literal "}"
1253− let renderEmpty = mempty <$ report (InlineNotRendered il)
1254− case variant of
1255− PlainText -> renderEmpty
1256− Commonmark
1257− | f `elem` ["gfm", "commonmark", "commonmark_x", "markdown"]
1258− -> return $ literal str
1259− Markdown
1260− | f `elem` ["markdown", "markdown_github", "markdown_phpextra",
1261− "markdown_mmd", "markdown_strict"]
1262− -> return $ literal str
1263− _ | isEnabled Ext_raw_attribute opts -> rawAttribInline
1264− | f `elem` ["html", "html5", "html4"]
1265− , isEnabled Ext_raw_html opts
1266− -> return $ literal str
1267− | f `elem` ["latex", "tex"]
1268− , isEnabled Ext_raw_tex opts
1269− -> return $ literal str
1270− _ -> renderEmpty
1271−
1272−
1273−inlineToMarkdown opts LineBreak = do
1274− variant <- asks envVariant
1275− if variant == PlainText || isEnabled Ext_hard_line_breaks opts
1276− then return cr
1277− else return $
1278− if isEnabled Ext_escaped_line_breaks opts
1279− then "\\" <> cr
1280− else " " <> cr
1281−inlineToMarkdown _ Space = do
1282− escapeSpaces <- asks envEscapeSpaces
1283− return $ if escapeSpaces then "\\ " else space
1284−inlineToMarkdown opts SoftBreak = do
1285− escapeSpaces <- asks envEscapeSpaces
1286− let space' = if escapeSpaces then "\\ " else space
1287− return $ case writerWrapText opts of
1288− WrapNone -> space'
1289− WrapAuto -> space'
1290− WrapPreserve -> cr
1291−inlineToMarkdown opts (Cite [] lst) = inlineListToMarkdown opts lst
1292−inlineToMarkdown opts (Cite (c:cs) lst)
1293− | not (isEnabled Ext_citations opts) = inlineListToMarkdown opts lst
1294− | otherwise =
1295− if citationMode c == AuthorInText
1296− then do
1297− suffs <- inlineListToMarkdown opts $ citationSuffix c
1298− rest <- mapM convertOne cs
1299− let inbr = suffs <+> joincits rest
1300− br = if isEmpty inbr then empty else char '[' <> inbr <> char ']'
1301− return $ literal ("@" <> citationId c) <+> br
1302− else do
1303− cits <- mapM convertOne (c:cs)
1304− return $ literal "[" <> joincits cits <> literal "]"
1305− where
1306− joincits = hcat . intersperse (literal "; ") . filter (not . isEmpty)
1307− convertOne Citation { citationId = k
1308− , citationPrefix = pinlines
1309− , citationSuffix = sinlines
1310− , citationMode = m }
1311− = do
1312− pdoc <- inlineListToMarkdown opts pinlines
1313− sdoc <- inlineListToMarkdown opts sinlines
1314− let k' = literal (modekey m <> "@" <> k)
1315− r = case sinlines of
1316− Str (T.uncons -> Just (y,_)):_ | y `elem` (",;]@" :: String) -> k' <> sdoc
1317− _ -> k' <+> sdoc
1318− return $ pdoc <+> r
1319− modekey SuppressAuthor = "-"
1320− modekey _ = ""
1321−inlineToMarkdown opts lnk@(Link attr txt (src, tit)) = do
1322− variant <- asks envVariant
1323− linktext <- inlineListToMarkdown opts txt
1324− let linktitle = if T.null tit
1325− then empty
1326− else literal $ " \"" <> tit <> "\""
1327− let srcSuffix = fromMaybe src (T.stripPrefix "mailto:" src)
1328− let useAuto = isURI src &&
1329− case txt of
1330− [Str s] | escapeURI s == srcSuffix -> True
1331− _ -> False
1332− let useRefLinks = writerReferenceLinks opts && not useAuto
1333− shortcutable <- asks envRefShortcutable
1334− let useShortcutRefLinks = shortcutable &&
1335− isEnabled Ext_shortcut_reference_links opts
1336− reftext <- if useRefLinks
1337− then literal <$> getReference attr linktext (src, tit)
1338− else return mempty
1339− case variant of
1340− PlainText
1341− | useAuto -> return $ literal srcSuffix
1342− | otherwise -> return linktext
1343− _ | useAuto -> return $ "<" <> literal srcSuffix <> ">"
1344− | useRefLinks ->
1345− let first = "[" <> linktext <> "]"
1346− second = if getKey linktext == getKey reftext
1347− then if useShortcutRefLinks
1348− then ""
1349− else "[]"
1350− else "[" <> reftext <> "]"
1351− in return $ first <> second
1352− | isEnabled Ext_raw_html opts
1353− , not (isEnabled Ext_link_attributes opts)
1354− , attr /= nullAttr -> -- use raw HTML to render attributes
1355− literal . T.strip <$>
1356− writeHtml5String opts{ writerTemplate = Nothing }
1357− (Pandoc nullMeta [Plain [lnk]])
1358− | otherwise -> return $
1359− "[" <> linktext <> "](" <> literal src <> linktitle <> ")" <>
1360− linkAttributes opts attr
1361−inlineToMarkdown opts img@(Image attr alternate (source, tit))
1362− | isEnabled Ext_raw_html opts &&
1363− not (isEnabled Ext_link_attributes opts) &&
1364− attr /= nullAttr = -- use raw HTML
1365− literal . T.strip <$>
1366− writeHtml5String opts{ writerTemplate = Nothing } (Pandoc nullMeta [Plain [img]])
1367− | otherwise = do
1368− variant <- asks envVariant
1369− let txt = if null alternate || alternate == [Str source]
1370− -- to prevent autolinks
1371− then [Str ""]
1372− else alternate
1373− linkPart <- inlineToMarkdown opts (Link attr txt (source, tit))
1374− return $ case variant of
1375− PlainText -> "[" <> linkPart <> "]"
1376− _ -> "!" <> linkPart
1377−inlineToMarkdown opts (Note contents) = do
1378− modify (\st -> st{ stNotes = contents : stNotes st })
1379− st <- get
1380− let ref = literal $ writerIdentifierPrefix opts <> tshow (stNoteNum st + length (stNotes st) - 1)
1381− if isEnabled Ext_footnotes opts
1382− then return $ "[^" <> ref <> "]"
1383− else return $ "[" <> ref <> "]"
1384−
1385−makeMathPlainer :: [Inline] -> [Inline]
1386−makeMathPlainer = walk go
1387− where
1388− go (Emph xs) = Span nullAttr xs
1389− go x = x
1390−
1391822lineBreakToSpace :: Inline -> Inline
1392823lineBreakToSpace LineBreak = Space
1393824lineBreakToSpace SoftBreak = Space
1394825lineBreakToSpace x = x
1395−
1396−toSubscriptInline :: Inline -> Maybe Inline
1397−toSubscriptInline Space = Just Space
1398−toSubscriptInline (Span attr ils) = Span attr <$> traverse toSubscriptInline ils
1399−toSubscriptInline (Str s) = Str . T.pack <$> traverse toSubscript (T.unpack s)
1400−toSubscriptInline LineBreak = Just LineBreak
1401−toSubscriptInline SoftBreak = Just SoftBreak
1402−toSubscriptInline _ = Nothing
1403−
1404−toSuperscriptInline :: Inline -> Maybe Inline
1405−toSuperscriptInline Space = Just Space
1406−toSuperscriptInline (Span attr ils) = Span attr <$> traverse toSuperscriptInline ils
1407−toSuperscriptInline (Str s) = Str . T.pack <$> traverse toSuperscript (T.unpack s)
1408−toSuperscriptInline LineBreak = Just LineBreak
1409−toSuperscriptInline SoftBreak = Just SoftBreak
1410−toSuperscriptInline _ = Nothing
src/Text/Pandoc/Writers/Markdown/Inline.hsnew file+601
@@ -0,0 +1,601 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+{-# LANGUAGE ScopedTypeVariables #-}
3+{-# LANGUAGE ViewPatterns #-}
4+{- |
5+ Module : Text.Pandoc.Writers.Markdown.Inline
6+ Copyright : Copyright (C) 2006-2021 John MacFarlane
7+ License : GNU GPL, version 2 or above
8+
9+ Maintainer : John MacFarlane <jgm@berkeley.edu>
10+ Stability : alpha
11+ Portability : portable
12+-}
13+module Text.Pandoc.Writers.Markdown.Inline (
14+ inlineListToMarkdown
15+ ) where
16+import Control.Monad.Reader
17+import Control.Monad.State.Strict
18+import Data.Char (isAlphaNum, isDigit)
19+import Data.List (find, intersperse)
20+import qualified Data.Map as M
21+import Data.Maybe (fromMaybe)
22+import Data.Text (Text)
23+import qualified Data.Text as T
24+import Network.HTTP (urlEncode)
25+import Text.Pandoc.Class.PandocMonad (PandocMonad, report)
26+import Text.Pandoc.Definition
27+import Text.Pandoc.Logging
28+import Text.Pandoc.Options
29+import Text.Pandoc.Parsing hiding (blankline, blanklines, char, space)
30+import Text.DocLayout
31+import Text.Pandoc.Shared
32+import Text.Pandoc.Writers.Shared
33+import Text.Pandoc.Walk
34+import Text.Pandoc.Writers.HTML (writeHtml5String)
35+import Text.Pandoc.Writers.Math (texMathToInlines)
36+import Text.Pandoc.XML (toHtml5Entities)
37+import Data.Coerce (coerce)
38+import Text.Pandoc.Writers.Markdown.Types (MarkdownVariant(..),
39+ WriterState(..),
40+ WriterEnv(..), MD)
41+
42+-- | Escape special characters for Markdown.
43+escapeText :: WriterOptions -> Text -> Text
44+escapeText opts = T.pack . go . T.unpack
45+ where
46+ go [] = []
47+ go (c:cs) =
48+ case c of
49+ '<' | isEnabled Ext_all_symbols_escapable opts ->
50+ '\\' : '<' : go cs
51+ | otherwise -> "&lt;" ++ go cs
52+ '>' | isEnabled Ext_all_symbols_escapable opts ->
53+ '\\' : '>' : go cs
54+ | otherwise -> "&gt;" ++ go cs
55+ '@' | isEnabled Ext_citations opts ->
56+ case cs of
57+ (d:_)
58+ | isAlphaNum d || d == '_'
59+ -> '\\':'@':go cs
60+ _ -> '@':go cs
61+ _ | c `elem` ['\\','`','*','_','[',']','#'] ->
62+ '\\':c:go cs
63+ '|' | isEnabled Ext_pipe_tables opts -> '\\':'|':go cs
64+ '^' | isEnabled Ext_superscript opts -> '\\':'^':go cs
65+ '~' | isEnabled Ext_subscript opts ||
66+ isEnabled Ext_strikeout opts -> '\\':'~':go cs
67+ '$' | isEnabled Ext_tex_math_dollars opts -> '\\':'$':go cs
68+ '\'' | isEnabled Ext_smart opts -> '\\':'\'':go cs
69+ '"' | isEnabled Ext_smart opts -> '\\':'"':go cs
70+ '-' | isEnabled Ext_smart opts ->
71+ case cs of
72+ '-':_ -> '\\':'-':go cs
73+ _ -> '-':go cs
74+ '.' | isEnabled Ext_smart opts ->
75+ case cs of
76+ '.':'.':rest -> '\\':'.':'.':'.':go rest
77+ _ -> '.':go cs
78+ _ -> case cs of
79+ '_':x:xs
80+ | isEnabled Ext_intraword_underscores opts
81+ , isAlphaNum c
82+ , isAlphaNum x -> c : '_' : x : go xs
83+ _ -> c : go cs
84+
85+attrsToMarkdown :: Attr -> Doc Text
86+attrsToMarkdown attribs = braces $ hsep [attribId, attribClasses, attribKeys]
87+ where attribId = case attribs of
88+ ("",_,_) -> empty
89+ (i,_,_) -> "#" <> escAttr i
90+ attribClasses = case attribs of
91+ (_,[],_) -> empty
92+ (_,cs,_) -> hsep $
93+ map (escAttr . ("."<>))
94+ cs
95+ attribKeys = case attribs of
96+ (_,_,[]) -> empty
97+ (_,_,ks) -> hsep $
98+ map (\(k,v) -> escAttr k
99+ <> "=\"" <>
100+ escAttr v <> "\"") ks
101+ escAttr = mconcat . map escAttrChar . T.unpack
102+ escAttrChar '"' = literal "\\\""
103+ escAttrChar '\\' = literal "\\\\"
104+ escAttrChar c = literal $ T.singleton c
105+
106+linkAttributes :: WriterOptions -> Attr -> Doc Text
107+linkAttributes opts attr =
108+ if isEnabled Ext_link_attributes opts && attr /= nullAttr
109+ then attrsToMarkdown attr
110+ else empty
111+
112+getKey :: Doc Text -> Key
113+getKey = toKey . render Nothing
114+
115+findUsableIndex :: [Text] -> Int -> Int
116+findUsableIndex lbls i = if tshow i `elem` lbls
117+ then findUsableIndex lbls (i + 1)
118+ else i
119+
120+getNextIndex :: PandocMonad m => MD m Int
121+getNextIndex = do
122+ prevRefs <- gets stPrevRefs
123+ refs <- gets stRefs
124+ i <- (+ 1) <$> gets stLastIdx
125+ modify $ \s -> s{ stLastIdx = i }
126+ let refLbls = map (\(r,_,_) -> r) $ prevRefs ++ refs
127+ return $ findUsableIndex refLbls i
128+
129+-- | Get reference for target; if none exists, create unique one and return.
130+-- Prefer label if possible; otherwise, generate a unique key.
131+getReference :: PandocMonad m => Attr -> Doc Text -> Target -> MD m Text
132+getReference attr label target = do
133+ refs <- gets stRefs
134+ case find (\(_,t,a) -> t == target && a == attr) refs of
135+ Just (ref, _, _) -> return ref
136+ Nothing -> do
137+ keys <- gets stKeys
138+ let key = getKey label
139+ let rawkey = coerce key
140+ case M.lookup key keys of
141+ Nothing -> do -- no other refs with this label
142+ (lab', idx) <- if T.null rawkey ||
143+ T.length rawkey > 999 ||
144+ T.any (\c -> c == '[' || c == ']') rawkey
145+ then do
146+ i <- getNextIndex
147+ return (tshow i, i)
148+ else
149+ return (render Nothing label, 0)
150+ modify (\s -> s{
151+ stRefs = (lab', target, attr) : refs,
152+ stKeys = M.insert (getKey label)
153+ (M.insert (target, attr) idx mempty)
154+ (stKeys s) })
155+ return lab'
156+
157+ Just km -> -- we have refs with this label
158+ case M.lookup (target, attr) km of
159+ Just i -> do
160+ let lab' = render Nothing $
161+ label <> if i == 0
162+ then mempty
163+ else literal (tshow i)
164+ -- make sure it's in stRefs; it may be
165+ -- a duplicate that was printed in a previous
166+ -- block:
167+ when ((lab', target, attr) `notElem` refs) $
168+ modify (\s -> s{
169+ stRefs = (lab', target, attr) : refs })
170+ return lab'
171+ Nothing -> do -- but this one is to a new target
172+ i <- getNextIndex
173+ let lab' = tshow i
174+ modify (\s -> s{
175+ stRefs = (lab', target, attr) : refs,
176+ stKeys = M.insert key
177+ (M.insert (target, attr) i km)
178+ (stKeys s) })
179+ return lab'
180+
181+-- | Convert list of Pandoc inline elements to markdown.
182+inlineListToMarkdown :: PandocMonad m => WriterOptions -> [Inline] -> MD m (Doc Text)
183+inlineListToMarkdown opts lst = do
184+ inlist <- asks envInList
185+ go (if inlist then avoidBadWrapsInList lst else lst)
186+ where go [] = return empty
187+ go (x@Math{}:y@(Str t):zs)
188+ | T.all isDigit (T.take 1 t) -- starts with digit -- see #7058
189+ = liftM2 (<>) (inlineToMarkdown opts x)
190+ (go (RawInline (Format "html") "<!-- -->" : y : zs))
191+ go (i:is) = case i of
192+ Link {} -> case is of
193+ -- If a link is followed by another link, or '[', '(' or ':'
194+ -- then we don't shortcut
195+ Link {}:_ -> unshortcutable
196+ Space:Link {}:_ -> unshortcutable
197+ Space:(Str(thead -> Just '[')):_ -> unshortcutable
198+ Space:(RawInline _ (thead -> Just '[')):_ -> unshortcutable
199+ Space:(Cite _ _):_ -> unshortcutable
200+ SoftBreak:Link {}:_ -> unshortcutable
201+ SoftBreak:(Str(thead -> Just '[')):_ -> unshortcutable
202+ SoftBreak:(RawInline _ (thead -> Just '[')):_ -> unshortcutable
203+ SoftBreak:(Cite _ _):_ -> unshortcutable
204+ LineBreak:Link {}:_ -> unshortcutable
205+ LineBreak:(Str(thead -> Just '[')):_ -> unshortcutable
206+ LineBreak:(RawInline _ (thead -> Just '[')):_ -> unshortcutable
207+ LineBreak:(Cite _ _):_ -> unshortcutable
208+ (Cite _ _):_ -> unshortcutable
209+ Str (thead -> Just '['):_ -> unshortcutable
210+ Str (thead -> Just '('):_ -> unshortcutable
211+ Str (thead -> Just ':'):_ -> unshortcutable
212+ (RawInline _ (thead -> Just '[')):_ -> unshortcutable
213+ (RawInline _ (thead -> Just '(')):_ -> unshortcutable
214+ (RawInline _ (thead -> Just ':')):_ -> unshortcutable
215+ (RawInline _ (T.stripPrefix " [" -> Just _ )):_ -> unshortcutable
216+ _ -> shortcutable
217+ _ -> shortcutable
218+ where shortcutable = liftM2 (<>) (inlineToMarkdown opts i) (go is)
219+ unshortcutable = do
220+ iMark <- local
221+ (\env -> env { envRefShortcutable = False })
222+ (inlineToMarkdown opts i)
223+ fmap (iMark <>) (go is)
224+ thead = fmap fst . T.uncons
225+
226+isSp :: Inline -> Bool
227+isSp Space = True
228+isSp SoftBreak = True
229+isSp _ = False
230+
231+avoidBadWrapsInList :: [Inline] -> [Inline]
232+avoidBadWrapsInList [] = []
233+avoidBadWrapsInList (s:Str (T.uncons -> Just ('>',cs)):xs) | isSp s =
234+ Str (" >" <> cs) : avoidBadWrapsInList xs
235+avoidBadWrapsInList [s, Str (T.uncons -> Just (c, cs))]
236+ | T.null cs && isSp s && c `elem` ['-','*','+'] = [Str $ T.pack [' ', c]]
237+avoidBadWrapsInList (s:Str (T.uncons -> Just (c, cs)):Space:xs)
238+ | T.null cs && isSp s && c `elem` ['-','*','+'] =
239+ Str (T.pack [' ', c]) : Space : avoidBadWrapsInList xs
240+avoidBadWrapsInList (s:Str cs:Space:xs)
241+ | isSp s && isOrderedListMarker cs =
242+ Str (" " <> cs) : Space : avoidBadWrapsInList xs
243+avoidBadWrapsInList [s, Str cs]
244+ | isSp s && isOrderedListMarker cs = [Str $ " " <> cs]
245+avoidBadWrapsInList (x:xs) = x : avoidBadWrapsInList xs
246+
247+isOrderedListMarker :: Text -> Bool
248+isOrderedListMarker xs = not (T.null xs) && (T.last xs `elem` ['.',')']) &&
249+ isRight (runParser (anyOrderedListMarker >> eof)
250+ defaultParserState "" xs)
251+ where
252+ isRight (Right _) = True
253+ isRight (Left _) = False
254+
255+-- | Convert Pandoc inline element to markdown.
256+inlineToMarkdown :: PandocMonad m => WriterOptions -> Inline -> MD m (Doc Text)
257+inlineToMarkdown opts (Span ("",["emoji"],kvs) [Str s]) =
258+ case lookup "data-emoji" kvs of
259+ Just emojiname | isEnabled Ext_emoji opts ->
260+ return $ ":" <> literal emojiname <> ":"
261+ _ -> inlineToMarkdown opts (Str s)
262+inlineToMarkdown opts (Span attrs ils) = do
263+ variant <- asks envVariant
264+ contents <- inlineListToMarkdown opts ils
265+ return $ case attrs of
266+ (_,["csl-block"],_) -> (cr <>)
267+ (_,["csl-left-margin"],_) -> (cr <>)
268+ (_,["csl-indent"],_) -> (cr <>)
269+ _ -> id
270+ $ case variant of
271+ PlainText -> contents
272+ _ | attrs == nullAttr -> contents
273+ | isEnabled Ext_bracketed_spans opts ->
274+ let attrs' = if attrs /= nullAttr
275+ then attrsToMarkdown attrs
276+ else empty
277+ in "[" <> contents <> "]" <> attrs'
278+ | isEnabled Ext_raw_html opts ||
279+ isEnabled Ext_native_spans opts ->
280+ tagWithAttrs "span" attrs <> contents <> literal "</span>"
281+ | otherwise -> contents
282+inlineToMarkdown _ (Emph []) = return empty
283+inlineToMarkdown opts (Emph lst) = do
284+ variant <- asks envVariant
285+ contents <- inlineListToMarkdown opts lst
286+ return $ case variant of
287+ PlainText
288+ | isEnabled Ext_gutenberg opts -> "_" <> contents <> "_"
289+ | otherwise -> contents
290+ _ -> "*" <> contents <> "*"
291+inlineToMarkdown _ (Underline []) = return empty
292+inlineToMarkdown opts (Underline lst) = do
293+ variant <- asks envVariant
294+ contents <- inlineListToMarkdown opts lst
295+ case variant of
296+ PlainText -> return contents
297+ _ | isEnabled Ext_bracketed_spans opts ->
298+ return $ "[" <> contents <> "]" <> "{.ul}"
299+ | isEnabled Ext_native_spans opts ->
300+ return $ tagWithAttrs "span" ("", ["underline"], [])
301+ <> contents
302+ <> literal "</span>"
303+ | isEnabled Ext_raw_html opts ->
304+ return $ "<u>" <> contents <> "</u>"
305+ | otherwise -> inlineToMarkdown opts (Emph lst)
306+inlineToMarkdown _ (Strong []) = return empty
307+inlineToMarkdown opts (Strong lst) = do
308+ variant <- asks envVariant
309+ case variant of
310+ PlainText ->
311+ inlineListToMarkdown opts $
312+ if isEnabled Ext_gutenberg opts
313+ then capitalize lst
314+ else lst
315+ _ -> do
316+ contents <- inlineListToMarkdown opts lst
317+ return $ "**" <> contents <> "**"
318+inlineToMarkdown _ (Strikeout []) = return empty
319+inlineToMarkdown opts (Strikeout lst) = do
320+ contents <- inlineListToMarkdown opts lst
321+ return $ if isEnabled Ext_strikeout opts
322+ then "~~" <> contents <> "~~"
323+ else if isEnabled Ext_raw_html opts
324+ then "<s>" <> contents <> "</s>"
325+ else contents
326+inlineToMarkdown _ (Superscript []) = return empty
327+inlineToMarkdown opts (Superscript lst) =
328+ local (\env -> env {envEscapeSpaces = envVariant env == Markdown}) $ do
329+ contents <- inlineListToMarkdown opts lst
330+ if isEnabled Ext_superscript opts
331+ then return $ "^" <> contents <> "^"
332+ else if isEnabled Ext_raw_html opts
333+ then return $ "<sup>" <> contents <> "</sup>"
334+ else
335+ case traverse toSuperscriptInline lst of
336+ Just xs' | not (writerPreferAscii opts)
337+ -> inlineListToMarkdown opts xs'
338+ _ -> do
339+ let rendered = render Nothing contents
340+ return $
341+ case mapM toSuperscript (T.unpack rendered) of
342+ Just r -> literal $ T.pack r
343+ Nothing -> literal $ "^(" <> rendered <> ")"
344+inlineToMarkdown _ (Subscript []) = return empty
345+inlineToMarkdown opts (Subscript lst) =
346+ local (\env -> env {envEscapeSpaces = envVariant env == Markdown}) $ do
347+ contents <- inlineListToMarkdown opts lst
348+ if isEnabled Ext_subscript opts
349+ then return $ "~" <> contents <> "~"
350+ else if isEnabled Ext_raw_html opts
351+ then return $ "<sub>" <> contents <> "</sub>"
352+ else
353+ case traverse toSubscriptInline lst of
354+ Just xs' | not (writerPreferAscii opts)
355+ -> inlineListToMarkdown opts xs'
356+ _ -> do
357+ let rendered = render Nothing contents
358+ return $
359+ case mapM toSuperscript (T.unpack rendered) of
360+ Just r -> literal $ T.pack r
361+ Nothing -> literal $ "_(" <> rendered <> ")"
362+inlineToMarkdown opts (SmallCaps lst) = do
363+ variant <- asks envVariant
364+ if variant /= PlainText &&
365+ (isEnabled Ext_raw_html opts || isEnabled Ext_native_spans opts)
366+ then inlineToMarkdown opts (Span ("",["smallcaps"],[]) lst)
367+ else inlineListToMarkdown opts $ capitalize lst
368+inlineToMarkdown opts (Quoted SingleQuote lst) = do
369+ contents <- inlineListToMarkdown opts lst
370+ return $ if isEnabled Ext_smart opts
371+ then "'" <> contents <> "'"
372+ else
373+ if writerPreferAscii opts
374+ then "&lsquo;" <> contents <> "&rsquo;"
375+ else "‘" <> contents <> "’"
376+inlineToMarkdown opts (Quoted DoubleQuote lst) = do
377+ contents <- inlineListToMarkdown opts lst
378+ return $ if isEnabled Ext_smart opts
379+ then "\"" <> contents <> "\""
380+ else
381+ if writerPreferAscii opts
382+ then "&ldquo;" <> contents <> "&rdquo;"
383+ else "“" <> contents <> "”"
384+inlineToMarkdown opts (Code attr str) = do
385+ let tickGroups = filter (T.any (== '`')) $ T.group str
386+ let longest = if null tickGroups
387+ then 0
388+ else maximum $ map T.length tickGroups
389+ let marker = T.replicate (longest + 1) "`"
390+ let spacer = if longest == 0 then "" else " "
391+ let attrs = if isEnabled Ext_inline_code_attributes opts && attr /= nullAttr
392+ then attrsToMarkdown attr
393+ else empty
394+ variant <- asks envVariant
395+ case variant of
396+ PlainText -> return $ literal str
397+ _ -> return $ literal
398+ (marker <> spacer <> str <> spacer <> marker) <> attrs
399+inlineToMarkdown opts (Str str) = do
400+ variant <- asks envVariant
401+ let str' = (if writerPreferAscii opts
402+ then toHtml5Entities
403+ else id) .
404+ (if isEnabled Ext_smart opts
405+ then unsmartify opts
406+ else id) .
407+ (if variant == PlainText
408+ then id
409+ else escapeText opts) $ str
410+ return $ literal str'
411+inlineToMarkdown opts (Math InlineMath str) =
412+ case writerHTMLMathMethod opts of
413+ WebTeX url -> inlineToMarkdown opts
414+ (Image nullAttr [Str str] (url <> T.pack (urlEncode $ T.unpack str), str))
415+ _ | isEnabled Ext_tex_math_dollars opts ->
416+ return $ "$" <> literal str <> "$"
417+ | isEnabled Ext_tex_math_single_backslash opts ->
418+ return $ "\\(" <> literal str <> "\\)"
419+ | isEnabled Ext_tex_math_double_backslash opts ->
420+ return $ "\\\\(" <> literal str <> "\\\\)"
421+ | otherwise -> do
422+ variant <- asks envVariant
423+ texMathToInlines InlineMath str >>=
424+ inlineListToMarkdown opts .
425+ (if variant == PlainText then makeMathPlainer else id)
426+inlineToMarkdown opts (Math DisplayMath str) =
427+ case writerHTMLMathMethod opts of
428+ WebTeX url -> (\x -> blankline <> x <> blankline) `fmap`
429+ inlineToMarkdown opts (Image nullAttr [Str str]
430+ (url <> T.pack (urlEncode $ T.unpack str), str))
431+ _ | isEnabled Ext_tex_math_dollars opts ->
432+ return $ "$$" <> literal str <> "$$"
433+ | isEnabled Ext_tex_math_single_backslash opts ->
434+ return $ "\\[" <> literal str <> "\\]"
435+ | isEnabled Ext_tex_math_double_backslash opts ->
436+ return $ "\\\\[" <> literal str <> "\\\\]"
437+ | otherwise -> (\x -> cr <> x <> cr) `fmap`
438+ (texMathToInlines DisplayMath str >>= inlineListToMarkdown opts)
439+inlineToMarkdown opts il@(RawInline f str) = do
440+ let tickGroups = filter (T.any (== '`')) $ T.group str
441+ let numticks = if null tickGroups
442+ then 1
443+ else 1 + maximum (map T.length tickGroups)
444+ variant <- asks envVariant
445+ let Format fmt = f
446+ let rawAttribInline = return $
447+ literal (T.replicate numticks "`") <> literal str <>
448+ literal (T.replicate numticks "`") <> literal "{=" <> literal fmt <> literal "}"
449+ let renderEmpty = mempty <$ report (InlineNotRendered il)
450+ case variant of
451+ PlainText -> renderEmpty
452+ Commonmark
453+ | f `elem` ["gfm", "commonmark", "commonmark_x", "markdown"]
454+ -> return $ literal str
455+ Markdown
456+ | f `elem` ["markdown", "markdown_github", "markdown_phpextra",
457+ "markdown_mmd", "markdown_strict"]
458+ -> return $ literal str
459+ _ | isEnabled Ext_raw_attribute opts -> rawAttribInline
460+ | f `elem` ["html", "html5", "html4"]
461+ , isEnabled Ext_raw_html opts
462+ -> return $ literal str
463+ | f `elem` ["latex", "tex"]
464+ , isEnabled Ext_raw_tex opts
465+ -> return $ literal str
466+ _ -> renderEmpty
467+
468+
469+inlineToMarkdown opts LineBreak = do
470+ variant <- asks envVariant
471+ if variant == PlainText || isEnabled Ext_hard_line_breaks opts
472+ then return cr
473+ else return $
474+ if isEnabled Ext_escaped_line_breaks opts
475+ then "\\" <> cr
476+ else " " <> cr
477+inlineToMarkdown _ Space = do
478+ escapeSpaces <- asks envEscapeSpaces
479+ return $ if escapeSpaces then "\\ " else space
480+inlineToMarkdown opts SoftBreak = do
481+ escapeSpaces <- asks envEscapeSpaces
482+ let space' = if escapeSpaces then "\\ " else space
483+ return $ case writerWrapText opts of
484+ WrapNone -> space'
485+ WrapAuto -> space'
486+ WrapPreserve -> cr
487+inlineToMarkdown opts (Cite [] lst) = inlineListToMarkdown opts lst
488+inlineToMarkdown opts (Cite (c:cs) lst)
489+ | not (isEnabled Ext_citations opts) = inlineListToMarkdown opts lst
490+ | otherwise =
491+ if citationMode c == AuthorInText
492+ then do
493+ suffs <- inlineListToMarkdown opts $ citationSuffix c
494+ rest <- mapM convertOne cs
495+ let inbr = suffs <+> joincits rest
496+ br = if isEmpty inbr then empty else char '[' <> inbr <> char ']'
497+ return $ literal ("@" <> citationId c) <+> br
498+ else do
499+ cits <- mapM convertOne (c:cs)
500+ return $ literal "[" <> joincits cits <> literal "]"
501+ where
502+ joincits = hcat . intersperse (literal "; ") . filter (not . isEmpty)
503+ convertOne Citation { citationId = k
504+ , citationPrefix = pinlines
505+ , citationSuffix = sinlines
506+ , citationMode = m }
507+ = do
508+ pdoc <- inlineListToMarkdown opts pinlines
509+ sdoc <- inlineListToMarkdown opts sinlines
510+ let k' = literal (modekey m <> "@" <> k)
511+ r = case sinlines of
512+ Str (T.uncons -> Just (y,_)):_ | y `elem` (",;]@" :: String) -> k' <> sdoc
513+ _ -> k' <+> sdoc
514+ return $ pdoc <+> r
515+ modekey SuppressAuthor = "-"
516+ modekey _ = ""
517+inlineToMarkdown opts lnk@(Link attr txt (src, tit)) = do
518+ variant <- asks envVariant
519+ linktext <- inlineListToMarkdown opts txt
520+ let linktitle = if T.null tit
521+ then empty
522+ else literal $ " \"" <> tit <> "\""
523+ let srcSuffix = fromMaybe src (T.stripPrefix "mailto:" src)
524+ let useAuto = isURI src &&
525+ case txt of
526+ [Str s] | escapeURI s == srcSuffix -> True
527+ _ -> False
528+ let useRefLinks = writerReferenceLinks opts && not useAuto
529+ shortcutable <- asks envRefShortcutable
530+ let useShortcutRefLinks = shortcutable &&
531+ isEnabled Ext_shortcut_reference_links opts
532+ reftext <- if useRefLinks
533+ then literal <$> getReference attr linktext (src, tit)
534+ else return mempty
535+ case variant of
536+ PlainText
537+ | useAuto -> return $ literal srcSuffix
538+ | otherwise -> return linktext
539+ _ | useAuto -> return $ "<" <> literal srcSuffix <> ">"
540+ | useRefLinks ->
541+ let first = "[" <> linktext <> "]"
542+ second = if getKey linktext == getKey reftext
543+ then if useShortcutRefLinks
544+ then ""
545+ else "[]"
546+ else "[" <> reftext <> "]"
547+ in return $ first <> second
548+ | isEnabled Ext_raw_html opts
549+ , not (isEnabled Ext_link_attributes opts)
550+ , attr /= nullAttr -> -- use raw HTML to render attributes
551+ literal . T.strip <$>
552+ writeHtml5String opts{ writerTemplate = Nothing }
553+ (Pandoc nullMeta [Plain [lnk]])
554+ | otherwise -> return $
555+ "[" <> linktext <> "](" <> literal src <> linktitle <> ")" <>
556+ linkAttributes opts attr
557+inlineToMarkdown opts img@(Image attr alternate (source, tit))
558+ | isEnabled Ext_raw_html opts &&
559+ not (isEnabled Ext_link_attributes opts) &&
560+ attr /= nullAttr = -- use raw HTML
561+ literal . T.strip <$>
562+ writeHtml5String opts{ writerTemplate = Nothing } (Pandoc nullMeta [Plain [img]])
563+ | otherwise = do
564+ variant <- asks envVariant
565+ let txt = if null alternate || alternate == [Str source]
566+ -- to prevent autolinks
567+ then [Str ""]
568+ else alternate
569+ linkPart <- inlineToMarkdown opts (Link attr txt (source, tit))
570+ return $ case variant of
571+ PlainText -> "[" <> linkPart <> "]"
572+ _ -> "!" <> linkPart
573+inlineToMarkdown opts (Note contents) = do
574+ modify (\st -> st{ stNotes = contents : stNotes st })
575+ st <- get
576+ let ref = literal $ writerIdentifierPrefix opts <> tshow (stNoteNum st + length (stNotes st) - 1)
577+ if isEnabled Ext_footnotes opts
578+ then return $ "[^" <> ref <> "]"
579+ else return $ "[" <> ref <> "]"
580+
581+makeMathPlainer :: [Inline] -> [Inline]
582+makeMathPlainer = walk go
583+ where
584+ go (Emph xs) = Span nullAttr xs
585+ go x = x
586+
587+toSubscriptInline :: Inline -> Maybe Inline
588+toSubscriptInline Space = Just Space
589+toSubscriptInline (Span attr ils) = Span attr <$> traverse toSubscriptInline ils
590+toSubscriptInline (Str s) = Str . T.pack <$> traverse toSubscript (T.unpack s)
591+toSubscriptInline LineBreak = Just LineBreak
592+toSubscriptInline SoftBreak = Just SoftBreak
593+toSubscriptInline _ = Nothing
594+
595+toSuperscriptInline :: Inline -> Maybe Inline
596+toSuperscriptInline Space = Just Space
597+toSuperscriptInline (Span attr ils) = Span attr <$> traverse toSuperscriptInline ils
598+toSuperscriptInline (Str s) = Str . T.pack <$> traverse toSuperscript (T.unpack s)
599+toSuperscriptInline LineBreak = Just LineBreak
600+toSuperscriptInline SoftBreak = Just SoftBreak
601+toSuperscriptInline _ = Nothing
src/Text/Pandoc/Writers/Markdown/Types.hsnew file+81
@@ -0,0 +1,81 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+{- |
3+ Module : Text.Pandoc.Writers.Markdown.Types
4+ Copyright : Copyright (C) 2006-2021 John MacFarlane
5+ License : GNU GPL, version 2 or above
6+
7+ Maintainer : John MacFarlane <jgm@berkeley.edu>
8+ Stability : alpha
9+ Portability : portable
10+-}
11+module Text.Pandoc.Writers.Markdown.Types (
12+ MarkdownVariant(..),
13+ WriterState(..),
14+ WriterEnv(..),
15+ Notes,
16+ Ref,
17+ Refs,
18+ MD,
19+ evalMD
20+ ) where
21+import Control.Monad.Reader
22+import Control.Monad.State.Strict
23+import Data.Default
24+import qualified Data.Map as M
25+import qualified Data.Set as Set
26+import Data.Text (Text)
27+import Text.Pandoc.Parsing (Key)
28+import Text.Pandoc.Class.PandocMonad (PandocMonad)
29+import Text.Pandoc.Definition
30+
31+type Notes = [[Block]]
32+type Ref = (Text, Target, Attr)
33+type Refs = [Ref]
34+
35+type MD m = ReaderT WriterEnv (StateT WriterState m)
36+
37+evalMD :: PandocMonad m => MD m a -> WriterEnv -> WriterState -> m a
38+evalMD md env st = evalStateT (runReaderT md env) st
39+
40+data WriterEnv = WriterEnv { envInList :: Bool
41+ , envVariant :: MarkdownVariant
42+ , envRefShortcutable :: Bool
43+ , envBlockLevel :: Int
44+ , envEscapeSpaces :: Bool
45+ }
46+
47+data MarkdownVariant =
48+ PlainText
49+ | Commonmark
50+ | Markdown
51+ deriving (Show, Eq)
52+
53+instance Default WriterEnv
54+ where def = WriterEnv { envInList = False
55+ , envVariant = Markdown
56+ , envRefShortcutable = True
57+ , envBlockLevel = 0
58+ , envEscapeSpaces = False
59+ }
60+
61+data WriterState = WriterState { stNotes :: Notes
62+ , stPrevRefs :: Refs
63+ , stRefs :: Refs
64+ , stKeys :: M.Map Key
65+ (M.Map (Target, Attr) Int)
66+ , stLastIdx :: Int
67+ , stIds :: Set.Set Text
68+ , stNoteNum :: Int
69+ }
70+
71+instance Default WriterState
72+ where def = WriterState{ stNotes = []
73+ , stPrevRefs = []
74+ , stRefs = []
75+ , stKeys = M.empty
76+ , stLastIdx = 0
77+ , stIds = Set.empty
78+ , stNoteNum = 1
79+ }
80+
81+