Markdown writer: move table-related code into submodule.
This commit is contained in:
parent
d69807fb92
commit
43e549b2fb
3 changed files with 137 additions and 97 deletions
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
134
src/Text/Pandoc/Writers/Markdown/Table.hs
Normal file
134
src/Text/Pandoc/Writers/Markdown/Table.hs
Normal 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
|
Loading…
Reference in a new issue