{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE OverloadedStrings #-}
module Text.Pandoc.Readers.Muse (readMuse) where
import Control.Monad
import Control.Monad.Reader
import Control.Monad.Except (throwError)
import Data.Bifunctor
import Data.Default
import Data.List (transpose, uncons)
import qualified Data.Map as M
import qualified Data.Set as Set
import Data.Maybe (fromMaybe, isNothing, maybeToList)
import Data.Text (Text)
import qualified Data.Text as T
import Text.Pandoc.Builder (Blocks, Inlines, underline)
import qualified Text.Pandoc.Builder as B
import Text.Pandoc.Class.PandocMonad (PandocMonad (..))
import Text.Pandoc.Definition
import Text.Pandoc.Error (PandocError (PandocParsecError))
import Text.Pandoc.Logging
import Text.Pandoc.Options
import Text.Pandoc.Parsing hiding (F)
import Text.Pandoc.Shared (crFilter, trimr, tshow)
readMuse :: PandocMonad m
=> ReaderOptions
-> Text
-> m Pandoc
readMuse opts s = do
let input = crFilter s
res <- flip runReaderT def $ runParserT parseMuse def{ museOptions = opts } "source" input
case res of
Left e -> throwError $ PandocParsecError input e
Right d -> return d
type F = Future MuseState
data MuseState = MuseState { museMeta :: F Meta
, museOptions :: ReaderOptions
, museIdentifierList :: Set.Set Text
, museLastSpacePos :: Maybe SourcePos
, museLastStrPos :: Maybe SourcePos
, museLogMessages :: [LogMessage]
, museNotes :: M.Map Text (SourcePos, F Blocks)
}
instance Default MuseState where
def = MuseState { museMeta = return nullMeta
, museOptions = def
, museIdentifierList = Set.empty
, museLastStrPos = Nothing
, museLastSpacePos = Nothing
, museLogMessages = []
, museNotes = M.empty
}
data MuseEnv =
MuseEnv { museInLink :: Bool
, museInPara :: Bool
}
instance Default MuseEnv where
def = MuseEnv { museInLink = False
, museInPara = False
}
type MuseParser m = ParserT Text MuseState (ReaderT MuseEnv m)
instance HasReaderOptions MuseState where
extractReaderOptions = museOptions
instance HasIdentifierList MuseState where
extractIdentifierList = museIdentifierList
updateIdentifierList f st = st{ museIdentifierList = f $ museIdentifierList st }
instance HasLastStrPosition MuseState where
setLastStrPos pos st = st{ museLastStrPos = pos }
getLastStrPos st = museLastStrPos st
instance HasLogMessages MuseState where
addLogMessage m s = s{ museLogMessages = m : museLogMessages s }
getLogMessages = reverse . museLogMessages
updateLastSpacePos :: Monad m => MuseParser m ()
updateLastSpacePos = getPosition >>= \pos ->
updateState $ \s -> s { museLastSpacePos = Just pos }
parseMuse :: PandocMonad m => MuseParser m Pandoc
parseMuse = do
many directive
blocks <- (:) <$> parseBlocks <*> many parseSection
eof
st <- getState
runF (Pandoc <$> museMeta st <*> fmap B.toList (mconcat blocks)) st <$ reportLogMessages
lchop :: Text -> Text
lchop s = case T.uncons s of
Just ('\n', xs) -> xs
_ -> s
rchop :: Text -> Text
rchop s = case T.unsnoc s of
Just (xs, '\n') -> xs
_ -> s
unindent :: Text -> Text
unindent = rchop . T.intercalate "\n" . dropSpacePrefix . T.splitOn "\n" . lchop
dropSpacePrefix :: [Text] -> [Text]
dropSpacePrefix lns = T.drop maxIndent <$> lns
where isSpaceChar c = c == ' ' || c == '\t'
maxIndent = length $ takeWhile (isSpaceChar . T.head) $ takeWhile same $ T.transpose lns
same t = case T.uncons t of
Just (c, cs) -> T.all (== c) cs
Nothing -> True
atStart :: PandocMonad m => MuseParser m ()
atStart = do
pos <- getPosition
st <- getState
guard $ museLastStrPos st /= Just pos
noSpaceBefore :: PandocMonad m => MuseParser m ()
noSpaceBefore = do
pos <- getPosition
st <- getState
guard $ museLastSpacePos st /= Just pos
firstColumn :: PandocMonad m => MuseParser m ()
firstColumn = getPosition >>= \pos -> guard (sourceColumn pos == 1)
eol :: Stream s m Char => ParserT s st m ()
eol = void newline <|> eof
getIndent :: PandocMonad m
=> MuseParser m Int
getIndent = subtract 1 . sourceColumn <$ many spaceChar <*> getPosition
openTag :: PandocMonad m => Text -> MuseParser m [(Text, Text)]
openTag tag = try $
char '<' *> textStr tag *> manyTill attr (char '>')
where
attr = try $ (,)
<$ many1 spaceChar
<*> many1Char (noneOf "=\n")
<* string "=\""
<*> manyTillChar (noneOf "\"") (char '"')
closeTag :: PandocMonad m => Text -> MuseParser m ()
closeTag tag = try $ string "</" *> textStr tag *> void (char '>')
htmlAttrToPandoc :: [(Text, Text)] -> Attr
htmlAttrToPandoc attrs = (ident, classes, keyvals)
where
ident = fromMaybe "" $ lookup "id" attrs
classes = maybe [] T.words $ lookup "class" attrs
keyvals = [(k,v) | (k,v) <- attrs, k /= "id", k /= "class"]
parseHtmlContent :: PandocMonad m
=> Text
-> MuseParser m (Attr, F Blocks)
parseHtmlContent tag = try $ getIndent >>= \indent -> (,)
<$> fmap htmlAttrToPandoc (openTag tag)
<* manyTill spaceChar eol
<*> allowPara (parseBlocksTill (try $ indentWith indent *> closeTag tag))
<* manyTill spaceChar eol
parseDirectiveKey :: PandocMonad m => MuseParser m Text
parseDirectiveKey = char '#' *> manyChar (letter <|> char '-')
parseEmacsDirective :: PandocMonad m => MuseParser m (Text, F Inlines)
parseEmacsDirective = (,)
<$> parseDirectiveKey
<* spaceChar
<*> (trimInlinesF . mconcat <$> manyTill inline' eol)
parseAmuseDirective :: PandocMonad m => MuseParser m (Text, F Inlines)
parseAmuseDirective = (,)
<$> parseDirectiveKey
<* many1 spaceChar
<*> (trimInlinesF . mconcat <$> many1Till inline endOfDirective)
<* many blankline
where
endOfDirective = lookAhead $ eof <|> try (newline *> (void blankline <|> void parseDirectiveKey))
directive :: PandocMonad m => MuseParser m ()
directive = do
ext <- getOption readerExtensions
(key, value) <- if extensionEnabled Ext_amuse ext then parseAmuseDirective else parseEmacsDirective
updateState $ \st -> st { museMeta = B.setMeta (translateKey key) <$> value <*> museMeta st }
where translateKey "cover" = "cover-image"
translateKey x = x
allowPara :: MonadReader MuseEnv m => m a -> m a
allowPara p = local (\s -> s { museInPara = False }) p
parseBlocks :: PandocMonad m
=> MuseParser m (F Blocks)
parseBlocks =
try (parseEnd <|>
nextSection <|>
listStart <|>
blockStart <|>
paraStart)
where
nextSection = mempty <$ lookAhead headingStart
parseEnd = mempty <$ eof
blockStart = (B.<>) <$> (blockElements <|> emacsNoteBlock)
<*> allowPara parseBlocks
listStart =
uncurry (B.<>) <$> allowPara (anyListUntil parseBlocks <|> amuseNoteBlockUntil parseBlocks)
paraStart = do
indent <- length <$> many spaceChar
uncurry (B.<>) . first (p indent) <$> paraUntil parseBlocks
where p indent = if indent >= 2 && indent < 6 then fmap B.blockQuote else id
parseSection :: PandocMonad m
=> MuseParser m (F Blocks)
parseSection =
((B.<>) <$> emacsHeading <*> parseBlocks) <|>
(uncurry (B.<>) <$> amuseHeadingUntil parseBlocks)
parseBlocksTill :: PandocMonad m
=> MuseParser m a
-> MuseParser m (F Blocks)
parseBlocksTill end = continuation
where
parseEnd = mempty <$ end
blockStart = (B.<>) <$> blockElements <*> allowPara continuation
listStart = uncurry (B.<>) <$> allowPara (anyListUntil (parseEnd <|> continuation))
paraStart = uncurry (B.<>) <$> paraUntil (parseEnd <|> continuation)
continuation = try $ parseEnd <|> listStart <|> blockStart <|> paraStart
listItemContentsUntil :: PandocMonad m
=> Int
-> MuseParser m a
-> MuseParser m a
-> MuseParser m (F Blocks, a)
listItemContentsUntil col pre end = p
where
p = try listStart <|> try blockStart <|> try paraStart
parsePre = (mempty,) <$> pre
parseEnd = (mempty,) <$> end
paraStart = do
(f, (r, e)) <- paraUntil (parsePre <|> continuation <|> parseEnd)
return (f B.<> r, e)
blockStart = first <$> ((B.<>) <$> blockElements)
<*> allowPara (parsePre <|> continuation <|> parseEnd)
listStart = do
(f, (r, e)) <- allowPara $ anyListUntil (parsePre <|> continuation <|> parseEnd)
return (f B.<> r, e)
continuation = try $ do blank <- optionMaybe blankline
skipMany blankline
indentWith col
local (\s -> s { museInPara = museInPara s && isNothing blank }) p
parseBlock :: PandocMonad m => MuseParser m (F Blocks)
parseBlock = do
res <- blockElements <|> para
trace (T.take 60 $ tshow $ B.toList $ runF res def)
return res
where para = fst <$> paraUntil (try (eof <|> void (lookAhead blockElements)))
blockElements :: PandocMonad m => MuseParser m (F Blocks)
blockElements = (mempty <$ blankline)
<|> comment
<|> separator
<|> pagebreak
<|> example
<|> exampleTag
<|> literalTag
<|> centerTag
<|> rightTag
<|> quoteTag
<|> divTag
<|> biblioTag
<|> playTag
<|> verseTag
<|> lineBlock
<|> museGridTable
<|> table
<|> commentTag
comment :: PandocMonad m => MuseParser m (F Blocks)
comment = try $ mempty
<$ firstColumn
<* char ';'
<* optional (spaceChar *> many (noneOf "\n"))
<* eol
separator :: PandocMonad m => MuseParser m (F Blocks)
separator = try $ pure B.horizontalRule
<$ string "----"
<* many (char '-')
<* many spaceChar
<* eol
pagebreak :: PandocMonad m => MuseParser m (F Blocks)
pagebreak = try $ pure (B.divWith ("", [], [("style", "page-break-before: always;")]) mempty)
<$ count 6 spaceChar
<* many spaceChar
<* string "* * * * *"
<* manyTill spaceChar eol
headingStart :: PandocMonad m => MuseParser m (Text, Int)
headingStart = try $ (,)
<$> option "" (try (parseAnchor <* manyTill spaceChar eol))
<* firstColumn
<*> fmap length (many1 $ char '*')
<* spaceChar
emacsHeading :: PandocMonad m => MuseParser m (F Blocks)
emacsHeading = try $ do
guardDisabled Ext_amuse
(anchorId, level) <- headingStart
content <- trimInlinesF . mconcat <$> manyTill inline eol
attr <- registerHeader (anchorId, [], []) (runF content def)
return $ B.headerWith attr level <$> content
amuseHeadingUntil :: PandocMonad m
=> MuseParser m a
-> MuseParser m (F Blocks, a)
amuseHeadingUntil end = try $ do
guardEnabled Ext_amuse
(anchorId, level) <- headingStart
(content, e) <- paraContentsUntil end
attr <- registerHeader (anchorId, [], []) (runF content def)
return (B.headerWith attr level <$> content, e)
example :: PandocMonad m => MuseParser m (F Blocks)
example = try $ pure . B.codeBlock
<$ string "{{{"
<* many spaceChar
<*> (unindent <$> manyTillChar anyChar (string "}}}"))
exampleTag :: PandocMonad m => MuseParser m (F Blocks)
exampleTag = try $ fmap pure $ B.codeBlockWith
<$ many spaceChar
<*> (htmlAttrToPandoc <$> openTag "example")
<*> (unindent <$> manyTillChar anyChar (closeTag "example"))
<* manyTill spaceChar eol
literalTag :: PandocMonad m => MuseParser m (F Blocks)
literalTag = try $ fmap pure $ B.rawBlock
<$ many spaceChar
<*> (fromMaybe "html" . lookup "style" <$> openTag "literal")
<* manyTill spaceChar eol
<*> (unindent <$> manyTillChar anyChar (closeTag "literal"))
<* manyTill spaceChar eol
centerTag :: PandocMonad m => MuseParser m (F Blocks)
centerTag = snd <$> parseHtmlContent "center"
rightTag :: PandocMonad m => MuseParser m (F Blocks)
rightTag = snd <$> parseHtmlContent "right"
quoteTag :: PandocMonad m => MuseParser m (F Blocks)
quoteTag = fmap B.blockQuote . snd <$> parseHtmlContent "quote"
divTag :: PandocMonad m => MuseParser m (F Blocks)
divTag = do
(attrs, content) <- parseHtmlContent "div"
return $ B.divWith attrs <$> content
biblioTag :: PandocMonad m => MuseParser m (F Blocks)
biblioTag = fmap (B.divWith ("", ["biblio"], [])) . snd
<$ guardEnabled Ext_amuse
<*> parseHtmlContent "biblio"
playTag :: PandocMonad m => MuseParser m (F Blocks)
playTag = do
guardEnabled Ext_amuse
fmap (B.divWith ("", ["play"], [])) . snd <$> parseHtmlContent "play"
verseLine :: PandocMonad m => MuseParser m (F Inlines)
verseLine = (<>)
<$> fmap pure (option mempty (B.str <$> many1Char ('\160' <$ char ' ')))
<*> fmap (trimInlinesF . mconcat) (manyTill inline' eol)
verseTag :: PandocMonad m => MuseParser m (F Blocks)
verseTag = try $ getIndent >>= \indent -> fmap B.lineBlock . sequence
<$ openTag "verse"
<* manyTill spaceChar eol
<*> manyTill (indentWith indent *> verseLine) (try $ indentWith indent *> closeTag "verse")
<* manyTill spaceChar eol
commentTag :: PandocMonad m => MuseParser m (F Blocks)
commentTag = try $ mempty
<$ many spaceChar
<* openTag "comment"
<* manyTill anyChar (closeTag "comment")
<* manyTill spaceChar eol
paraContentsUntil :: PandocMonad m
=> MuseParser m a
-> MuseParser m (F Inlines, a)
paraContentsUntil end = first (trimInlinesF . mconcat)
<$> manyUntil inline (try (manyTill spaceChar eol *> local (\s -> s { museInPara = True}) end))
paraUntil :: PandocMonad m
=> MuseParser m a
-> MuseParser m (F Blocks, a)
paraUntil end = do
inPara <- asks museInPara
guard $ not inPara
first (fmap B.para) <$> paraContentsUntil end
noteMarker' :: PandocMonad m
=> Char
-> Char
-> MuseParser m Text
noteMarker' l r = try $ (\x y -> T.pack $ l:x:y ++ [r])
<$ char l
<*> oneOf "123456789"
<*> manyTill digit (char r)
noteMarker :: PandocMonad m => MuseParser m Text
noteMarker = noteMarker' '[' ']' <|> noteMarker' '{' '}'
addNote :: PandocMonad m
=> Text
-> SourcePos
-> F Blocks
-> MuseParser m ()
addNote ref pos content = do
oldnotes <- museNotes <$> getState
when (M.member ref oldnotes)
(logMessage $ DuplicateNoteReference ref pos)
updateState $ \s -> s{ museNotes = M.insert ref (pos, content) oldnotes }
amuseNoteBlockUntil :: PandocMonad m
=> MuseParser m a
-> MuseParser m (F Blocks, a)
amuseNoteBlockUntil end = try $ do
guardEnabled Ext_amuse
ref <- noteMarker
pos <- getPosition
void spaceChar <|> lookAhead eol
(content, e) <- allowPara $ listItemContentsUntil (sourceColumn pos) (Prelude.fail "x") end
addNote ref pos content
return (mempty, e)
emacsNoteBlock :: PandocMonad m => MuseParser m (F Blocks)
emacsNoteBlock = try $ do
guardDisabled Ext_amuse
ref <- noteMarker
pos <- getPosition
content <- fmap mconcat blocksTillNote
addNote ref pos content
return mempty
where
blocksTillNote =
many1Till parseBlock (eof <|> () <$ lookAhead noteMarker)
lineBlock :: PandocMonad m => MuseParser m (F Blocks)
lineBlock = try $ getIndent >>= \indent -> fmap B.lineBlock . sequence
<$> (blankVerseLine <|> nonblankVerseLine) `sepBy1'` try (indentWith indent)
where
blankVerseLine = try $ mempty <$ char '>' <* blankline
nonblankVerseLine = try (string "> ") *> verseLine
bulletListItemsUntil :: PandocMonad m
=> Int
-> MuseParser m a
-> MuseParser m ([F Blocks], a)
bulletListItemsUntil indent end = try $ do
char '-'
void spaceChar <|> lookAhead eol
(x, (xs, e)) <- allowPara $ listItemContentsUntil (indent + 2) (try (optional blankline *> indentWith indent *> bulletListItemsUntil indent end)) (([],) <$> end)
return (x:xs, e)
bulletListUntil :: PandocMonad m
=> MuseParser m a
-> MuseParser m (F Blocks, a)
bulletListUntil end = try $ do
indent <- getIndent
guard $ indent /= 0
first (fmap B.bulletList . sequence) <$> bulletListItemsUntil indent end
museOrderedListMarker :: PandocMonad m
=> ListNumberStyle
-> MuseParser m Int
museOrderedListMarker style =
snd <$> p <* char '.'
where p = case style of
Decimal -> decimal
UpperRoman -> upperRoman
LowerRoman -> lowerRoman
UpperAlpha -> upperAlpha
LowerAlpha -> lowerAlpha
_ -> Prelude.fail "Unhandled case"
orderedListItemsUntil :: PandocMonad m
=> Int
-> ListNumberStyle
-> MuseParser m a
-> MuseParser m ([F Blocks], a)
orderedListItemsUntil indent style end =
continuation
where
continuation = try $ do
pos <- getPosition
void spaceChar <|> lookAhead eol
(x, (xs, e)) <- allowPara $ listItemContentsUntil (sourceColumn pos) (try (optional blankline *> indentWith indent *> museOrderedListMarker style *> continuation)) (([],) <$> end)
return (x:xs, e)
orderedListUntil :: PandocMonad m
=> MuseParser m a
-> MuseParser m (F Blocks, a)
orderedListUntil end = try $ do
indent <- getIndent
guard $ indent /= 0
(style, start) <- decimal <|> lowerRoman <|> upperRoman <|> lowerAlpha <|> upperAlpha
char '.'
first (fmap (B.orderedListWith (start, style, Period)) . sequence)
<$> orderedListItemsUntil indent style end
descriptionsUntil :: PandocMonad m
=> Int
-> MuseParser m a
-> MuseParser m ([F Blocks], a)
descriptionsUntil indent end = do
void spaceChar <|> lookAhead eol
(x, (xs, e)) <- allowPara $ listItemContentsUntil indent (try (optional blankline *> indentWith indent *> manyTill spaceChar (string "::") *> descriptionsUntil indent end)) (([],) <$> end)
return (x:xs, e)
definitionListItemsUntil :: PandocMonad m
=> Int
-> MuseParser m a
-> MuseParser m ([F (Inlines, [Blocks])], a)
definitionListItemsUntil indent end =
continuation
where
continuation = try $ do
pos <- getPosition
term <- trimInlinesF . mconcat <$> manyTill inline' (try $ string "::")
(x, (xs, e)) <- descriptionsUntil (sourceColumn pos) (try (optional blankline *> indentWith indent *> continuation) <|> (([],) <$> end))
let xx = (,) <$> term <*> sequence x
return (xx:xs, e)
definitionListUntil :: PandocMonad m
=> MuseParser m a
-> MuseParser m (F Blocks, a)
definitionListUntil end = try $ do
indent <- getIndent
guardDisabled Ext_amuse <|> guard (indent /= 0)
first (fmap B.definitionList . sequence) <$> definitionListItemsUntil indent end
anyListUntil :: PandocMonad m
=> MuseParser m a
-> MuseParser m (F Blocks, a)
anyListUntil end =
bulletListUntil end <|> orderedListUntil end <|> definitionListUntil end
data MuseTable = MuseTable
{ museTableCaption :: Inlines
, museTableHeaders :: [[Blocks]]
, museTableRows :: [[Blocks]]
, museTableFooters :: [[Blocks]]
}
data MuseTableElement = MuseHeaderRow [Blocks]
| MuseBodyRow [Blocks]
| MuseFooterRow [Blocks]
| MuseCaption Inlines
museToPandocTable :: MuseTable -> Blocks
museToPandocTable (MuseTable caption headers body footers) =
B.table (B.simpleCaption $ B.plain caption)
attrs
(TableHead nullAttr $ toHeaderRow headRow)
[TableBody nullAttr 0 [] $ map toRow $ rows ++ body ++ footers]
(TableFoot nullAttr [])
where attrs = (AlignDefault, ColWidthDefault) <$ transpose (headers ++ body ++ footers)
(headRow, rows) = fromMaybe ([], []) $ uncons headers
toRow = Row nullAttr . map B.simpleCell
toHeaderRow l = if null l then [] else [toRow l]
museAppendElement :: MuseTableElement
-> MuseTable
-> MuseTable
museAppendElement element tbl =
case element of
MuseHeaderRow row -> tbl{ museTableHeaders = row : museTableHeaders tbl }
MuseBodyRow row -> tbl{ museTableRows = row : museTableRows tbl }
MuseFooterRow row -> tbl{ museTableFooters = row : museTableFooters tbl }
MuseCaption inlines -> tbl{ museTableCaption = inlines }
tableElements :: PandocMonad m => MuseParser m (F [MuseTableElement])
tableElements = sequence <$> many1 tableParseElement
elementsToTable :: [MuseTableElement] -> MuseTable
elementsToTable = foldr museAppendElement emptyTable
where emptyTable = MuseTable mempty mempty mempty mempty
museGridPart :: PandocMonad m => MuseParser m Int
museGridPart = try $ length <$> many1 (char '-') <* char '+'
museGridTableHeader :: PandocMonad m => MuseParser m [Int]
museGridTableHeader = try $ char '+' *> many1 museGridPart <* manyTill spaceChar eol
museGridTableRow :: PandocMonad m
=> Int
-> [Int]
-> MuseParser m (F [Blocks])
museGridTableRow indent indices = try $ do
lns <- many1 $ try (indentWith indent *> museGridTableRawLine indices)
let cols = map (T.unlines . map trimr) $ transpose lns
indentWith indent *> museGridTableHeader
sequence <$> mapM (parseFromString' parseBlocks) cols
museGridTableRawLine :: PandocMonad m
=> [Int]
-> MuseParser m [Text]
museGridTableRawLine indices =
char '|' *> forM indices (\n -> countChar n anyChar <* char '|') <* manyTill spaceChar eol
museGridTable :: PandocMonad m => MuseParser m (F Blocks)
museGridTable = try $ do
indent <- getIndent
indices <- museGridTableHeader
fmap rowsToTable . sequence <$> many1 (museGridTableRow indent indices)
where rowsToTable rows = B.table B.emptyCaption
attrs
(TableHead nullAttr [])
[TableBody nullAttr 0 [] $ map toRow rows]
(TableFoot nullAttr [])
where attrs = (AlignDefault, ColWidthDefault) <$ transpose rows
toRow = Row nullAttr . map B.simpleCell
table :: PandocMonad m => MuseParser m (F Blocks)
table = try $ fmap (museToPandocTable . elementsToTable) <$> tableElements
tableParseElement :: PandocMonad m => MuseParser m (F MuseTableElement)
tableParseElement = tableParseHeader
<|> tableParseBody
<|> tableParseFooter
<|> tableParseCaption
tableParseRow :: PandocMonad m
=> Int
-> MuseParser m (F [Blocks])
tableParseRow n = try $ sequence <$> tableCells
where tableCells = (:) <$> tableCell sep <*> (tableCells <|> fmap pure (tableCell eol))
tableCell p = try $ fmap B.plain . trimInlinesF . mconcat <$> manyTill inline' p
sep = try $ many1 spaceChar *> count n (char '|') *> lookAhead (void (many1 spaceChar) <|> void eol)
tableParseHeader :: PandocMonad m => MuseParser m (F MuseTableElement)
tableParseHeader = fmap MuseHeaderRow <$> tableParseRow 2
tableParseBody :: PandocMonad m => MuseParser m (F MuseTableElement)
tableParseBody = fmap MuseBodyRow <$> tableParseRow 1
tableParseFooter :: PandocMonad m => MuseParser m (F MuseTableElement)
tableParseFooter = fmap MuseFooterRow <$> tableParseRow 3
tableParseCaption :: PandocMonad m => MuseParser m (F MuseTableElement)
tableParseCaption = try $ fmap MuseCaption . trimInlinesF . mconcat
<$ many spaceChar
<* string "|+"
<*> many1Till inline (try $ string "+|" *> eol)
inline' :: PandocMonad m => MuseParser m (F Inlines)
inline' = whitespace
<|> br
<|> anchor
<|> footnote
<|> strongEmph
<|> strong
<|> strongTag
<|> emph
<|> emphTag
<|> underlined
<|> superscriptTag
<|> subscriptTag
<|> strikeoutTag
<|> verbatimTag
<|> classTag
<|> inlineRtl
<|> inlineLtr
<|> nbsp
<|> linkOrImage
<|> code
<|> codeTag
<|> mathTag
<|> inlineLiteralTag
<|> str
<|> asterisks
<|> symbol
<?> "inline"
inline :: PandocMonad m => MuseParser m (F Inlines)
inline = endline <|> inline'
endline :: PandocMonad m => MuseParser m (F Inlines)
endline = try $ pure B.softbreak <$ newline <* notFollowedBy blankline <* updateLastSpacePos
parseAnchor :: PandocMonad m => MuseParser m Text
parseAnchor = try $ T.cons
<$ firstColumn
<* char '#'
<*> letter
<*> manyChar (letter <|> digit <|> char '-')
anchor :: PandocMonad m => MuseParser m (F Inlines)
anchor = try $ do
anchorId <- parseAnchor
skipMany spaceChar <|> void newline
return $ return $ B.spanWith (anchorId, [], []) mempty
footnote :: PandocMonad m => MuseParser m (F Inlines)
footnote = try $ do
inLink <- asks museInLink
guard $ not inLink
ref <- noteMarker
return $ do
notes <- asksF museNotes
case M.lookup ref notes of
Nothing -> return $ B.str ref
Just (_pos, contents) -> do
st <- askF
let contents' = runF contents st { museNotes = M.delete ref (museNotes st) }
return $ B.note contents'
whitespace :: PandocMonad m => MuseParser m (F Inlines)
whitespace = try $ pure B.space <$ skipMany1 spaceChar <* updateLastSpacePos
br :: PandocMonad m => MuseParser m (F Inlines)
br = try $ pure B.linebreak <$ string "<br>"
emphasisBetween :: (PandocMonad m, Show a)
=> MuseParser m a
-> MuseParser m (F Inlines)
emphasisBetween p = try $ trimInlinesF . mconcat
<$ atStart
<* p
<* notFollowedBy space
<*> many1Till inline (try $ noSpaceBefore *> p <* notFollowedBy alphaNum)
inlineTag :: PandocMonad m
=> Text
-> MuseParser m (F Inlines)
inlineTag tag = try $ mconcat
<$ openTag tag
<*> manyTill inline (closeTag tag)
strongEmph :: PandocMonad m => MuseParser m (F Inlines)
strongEmph = fmap (B.strong . B.emph) <$> emphasisBetween (string "***" <* notFollowedBy (char '*'))
strong :: PandocMonad m => MuseParser m (F Inlines)
strong = fmap B.strong <$> emphasisBetween (string "**" <* notFollowedBy (char '*'))
emph :: PandocMonad m => MuseParser m (F Inlines)
emph = fmap B.emph <$> emphasisBetween (char '*' <* notFollowedBy (char '*'))
underlined :: PandocMonad m => MuseParser m (F Inlines)
underlined = fmap underline
<$ guardDisabled Ext_amuse
<*> emphasisBetween (char '_')
strongTag :: PandocMonad m => MuseParser m (F Inlines)
strongTag = fmap B.strong <$> inlineTag "strong"
emphTag :: PandocMonad m => MuseParser m (F Inlines)
emphTag = fmap B.emph <$> inlineTag "em"
superscriptTag :: PandocMonad m => MuseParser m (F Inlines)
superscriptTag = fmap B.superscript <$> inlineTag "sup"
subscriptTag :: PandocMonad m => MuseParser m (F Inlines)
subscriptTag = fmap B.subscript <$> inlineTag "sub"
strikeoutTag :: PandocMonad m => MuseParser m (F Inlines)
strikeoutTag = fmap B.strikeout <$> inlineTag "del"
verbatimTag :: PandocMonad m => MuseParser m (F Inlines)
verbatimTag = return . B.text
<$ openTag "verbatim"
<*> manyTillChar anyChar (closeTag "verbatim")
classTag :: PandocMonad m => MuseParser m (F Inlines)
classTag = do
classes <- maybe [] T.words . lookup "name" <$> openTag "class"
fmap (B.spanWith ("", classes, [])) . mconcat <$> manyTill inline (closeTag "class")
inlineRtl :: PandocMonad m => MuseParser m (F Inlines)
inlineRtl = try $
fmap (B.spanWith ("", [], [("dir", "rtl")])) . mconcat <$ string "<<<" <*> manyTill inline (string ">>>")
inlineLtr :: PandocMonad m => MuseParser m (F Inlines)
inlineLtr = try $
fmap (B.spanWith ("", [], [("dir", "ltr")])) . mconcat <$ string ">>>" <*> manyTill inline (string "<<<")
nbsp :: PandocMonad m => MuseParser m (F Inlines)
nbsp = try $ pure (B.str "\160") <$ string "~~"
code :: PandocMonad m => MuseParser m (F Inlines)
code = try $ fmap pure $ B.code . uncurry (<>)
<$ atStart
<* char '='
<* notFollowedBy (spaceChar <|> newline)
<*> manyUntilChar (noneOf "\n\r" <|> (newline <* notFollowedBy newline)) (try $ fmap T.singleton $ noneOf " \t\n\r=" <* char '=')
<* notFollowedBy alphaNum
codeTag :: PandocMonad m => MuseParser m (F Inlines)
codeTag = fmap pure $ B.codeWith
<$> (htmlAttrToPandoc <$> openTag "code")
<*> manyTillChar anyChar (closeTag "code")
mathTag :: PandocMonad m => MuseParser m (F Inlines)
mathTag = return . B.math
<$ openTag "math"
<*> manyTillChar anyChar (closeTag "math")
inlineLiteralTag :: PandocMonad m => MuseParser m (F Inlines)
inlineLiteralTag = try $ fmap pure $ B.rawInline
<$> (fromMaybe "html" . lookup "style" <$> openTag "literal")
<*> manyTillChar anyChar (closeTag "literal")
str :: PandocMonad m => MuseParser m (F Inlines)
str = return . B.str <$> many1Char alphaNum <* updateLastStrPos
asterisks :: PandocMonad m => MuseParser m (F Inlines)
asterisks = pure . B.str <$> many1Char (char '*')
symbol :: PandocMonad m => MuseParser m (F Inlines)
symbol = pure . B.str . T.singleton <$> nonspaceChar
linkOrImage :: PandocMonad m => MuseParser m (F Inlines)
linkOrImage = try $ link "URL:" <|> image <|> link ""
linkContent :: PandocMonad m => MuseParser m (F Inlines)
linkContent = trimInlinesF . mconcat
<$ char '['
<*> manyTill inline (char ']')
link :: PandocMonad m => Text -> MuseParser m (F Inlines)
link prefix = try $ do
inLink <- asks museInLink
guard $ not inLink
textStr $ "[[" <> prefix
url <- manyTillChar anyChar $ char ']'
content <- option (pure $ B.str url) (local (\s -> s { museInLink = True }) linkContent)
char ']'
return $ B.link url "" <$> content
image :: PandocMonad m => MuseParser m (F Inlines)
image = try $ do
string "[["
(url, (ext, width, align)) <- manyUntilChar (noneOf "]") (imageExtensionAndOptions <* char ']')
content <- option mempty linkContent
char ']'
let widthAttr = case align of
Just 'f' -> [("width", fromMaybe "100" width <> "%"), ("height", "75%")]
_ -> maybeToList (("width",) . (<> "%") <$> width)
let alignClass = case align of
Just 'r' -> ["align-right"]
Just 'l' -> ["align-left"]
Just 'f' -> []
_ -> []
return $ B.imageWith ("", alignClass, widthAttr) (url <> ext) mempty <$> content
where
imageExtensions = [".eps", ".gif", ".jpg", ".jpeg", ".pbm", ".png", ".tiff", ".xbm", ".xpm"]
imageExtension = choice (try . textStr <$> imageExtensions)
imageExtensionAndOptions = do
ext <- imageExtension
(width, align) <- option (Nothing, Nothing) imageAttrs
return (ext, width, align)
imageAttrs = (,)
<$ many1 spaceChar
<*> optionMaybe (many1Char digit)
<* many spaceChar
<*> optionMaybe (oneOf "rlf")