diff options
Diffstat (limited to 'src/Text')
-rw-r--r-- | src/Text/Pandoc/Class.hs | 1 | ||||
-rw-r--r-- | src/Text/Pandoc/Logging.hs | 3 | ||||
-rw-r--r-- | src/Text/Pandoc/Lua/Init.hs | 1 | ||||
-rw-r--r-- | src/Text/Pandoc/Lua/StackInstances.hs | 94 | ||||
-rw-r--r-- | src/Text/Pandoc/Parsing.hs | 2 | ||||
-rw-r--r-- | src/Text/Pandoc/Readers/HTML.hs | 18 | ||||
-rw-r--r-- | src/Text/Pandoc/Readers/LaTeX.hs | 129 | ||||
-rw-r--r-- | src/Text/Pandoc/Readers/LaTeX/Parsing.hs | 194 | ||||
-rw-r--r-- | src/Text/Pandoc/Readers/Markdown.hs | 18 | ||||
-rw-r--r-- | src/Text/Pandoc/Readers/Muse.hs | 266 | ||||
-rw-r--r-- | src/Text/Pandoc/Readers/RST.hs | 41 | ||||
-rw-r--r-- | src/Text/Pandoc/Readers/Vimwiki.hs | 12 | ||||
-rw-r--r-- | src/Text/Pandoc/Shared.hs | 4 | ||||
-rw-r--r-- | src/Text/Pandoc/Writers/Custom.hs | 29 | ||||
-rw-r--r-- | src/Text/Pandoc/Writers/Docx.hs | 30 | ||||
-rw-r--r-- | src/Text/Pandoc/Writers/LaTeX.hs | 39 | ||||
-rw-r--r-- | src/Text/Pandoc/Writers/Markdown.hs | 7 | ||||
-rw-r--r-- | src/Text/Pandoc/Writers/Muse.hs | 8 | ||||
-rw-r--r-- | src/Text/Pandoc/Writers/ODT.hs | 33 | ||||
-rw-r--r-- | src/Text/Pandoc/Writers/Shared.hs | 18 |
20 files changed, 527 insertions, 420 deletions
diff --git a/src/Text/Pandoc/Class.hs b/src/Text/Pandoc/Class.hs index e47546dfc..92b41aa4e 100644 --- a/src/Text/Pandoc/Class.hs +++ b/src/Text/Pandoc/Class.hs @@ -629,6 +629,7 @@ getDefaultReferenceDocx = do "_rels/.rels", "docProps/app.xml", "docProps/core.xml", + "docProps/custom.xml", "word/document.xml", "word/fontTable.xml", "word/footnotes.xml", diff --git a/src/Text/Pandoc/Logging.hs b/src/Text/Pandoc/Logging.hs index 4b025821c..675396067 100644 --- a/src/Text/Pandoc/Logging.hs +++ b/src/Text/Pandoc/Logging.hs @@ -292,7 +292,8 @@ showLogMessage msg = "Extracting " ++ fp ++ "..." NoTitleElement fallback -> "This document format requires a nonempty <title> element.\n" ++ - "Please specify either 'title' or 'pagetitle' in the metadata.\n" ++ + "Please specify either 'title' or 'pagetitle' in the metadata,\n" ++ + "e.g. by using --metadata pagetitle=\"...\" on the command line.\n" ++ "Falling back to '" ++ fallback ++ "'" NoLangSpecified -> "No value for 'lang' was specified in the metadata.\n" ++ diff --git a/src/Text/Pandoc/Lua/Init.hs b/src/Text/Pandoc/Lua/Init.hs index 35611d481..8449d736d 100644 --- a/src/Text/Pandoc/Lua/Init.hs +++ b/src/Text/Pandoc/Lua/Init.hs @@ -111,6 +111,7 @@ putConstructorsInRegistry = do constrsToReg $ Pandoc.MetaList mempty constrsToReg $ Pandoc.Citation mempty mempty mempty Pandoc.AuthorInText 0 0 putInReg "Attr" -- used for Attr type alias + putInReg "ListAttributes" -- used for ListAttributes type alias Lua.pop 1 where constrsToReg :: Data a => a -> Lua () diff --git a/src/Text/Pandoc/Lua/StackInstances.hs b/src/Text/Pandoc/Lua/StackInstances.hs index 931b8c225..2d7b9c583 100644 --- a/src/Text/Pandoc/Lua/StackInstances.hs +++ b/src/Text/Pandoc/Lua/StackInstances.hs @@ -36,9 +36,10 @@ module Text.Pandoc.Lua.StackInstances () where import Prelude import Control.Applicative ((<|>)) -import Control.Monad (when) import Data.Data (showConstr, toConstr) import Foreign.Lua (Lua, Peekable, Pushable, StackIndex) +import Foreign.Lua.Userdata ( ensureUserdataMetatable, pushAnyWithMetatable + , metatableName) import Text.Pandoc.Definition import Text.Pandoc.Extensions (Extensions) import Text.Pandoc.Lua.Util (defineHowTo, pushViaConstructor) @@ -185,7 +186,8 @@ pushBlock = \case Header lvl attr inlns -> pushViaConstructor "Header" lvl inlns (LuaAttr attr) HorizontalRule -> pushViaConstructor "HorizontalRule" LineBlock blcks -> pushViaConstructor "LineBlock" blcks - OrderedList lstAttr list -> pushViaConstructor "OrderedList" list lstAttr + OrderedList lstAttr list -> pushViaConstructor "OrderedList" list + (LuaListAttributes lstAttr) Null -> pushViaConstructor "Null" Para blcks -> pushViaConstructor "Para" blcks Plain blcks -> pushViaConstructor "Plain" blcks @@ -207,7 +209,9 @@ peekBlock idx = defineHowTo "get Block value" $ do <$> elementContent "HorizontalRule" -> return HorizontalRule "LineBlock" -> LineBlock <$> elementContent - "OrderedList" -> uncurry OrderedList <$> elementContent + "OrderedList" -> (\(LuaListAttributes lstAttr, lst) -> + OrderedList lstAttr lst) + <$> elementContent "Null" -> return Null "Para" -> Para <$> elementContent "Plain" -> Plain <$> elementContent @@ -289,29 +293,44 @@ instance Pushable LuaAttr where instance Peekable LuaAttr where peek idx = defineHowTo "get Attr value" (LuaAttr <$> Lua.peek idx) +-- | Wrapper for ListAttributes +newtype LuaListAttributes = LuaListAttributes ListAttributes + +instance Pushable LuaListAttributes where + push (LuaListAttributes (start, style, delimiter)) = + pushViaConstructor "ListAttributes" start style delimiter + +instance Peekable LuaListAttributes where + peek = defineHowTo "get ListAttributes value" . + fmap LuaListAttributes . Lua.peek + -- -- Hierarchical elements -- instance Pushable Element where push (Blk blk) = Lua.push blk - push (Sec lvl num attr label contents) = do - Lua.newtable - LuaUtil.addField "level" lvl - LuaUtil.addField "numbering" num - LuaUtil.addField "attr" (LuaAttr attr) - LuaUtil.addField "label" label - LuaUtil.addField "contents" contents - pushSecMetaTable - Lua.setmetatable (-2) - where - pushSecMetaTable :: Lua () - pushSecMetaTable = do - inexistant <- Lua.newmetatable "PandocElementSec" - when inexistant $ do - LuaUtil.addField "t" "Sec" - Lua.push "__index" - Lua.pushvalue (-2) - Lua.rawset (-3) + push sec = pushAnyWithMetatable pushElementMetatable sec + where + pushElementMetatable = ensureUserdataMetatable (metatableName sec) $ + LuaUtil.addFunction "__index" indexElement + +instance Peekable Element where + peek idx = Lua.ltype idx >>= \case + Lua.TypeUserdata -> Lua.peekAny idx + _ -> Blk <$> Lua.peek idx + +indexElement :: Element -> String -> Lua Lua.NumResults +indexElement = \case + (Blk _) -> const (1 <$ Lua.pushnil) -- this shouldn't happen + (Sec lvl num attr label contents) -> fmap (return 1) . \case + "level" -> Lua.push lvl + "numbering" -> Lua.push num + "attr" -> Lua.push (LuaAttr attr) + "label" -> Lua.push label + "contents" -> Lua.push contents + "tag" -> Lua.push "Sec" + "t" -> Lua.push "Sec" + _ -> Lua.pushnil -- @@ -340,9 +359,32 @@ instance Pushable ReaderOptions where LuaUtil.addField "extensions" extensions LuaUtil.addField "standalone" standalone LuaUtil.addField "columns" columns - LuaUtil.addField "tabStop" tabStop - LuaUtil.addField "indentedCodeClasses" indentedCodeClasses + LuaUtil.addField "tab_stop" tabStop + LuaUtil.addField "indented_code_classes" indentedCodeClasses LuaUtil.addField "abbreviations" abbreviations - LuaUtil.addField "defaultImageExtension" defaultImageExtension - LuaUtil.addField "trackChanges" trackChanges - LuaUtil.addField "stripComments" stripComments + LuaUtil.addField "default_image_extension" defaultImageExtension + LuaUtil.addField "track_changes" trackChanges + LuaUtil.addField "strip_comments" stripComments + + -- add metatable + let indexReaderOptions :: AnyValue -> AnyValue -> Lua Lua.NumResults + indexReaderOptions _tbl (AnyValue key) = do + Lua.ltype key >>= \case + Lua.TypeString -> Lua.peek key >>= \case + "defaultImageExtension" -> Lua.push defaultImageExtension + "indentedCodeClasses" -> Lua.push indentedCodeClasses + "stripComments" -> Lua.push stripComments + "tabStop" -> Lua.push tabStop + "trackChanges" -> Lua.push trackChanges + _ -> Lua.pushnil + _ -> Lua.pushnil + return 1 + Lua.newtable + LuaUtil.addFunction "__index" indexReaderOptions + Lua.setmetatable (Lua.nthFromTop 2) + +-- | Dummy type to allow values of arbitrary Lua type. +newtype AnyValue = AnyValue StackIndex + +instance Peekable AnyValue where + peek = return . AnyValue diff --git a/src/Text/Pandoc/Parsing.hs b/src/Text/Pandoc/Parsing.hs index 5d95d0e27..da8cc6433 100644 --- a/src/Text/Pandoc/Parsing.hs +++ b/src/Text/Pandoc/Parsing.hs @@ -397,7 +397,7 @@ spaceChar = satisfy $ \c -> c == ' ' || c == '\t' -- | Parses a nonspace, nonnewline character. nonspaceChar :: Stream s m Char => ParserT s st m Char -nonspaceChar = satisfy $ flip notElem ['\t', '\n', ' ', '\r'] +nonspaceChar = noneOf ['\t', '\n', ' ', '\r'] -- | Skips zero or more spaces or tabs. skipSpaces :: Stream s m Char => ParserT s st m () diff --git a/src/Text/Pandoc/Readers/HTML.hs b/src/Text/Pandoc/Readers/HTML.hs index b06e07a80..dab3d5db2 100644 --- a/src/Text/Pandoc/Readers/HTML.hs +++ b/src/Text/Pandoc/Readers/HTML.hs @@ -1069,11 +1069,11 @@ instance NamedTag (Tag String) where getTagName _ = Nothing isInlineTag :: NamedTag (Tag a) => Tag a -> Bool -isInlineTag t = isInlineTagName || isCommentTag t - where isInlineTagName = case getTagName t of - Just x -> x - `Set.notMember` blockTags - Nothing -> False +isInlineTag t = + isCommentTag t || case getTagName t of + Nothing -> False + Just x -> x `Set.notMember` blockTags || + T.take 1 x == "?" -- processing instr. isBlockTag :: NamedTag (Tag a) => Tag a -> Bool isBlockTag t = isBlockTagName || isTagComment t @@ -1208,8 +1208,10 @@ htmlTag f = try $ do let isNameChar c = isAlphaNum c || c == ':' || c == '-' || c == '_' let isName s = case s of [] -> False - ('?':_) -> True -- processing instruction (c:cs) -> isLetter c && all isNameChar cs + let isPI s = case s of + ('?':_) -> True -- processing instruction + _ -> False let endpos = if ln == 1 then setSourceColumn startpos @@ -1225,7 +1227,7 @@ htmlTag f = try $ do let handleTag tagname = do -- basic sanity check, since the parser is very forgiving -- and finds tags in stuff like x<y) - guard $ isName tagname + guard $ isName tagname || isPI tagname guard $ not $ null tagname -- <https://example.org> should NOT be a tag either. -- tagsoup will parse it as TagOpen "https:" [("example.org","")] @@ -1245,7 +1247,7 @@ htmlTag f = try $ do else return (next, "<!--" <> s <> "-->") | otherwise -> fail "bogus comment mode, HTML5 parse error" TagOpen tagname attr -> do - guard $ all (isName . fst) attr + guard $ isPI tagname || all (isName . fst) attr handleTag tagname TagClose tagname -> handleTag tagname diff --git a/src/Text/Pandoc/Readers/LaTeX.hs b/src/Text/Pandoc/Readers/LaTeX.hs index 7c5619165..26ac781db 100644 --- a/src/Text/Pandoc/Readers/LaTeX.hs +++ b/src/Text/Pandoc/Readers/LaTeX.hs @@ -1308,13 +1308,7 @@ isBlockCommand s = treatAsBlock :: Set.Set Text treatAsBlock = Set.fromList - [ "let", "def", "DeclareRobustCommand" - , "newcommand", "renewcommand" - , "newenvironment", "renewenvironment" - , "providecommand", "provideenvironment" - -- newcommand, etc. should be parsed by macroDef, but we need this - -- here so these aren't parsed as inline commands to ignore - , "special", "pdfannot", "pdfstringdef" + [ "special", "pdfannot", "pdfstringdef" , "bibliographystyle" , "maketitle", "makeindex", "makeglossary" , "addcontentsline", "addtocontents", "addtocounter" @@ -1375,6 +1369,7 @@ inline = (mempty <$ comment) <|> (space <$ whitespace) <|> (softbreak <$ endline) <|> word + <|> macroDef <|> inlineCommand' <|> inlineEnvironment <|> inlineGroup @@ -1420,8 +1415,7 @@ end_ t = try (do preamble :: PandocMonad m => LP m Blocks preamble = mempty <$ many preambleBlock where preambleBlock = spaces1 - <|> void macroDef - <|> void blockCommand + <|> void (macroDef <|> blockCommand) <|> void braced <|> (notFollowedBy (begin_ "document") >> void anyTok) @@ -1484,9 +1478,9 @@ authors = try $ do egroup addMeta "author" (map trimInlines auths) -macroDef :: PandocMonad m => LP m Blocks +macroDef :: (Monoid a, PandocMonad m) => LP m a macroDef = - mempty <$ ((commandDef <|> environmentDef) <* doMacros 0) + mempty <$ (commandDef <|> environmentDef) where commandDef = do (name, macro') <- newcommand <|> letmacro <|> defmacro guardDisabled Ext_latex_macros <|> @@ -1506,21 +1500,28 @@ macroDef = letmacro :: PandocMonad m => LP m (Text, Macro) letmacro = do controlSeq "let" - Tok _ (CtrlSeq name) _ <- anyControlSeq - optional $ symbol '=' - spaces - contents <- bracedOrToken - return (name, Macro ExpandWhenDefined [] Nothing contents) + (name, contents) <- withVerbatimMode $ do + Tok _ (CtrlSeq name) _ <- anyControlSeq + optional $ symbol '=' + spaces + -- we first parse in verbatim mode, and then expand macros, + -- because we don't want \let\foo\bar to turn into + -- \let\foo hello if we have previously \def\bar{hello} + contents <- bracedOrToken + return (name, contents) + contents' <- doMacros' 0 contents + return (name, Macro ExpandWhenDefined [] Nothing contents') defmacro :: PandocMonad m => LP m (Text, Macro) -defmacro = try $ do - controlSeq "def" - Tok _ (CtrlSeq name) _ <- anyControlSeq - argspecs <- many (argspecArg <|> argspecPattern) +defmacro = try $ -- we use withVerbatimMode, because macros are to be expanded -- at point of use, not point of definition - contents <- withVerbatimMode bracedOrToken - return (name, Macro ExpandWhenUsed argspecs Nothing contents) + withVerbatimMode $ do + controlSeq "def" + Tok _ (CtrlSeq name) _ <- anyControlSeq + argspecs <- many (argspecArg <|> argspecPattern) + contents <- bracedOrToken + return (name, Macro ExpandWhenUsed argspecs Nothing contents) argspecArg :: PandocMonad m => LP m ArgSpec argspecArg = do @@ -1530,8 +1531,8 @@ argspecArg = do argspecPattern :: PandocMonad m => LP m ArgSpec argspecPattern = Pattern <$> many1 (satisfyTok (\(Tok _ toktype' txt) -> - (toktype' == Symbol || toktype' == Word) && - (txt /= "{" && txt /= "\\" && txt /= "}"))) + (toktype' == Symbol || toktype' == Word) && + (txt /= "{" && txt /= "\\" && txt /= "}"))) newcommand :: PandocMonad m => LP m (Text, Macro) newcommand = do @@ -1540,22 +1541,24 @@ newcommand = do controlSeq "renewcommand" <|> controlSeq "providecommand" <|> controlSeq "DeclareRobustCommand" - optional $ symbol '*' - Tok _ (CtrlSeq name) txt <- withVerbatimMode $ anyControlSeq <|> - (symbol '{' *> spaces *> anyControlSeq <* spaces <* symbol '}') - spaces - numargs <- option 0 $ try bracketedNum - let argspecs = map (\i -> ArgNum i) [1..numargs] - spaces - optarg <- option Nothing $ Just <$> try bracketedToks - spaces - contents <- withVerbatimMode bracedOrToken - when (mtype == "newcommand") $ do - macros <- sMacros <$> getState - case M.lookup name macros of - Just _ -> report $ MacroAlreadyDefined (T.unpack txt) pos - Nothing -> return () - return (name, Macro ExpandWhenUsed argspecs optarg contents) + withVerbatimMode $ do + Tok _ (CtrlSeq name) txt <- do + optional (symbol '*') + anyControlSeq <|> + (symbol '{' *> spaces *> anyControlSeq <* spaces <* symbol '}') + spaces + numargs <- option 0 $ try bracketedNum + let argspecs = map (\i -> ArgNum i) [1..numargs] + spaces + optarg <- option Nothing $ Just <$> try bracketedToks + spaces + contents <- bracedOrToken + when (mtype == "newcommand") $ do + macros <- sMacros <$> getState + case M.lookup name macros of + Just _ -> report $ MacroAlreadyDefined (T.unpack txt) pos + Nothing -> return () + return (name, Macro ExpandWhenUsed argspecs optarg contents) newenvironment :: PandocMonad m => LP m (Text, Macro, Macro) newenvironment = do @@ -1563,24 +1566,23 @@ newenvironment = do Tok _ (CtrlSeq mtype) _ <- controlSeq "newenvironment" <|> controlSeq "renewenvironment" <|> controlSeq "provideenvironment" - optional $ symbol '*' - spaces - name <- untokenize <$> braced - spaces - numargs <- option 0 $ try bracketedNum - let argspecs = map (\i -> ArgNum i) [1..numargs] - spaces - optarg <- option Nothing $ Just <$> try bracketedToks - spaces - startcontents <- withVerbatimMode bracedOrToken - spaces - endcontents <- withVerbatimMode bracedOrToken - when (mtype == "newenvironment") $ do - macros <- sMacros <$> getState - case M.lookup name macros of - Just _ -> report $ MacroAlreadyDefined (T.unpack name) pos - Nothing -> return () - return (name, Macro ExpandWhenUsed argspecs optarg startcontents, + withVerbatimMode $ do + optional $ symbol '*' + spaces + name <- untokenize <$> braced + spaces + numargs <- option 0 $ try bracketedNum + spaces + optarg <- option Nothing $ Just <$> try bracketedToks + let argspecs = map (\i -> ArgNum i) [1..numargs] + startcontents <- spaces >> bracedOrToken + endcontents <- spaces >> bracedOrToken + when (mtype == "newenvironment") $ do + macros <- sMacros <$> getState + case M.lookup name macros of + Just _ -> report $ MacroAlreadyDefined (T.unpack name) pos + Nothing -> return () + return (name, Macro ExpandWhenUsed argspecs optarg startcontents, Macro ExpandWhenUsed [] Nothing endcontents) bracketedNum :: PandocMonad m => LP m Int @@ -1644,7 +1646,9 @@ blockCommand = try $ do let names = ordNub [name', name] let rawDefiniteBlock = do guard $ isBlockCommand name - rawBlock "latex" <$> getRawCommand name (txt <> star) + rawcontents <- getRawCommand name (txt <> star) + (guardEnabled Ext_raw_tex >> return (rawBlock "latex" rawcontents)) + <|> ignore rawcontents -- heuristic: if it could be either block or inline, we -- treat it if block if we have a sequence of block -- commands followed by a newline. But we stop if we @@ -1656,7 +1660,10 @@ blockCommand = try $ do guard $ "start" `T.isPrefixOf` n let rawMaybeBlock = try $ do guard $ not $ isInlineCommand name - curr <- rawBlock "latex" <$> getRawCommand name (txt <> star) + rawcontents <- getRawCommand name (txt <> star) + curr <- (guardEnabled Ext_raw_tex >> + return (rawBlock "latex" rawcontents)) + <|> ignore rawcontents rest <- many $ notFollowedBy startCommand *> blockCommand lookAhead $ blankline <|> startCommand return $ curr <> mconcat rest @@ -1757,6 +1764,8 @@ blockCommands = M.fromList , ("input", include "input") , ("subfile", include "subfile") , ("usepackage", include "usepackage") + -- preamble + , ("PackageError", mempty <$ (braced >> braced >> braced)) ] diff --git a/src/Text/Pandoc/Readers/LaTeX/Parsing.hs b/src/Text/Pandoc/Readers/LaTeX/Parsing.hs index 9256217fe..69bbf28d4 100644 --- a/src/Text/Pandoc/Readers/LaTeX/Parsing.hs +++ b/src/Text/Pandoc/Readers/LaTeX/Parsing.hs @@ -49,6 +49,7 @@ module Text.Pandoc.Readers.LaTeX.Parsing , toksToString , satisfyTok , doMacros + , doMacros' , setpos , anyControlSeq , anySymbol @@ -110,6 +111,8 @@ import Text.Pandoc.Readers.LaTeX.Types (ExpansionPoint (..), Macro (..), import Text.Pandoc.Shared import Text.Parsec.Pos +-- import Debug.Trace (traceShowId) + newtype DottedNum = DottedNum [Int] deriving (Show) @@ -140,6 +143,7 @@ data LaTeXState = LaTeXState{ sOptions :: ReaderOptions , sLabels :: M.Map String [Inline] , sHasChapters :: Bool , sToggles :: M.Map String Bool + , sExpanded :: Bool } deriving Show @@ -161,6 +165,7 @@ defaultLaTeXState = LaTeXState{ sOptions = def , sLabels = M.empty , sHasChapters = False , sToggles = M.empty + , sExpanded = False } instance PandocMonad m => HasQuoteContext LaTeXState m where @@ -211,10 +216,14 @@ type LP m = ParserT [Tok] LaTeXState m withVerbatimMode :: PandocMonad m => LP m a -> LP m a withVerbatimMode parser = do - updateState $ \st -> st{ sVerbatimMode = True } - result <- parser - updateState $ \st -> st{ sVerbatimMode = False } - return result + alreadyVerbatimMode <- sVerbatimMode <$> getState + if alreadyVerbatimMode + then parser + else do + updateState $ \st -> st{ sVerbatimMode = True } + result <- parser + updateState $ \st -> st{ sVerbatimMode = False } + return result rawLaTeXParser :: (PandocMonad m, HasMacros s, HasReaderOptions s) => Bool -> LP m a -> LP m a -> ParserT String s m (a, String) @@ -231,7 +240,7 @@ rawLaTeXParser retokenize parser valParser = do Right toks' -> do res <- lift $ runParserT (do when retokenize $ do -- retokenize, applying macros - doMacros 0 + doMacros ts <- many (satisfyTok (const True)) setInput ts rawparser) @@ -246,8 +255,7 @@ rawLaTeXParser retokenize parser valParser = do applyMacros :: (PandocMonad m, HasMacros s, HasReaderOptions s) => String -> ParserT String s m String applyMacros s = (guardDisabled Ext_latex_macros >> return s) <|> - do let retokenize = doMacros 0 *> - (toksToString <$> many (satisfyTok (const True))) + do let retokenize = toksToString <$> many (satisfyTok (const True)) pstate <- getState let lstate = def{ sOptions = extractReaderOptions pstate , sMacros = extractMacros pstate } @@ -255,6 +263,7 @@ applyMacros s = (guardDisabled Ext_latex_macros >> return s) <|> case res of Left e -> fail (show e) Right s' -> return s' + tokenize :: SourceName -> Text -> [Tok] tokenize sourcename = totoks (initialPos sourcename) @@ -368,10 +377,10 @@ toksToString :: [Tok] -> String toksToString = T.unpack . untokenize satisfyTok :: PandocMonad m => (Tok -> Bool) -> LP m Tok -satisfyTok f = - try $ do +satisfyTok f = do + doMacros -- apply macros on remaining input stream res <- tokenPrim (T.unpack . untoken) updatePos matcher - doMacros 0 -- apply macros on remaining input stream + updateState $ \st -> st{ sExpanded = False } return res where matcher t | f t = Just t | otherwise = Nothing @@ -379,82 +388,97 @@ satisfyTok f = updatePos _spos _ (Tok pos _ _ : _) = pos updatePos spos _ [] = incSourceColumn spos 1 -doMacros :: PandocMonad m => Int -> LP m () -doMacros n = do +doMacros :: PandocMonad m => LP m () +doMacros = do + expanded <- sExpanded <$> getState verbatimMode <- sVerbatimMode <$> getState - unless verbatimMode $ do - inp <- getInput - case inp of - Tok spos (CtrlSeq "begin") _ : Tok _ Symbol "{" : - Tok _ Word name : Tok _ Symbol "}" : ts - -> handleMacros spos name ts - Tok spos (CtrlSeq "end") _ : Tok _ Symbol "{" : - Tok _ Word name : Tok _ Symbol "}" : ts - -> handleMacros spos ("end" <> name) ts - Tok _ (CtrlSeq "expandafter") _ : t : ts - -> do setInput ts - doMacros n - getInput >>= setInput . combineTok t - Tok spos (CtrlSeq name) _ : ts - -> handleMacros spos name ts - _ -> return () - where combineTok (Tok spos (CtrlSeq name) x) (Tok _ Word w : ts) - | T.all isLetterOrAt w = - Tok spos (CtrlSeq (name <> w)) (x1 <> w <> x2) : ts - where (x1, x2) = T.break isSpaceOrTab x - combineTok t ts = t:ts - handleMacros spos name ts = do - macros <- sMacros <$> getState - case M.lookup name macros of - Nothing -> return () - Just (Macro expansionPoint argspecs optarg newtoks) -> do - setInput ts - let matchTok (Tok _ toktype txt) = - satisfyTok (\(Tok _ toktype' txt') -> - toktype == toktype' && - txt == txt') - let matchPattern toks = try $ mapM_ matchTok toks - let getargs argmap [] = return argmap - getargs argmap (Pattern toks : rest) = try $ do - matchPattern toks - getargs argmap rest - getargs argmap (ArgNum i : Pattern toks : rest) = - try $ do - x <- mconcat <$> manyTill - (braced <|> ((:[]) <$> anyTok)) - (matchPattern toks) - getargs (M.insert i x argmap) rest - getargs argmap (ArgNum i : rest) = do - x <- try $ spaces >> bracedOrToken - getargs (M.insert i x argmap) rest - args <- case optarg of - Nothing -> getargs M.empty argspecs - Just o -> do - x <- option o bracketedToks - getargs (M.singleton 1 x) argspecs - -- first boolean param is true if we're tokenizing - -- an argument (in which case we don't want to - -- expand #1 etc.) - let addTok False (Tok _ (Arg i) _) acc = - case M.lookup i args of - Nothing -> mzero - Just xs -> foldr (addTok True) acc xs - -- see #4007 - addTok _ (Tok _ (CtrlSeq x) txt) - acc@(Tok _ Word _ : _) - | not (T.null txt) && - isLetter (T.last txt) = - Tok spos (CtrlSeq x) (txt <> " ") : acc - addTok _ t acc = setpos spos t : acc - ts' <- getInput - setInput $ foldr (addTok False) ts' newtoks - case expansionPoint of - ExpandWhenUsed -> - if n > 20 -- detect macro expansion loops - then throwError $ PandocMacroLoop (T.unpack name) - else doMacros (n + 1) - ExpandWhenDefined -> return () - + unless (expanded || verbatimMode) $ do + getInput >>= doMacros' 1 >>= setInput + updateState $ \st -> st{ sExpanded = True } + +doMacros' :: PandocMonad m => Int -> [Tok] -> LP m [Tok] +doMacros' n inp = do + case inp of + Tok spos (CtrlSeq "begin") _ : Tok _ Symbol "{" : + Tok _ Word name : Tok _ Symbol "}" : ts + -> handleMacros n spos name ts + Tok spos (CtrlSeq "end") _ : Tok _ Symbol "{" : + Tok _ Word name : Tok _ Symbol "}" : ts + -> handleMacros n spos ("end" <> name) ts + Tok _ (CtrlSeq "expandafter") _ : t : ts + -> combineTok t <$> doMacros' n ts + Tok spos (CtrlSeq name) _ : ts + -> handleMacros n spos name ts + _ -> return inp + <|> return inp + + where + combineTok (Tok spos (CtrlSeq name) x) (Tok _ Word w : ts) + | T.all isLetterOrAt w = + Tok spos (CtrlSeq (name <> w)) (x1 <> w <> x2) : ts + where (x1, x2) = T.break isSpaceOrTab x + combineTok t ts = t:ts + + matchTok (Tok _ toktype txt) = + satisfyTok (\(Tok _ toktype' txt') -> + toktype == toktype' && + txt == txt') + + matchPattern toks = try $ mapM_ matchTok toks + + getargs argmap [] = return argmap + getargs argmap (Pattern toks : rest) = try $ do + matchPattern toks + getargs argmap rest + getargs argmap (ArgNum i : Pattern toks : rest) = + try $ do + x <- mconcat <$> manyTill (braced <|> ((:[]) <$> anyTok)) + (matchPattern toks) + getargs (M.insert i x argmap) rest + getargs argmap (ArgNum i : rest) = do + x <- try $ spaces >> bracedOrToken + getargs (M.insert i x argmap) rest + + addTok False args spos (Tok _ (Arg i) _) acc = + case M.lookup i args of + Nothing -> mzero + Just xs -> foldr (addTok True args spos) acc xs + -- see #4007 + addTok _ _ spos (Tok _ (CtrlSeq x) txt) + acc@(Tok _ Word _ : _) + | not (T.null txt) + , isLetter (T.last txt) = + Tok spos (CtrlSeq x) (txt <> " ") : acc + addTok _ _ spos t acc = setpos spos t : acc + + handleMacros n' spos name ts = do + when (n' > 20) -- detect macro expansion loops + $ throwError $ PandocMacroLoop (T.unpack name) + macros <- sMacros <$> getState + case M.lookup name macros of + Nothing -> mzero + Just (Macro expansionPoint argspecs optarg newtoks) -> do + let getargs' = do + args <- case optarg of + Nothing -> getargs M.empty argspecs + Just o -> do + x <- option o bracketedToks + getargs (M.singleton 1 x) argspecs + rest <- getInput + return (args, rest) + lstate <- getState + res <- lift $ runParserT getargs' lstate "args" ts + case res of + Left _ -> fail $ "Could not parse arguments for " ++ + T.unpack name + Right (args, rest) -> do + -- first boolean param is true if we're tokenizing + -- an argument (in which case we don't want to + -- expand #1 etc.) + let result = foldr (addTok False args spos) rest newtoks + case expansionPoint of + ExpandWhenUsed -> doMacros' (n' + 1) result + ExpandWhenDefined -> return result setpos :: SourcePos -> Tok -> Tok setpos spos (Tok _ tt txt) = Tok spos tt txt diff --git a/src/Text/Pandoc/Readers/Markdown.hs b/src/Text/Pandoc/Readers/Markdown.hs index d1ea7a1a5..5944ecf82 100644 --- a/src/Text/Pandoc/Readers/Markdown.hs +++ b/src/Text/Pandoc/Readers/Markdown.hs @@ -1586,6 +1586,7 @@ symbol = do <|> try (do lookAhead $ char '\\' notFollowedBy' (() <$ rawTeXBlock) char '\\') + updateLastStrPos return $ return $ B.str [result] -- parses inline code, between n `s and n `s @@ -1632,7 +1633,7 @@ enclosure c = do 3 -> three c 2 -> two c mempty 1 -> one c mempty - _ -> return (return $ B.str cs) + _ -> updateLastStrPos >> return (return $ B.str cs) ender :: PandocMonad m => Char -> Int -> MarkdownParser m () ender c n = try $ do @@ -1716,19 +1717,24 @@ nonEndline = satisfy (/='\n') str :: PandocMonad m => MarkdownParser m (F Inlines) str = do + canRelocateSpace <- notAfterString result <- many1 (alphaNum <|> try (char '.' <* notFollowedBy (char '.'))) updateLastStrPos (do guardEnabled Ext_smart abbrevs <- getOption readerAbbreviations if not (null result) && last result == '.' && result `Set.member` abbrevs then try (do ils <- whitespace <|> endline - lookAhead alphaNum + -- ?? lookAhead alphaNum + -- replace space after with nonbreaking space + -- if softbreak, move before abbrev if possible (#4635) return $ do ils' <- ils - if ils' == B.space - then return (B.str result <> B.str "\160") - else -- linebreak or softbreak - return (ils' <> B.str result <> B.str "\160")) + case B.toList ils' of + [Space] -> + return (B.str result <> B.str "\160") + [SoftBreak] | canRelocateSpace -> + return (ils' <> B.str result <> B.str "\160") + _ -> return (B.str result <> ils')) <|> return (return (B.str result)) else return (return (B.str result))) <|> return (return (B.str result)) diff --git a/src/Text/Pandoc/Readers/Muse.hs b/src/Text/Pandoc/Readers/Muse.hs index 134598c07..6acc88b3d 100644 --- a/src/Text/Pandoc/Readers/Muse.hs +++ b/src/Text/Pandoc/Readers/Muse.hs @@ -44,7 +44,6 @@ import Control.Monad import Control.Monad.Reader import Control.Monad.Except (throwError) import Data.Bifunctor -import Data.Char (isAlphaNum) import Data.Default import Data.List (intercalate) import Data.List.Split (splitOn) @@ -59,8 +58,8 @@ import Text.Pandoc.Definition import Text.Pandoc.Error (PandocError (PandocParsecError)) import Text.Pandoc.Logging import Text.Pandoc.Options -import Text.Pandoc.Parsing hiding (F, enclosed) -import Text.Pandoc.Shared (crFilter, underlineSpan, mapLeft) +import Text.Pandoc.Parsing hiding (F) +import Text.Pandoc.Shared (crFilter, underlineSpan) -- | Read Muse from an input string and return a Pandoc document. readMuse :: PandocMonad m @@ -69,9 +68,9 @@ readMuse :: PandocMonad m -> m Pandoc readMuse opts s = do let input = crFilter s - res <- mapLeft (PandocParsecError $ unpack input) `liftM` runReaderT (runParserT parseMuse def{ museOptions = opts } "source" input) def + res <- flip runReaderT def $ runParserT parseMuse def{ museOptions = opts } "source" input case res of - Left e -> throwError e + Left e -> throwError $ PandocParsecError (unpack input) e Right d -> return d type F = Future MuseState @@ -83,7 +82,6 @@ data MuseState = MuseState { museMeta :: F Meta -- ^ Document metadata , museLastStrPos :: Maybe SourcePos -- ^ Position after last str parsed , museLogMessages :: [LogMessage] , museNotes :: M.Map String (SourcePos, F Blocks) - , museInPara :: Bool -- ^ True when looking for a paragraph terminator } instance Default MuseState where @@ -94,15 +92,17 @@ instance Default MuseState where , museLastStrPos = Nothing , museLogMessages = [] , museNotes = M.empty - , museInPara = False } data MuseEnv = MuseEnv { museInLink :: Bool -- ^ True when parsing a link description to avoid nested links + , museInPara :: Bool -- ^ True when parsing paragraph is not allowed } instance Default MuseEnv where - def = MuseEnv { museInLink = False } + def = MuseEnv { museInLink = False + , museInPara = False + } type MuseParser m = ParserT Text MuseState (ReaderT MuseEnv m) @@ -131,18 +131,12 @@ parseMuse = do many directive blocks <- (:) <$> parseBlocks <*> many parseSection st <- getState - let doc = runF (Pandoc <$> museMeta st <*> fmap B.toList (mconcat blocks)) st - reportLogMessages - return doc + runF (Pandoc <$> museMeta st <*> fmap B.toList (mconcat blocks)) st <$ reportLogMessages -- * Utility functions commonPrefix :: String -> String -> String -commonPrefix _ [] = [] -commonPrefix [] _ = [] -commonPrefix (x:xs) (y:ys) - | x == y = x : commonPrefix xs ys - | otherwise = [] +commonPrefix xs ys = map fst $ takeWhile (uncurry (==)) $ zip xs ys -- | Trim up to one newline from the beginning of the string. lchop :: String -> String @@ -159,12 +153,11 @@ dropSpacePrefix lns = where flns = filter (not . all (== ' ')) lns maxIndent = if null flns then maximum (map length lns) else length $ takeWhile (== ' ') $ foldl1 commonPrefix flns -atStart :: PandocMonad m => MuseParser m a -> MuseParser m a -atStart p = do +atStart :: PandocMonad m => MuseParser m () +atStart = do pos <- getPosition st <- getState guard $ museLastStrPos st /= Just pos - p firstColumn :: PandocMonad m => MuseParser m () firstColumn = getPosition >>= \pos -> guard (sourceColumn pos == 1) @@ -206,18 +199,16 @@ htmlAttrToPandoc attrs = (ident, classes, keyvals) where ident = fromMaybe "" $ lookup "id" attrs classes = maybe [] words $ lookup "class" attrs - keyvals = [(k,v) | (k,v) <- attrs, k /= "id" && k /= "class"] + keyvals = [(k,v) | (k,v) <- attrs, k /= "id", k /= "class"] parseHtmlContent :: PandocMonad m => String -- ^ Tag name -> MuseParser m (Attr, F Blocks) -parseHtmlContent tag = try $ do - indent <- getIndent - attr <- openTag tag - manyTill spaceChar eol - content <- parseBlocksTill $ try $ count indent spaceChar *> closeTag tag - manyTill spaceChar eol -- closing tag must be followed by optional whitespace and newline - return (htmlAttrToPandoc attr, content) +parseHtmlContent tag = try $ getIndent >>= \indent -> (,) + <$> fmap htmlAttrToPandoc (openTag tag) + <* manyTill spaceChar eol + <*> allowPara (parseBlocksTill (try $ indentWith indent *> closeTag tag)) + <* manyTill spaceChar eol -- closing tag must be followed by optional whitespace and newline -- ** Directive parsers @@ -250,6 +241,9 @@ directive = do -- ** Block parsers +allowPara :: MonadReader MuseEnv m => m a -> m a +allowPara p = local (\s -> s { museInPara = False }) p + -- | Parse section contents until EOF or next header parseBlocks :: PandocMonad m => MuseParser m (F Blocks) @@ -263,10 +257,9 @@ parseBlocks = nextSection = mempty <$ lookAhead headingStart parseEnd = mempty <$ eof blockStart = (B.<>) <$> (blockElements <|> emacsNoteBlock) - <*> parseBlocks - listStart = do - updateState (\st -> st { museInPara = False }) - uncurry (B.<>) <$> (anyListUntil parseBlocks <|> amuseNoteBlockUntil parseBlocks) + <*> allowPara parseBlocks + listStart = + uncurry (B.<>) <$> allowPara (anyListUntil parseBlocks <|> amuseNoteBlockUntil parseBlocks) paraStart = do indent <- length <$> many spaceChar uncurry (B.<>) . first (p indent) <$> paraUntil parseBlocks @@ -282,46 +275,36 @@ parseSection = parseBlocksTill :: PandocMonad m => MuseParser m a -> MuseParser m (F Blocks) -parseBlocksTill end = - try (parseEnd <|> - blockStart <|> - listStart <|> - paraStart) +parseBlocksTill end = continuation where parseEnd = mempty <$ end - blockStart = (B.<>) <$> blockElements <*> continuation - listStart = do - updateState (\st -> st { museInPara = False }) - uncurry (B.<>) <$> anyListUntil (parseEnd <|> continuation) + blockStart = (B.<>) <$> blockElements <*> allowPara continuation + listStart = uncurry (B.<>) <$> allowPara (anyListUntil (parseEnd <|> continuation)) paraStart = uncurry (B.<>) <$> paraUntil (parseEnd <|> continuation) - continuation = parseBlocksTill end + continuation = try $ parseEnd <|> blockStart <|> listStart <|> paraStart listItemContentsUntil :: PandocMonad m => Int -> MuseParser m a -> MuseParser m a -> MuseParser m (F Blocks, a) -listItemContentsUntil col pre end = - try blockStart <|> - try listStart <|> - try paraStart +listItemContentsUntil col pre end = p where + p = try blockStart <|> try listStart <|> 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) - <*> (parsePre <|> continuation <|> parseEnd) + <*> allowPara (parsePre <|> continuation <|> parseEnd) listStart = do - updateState (\st -> st { museInPara = False }) - (f, (r, e)) <- anyListUntil (parsePre <|> continuation <|> parseEnd) + (f, (r, e)) <- allowPara $ anyListUntil (parsePre <|> continuation <|> parseEnd) return (f B.<> r, e) continuation = try $ do blank <- optionMaybe blankline skipMany blankline indentWith col - updateState (\st -> st { museInPara = museInPara st && isNothing blank }) - listItemContentsUntil col pre end + local (\s -> s { museInPara = museInPara s && isNothing blank }) p parseBlock :: PandocMonad m => MuseParser m (F Blocks) parseBlock = do @@ -331,25 +314,22 @@ parseBlock = do where para = fst <$> paraUntil (try (eof <|> void (lookAhead blockElements))) blockElements :: PandocMonad m => MuseParser m (F Blocks) -blockElements = do - updateState (\st -> st { museInPara = False }) - choice [ mempty <$ blankline - , comment - , separator - , example - , exampleTag - , literalTag - , centerTag - , rightTag - , quoteTag - , divTag - , biblioTag - , playTag - , verseTag - , lineBlock - , table - , commentTag - ] +blockElements = (mempty <$ blankline) + <|> comment + <|> separator + <|> example + <|> exampleTag + <|> literalTag + <|> centerTag + <|> rightTag + <|> quoteTag + <|> divTag + <|> biblioTag + <|> playTag + <|> verseTag + <|> lineBlock + <|> table + <|> commentTag -- | Parse a line comment, starting with @;@ in the first column. comment :: PandocMonad m => MuseParser m (F Blocks) @@ -445,9 +425,9 @@ divTag = do -- | Parse @\<biblio>@ tag, the result is the same as @\<div class="biblio">@. -- @\<biblio>@ tag is supported only in Text::Amuse mode. biblioTag :: PandocMonad m => MuseParser m (F Blocks) -biblioTag = do - guardEnabled Ext_amuse - fmap (B.divWith ("", ["biblio"], [])) . snd <$> parseHtmlContent "biblio" +biblioTag = fmap (B.divWith ("", ["biblio"], [])) . snd + <$ guardEnabled Ext_amuse + <*> parseHtmlContent "biblio" -- | Parse @\<play>@ tag, the result is the same as @\<div class="play">@. -- @\<play>@ tag is supported only in Text::Amuse mode. @@ -463,13 +443,11 @@ verseLine = (<>) -- | Parse @\<verse>@ tag. verseTag :: PandocMonad m => MuseParser m (F Blocks) -verseTag = try $ do - indent <- getIndent - openTag "verse" - manyTill spaceChar eol - content <- sequence <$> manyTill (count indent spaceChar *> verseLine) (try $ count indent spaceChar *> closeTag "verse") - manyTill spaceChar eol - return $ B.lineBlock <$> content +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 -- | Parse @\<comment>@ tag. commentTag :: PandocMonad m => MuseParser m (F Blocks) @@ -483,19 +461,16 @@ commentTag = try $ mempty paraContentsUntil :: PandocMonad m => MuseParser m a -- ^ Terminator parser -> MuseParser m (F Inlines, a) -paraContentsUntil end = do - updateState (\st -> st { museInPara = True }) - (l, e) <- someUntil inline $ try (manyTill spaceChar eol *> end) - updateState (\st -> st { museInPara = False }) - return (trimInlinesF $ mconcat l, e) +paraContentsUntil end = first (trimInlinesF . mconcat) + <$> someUntil inline (try (manyTill spaceChar eol *> local (\s -> s { museInPara = True}) end)) -- | Parse a paragraph. paraUntil :: PandocMonad m => MuseParser m a -- ^ Terminator parser -> MuseParser m (F Blocks, a) paraUntil end = do - state <- getState - guard $ not $ museInPara state + inPara <- asks museInPara + guard $ not inPara first (fmap B.para) <$> paraContentsUntil end noteMarker :: PandocMonad m => MuseParser m String @@ -504,6 +479,17 @@ noteMarker = try $ (:) <*> oneOf "123456789" <*> manyTill digit (char ']') +addNote :: PandocMonad m + => String + -> 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 } + -- Amusewiki version of note -- Parsing is similar to list item, except that note marker is used instead of list marker amuseNoteBlockUntil :: PandocMonad m @@ -513,12 +499,8 @@ amuseNoteBlockUntil end = try $ do guardEnabled Ext_amuse ref <- noteMarker <* spaceChar pos <- getPosition - updateState (\st -> st { museInPara = False }) - (content, e) <- listItemContentsUntil (sourceColumn pos - 1) (fail "x") end - oldnotes <- museNotes <$> getState - when (M.member ref oldnotes) - (logMessage $ DuplicateNoteReference ref pos) - updateState $ \s -> s{ museNotes = M.insert ref (pos, content) oldnotes } + (content, e) <- allowPara $ listItemContentsUntil (sourceColumn pos - 1) (fail "x") end + addNote ref pos content return (mempty, e) -- Emacs version of note @@ -526,13 +508,10 @@ amuseNoteBlockUntil end = try $ do emacsNoteBlock :: PandocMonad m => MuseParser m (F Blocks) emacsNoteBlock = try $ do guardDisabled Ext_amuse - pos <- getPosition ref <- noteMarker <* skipSpaces - content <- mconcat <$> blocksTillNote - oldnotes <- museNotes <$> getState - when (M.member ref oldnotes) - (logMessage $ DuplicateNoteReference ref pos) - updateState $ \s -> s{ museNotes = M.insert ref (pos, content) oldnotes } + pos <- getPosition + content <- fmap mconcat blocksTillNote + addNote ref pos content return mempty where blocksTillNote = @@ -544,10 +523,8 @@ emacsNoteBlock = try $ do -- | Parse a line block indicated by @\'>\'@ characters. lineBlock :: PandocMonad m => MuseParser m (F Blocks) -lineBlock = try $ do - indent <- getIndent - lns <- (blankVerseLine <|> nonblankVerseLine) `sepBy1'` try (indentWith indent) - return $ B.lineBlock <$> sequence lns +lineBlock = try $ getIndent >>= \indent -> fmap B.lineBlock . sequence + <$> (blankVerseLine <|> nonblankVerseLine) `sepBy1'` try (indentWith indent) where blankVerseLine = try $ mempty <$ char '>' <* blankline nonblankVerseLine = try (string "> ") *> verseLine @@ -561,8 +538,7 @@ bulletListItemsUntil :: PandocMonad m bulletListItemsUntil indent end = try $ do char '-' void spaceChar <|> lookAhead eol - updateState (\st -> st { museInPara = False }) - (x, (xs, e)) <- listItemContentsUntil (indent + 2) (try (optional blankline *> indentWith indent *> bulletListItemsUntil indent end)) (([],) <$> end) + (x, (xs, e)) <- allowPara $ listItemContentsUntil (indent + 2) (try (optional blankline *> indentWith indent *> bulletListItemsUntil indent end)) (([],) <$> end) return (x:xs, e) -- | Parse a bullet list. @@ -598,8 +574,7 @@ orderedListItemsUntil indent style end = continuation = try $ do pos <- getPosition void spaceChar <|> lookAhead eol - updateState (\st -> st { museInPara = False }) - (x, (xs, e)) <- listItemContentsUntil (sourceColumn pos) (try (optional blankline *> indentWith indent *> museOrderedListMarker style *> continuation)) (([],) <$> end) + (x, (xs, e)) <- allowPara $ listItemContentsUntil (sourceColumn pos) (try (optional blankline *> indentWith indent *> museOrderedListMarker style *> continuation)) (([],) <$> end) return (x:xs, e) -- | Parse an ordered list. @@ -620,8 +595,7 @@ descriptionsUntil :: PandocMonad m -> MuseParser m ([F Blocks], a) descriptionsUntil indent end = do void spaceChar <|> lookAhead eol - updateState (\st -> st { museInPara = False }) - (x, (xs, e)) <- listItemContentsUntil indent (try (optional blankline *> indentWith indent *> manyTill spaceChar (string "::") *> descriptionsUntil indent end)) (([],) <$> end) + (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 @@ -686,12 +660,8 @@ museAppendElement element tbl = MuseFooterRow row -> tbl{ museTableFooters = row : museTableFooters tbl } MuseCaption inlines -> tbl{ museTableCaption = inlines } -tableCell :: PandocMonad m => MuseParser m (F Blocks) -tableCell = try $ fmap B.plain . trimInlinesF . mconcat <$> manyTill inline (lookAhead cellEnd) - where cellEnd = try $ void (many1 spaceChar *> char '|') <|> eol - tableElements :: PandocMonad m => MuseParser m (F [MuseTableElement]) -tableElements = sequence <$> (tableParseElement `sepEndBy1` eol) +tableElements = sequence <$> many1 tableParseElement elementsToTable :: [MuseTableElement] -> MuseTable elementsToTable = foldr museAppendElement emptyTable @@ -710,10 +680,10 @@ tableParseElement = tableParseHeader tableParseRow :: PandocMonad m => Int -- ^ Number of separator characters -> MuseParser m (F [Blocks]) -tableParseRow n = try $ - sequence <$> (tableCell `sepBy2` fieldSep) - where p `sepBy2` sep = (:) <$> p <*> many1 (sep *> p) - fieldSep = many1 spaceChar *> count n (char '|') *> (void (many1 spaceChar) <|> void (lookAhead newline)) +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) -- | Parse a table header row. tableParseHeader :: PandocMonad m => MuseParser m (F MuseTableElement) @@ -732,7 +702,7 @@ tableParseCaption :: PandocMonad m => MuseParser m (F MuseTableElement) tableParseCaption = try $ fmap MuseCaption . trimInlinesF . mconcat <$ many spaceChar <* string "|+" - <*> many1Till inline (try $ string "+|") + <*> many1Till inline (try $ string "+|" *> eol) -- ** Inline parsers @@ -803,24 +773,15 @@ whitespace = try $ pure B.space <$ skipMany1 spaceChar 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 c = try $ enclosedInlines c c - --- | Parses material enclosed between start and end parsers. -enclosed :: (Show end, Stream s m Char) => ParserT s st m t -- ^ start parser - -> ParserT s st m end -- ^ end parser - -> ParserT s st m a -- ^ content parser (to be used repeatedly) - -> ParserT s st m [a] -enclosed start end parser = try $ - start *> notFollowedBy spaceChar *> many1Till parser end - -enclosedInlines :: (PandocMonad m, Show a, Show b) +emphasisBetween :: (PandocMonad m, Show a) => MuseParser m a - -> MuseParser m b -> MuseParser m (F Inlines) -enclosedInlines start end = try $ trimInlinesF . mconcat - <$> enclosed (atStart start) end inline - <* notFollowedBy (satisfy isAlphaNum) +emphasisBetween p = try $ trimInlinesF . mconcat + <$ atStart + <* p + <* notFollowedBy spaceChar + <*> many1Till inline p + <* notFollowedBy alphaNum -- | Parse an inline tag, such as @\<em>@ and @\<strong>@. inlineTag :: PandocMonad m @@ -875,8 +836,7 @@ verbatimTag = return . B.text classTag :: PandocMonad m => MuseParser m (F Inlines) classTag = do classes <- maybe [] words . lookup "name" <$> openTag "class" - res <- manyTill inline $ closeTag "class" - return $ B.spanWith ("", classes, []) <$> mconcat res + fmap (B.spanWith ("", classes, [])) . mconcat <$> manyTill inline (closeTag "class") -- | Parse "~~" as nonbreaking space. nbsp :: PandocMonad m => MuseParser m (F Inlines) @@ -884,14 +844,12 @@ nbsp = try $ pure (B.str "\160") <$ string "~~" -- | Parse code markup, indicated by @\'=\'@ characters. code :: PandocMonad m => MuseParser m (F Inlines) -code = try $ do - atStart $ char '=' - contents <- many1Till (noneOf "\n\r" <|> (newline <* notFollowedBy newline)) $ char '=' - guard $ not $ null contents - guard $ head contents `notElem` " \t\n" - guard $ last contents `notElem` " \t\n" - notFollowedBy $ satisfy isAlphaNum - return $ return $ B.code contents +code = try $ fmap pure $ B.code . uncurry (++) + <$ atStart + <* char '=' + <* notFollowedBy (spaceChar <|> newline) + <*> manyUntil (noneOf "\n\r" <|> (newline <* notFollowedBy newline)) (try $ fmap pure $ noneOf " \t\n\r=" <* char '=') + <* notFollowedBy alphaNum -- | Parse @\<code>@ tag. codeTag :: PandocMonad m => MuseParser m (F Inlines) @@ -916,24 +874,24 @@ str :: PandocMonad m => MuseParser m (F Inlines) str = return . B.str <$> many1 alphaNum <* updateLastStrPos symbol :: PandocMonad m => MuseParser m (F Inlines) -symbol = return . B.str <$> count 1 nonspaceChar +symbol = pure . B.str . pure <$> nonspaceChar -- | Parse a link or image. linkOrImage :: PandocMonad m => MuseParser m (F Inlines) linkOrImage = try $ do inLink <- asks museInLink guard $ not inLink - local (\s -> s { museInLink = True }) (explicitLink <|> image <|> link) + local (\s -> s { museInLink = True }) (link "URL:" <|> image <|> link "") linkContent :: PandocMonad m => MuseParser m (F Inlines) linkContent = trimInlinesF . mconcat <$ char '[' <*> manyTill inline (char ']') --- | Parse a link starting with @URL:@ -explicitLink :: PandocMonad m => MuseParser m (F Inlines) -explicitLink = try $ do - string "[[URL:" +-- | Parse a link starting with (possibly null) prefix +link :: PandocMonad m => String -> MuseParser m (F Inlines) +link prefix = try $ do + string $ "[[" ++ prefix url <- manyTill anyChar $ char ']' content <- option (pure $ B.str url) linkContent char ']' @@ -966,11 +924,3 @@ image = try $ do <*> optionMaybe (many1 digit) <* many spaceChar <*> optionMaybe (oneOf "rlf") - -link :: PandocMonad m => MuseParser m (F Inlines) -link = try $ do - string "[[" - url <- manyTill anyChar $ char ']' - content <- optionMaybe linkContent - char ']' - return $ B.link url "" <$> fromMaybe (return $ B.str url) content diff --git a/src/Text/Pandoc/Readers/RST.hs b/src/Text/Pandoc/Readers/RST.hs index 28fa7b83e..1938ca171 100644 --- a/src/Text/Pandoc/Readers/RST.hs +++ b/src/Text/Pandoc/Readers/RST.hs @@ -665,11 +665,13 @@ directive' = do optional blanklines let body' = body ++ "\n\n" name = trim $ fromMaybe "" (lookup "name" fields) - imgAttr cl = ("", classes, widthAttr ++ heightAttr) + classes = words $ maybe "" trim (lookup "class" fields) + keyvals = [(k, trim v) | (k, v) <- fields, k /= "name", k /= "class"] + imgAttr cl = ("", classes ++ alignClasses, widthAttr ++ heightAttr) where - classes = words $ maybe "" trim (lookup cl fields) ++ - maybe "" (\x -> "align-" ++ trim x) - (lookup "align" fields) + alignClasses = words $ maybe "" trim (lookup cl fields) ++ + maybe "" (\x -> "align-" ++ trim x) + (lookup "align" fields) scale = case trim <$> lookup "scale" fields of Just v -> case reverse v of '%':vv -> @@ -698,8 +700,9 @@ directive' = do "line-block" -> lineBlockDirective body' "raw" -> return $ B.rawBlock (trim top) (stripTrailingNewlines body) "role" -> addNewRole top $ map (second trim) fields - "container" -> B.divWith (name, "container" : words top, []) <$> - parseFromString' parseBlocks body' + "container" -> B.divWith + (name, "container" : words top ++ classes, []) <$> + parseFromString' parseBlocks body' "replace" -> B.para <$> -- consumed by substKey parseInlineFromString (trim top) "unicode" -> B.para <$> -- consumed by substKey @@ -717,7 +720,7 @@ directive' = do (l:ls) -> B.divWith ("",["admonition-title"],[]) (B.para (B.str (toUpper l : ls))) [] -> mempty - return $ B.divWith ("",[label],[]) (lab <> bod) + return $ B.divWith (name,label:classes,keyvals) (lab <> bod) "sidebar" -> do let subtit = maybe "" trim $ lookup "subtitle" fields tit <- B.para . B.strong <$> parseInlineFromString @@ -725,21 +728,21 @@ directive' = do then "" else (": " ++ subtit)) bod <- parseFromString' parseBlocks body' - return $ B.divWith ("",["sidebar"],[]) $ tit <> bod + return $ B.divWith (name,"sidebar":classes,keyvals) $ tit <> bod "topic" -> do tit <- B.para . B.strong <$> parseInlineFromString top bod <- parseFromString' parseBlocks body' - return $ B.divWith ("",["topic"],[]) $ tit <> bod + return $ B.divWith (name,"topic":classes,keyvals) $ tit <> bod "default-role" -> mempty <$ updateState (\s -> s { stateRstDefaultRole = case trim top of "" -> stateRstDefaultRole def role -> role }) x | x == "code" || x == "code-block" -> - codeblock (words $ fromMaybe [] $ lookup "class" fields) + codeblock name classes (lookup "number-lines" fields) (trim top) body "aafig" -> do - let attribs = ("", ["aafig"], map (second trimr) fields) + let attribs = (name, ["aafig"], map (second trimr) fields) return $ B.codeBlockWith attribs $ stripTrailingNewlines body "math" -> return $ B.para $ mconcat $ map B.displayMath $ toChunks $ top ++ "\n\n" ++ body @@ -758,8 +761,7 @@ directive' = do $ B.imageWith attr src "" alt Nothing -> B.imageWith attr src "" alt "class" -> do - let attrs = ("", splitBy isSpace $ trim top, - map (second trimr) fields) + let attrs = (name, words (trim top), map (second trimr) fields) -- directive content or the first immediately following element children <- case body of "" -> block @@ -769,7 +771,7 @@ directive' = do pos <- getPosition logMessage $ SkippedContent (".. " ++ other) pos bod <- parseFromString' parseBlocks $ top ++ "\n\n" ++ body' - return $ B.divWith ("",[other],[]) bod + return $ B.divWith (name, other:classes, keyvals) bod tableDirective :: PandocMonad m => String -> [(String, String)] -> String -> RSTParser m Blocks @@ -989,10 +991,11 @@ toChunks = dropWhile null then "\\begin{aligned}\n" ++ s ++ "\n\\end{aligned}" else s -codeblock :: [String] -> Maybe String -> String -> String -> RSTParser m Blocks -codeblock classes numberLines lang body = +codeblock :: String -> [String] -> Maybe String -> String -> String + -> RSTParser m Blocks +codeblock ident classes numberLines lang body = return $ B.codeBlockWith attribs $ stripTrailingNewlines body - where attribs = ("", classes', kvs) + where attribs = (ident, classes', kvs) classes' = "sourceCode" : lang : maybe [] (const ["numberLines"]) numberLines ++ classes @@ -1266,7 +1269,9 @@ simpleTableHeader headless = try $ do rawContent <- if headless then return "" else simpleTableSep '=' >> anyLine - dashes <- simpleDashedLines '=' <|> simpleDashedLines '-' + dashes <- if headless + then simpleDashedLines '=' + else simpleDashedLines '=' <|> simpleDashedLines '-' newline let lines' = map snd dashes let indices = scanl (+) 0 lines' diff --git a/src/Text/Pandoc/Readers/Vimwiki.hs b/src/Text/Pandoc/Readers/Vimwiki.hs index 15f0d991f..6fdbcb50e 100644 --- a/src/Text/Pandoc/Readers/Vimwiki.hs +++ b/src/Text/Pandoc/Readers/Vimwiki.hs @@ -85,12 +85,12 @@ import qualified Text.Pandoc.Builder as B (blockQuote, bulletList, code, import Text.Pandoc.Class (PandocMonad (..)) import Text.Pandoc.Definition (Attr, Block (BulletList, OrderedList), Inline (Space), ListNumberDelim (..), - ListNumberStyle (..), Meta, Pandoc (..), + ListNumberStyle (..), Pandoc (..), nullMeta) import Text.Pandoc.Options (ReaderOptions) -import Text.Pandoc.Parsing (F, ParserState, ParserT, blanklines, emailAddress, +import Text.Pandoc.Parsing (ParserState, ParserT, blanklines, emailAddress, many1Till, orderedListMarker, readWithM, - registerHeader, runF, spaceChar, stateMeta', + registerHeader, spaceChar, stateMeta, stateOptions, uri) import Text.Pandoc.Shared (crFilter, splitBy, stringify, stripFirstAndLast) import Text.Parsec.Char (alphaNum, anyChar, char, newline, noneOf, oneOf, space, @@ -126,7 +126,7 @@ parseVimwiki = do spaces eof st <- getState - let meta = runF (stateMeta' st) st + let meta = stateMeta st return $ Pandoc meta (toList bs) -- block parser @@ -444,8 +444,8 @@ ph s = try $ do many spaceChar >>string ('%':s) >> spaceChar contents <- trimInlines . mconcat <$> manyTill inline (lookAhead newline) --use lookAhead because of placeholder in the whitespace parser - let meta' = return $ B.setMeta s contents nullMeta :: F Meta - updateState $ \st -> st { stateMeta' = stateMeta' st <> meta' } + let meta' = B.setMeta s contents nullMeta + updateState $ \st -> st { stateMeta = stateMeta st <> meta' } noHtmlPh :: PandocMonad m => VwParser m () noHtmlPh = try $ diff --git a/src/Text/Pandoc/Shared.hs b/src/Text/Pandoc/Shared.hs index 9f48080b8..78a5a6a54 100644 --- a/src/Text/Pandoc/Shared.hs +++ b/src/Text/Pandoc/Shared.hs @@ -111,6 +111,7 @@ import qualified Control.Exception as E import Control.Monad (MonadPlus (..), msum, unless) import qualified Control.Monad.State.Strict as S import qualified Data.ByteString.Lazy as BL +import qualified Data.Bifunctor as Bifunctor import Data.Char (isAlpha, isDigit, isLetter, isLower, isSpace, isUpper, toLower) import Data.Data (Data, Typeable) @@ -637,8 +638,7 @@ inDirectory path action = E.bracket -- mapLeft :: (a -> b) -> Either a c -> Either b c -mapLeft f (Left x) = Left (f x) -mapLeft _ (Right x) = Right x +mapLeft = Bifunctor.first -- | Remove intermediate "." and ".." directories from a path. -- diff --git a/src/Text/Pandoc/Writers/Custom.hs b/src/Text/Pandoc/Writers/Custom.hs index 37fec9f0f..3ec8781be 100644 --- a/src/Text/Pandoc/Writers/Custom.hs +++ b/src/Text/Pandoc/Writers/Custom.hs @@ -36,18 +36,21 @@ import Control.Arrow ((***)) import Control.Exception import Control.Monad (when) import Data.Char (toLower) +import Data.Data (Data) import Data.List (intersperse) import qualified Data.Map as M import Data.Text (Text, pack) import Data.Typeable -import Foreign.Lua (Lua, Pushable) +import Foreign.Lua (Lua, Peekable, Pushable) +import Foreign.Lua.Userdata ( ensureUserdataMetatable, pushAnyWithMetatable + , metatableName) import Text.Pandoc.Class (PandocIO) import Text.Pandoc.Definition import Text.Pandoc.Error import Text.Pandoc.Lua.Init (LuaException (LuaException), runPandocLua, registerScriptPath) import Text.Pandoc.Lua.StackInstances () -import Text.Pandoc.Lua.Util (addField, dofileWithTraceback) +import Text.Pandoc.Lua.Util (addField, addFunction, dofileWithTraceback) import Text.Pandoc.Options import Text.Pandoc.Templates import qualified Text.Pandoc.UTF8 as UTF8 @@ -106,17 +109,37 @@ data PandocLuaException = PandocLuaException String instance Exception PandocLuaException +-- | Readonly and lazy pandoc objects. +newtype LazyPandoc = LazyPandoc Pandoc + deriving (Data) + +instance Pushable LazyPandoc where + push lazyDoc = pushAnyWithMetatable pushPandocMetatable lazyDoc + where + pushPandocMetatable = ensureUserdataMetatable (metatableName lazyDoc) $ + addFunction "__index" indexLazyPandoc + +instance Peekable LazyPandoc where + peek = Lua.peekAny + +indexLazyPandoc :: LazyPandoc -> String -> Lua Lua.NumResults +indexLazyPandoc (LazyPandoc (Pandoc meta blks)) field = 1 <$ + case field of + "blocks" -> Lua.push blks + "meta" -> Lua.push meta + _ -> Lua.pushnil + -- | Convert Pandoc to custom markup. writeCustom :: FilePath -> WriterOptions -> Pandoc -> PandocIO Text writeCustom luaFile opts doc@(Pandoc meta _) = do res <- runPandocLua $ do + Lua.push (LazyPandoc doc) *> Lua.setglobal "PANDOC_DOCUMENT" registerScriptPath luaFile stat <- dofileWithTraceback luaFile -- check for error in lua script (later we'll change the return type -- to handle this more gracefully): when (stat /= Lua.OK) $ Lua.tostring' (-1) >>= throw . PandocLuaException . UTF8.toString - -- TODO - call hierarchicalize, so we have that info rendered <- docToCustom opts doc context <- metaToJSON opts blockListToCustom diff --git a/src/Text/Pandoc/Writers/Docx.hs b/src/Text/Pandoc/Writers/Docx.hs index 524d20fd1..d80b4a7bc 100644 --- a/src/Text/Pandoc/Writers/Docx.hs +++ b/src/Text/Pandoc/Writers/Docx.hs @@ -349,6 +349,8 @@ writeDocx opts doc@(Pandoc meta _) = do "application/vnd.openxmlformats-officedocument.extended-properties+xml") ,("/docProps/core.xml", "application/vnd.openxmlformats-package.core-properties+xml") + ,("/docProps/custom.xml", + "application/vnd.openxmlformats-officedocument.custom-properties+xml") ,("/word/styles.xml", "application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml") ,("/word/document.xml", @@ -507,6 +509,19 @@ writeDocx opts doc@(Pandoc meta _) = do ]) (formatTime defaultTimeLocale "%FT%XZ" utctime) let docPropsEntry = toEntry docPropsPath epochtime $ renderXml docProps + let customProperties :: [(String, String)] + customProperties = [] -- FIXME + let mkCustomProp (k, v) pid = mknode "property" + [("fmtid","{D5CDD505-2E9C-101B-9397-08002B2CF9AE}") + ,("pid", show pid) + ,("name", k)] $ mknode "vt:lpwstr" [] v + let customPropsPath = "docProps/custom.xml" + let customProps = mknode "Properties" + [("xmlns","http://schemas.openxmlformats.org/officeDocument/2006/custom-properties") + ,("xmlns:vt","http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes") + ] $ zipWith mkCustomProp customProperties [(2 :: Int)..] + let customPropsEntry = toEntry customPropsPath epochtime $ renderXml customProps + let relsPath = "_rels/.rels" let rels = mknode "Relationships" [("xmlns", "http://schemas.openxmlformats.org/package/2006/relationships")] $ map (\attrs -> mknode "Relationship" attrs ()) @@ -519,6 +534,9 @@ writeDocx opts doc@(Pandoc meta _) = do , [("Id","rId3") ,("Type","http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties") ,("Target","docProps/core.xml")] + , [("Id","rId5") + ,("Type","http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties") + ,("Target","docProps/custom.xml")] ] let relsEntry = toEntry relsPath epochtime $ renderXml rels @@ -558,7 +576,8 @@ writeDocx opts doc@(Pandoc meta _) = do contentTypesEntry : relsEntry : contentEntry : relEntry : footnoteRelEntry : numEntry : styleEntry : footnotesEntry : commentsEntry : - docPropsEntry : docPropsAppEntry : themeEntry : + docPropsEntry : docPropsAppEntry : customPropsEntry : + themeEntry : fontTableEntry : settingsEntry : webSettingsEntry : imageEntries ++ headerFooterEntries ++ miscRelEntries ++ otherMediaEntries @@ -945,8 +964,13 @@ blockToOpenXML' opts (Table caption aligns widths headers rows) = do else withParaProp (pCustomStyle "TableCaption") $ blockToOpenXML opts (Para caption) let alignmentFor al = mknode "w:jc" [("w:val",alignmentToString al)] () - let cellToOpenXML (al, cell) = withParaProp (alignmentFor al) - $ blocksToOpenXML opts cell + -- Table cells require a <w:p> element, even an empty one! + -- Not in the spec but in Word 2007, 2010. See #4953. + let cellToOpenXML (al, cell) = do + es <- withParaProp (alignmentFor al) $ blocksToOpenXML opts cell + if any (\e -> qName (elName e) == "p") es + then return es + else return $ es ++ [mknode "w:p" [] ()] headers' <- mapM cellToOpenXML $ zip aligns headers rows' <- mapM (mapM cellToOpenXML . zip aligns) rows let borderProps = mknode "w:tcPr" [] diff --git a/src/Text/Pandoc/Writers/LaTeX.hs b/src/Text/Pandoc/Writers/LaTeX.hs index c1b5d0fa4..11d58b90a 100644 --- a/src/Text/Pandoc/Writers/LaTeX.hs +++ b/src/Text/Pandoc/Writers/LaTeX.hs @@ -655,7 +655,10 @@ blockToLaTeX (CodeBlock (identifier,classes,keyvalAttr) str) = do [ (if key == "startFrom" then "firstnumber" else key) ++ "=" ++ mbBraced attr | - (key,attr) <- keyvalAttr ] ++ + (key,attr) <- keyvalAttr, + key `notElem` ["exports", "tangle", "results"] + -- see #4889 + ] ++ (if identifier == "" then [] else [ "label=" ++ ref ]) @@ -1366,19 +1369,27 @@ citationsToBiblatex AuthorInText -> "textcite" NormalCitation -> "autocite" -citationsToBiblatex (c:cs) = do - args <- mapM convertOne (c:cs) - return $ text cmd <> foldl' (<>) empty args - where - cmd = case citationMode c of - SuppressAuthor -> "\\autocites*" - AuthorInText -> "\\textcites" - NormalCitation -> "\\autocites" - convertOne Citation { citationId = k - , citationPrefix = p - , citationSuffix = s - } - = citeArguments p s k +citationsToBiblatex (c:cs) + | all (\cit -> null (citationPrefix cit) && null (citationSuffix cit)) (c:cs) + = do + let cmd = case citationMode c of + SuppressAuthor -> "\\autocite*" + AuthorInText -> "\\textcite" + NormalCitation -> "\\autocite" + return $ text cmd <> + braces (text (intercalate "," (map citationId (c:cs)))) + | otherwise = do + let cmd = case citationMode c of + SuppressAuthor -> "\\autocites*" + AuthorInText -> "\\textcites" + NormalCitation -> "\\autocites" + let convertOne Citation { citationId = k + , citationPrefix = p + , citationSuffix = s + } + = citeArguments p s k + args <- mapM convertOne (c:cs) + return $ text cmd <> foldl' (<>) empty args citationsToBiblatex _ = return empty diff --git a/src/Text/Pandoc/Writers/Markdown.hs b/src/Text/Pandoc/Writers/Markdown.hs index 9a4acb59d..ad8d5c483 100644 --- a/src/Text/Pandoc/Writers/Markdown.hs +++ b/src/Text/Pandoc/Writers/Markdown.hs @@ -847,6 +847,13 @@ blockListToMarkdown opts blocks = do Plain ils : fixBlocks bs fixBlocks (Plain ils : bs) = Para ils : fixBlocks bs + fixBlocks (r@(RawBlock f raw) : b : bs) + | not (null raw) + , last raw /= '\n' = + case b of + Plain{} -> r : fixBlocks (b:bs) + RawBlock{} -> r : fixBlocks (b:bs) + _ -> RawBlock f (raw ++ "\n") : fixBlocks (b:bs) -- #4629 fixBlocks (x : xs) = x : fixBlocks xs fixBlocks [] = [] isListBlock (BulletList _) = True diff --git a/src/Text/Pandoc/Writers/Muse.hs b/src/Text/Pandoc/Writers/Muse.hs index 18aebc364..1374cdde3 100644 --- a/src/Text/Pandoc/Writers/Muse.hs +++ b/src/Text/Pandoc/Writers/Muse.hs @@ -250,11 +250,11 @@ blockToMuse (Header level (ident,_,_) inlines) = do let attr' = if null ident || (isEnabled Ext_auto_identifiers opts && ident == autoId) then empty else "#" <> text ident <> cr - let header' = if topLevel then (text $ replicate level '*') <> space else mempty + let header' = if topLevel then text (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 caption _ _ headers rows) = do +blockToMuse (Table caption _ _ headers rows) = do caption' <- inlineListToMuse caption headers' <- mapM blockListToMuse headers rows' <- mapM (mapM blockListToMuse) rows @@ -294,10 +294,10 @@ noteToMuse :: PandocMonad m -> Muse m Doc noteToMuse num note = hang (length marker) (text marker) <$> - (local (\env -> env { envInsideBlock = True + local (\env -> env { envInsideBlock = True , envInlineStart = True , envAfterSpace = True - }) $ blockListToMuse note) + }) (blockListToMuse note) where marker = "[" ++ show num ++ "] " diff --git a/src/Text/Pandoc/Writers/ODT.hs b/src/Text/Pandoc/Writers/ODT.hs index 1c9481630..ac2ed5b4c 100644 --- a/src/Text/Pandoc/Writers/ODT.hs +++ b/src/Text/Pandoc/Writers/ODT.hs @@ -36,8 +36,9 @@ import Control.Monad.Except (catchError) import Control.Monad.State.Strict import qualified Data.ByteString.Lazy as B import Data.Generics (everywhere', mkT) -import Data.List (isPrefixOf) +import Data.List (isPrefixOf, intercalate) import Data.Maybe (fromMaybe) +import qualified Data.Map as Map import qualified Data.Text.Lazy as TL import System.FilePath (takeDirectory, takeExtension, (<.>)) import Text.Pandoc.BCP47 (Lang (..), getLang, renderLang) @@ -49,7 +50,8 @@ import Text.Pandoc.Logging import Text.Pandoc.MIME (extensionFromMimeType, getMimeType) import Text.Pandoc.Options (WrapOption (..), WriterOptions (..)) import Text.Pandoc.Pretty -import Text.Pandoc.Shared (stringify) +import Text.Pandoc.Shared (stringify, normalizeDate) +import Text.Pandoc.Writers.Shared (lookupMetaString) import Text.Pandoc.UTF8 (fromStringLazy, fromTextLazy, toStringLazy) import Text.Pandoc.Walk import Text.Pandoc.Writers.OpenDocument (writeOpenDocument) @@ -81,6 +83,7 @@ pandocToODT :: PandocMonad m -> O m B.ByteString pandocToODT opts doc@(Pandoc meta _) = do let title = docTitle meta + let authors = docAuthors meta lang <- toLang (getLang opts meta) refArchive <- case writerReferenceDoc opts of @@ -123,6 +126,15 @@ pandocToODT opts doc@(Pandoc meta _) = do ) ) let archive' = addEntryToArchive manifestEntry archive + let userDefinedMetaFields = [k | k <- Map.keys (unMeta meta) + , k `notElem` ["title", "lang", "author", "date"]] + let escapedText = text . escapeStringForXML + let userDefinedMeta = + map (\k -> inTags False "meta:user-defined" + [ ("meta_name", escapeStringForXML k) + ,("meta-value-type", "string") + ] (escapedText $ lookupMetaString k meta)) userDefinedMetaFields + let metaTag metafield = inTagsSimple metafield . escapedText let metaEntry = toEntry "meta.xml" epochtime $ fromStringLazy $ render Nothing $ text "<?xml version=\"1.0\" encoding=\"utf-8\"?>" @@ -134,14 +146,21 @@ pandocToODT opts doc@(Pandoc meta _) = do ,("xmlns:meta","urn:oasis:names:tc:opendocument:xmlns:meta:1.0") ,("xmlns:ooo","http://openoffice.org/2004/office") ,("xmlns:grddl","http://www.w3.org/2003/g/data-view#") - ,("office:version","1.2")] ( inTagsSimple "office:meta" $ - ( inTagsSimple "dc:title" - (text $ escapeStringForXML (stringify title)) + ,("office:version","1.2")] ( inTags True "office:meta" [] $ + ( metaTag "dc:title" (stringify title) $$ case lang of - Just l -> inTagsSimple "dc:language" - (text (escapeStringForXML (renderLang l))) + Just l -> metaTag "dc:language" (renderLang l) Nothing -> empty + $$ + metaTag "dc:creator" + (intercalate "; " (map stringify authors)) + $$ + maybe mempty + (metaTag "dc:date") + (normalizeDate (lookupMetaString "date" meta)) + $$ + vcat userDefinedMeta ) ) ) diff --git a/src/Text/Pandoc/Writers/Shared.hs b/src/Text/Pandoc/Writers/Shared.hs index ed2c46d7b..a7bf30aaa 100644 --- a/src/Text/Pandoc/Writers/Shared.hs +++ b/src/Text/Pandoc/Writers/Shared.hs @@ -41,7 +41,6 @@ module Text.Pandoc.Writers.Shared ( , isDisplayMath , fixDisplayMath , unsmartify - , hasSimpleCells , gridTable , lookupMetaBool , lookupMetaBlocks @@ -55,7 +54,6 @@ module Text.Pandoc.Writers.Shared ( where import Prelude import Control.Monad (zipWithM) -import Data.Monoid (Any (..)) import Data.Aeson (FromJSON (..), Result (..), ToJSON (..), Value (Object), encode, fromJSON) import Data.Char (chr, ord, isAscii, isSpace) @@ -72,7 +70,6 @@ import Text.Pandoc.Pretty import Text.Pandoc.Shared (stringify) import Text.Pandoc.UTF8 (toStringLazy) import Text.Pandoc.XML (escapeStringForXML) -import Text.Pandoc.Walk (query) import Text.Printf (printf) -- | Create JSON value for template from a 'Meta' and an association list @@ -246,21 +243,6 @@ unsmartify opts ('\8216':xs) = '\'' : unsmartify opts xs unsmartify opts (x:xs) = x : unsmartify opts xs unsmartify _ [] = [] --- | True if block is a table that can be represented with --- one line per row. -hasSimpleCells :: Block -> Bool -hasSimpleCells (Table _caption _aligns _widths headers rows) = - all isSimpleCell (concat (headers:rows)) - where - isLineBreak LineBreak = Any True - isLineBreak _ = Any False - hasLineBreak = getAny . query isLineBreak - isSimpleCell [Plain ils] = not (hasLineBreak ils) - isSimpleCell [Para ils ] = not (hasLineBreak ils) - isSimpleCell [] = True - isSimpleCell _ = False -hasSimpleCells _ = False - gridTable :: Monad m => WriterOptions -> (WriterOptions -> [Block] -> m Doc) |