From 6b14302c166b734b276aa5d8cfbd6c175d45dead Mon Sep 17 00:00:00 2001 From: John MacFarlane Date: Wed, 9 Sep 2026 02:58:08 +0000 Subject: [PATCH] HTML reader: allow omitted in tables. The closing tag is optional in HTML. Previously pRow and pHeaderRow required it, so a table using the omitted form was silently degraded to a sequence of plain blocks. The closing tag is now optional; pHeaderRow instead checks that the row isn't followed by a cell, so body rows are still parsed by pRow. Co-Authored-By: Claude --- src/Text/Pandoc/Readers/HTML/Table.hs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/Text/Pandoc/Readers/HTML/Table.hs b/src/Text/Pandoc/Readers/HTML/Table.hs index b452a60ac..75684fee9 100644 --- a/src/Text/Pandoc/Readers/HTML/Table.hs +++ b/src/Text/Pandoc/Readers/HTML/Table.hs @@ -28,7 +28,7 @@ import Text.Pandoc.CSS (cssAttributes) import Text.Pandoc.Definition import Text.Pandoc.Class.PandocMonad (PandocMonad (..)) import Text.Pandoc.Parsing - ( eof, lookAhead, many, many1, manyTill, option, optional + ( eof, lookAhead, many, many1, manyTill, notFollowedBy, option, optional , optionMaybe, skipMany, try ) import Text.Pandoc.Readers.HTML.Parsing import Text.Pandoc.Readers.HTML.Types (TagParser) @@ -134,7 +134,8 @@ pRow block = try $ do skipMany pBlank TagOpen _ attribs <- pSatisfy (matchTagOpen "tr" []) <* skipMany pBlank cells <- many (pCell block BodyCell <|> pCell block HeaderCell) - TagClose _ <- pSatisfy (matchTagClose "tr") + -- the closing tag may be omitted (it is optional in HTML): + optional $ pSatisfy (matchTagClose "tr") let numheadcells = length $ takeWhile (\(ct,_) -> ct == HeaderCell) cells return (numheadcells, Row (toAttr attribs) $ map snd cells) @@ -145,9 +146,15 @@ pHeaderRow :: PandocMonad m -> TagParser m B.Row pHeaderRow block = try $ do skipMany pBlank - let pThs = many (snd <$> pCell block HeaderCell) - let mkRow (attribs, cells) = Row (toAttr attribs) cells - mkRow <$> pInTagWithAttribs TagsRequired "tr" pThs + TagOpen _ attribs <- pSatisfy (matchTagOpen "tr" []) + cells <- many (snd <$> pCell block HeaderCell) + skipMany pBlank + -- a header row may contain only cells; a following + -- means this is a body row, so we backtrack and let pRow parse it: + notFollowedBy $ pSatisfy (matchTagOpen "td" []) + -- the closing tag may be omitted (it is optional in HTML): + optional $ pSatisfy (matchTagClose "tr") + return $ Row (toAttr attribs) cells -- | Parses a table head. If there is no @thead@ element, this looks for -- a row of @@-only elements as the first line of the table.