pandoc-3.1.13: src/Text/Pandoc/Readers/Typst.hs
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE ScopedTypeVariables #-}
{- |
Module : Text.Pandoc.Readers.Typst
Copyright : Copyright (C) 2023 John MacFarlane
License : GNU GPL, version 2 or above
Maintainer : John MacFarlane <jgm@berkeley.edu>
Stability : alpha
Portability : portable
Reads and evaluates a Typst document as a Pandoc AST.
-}
module Text.Pandoc.Readers.Typst
( readTypst
)
where
import Text.Pandoc.Class
import Text.Pandoc.Sources
import Text.Pandoc.Options
import Text.Pandoc.Definition
import Typst ( parseTypst, evaluateTypst )
import Text.Pandoc.Error (PandocError(..))
import Text.Pandoc.Shared (tshow, blocksToInlines)
import Control.Monad.Except (throwError)
import Control.Monad (MonadPlus (mplus), void, guard, foldM)
import qualified Data.Foldable as F
import qualified Data.Map as M
import Data.Maybe (catMaybes, fromMaybe)
import Data.Sequence (Seq)
import qualified Data.Sequence as Seq
import qualified Data.Set as Set
import Data.Text (Text)
import qualified Data.Text as T
import qualified Text.Pandoc.Builder as B
import Text.Pandoc.Walk
import Text.Parsec
import Text.TeXMath (writeTeX)
import Text.TeXMath.Shared (getSpaceChars)
import Text.Pandoc.Readers.Typst.Math (pMathMany)
import Text.Pandoc.Readers.Typst.Parsing (pTok, ignored, getField, P,
PState(..), defaultPState)
import Typst.Methods (formatNumber, applyPureFunction)
import Typst.Types
import qualified Data.Vector as V
-- import Debug.Trace
-- | Read Typst from an input string and return a Pandoc document.
readTypst :: (PandocMonad m, ToSources a)
=> ReaderOptions -> a -> m Pandoc
readTypst _opts inp = do
let sources = toSources inp
let inputName = case sources of
Sources ((pos, _):_) -> sourceName pos
_ -> ""
case parseTypst inputName (sourcesToText sources) of
Left e -> throwError $ PandocParseError $ T.pack $ show e
Right parsed -> do
let ops = Operations {
loadBytes = readFileStrict,
currentUTCTime = getCurrentTime,
lookupEnvVar = fmap (fmap T.unpack) . lookupEnv . T.pack,
checkExistence = fileExists }
res <- evaluateTypst ops inputName parsed
case res of
Left e -> throwError $ PandocParseError $ tshow e
Right content -> do
let labs = findLabels [content]
runParserT pPandoc defaultPState{ sLabels = labs }
inputName [content] >>=
either (throwError . PandocParseError . T.pack . show) pure
pBlockElt :: PandocMonad m => P m B.Blocks
pBlockElt = try $ do
res <- pTok (\t -> isBlock t || not (isInline t))
-- check for following label
mbident <- option Nothing $ Just <$> pLab
case res of
Elt name@(Identifier tname) pos fields -> do
case M.lookup name blockHandlers of
Nothing -> do
ignored ("unknown block element " <> tname <>
" at " <> tshow pos)
pure mempty
Just handler -> handler mbident fields
_ -> pure mempty
pInline :: PandocMonad m => P m B.Inlines
pInline = try $ do
res <- pTok (\t -> isInline t || not (isBlock t))
case res of
Txt t -> pure $ B.text t
Lab name -> pure $ B.spanWith (name, [], []) mempty
Elt (Identifier tname) _ _
| "math." `T.isPrefixOf` tname
, tname /= "math.equation" ->
B.math . writeTeX <$> pMathMany (Seq.singleton res)
Elt name@(Identifier tname) pos fields -> do
labs <- sLabels <$> getState
labelTarget <- (do VLabel t <- getField "target" fields
True <$ guard (t `elem` labs))
<|> pure False
if tname == "ref" && not labelTarget
then do
-- @foo is a citation unless it links to a lab in the doc:
let targetToKey (Identifier "target") = Identifier "key"
targetToKey k = k
case M.lookup "cite" inlineHandlers of
Nothing -> do
ignored ("unknown inline element " <> tname <>
" at " <> tshow pos)
pure mempty
Just handler -> handler Nothing (M.mapKeys targetToKey fields)
else do
case M.lookup name inlineHandlers of
Nothing -> do
ignored ("unknown inline element " <> tname <>
" at " <> tshow pos)
pure mempty
Just handler -> handler Nothing fields
pPandoc :: PandocMonad m => P m B.Pandoc
pPandoc = do
Elt "document" _ fields <- pTok isDocument
bs <- getField "body" fields >>= pWithContents pBlocks
pure $ B.doc bs
-- The following alternative code would add metadata from the
-- fields on the document element. It is commented out because
-- the typst metadata doesn't print anything by default, in contrast
-- to pandoc with its usual templates. Hence, with this code,
-- converting a typst document might yield a double title, author, etc.
--
-- title <- (getField "title" fields >>= pWithContents pInlines) <|>
-- pure mempty
-- authors <- (getField "author" fields >>=
-- mapM (pWithContents pInlines) . V.toList) <|>
-- ((:[]) <$> (getField "author" fields >>=
-- (\x -> guard (not (null x)) *>
-- pWithContents pInlines x))) <|>
-- pure []
-- date <- (getField "date" fields >>= pWithContents pInlines) <|>
-- pure mempty
-- keywords <- (getField "keywords" fields >>=
-- mapM (pWithContents pInlines) . V.toList)
-- <|> pure []
-- pure $
-- (if title == mempty
-- then id
-- else B.setMeta "title" title) .
-- (if null authors
-- then id
-- else B.setMeta "author" authors) .
-- (if null date
-- then id
-- else B.setMeta "date" date) .
-- (if null keywords
-- then id
-- else B.setMeta "keywords" keywords) $ B.doc bs
pBlocks :: PandocMonad m => P m B.Blocks
pBlocks = mconcat <$> many pBlock
pBlock :: PandocMonad m => P m B.Blocks
pBlock = pPara <|> pBlockElt
pSpace :: PandocMonad m => P m Content
pSpace = pTok
( \case
Txt t | T.all (== ' ') t -> True
_ -> False )
pLab :: PandocMonad m => P m Text
pLab = try $ do
optional pSpace
Lab t <- pTok
( \case
Lab _ -> True
_ -> False
)
pure t
isDocument :: Content -> Bool
isDocument (Elt "document" _ _) = True
isDocument _ = False
isBlock :: Content -> Bool
isBlock (Elt "raw" _ fields) = M.lookup "block" fields == Just (VBoolean True)
isBlock (Elt name _ _) = name `Set.member` blockKeys
isBlock Lab{} = True
isBlock _ = False
isInline :: Content -> Bool
isInline (Elt "raw" _ fields) = M.lookup "block" fields /= Just (VBoolean True)
isInline (Elt name _ _) = name `Set.member` inlineKeys
isInline Lab{} = True
isInline Txt{} = True
blockKeys :: Set.Set Identifier
blockKeys = Set.fromList $ M.keys
(blockHandlers :: M.Map Identifier
(Maybe Text -> M.Map Identifier Val -> P PandocPure B.Blocks))
inlineKeys :: Set.Set Identifier
inlineKeys = Set.fromList $ M.keys
(inlineHandlers :: M.Map Identifier
(Maybe Text -> M.Map Identifier Val -> P PandocPure B.Inlines))
blockHandlers :: PandocMonad m =>
M.Map Identifier
(Maybe Text -> M.Map Identifier Val -> P m B.Blocks)
blockHandlers = M.fromList
[("text", \_ fields -> do
body <- getField "body" fields
-- sometimes text elements include para breaks
notFollowedBy $ void $ pWithContents pInlines body
pWithContents pBlocks body)
,("box", \_ fields -> do
body <- getField "body" fields
B.divWith ("", ["box"], []) <$> pWithContents pBlocks body)
,("heading", \mbident fields -> do
body <- getField "body" fields
lev <- getField "level" fields <|> pure 1
B.headerWith (fromMaybe "" mbident,[],[]) lev
<$> pWithContents pInlines body)
,("quote", \_ fields -> do
getField "block" fields >>= guard
body <- getField "body" fields >>= pWithContents pBlocks
attribution <-
((\x -> B.para ("\x2104\xa0" <> x)) <$>
(getField "attribution" fields >>= pWithContents pInlines))
<|> pure mempty
pure $ B.blockQuote $ body <> attribution)
,("list", \_ fields -> do
children <- V.toList <$> getField "children" fields
B.bulletList <$> mapM (pWithContents pBlocks) children)
,("list.item", \_ fields -> getField "body" fields >>= pWithContents pBlocks)
,("enum", \_ fields -> do
children <- V.toList <$> getField "children" fields
mbstart <- getField "start" fields
start <- case mbstart of
Nothing -> pure 1
Just x
| x >= 0 -> pure x
| otherwise -> fail "number must be positive"
(numbering :: Text) <- getField "numbering" fields `mplus` pure ""
let (sty, delim) =
case numbering of
"1." -> (B.Decimal, B.Period)
"1)" -> (B.Decimal, B.OneParen)
"(1)" -> (B.Decimal, B.TwoParens)
"a." -> (B.LowerAlpha, B.Period)
"a)" -> (B.LowerAlpha, B.OneParen)
"(a)" -> (B.LowerAlpha, B.TwoParens)
"A." -> (B.UpperAlpha, B.Period)
"A)" -> (B.UpperAlpha, B.OneParen)
"(A)" -> (B.UpperAlpha, B.TwoParens)
"i." -> (B.LowerRoman, B.Period)
"i)" -> (B.LowerRoman, B.OneParen)
"(i)" -> (B.LowerRoman, B.TwoParens)
"I." -> (B.UpperRoman, B.Period)
"I)" -> (B.UpperRoman, B.OneParen)
"(I)" -> (B.UpperRoman, B.TwoParens)
_ -> (B.DefaultStyle, B.DefaultDelim)
let listAttr = (start, sty, delim)
B.orderedListWith listAttr <$> mapM (pWithContents pBlocks) children)
,("enum.item", \_ fields -> getField "body" fields >>= pWithContents pBlocks)
,("terms", \_ fields -> do
children <- V.toList <$> getField "children" fields
B.definitionList
<$> mapM
( \case
VTermItem t d -> do
t' <- pWithContents pInlines t
d' <- pWithContents pBlocks d
pure (t', [d'])
_ -> pure (mempty, [])
)
children)
,("terms.item", \_ fields -> getField "body" fields >>= pWithContents pBlocks)
,("raw", \mbident fields -> do
txt <- T.filter (/= '\r') <$> getField "text" fields
mblang <- getField "lang" fields
let attr = (fromMaybe "" mbident, maybe [] (\l -> [l]) mblang, [])
pure $ B.codeBlockWith attr txt)
,("parbreak", \_ _ -> pure mempty)
,("block", \mbident fields ->
B.divWith (fromMaybe "" mbident, [], [])
<$> (getField "body" fields >>= pWithContents pBlocks))
,("place", \_ fields -> do
ignored "parameters of place"
getField "body" fields >>= pWithContents pBlocks)
,("columns", \_ fields -> do
(cnt :: Integer) <- getField "count" fields
B.divWith ("", ["columns-flow"], [("count", T.pack (show cnt))])
<$> (getField "body" fields >>= pWithContents pBlocks))
,("rect", \_ fields ->
B.divWith ("", ["rect"], []) <$> (getField "body" fields >>= pWithContents pBlocks))
,("circle", \_ fields ->
B.divWith ("", ["circle"], []) <$> (getField "body" fields >>= pWithContents pBlocks))
,("ellipse", \_ fields ->
B.divWith ("", ["ellipse"], []) <$> (getField "body" fields >>= pWithContents pBlocks))
,("polygon", \_ fields ->
B.divWith ("", ["polygon"], []) <$> (getField "body" fields >>= pWithContents pBlocks))
,("square", \_ fields ->
B.divWith ("", ["square"], []) <$> (getField "body" fields >>= pWithContents pBlocks))
,("align", \_ fields -> do
alignment <- getField "alignment" fields
B.divWith ("", [], [("align", repr alignment)])
<$> (getField "body" fields >>= pWithContents pBlocks))
,("stack", \_ fields -> do
(dir :: Direction) <- getField "dir" fields `mplus` pure Ltr
rawchildren <- getField "children" fields
children <-
mapM
( \case
val@(VFraction {}) ->
pure $ B.divWith ("", [], [("space", repr val)]) mempty
val -> fromVal val >>= pWithContents pBlocks
)
(V.toList rawchildren)
pure $
B.divWith ("", [], [("stack", repr (VDirection dir))]) $
mconcat $
map (B.divWith ("", [], [])) children)
,("grid", \mbident fields -> parseTable mbident fields)
,("table", \mbident fields -> parseTable mbident fields)
,("figure", \mbident fields -> do
body <- getField "body" fields >>= pWithContents pBlocks
(mbCaption :: Maybe (Seq Content)) <- getField "caption" fields
(caption :: B.Blocks) <- maybe mempty (pWithContents pBlocks) mbCaption
pure $ case B.toList body of
[B.Table attr _ colspecs thead tbodies tfoot] ->
B.singleton
(B.Table attr (B.Caption Nothing (B.toList caption)) colspecs thead tbodies tfoot)
_ -> B.figureWith (fromMaybe "" mbident, [], [])
(B.Caption Nothing (B.toList caption)) body)
,("line", \_ fields ->
case ( M.lookup "start" fields
>> M.lookup "end" fields
>> M.lookup "angle" fields ) of
Nothing -> pure B.horizontalRule
_ -> pure mempty)
,("numbering", \_ fields -> do
numStyle <- getField "numbering" fields
(nums :: V.Vector Integer) <- getField "numbers" fields
let toText v = fromMaybe "" $ fromVal v
let toNum n =
case numStyle of
VString t -> formatNumber t (fromIntegral n)
VFunction _ _ f ->
case applyPureFunction f [VInteger n] of
Success x -> toText x
Failure _ -> "?"
_ -> "?"
pure $ B.plain . B.text . mconcat . map toNum $ V.toList nums)
,("footnote.entry", \_ fields ->
getField "body" fields >>= pWithContents pBlocks)
]
inlineHandlers :: PandocMonad m =>
M.Map Identifier (Maybe Text -> M.Map Identifier Val -> P m B.Inlines)
inlineHandlers = M.fromList
[("ref", \_ fields -> do
VLabel target <- getField "target" fields
supplement' <- getField "supplement" fields
supplement <- case supplement' of
VAuto -> -- TODO for now, until we can locate the element
pure $ B.text ("[" <> target <> "]")
VContent cs -> pWithContents pInlines cs
VFunction _ _ _f -> -- TODO for now, until we can locate the element
pure $ B.text ("[" <> target <> "]")
_ -> pure mempty
pure $ B.linkWith ("", ["ref"], []) ("#" <> target) "" supplement)
,("linebreak", \_ _ -> pure B.linebreak)
,("text", \_ fields -> do
body <- getField "body" fields
(mbweight :: Maybe Text) <- getField "weight" fields
case mbweight of
Just "bold" -> B.strong <$> pWithContents pInlines body
_ -> pWithContents pInlines body)
,("raw", \_ fields -> B.code . T.filter (/= '\r') <$> getField "text" fields)
,("footnote", \_ fields ->
B.note <$> (getField "body" fields >>= pWithContents pBlocks))
,("cite", \_ fields -> do
VLabel key <- getField "key" fields
(form :: Text) <- getField "form" fields <|> pure "normal"
let citation =
B.Citation
{ B.citationId = key,
B.citationPrefix = mempty,
B.citationSuffix = mempty,
B.citationMode = case form of
"year" -> B.SuppressAuthor
_ -> B.NormalCitation,
B.citationNoteNum = 0,
B.citationHash = 0
}
pure $ B.cite [citation] (B.text $ "[" <> key <> "]"))
,("lower", \_ fields -> do
body <- getField "text" fields
walk (modString T.toLower) <$> pWithContents pInlines body)
,("upper", \_ fields -> do
body <- getField "text" fields
walk (modString T.toUpper) <$> pWithContents pInlines body)
,("emph", \_ fields -> do
body <- getField "body" fields
B.emph <$> pWithContents pInlines body)
,("strong", \_ fields -> do
body <- getField "body" fields
B.strong <$> pWithContents pInlines body)
,("sub", \_ fields -> do
body <- getField "body" fields
B.subscript <$> pWithContents pInlines body)
,("super", \_ fields -> do
body <- getField "body" fields
B.superscript <$> pWithContents pInlines body)
,("strike", \_ fields -> do
body <- getField "body" fields
B.strikeout <$> pWithContents pInlines body)
,("smallcaps", \_ fields -> do
body <- getField "body" fields
B.smallcaps <$> pWithContents pInlines body)
,("underline", \_ fields -> do
body <- getField "body" fields
B.underline <$> pWithContents pInlines body)
,("quote", \_ fields -> do
(getField "block" fields <|> pure False) >>= guard . not
body <- getInlineBody fields >>= pWithContents pInlines
pure $ B.doubleQuoted $ B.trimInlines body)
,("link", \_ fields -> do
dest <- getField "dest" fields
src <- case dest of
VString t -> pure t
VLabel t -> pure $ "#" <> t
VDict _ -> do
ignored "link to location, linking to #"
pure "#"
_ -> fail $ "Expected string or label for dest"
body <- getField "body" fields
description <-
if null body
then
pure $
B.text $
if "mailto:" `T.isPrefixOf` src
then T.drop 7 src
else
if "tel:" `T.isPrefixOf` src
then T.drop 4 src
else src
else pWithContents pInlines body <|>
pWithContents
(B.fromList . blocksToInlines . B.toList <$> pBlocks) body
pure $ B.link src "" description)
,("image", \_ fields -> do
path <- getField "path" fields
alt <- (B.text <$> getField "alt" fields) `mplus` pure mempty
(mbwidth :: Maybe Text) <-
fmap (renderLength False) <$> getField "width" fields
(mbheight :: Maybe Text) <-
fmap (renderLength False) <$> getField "height" fields
let attr =
( "",
[],
maybe [] (\x -> [("width", x)]) mbwidth
++ maybe [] (\x -> [("height", x)]) mbheight
)
pure $ B.imageWith attr path "" alt)
,("box", \_ fields -> do
body <- getField "body" fields
B.spanWith ("", ["box"], []) <$> pWithContents pInlines body)
,("h", \_ fields -> do
amount <- getField "amount" fields `mplus` pure (LExact 1 LEm)
let em = case amount of
LExact x LEm -> toRational x
_ -> case amount <> LExact 0 LPt of -- force to Pt
LExact x LPt -> toRational x / 12
_ -> 1 / 3 -- guess!
pure $ B.text $ getSpaceChars em)
,("place", \_ fields -> do
ignored "parameters of place"
getField "body" fields >>= pWithContents pInlines)
,("align", \_ fields -> do
alignment <- getField "alignment" fields
B.spanWith ("", [], [("align", repr alignment)])
<$> (getField "body" fields >>= pWithContents pInlines))
,("sys.version", \_ _ -> pure $ B.text "typst-hs")
,("math.equation", \_ fields -> do
body <- getField "body" fields
display <- getField "block" fields
(if display then B.displayMath else B.math) . writeTeX <$> pMathMany body)
]
getInlineBody :: PandocMonad m => M.Map Identifier Val -> P m (Seq Content)
getInlineBody fields =
parbreaksToLinebreaks <$> getField "body" fields
parbreaksToLinebreaks :: Seq Content -> Seq Content
parbreaksToLinebreaks =
fmap go . Seq.dropWhileL isParbreak . Seq.dropWhileR isParbreak
where
go (Elt "parbreak" pos _) = Elt "linebreak" pos mempty
go x = x
isParbreak (Elt "parbreak" _ _) = True
isParbreak _ = False
pPara :: PandocMonad m => P m B.Blocks
pPara =
B.para . B.trimInlines . collapseAdjacentCites . mconcat
<$> (many1 pInline <* optional pParBreak)
pParBreak :: PandocMonad m => P m ()
pParBreak =
void $
pTok
( \case
Elt "parbreak" _ _ -> True
_ -> False
)
pWithContents :: PandocMonad m => P m a -> Seq Content -> P m a
pWithContents pa cs = try $ do
inp <- getInput
setInput $ F.toList cs
res <- pa
eof
setInput inp
pure res
pInlines :: PandocMonad m => P m B.Inlines
pInlines =
collapseAdjacentCites . mconcat <$> many pInline
collapseAdjacentCites :: B.Inlines -> B.Inlines
collapseAdjacentCites = B.fromList . foldr go [] . B.toList
where
go (Cite cs1 ils1) (Cite cs2 ils2 : xs) =
Cite (cs1 ++ cs2) (ils1 <> ils2) : xs
go (Cite cs1 ils1) (Space : Cite cs2 ils2 : xs) =
Cite (cs1 ++ cs2) (ils1 <> ils2) : xs
go x xs = x:xs
modString :: (Text -> Text) -> B.Inline -> B.Inline
modString f (B.Str t) = B.Str (f t)
modString _ x = x
findLabels :: Seq.Seq Content -> [Text]
findLabels = foldr go []
where
go (Txt{}) = id
go (Lab t) = (t :)
go (Elt{ eltFields = fs }) = \ts -> foldr go' ts fs
go' (VContent cs) = (findLabels cs ++)
go' _ = id
parseTable :: PandocMonad m
=> Maybe Text -> M.Map Identifier Val -> P m B.Blocks
parseTable mbident fields = do
children <- V.toList <$> getField "children" fields
(columns :: Val) <- getField "columns" fields
let toWidth (VFraction f) = Just (floor $ 1000 * f)
toWidth _ = Nothing
let normalizeWidths xs =
let givenwidths = catMaybes xs
(totgivenwidth :: Int) = sum givenwidths
avgwidth = totgivenwidth `div` length givenwidths
totwidth = avgwidth * length xs
in if null givenwidths
then replicate (length xs) B.ColWidthDefault
else
map
( \case
Just x ->
B.ColWidth (fromIntegral x / fromIntegral totwidth)
Nothing ->
B.ColWidth
(fromIntegral avgwidth / fromIntegral totwidth)
)
xs
widths <- case columns of
VInteger x -> pure $ replicate (fromIntegral x) B.ColWidthDefault
VArray x -> pure $ normalizeWidths $ map toWidth (V.toList x)
VNone -> pure [B.ColWidthDefault]
_ -> fail $ "Could not determine number of columns: " <> show columns
let numcols = length widths
align <- getField "align" fields
let toAlign (VAlignment (Just horiz) _) =
case horiz of
HorizStart -> B.AlignLeft
HorizLeft -> B.AlignLeft
HorizEnd -> B.AlignRight
HorizRight -> B.AlignRight
HorizCenter -> B.AlignCenter
toAlign _ = B.AlignDefault
aligns <-
case align of
VAlignment {} -> pure $ replicate numcols (toAlign align)
VArray v -> pure $ map toAlign (V.toList v)
VFunction _ _ f -> do
mapM
( \colnum -> case applyPureFunction
f
[VInteger colnum, VInteger 0] of
Success x -> pure $ toAlign x
Failure e -> fail e
)
[0 .. (fromIntegral numcols - 1)]
_ -> pure $ replicate numcols B.AlignDefault
let colspecs = zip (aligns ++ repeat B.AlignDefault) widths
let addCell' cell Nothing = addCell' cell (Just ([], []))
addCell' cell@(B.Cell _ _ (B.RowSpan rowspan) (B.ColSpan colspan) _)
(Just (freecols, revrows)) =
let freecols' =
case (rowspan + 1) - length freecols of
n | n < 0 -> freecols
| otherwise -> freecols ++ replicate n numcols
in case freecols' of
[] -> -- should not happen
error "empty freecols'"
x:xs
| colspan <= x -- there is room on current row
-> let (as, bs) = splitAt rowspan (x:xs)
in Just
(map (\z -> z - colspan) as ++ bs,
case revrows of
[] -> [[cell]]
r:rs -> (cell:r):rs)
| otherwise ->
let (as, bs) = splitAt rowspan xs
in Just (map (\z -> z - colspan) as ++ bs, [cell]:revrows)
let addCell tableSection cell (TableData tdata) =
TableData (M.alter (addCell' cell) tableSection tdata)
let toCell tableSection tableData contents = do
case contents of
[Elt (Identifier "grid.cell") _pos fs] -> do
bs <- B.toList <$> (getField "body" fs >>= pWithContents pBlocks)
rowspan <- getField "rowspan" fs <|> pure 1
colspan <- getField "colspan" fs <|> pure 1
align' <- (toAlign <$> getField "align" fs) <|> pure B.AlignDefault
pure $ addCell tableSection
(B.Cell B.nullAttr align' (B.RowSpan rowspan)
(B.ColSpan colspan) bs) tableData
[Elt (Identifier "table.cell") pos fs] ->
toCell tableSection tableData [Elt (Identifier "grid.cell") pos fs]
[Elt (Identifier "table.vline") _pos _fs] -> pure tableData
[Elt (Identifier "table.hline") _pos _fs] -> pure tableData
[Elt (Identifier "grid.vline") _pos _fs] -> pure tableData
[Elt (Identifier "grid.hline") _pos _fs] -> pure tableData
[Elt (Identifier "table.header") _pos fs] ->
getField "children" fs >>=
foldM (toCell THeader) tableData . V.toList
[Elt (Identifier "table.footer") _pos fs] ->
getField "children" fs >>=
foldM (toCell TFooter) tableData . V.toList
_ -> do
bs <- B.toList <$> pWithContents pBlocks contents
pure $ addCell tableSection
(B.Cell B.nullAttr B.AlignDefault (B.RowSpan 1) (B.ColSpan 1) bs)
tableData
tableData <- foldM (toCell TBody) (TableData mempty) children
let getRows tablePart = map (B.Row B.nullAttr . reverse)
. maybe [] (reverse . snd)
. M.lookup tablePart . unTableData
let headRows = getRows THeader tableData
let bodyRows = getRows TBody tableData
let footRows = getRows TFooter tableData
pure $
B.tableWith
(fromMaybe "" mbident, [], [])
(B.Caption mempty mempty)
colspecs
(B.TableHead B.nullAttr headRows)
[B.TableBody B.nullAttr 0 [] bodyRows]
(B.TableFoot B.nullAttr footRows)
data TableSection = THeader | TBody | TFooter
deriving (Show, Ord, Eq)
newtype TableData =
TableData { unTableData :: M.Map TableSection ([Int], [[Cell]]) }
deriving (Show)
-- for each table section, we have a pair
-- the first element indicates the number of column spaces left
-- in [currentLine, nextLine, lineAfter, etc.]
-- the second element is a list of rows, in reverse order,
-- each of which is a list of cells, in reverse order