git.iko.soy/typeable/pandoc
on master help sign in
Factor out T.P.Readers.LaTeX.Table.
commit08231f5cdd16e31d38d9d6bf59bc5ca12638b438 authorJohn MacFarlane <jgm@berkeley.edu> dateSat 27 Feb 2021, 21:40 (6 years ago) parent925815b
4 files changed+412−363
pandoc.cabal+1 src/Text/Pandoc/Readers/LaTeX.hs+5−363 src/Text/Pandoc/Readers/LaTeX/Parsing.hs+33 src/Text/Pandoc/Readers/LaTeX/Table.hs+373
pandoc.cabal+1
@@ -631,6 +631,7 @@ library
631631 Text.Pandoc.Readers.LaTeX.Lang,
632632 Text.Pandoc.Readers.LaTeX.SIunitx,
633633 Text.Pandoc.Readers.LaTeX.Accent,
634+ Text.Pandoc.Readers.LaTeX.Table,
634635 Text.Pandoc.Readers.Odt.Base,
635636 Text.Pandoc.Readers.Odt.Namespaces,
636637 Text.Pandoc.Readers.Odt.StyleReader,
src/Text/Pandoc/Readers/LaTeX.hs+5−363
@@ -1,4 +1,3 @@
1−{-# LANGUAGE BangPatterns #-}
21{-# LANGUAGE CPP #-}
32{-# LANGUAGE FlexibleInstances #-}
43{-# LANGUAGE MultiParamTypeClasses #-}
@@ -32,7 +31,6 @@ import Control.Monad
3231import Control.Monad.Except (throwError)
3332import Data.Char (isDigit, isLetter, toUpper, chr)
3433import Data.Default
35−import Data.Functor (($>))
3634import Data.List (intercalate)
3735import qualified Data.Map as M
3836import Data.Maybe (fromMaybe, maybeToList)
@@ -58,6 +56,7 @@ import Text.Pandoc.Readers.LaTeX.Types (ExpansionPoint (..), Macro (..),
5856 ArgSpec (..), Tok (..), TokType (..))
5957import Text.Pandoc.Readers.LaTeX.Parsing
6058import Text.Pandoc.Readers.LaTeX.Accent (accentCommands)
59+import Text.Pandoc.Readers.LaTeX.Table (tableEnvironments)
6160import Text.Pandoc.Readers.LaTeX.Lang (polyglossiaLangToBCP47,
6261 babelLangToBCP47)
6362import Text.Pandoc.Readers.LaTeX.SIunitx
@@ -551,12 +550,8 @@ inlineCommand' = try $ do
551550 <|> ignore rawcommand
552551 lookupListDefault raw names inlineCommands
553552
554−
555553tok :: PandocMonad m => LP m Inlines
556−tok = try $ spaces >> grouped inline <|> inlineCommand' <|> singleChar'
557− where singleChar' = do
558− Tok _ _ t <- singleChar
559− return $ str t
554+tok = tokWith inline
560555
561556opt :: PandocMonad m => LP m Inlines
562557opt = do
@@ -1118,12 +1113,6 @@ treatAsInline = Set.fromList
11181113 , "pagebreak"
11191114 ]
11201115
1121−label :: PandocMonad m => LP m ()
1122−label = do
1123− controlSeq "label"
1124− t <- braced
1125− updateState $ \st -> st{ sLastLabel = Just $ untokenize t }
1126−
11271116dolabel :: PandocMonad m => LP m Inlines
11281117dolabel = do
11291118 v <- braced
@@ -1421,13 +1410,6 @@ bracketedNum = do
14211410 Just i -> return i
14221411 _ -> return 0
14231412
1424−setCaption :: PandocMonad m => LP m ()
1425−setCaption = try $ do
1426− skipopts
1427− ils <- tok
1428− optional $ try $ spaces *> label
1429− updateState $ \st -> st{ sCaption = Just ils }
1430−
14311413looseItem :: PandocMonad m => LP m Blocks
14321414looseItem = do
14331415 inListItem <- sInListItem <$> getState
@@ -1441,10 +1423,6 @@ epigraph = do
14411423 p2 <- grouped block
14421424 return $ divWith ("", ["epigraph"], []) (p1 <> p2)
14431425
1444−resetCaption :: PandocMonad m => LP m ()
1445−resetCaption = updateState $ \st -> st{ sCaption = Nothing
1446− , sLastLabel = Nothing }
1447−
14481426section :: PandocMonad m => Attr -> Int -> LP m Blocks
14491427section (ident, classes, kvs) lvl = do
14501428 skipopts
@@ -1585,7 +1563,7 @@ blockCommands = M.fromList
15851563 , ("item", looseItem)
15861564 , ("documentclass", skipopts *> braced *> preamble)
15871565 , ("centerline", para . trimInlines <$> (skipopts *> tok))
1588− , ("caption", mempty <$ setCaption)
1566+ , ("caption", mempty <$ setCaption inline)
15891567 , ("bibliography", mempty <$ (skipopts *> braced >>=
15901568 addMeta "bibliography" . splitBibs . untokenize))
15911569 , ("addbibresource", mempty <$ (skipopts *> braced >>=
@@ -1623,7 +1601,8 @@ blockCommands = M.fromList
16231601
16241602
16251603environments :: PandocMonad m => M.Map Text (LP m Blocks)
1626−environments = M.fromList
1604+environments = M.union (tableEnvironments blocks inline) $
1605+ M.fromList
16271606 [ ("document", env "document" blocks <* skipMany anyTok)
16281607 , ("abstract", mempty <$ (env "abstract" blocks >>= addMeta "abstract"))
16291608 , ("sloppypar", env "sloppypar" blocks)
@@ -1633,13 +1612,6 @@ environments = M.fromList
16331612 , ("figure", env "figure" $ skipopts *> figure)
16341613 , ("subfigure", env "subfigure" $ skipopts *> tok *> figure)
16351614 , ("center", divWith ("", ["center"], []) <$> env "center" blocks)
1636− , ("longtable", env "longtable" $
1637− resetCaption *> simpTable "longtable" False >>= addTableCaption)
1638− , ("table", env "table" $
1639− skipopts *> resetCaption *> blocks >>= addTableCaption)
1640− , ("tabular*", env "tabular*" $ simpTable "tabular*" True)
1641− , ("tabularx", env "tabularx" $ simpTable "tabularx" True)
1642− , ("tabular", env "tabular" $ simpTable "tabular" False)
16431615 , ("quote", blockQuote <$> env "quote" blocks)
16441616 , ("quotation", blockQuote <$> env "quotation" blocks)
16451617 , ("verse", blockQuote <$> env "verse" blocks)
@@ -1805,9 +1777,6 @@ italicize (Para ils) = Para [Emph ils]
18051777italicize (Plain ils) = Plain [Emph ils]
18061778italicize x = x
18071779
1808−env :: PandocMonad m => Text -> LP m a -> LP m a
1809−env name p = p <* end_ name
1810−
18111780rawEnv :: PandocMonad m => Text -> LP m Blocks
18121781rawEnv name = do
18131782 exts <- getOption readerExtensions
@@ -2045,333 +2014,6 @@ orderedList' = try $ do
20452014 bs <- listenv "enumerate" (many item)
20462015 return $ orderedListWith (start, style, delim) bs
20472016
2048−-- tables
2049−
2050−hline :: PandocMonad m => LP m ()
2051−hline = try $ do
2052− spaces
2053− controlSeq "hline" <|>
2054− -- booktabs rules:
2055− controlSeq "toprule" <|>
2056− controlSeq "bottomrule" <|>
2057− controlSeq "midrule" <|>
2058− controlSeq "endhead" <|>
2059− controlSeq "endfirsthead"
2060− spaces
2061− optional opt
2062− return ()
2063−
2064−lbreak :: PandocMonad m => LP m Tok
2065−lbreak = (controlSeq "\\" <|> controlSeq "tabularnewline")
2066− <* skipopts <* spaces
2067−
2068−amp :: PandocMonad m => LP m Tok
2069−amp = symbol '&'
2070−
2071−-- Split a Word into individual Symbols (for parseAligns)
2072−splitWordTok :: PandocMonad m => LP m ()
2073−splitWordTok = do
2074− inp <- getInput
2075− case inp of
2076− (Tok spos Word t : rest) ->
2077− setInput $ map (Tok spos Symbol . T.singleton) (T.unpack t) <> rest
2078− _ -> return ()
2079−
2080−parseAligns :: PandocMonad m => LP m [(Alignment, ColWidth, ([Tok], [Tok]))]
2081−parseAligns = try $ do
2082− let maybeBar = skipMany
2083− (try $ sp *> (() <$ symbol '|' <|> () <$ (symbol '@' >> braced)))
2084− let cAlign = AlignCenter <$ symbol 'c'
2085− let lAlign = AlignLeft <$ symbol 'l'
2086− let rAlign = AlignRight <$ symbol 'r'
2087− let parAlign = AlignLeft <$ symbol 'p'
2088− -- aligns from tabularx
2089− let xAlign = AlignLeft <$ symbol 'X'
2090− let mAlign = AlignLeft <$ symbol 'm'
2091− let bAlign = AlignLeft <$ symbol 'b'
2092− let alignChar = splitWordTok *> ( cAlign <|> lAlign <|> rAlign <|> parAlign
2093− <|> xAlign <|> mAlign <|> bAlign )
2094− let alignPrefix = symbol '>' >> braced
2095− let alignSuffix = symbol '<' >> braced
2096− let colWidth = try $ do
2097− symbol '{'
2098− ds <- trim . untokenize <$> manyTill anyTok (controlSeq "linewidth")
2099− spaces
2100− symbol '}'
2101− return $ safeRead ds
2102− let alignSpec = do
2103− pref <- option [] alignPrefix
2104− spaces
2105− al <- alignChar
2106− width <- colWidth <|> option Nothing (do s <- untokenize <$> braced
2107− pos <- getPosition
2108− report $ SkippedContent s pos
2109− return Nothing)
2110− spaces
2111− suff <- option [] alignSuffix
2112− return (al, width, (pref, suff))
2113− let starAlign = do -- '*{2}{r}' == 'rr', we just expand like a macro
2114− symbol '*'
2115− spaces
2116− ds <- trim . untokenize <$> braced
2117− spaces
2118− spec <- braced
2119− case safeRead ds of
2120− Just n ->
2121− getInput >>= setInput . (mconcat (replicate n spec) ++)
2122− Nothing -> Prelude.fail $ "Could not parse " <> T.unpack ds <> " as number"
2123− bgroup
2124− spaces
2125− maybeBar
2126− aligns' <- many $ try $ spaces >> optional starAlign >>
2127− (alignSpec <* maybeBar)
2128− spaces
2129− egroup
2130− spaces
2131− return $ map toSpec aligns'
2132− where
2133− toColWidth (Just w) | w > 0 = ColWidth w
2134− toColWidth _ = ColWidthDefault
2135− toSpec (x, y, z) = (x, toColWidth y, z)
2136−
2137−-- N.B. this parser returns a Row that may have erroneous empty cells
2138−-- in it. See the note above fixTableHead for details.
2139−parseTableRow :: PandocMonad m
2140− => Text -- ^ table environment name
2141− -> [([Tok], [Tok])] -- ^ pref/suffixes
2142− -> LP m Row
2143−parseTableRow envname prefsufs = do
2144− notFollowedBy (spaces *> end_ envname)
2145− -- add prefixes and suffixes in token stream:
2146− let celltoks (pref, suff) = do
2147− prefpos <- getPosition
2148− contents <- mconcat <$>
2149− many ( snd <$> withRaw (controlSeq "parbox" >> parbox) -- #5711
2150− <|>
2151− snd <$> withRaw (inlineEnvironment <|> dollarsMath)
2152− <|>
2153− (do notFollowedBy
2154− (() <$ amp <|> () <$ lbreak <|> end_ envname)
2155− count 1 anyTok) )
2156−
2157− suffpos <- getPosition
2158− option [] (count 1 amp)
2159− return $ map (setpos prefpos) pref ++ contents ++ map (setpos suffpos) suff
2160− rawcells <- mapM celltoks prefsufs
2161− cells <- mapM (parseFromToks parseTableCell) rawcells
2162− spaces
2163− return $ Row nullAttr cells
2164−
2165−parseTableCell :: PandocMonad m => LP m Cell
2166−parseTableCell = do
2167− spaces
2168− updateState $ \st -> st{ sInTableCell = True }
2169− cell' <- multicolumnCell
2170− <|> multirowCell
2171− <|> parseSimpleCell
2172− <|> parseEmptyCell
2173− updateState $ \st -> st{ sInTableCell = False }
2174− spaces
2175− return cell'
2176− where
2177− -- The parsing of empty cells is important in LaTeX, especially when dealing
2178− -- with multirow/multicolumn. See #6603.
2179− parseEmptyCell = spaces $> emptyCell
2180−
2181−cellAlignment :: PandocMonad m => LP m Alignment
2182−cellAlignment = skipMany (symbol '|') *> alignment <* skipMany (symbol '|')
2183− where
2184− alignment = do
2185− c <- untoken <$> singleChar
2186− return $ case c of
2187− "l" -> AlignLeft
2188− "r" -> AlignRight
2189− "c" -> AlignCenter
2190− "*" -> AlignDefault
2191− _ -> AlignDefault
2192−
2193−plainify :: Blocks -> Blocks
2194−plainify bs = case toList bs of
2195− [Para ils] -> plain (fromList ils)
2196− _ -> bs
2197−
2198−multirowCell :: PandocMonad m => LP m Cell
2199−multirowCell = controlSeq "multirow" >> do
2200− -- Full prototype for \multirow macro is:
2201− -- \multirow[vpos]{nrows}[bigstruts]{width}[vmove]{text}
2202− -- However, everything except `nrows` and `text` make
2203− -- sense in the context of the Pandoc AST
2204− _ <- optional $ symbol '[' *> cellAlignment <* symbol ']' -- vertical position
2205− nrows <- fmap (fromMaybe 1 . safeRead . untokenize) braced
2206− _ <- optional $ symbol '[' *> manyTill anyTok (symbol ']') -- bigstrut-related
2207− _ <- symbol '{' *> manyTill anyTok (symbol '}') -- Cell width
2208− _ <- optional $ symbol '[' *> manyTill anyTok (symbol ']') -- Length used for fine-tuning
2209− content <- symbol '{' *> (plainify <$> blocks) <* symbol '}'
2210− return $ cell AlignDefault (RowSpan nrows) (ColSpan 1) content
2211−
2212−multicolumnCell :: PandocMonad m => LP m Cell
2213−multicolumnCell = controlSeq "multicolumn" >> do
2214− span' <- fmap (fromMaybe 1 . safeRead . untokenize) braced
2215− alignment <- symbol '{' *> cellAlignment <* symbol '}'
2216−
2217− let singleCell = do
2218− content <- plainify <$> blocks
2219− return $ cell alignment (RowSpan 1) (ColSpan span') content
2220−
2221− -- Two possible contents: either a \multirow cell, or content.
2222− -- E.g. \multicol{1}{c}{\multirow{2}{1em}{content}}
2223− -- Note that a \multirow cell can be nested in a \multicolumn,
2224− -- but not the other way around. See #6603
2225− let nestedCell = do
2226− (Cell _ _ (RowSpan rs) _ bs) <- multirowCell
2227− return $ cell
2228− alignment
2229− (RowSpan rs)
2230− (ColSpan span')
2231− (fromList bs)
2232−
2233− symbol '{' *> (nestedCell <|> singleCell) <* symbol '}'
2234−
2235−-- Parse a simple cell, i.e. not multirow/multicol
2236−parseSimpleCell :: PandocMonad m => LP m Cell
2237−parseSimpleCell = simpleCell <$> (plainify <$> blocks)
2238−
2239−-- LaTeX tables are stored with empty cells underneath multirow cells
2240−-- denoting the grid spaces taken up by them. More specifically, if a
2241−-- cell spans m rows, then it will overwrite all the cells in the
2242−-- columns it spans for (m-1) rows underneath it, requiring padding
2243−-- cells in these places. These padding cells need to be removed for
2244−-- proper table reading. See #6603.
2245−--
2246−-- These fixTable functions do not otherwise fix up malformed
2247−-- input tables: that is left to the table builder.
2248−fixTableHead :: TableHead -> TableHead
2249−fixTableHead (TableHead attr rows) = TableHead attr rows'
2250− where
2251− rows' = fixTableRows rows
2252−
2253−fixTableBody :: TableBody -> TableBody
2254−fixTableBody (TableBody attr rhc th tb)
2255− = TableBody attr rhc th' tb'
2256− where
2257− th' = fixTableRows th
2258− tb' = fixTableRows tb
2259−
2260−fixTableRows :: [Row] -> [Row]
2261−fixTableRows = fixTableRows' $ repeat Nothing
2262− where
2263− fixTableRows' oldHang (Row attr cells : rs)
2264− = let (newHang, cells') = fixTableRow oldHang cells
2265− rs' = fixTableRows' newHang rs
2266− in Row attr cells' : rs'
2267− fixTableRows' _ [] = []
2268−
2269−-- The overhang is represented as Just (relative cell dimensions) or
2270−-- Nothing for an empty grid space.
2271−fixTableRow :: [Maybe (ColSpan, RowSpan)] -> [Cell] -> ([Maybe (ColSpan, RowSpan)], [Cell])
2272−fixTableRow oldHang cells
2273− -- If there's overhang, drop cells until their total width meets the
2274− -- width of the occupied grid spaces (or we run out)
2275− | (n, prefHang, restHang) <- splitHang oldHang
2276− , n > 0
2277− = let cells' = dropToWidth getCellW n cells
2278− (restHang', cells'') = fixTableRow restHang cells'
2279− in (prefHang restHang', cells'')
2280− -- Otherwise record the overhang of a pending cell and fix the rest
2281− -- of the row
2282− | c@(Cell _ _ h w _):cells' <- cells
2283− = let h' = max 1 h
2284− w' = max 1 w
2285− oldHang' = dropToWidth getHangW w' oldHang
2286− (newHang, cells'') = fixTableRow oldHang' cells'
2287− in (toHang w' h' <> newHang, c : cells'')
2288− | otherwise
2289− = (oldHang, [])
2290− where
2291− getCellW (Cell _ _ _ w _) = w
2292− getHangW = maybe 1 fst
2293− getCS (ColSpan n) = n
2294−
2295− toHang c r
2296− | r > 1 = [Just (c, r)]
2297− | otherwise = replicate (getCS c) Nothing
2298−
2299− -- Take the prefix of the overhang list representing filled grid
2300− -- spaces. Also return the remainder and the length of this prefix.
2301− splitHang = splitHang' 0 id
2302−
2303− splitHang' !n l (Just (c, r):xs)
2304− = splitHang' (n + c) (l . (toHang c (r-1) ++)) xs
2305− splitHang' n l xs = (n, l, xs)
2306−
2307− -- Drop list items until the total width of the dropped items
2308− -- exceeds the passed width.
2309− dropToWidth _ n l | n < 1 = l
2310− dropToWidth wproj n (c:cs) = dropToWidth wproj (n - wproj c) cs
2311− dropToWidth _ _ [] = []
2312−
2313−simpTable :: PandocMonad m => Text -> Bool -> LP m Blocks
2314−simpTable envname hasWidthParameter = try $ do
2315− when hasWidthParameter $ () <$ (spaces >> tok)
2316− skipopts
2317− colspecs <- parseAligns
2318− let (aligns, widths, prefsufs) = unzip3 colspecs
2319− optional $ controlSeq "caption" *> setCaption
2320− spaces
2321− optional label
2322− spaces
2323− optional lbreak
2324− spaces
2325− skipMany hline
2326− spaces
2327− header' <- option [] . try . fmap (:[]) $
2328− parseTableRow envname prefsufs <* lbreak <* many1 hline
2329− spaces
2330− rows <- sepEndBy (parseTableRow envname prefsufs)
2331− (lbreak <* optional (skipMany hline))
2332− spaces
2333− optional $ controlSeq "caption" *> setCaption
2334− spaces
2335− optional label
2336− spaces
2337− optional lbreak
2338− spaces
2339− lookAhead $ controlSeq "end" -- make sure we're at end
2340− let th = fixTableHead $ TableHead nullAttr header'
2341− let tbs = [fixTableBody $ TableBody nullAttr 0 [] rows]
2342− let tf = TableFoot nullAttr []
2343− return $ table emptyCaption (zip aligns widths) th tbs tf
2344−
2345−addTableCaption :: PandocMonad m => Blocks -> LP m Blocks
2346−addTableCaption = walkM go
2347− where go (Table attr c spec th tb tf) = do
2348− st <- getState
2349− let mblabel = sLastLabel st
2350− capt <- case (sCaption st, mblabel) of
2351− (Just ils, Nothing) -> return $ caption Nothing (plain ils)
2352− (Just ils, Just lab) -> do
2353− num <- getNextNumber sLastTableNum
2354− setState
2355− st{ sLastTableNum = num
2356− , sLabels = M.insert lab
2357− [Str (renderDottedNum num)]
2358− (sLabels st) }
2359− return $ caption Nothing (plain ils) -- add number??
2360− (Nothing, _) -> return c
2361− let attr' = case (attr, mblabel) of
2362− ((_,classes,kvs), Just ident) ->
2363− (ident,classes,kvs)
2364− _ -> attr
2365− return $ addAttrDiv attr' $ Table nullAttr capt spec th tb tf
2366− go x = return x
2367−
2368−-- TODO: For now we add a Div to contain table attributes, since
2369−-- most writers don't do anything yet with attributes on Table.
2370−-- This can be removed when that changes.
2371−addAttrDiv :: Attr -> Block -> Block
2372−addAttrDiv ("",[],[]) b = b
2373−addAttrDiv attr b = Div attr [b]
2374−
23752017block :: PandocMonad m => LP m Blocks
23762018block = do
23772019 res <- (mempty <$ spaces1)
src/Text/Pandoc/Readers/LaTeX/Parsing.hs+33
@@ -54,6 +54,7 @@ module Text.Pandoc.Readers.LaTeX.Parsing
5454 , comment
5555 , anyTok
5656 , singleChar
57+ , tokWith
5758 , specialChars
5859 , endline
5960 , blankline
@@ -80,6 +81,10 @@ module Text.Pandoc.Readers.LaTeX.Parsing
8081 , rawopt
8182 , overlaySpecification
8283 , getNextNumber
84+ , label
85+ , setCaption
86+ , resetCaption
87+ , env
8388 ) where
8489
8590import Control.Applicative (many, (<|>))
@@ -914,3 +919,31 @@ getNextNumber getCurrentNum = do
914919 Just n -> [n, 1]
915920 Nothing -> [1]
916921
922+label :: PandocMonad m => LP m ()
923+label = do
924+ controlSeq "label"
925+ t <- braced
926+ updateState $ \st -> st{ sLastLabel = Just $ untokenize t }
927+
928+setCaption :: PandocMonad m => LP m Inlines -> LP m ()
929+setCaption inline = try $ do
930+ skipopts
931+ ils <- tokWith inline
932+ optional $ try $ spaces *> label
933+ updateState $ \st -> st{ sCaption = Just ils }
934+
935+resetCaption :: PandocMonad m => LP m ()
936+resetCaption = updateState $ \st -> st{ sCaption = Nothing
937+ , sLastLabel = Nothing }
938+
939+env :: PandocMonad m => Text -> LP m a -> LP m a
940+env name p = p <* end_ name
941+
942+tokWith :: PandocMonad m => LP m Inlines -> LP m Inlines
943+tokWith inlineParser = try $ spaces >>
944+ grouped inlineParser
945+ <|> (lookAhead anyControlSeq >> inlineParser)
946+ <|> singleChar'
947+ where singleChar' = do
948+ Tok _ _ t <- singleChar
949+ return $ str t
src/Text/Pandoc/Readers/LaTeX/Table.hsnew file+373
@@ -0,0 +1,373 @@
1+{-# LANGUAGE BangPatterns #-}
2+{-# LANGUAGE OverloadedStrings #-}
3+module Text.Pandoc.Readers.LaTeX.Table
4+ ( tableEnvironments )
5+where
6+
7+import Data.Functor (($>))
8+import Text.Pandoc.Class
9+import Text.Pandoc.Readers.LaTeX.Parsing
10+import Text.Pandoc.Readers.LaTeX.Types
11+import Text.Pandoc.Builder as B
12+import qualified Data.Map as M
13+import Data.Text (Text)
14+import Data.Maybe (fromMaybe)
15+import qualified Data.Text as T
16+import Control.Applicative ((<|>), optional, many)
17+import Control.Monad (when, void)
18+import Text.Pandoc.Shared (safeRead, trim)
19+import Text.Pandoc.Logging (LogMessage(SkippedContent))
20+import Text.Pandoc.Walk (walkM)
21+import Text.Pandoc.Parsing hiding (blankline, many, mathDisplay, mathInline,
22+ optional, space, spaces, withRaw, (<|>))
23+
24+tableEnvironments :: PandocMonad m
25+ => LP m Blocks
26+ -> LP m Inlines
27+ -> M.Map Text (LP m Blocks)
28+tableEnvironments blocks inline =
29+ M.fromList
30+ [ ("longtable", env "longtable" $
31+ resetCaption *>
32+ simpTable blocks inline "longtable" False >>= addTableCaption)
33+ , ("table", env "table" $
34+ skipopts *> resetCaption *> blocks >>= addTableCaption)
35+ , ("tabular*", env "tabular*" $ simpTable blocks inline "tabular*" True)
36+ , ("tabularx", env "tabularx" $ simpTable blocks inline "tabularx" True)
37+ , ("tabular", env "tabular" $ simpTable blocks inline "tabular" False)
38+ ]
39+
40+hline :: PandocMonad m => LP m ()
41+hline = try $ do
42+ spaces
43+ controlSeq "hline" <|>
44+ -- booktabs rules:
45+ controlSeq "toprule" <|>
46+ controlSeq "bottomrule" <|>
47+ controlSeq "midrule" <|>
48+ controlSeq "endhead" <|>
49+ controlSeq "endfirsthead"
50+ spaces
51+ optional rawopt
52+ return ()
53+
54+lbreak :: PandocMonad m => LP m Tok
55+lbreak = (controlSeq "\\" <|> controlSeq "tabularnewline")
56+ <* skipopts <* spaces
57+
58+amp :: PandocMonad m => LP m Tok
59+amp = symbol '&'
60+
61+-- Split a Word into individual Symbols (for parseAligns)
62+splitWordTok :: PandocMonad m => LP m ()
63+splitWordTok = do
64+ inp <- getInput
65+ case inp of
66+ (Tok spos Word t : rest) ->
67+ setInput $ map (Tok spos Symbol . T.singleton) (T.unpack t) <> rest
68+ _ -> return ()
69+
70+parseAligns :: PandocMonad m => LP m [(Alignment, ColWidth, ([Tok], [Tok]))]
71+parseAligns = try $ do
72+ let maybeBar = skipMany
73+ (try $ sp *> (() <$ symbol '|' <|> () <$ (symbol '@' >> braced)))
74+ let cAlign = AlignCenter <$ symbol 'c'
75+ let lAlign = AlignLeft <$ symbol 'l'
76+ let rAlign = AlignRight <$ symbol 'r'
77+ let parAlign = AlignLeft <$ symbol 'p'
78+ -- aligns from tabularx
79+ let xAlign = AlignLeft <$ symbol 'X'
80+ let mAlign = AlignLeft <$ symbol 'm'
81+ let bAlign = AlignLeft <$ symbol 'b'
82+ let alignChar = splitWordTok *> ( cAlign <|> lAlign <|> rAlign <|> parAlign
83+ <|> xAlign <|> mAlign <|> bAlign )
84+ let alignPrefix = symbol '>' >> braced
85+ let alignSuffix = symbol '<' >> braced
86+ let colWidth = try $ do
87+ symbol '{'
88+ ds <- trim . untokenize <$> manyTill anyTok (controlSeq "linewidth")
89+ spaces
90+ symbol '}'
91+ return $ safeRead ds
92+ let alignSpec = do
93+ pref <- option [] alignPrefix
94+ spaces
95+ al <- alignChar
96+ width <- colWidth <|> option Nothing (do s <- untokenize <$> braced
97+ pos <- getPosition
98+ report $ SkippedContent s pos
99+ return Nothing)
100+ spaces
101+ suff <- option [] alignSuffix
102+ return (al, width, (pref, suff))
103+ let starAlign = do -- '*{2}{r}' == 'rr', we just expand like a macro
104+ symbol '*'
105+ spaces
106+ ds <- trim . untokenize <$> braced
107+ spaces
108+ spec <- braced
109+ case safeRead ds of
110+ Just n ->
111+ getInput >>= setInput . (mconcat (replicate n spec) ++)
112+ Nothing -> Prelude.fail $ "Could not parse " <> T.unpack ds <> " as number"
113+ bgroup
114+ spaces
115+ maybeBar
116+ aligns' <- many $ try $ spaces >> optional starAlign >>
117+ (alignSpec <* maybeBar)
118+ spaces
119+ egroup
120+ spaces
121+ return $ map toSpec aligns'
122+ where
123+ toColWidth (Just w) | w > 0 = ColWidth w
124+ toColWidth _ = ColWidthDefault
125+ toSpec (x, y, z) = (x, toColWidth y, z)
126+
127+-- N.B. this parser returns a Row that may have erroneous empty cells
128+-- in it. See the note above fixTableHead for details.
129+parseTableRow :: PandocMonad m
130+ => LP m Blocks -- ^ block parser
131+ -> LP m Inlines -- ^ inline parser
132+ -> Text -- ^ table environment name
133+ -> [([Tok], [Tok])] -- ^ pref/suffixes
134+ -> LP m Row
135+parseTableRow blocks inline envname prefsufs = do
136+ notFollowedBy (spaces *> end_ envname)
137+ -- add prefixes and suffixes in token stream:
138+ let celltoks (pref, suff) = do
139+ prefpos <- getPosition
140+ contents <- mconcat <$>
141+ many ( snd <$> withRaw
142+ ((lookAhead (controlSeq "parbox") >>
143+ void blocks) -- #5711
144+ <|>
145+ (lookAhead (controlSeq "begin") >> void inline)
146+ <|>
147+ (lookAhead (symbol '$') >> void inline))
148+ <|>
149+ (do notFollowedBy
150+ (() <$ amp <|> () <$ lbreak <|> end_ envname)
151+ count 1 anyTok) )
152+
153+ suffpos <- getPosition
154+ option [] (count 1 amp)
155+ return $ map (setpos prefpos) pref ++ contents ++ map (setpos suffpos) suff
156+ rawcells <- mapM celltoks prefsufs
157+ cells <- mapM (parseFromToks (parseTableCell blocks)) rawcells
158+ spaces
159+ return $ Row nullAttr cells
160+
161+parseTableCell :: PandocMonad m => LP m Blocks -> LP m Cell
162+parseTableCell blocks = do
163+ spaces
164+ updateState $ \st -> st{ sInTableCell = True }
165+ cell' <- multicolumnCell blocks
166+ <|> multirowCell blocks
167+ <|> parseSimpleCell
168+ <|> parseEmptyCell
169+ updateState $ \st -> st{ sInTableCell = False }
170+ spaces
171+ return cell'
172+ where
173+ -- The parsing of empty cells is important in LaTeX, especially when dealing
174+ -- with multirow/multicolumn. See #6603.
175+ parseEmptyCell = spaces $> emptyCell
176+ parseSimpleCell = simpleCell <$> (plainify <$> blocks)
177+
178+
179+cellAlignment :: PandocMonad m => LP m Alignment
180+cellAlignment = skipMany (symbol '|') *> alignment <* skipMany (symbol '|')
181+ where
182+ alignment = do
183+ c <- untoken <$> singleChar
184+ return $ case c of
185+ "l" -> AlignLeft
186+ "r" -> AlignRight
187+ "c" -> AlignCenter
188+ "*" -> AlignDefault
189+ _ -> AlignDefault
190+
191+plainify :: Blocks -> Blocks
192+plainify bs = case toList bs of
193+ [Para ils] -> plain (fromList ils)
194+ _ -> bs
195+
196+multirowCell :: PandocMonad m => LP m Blocks -> LP m Cell
197+multirowCell blocks = controlSeq "multirow" >> do
198+ -- Full prototype for \multirow macro is:
199+ -- \multirow[vpos]{nrows}[bigstruts]{width}[vmove]{text}
200+ -- However, everything except `nrows` and `text` make
201+ -- sense in the context of the Pandoc AST
202+ _ <- optional $ symbol '[' *> cellAlignment <* symbol ']' -- vertical position
203+ nrows <- fmap (fromMaybe 1 . safeRead . untokenize) braced
204+ _ <- optional $ symbol '[' *> manyTill anyTok (symbol ']') -- bigstrut-related
205+ _ <- symbol '{' *> manyTill anyTok (symbol '}') -- Cell width
206+ _ <- optional $ symbol '[' *> manyTill anyTok (symbol ']') -- Length used for fine-tuning
207+ content <- symbol '{' *> (plainify <$> blocks) <* symbol '}'
208+ return $ cell AlignDefault (RowSpan nrows) (ColSpan 1) content
209+
210+multicolumnCell :: PandocMonad m => LP m Blocks -> LP m Cell
211+multicolumnCell blocks = controlSeq "multicolumn" >> do
212+ span' <- fmap (fromMaybe 1 . safeRead . untokenize) braced
213+ alignment <- symbol '{' *> cellAlignment <* symbol '}'
214+
215+ let singleCell = do
216+ content <- plainify <$> blocks
217+ return $ cell alignment (RowSpan 1) (ColSpan span') content
218+
219+ -- Two possible contents: either a \multirow cell, or content.
220+ -- E.g. \multicol{1}{c}{\multirow{2}{1em}{content}}
221+ -- Note that a \multirow cell can be nested in a \multicolumn,
222+ -- but not the other way around. See #6603
223+ let nestedCell = do
224+ (Cell _ _ (RowSpan rs) _ bs) <- multirowCell blocks
225+ return $ cell
226+ alignment
227+ (RowSpan rs)
228+ (ColSpan span')
229+ (fromList bs)
230+
231+ symbol '{' *> (nestedCell <|> singleCell) <* symbol '}'
232+
233+-- LaTeX tables are stored with empty cells underneath multirow cells
234+-- denoting the grid spaces taken up by them. More specifically, if a
235+-- cell spans m rows, then it will overwrite all the cells in the
236+-- columns it spans for (m-1) rows underneath it, requiring padding
237+-- cells in these places. These padding cells need to be removed for
238+-- proper table reading. See #6603.
239+--
240+-- These fixTable functions do not otherwise fix up malformed
241+-- input tables: that is left to the table builder.
242+fixTableHead :: TableHead -> TableHead
243+fixTableHead (TableHead attr rows) = TableHead attr rows'
244+ where
245+ rows' = fixTableRows rows
246+
247+fixTableBody :: TableBody -> TableBody
248+fixTableBody (TableBody attr rhc th tb)
249+ = TableBody attr rhc th' tb'
250+ where
251+ th' = fixTableRows th
252+ tb' = fixTableRows tb
253+
254+fixTableRows :: [Row] -> [Row]
255+fixTableRows = fixTableRows' $ repeat Nothing
256+ where
257+ fixTableRows' oldHang (Row attr cells : rs)
258+ = let (newHang, cells') = fixTableRow oldHang cells
259+ rs' = fixTableRows' newHang rs
260+ in Row attr cells' : rs'
261+ fixTableRows' _ [] = []
262+
263+-- The overhang is represented as Just (relative cell dimensions) or
264+-- Nothing for an empty grid space.
265+fixTableRow :: [Maybe (ColSpan, RowSpan)] -> [Cell] -> ([Maybe (ColSpan, RowSpan)], [Cell])
266+fixTableRow oldHang cells
267+ -- If there's overhang, drop cells until their total width meets the
268+ -- width of the occupied grid spaces (or we run out)
269+ | (n, prefHang, restHang) <- splitHang oldHang
270+ , n > 0
271+ = let cells' = dropToWidth getCellW n cells
272+ (restHang', cells'') = fixTableRow restHang cells'
273+ in (prefHang restHang', cells'')
274+ -- Otherwise record the overhang of a pending cell and fix the rest
275+ -- of the row
276+ | c@(Cell _ _ h w _):cells' <- cells
277+ = let h' = max 1 h
278+ w' = max 1 w
279+ oldHang' = dropToWidth getHangW w' oldHang
280+ (newHang, cells'') = fixTableRow oldHang' cells'
281+ in (toHang w' h' <> newHang, c : cells'')
282+ | otherwise
283+ = (oldHang, [])
284+ where
285+ getCellW (Cell _ _ _ w _) = w
286+ getHangW = maybe 1 fst
287+ getCS (ColSpan n) = n
288+
289+ toHang c r
290+ | r > 1 = [Just (c, r)]
291+ | otherwise = replicate (getCS c) Nothing
292+
293+ -- Take the prefix of the overhang list representing filled grid
294+ -- spaces. Also return the remainder and the length of this prefix.
295+ splitHang = splitHang' 0 id
296+
297+ splitHang' !n l (Just (c, r):xs)
298+ = splitHang' (n + c) (l . (toHang c (r-1) ++)) xs
299+ splitHang' n l xs = (n, l, xs)
300+
301+ -- Drop list items until the total width of the dropped items
302+ -- exceeds the passed width.
303+ dropToWidth _ n l | n < 1 = l
304+ dropToWidth wproj n (c:cs) = dropToWidth wproj (n - wproj c) cs
305+ dropToWidth _ _ [] = []
306+
307+simpTable :: PandocMonad m
308+ => LP m Blocks
309+ -> LP m Inlines
310+ -> Text
311+ -> Bool
312+ -> LP m Blocks
313+simpTable blocks inline envname hasWidthParameter = try $ do
314+ when hasWidthParameter $ () <$ tokWith inline
315+ skipopts
316+ colspecs <- parseAligns
317+ let (aligns, widths, prefsufs) = unzip3 colspecs
318+ optional $ controlSeq "caption" *> setCaption inline
319+ spaces
320+ optional label
321+ spaces
322+ optional lbreak
323+ spaces
324+ skipMany hline
325+ spaces
326+ header' <- option [] . try . fmap (:[]) $
327+ parseTableRow blocks inline envname prefsufs <*
328+ lbreak <* many1 hline
329+ spaces
330+ rows <- sepEndBy (parseTableRow blocks inline envname prefsufs)
331+ (lbreak <* optional (skipMany hline))
332+ spaces
333+ optional $ controlSeq "caption" *> setCaption inline
334+ spaces
335+ optional label
336+ spaces
337+ optional lbreak
338+ spaces
339+ lookAhead $ controlSeq "end" -- make sure we're at end
340+ let th = fixTableHead $ TableHead nullAttr header'
341+ let tbs = [fixTableBody $ TableBody nullAttr 0 [] rows]
342+ let tf = TableFoot nullAttr []
343+ return $ table emptyCaption (zip aligns widths) th tbs tf
344+
345+addTableCaption :: PandocMonad m => Blocks -> LP m Blocks
346+addTableCaption = walkM go
347+ where go (Table attr c spec th tb tf) = do
348+ st <- getState
349+ let mblabel = sLastLabel st
350+ capt <- case (sCaption st, mblabel) of
351+ (Just ils, Nothing) -> return $ caption Nothing (plain ils)
352+ (Just ils, Just lab) -> do
353+ num <- getNextNumber sLastTableNum
354+ setState
355+ st{ sLastTableNum = num
356+ , sLabels = M.insert lab
357+ [Str (renderDottedNum num)]
358+ (sLabels st) }
359+ return $ caption Nothing (plain ils) -- add number??
360+ (Nothing, _) -> return c
361+ let attr' = case (attr, mblabel) of
362+ ((_,classes,kvs), Just ident) ->
363+ (ident,classes,kvs)
364+ _ -> attr
365+ return $ addAttrDiv attr' $ Table nullAttr capt spec th tb tf
366+ go x = return x
367+
368+-- TODO: For now we add a Div to contain table attributes, since
369+-- most writers don't do anything yet with attributes on Table.
370+-- This can be removed when that changes.
371+addAttrDiv :: Attr -> Block -> Block
372+addAttrDiv ("",[],[]) b = b
373+addAttrDiv attr b = Div attr [b]