Markdown writer: move table-related code into submodule.

This commit is contained in:
Albert Krewinkel 2022-03-15 15:34:29 +01:00
parent d69807fb92
commit 43e549b2fb
No known key found for this signature in database
GPG key ID: 388DC0B21F631124
3 changed files with 137 additions and 97 deletions

View file

@ -713,6 +713,7 @@ library
Text.Pandoc.Writers.LaTeX.Types, Text.Pandoc.Writers.LaTeX.Types,
Text.Pandoc.Writers.LaTeX.Citation, Text.Pandoc.Writers.LaTeX.Citation,
Text.Pandoc.Writers.LaTeX.Util, Text.Pandoc.Writers.LaTeX.Util,
Text.Pandoc.Writers.Markdown.Table,
Text.Pandoc.Writers.Markdown.Types, Text.Pandoc.Writers.Markdown.Types,
Text.Pandoc.Writers.Markdown.Inline, Text.Pandoc.Writers.Markdown.Inline,
Text.Pandoc.Writers.Roff, Text.Pandoc.Writers.Roff,

View file

@ -23,7 +23,7 @@ module Text.Pandoc.Writers.Markdown (
import Control.Monad.Reader import Control.Monad.Reader
import Control.Monad.State.Strict import Control.Monad.State.Strict
import Data.Default import Data.Default
import Data.List (intersperse, sortOn, transpose) import Data.List (intersperse, sortOn)
import Data.List.NonEmpty (nonEmpty, NonEmpty(..)) import Data.List.NonEmpty (nonEmpty, NonEmpty(..))
import qualified Data.Map as M import qualified Data.Map as M
import Data.Maybe (fromMaybe, mapMaybe, isNothing) import Data.Maybe (fromMaybe, mapMaybe, isNothing)
@ -47,6 +47,7 @@ import Text.Pandoc.Writers.Markdown.Inline (inlineListToMarkdown,
linkAttributes, linkAttributes,
attrsToMarkdown, attrsToMarkdown,
attrsToMarkua) attrsToMarkua)
import Text.Pandoc.Writers.Markdown.Table (pipeTable, pandocTable)
import Text.Pandoc.Writers.Markdown.Types (MarkdownVariant(..), import Text.Pandoc.Writers.Markdown.Types (MarkdownVariant(..),
WriterState(..), WriterState(..),
WriterEnv(..), WriterEnv(..),
@ -670,102 +671,6 @@ addMarkdownAttribute s =
x /= "markdown"] x /= "markdown"]
_ -> s _ -> s
pipeTable :: PandocMonad m
=> WriterOptions
-> Bool -> [Alignment] -> [Double] -> [Doc Text] -> [[Doc Text]]
-> MD m (Doc Text)
pipeTable opts headless aligns widths rawHeaders rawRows = do
let sp = literal " "
let blockFor AlignLeft x y = lblock (x + 2) (sp <> y) <> lblock 0 empty
blockFor AlignCenter x y = cblock (x + 2) (sp <> y <> sp) <> lblock 0 empty
blockFor AlignRight x y = rblock (x + 2) (y <> sp) <> lblock 0 empty
blockFor _ x y = lblock (x + 2) (sp <> y) <> lblock 0 empty
let contentWidths = map (max 3 . maybe 3 maximum . nonEmpty . map offset) $
transpose (rawHeaders : rawRows)
let colwidth = writerColumns opts
let numcols = length contentWidths
let maxwidth = sum contentWidths
variant <- asks envVariant
let pipeWidths = if variant == Markdown &&
not (all (== 0) widths) &&
maxwidth + (numcols + 1) > colwidth
then map
(floor . (* fromIntegral (colwidth - (numcols +1))))
widths
else contentWidths
let torow cs = nowrap $ literal "|" <>
hcat (intersperse (literal "|") $
zipWith3 blockFor aligns contentWidths (map chomp cs))
<> literal "|"
let toborder a w = literal $ case a of
AlignLeft -> ":" <> T.replicate (w + 1) "-"
AlignCenter -> ":" <> T.replicate w "-" <> ":"
AlignRight -> T.replicate (w + 1) "-" <> ":"
AlignDefault -> T.replicate (w + 2) "-"
-- note: pipe tables can't completely lack a
-- header; for a headerless table, we need a header of empty cells.
-- see jgm/pandoc#1996.
let header = if headless
then torow (replicate (length aligns) empty)
else torow rawHeaders
let border = nowrap $ literal "|" <> hcat (intersperse (literal "|") $
zipWith toborder aligns pipeWidths) <> literal "|"
let body = vcat $ map torow rawRows
return $ header $$ border $$ body
pandocTable :: PandocMonad m
=> WriterOptions -> Bool -> Bool -> [Alignment] -> [Double]
-> [Doc Text] -> [[Doc Text]] -> MD m (Doc Text)
pandocTable opts multiline headless aligns widths rawHeaders rawRows = do
let isSimple = all (==0) widths
let alignHeader alignment = case alignment of
AlignLeft -> lblock
AlignCenter -> cblock
AlignRight -> rblock
AlignDefault -> lblock
-- Number of characters per column necessary to output every cell
-- without requiring a line break.
-- The @+2@ is needed for specifying the alignment.
let numChars = (+ 2) . maybe 0 maximum . nonEmpty . map offset
-- Number of characters per column necessary to output every cell
-- without requiring a line break *inside a word*.
-- The @+2@ is needed for specifying the alignment.
let minNumChars = (+ 2) . maybe 0 maximum . nonEmpty . map minOffset
let columns = transpose (rawHeaders : rawRows)
-- minimal column width without wrapping a single word
let relWidth w col =
max (floor $ fromIntegral (writerColumns opts - 1) * w)
(if writerWrapText opts == WrapAuto
then minNumChars col
else numChars col)
let widthsInChars
| isSimple = map numChars columns
| otherwise = zipWith relWidth widths columns
let makeRow = hcat . intersperse (lblock 1 (literal " ")) .
zipWith3 alignHeader aligns widthsInChars
let rows' = map makeRow rawRows
let head' = makeRow rawHeaders
let underline = mconcat $ intersperse (literal " ") $
map (\width -> literal (T.replicate width "-")) widthsInChars
let border
| multiline = literal (T.replicate (sum widthsInChars +
length widthsInChars - 1) "-")
| headless = underline
| otherwise = empty
let head'' = if headless
then empty
else border <> cr <> head'
let body = if multiline
then vsep rows' $$
if length rows' < 2
then blankline -- #4578
else empty
else vcat rows'
let bottom = if headless
then underline
else border
return $ head'' $$ underline $$ body $$ bottom
itemEndsWithTightList :: [Block] -> Bool itemEndsWithTightList :: [Block] -> Bool
itemEndsWithTightList bs = itemEndsWithTightList bs =
case bs of case bs of

View file

@ -0,0 +1,134 @@
{-# LANGUAGE OverloadedStrings #-}
{- |
Module : Text.Pandoc.Writers.Markdown
Copyright : © 2006-2022 John MacFarlane
License : GPL-2.0-or-later
Maintainer : John MacFarlane <jgm@berkeley.edu>
Create Markdown pipe-tables and pandoc-style tables.
-}
module Text.Pandoc.Writers.Markdown.Table
( pipeTable
, pandocTable
) where
import Control.Monad.Reader (asks)
import Data.List (intersperse, transpose)
import Data.List.NonEmpty (nonEmpty)
import Data.Text (Text)
import qualified Data.Text as T
import Text.DocLayout
import Text.Pandoc.Class.PandocMonad (PandocMonad)
import Text.Pandoc.Definition (Alignment (..))
import Text.Pandoc.Options (WriterOptions (writerColumns, writerWrapText),
WrapOption(WrapAuto))
import Text.Pandoc.Writers.Markdown.Types (MarkdownVariant(Markdown),
WriterEnv(..), MD)
-- | Creates a Markdown pipe table.
pipeTable :: PandocMonad m
=> WriterOptions
-> Bool -- ^ headless?
-> [Alignment] -- ^ column alignments
-> [Double] -- ^ column widhts
-> [Doc Text] -- ^ table header cells
-> [[Doc Text]] -- ^ table body rows
-> MD m (Doc Text)
pipeTable opts headless aligns widths rawHeaders rawRows = do
let sp = literal " "
let blockFor AlignLeft x y = lblock (x + 2) (sp <> y) <> lblock 0 empty
blockFor AlignCenter x y = cblock (x + 2) (sp <> y <> sp) <> lblock 0 empty
blockFor AlignRight x y = rblock (x + 2) (y <> sp) <> lblock 0 empty
blockFor _ x y = lblock (x + 2) (sp <> y) <> lblock 0 empty
let contentWidths = map (max 3 . maybe 3 maximum . nonEmpty . map offset) $
transpose (rawHeaders : rawRows)
let colwidth = writerColumns opts
let numcols = length contentWidths
let maxwidth = sum contentWidths
variant <- asks envVariant
let pipeWidths = if variant == Markdown &&
not (all (== 0) widths) &&
maxwidth + (numcols + 1) > colwidth
then map
(floor . (* fromIntegral (colwidth - (numcols +1))))
widths
else contentWidths
let torow cs = nowrap $ literal "|" <>
hcat (intersperse (literal "|") $
zipWith3 blockFor aligns contentWidths (map chomp cs))
<> literal "|"
let toborder a w = literal $ case a of
AlignLeft -> ":" <> T.replicate (w + 1) "-"
AlignCenter -> ":" <> T.replicate w "-" <> ":"
AlignRight -> T.replicate (w + 1) "-" <> ":"
AlignDefault -> T.replicate (w + 2) "-"
-- note: pipe tables can't completely lack a
-- header; for a headerless table, we need a header of empty cells.
-- see jgm/pandoc#1996.
let header = if headless
then torow (replicate (length aligns) empty)
else torow rawHeaders
let border = nowrap $ literal "|" <> hcat (intersperse (literal "|") $
zipWith toborder aligns pipeWidths) <> literal "|"
let body = vcat $ map torow rawRows
return $ header $$ border $$ body
-- | Write a pandoc-style Markdown table.
pandocTable :: PandocMonad m
=> WriterOptions
-> Bool -- ^ whether this is a multiline table
-> Bool -- ^ whether the table has a header
-> [Alignment] -- ^ column alignments
-> [Double] -- ^ column widths
-> [Doc Text] -- ^ table header cells
-> [[Doc Text]] -- ^ table body rows
-> MD m (Doc Text)
pandocTable opts multiline headless aligns widths rawHeaders rawRows = do
let isSimple = all (==0) widths
let alignHeader alignment = case alignment of
AlignLeft -> lblock
AlignCenter -> cblock
AlignRight -> rblock
AlignDefault -> lblock
-- Number of characters per column necessary to output every cell
-- without requiring a line break.
-- The @+2@ is needed for specifying the alignment.
let numChars = (+ 2) . maybe 0 maximum . nonEmpty . map offset
-- Number of characters per column necessary to output every cell
-- without requiring a line break *inside a word*.
-- The @+2@ is needed for specifying the alignment.
let minNumChars = (+ 2) . maybe 0 maximum . nonEmpty . map minOffset
let columns = transpose (rawHeaders : rawRows)
-- minimal column width without wrapping a single word
let relWidth w col =
max (floor $ fromIntegral (writerColumns opts - 1) * w)
(if writerWrapText opts == WrapAuto
then minNumChars col
else numChars col)
let widthsInChars
| isSimple = map numChars columns
| otherwise = zipWith relWidth widths columns
let makeRow = hcat . intersperse (lblock 1 (literal " ")) .
zipWith3 alignHeader aligns widthsInChars
let rows' = map makeRow rawRows
let head' = makeRow rawHeaders
let underline = mconcat $ intersperse (literal " ") $
map (\width -> literal (T.replicate width "-")) widthsInChars
let border
| multiline = literal (T.replicate (sum widthsInChars +
length widthsInChars - 1) "-")
| headless = underline
| otherwise = empty
let head'' = if headless
then empty
else border <> cr <> head'
let body = if multiline
then vsep rows' $$
if length rows' < 2
then blankline -- #4578
else empty
else vcat rows'
let bottom = if headless
then underline
else border
return $ head'' $$ underline $$ body $$ bottom