diff options
| author | danse <f.occhipinti@gmail.com> | 2022-09-24 20:11:04 +0200 |
|---|---|---|
| committer | John MacFarlane <jgm@berkeley.edu> | 2022-09-29 09:56:00 -0700 |
| commit | c974ed0caea0e1266ba7a606a7bf6e35b050df13 (patch) | |
| tree | 4e9cc9c1e83a8f3aca90052bd82ab8b16e5efc0e | |
| parent | 45820e79f496ba593b25d83963cdd94a9fb03cf7 (diff) | |
rST writer: list tables rendering, closes #4564
When a table is marked with a "list-table" attribute class, it will
now be rendered using the list table syntax documented here
http://docutils.sourceforge.net/docs/ref/rst/directives.html#list-table
| -rw-r--r-- | src/Text/Pandoc/Writers/RST.hs | 104 | ||||
| -rw-r--r-- | test/command/4564.md | 65 |
2 files changed, 156 insertions, 13 deletions
diff --git a/src/Text/Pandoc/Writers/RST.hs b/src/Text/Pandoc/Writers/RST.hs index 021674b34..08922bbd0 100644 --- a/src/Text/Pandoc/Writers/RST.hs +++ b/src/Text/Pandoc/Writers/RST.hs @@ -311,7 +311,7 @@ blockToRST (CodeBlock (_,classes,kvs) str) = do blockToRST (BlockQuote blocks) = do contents <- blockListToRST blocks return $ nest 3 contents <> blankline -blockToRST (Table _ blkCapt specs thead tbody tfoot) = do +blockToRST (Table attrs blkCapt specs thead tbody tfoot) = do let (caption, aligns, widths, headers, rows) = toLegacyTable blkCapt specs thead tbody tfoot caption' <- inlineListToRST caption let blocksToDoc opts bs = do @@ -321,18 +321,23 @@ blockToRST (Table _ blkCapt specs thead tbody tfoot) = do modify $ \st -> st{ stOptions = oldOpts } return result opts <- gets stOptions - let isSimple = all (== 0) widths && length widths > 1 - tbl <- if isSimple - then do - tbl' <- simpleTable opts blocksToDoc headers rows - if offset tbl' > writerColumns opts - then gridTable opts blocksToDoc (all null headers) - (map (const AlignDefault) aligns) widths - headers rows - else return tbl' - else gridTable opts blocksToDoc (all null headers) - (map (const AlignDefault) aligns) widths - headers rows + let renderGrid = gridTable opts blocksToDoc (all null headers) + (map (const AlignDefault) aligns) widths + headers rows + isSimple = all (== 0) widths && length widths > 1 + renderSimple = do + tbl' <- simpleTable opts blocksToDoc headers rows + if offset tbl' > writerColumns opts + then renderGrid + else return tbl' + isList = any ("list-table" ==) $ (\(_, classes, _) -> classes) attrs + renderList = tableToRSTList caption (map (const AlignDefault) aligns) + widths headers rows + rendered + | isList = renderList + | isSimple = renderSimple + | otherwise = renderGrid + tbl <- rendered return $ blankline $$ (if null caption then tbl @@ -438,6 +443,79 @@ blockListToRST :: PandocMonad m -> RST m (Doc Text) blockListToRST = blockListToRST' False +{- + +http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html#directives + +According to the terminology used in the spec, a marker includes a +final whitespace and a block includes the directive arguments. Here +the variable names have slightly different meanings because we don't +want to finish the line with a space if there are no arguments, it +would produce rST that differs from what users expect in a way that's +not easy to detect + +-} +toRSTDirective :: Doc Text -> Doc Text -> [(Doc Text, Doc Text)] -> Doc Text -> Doc Text +toRSTDirective typ args options content = marker <> spaceArgs <> cr <> block + where marker = ".. " <> typ <> "::" + block = nest 3 (fieldList $$ + blankline $$ + content $$ + blankline) + spaceArgs = if isEmpty args then "" else " " <> args + -- a field list could end up being an empty doc thus being + -- omitted by $$ + fieldList = foldl ($$) "" $ map joinField options + -- a field body can contain multiple lines + joinField (name, body) = ":" <> name <> ": " <> body + +tableToRSTList :: PandocMonad m + => [Inline] + -> [Alignment] + -> [Double] + -> [[Block]] + -> [[[Block]]] + -> RST m (Doc Text) +tableToRSTList caption _ propWidths headers rows = do + captionRST <- inlineListToRST caption + opts <- gets stOptions + content <- listTableContent toWrite + pure $ toRSTDirective "list-table" captionRST (directiveOptions opts) content + where directiveOptions opts = widths (writerColumns opts) propWidths <> + headerRows + toWrite = if noHeaders then rows else headers:rows + headerRows = [("header-rows", text $ show (1 :: Int)) | not noHeaders] + widths tot pro = [("widths", showWidths tot pro) | + not (null propWidths || all (==0.0) propWidths)] + noHeaders = all null headers + -- >>> showWidths 70 [0.5, 0.5] + -- "35 35" + showWidths :: Int -> [Double] -> Doc Text + showWidths tot = text . unwords . map (show . toColumns tot) + -- toColumns converts a width expressed as a proportion of the + -- total into a width expressed as a number of columns + toColumns :: Int -> Double -> Int + toColumns t p = round (p * fromIntegral t) + listTableContent :: PandocMonad m => [[[Block]]] -> RST m (Doc Text) + listTableContent = joinTable joinDocsM joinDocsM . + mapTable blockListToRST + -- joinDocsM adapts joinDocs in order to work in the `RST m` monad + joinDocsM :: PandocMonad m => [RST m (Doc Text)] -> RST m (Doc Text) + joinDocsM = fmap joinDocs . sequence + -- joinDocs will be used to join cells and to join rows + joinDocs :: [Doc Text] -> Doc Text + joinDocs items = blankline $$ + (chomp . vcat . map formatItem) items $$ + blankline + formatItem :: Doc Text -> Doc Text + formatItem i = hang 3 "- " (i <> cr) + -- apply a function to all table cells changing their type + mapTable :: (a -> b) -> [[a]] -> [[b]] + mapTable = map . map + -- function hor to join cells and function ver to join rows + joinTable :: ([a] -> a) -> ([a] -> a) -> [[a]] -> a + joinTable hor ver = ver . map hor + transformInlines :: [Inline] -> [Inline] transformInlines = insertBS . filter hasContents . diff --git a/test/command/4564.md b/test/command/4564.md new file mode 100644 index 000000000..475413956 --- /dev/null +++ b/test/command/4564.md @@ -0,0 +1,65 @@ +``` +% pandoc -f native -t rst +[BlockQuote + [Table ("",["list-table"],[]) (Caption Nothing + []) + [(AlignDefault,ColWidth 0.1527777777777778) + ,(AlignDefault,ColWidth 0.1388888888888889) + ,(AlignDefault,ColWidth 0.16666666666666666) + ,(AlignDefault,ColWidth 0.375)] + (TableHead ("",[],[]) + [Row ("",[],[]) + [Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1) + [Plain [Str "Centered",SoftBreak,Str "Header"]] + ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1) + [Plain [Str "Left",SoftBreak,Str "Aligned"]] + ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1) + [Plain [Str "Right",SoftBreak,Str "Aligned"]] + ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1) + [Plain [Str "Default",Space,Str "aligned"]]]]) + [(TableBody ("",[],[]) (RowHeadColumns 0) + [] + [Row ("",[],[]) + [Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1) + [Plain [Str "First"]] + ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1) + [Plain [Str "row"]] + ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1) + [Plain [Str "12.0"]] + ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1) + [Plain [Str "Example",Space,Str "of",Space,Str "a",Space,Str "row",Space,Str "that",SoftBreak,Str "spans",Space,Str "multiple",Space,Str "lines."]]] + ,Row ("",[],[]) + [Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1) + [Plain [Str "Second"]] + ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1) + [Plain [Str "row"]] + ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1) + [Plain [Str "5.0"]] + ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1) + [Plain [Str "Here\8217s",Space,Str "another",Space,Str "one.",Space,Str "Note",SoftBreak,Str "the",Space,Str "blank",Space,Str "line",Space,Str "between",SoftBreak,Str "rows."]]]])] + (TableFoot ("",[],[]) + [])]] +^D + .. list-table:: + :widths: 11 10 12 27 + :header-rows: 1 + + - + + - Centered Header + - Left Aligned + - Right Aligned + - Default aligned + - + + - First + - row + - 12.0 + - Example of a row that spans multiple lines. + - + + - Second + - row + - 5.0 + - Here’s another one. Note the blank line between rows. +``` |
