pandoc-3.13: src/Text/Pandoc/Writers/Muse.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ViewPatterns #-}
{- |
Module : Text.Pandoc.Writers.Muse
Copyright : Copyright (C) 2017-2020 Alexander Krotov
License : GNU GPL, version 2 or above
Maintainer : Alexander Krotov <ilabdsf@gmail.com>
Stability : stable
Portability : portable
Conversion of 'Pandoc' documents to Muse.
This module is mostly intended for <https://amusewiki.org/ Amusewiki> markup support,
as described by <https://amusewiki.org/library/manual Text::Amuse markup manual>.
Original <https://www.gnu.org/software/emacs-muse/ Emacs Muse> markup support
is a secondary goal.
Where Text::Amuse markup
<https://metacpan.org/pod/Text::Amuse#DIFFERENCES-WITH-THE-ORIGINAL-EMACS-MUSE-MARKUP differs>
from <https://www.gnu.org/software/emacs-muse/manual/ Emacs Muse markup>,
Text::Amuse markup is supported.
For example, native tables are always used instead of Org Mode tables.
However, @\<literal style="html">@ tag is used for HTML raw blocks
even though it is supported only in Emacs Muse.
-}
module Text.Pandoc.Writers.Muse (writeMuse) where
import Control.Monad (zipWithM)
import Control.Monad.Except (throwError)
import Control.Monad.Reader
( asks, MonadReader(local), ReaderT(runReaderT) )
import Control.Monad.State.Strict
( StateT, gets, modify, evalStateT )
import Data.Char (isAlphaNum, isAsciiLower, isAsciiUpper, isDigit, isSpace)
import Data.Default
import Data.List (intersperse, transpose)
import Data.List.NonEmpty (nonEmpty, NonEmpty(..))
import qualified Data.Set as Set
import qualified Data.Text as T
import Data.Text (Text)
import System.FilePath (takeExtension)
import Text.Pandoc.Class.PandocMonad (PandocMonad)
import Text.Pandoc.Definition
import Text.Pandoc.Error
import Text.Pandoc.ImageSize
import Text.Pandoc.Options
import Text.DocLayout
import Text.Pandoc.Shared
import Text.Pandoc.URI
import Text.Pandoc.Writers.Math
import Text.Pandoc.Writers.Shared
type Notes = [[Block]]
type Muse m = ReaderT WriterEnv (StateT WriterState m)
data WriterEnv =
WriterEnv { envOptions :: WriterOptions
, envTopLevel :: Bool
, envInsideBlock :: Bool
, envInlineStart :: Bool -- ^ True if there is only whitespace since last newline
, envInsideLinkDescription :: Bool -- ^ Escape ] if True
, envAfterSpace :: Bool -- ^ There is whitespace (not just newline) before
, envOneLine :: Bool -- ^ True if newlines are not allowed
, envInsideAsterisks :: Bool -- ^ True if outer element is emphasis with asterisks
, envNearAsterisks :: Bool -- ^ Rendering inline near asterisks
}
data WriterState =
WriterState { stNotes :: Notes
, stNoteNum :: Int
, stIds :: Set.Set Text
, stUseTags :: Bool -- ^ Use tags for emphasis, for example because previous character is a letter
}
instance Default WriterState
where def = WriterState { stNotes = []
, stNoteNum = 1
, stIds = Set.empty
, stUseTags = False
}
evalMuse :: PandocMonad m => Muse m a -> WriterEnv -> WriterState -> m a
evalMuse document env = evalStateT $ runReaderT document env
-- | Convert Pandoc to Muse.
writeMuse :: PandocMonad m
=> WriterOptions
-> Pandoc
-> m Text
writeMuse opts document =
evalMuse (pandocToMuse document) env def
where env = WriterEnv { envOptions = opts
, envTopLevel = True
, envInsideBlock = False
, envInlineStart = True
, envInsideLinkDescription = False
, envAfterSpace = False
, envOneLine = False
, envInsideAsterisks = False
, envNearAsterisks = False
}
-- | Return Muse representation of document.
pandocToMuse :: PandocMonad m
=> Pandoc
-> Muse m Text
pandocToMuse (Pandoc meta blocks) = do
opts <- asks envOptions
metadata <- metaToContext opts
blockListToMuse
(fmap chomp . inlineListToMuse)
meta
body <- blockListToMuse blocks
notes <- currentNotesToMuse
let main = body $+$ notes
let context = defField "body" main metadata
return $ renderWithTemplate opts context main
-- | Helper function for flatBlockListToMuse
-- | Render all blocks and insert blank lines between the first two
catWithBlankLines :: PandocMonad m
=> [Block] -- ^ List of block elements
-> Int -- ^ Number of blank lines
-> Muse m (Doc Text)
catWithBlankLines (b : bs) n = do
b' <- blockToMuseWithNotes b
bs' <- flatBlockListToMuse bs
return $ b' <> blanklines n <> bs'
catWithBlankLines _ _ = error "Expected at least one block"
-- | Convert list of Pandoc block elements to Muse
-- | without setting envTopLevel.
flatBlockListToMuse :: PandocMonad m
=> [Block] -- ^ List of block elements
-> Muse m (Doc Text)
flatBlockListToMuse bs@(BulletList _ : BulletList _ : _) = catWithBlankLines bs 2
flatBlockListToMuse bs@(OrderedList (_, style1, _) _ : OrderedList (_, style2, _) _ : _) =
catWithBlankLines bs (if style1' == style2' then 2 else 0)
where
style1' = normalizeStyle style1
style2' = normalizeStyle style2
normalizeStyle DefaultStyle = Decimal
normalizeStyle s = s
flatBlockListToMuse bs@(DefinitionList _ : DefinitionList _ : _) = catWithBlankLines bs 2
flatBlockListToMuse bs@(_ : _) = catWithBlankLines bs 0
flatBlockListToMuse [] = return mempty
simpleTable :: PandocMonad m
=> [Inline]
-> [[Block]]
-> [[[Block]]]
-> Muse m (Doc Text)
simpleTable caption headers rows = do
topLevel <- asks envTopLevel
caption' <- inlineListToMuse caption
headers' <- mapM blockListToMuse headers
rows' <- mapM (mapM blockListToMuse) rows
let widthsInChars = maybe 0 maximum . nonEmpty . map offset <$>
transpose (headers' : rows')
let hpipeBlocks sep blocks = hcat $ intersperse sep' blocks
where sep' = lblock (T.length sep) $ literal sep
let makeRow sep = hpipeBlocks sep . zipWith lblock widthsInChars
let head' = makeRow " || " headers'
rows'' <- mapM (\row -> makeRow rowSeparator <$> mapM blockListToMuse row) rows
let body = vcat rows''
return $ (if topLevel then nest 1 else id) ((if noHeaders then empty else head')
$$ body
$$ (if null caption then empty else "|+ " <> caption' <> " +|"))
$$ blankline
where noHeaders = all null headers
rowSeparator = if noHeaders then " | " else " | "
-- | Convert list of Pandoc block elements to Muse.
blockListToMuse :: PandocMonad m
=> [Block] -- ^ List of block elements
-> Muse m (Doc Text)
blockListToMuse =
local (\env -> env { envTopLevel = not (envInsideBlock env)
, envInsideBlock = True
}) . flatBlockListToMuse
-- | Convert Pandoc block element to Muse.
blockToMuse :: PandocMonad m
=> Block -- ^ Block element
-> Muse m (Doc Text)
blockToMuse (Plain inlines) = inlineListToMuse' inlines
blockToMuse (Para inlines) = do
contents <- inlineListToMuse' inlines
return $ contents <> blankline
blockToMuse (LineBlock lns) = do
lns' <- local (\env -> env { envOneLine = True }) $ mapM inlineListToMuse lns
return $ nowrap $ vcat (map (literal "> " <>) lns') <> blankline
blockToMuse (CodeBlock (_,_,_) str) =
return $ "<example>" $$ literal str $$ "</example>" $$ blankline
blockToMuse (RawBlock (Format format) str) =
return $ blankline $$ "<literal style=\"" <> literal format <> "\">" $$
literal str $$ "</literal>" $$ blankline
blockToMuse (BlockQuote blocks) = do
contents <- flatBlockListToMuse blocks
return $ blankline
<> "<quote>"
$$ nest 0 contents -- nest 0 to remove trailing blank lines
$$ "</quote>"
<> blankline
blockToMuse (OrderedList (start, style, _) items) = do
let markers = take (length items) $ orderedListMarkers
(start, style, Period)
contents <- zipWithM orderedListItemToMuse markers items
topLevel <- asks envTopLevel
return $ (if topLevel then nest 1 else id) (vcat contents) $$ blankline
where orderedListItemToMuse :: PandocMonad m
=> Text -- marker for list item
-> [Block] -- list item (list of blocks)
-> Muse m (Doc Text)
orderedListItemToMuse marker item = hang (T.length marker + 1) (literal marker <> space)
<$> blockListToMuse item
blockToMuse (BulletList items) = do
contents <- mapM bulletListItemToMuse items
topLevel <- asks envTopLevel
return $ (if topLevel then nest 1 else id) (vcat contents) $$ blankline
where bulletListItemToMuse :: PandocMonad m
=> [Block]
-> Muse m (Doc Text)
bulletListItemToMuse item = do
modify $ \st -> st { stUseTags = False }
hang 2 "- " <$> blockListToMuse item
blockToMuse (DefinitionList items) = do
contents <- mapM definitionListItemToMuse items
topLevel <- asks envTopLevel
return $ (if topLevel then nest 1 else id) (vcat contents) $$ blankline
where definitionListItemToMuse :: PandocMonad m
=> ([Inline], [[Block]])
-> Muse m (Doc Text)
definitionListItemToMuse (label, defs) = do
modify $ \st -> st { stUseTags = False }
label' <- local (\env -> env { envOneLine = True, envAfterSpace = True }) $ inlineListToMuse' label
let ind = offset' label' -- using Text.DocLayout.offset results in round trip failures
hang ind (nowrap label') . vcat <$> mapM descriptionToMuse defs
where offset' d = maximum (0 :| map T.length
(T.lines $ render Nothing d))
descriptionToMuse :: PandocMonad m
=> [Block]
-> Muse m (Doc Text)
descriptionToMuse desc = hang 4 " :: " <$> blockListToMuse desc
blockToMuse (Header level (ident,_,_) inlines) = do
opts <- asks envOptions
topLevel <- asks envTopLevel
contents <- local (\env -> env { envOneLine = True }) $ inlineListToMuse' inlines
ids <- gets stIds
let autoId = uniqueIdent (writerExtensions opts) inlines ids
modify $ \st -> st{ stIds = Set.insert autoId ids }
let attr' = if T.null ident || (isEnabled Ext_auto_identifiers opts && ident == autoId)
then empty
else "#" <> literal ident <> cr
let header' = if topLevel then literal (T.replicate level "*") <> space else mempty
return $ blankline <> attr' $$ nowrap (header' <> contents) <> blankline
-- https://www.gnu.org/software/emacs-muse/manual/muse.html#Horizontal-Rules-and-Anchors
blockToMuse HorizontalRule = return $ blankline $$ "----" $$ blankline
blockToMuse (Table _ blkCapt specs thead@(TableHead hattr hrows) tbody tfoot) =
if isSimple && numcols > 1
then simpleTable caption headers rows
else do
opts <- asks envOptions
let tbody' = case hrows of
[] -> tbody
_ -> TableBody nullAttr 0 [] hrows : tbody
gridTable opts blocksToDoc specs (TableHead hattr []) tbody' tfoot
where
(caption, aligns, widths, headers, rows) = toLegacyTable blkCapt specs thead tbody tfoot
blocksToDoc opts blocks =
local (\env -> env { envOptions = opts }) $ blockListToMuse blocks
numcols = maximum
(length aligns :| length widths : map length (headers:rows))
isSimple = onlySimpleTableCells (headers : rows) && all (== 0) widths
blockToMuse (Div _ bs) = flatBlockListToMuse bs
blockToMuse (Figure attr capt body) = do
blockToMuse (figureDiv attr capt body)
-- | Return Muse representation of notes collected so far.
currentNotesToMuse :: PandocMonad m
=> Muse m (Doc Text)
currentNotesToMuse = do
notes <- reverse <$> gets stNotes
modify $ \st -> st { stNotes = mempty }
notesToMuse notes
-- | Return Muse representation of notes.
notesToMuse :: PandocMonad m
=> Notes
-> Muse m (Doc Text)
notesToMuse notes = do
n <- gets stNoteNum
modify $ \st -> st { stNoteNum = stNoteNum st + length notes }
vsep <$> zipWithM noteToMuse [n ..] notes
-- | Return Muse representation of a note.
noteToMuse :: PandocMonad m
=> Int
-> [Block]
-> Muse m (Doc Text)
noteToMuse num note = do
res <- hang (T.length marker) (literal marker) <$>
local (\env -> env { envInsideBlock = True
, envInlineStart = True
, envAfterSpace = True
}) (blockListToMuse note)
return $ res <> blankline
where
marker = "[" <> tshow num <> "] "
-- | Return Muse representation of block and accumulated notes.
blockToMuseWithNotes :: PandocMonad m
=> Block
-> Muse m (Doc Text)
blockToMuseWithNotes blk = do
topLevel <- asks envTopLevel
opts <- asks envOptions
let hdrToMuse hdr@Header{} = do
b <- blockToMuse hdr
if topLevel && writerReferenceLocation opts == EndOfSection
then do
notes <- currentNotesToMuse
return $ notes $+$ b
else
return b
hdrToMuse b = blockToMuse b
b <- hdrToMuse blk
if topLevel && writerReferenceLocation opts == EndOfBlock
then do
notes <- currentNotesToMuse
return $ b $+$ notes <> blankline
else return b
-- | Escape special characters for Muse.
escapeText :: Text -> Text
escapeText s =
"<verbatim>" <>
T.replace "</verbatim>" "<</verbatim><verbatim>/verbatim>" s <>
"</verbatim>"
-- | Replace newlines with spaces
replaceNewlines :: Text -> Text
replaceNewlines = T.map $ \c ->
if c == '\n' then ' ' else c
startsWithMarker :: (Char -> Bool) -> Text -> Bool
startsWithMarker f t = case T.uncons $ T.dropWhile f' t of
Just ('.', xs) -> T.null xs || isSpace (T.head xs)
_ -> False
where
f' c = c == ' ' || f c
containsNotes :: Char -> Char -> Text -> Bool
containsNotes left right = p . T.unpack -- This ought to be a parser
where p (left':xs)
| left' == left = q xs || p xs
| otherwise = p xs
p "" = False
q (x:xs)
| x `elem` ("123456789"::String) = r xs || p xs
| otherwise = p xs
q [] = False
r ('0':xs) = r xs || p xs
r xs = s xs || q xs || p xs
s (right':xs)
| right' == right = True
| otherwise = p xs
s [] = False
-- | Return True if string should be escaped with <verbatim> tags
shouldEscapeText :: PandocMonad m
=> Text
-> Muse m Bool
shouldEscapeText s = do
insideLink <- asks envInsideLinkDescription
return $ T.null s ||
T.any (`elem` ("#*<=|" :: String)) s ||
"::" `T.isInfixOf` s ||
"~~" `T.isInfixOf` s ||
"[[" `T.isInfixOf` s ||
">>>" `T.isInfixOf` s ||
("]" `T.isInfixOf` s && insideLink) ||
containsNotes '[' ']' s ||
containsNotes '{' '}' s
-- | Escape special characters for Muse if needed.
conditionalEscapeText :: PandocMonad m
=> Text
-> Muse m Text
conditionalEscapeText s = do
shouldEscape <- shouldEscapeText s
return $ if shouldEscape
then escapeText s
else s
-- Expand Math and Cite before normalizing inline list
preprocessInlineList :: PandocMonad m
=> [Inline]
-> m [Inline]
preprocessInlineList (Math t str:xs) = (++) <$> texMathToInlines t str <*> preprocessInlineList xs
-- Amusewiki does not support <cite> tag,
-- and Emacs Muse citation support is limited
-- (https://www.gnu.org/software/emacs-muse/manual/html_node/Citations.html#Citation)
-- so just fallback to expanding inlines.
preprocessInlineList (Cite _ lst:xs) = (lst ++) <$> preprocessInlineList xs
preprocessInlineList (x:xs) = (x:) <$> preprocessInlineList xs
preprocessInlineList [] = return []
replaceSmallCaps :: Inline -> Inline
replaceSmallCaps (SmallCaps lst) = Emph lst
replaceSmallCaps x = x
removeKeyValues :: Inline -> Inline
removeKeyValues (Code (i, cls, _) xs) = Code (i, cls, []) xs
-- Do not remove attributes from Link
-- Do not remove attributes, such as "width", from Image
-- Do not remove attributes, such as "dir", from Span
removeKeyValues x = x
normalizeInlineList :: [Inline] -> [Inline]
normalizeInlineList (Str "" : xs)
= normalizeInlineList xs
normalizeInlineList (x : Str "" : xs)
= normalizeInlineList (x:xs)
normalizeInlineList (Str x1 : Str x2 : xs)
= normalizeInlineList $ Str (x1 <> x2) : xs
normalizeInlineList (Emph x1 : Emph x2 : ils)
= normalizeInlineList $ Emph (x1 <> x2) : ils
normalizeInlineList (Strong x1 : Strong x2 : ils)
= normalizeInlineList $ Strong (x1 <> x2) : ils
normalizeInlineList (Strikeout x1 : Strikeout x2 : ils)
= normalizeInlineList $ Strikeout (x1 <> x2) : ils
normalizeInlineList (Superscript x1 : Superscript x2 : ils)
= normalizeInlineList $ Superscript (x1 <> x2) : ils
normalizeInlineList (Subscript x1 : Subscript x2 : ils)
= normalizeInlineList $ Subscript (x1 <> x2) : ils
normalizeInlineList (SmallCaps x1 : SmallCaps x2 : ils)
= normalizeInlineList $ SmallCaps (x1 <> x2) : ils
normalizeInlineList (Code _ x1 : Code _ x2 : ils)
= normalizeInlineList $ Code nullAttr (x1 <> x2) : ils
normalizeInlineList (RawInline f1 x1 : RawInline f2 x2 : ils) | f1 == f2
= normalizeInlineList $ RawInline f1 (x1 <> x2) : ils
-- Do not join Span's during normalization
normalizeInlineList (x:xs) = x : normalizeInlineList xs
normalizeInlineList [] = []
fixNotes :: [Inline] -> [Inline]
fixNotes [] = []
fixNotes (Space : n@Note{} : rest) = Str " " : n : fixNotes rest
fixNotes (SoftBreak : n@Note{} : rest) = Str " " : n : fixNotes rest
fixNotes (x:xs) = x : fixNotes xs
startsWithSpace :: [Inline] -> Bool
startsWithSpace (Space:_) = True
startsWithSpace (SoftBreak:_) = True
startsWithSpace (Str s:_) = stringStartsWithSpace s
startsWithSpace _ = False
endsWithSpace :: [Inline] -> Bool
endsWithSpace [Space] = True
endsWithSpace [SoftBreak] = True
endsWithSpace [Str s] = stringEndsWithSpace s
endsWithSpace (_:xs) = endsWithSpace xs
endsWithSpace [] = False
urlEscapeBrackets :: Text -> Text
urlEscapeBrackets = T.concatMap $ \c -> case c of
']' -> "%5D"
_ -> T.singleton c
isHorizontalRule :: Text -> Bool
isHorizontalRule s = T.length s >= 4 && T.all (== '-') s
stringStartsWithSpace :: Text -> Bool
stringStartsWithSpace = maybe False (isSpace . fst) . T.uncons
stringEndsWithSpace :: Text -> Bool
stringEndsWithSpace = maybe False (isSpace . snd) . T.unsnoc
fixOrEscape :: Bool -> Inline -> Bool
fixOrEscape b (Str s) = fixOrEscapeStr b s
where
fixOrEscapeStr sp t = case T.uncons t of
Just ('-', xs)
| T.null xs -> sp
| otherwise -> (sp && isSpace (T.head xs)) || isHorizontalRule t
Just (';', xs)
| T.null xs -> not sp
| otherwise -> not sp && isSpace (T.head xs)
Just ('>', xs)
| T.null xs -> True
| otherwise -> isSpace (T.head xs)
_ -> (sp && (startsWithMarker isDigit s ||
startsWithMarker isAsciiLower s ||
startsWithMarker isAsciiUpper s))
|| stringStartsWithSpace s
fixOrEscape _ Space = True
fixOrEscape _ SoftBreak = True
fixOrEscape _ _ = False
inlineListStartsWithAlnum :: PandocMonad m
=> [Inline]
-> Muse m Bool
inlineListStartsWithAlnum (Str s:_) = do
esc <- shouldEscapeText s
return $ esc || isAlphaNum (T.head s)
inlineListStartsWithAlnum _ = return False
-- | Convert list of Pandoc inline elements to Muse
renderInlineList :: PandocMonad m
=> [Inline]
-> Muse m (Doc Text)
renderInlineList [] = pure ""
renderInlineList (x:xs) = do
start <- asks envInlineStart
afterSpace <- asks envAfterSpace
topLevel <- asks envTopLevel
insideAsterisks <- asks envInsideAsterisks
nearAsterisks <- asks envNearAsterisks
useTags <- gets stUseTags
alnumNext <- inlineListStartsWithAlnum xs
let newUseTags = useTags || alnumNext
modify $ \st -> st { stUseTags = newUseTags }
r <- local (\env -> env { envInlineStart = False
, envInsideAsterisks = False
, envNearAsterisks = nearAsterisks || (null xs && insideAsterisks)
}) $ inlineToMuse x
opts <- asks envOptions
let isNewline = (x == SoftBreak && writerWrapText opts == WrapPreserve) || x == LineBreak
lst' <- local (\env -> env { envInlineStart = isNewline
, envAfterSpace = x == Space || (not topLevel && isNewline)
, envNearAsterisks = False
}) $ renderInlineList xs
if start && fixOrEscape afterSpace x
then pure (literal "<verbatim></verbatim>" <> r <> lst')
else pure (r <> lst')
-- | Normalize and convert list of Pandoc inline elements to Muse.
inlineListToMuse :: PandocMonad m
=> [Inline]
-> Muse m (Doc Text)
inlineListToMuse lst = do
lst' <- normalizeInlineList . fixNotes <$> preprocessInlineList (map (removeKeyValues . replaceSmallCaps) lst)
insideAsterisks <- asks envInsideAsterisks
start <- asks envInlineStart
modify $ \st -> st { stUseTags = False } -- Previous character is likely a '>' or some other markup
if start && null lst'
then pure "<verbatim></verbatim>"
else local (\env -> env { envNearAsterisks = insideAsterisks }) $ renderInlineList lst'
inlineListToMuse' :: PandocMonad m => [Inline] -> Muse m (Doc Text)
inlineListToMuse' lst = do
topLevel <- asks envTopLevel
afterSpace <- asks envAfterSpace
local (\env -> env { envInlineStart = True
, envAfterSpace = afterSpace || not topLevel
}) $ inlineListToMuse lst
emphasis :: PandocMonad m => Text -> Text -> [Inline] -> Muse m (Doc Text)
emphasis b e lst = do
contents <- local (\env -> env { envInsideAsterisks = inAsterisks }) $ inlineListToMuse lst
modify $ \st -> st { stUseTags = useTags }
return $ literal b <> contents <> literal e
where inAsterisks = T.last b == '*' || T.head e == '*'
useTags = T.last e /= '>'
-- | Convert Pandoc inline element to Muse.
inlineToMuse :: PandocMonad m
=> Inline
-> Muse m (Doc Text)
inlineToMuse (Str str) = do
escapedStr <- conditionalEscapeText $ replaceNewlines str
let useTags = isAlphaNum $ T.last escapedStr -- escapedStr is never empty because empty strings are escaped
modify $ \st -> st { stUseTags = useTags }
return $ literal escapedStr
inlineToMuse (Emph [Strong lst]) = do
useTags <- gets stUseTags
let lst' = normalizeInlineList lst
if useTags
then emphasis "<em>**" "**</em>" lst'
else if null lst' || startsWithSpace lst' || endsWithSpace lst'
then emphasis "*<strong>" "</strong>*" lst'
else emphasis "***" "***" lst'
inlineToMuse (Emph lst) = do
useTags <- gets stUseTags
let lst' = normalizeInlineList lst
if useTags || null lst' || startsWithSpace lst' || endsWithSpace lst'
then emphasis "<em>" "</em>" lst'
else emphasis "*" "*" lst'
inlineToMuse (Strong [Emph lst]) = do
useTags <- gets stUseTags
let lst' = normalizeInlineList lst
if useTags
then emphasis "<strong>*" "*</strong>" lst'
else if null lst' || startsWithSpace lst' || endsWithSpace lst'
then emphasis "**<em>" "</em>**" lst'
else emphasis "***" "***" lst'
-- Underline is only supported in Emacs Muse mode.
inlineToMuse (Underline lst) = do
opts <- asks envOptions
contents <- inlineListToMuse lst
if isEnabled Ext_amuse opts
then return $ "_" <> contents <> "_"
else inlineToMuse (Emph lst)
inlineToMuse (Strong lst) = do
useTags <- gets stUseTags
let lst' = normalizeInlineList lst
if useTags || null lst' || startsWithSpace lst' || endsWithSpace lst'
then emphasis "<strong>" "</strong>" lst'
else emphasis "**" "**" lst'
inlineToMuse (Strikeout lst) = do
contents <- inlineListToMuse lst
modify $ \st -> st { stUseTags = False }
return $ "<del>" <> contents <> "</del>"
inlineToMuse (Superscript lst) = do
contents <- inlineListToMuse lst
modify $ \st -> st { stUseTags = False }
return $ "<sup>" <> contents <> "</sup>"
inlineToMuse (Subscript lst) = do
contents <- inlineListToMuse lst
modify $ \st -> st { stUseTags = False }
return $ "<sub>" <> contents <> "</sub>"
inlineToMuse SmallCaps {} =
throwError $ PandocShouldNeverHappenError
"SmallCaps should be expanded before normalization"
inlineToMuse (Quoted SingleQuote lst) = do
contents <- inlineListToMuse lst
modify $ \st -> st { stUseTags = False }
return $ "‘" <> contents <> "’"
inlineToMuse (Quoted DoubleQuote lst) = do
contents <- inlineListToMuse lst
modify $ \st -> st { stUseTags = False }
return $ "“" <> contents <> "”"
inlineToMuse Cite {} =
throwError $ PandocShouldNeverHappenError
"Citations should be expanded before normalization"
inlineToMuse (Code _ str) = do
useTags <- gets stUseTags
modify $ \st -> st { stUseTags = False }
return $ if useTags || T.null str || T.any (== '=') str
|| isSpace (T.head str) || isSpace (T.last str)
then "<code>" <> literal (T.replace "</code>" "<</code><code>/code>" str) <> "</code>"
else "=" <> literal str <> "="
inlineToMuse Math{} =
throwError $ PandocShouldNeverHappenError
"Math should be expanded before normalization"
inlineToMuse (RawInline (Format f) str) = do
modify $ \st -> st { stUseTags = False }
return $ "<literal style=\"" <> literal f <> "\">" <> literal str <> "</literal>"
inlineToMuse LineBreak = do
oneline <- asks envOneLine
modify $ \st -> st { stUseTags = False }
return $ if oneline then "<br>" else "<br>" <> cr
inlineToMuse Space = do
modify $ \st -> st { stUseTags = False }
return space
inlineToMuse SoftBreak = do
oneline <- asks envOneLine
wrapText <- asks $ writerWrapText . envOptions
modify $ \st -> st { stUseTags = False }
return $ if not oneline && wrapText == WrapPreserve then cr else space
inlineToMuse (Link _ txt (src, _)) =
case txt of
[Str x] | escapeURI x == src -> do
modify $ \st -> st { stUseTags = False }
return $ "[[" <> literal (escapeLink x) <> "]]"
_ -> do contents <- local (\env -> env { envInsideLinkDescription = True }) $ inlineListToMuse txt
modify $ \st -> st { stUseTags = False }
return $ "[[" <> literal (escapeLink src) <> "][" <> contents <> "]]"
where escapeLink lnk = if isImageUrl lnk then "URL:" <> urlEscapeBrackets lnk else urlEscapeBrackets lnk
-- Taken from muse-image-regexp defined in Emacs Muse file lisp/muse-regexps.el
imageExtensions = [".eps", ".gif", ".jpg", ".jpeg", ".pbm", ".png", ".tiff", ".xbm", ".xpm"]
isImageUrl = (`elem` imageExtensions) . takeExtension . T.unpack
inlineToMuse (Image attr alt (source,T.stripPrefix "fig:" -> Just title)) =
inlineToMuse (Image attr alt (source,title))
inlineToMuse (Image attr@(_, classes, _) inlines (source, title)) = do
opts <- asks envOptions
alt <- local (\env -> env { envInsideLinkDescription = True }) $ inlineListToMuse inlines
title' <- if T.null title
then if null inlines
then return ""
else return $ "[" <> alt <> "]"
else do s <- local (\env -> env { envInsideLinkDescription = True }) $ conditionalEscapeText title
return $ "[" <> literal s <> "]"
let width = case dimension Width attr of
Just (Percent x) | isEnabled Ext_amuse opts -> " " <> tshow (round x :: Integer)
_ -> ""
let leftalign = if "align-left" `elem` classes
then " l"
else ""
let rightalign = if "align-right" `elem` classes
then " r"
else ""
modify $ \st -> st { stUseTags = False }
return $ "[[" <> literal (urlEscapeBrackets source <> width <> leftalign <> rightalign) <> "]" <> title' <> "]"
inlineToMuse (Note contents) = do
-- add to notes in state
notes <- gets stNotes
modify $ \st -> st { stNotes = contents:notes
, stUseTags = False
}
n <- gets stNoteNum
let ref = tshow $ n + length notes
return $ "[" <> literal ref <> "]"
inlineToMuse (Span (anchor,names,kvs) inlines) = do
contents <- inlineListToMuse inlines
let (contents', hasDir) = case lookup "dir" kvs of
Just "rtl" -> ("<<<" <> contents <> ">>>", True)
Just "ltr" -> (">>>" <> contents <> "<<<", True)
_ -> (contents, False)
let anchorDoc = if T.null anchor
then mempty
else literal ("#" <> anchor) <> space
modify $ \st -> st { stUseTags = False }
return $ anchorDoc <>
if null inlines && not (T.null anchor)
then mempty
else case names of
[] | hasDir -> contents'
| otherwise -> "<class>" <> contents' <> "</class>"
(n:_) -> "<class name=\"" <> literal n <> "\">" <>
contents' <> "</class>"