From dd970030ba5be6aac9bf831ea08df39b1d4f874c Mon Sep 17 00:00:00 2001 From: John MacFarlane Date: Mon, 17 Aug 2026 09:27:38 -0700 Subject: [PATCH] HTML reader: handle pre without code. We preserve whitespace as nonbreaking spaces. Closes #11810. --- src/Text/Pandoc/Readers/HTML.hs | 31 +++++++++++++++++++++------ src/Text/Pandoc/Readers/HTML/Types.hs | 1 + test/command/11810.md | 15 +++++++++++++ 3 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 test/command/11810.md diff --git a/src/Text/Pandoc/Readers/HTML.hs b/src/Text/Pandoc/Readers/HTML.hs index 1baeda555..9e57f95da 100644 --- a/src/Text/Pandoc/Readers/HTML.hs +++ b/src/Text/Pandoc/Readers/HTML.hs @@ -92,7 +92,7 @@ readHtml opts inp = do result <- flip runReaderT def $ runParserT parseDoc (HTMLState def{ stateOptions = opts } - [] Nothing Set.empty [] M.empty opts False) + [] Nothing Set.empty [] M.empty opts False False) "source" tags case result of Right doc -> return doc @@ -222,7 +222,7 @@ block = ((do "h5" -> pHeader "h6" -> pHeader "blockquote" -> pBlockQuote - "pre" -> pCodeBlock + "pre" -> pCodeBlock <|> pPreBlock "ul" -> pBulletList "ol" -> pOrderedList "dl" -> pDefinitionList @@ -646,6 +646,15 @@ pFigure = do (B.simpleCaption (mconcat captions)) (mconcat rest) +pPreBlock :: PandocMonad m => TagParser m Blocks +pPreBlock = try $ do + pSatisfy (matchTagOpen "pre" []) + oldInPre <- inPre <$> getState + updateState $ \st -> st{ inPre = True } + contents <- mconcat <$> manyTill block (pCloses "pre" <|> eof) + updateState $ \st -> st{ inPre = oldInPre } + return contents + pCodeBlock :: PandocMonad m => TagParser m Blocks pCodeBlock = try $ do TagOpen _ attr' <- pSatisfy (matchTagOpen "pre" []) @@ -1062,10 +1071,20 @@ pBad = do return $ B.str $ T.singleton c' pSpace :: PandocMonad m => InlinesParser m Inlines -pSpace = many1 (satisfy isSpace) >>= \xs -> - if '\n' `elem` xs - then return B.softbreak - else return B.space +pSpace = do + inpre <- inPre <$> getState + xs <- many1 (satisfy isSpace) + if inpre + then return $ makePreInlines xs + else if '\n' `elem` xs + then return B.softbreak + else return B.space + where + makePreInlines cs = + let chunks = splitWhen (=='\n') cs + tostr = B.str . T.pack . map (\c -> if c == ' ' then '\160' else c) + in mconcat $ filter (/= B.str "") + $ L.intersperse B.linebreak (map tostr chunks) getTagName :: Tag Text -> Maybe Text getTagName (TagOpen t _) = Just t diff --git a/src/Text/Pandoc/Readers/HTML/Types.hs b/src/Text/Pandoc/Readers/HTML/Types.hs index 28798de41..9398767e5 100644 --- a/src/Text/Pandoc/Readers/HTML/Types.hs +++ b/src/Text/Pandoc/Readers/HTML/Types.hs @@ -53,6 +53,7 @@ data HTMLState = HTMLState , macros :: Map Text Macro , readerOpts :: ReaderOptions , inFootnotes :: Bool + , inPre :: Bool } -- | Local HTML parser state diff --git a/test/command/11810.md b/test/command/11810.md new file mode 100644 index 000000000..36bf5246e --- /dev/null +++ b/test/command/11810.md @@ -0,0 +1,15 @@ +``` +% pandoc -f html -t native +
alpha
+    beta
+        gamma
+^D +[ Plain + [ Str "alpha" + , LineBreak + , Str "\160\160\160\160beta" + , LineBreak + , Str "\160\160\160\160\160\160\160\160gamma" + ] +] +```