git.iko.soy/typeable/pandoc
on master help sign in
HTML reader: improve support for table headers, footer, attributes

- `<tfoot>` elements are no longer added to the table body but used as table footer. - Separate `<tbody>` elements are no longer combined into one. - Attributes on `<thead>`, `<tbody>`, `<th>`/`<td>`, and `<tfoot>` elements are preserved.

commit07919e1b2270a906019575e4ce85590d6754d41c authorAlbert Krewinkel <albert@zeitkraut.de> dateThu 26 Nov 2020, 07:22 (6 years ago) parent3e01ae4
5 files changed+268−140
src/Text/Pandoc/CSS.hs+21−16 src/Text/Pandoc/Readers/HTML.hs+4−36 src/Text/Pandoc/Readers/HTML/Parsing.hs+36−11 src/Text/Pandoc/Readers/HTML/Table.hs+183−55 test/html-reader.native+24−22
src/Text/Pandoc/CSS.hs+21−16
@@ -11,41 +11,46 @@ Portability : portable
1111
1212Tools for working with CSS.
1313-}
14−module Text.Pandoc.CSS ( pickStyleAttrProps
15− , pickStylesToKVs
16− )
14+module Text.Pandoc.CSS
15+ ( cssAttributes
16+ , pickStyleAttrProps
17+ , pickStylesToKVs
18+ )
1719where
1820
19−import qualified Data.Text as T
2021import Data.Maybe (mapMaybe, listToMaybe)
22+import Data.Text (Text, pack)
2123import Text.Pandoc.Shared (trim)
2224import Text.Parsec
2325import Text.Parsec.Text
2426
25−ruleParser :: Parser (T.Text, T.Text)
27+ruleParser :: Parser (Text, Text)
2628ruleParser = do
2729 p <- many1 (noneOf ":") <* char ':'
2830 v <- many1 (noneOf ":;") <* optional (char ';') <* spaces
29− return (trim $ T.pack p, trim $ T.pack v)
31+ return (trim $ pack p, trim $ pack v)
3032
31−styleAttrParser :: Parser [(T.Text, T.Text)]
33+styleAttrParser :: Parser [(Text, Text)]
3234styleAttrParser = many1 ruleParser
3335
34−eitherToMaybe :: Either a b -> Maybe b
35−eitherToMaybe (Right x) = Just x
36−eitherToMaybe _ = Nothing
36+-- | Parses a style string, returning the CSS attributes.
37+-- Returns an empty list on failure.
38+cssAttributes :: Text -> [(Text, Text)]
39+cssAttributes styleString =
40+ -- Use Data.Either.fromRight once GHC 8.0 is no longer supported
41+ case parse styleAttrParser "" styleString of
42+ Left _ -> []
43+ Right x -> x
3744
3845-- | takes a list of keys/properties and a CSS string and
3946-- returns the corresponding key-value-pairs.
40−pickStylesToKVs :: [T.Text] -> T.Text -> [(T.Text, T.Text)]
47+pickStylesToKVs :: [Text] -> Text -> [(Text, Text)]
4148pickStylesToKVs props styleAttr =
42− case parse styleAttrParser "" styleAttr of
43− Left _ -> []
44− Right styles -> filter (\s -> fst s `elem` props) styles
49+ filter (\s -> fst s `elem` props) $ cssAttributes styleAttr
4550
4651-- | takes a list of key/property synonyms and a CSS string and maybe
4752-- returns the value of the first match (in order of the supplied list)
48−pickStyleAttrProps :: [T.Text] -> T.Text -> Maybe T.Text
53+pickStyleAttrProps :: [Text] -> Text -> Maybe Text
4954pickStyleAttrProps lookupProps styleAttr = do
50− styles <- eitherToMaybe $ parse styleAttrParser "" styleAttr
55+ styles <- either (const Nothing) Just $ parse styleAttrParser "" styleAttr
5156 listToMaybe $ mapMaybe (`lookup` styles) lookupProps
src/Text/Pandoc/Readers/HTML.hs+4−36
@@ -26,7 +26,7 @@ module Text.Pandoc.Readers.HTML ( readHtml
2626
2727import Control.Applicative ((<|>))
2828import Control.Arrow (first)
29−import Control.Monad (guard, mplus, msum, mzero, unless, void)
29+import Control.Monad (guard, msum, mzero, unless, void)
3030import Control.Monad.Except (throwError)
3131import Control.Monad.Reader (ask, asks, lift, local, runReaderT)
3232import Data.ByteString.Base64 (encode)
@@ -50,7 +50,7 @@ import Text.Pandoc.CSS (pickStyleAttrProps)
5050import qualified Text.Pandoc.UTF8 as UTF8
5151import Text.Pandoc.Definition
5252import Text.Pandoc.Readers.HTML.Parsing
53−import Text.Pandoc.Readers.HTML.Table (pTable')
53+import Text.Pandoc.Readers.HTML.Table (pTable)
5454import Text.Pandoc.Readers.HTML.TagCategories
5555import Text.Pandoc.Readers.HTML.Types
5656import Text.Pandoc.Readers.LaTeX (rawLaTeXInline)
@@ -63,8 +63,7 @@ import Text.Pandoc.Options (
6363 extensionEnabled)
6464import Text.Pandoc.Parsing hiding ((<|>))
6565import Text.Pandoc.Shared (addMetaField, blocksToInlines', crFilter, escapeURI,
66− extractSpaces, htmlSpanLikeElements, elemText, splitTextBy,
67− safeRead, tshow)
66+ extractSpaces, htmlSpanLikeElements, safeRead, tshow)
6867import Text.Pandoc.Walk
6968import Text.Parsec.Error
7069import Text.TeXMath (readMathML, writeTeX)
@@ -159,7 +158,7 @@ block = do
159158 , pCodeBlock
160159 , pList
161160 , pHrule
162− , pTable
161+ , pTable block
163162 , pHtml
164163 , pHead
165164 , pBody
@@ -464,31 +463,6 @@ pHrule = do
464463 pSelfClosing (=="hr") (const True)
465464 return B.horizontalRule
466465
467−pTable :: PandocMonad m => TagParser m Blocks
468−pTable = pTable' block pCell
469−
470−pCell :: PandocMonad m => Text -> TagParser m [Cell]
471−pCell celltype = try $ do
472− skipMany pBlank
473− tag <- lookAhead $ pSatisfy (\t -> t ~== TagOpen celltype [])
474− let extractAlign' [] = ""
475− extractAlign' ("text-align":x:_) = x
476− extractAlign' (_:xs) = extractAlign' xs
477− let extractAlign = extractAlign' . splitTextBy (`elemText` " \t;:")
478− let align = case maybeFromAttrib "align" tag `mplus`
479− (extractAlign <$> maybeFromAttrib "style" tag) of
480− Just "left" -> AlignLeft
481− Just "right" -> AlignRight
482− Just "center" -> AlignCenter
483− _ -> AlignDefault
484− let rowspan = RowSpan . fromMaybe 1 $
485− safeRead =<< maybeFromAttrib "rowspan" tag
486− let colspan = ColSpan . fromMaybe 1 $
487− safeRead =<< maybeFromAttrib "colspan" tag
488− res <- pInTags celltype block
489− skipMany pBlank
490− return [B.cell align rowspan colspan res]
491−
492466pBlockQuote :: PandocMonad m => TagParser m Blocks
493467pBlockQuote = do
494468 contents <- pInTags "blockquote" block
@@ -653,12 +627,6 @@ pLineBreak = do
653627 pSelfClosing (=="br") (const True)
654628 return B.linebreak
655629
656−-- Unlike fromAttrib from tagsoup, this distinguishes
657−-- between a missing attribute and an attribute with empty content.
658−maybeFromAttrib :: Text -> Tag Text -> Maybe Text
659−maybeFromAttrib name (TagOpen _ attrs) = lookup name attrs
660−maybeFromAttrib _ _ = Nothing
661−
662630pLink :: PandocMonad m => TagParser m Inlines
663631pLink = try $ do
664632 tag <- pSatisfy $ tagOpenLit "a" (const True)
src/Text/Pandoc/Readers/HTML/Parsing.hs+36−11
@@ -1,3 +1,4 @@
1+{-# LANGUAGE LambdaCase #-}
12{-# LANGUAGE OverloadedStrings #-}
23{- |
34 Module : Text.Pandoc.Readers.HTML.Parsing
@@ -15,6 +16,7 @@ module Text.Pandoc.Readers.HTML.Parsing
1516 , pInTags
1617 , pInTags'
1718 , pInTag
19+ , pInTagWithAttribs
1820 , pAny
1921 , pCloses
2022 , pSatisfy
@@ -22,6 +24,7 @@ module Text.Pandoc.Readers.HTML.Parsing
2224 , matchTagClose
2325 , matchTagOpen
2426 , isSpace
27+ , maybeFromAttrib
2528 , toAttr
2629 , toStringAttr
2730 )
@@ -31,11 +34,11 @@ import Control.Monad (guard, void, mzero)
3134import Data.Maybe (fromMaybe)
3235import Data.Text (Text)
3336import Text.HTML.TagSoup
34− ( Tag (..), (~==), isTagText, isTagPosition, isTagOpen, isTagClose )
37+ ( Attribute, Tag (..), isTagText, isTagPosition, isTagOpen, isTagClose, (~==) )
3538import Text.Pandoc.Class.PandocMonad (PandocMonad (..))
3639import Text.Pandoc.Definition (Attr)
3740import Text.Pandoc.Parsing
38− ( (<|>), eof, getPosition, lookAhead, manyTill, newPos, optional
41+ ( (<|>), eof, getPosition, lookAhead, manyTill, newPos, option, optional
3942 , skipMany, setPosition, token, try)
4043import Text.Pandoc.Readers.HTML.TagCategories
4144import Text.Pandoc.Readers.HTML.Types
@@ -60,25 +63,41 @@ pInTags' :: (PandocMonad m, Monoid a)
6063 -> TagParser m a
6164 -> TagParser m a
6265pInTags' tagtype tagtest parser = try $ do
63− pSatisfy (\t -> t ~== TagOpen tagtype [] && tagtest t)
66+ pSatisfy $ \t -> matchTagOpen tagtype [] t && tagtest t
6467 mconcat <$> manyTill parser (pCloses tagtype <|> eof)
6568
66−-- parses p, preceded by an opening tag (optional if tagsOptional)
67−-- and followed by a closing tag (optional if tagsOptional)
68−pInTag :: PandocMonad m => TagOmission -> Text -> TagParser m a -> TagParser m a
69−pInTag tagOmission tagtype p = try $ do
70− skipMany pBlank
69+pInTag :: PandocMonad m
70+ => TagOmission -- ^ Whether some tags can be omitted
71+ -> Text -- ^ @tagtype@ Tag name
72+ -> TagParser m a -- ^ @p@ Content parser
73+ -> TagParser m a
74+pInTag tagOmission tagtype = fmap snd . pInTagWithAttribs tagOmission tagtype
75+
76+-- | Returns the contents of a tag together with its attributes; parses
77+-- @p@, preceded by an opening tag (optional if TagsOmittable) and
78+-- followed by a closing tag (optional unless TagsRequired).
79+pInTagWithAttribs :: PandocMonad m
80+ => TagOmission -- ^ Whether some tags can be omitted
81+ -> Text -- ^ @tagtype@ Tag name
82+ -> TagParser m a -- ^ @p@ Content parser
83+ -> TagParser m ([Attribute Text], a)
84+pInTagWithAttribs tagOmission tagtype p = try $ do
7185 let openingOptional = tagOmission == TagsOmittable
7286 let closingOptional = tagOmission /= TagsRequired
73− (if openingOptional then optional else void) $
74− pSatisfy (matchTagOpen tagtype [])
87+ skipMany pBlank
88+ attribs <- (if openingOptional then option [] else id)
89+ (getAttribs <$> pSatisfy (matchTagOpen tagtype []))
7590 skipMany pBlank
7691 x <- p
7792 skipMany pBlank
7893 (if closingOptional then optional else void) $
7994 pSatisfy (matchTagClose tagtype)
8095 skipMany pBlank
81− return x
96+ return (attribs, x)
97+ where
98+ getAttribs = \case
99+ TagOpen _ attribs -> attribs
100+ _ -> []
82101
83102pCloses :: PandocMonad m => Text -> TagParser m ()
84103pCloses tagtype = try $ do
@@ -183,6 +202,12 @@ toStringAttr = map go
183202 -> (x',y)
184203 _ -> (x,y)
185204
205+-- Unlike fromAttrib from tagsoup, this distinguishes
206+-- between a missing attribute and an attribute with empty content.
207+maybeFromAttrib :: Text -> Tag Text -> Maybe Text
208+maybeFromAttrib name (TagOpen _ attrs) = lookup name attrs
209+maybeFromAttrib _ _ = Nothing
210+
186211mkAttr :: [(Text, Text)] -> Attr
187212mkAttr attr = (attribsId, attribsClasses, attribsKV)
188213 where attribsId = fromMaybe "" $ lookup "id" attr
src/Text/Pandoc/Readers/HTML/Table.hs+183−55
@@ -1,3 +1,4 @@
1+{-# LANGUAGE LambdaCase #-}
12{-# LANGUAGE OverloadedStrings #-}
23{-# LANGUAGE ViewPatterns #-}
34{- |
@@ -12,17 +13,19 @@
1213
1314HTML table parser.
1415-}
15−module Text.Pandoc.Readers.HTML.Table (pTable') where
16+module Text.Pandoc.Readers.HTML.Table (pTable) where
1617
17−import Control.Monad (guard)
18+import Control.Applicative ((<|>))
1819import Data.Maybe (fromMaybe)
1920import Data.Text (Text)
2021import Text.HTML.TagSoup
2122import Text.Pandoc.Builder (Blocks)
23+import Text.Pandoc.CSS (cssAttributes)
2224import Text.Pandoc.Definition
2325import Text.Pandoc.Class.PandocMonad (PandocMonad (..))
2426import Text.Pandoc.Parsing
25− ( (<|>), eof, many, many1, manyTill, option, optional, skipMany, try)
27+ ( eof, lookAhead, many, many1, manyTill, option, optional
28+ , optionMaybe, skipMany, try)
2629import Text.Pandoc.Readers.HTML.Parsing
2730import Text.Pandoc.Readers.HTML.Types (TagParser)
2831import Text.Pandoc.Shared (onlySimpleTableCells, safeRead)
@@ -57,58 +60,183 @@ pColgroup = try $ do
5760 skipMany pBlank
5861 manyTill pCol (pCloses "colgroup" <|> eof) <* skipMany pBlank
5962
60−-- | Parses a simple HTML table
61−pTable' :: PandocMonad m
62− => TagParser m Blocks -- ^ Caption parser
63− -> (Text -> TagParser m [Cell]) -- ^ Table cell parser
64− -> TagParser m Blocks
65−pTable' block pCell = try $ do
66− TagOpen _ attribs' <- pSatisfy (matchTagOpen "table" [])
67− let attribs = toAttr attribs'
63+pCell :: PandocMonad m
64+ => TagParser m Blocks
65+ -> Text
66+ -> TagParser m [Cell]
67+pCell block celltype = try $ do
68+ skipMany pBlank
69+ TagOpen _ attribs <- lookAhead $ pSatisfy (matchTagOpen celltype [])
70+ let cssAttribs = maybe [] cssAttributes $ lookup "style" attribs
71+ let align = case lookup "align" attribs <|>
72+ lookup "text-align" cssAttribs of
73+ Just "left" -> AlignLeft
74+ Just "right" -> AlignRight
75+ Just "center" -> AlignCenter
76+ _ -> AlignDefault
77+ let rowspan = RowSpan . fromMaybe 1 $
78+ safeRead =<< lookup "rowspan" attribs
79+ let colspan = ColSpan . fromMaybe 1 $
80+ safeRead =<< lookup "colspan" attribs
81+ res <- pInTags celltype block
82+ skipMany pBlank
83+ let handledAttribs = ["align", "colspan", "rowspan", "text-align"]
84+ attribs' = foldr go [] attribs
85+ go kv@(k, _) acc = case k of
86+ "style" -> case filter ((/= "text-align") . fst) cssAttribs of
87+ [] -> acc
88+ cs -> ("style", toStyleString cs) : acc
89+ -- drop attrib if it's already handled
90+ _ | k `elem` handledAttribs -> acc
91+ _ -> kv : acc
92+ return [B.cellWith (toAttr attribs') align rowspan colspan res]
93+
94+-- | Create a style attribute string from a list of CSS attributes
95+toStyleString :: [(Text, Text)] -> Text
96+toStyleString = T.intercalate "; " . map (\(k, v) -> k <> ": " <> v)
97+
98+data RowType
99+ = HeaderCells
100+ | AllCells
101+
102+-- | Parses a table row
103+pRow :: PandocMonad m
104+ => TagParser m Blocks
105+ -> RowType
106+ -> TagParser m [B.Row]
107+pRow block rowType = try $ do
108+ skipMany pBlank
109+ case rowType of
110+ HeaderCells -> do
111+ maybeCells <- optionMaybe (pInTags "tr" (pCell block "th"))
112+ return $ case maybeCells of
113+ Nothing -> []
114+ Just cells -> [Row nullAttr cells]
115+ AllCells -> do
116+ cells <- pInTags "tr" (pCell block "td" <|> pCell block "th")
117+ return [Row nullAttr cells]
118+
119+-- | Parses a table head
120+pTableHead :: PandocMonad m
121+ => TagParser m Blocks
122+ -> TagParser m TableHead
123+pTableHead block = try $ do
124+ skipMany pBlank
125+ (attribs, rows) <- pInTagWithAttribs ClosingTagOptional "thead"
126+ (option [] $ pRow block AllCells)
127+ <|> pInTagWithAttribs TagsOmittable "thead"
128+ (pRow block HeaderCells)
129+ let cells = concatMap (\(Row _ cs) -> cs) rows
130+ if null cells
131+ then TableHead nullAttr <$>
132+ pInTag TagsOmittable "tbody" (pRow block HeaderCells)
133+ else return $ TableHead (toAttr attribs) [Row nullAttr cells]
134+
135+-- | Parses a table foot
136+pTableFoot :: PandocMonad m
137+ => TagParser m Blocks
138+ -> TagParser m TableFoot
139+pTableFoot block = try $ do
140+ skipMany pBlank
141+ TagOpen _ attribs <- pSatisfy (matchTagOpen "tfoot" []) <* skipMany pBlank
142+ rows <- mconcat <$> many (pRow block AllCells <* skipMany pBlank)
143+ optional $ pSatisfy (matchTagClose "tfoot")
144+ return $ TableFoot (toAttr attribs) rows
145+
146+-- | Parses a table body
147+pTableBody :: PandocMonad m
148+ => TagParser m Blocks
149+ -> TagParser m TableBody
150+pTableBody block = do
68151 skipMany pBlank
69− caption <- option mempty $ pInTags "caption" block <* skipMany pBlank
70− widths' <- (mconcat <$> many1 pColgroup) <|> many pCol
71− let pTh = option [] $ pInTags "tr" (pCell "th")
72− pTr = try $ skipMany pBlank
73− *> pInTags "tr" (pCell "td" <|> pCell "th")
74− pTBody = pInTag TagsOmittable "tbody" $ many1 pTr
75− head'' <- pInTag ClosingTagOptional "thead" (option [] pTr)
76− <|> pInTag TagsOmittable "thead" pTh
77− head' <- pInTag TagsOmittable "tbody"
78− (if null head'' then pTh else return head'')
79− topfoot <- option [] $ pInTag TagsRequired "tfoot" $ many pTr
80− rowsLs <- many pTBody
81− bottomfoot <- option [] $ pInTag ClosingTagOptional "tfoot" $ many pTr
152+ (attribs, rows) <- pInTagWithAttribs TagsOmittable "tbody"
153+ (mconcat <$> many1 (pRow block AllCells))
154+ return $ TableBody (toAttr attribs) 0 [] rows
155+
156+
157+-- | Parses a simple HTML table
158+pTable :: PandocMonad m
159+ => TagParser m Blocks -- ^ Caption and cell contents parser
160+ -> TagParser m Blocks
161+pTable block = try $ do
162+ TagOpen _ attribs <- pSatisfy (matchTagOpen "table" []) <* skipMany pBlank
163+ caption <- option mempty $ pInTags "caption" block <* skipMany pBlank
164+ widths <- ((mconcat <$> many1 pColgroup) <|> many pCol) <* skipMany pBlank
165+ thead <- pTableHead block <* skipMany pBlank
166+ topfoot <- optionMaybe (pTableFoot block) <* skipMany pBlank
167+ tbodies <- many (pTableBody block) <* skipMany pBlank
168+ botfoot <- optionMaybe (pTableFoot block) <* skipMany pBlank
82169 TagClose _ <- pSatisfy (matchTagClose "table")
83− let rows = concat rowsLs <> topfoot <> bottomfoot
84− rows''' = map (map cellContents) rows
170+ let tfoot = fromMaybe (TableFoot nullAttr []) $ topfoot <|> botfoot
171+ case normalize widths thead tbodies tfoot of
172+ Left err -> fail err
173+ Right (colspecs, thead', tbodies', tfoot') -> return $
174+ B.tableWith (toAttr attribs)
175+ (B.simpleCaption caption)
176+ colspecs
177+ thead'
178+ tbodies'
179+ tfoot'
180+data TableType
181+ = SimpleTable
182+ | NormalTable
183+
184+tableType :: [[Cell]] -> TableType
185+tableType cells =
186+ if onlySimpleTableCells $ map (map cellContents) cells
187+ then SimpleTable
188+ else NormalTable
189+ where
190+ cellContents :: Cell -> [Block]
191+ cellContents (Cell _ _ _ _ bs) = bs
192+
193+normalize :: [ColWidth] -> TableHead -> [TableBody] -> TableFoot
194+ -> Either String ([ColSpec], TableHead, [TableBody], TableFoot)
195+normalize widths head' bodies foot = do
196+ let rows = headRows head' <> concatMap bodyRows bodies <> footRows foot
197+ let rowLength = length . rowCells
198+ let ncols = maximum (map rowLength rows)
199+ let tblType = tableType (map rowCells rows)
85200 -- fail on empty table
86− guard $ not $ null head' && null rows'''
87− let isSimple = onlySimpleTableCells $
88− map cellContents head' : rows'''
89− let cols = if null head'
90− then maximum (map length rows''')
91− else length head'
92− let aligns = case rows of
93− (cs:_) -> take cols $
94− concatMap cellAligns cs ++ repeat AlignDefault
95− _ -> replicate cols AlignDefault
96− let widths = if null widths'
97− then if isSimple
98− then replicate cols ColWidthDefault
99− else replicate cols (ColWidth (1.0 / fromIntegral cols))
100− else widths'
101− let toRow = Row nullAttr
102− toHeaderRow l = [toRow l | not (null l)]
103− return $ B.tableWith attribs
104− (B.simpleCaption caption)
105− (zip aligns widths)
106− (TableHead nullAttr $ toHeaderRow head')
107− [TableBody nullAttr 0 [] $ map toRow rows]
108− (TableFoot nullAttr [])
109−
110−cellContents :: Cell -> [Block]
111−cellContents (Cell _ _ _ _ bs) = bs
112−
113−cellAligns :: Cell -> [Alignment]
114−cellAligns (Cell _ align _ (ColSpan cs) _) = replicate cs align
201+ if null rows
202+ then Left "empty table"
203+ else Right
204+ ( zip (calculateAlignments ncols bodies)
205+ (normalizeColWidths ncols tblType widths)
206+ , head'
207+ , bodies
208+ , foot
209+ )
210+
211+normalizeColWidths :: Int -> TableType -> [ColWidth] -> [ColWidth]
212+normalizeColWidths ncols tblType = \case
213+ [] -> case tblType of
214+ SimpleTable -> replicate ncols ColWidthDefault
215+ NormalTable -> replicate ncols (ColWidth $ 1 / fromIntegral ncols)
216+ widths -> widths
217+
218+calculateAlignments :: Int -> [TableBody] -> [Alignment]
219+calculateAlignments cols tbodies =
220+ case cells of
221+ cs:_ -> take cols $ concatMap cellAligns cs ++ repeat AlignDefault
222+ _ -> replicate cols AlignDefault
223+ where
224+ cells :: [[Cell]]
225+ cells = concatMap bodyRowCells tbodies
226+ cellAligns :: Cell -> [Alignment]
227+ cellAligns (Cell _ align _ (ColSpan cs) _) = replicate cs align
228+
229+bodyRowCells :: TableBody -> [[Cell]]
230+bodyRowCells = map rowCells . bodyRows
231+
232+headRows :: TableHead -> [B.Row]
233+headRows (TableHead _ rows) = rows
234+
235+bodyRows :: TableBody -> [B.Row]
236+bodyRows (TableBody _ _ headerRows bodyRows') = headerRows <> bodyRows'
237+
238+footRows :: TableFoot -> [B.Row]
239+footRows (TableFoot _ rows) = rows
240+
241+rowCells :: B.Row -> [Cell]
242+rowCells (Row _ cells) = cells
test/html-reader.native+24−22
@@ -448,16 +448,15 @@ Pandoc (Meta {unMeta = fromList [("generator",MetaInlines [Str "pandoc"]),("titl
448448 ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
449449 [Plain [Str "2"]]
450450 ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
451− [Plain [Str "3"]]]
452− ,Row ("",[],[])
453− [Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
454− [Plain [Str "4"]]
455− ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
456− [Plain [Str "5"]]
457− ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
458− [Plain [Str "6"]]]])]
451+ [Plain [Str "3"]]]])]
459452 (TableFoot ("",[],[])
460− [])
453+ [Row ("",[],[])
454+ [Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
455+ [Plain [Str "4"]]
456+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
457+ [Plain [Str "5"]]
458+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
459+ [Plain [Str "6"]]]])
461460,HorizontalRule
462461,Table ("",[],[]) (Caption Nothing
463462 [])
@@ -576,8 +575,10 @@ Pandoc (Meta {unMeta = fromList [("generator",MetaInlines [Str "pandoc"]),("titl
576575 ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
577576 [Plain [Str "2"]]
578577 ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
579− [Plain [Str "3"]]]
580− ,Row ("",[],[])
578+ [Plain [Str "3"]]]])
579+ ,(TableBody ("",[],[]) (RowHeadColumns 0)
580+ []
581+ [Row ("",[],[])
581582 [Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
582583 [Plain [Str "4"]]
583584 ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
@@ -608,8 +609,10 @@ Pandoc (Meta {unMeta = fromList [("generator",MetaInlines [Str "pandoc"]),("titl
608609 ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
609610 [Para [Str "2"]]
610611 ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
611− [Plain [Str "3"]]]
612− ,Row ("",[],[])
612+ [Plain [Str "3"]]]])
613+ ,(TableBody ("",[],[]) (RowHeadColumns 0)
614+ []
615+ [Row ("",[],[])
613616 [Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
614617 [Plain [Str "4"]]
615618 ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
@@ -712,15 +715,14 @@ Pandoc (Meta {unMeta = fromList [("generator",MetaInlines [Str "pandoc"]),("titl
712715 ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
713716 [Plain [Str "2"]]
714717 ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
715− [Plain [Str "3"]]]
716− ,Row ("",[],[])
717− [Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
718− [Plain [Str "4"]]
719− ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
720− [Plain [Str "5"]]
721− ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
722− [Plain [Str "6"]]]])]
718+ [Plain [Str "3"]]]])]
723719 (TableFoot ("",[],[])
724− [])
720+ [Row ("",[],[])
721+ [Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
722+ [Plain [Str "4"]]
723+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
724+ [Plain [Str "5"]]
725+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
726+ [Plain [Str "6"]]]])
725727,Header 2 ("empty-tables",[],[]) [Str "Empty",Space,Str "Tables"]
726728,Para [Str "This",Space,Str "section",Space,Str "should",Space,Str "be",Space,Str "empty."]]