mirror of
https://github.com/jgm/pandoc.git
synced 2026-08-29 01:41:20 +08:00
ANSI writer: Apply row spans in tables (#11294)
The ANSI writer is now able to keep track of row spans and apply them in rows. [API change] T.P.Writers.Shared: Add functions `insertCurrentSpansAtColumn`, `takePreviousSpansAtColumn` and `decrementTrailingRowSpans` for applying and keeping track of RowSpans over multiple rows. Fixes: #10149
This commit is contained in:
committed by
GitHub
parent
6592dfb082
commit
58e741a0d6
@@ -15,6 +15,7 @@ module Text.Pandoc.Writers.ANSI ( writeANSI ) where
|
||||
import Control.Monad.State.Strict ( StateT, gets, modify, evalStateT )
|
||||
import Control.Monad (foldM)
|
||||
import Data.List (intersperse)
|
||||
import qualified Data.Map as M
|
||||
import Data.Maybe (fromMaybe)
|
||||
import Data.Text (Text)
|
||||
import Text.DocLayout ((<+>), ($$), ($+$))
|
||||
@@ -108,9 +109,7 @@ titleBlock :: Maybe Int -> Context Text -> D.Doc Text
|
||||
titleBlock width meta =
|
||||
if null most
|
||||
then D.empty
|
||||
else (case width of
|
||||
Just w -> D.cblock w
|
||||
Nothing -> id) $ most $+$ hr
|
||||
else (maybe id D.cblock width) $ most $+$ hr
|
||||
where
|
||||
title = D.bold (fromMaybe D.empty $ getField "title" meta)
|
||||
subtitle = fromMaybe D.empty $ getField "subtitle" meta
|
||||
@@ -193,7 +192,6 @@ blockToANSI opts (BlockQuote blocks) = do
|
||||
contents <- withFewerColumns 2 $ blockListToANSI opts blocks
|
||||
return ( D.prefixed "│ " contents $$ D.blankline)
|
||||
|
||||
-- TODO: Row spans don't work
|
||||
blockToANSI opts (Table _ (Caption _ caption) colSpecs (TableHead _ thead) tbody (TableFoot _ tfoot)) = do
|
||||
let captionInlines = blocksToInlines caption
|
||||
captionMarkup <-
|
||||
@@ -215,22 +213,40 @@ blockToANSI opts (Table _ (Caption _ caption) colSpecs (TableHead _ thead) tbody
|
||||
maxWidth k = claimWidth k
|
||||
let widths = map maxWidth inWidths
|
||||
let decor = [D.hsep $ map rule widths]
|
||||
head' <- mapM (goRow widths . unRow) thead
|
||||
body' <- mapM (goRow widths . unRow) (unBodies tbody)
|
||||
foot' <- mapM (goRow widths . unRow) tfoot
|
||||
head' <- (makeRows widths . map unRow) thead
|
||||
body' <- (makeRows widths . map unRow) (tableBodiesToRows tbody)
|
||||
foot' <- (makeRows widths . map unRow) tfoot
|
||||
modify $ \s -> s{stInTable = wasTable}
|
||||
return $ D.vcat (head' <> decor <> body' <> decor <> foot') $+$ captionMarkup
|
||||
where
|
||||
unRow (Row _ cs) = cs
|
||||
unBody (TableBody _ _ hd bd) = hd <> bd
|
||||
unBodies = concatMap unBody
|
||||
goRow ws cs = do
|
||||
(d, _) <- foldM goCell ([], ws) cs
|
||||
return $ D.hcat $ intersperse (D.vfill " ") $ reverse d
|
||||
goCell (r, ws) (Cell _ aln _ (ColSpan cspan) inner) = do
|
||||
makeRows ws rows = do
|
||||
(docs, _) <- foldM (goRow ws) ([], M.empty) rows
|
||||
return $ reverse docs
|
||||
goRow _ (r, spans) [] =
|
||||
-- Empty rows are not displayed but previous row spans still apply for them.
|
||||
let spans' = M.map decrementPreviousRowSpans spans
|
||||
in return (r, spans')
|
||||
goRow ws (r, spans) cs = do
|
||||
(d, (nextPos, spans'), _) <- foldM goCell ([], (0, spans), ws) cs
|
||||
let spans'' = decrementTrailingRowSpans nextPos spans' -- Handle previous row spans next to the end of the current row
|
||||
return (D.hcat (intersperse (D.vfill " ") $ reverse d):r, spans'')
|
||||
goCell (r, (colPos, spans), ws) cell@(Cell _ aln (RowSpan rspan) (ColSpan cspan) inner)
|
||||
| Just (ColSpan previousColSpan, spans') <- takePreviousSpansAtColumn colPos spans = do
|
||||
(r', nextPos, ws') <- makeCell r colPos ws AlignDefault previousColSpan []
|
||||
goCell (r', (nextPos, spans'), ws') cell
|
||||
| otherwise = do
|
||||
(r', nextPos, ws') <- makeCell r colPos ws aln cspan inner
|
||||
let spans' = insertCurrentSpansAtColumn colPos spans (RowSpan rspan) (ColSpan cspan)
|
||||
return (r', (nextPos, spans'), ws')
|
||||
decrementPreviousRowSpans spans@(RowSpan rspan, cspan) =
|
||||
if rspan >= 1
|
||||
then (RowSpan rspan - 1, cspan)
|
||||
else spans
|
||||
makeCell r colPos ws aln cspan inner = do
|
||||
let (ws', render) = next ws aln cspan
|
||||
innerDoc <- blockListToANSI opts inner
|
||||
return ((render innerDoc):r, ws')
|
||||
return ((render innerDoc):r, colPos + cspan, ws')
|
||||
tcell AlignLeft = D.lblock
|
||||
tcell AlignRight = D.rblock
|
||||
tcell AlignCenter = D.cblock
|
||||
|
||||
@@ -818,25 +818,19 @@ adjustFooters rows = adjustFooters' [] (0, length rows) M.empty rows
|
||||
|
||||
-- Apply row spans from a previous row that are next to the end of the
|
||||
-- current row's cells to keep track of the correct column position.
|
||||
previousRowSpans'' = M.mapWithKey (applyTrailingPreviousRowSpans nextColumnPosition) previousRowSpans'
|
||||
previousRowSpans'' = decrementTrailingRowSpans nextColumnPosition previousRowSpans'
|
||||
in (previousRowSpans'', Row attr cells'', catMaybes columnIndices)
|
||||
|
||||
applyTrailingPreviousRowSpans nextColumnPosition columnPosition previousRowSpan@(RowSpan rowSpan, ColSpan colSpan) =
|
||||
if columnPosition >= nextColumnPosition && rowSpan >= 1
|
||||
then (RowSpan rowSpan - 1, ColSpan colSpan)
|
||||
else previousRowSpan
|
||||
|
||||
-- | Adjust footer cell for the fact that AsciiDoc only supports a single footer row.
|
||||
--
|
||||
-- Collects cells whose RowSpan would reach to the last footer row and applies
|
||||
-- them as empty cells to that last footer row.
|
||||
adjustFooterCell :: (Int, Int) -> (Int, M.Map Int (RowSpan, ColSpan)) -> Cell -> ((Int, M.Map Int (RowSpan, ColSpan)), (Cell, Maybe (Int, Cell)))
|
||||
adjustFooterCell rowInfo@(rowIndex, footerLength) (columnPosition, previousSpans) cell@(Cell _ _ (RowSpan rowSpan) (ColSpan colSpan) _)
|
||||
| Just previous@(RowSpan previousRowSpan, ColSpan previousColSpan) <- M.lookup columnPosition previousSpans
|
||||
, previousRowSpan >= 1 =
|
||||
| Just (ColSpan previousColSpan, previousSpans') <- takePreviousSpansAtColumn columnPosition previousSpans =
|
||||
-- Apply row span from a previous row that occupies this column to keep
|
||||
-- track of the correct column position.
|
||||
adjustFooterCell rowInfo (columnPosition + previousColSpan, updatePreviousRowSpan previous) cell
|
||||
adjustFooterCell rowInfo (columnPosition + previousColSpan, previousSpans') cell
|
||||
| rowSpan > 1 && rowIndex + rowSpan >= footerLength =
|
||||
-- Adjust row span that would reach all the way to the last footer row and
|
||||
-- keep track of that to apply it to the last footer row.
|
||||
@@ -844,15 +838,7 @@ adjustFooterCell rowInfo@(rowIndex, footerLength) (columnPosition, previousSpans
|
||||
| otherwise = ((nextColumnPosition, previousRowSpans'), (cell, Nothing))
|
||||
where
|
||||
-- Keep track of this cell's RowSpan for the rows following it.
|
||||
previousRowSpans' = if rowSpan > 1
|
||||
then M.insert columnPosition (RowSpan rowSpan - 1, ColSpan colSpan) previousSpans -- Minus its own row.
|
||||
else previousSpans
|
||||
|
||||
updatePreviousRowSpan (RowSpan previousRowSpan, previousColSpan) =
|
||||
if previousRowSpan > 1
|
||||
then M.insert columnPosition (RowSpan previousRowSpan - 1, previousColSpan) previousSpans
|
||||
else M.delete columnPosition previousSpans
|
||||
|
||||
previousRowSpans' = insertCurrentSpansAtColumn columnPosition previousSpans (RowSpan rowSpan) (ColSpan colSpan)
|
||||
nextColumnPosition = columnPosition + colSpan
|
||||
emptyCellWithColSpan = Cell nullAttr AlignDefault (RowSpan 1) (ColSpan colSpan) []
|
||||
|
||||
|
||||
@@ -52,6 +52,9 @@ module Text.Pandoc.Writers.Shared (
|
||||
, delimited
|
||||
, allRowsEmpty
|
||||
, tableBodiesToRows
|
||||
, insertCurrentSpansAtColumn
|
||||
, takePreviousSpansAtColumn
|
||||
, decrementTrailingRowSpans
|
||||
)
|
||||
where
|
||||
import Safe (lastMay, maximumMay)
|
||||
@@ -873,3 +876,52 @@ tableBodiesToRows :: [TableBody] -> [Row]
|
||||
tableBodiesToRows = concatMap tableBodyToRows
|
||||
where
|
||||
tableBodyToRows (TableBody _ _ headerRows bodyRows) = headerRows ++ bodyRows
|
||||
|
||||
-- | Insert the current span information of a table cell to keep track of it in
|
||||
-- subsequent rows.
|
||||
--
|
||||
-- If 'RowSpan' @> 1@, the current span information will be inserted. Otherwise
|
||||
-- the previous span information will be left unchanged.
|
||||
--
|
||||
-- Use 'takePreviousSpansAtColumn' to take previous span information at
|
||||
-- subsequent rows. Use 'decrementTrailingRowSpans' to handle previous trailing
|
||||
-- spans at the end of a row.
|
||||
--
|
||||
-- For writers that need to manually apply the 'RowSpan' of cells over multiple
|
||||
-- rows or otherwise have to keep track of it.
|
||||
insertCurrentSpansAtColumn :: Int -> M.Map Int (RowSpan, ColSpan) -> RowSpan -> ColSpan -> M.Map Int (RowSpan, ColSpan)
|
||||
insertCurrentSpansAtColumn columnPosition previousSpans (RowSpan rowSpan) colSpan =
|
||||
if (rowSpan > 1)
|
||||
then M.insert columnPosition (RowSpan rowSpan - 1, colSpan) previousSpans -- Minus its own row.
|
||||
else previousSpans
|
||||
|
||||
-- | Take previous span information at a column position that was added with
|
||||
-- 'insertCurrentSpansAtColumn' if available.
|
||||
--
|
||||
-- If the previous 'RowSpan' @>= 1@, this will return 'Just' the previous
|
||||
-- 'ColSpan' and an adjusted span information where that 'RowSpan' is either
|
||||
-- decremented or deleted if it would fall to 0. Otherwise this will return
|
||||
-- 'Nothing'.
|
||||
takePreviousSpansAtColumn :: Int -> M.Map Int (RowSpan, ColSpan) -> Maybe (ColSpan, M.Map Int (RowSpan, ColSpan))
|
||||
takePreviousSpansAtColumn columnPosition previousSpans
|
||||
| Just previous@(RowSpan previousRowSpan, previousColSpan) <- M.lookup columnPosition previousSpans
|
||||
, previousRowSpan >= 1 = Just (previousColSpan, decrementPreviousRowSpans previous)
|
||||
| otherwise = Nothing
|
||||
where
|
||||
decrementPreviousRowSpans (RowSpan previousRowSpan, previousColSpan) =
|
||||
if previousRowSpan > 1
|
||||
then M.insert columnPosition (RowSpan previousRowSpan - 1, previousColSpan) previousSpans
|
||||
else M.delete columnPosition previousSpans
|
||||
|
||||
-- | Decrement all previously tracked trailing 'RowSpan' elements at or after a
|
||||
-- column position.
|
||||
--
|
||||
-- For handling previous row spans that are next to the end of a row's cells
|
||||
-- that were previously added with 'insertCurrentSpansAtColumn'.
|
||||
decrementTrailingRowSpans :: Int -> M.Map Int (RowSpan, ColSpan) -> M.Map Int (RowSpan, ColSpan)
|
||||
decrementTrailingRowSpans columnPosition = M.mapWithKey decrementTrailing
|
||||
where
|
||||
decrementTrailing previousColumnPosition previousSpan@(RowSpan rowSpan, colSpan) =
|
||||
if previousColumnPosition >= columnPosition && rowSpan >= 1
|
||||
then (RowSpan rowSpan - 1, colSpan)
|
||||
else previousSpan
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
Row spans
|
||||
```
|
||||
% pandoc -f html --columns 24 -t ansi
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td rowspan="3">1</td>
|
||||
<td>2</td>
|
||||
<td>3</td>
|
||||
<td>4</td>
|
||||
<td rowspan="4">5</td>
|
||||
<td>6</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td>
|
||||
<td colspan="2" rowspan="2">3</td>
|
||||
<td>6</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan="2">2</td>
|
||||
<td>6</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1</td>
|
||||
<td>3</td>
|
||||
<td>4</td>
|
||||
<td>6</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colspan="2">a</td>
|
||||
<td rowspan="2">c</td>
|
||||
<td>d</td>
|
||||
<td>e</td>
|
||||
<td>f</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>a</td>
|
||||
<td>b</td>
|
||||
<td colspan="3" rowspan="2">d</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>a</td>
|
||||
<td>b</td>
|
||||
<td>c</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="2" rowspan="4">A</td>
|
||||
<td>C</td>
|
||||
<td>D</td>
|
||||
<td colspan="2" rowspan="3">E</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>C</td>
|
||||
<td>D</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>C</td>
|
||||
<td>D</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>C</td>
|
||||
<td>D</td>
|
||||
<td>E</td>
|
||||
<td>F</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
^D
|
||||
1 2 3 4 5 6
|
||||
2 3 6
|
||||
2 6
|
||||
1 3 4 6
|
||||
─── ─── ─── ─── ─── ───
|
||||
a c d e f
|
||||
a b d
|
||||
a b c
|
||||
─── ─── ─── ─── ─── ───
|
||||
A C D E
|
||||
C D
|
||||
C D
|
||||
C D E F[0m]8;;\
|
||||
```
|
||||
|
||||
Empty rows
|
||||
```
|
||||
% pandoc -f native --columns 12 -t ansi
|
||||
[ Table
|
||||
( "" , [] , [] )
|
||||
(Caption Nothing [])
|
||||
[ ( AlignDefault , ColWidthDefault )
|
||||
, ( AlignDefault , ColWidthDefault )
|
||||
, ( AlignDefault , ColWidthDefault )
|
||||
]
|
||||
(TableHead
|
||||
( "" , [] , [] )
|
||||
[ Row
|
||||
( "" , [] , [] )
|
||||
[ Cell
|
||||
( "" , [] , [] )
|
||||
AlignDefault
|
||||
(RowSpan 3)
|
||||
(ColSpan 2)
|
||||
[ Plain [ Str "1" ] ]
|
||||
, Cell
|
||||
( "" , [] , [] )
|
||||
AlignDefault
|
||||
(RowSpan 1)
|
||||
(ColSpan 1)
|
||||
[ Plain [ Str "3" ] ]
|
||||
]
|
||||
, Row
|
||||
( "" , [] , [] )
|
||||
[ Cell
|
||||
( "" , [] , [] )
|
||||
AlignDefault
|
||||
(RowSpan 1)
|
||||
(ColSpan 1)
|
||||
[ Plain [ Str "3" ] ]
|
||||
]
|
||||
, Row ( "" , [] , [] ) []
|
||||
, Row
|
||||
( "" , [] , [] )
|
||||
[ Cell
|
||||
( "" , [] , [] )
|
||||
AlignDefault
|
||||
(RowSpan 1)
|
||||
(ColSpan 1)
|
||||
[ Plain [ Str "1" ] ]
|
||||
, Cell
|
||||
( "" , [] , [] )
|
||||
AlignDefault
|
||||
(RowSpan 1)
|
||||
(ColSpan 1)
|
||||
[ Plain [ Str "2" ] ]
|
||||
, Cell
|
||||
( "" , [] , [] )
|
||||
AlignDefault
|
||||
(RowSpan 1)
|
||||
(ColSpan 1)
|
||||
[ Plain [ Str "3" ] ]
|
||||
]
|
||||
])
|
||||
[ TableBody
|
||||
( "" , [] , [] )
|
||||
(RowHeadColumns 0)
|
||||
[]
|
||||
[ Row
|
||||
( "" , [] , [] )
|
||||
[ Cell
|
||||
( "" , [] , [] )
|
||||
AlignDefault
|
||||
(RowSpan 1)
|
||||
(ColSpan 1)
|
||||
[ Plain [ Str "1" ] ]
|
||||
, Cell
|
||||
( "" , [] , [] )
|
||||
AlignDefault
|
||||
(RowSpan 1)
|
||||
(ColSpan 1)
|
||||
[ Plain [ Str "2" ] ]
|
||||
, Cell
|
||||
( "" , [] , [] )
|
||||
AlignDefault
|
||||
(RowSpan 2)
|
||||
(ColSpan 1)
|
||||
[ Plain [ Str "3" ] ]
|
||||
]
|
||||
, Row
|
||||
( "" , [] , [] )
|
||||
[ Cell
|
||||
( "" , [] , [] )
|
||||
AlignDefault
|
||||
(RowSpan 1)
|
||||
(ColSpan 1)
|
||||
[ Plain [ Str "1" ] ]
|
||||
, Cell
|
||||
( "" , [] , [] )
|
||||
AlignDefault
|
||||
(RowSpan 4)
|
||||
(ColSpan 1)
|
||||
[ Plain [ Str "2" ] ]
|
||||
]
|
||||
, Row
|
||||
( "" , [] , [] )
|
||||
[ Cell
|
||||
( "" , [] , [] )
|
||||
AlignDefault
|
||||
(RowSpan 1)
|
||||
(ColSpan 1)
|
||||
[ Plain [ Str "1" ] ]
|
||||
, Cell
|
||||
( "" , [] , [] )
|
||||
AlignDefault
|
||||
(RowSpan 1)
|
||||
(ColSpan 1)
|
||||
[ Plain [ Str "3" ] ]
|
||||
]
|
||||
, Row ( "" , [] , [] ) []
|
||||
, Row
|
||||
( "" , [] , [] )
|
||||
[ Cell
|
||||
( "" , [] , [] )
|
||||
AlignDefault
|
||||
(RowSpan 1)
|
||||
(ColSpan 1)
|
||||
[ Plain [ Str "1" ] ]
|
||||
, Cell
|
||||
( "" , [] , [] )
|
||||
AlignDefault
|
||||
(RowSpan 1)
|
||||
(ColSpan 1)
|
||||
[ Plain [ Str "3" ] ]
|
||||
]
|
||||
]
|
||||
]
|
||||
(TableFoot
|
||||
( "" , [] , [] )
|
||||
[ Row
|
||||
( "" , [] , [] )
|
||||
[ Cell
|
||||
( "" , [] , [] )
|
||||
AlignDefault
|
||||
(RowSpan 1)
|
||||
(ColSpan 1)
|
||||
[ Plain [ Str "1" ] ]
|
||||
, Cell
|
||||
( "" , [] , [] )
|
||||
AlignDefault
|
||||
(RowSpan 1)
|
||||
(ColSpan 1)
|
||||
[ Plain [ Str "2" ] ]
|
||||
, Cell
|
||||
( "" , [] , [] )
|
||||
AlignDefault
|
||||
(RowSpan 2)
|
||||
(ColSpan 1)
|
||||
[ Plain [ Str "3" ] ]
|
||||
]
|
||||
, Row ( "" , [] , [] ) []
|
||||
, Row
|
||||
( "" , [] , [] )
|
||||
[ Cell
|
||||
( "" , [] , [] )
|
||||
AlignDefault
|
||||
(RowSpan 1)
|
||||
(ColSpan 1)
|
||||
[ Plain [ Str "1" ] ]
|
||||
, Cell
|
||||
( "" , [] , [] )
|
||||
AlignDefault
|
||||
(RowSpan 1)
|
||||
(ColSpan 1)
|
||||
[ Plain [ Str "2" ] ]
|
||||
, Cell
|
||||
( "" , [] , [] )
|
||||
AlignDefault
|
||||
(RowSpan 3)
|
||||
(ColSpan 1)
|
||||
[ Plain [ Str "3" ] ]
|
||||
]
|
||||
, Row
|
||||
( "" , [] , [] )
|
||||
[ Cell
|
||||
( "" , [] , [] )
|
||||
AlignDefault
|
||||
(RowSpan 1)
|
||||
(ColSpan 1)
|
||||
[ Plain [ Str "1" ] ]
|
||||
, Cell
|
||||
( "" , [] , [] )
|
||||
AlignDefault
|
||||
(RowSpan 1)
|
||||
(ColSpan 1)
|
||||
[ Plain [ Str "2" ] ]
|
||||
]
|
||||
, Row ( "" , [] , [] ) []
|
||||
])
|
||||
]
|
||||
^D
|
||||
1 3
|
||||
3
|
||||
1 2 3
|
||||
─── ─── ───
|
||||
1 2 3
|
||||
1 2
|
||||
1 3
|
||||
1 3
|
||||
─── ─── ───
|
||||
1 2 3
|
||||
1 2 3
|
||||
1 2[0m]8;;\
|
||||
```
|
||||
Reference in New Issue
Block a user