@@ -46,7 +46,7 @@ module Text.Pandoc.Writers.Muse (writeMuse) where
4646import Prelude
4747import Control.Monad.Reader
4848import Control.Monad.State.Strict
49−import Data.Char (isSpace, isDigit, isAsciiUpper, isAsciiLower)
49+import Data.Char (isSpace, isAlphaNum, isDigit, isAsciiUpper, isAsciiLower)
5050import Data.Default
5151import Data.Text (Text)
5252import Data.List (intersperse, transpose, isInfixOf)
@@ -74,16 +74,20 @@ data WriterEnv =
7474 , envInsideLinkDescription :: Bool -- ^ Escape ] if True
7575 , envAfterSpace :: Bool -- ^ There is whitespace (not just newline) before
7676 , envOneLine :: Bool -- ^ True if newlines are not allowed
77+ , envInsideAsterisks :: Bool -- ^ True if outer element is emphasis with asterisks
78+ , envNearAsterisks :: Bool -- ^ Rendering inline near asterisks
7779 }
7880
7981data WriterState =
8082 WriterState { stNotes :: Notes
8183 , stIds :: Set.Set String
84+ , stUseTags :: Bool -- ^ Use tags for emphasis, for example because previous character is a letter
8285 }
8386
8487instance Default WriterState
8588 where def = WriterState { stNotes = []
8689 , stIds = Set.empty
90+ , stUseTags = False
8791 }
8892
8993evalMuse :: PandocMonad m => Muse m a -> WriterEnv -> WriterState -> m a
@@ -103,6 +107,8 @@ writeMuse opts document =
103107 , envInsideLinkDescription = False
104108 , envAfterSpace = False
105109 , envOneLine = False
110+ , envInsideAsterisks = False
111+ , envNearAsterisks = False
106112 }
107113
108114-- | Return Muse representation of document.
@@ -212,6 +218,7 @@ blockToMuse (BulletList items) = do
212218 => [Block]
213219 -> Muse m Doc
214220 bulletListItemToMuse item = do
221+ modify $ \st -> st { stUseTags = False }
215222 contents <- blockListToMuse item
216223 return $ hang 2 "- " contents
217224blockToMuse (DefinitionList items) = do
@@ -223,6 +230,7 @@ blockToMuse (DefinitionList items) = do
223230 => ([Inline], [[Block]])
224231 -> Muse m Doc
225232 definitionListItemToMuse (label, defs) = do
233+ modify $ \st -> st { stUseTags = False }
226234 label' <- local (\env -> env { envOneLine = True, envAfterSpace = True }) $ inlineListToMuse' label
227235 contents <- vcat <$> mapM descriptionToMuse defs
228236 let ind = offset label'
@@ -401,6 +409,17 @@ fixNotes (Space : n@Note{} : rest) = Str " " : n : fixNotes rest
401409fixNotes (SoftBreak : n@Note{} : rest) = Str " " : n : fixNotes rest
402410fixNotes (x:xs) = x : fixNotes xs
403411
412+startsWithSpace :: [Inline] -> Bool
413+startsWithSpace (Space:_) = True
414+startsWithSpace (SoftBreak:_) = True
415+startsWithSpace _ = False
416+
417+endsWithSpace :: [Inline] -> Bool
418+endsWithSpace [Space] = True
419+endsWithSpace [SoftBreak] = True
420+endsWithSpace (_:xs) = endsWithSpace xs
421+endsWithSpace [] = False
422+
404423urlEscapeBrackets :: String -> String
405424urlEscapeBrackets (']':xs) = '%':'5':'D':urlEscapeBrackets xs
406425urlEscapeBrackets (x:xs) = x:urlEscapeBrackets xs
@@ -409,9 +428,9 @@ urlEscapeBrackets [] = []
409428isHorizontalRule :: String -> Bool
410429isHorizontalRule s = length s >= 4 && all (== '-') s
411430
412−startsWithSpace :: String -> Bool
413−startsWithSpace (x:_) = isSpace x
414−startsWithSpace [] = False
431+stringStartsWithSpace :: String -> Bool
432+stringStartsWithSpace (x:_) = isSpace x
433+stringStartsWithSpace [] = False
415434
416435fixOrEscape :: Bool -> Inline -> Bool
417436fixOrEscape sp (Str "-") = sp
@@ -420,11 +439,19 @@ fixOrEscape _ (Str ">") = True
420439fixOrEscape sp (Str s) = (sp && (startsWithMarker isDigit s ||
421440 startsWithMarker isAsciiLower s ||
422441 startsWithMarker isAsciiUpper s))
423− || isHorizontalRule s || startsWithSpace s
442+ || isHorizontalRule s || stringStartsWithSpace s
424443fixOrEscape _ Space = True
425444fixOrEscape _ SoftBreak = True
426445fixOrEscape _ _ = False
427446
447+inlineListStartsWithAlnum :: PandocMonad m
448+ => [Inline]
449+ -> Muse m Bool
450+inlineListStartsWithAlnum (Str s:_) = do
451+ esc <- shouldEscapeString s
452+ return $ esc || isAlphaNum (head s)
453+inlineListStartsWithAlnum _ = return False
454+
428455-- | Convert list of Pandoc inline elements to Muse
429456renderInlineList :: PandocMonad m
430457 => [Inline]
@@ -436,11 +463,22 @@ renderInlineList (x:xs) = do
436463 start <- asks envInlineStart
437464 afterSpace <- asks envAfterSpace
438465 topLevel <- asks envTopLevel
439− r <- local (\env -> env { envInlineStart = False }) $ inlineToMuse x
466+ insideAsterisks <- asks envInsideAsterisks
467+ nearAsterisks <- asks envNearAsterisks
468+ useTags <- gets stUseTags
469+ alnumNext <- inlineListStartsWithAlnum xs
470+ let newUseTags = useTags || alnumNext
471+ modify $ \st -> st { stUseTags = newUseTags }
472+
473+ r <- local (\env -> env { envInlineStart = False
474+ , envInsideAsterisks = False
475+ , envNearAsterisks = nearAsterisks || (null xs && insideAsterisks)
476+ }) $ inlineToMuse x
440477 opts <- asks envOptions
441478 let isNewline = (x == SoftBreak && writerWrapText opts == WrapPreserve) || x == LineBreak
442479 lst' <- local (\env -> env { envInlineStart = isNewline
443480 , envAfterSpace = x == Space || (not topLevel && isNewline)
481+ , envNearAsterisks = False
444482 }) $ renderInlineList xs
445483 if start && fixOrEscape afterSpace x
446484 then pure (text "<verbatim></verbatim>" <> r <> lst')
@@ -452,7 +490,9 @@ inlineListToMuse :: PandocMonad m
452490 -> Muse m Doc
453491inlineListToMuse lst = do
454492 lst' <- (normalizeInlineList . fixNotes) <$> preprocessInlineList (map (removeKeyValues . replaceSmallCaps) lst)
455− renderInlineList lst'
493+ insideAsterisks <- asks envInsideAsterisks
494+ modify $ \st -> st { stUseTags = False } -- Previous character is likely a '>' or some other markup
495+ local (\env -> env { envNearAsterisks = insideAsterisks }) $ renderInlineList lst'
456496
457497inlineListToMuse' :: PandocMonad m => [Inline] -> Muse m Doc
458498inlineListToMuse' lst = do
@@ -466,52 +506,112 @@ inlineListToMuse' lst = do
466506inlineToMuse :: PandocMonad m
467507 => Inline
468508 -> Muse m Doc
469−inlineToMuse (Str str) =
470− text <$> conditionalEscapeString str
509+inlineToMuse (Str str) = do
510+ escapedStr <- conditionalEscapeString str
511+ let useTags = isAlphaNum $ last escapedStr -- escapedStr is never empty because empty strings are escaped
512+ modify $ \st -> st { stUseTags = useTags }
513+ return $ text escapedStr
514+inlineToMuse (Emph [Strong lst]) = do
515+ useTags <- gets stUseTags
516+ if useTags
517+ then do contents <- local (\env -> env { envInsideAsterisks = True }) $ inlineListToMuse lst
518+ modify $ \st -> st { stUseTags = False }
519+ return $ "<em>**" <> contents <> "**</em>"
520+ else if null lst || startsWithSpace lst || endsWithSpace lst
521+ then do
522+ contents <- local (\env -> env { envInsideAsterisks = False }) $ inlineListToMuse lst
523+ modify $ \st -> st { stUseTags = True }
524+ return $ "*<strong>" <> contents <> "</strong>*"
525+ else do
526+ contents <- local (\env -> env { envInsideAsterisks = True }) $ inlineListToMuse lst
527+ modify $ \st -> st { stUseTags = True }
528+ return $ "***" <> contents <> "***"
471529inlineToMuse (Emph lst) = do
472− contents <- inlineListToMuse lst
473− return $ "<em>" <> contents <> "</em>"
530+ useTags <- gets stUseTags
531+ if useTags || null lst || startsWithSpace lst || endsWithSpace lst
532+ then do contents <- inlineListToMuse lst
533+ return $ "<em>" <> contents <> "</em>"
534+ else do contents <- local (\env -> env { envInsideAsterisks = True }) $ inlineListToMuse lst
535+ modify $ \st -> st { stUseTags = True }
536+ return $ "*" <> contents <> "*"
537+inlineToMuse (Strong [Emph lst]) = do
538+ useTags <- gets stUseTags
539+ if useTags
540+ then do contents <- local (\env -> env { envInsideAsterisks = True }) $ inlineListToMuse lst
541+ modify $ \st -> st { stUseTags = False }
542+ return $ "<strong>*" <> contents <> "*</strong>"
543+ else if null lst || startsWithSpace lst || endsWithSpace lst
544+ then do
545+ contents <- local (\env -> env { envInsideAsterisks = False }) $ inlineListToMuse lst
546+ modify $ \st -> st { stUseTags = True }
547+ return $ "**<em>" <> contents <> "</em>**"
548+ else do
549+ contents <- local (\env -> env { envInsideAsterisks = True }) $ inlineListToMuse lst
550+ modify $ \st -> st { stUseTags = True }
551+ return $ "***" <> contents <> "***"
474552inlineToMuse (Strong lst) = do
475− contents <- inlineListToMuse lst
476− return $ "<strong>" <> contents <> "</strong>"
553+ useTags <- gets stUseTags
554+ if useTags || null lst || startsWithSpace lst || endsWithSpace lst
555+ then do contents <- inlineListToMuse lst
556+ modify $ \st -> st { stUseTags = False }
557+ return $ "<strong>" <> contents <> "</strong>"
558+ else do contents <- local (\env -> env { envInsideAsterisks = True }) $ inlineListToMuse lst
559+ modify $ \st -> st { stUseTags = True }
560+ return $ "**" <> contents <> "**"
477561inlineToMuse (Strikeout lst) = do
478562 contents <- inlineListToMuse lst
563+ modify $ \st -> st { stUseTags = False }
479564 return $ "<del>" <> contents <> "</del>"
480565inlineToMuse (Superscript lst) = do
481566 contents <- inlineListToMuse lst
567+ modify $ \st -> st { stUseTags = False }
482568 return $ "<sup>" <> contents <> "</sup>"
483569inlineToMuse (Subscript lst) = do
484570 contents <- inlineListToMuse lst
571+ modify $ \st -> st { stUseTags = False }
485572 return $ "<sub>" <> contents <> "</sub>"
486573inlineToMuse SmallCaps {} =
487574 fail "SmallCaps should be expanded before normalization"
488575inlineToMuse (Quoted SingleQuote lst) = do
489576 contents <- inlineListToMuse lst
577+ modify $ \st -> st { stUseTags = False }
490578 return $ "‘" <> contents <> "’"
491579inlineToMuse (Quoted DoubleQuote lst) = do
492580 contents <- inlineListToMuse lst
581+ modify $ \st -> st { stUseTags = False }
493582 return $ "“" <> contents <> "”"
494583inlineToMuse Cite {} =
495584 fail "Citations should be expanded before normalization"
496−inlineToMuse (Code _ str) = return $
497− "<code>" <> text (substitute "</code>" "<</code><code>/code>" str) <> "</code>"
585+inlineToMuse (Code _ str) = do
586+ useTags <- gets stUseTags
587+ modify $ \st -> st { stUseTags = False }
588+ return $ if useTags || null str || '=' `elem` str || isSpace (head str) || isSpace (last str)
589+ then "<code>" <> text (substitute "</code>" "<</code><code>/code>" str) <> "</code>"
590+ else "=" <> text str <> "="
498591inlineToMuse Math{} =
499592 fail "Math should be expanded before normalization"
500−inlineToMuse (RawInline (Format f) str) =
593+inlineToMuse (RawInline (Format f) str) = do
594+ modify $ \st -> st { stUseTags = False }
501595 return $ "<literal style=\"" <> text f <> "\">" <> text str <> "</literal>"
502596inlineToMuse LineBreak = do
503597 oneline <- asks envOneLine
598+ modify $ \st -> st { stUseTags = False }
504599 return $ if oneline then "<br>" else "<br>" <> cr
505−inlineToMuse Space = return space
600+inlineToMuse Space = do
601+ modify $ \st -> st { stUseTags = False }
602+ return space
506603inlineToMuse SoftBreak = do
507604 oneline <- asks envOneLine
508605 wrapText <- asks $ writerWrapText . envOptions
606+ modify $ \st -> st { stUseTags = False }
509607 return $ if not oneline && wrapText == WrapPreserve then cr else space
510608inlineToMuse (Link _ txt (src, _)) =
511609 case txt of
512− [Str x] | escapeURI x == src ->
610+ [Str x] | escapeURI x == src -> do
611+ modify $ \st -> st { stUseTags = False }
513612 return $ "[[" <> text (escapeLink x) <> "]]"
514613 _ -> do contents <- local (\env -> env { envInsideLinkDescription = True }) $ inlineListToMuse txt
614+ modify $ \st -> st { stUseTags = False }
515615 return $ "[[" <> text (escapeLink src) <> "][" <> contents <> "]]"
516616 where escapeLink lnk = if isImageUrl lnk then "URL:" ++ urlEscapeBrackets lnk else urlEscapeBrackets lnk
517617 -- Taken from muse-image-regexp defined in Emacs Muse file lisp/muse-regexps.el
@@ -537,11 +637,14 @@ inlineToMuse (Image attr@(_, classes, _) inlines (source, title)) = do
537637 let rightalign = if "align-right" `elem` classes
538638 then " r"
539639 else ""
640+ modify $ \st -> st { stUseTags = False }
540641 return $ "[[" <> text (urlEscapeBrackets source ++ width ++ leftalign ++ rightalign) <> "]" <> title' <> "]"
541642inlineToMuse (Note contents) = do
542643 -- add to notes in state
543644 notes <- gets stNotes
544− modify $ \st -> st { stNotes = contents:notes }
645+ modify $ \st -> st { stNotes = contents:notes
646+ , stUseTags = False
647+ }
545648 let ref = show $ length notes + 1
546649 return $ "[" <> text ref <> "]"
547650inlineToMuse (Span (anchor,names,_) inlines) = do
@@ -549,6 +652,7 @@ inlineToMuse (Span (anchor,names,_) inlines) = do
549652 let anchorDoc = if null anchor
550653 then mempty
551654 else text ('#':anchor) <> space
655+ modify $ \st -> st { stUseTags = False }
552656 return $ anchorDoc <> (if null inlines && not (null anchor)
553657 then mempty
554658 else (if null names