git.iko.soy/typeable/pandoc
on master help sign in
Docx reader: Support new table features.

* Column spans * Row spans - The spec says that if the `val` attribute is ommitted, its value should be assumed to be `continue`, and that its values are restricted to {`restart`, `continue`}. If the value has any other value, I think it seems reasonable to default it to `continue`. It might cause problems if the spec is extended in the future by adding a third possible value, in which case this would probably give incorrect behaviour, and wouldn't error. * Allow multiple header rows * Include table description in simple caption - The table description element is like alt text for a table (along with the table caption element). It seems like we should include this somewhere, but I’m not 100% sure how – I’m pairing it with the simple caption for the moment. (Should it maybe go in the block caption instead?) * Detect table captions - Check for caption paragraph style /and/ either the simple or complex table field. This means the caption detection fails for captions which don’t contain a field, as in an example doc I added as a test. However, I think it’s better to be too conservative: a missed table caption will still show up as a paragraph next to the table, whereas if I incorrectly classify something else as a table caption it could cause havoc by pairing it up with a table it’s not at all related to, or dropping it entirely. * Update tests and add new ones Partially fixes: #6316

commit56b211120c62a01f8aba1c4512acfe4677d8c7d0 authorEmily Bourke <undergroundquizscene@protonmail.com> dateThu 18 Jun 2020, 09:53 (6 years ago) parent44484d0
15 files changed+487−68
src/Text/Pandoc/Readers/Docx.hs+56−36 src/Text/Pandoc/Readers/Docx/Parse.hs+100−13 src/Text/Pandoc/Readers/Docx/Util.hs+7 test/Tests/Readers/Docx.hs+16 test/docx/sdt_elements.native+6−7 test/docx/table_captions_no_field.docx test/docx/table_captions_no_field.native+34 test/docx/table_captions_with_field.docx test/docx/table_captions_with_field.native+54 test/docx/table_header_rowspan.docx test/docx/table_header_rowspan.native+189 test/docx/table_one_header_row.docx test/docx/table_one_header_row.native+18 test/docx/table_one_row.docx test/docx/table_variable_width.native+7−12
src/Text/Pandoc/Readers/Docx.hs+56−36
@@ -61,13 +61,14 @@ module Text.Pandoc.Readers.Docx
6161import Codec.Archive.Zip
6262import Control.Monad.Reader
6363import Control.Monad.State.Strict
64+import Data.Bifunctor (bimap, first)
6465import qualified Data.ByteString.Lazy as B
6566import Data.Default (Default)
66−import Data.List (delete, intersect)
67+import Data.List (delete, intersect, foldl')
6768import Data.Char (isSpace)
6869import qualified Data.Map as M
6970import qualified Data.Text as T
70−import Data.Maybe (isJust, fromMaybe)
71+import Data.Maybe (catMaybes, isJust, fromMaybe)
7172import Data.Sequence (ViewL (..), viewl)
7273import qualified Data.Sequence as Seq
7374import qualified Data.Set as Set
@@ -113,6 +114,7 @@ data DState = DState { docxAnchorMap :: M.Map T.Text T.Text
113114 -- restarting
114115 , docxListState :: M.Map (T.Text, T.Text) Integer
115116 , docxPrevPara :: Inlines
117+ , docxTableCaptions :: [Blocks]
116118 }
117119
118120instance Default DState where
@@ -123,6 +125,7 @@ instance Default DState where
123125 , docxDropCap = mempty
124126 , docxListState = M.empty
125127 , docxPrevPara = mempty
128+ , docxTableCaptions = []
126129 }
127130
128131data DEnv = DEnv { docxOptions :: ReaderOptions
@@ -491,15 +494,32 @@ singleParaToPlain blks
491494 singleton $ Plain ils
492495singleParaToPlain blks = blks
493496
494−cellToBlocks :: PandocMonad m => Docx.Cell -> DocxContext m Blocks
495−cellToBlocks (Docx.Cell bps) = do
497+cellToCell :: PandocMonad m => RowSpan -> Docx.Cell -> DocxContext m Pandoc.Cell
498+cellToCell rowSpan (Docx.Cell gridSpan _ bps) = do
496499 blks <- smushBlocks <$> mapM bodyPartToBlocks bps
497− return $ fromList $ blocksToDefinitions $ blocksToBullets $ toList blks
500+ let blks' = singleParaToPlain $ fromList $ blocksToDefinitions $ blocksToBullets $ toList blks
501+ return (cell AlignDefault rowSpan (ColSpan (fromIntegral gridSpan)) blks')
502+
503+rowsToRows :: PandocMonad m => [Docx.Row] -> DocxContext m [Pandoc.Row]
504+rowsToRows rows = do
505+ let rowspans = (fmap . fmap) (first RowSpan) (Docx.rowsToRowspans rows)
506+ cells <- traverse (traverse (uncurry cellToCell)) rowspans
507+ return (fmap (Pandoc.Row nullAttr) cells)
508+
509+splitHeaderRows :: Bool -> [Docx.Row] -> ([Docx.Row], [Docx.Row])
510+splitHeaderRows hasFirstRowFormatting rs = bimap reverse reverse $ fst
511+ $ if hasFirstRowFormatting
512+ then foldl' f ((take 1 rs, []), True) (drop 1 rs)
513+ else foldl' f (([], []), False) rs
514+ where
515+ f ((headerRows, bodyRows), previousRowWasHeader) r@(Docx.Row h cs)
516+ | h == HasTblHeader || (previousRowWasHeader && any isContinuationCell cs)
517+ = ((r : headerRows, bodyRows), True)
518+ | otherwise
519+ = ((headerRows, r : bodyRows), False)
520+
521+ isContinuationCell (Docx.Cell _ vm _) = vm == Docx.Continue
498522
499−rowToBlocksList :: PandocMonad m => Docx.Row -> DocxContext m [Blocks]
500−rowToBlocksList (Docx.Row cells) = do
501− blksList <- mapM cellToBlocks cells
502− return $ map singleParaToPlain blksList
503523
504524-- like trimInlines, but also take out linebreaks
505525trimSps :: Inlines -> Inlines
@@ -546,6 +566,11 @@ normalizeToClassName = T.map go . fromStyleName
546566 where go c | isSpace c = '-'
547567 | otherwise = c
548568
569+bodyPartToTableCaption :: PandocMonad m => BodyPart -> DocxContext m (Maybe Blocks)
570+bodyPartToTableCaption (TblCaption pPr parparts) =
571+ Just <$> bodyPartToBlocks (Paragraph pPr parparts)
572+bodyPartToTableCaption _ = pure Nothing
573+
549574bodyPartToBlocks :: PandocMonad m => BodyPart -> DocxContext m Blocks
550575bodyPartToBlocks (Paragraph pPr parparts)
551576 | Just True <- pBidi pPr = do
@@ -637,50 +662,43 @@ bodyPartToBlocks (ListItem pPr _ _ _ parparts) =
637662 let pPr' = pPr {pStyle = constructBogusParStyleData "list-paragraph": pStyle pPr}
638663 in
639664 bodyPartToBlocks $ Paragraph pPr' parparts
665+bodyPartToBlocks (TblCaption _ _) =
666+ return $ para mempty -- collected separately
640667bodyPartToBlocks (Tbl _ _ _ []) =
641668 return $ para mempty
642−bodyPartToBlocks (Tbl cap grid look parts@(r:rs)) = do
643− let cap' = simpleCaption $ plain $ text cap
644− (hdr, rows) = case firstRowFormatting look of
645− True | null rs -> (Nothing, [r])
646− | otherwise -> (Just r, rs)
647− False -> (Nothing, r:rs)
648−
649− cells <- mapM rowToBlocksList rows
669+bodyPartToBlocks (Tbl cap grid look parts) = do
670+ captions <- gets docxTableCaptions
671+ fullCaption <- case captions of
672+ c : cs -> do
673+ modify (\s -> s { docxTableCaptions = cs })
674+ return c
675+ [] -> return $ if T.null cap then mempty else plain (text cap)
676+ let shortCaption = if T.null cap then Nothing else Just (toList (text cap))
677+ cap' = caption shortCaption fullCaption
678+ (hdr, rows) = splitHeaderRows (firstRowFormatting look) parts
650679
651680 let width = maybe 0 maximum $ nonEmpty $ map rowLength parts
652681 rowLength :: Docx.Row -> Int
653− rowLength (Docx.Row c) = length c
682+ rowLength (Docx.Row _ c) = sum (fmap (\(Docx.Cell gridSpan _ _) -> fromIntegral gridSpan) c)
654683
655− let toRow = Pandoc.Row nullAttr . map simpleCell
656− toHeaderRow l = [toRow l | not (null l)]
684+ headerCells <- rowsToRows hdr
685+ bodyCells <- rowsToRows rows
657686
658− -- pad cells. New Text.Pandoc.Builder will do that for us,
659− -- so this is for compatibility while we switch over.
660− let cells' = map (\row -> toRow $ take width (row ++ repeat mempty)) cells
661−
662− hdrCells <- case hdr of
663− Just r' -> toHeaderRow <$> rowToBlocksList r'
664− Nothing -> return []
665−
666− -- The two following variables (horizontal column alignment and
667− -- relative column widths) go to the default at the
668− -- moment. Width information is in the TblGrid field of the Tbl,
669− -- so should be possible. Alignment might be more difficult,
670− -- since there doesn't seem to be a column entity in docx.
687+ -- Horizontal column alignment goes to the default at the moment. Getting
688+ -- it might be difficult, since there doesn't seem to be a column entity
689+ -- in docx.
671690 let alignments = replicate width AlignDefault
672691 totalWidth = sum grid
673692 widths = (\w -> ColWidth (fromInteger w / fromInteger totalWidth)) <$> grid
674693
675694 return $ table cap'
676695 (zip alignments widths)
677− (TableHead nullAttr hdrCells)
678− [TableBody nullAttr 0 [] cells']
696+ (TableHead nullAttr headerCells)
697+ [TableBody nullAttr 0 [] bodyCells]
679698 (TableFoot nullAttr [])
680699bodyPartToBlocks (OMathPara e) =
681700 return $ para $ displayMath (writeTeX e)
682701
683−
684702-- replace targets with generated anchors.
685703rewriteLink' :: PandocMonad m => Inline -> DocxContext m Inline
686704rewriteLink' l@(Link attr ils (T.uncons -> Just ('#',target), title)) = do
@@ -716,6 +734,8 @@ bodyToOutput :: PandocMonad m => Body -> DocxContext m (Meta, [Block])
716734bodyToOutput (Body bps) = do
717735 let (metabps, blkbps) = sepBodyParts bps
718736 meta <- bodyPartsToMeta metabps
737+ captions <- catMaybes <$> mapM bodyPartToTableCaption blkbps
738+ modify (\s -> s { docxTableCaptions = captions })
719739 blks <- smushBlocks <$> mapM bodyPartToBlocks blkbps
720740 blks' <- rewriteLinks $ blocksToDefinitions $ blocksToBullets $ toList blks
721741 blks'' <- removeOrphanAnchors blks'
src/Text/Pandoc/Readers/Docx/Parse.hs+100−13
@@ -33,7 +33,9 @@ module Text.Pandoc.Readers.Docx.Parse ( Docx(..)
3333 , ParStyle
3434 , CharStyle(cStyleData)
3535 , Row(..)
36+ , TblHeader(..)
3637 , Cell(..)
38+ , VMerge(..)
3739 , TrackedChange(..)
3840 , ChangeType(..)
3941 , ChangeInfo(..)
@@ -50,6 +52,7 @@ module Text.Pandoc.Readers.Docx.Parse ( Docx(..)
5052 , pHeading
5153 , constructBogusParStyleData
5254 , leftBiasedMergeRunStyle
55+ , rowsToRowspans
5356 ) where
5457import Text.Pandoc.Readers.Docx.Parse.Styles
5558import Codec.Archive.Zip
@@ -225,6 +228,7 @@ defaultParagraphStyle = ParagraphStyle { pStyle = []
225228data BodyPart = Paragraph ParagraphStyle [ParPart]
226229 | ListItem ParagraphStyle T.Text T.Text (Maybe Level) [ParPart]
227230 | Tbl T.Text TblGrid TblLook [Row]
231+ | TblCaption ParagraphStyle [ParPart]
228232 | OMathPara [Exp]
229233 deriving Show
230234
@@ -236,12 +240,61 @@ newtype TblLook = TblLook {firstRowFormatting::Bool}
236240defaultTblLook :: TblLook
237241defaultTblLook = TblLook{firstRowFormatting = False}
238242
239−newtype Row = Row [Cell]
240− deriving Show
243+data Row = Row TblHeader [Cell] deriving Show
244+
245+data TblHeader = HasTblHeader | NoTblHeader deriving (Show, Eq)
241246
242−newtype Cell = Cell [BodyPart]
247+data Cell = Cell GridSpan VMerge [BodyPart]
243248 deriving Show
244249
250+type GridSpan = Integer
251+
252+data VMerge = Continue
253+ -- ^ This cell should be merged with the one above it
254+ | Restart
255+ -- ^ This cell should not be merged with the one above it
256+ deriving (Show, Eq)
257+
258+rowsToRowspans :: [Row] -> [[(Int, Cell)]]
259+rowsToRowspans rows = let
260+ removeMergedCells = fmap (filter (\(_, Cell _ vmerge _) -> vmerge == Restart))
261+ in removeMergedCells (foldr f [] rows)
262+ where
263+ f :: Row -> [[(Int, Cell)]] -> [[(Int, Cell)]]
264+ f (Row _ cells) acc = let
265+ spans = g cells Nothing (listToMaybe acc)
266+ in spans : acc
267+
268+ g ::
269+ -- | The current row
270+ [Cell] ->
271+ -- | Number of columns left below
272+ Maybe Integer ->
273+ -- | (rowspan so far, cell) for the row below this one
274+ Maybe [(Int, Cell)] ->
275+ -- | (rowspan so far, cell) for this row
276+ [(Int, Cell)]
277+ g cells _ Nothing = zip (repeat 1) cells
278+ g cells columnsLeftBelow (Just rowBelow) =
279+ case cells of
280+ [] -> []
281+ thisCell@(Cell thisGridSpan _ _) : restOfRow -> case rowBelow of
282+ [] -> zip (repeat 1) cells
283+ (spanSoFarBelow, Cell gridSpanBelow vmerge _) : _ ->
284+ let spanSoFar = case vmerge of
285+ Restart -> 1
286+ Continue -> 1 + spanSoFarBelow
287+ columnsToDrop = thisGridSpan + (gridSpanBelow - fromMaybe gridSpanBelow columnsLeftBelow)
288+ (newColumnsLeftBelow, restOfRowBelow) = dropColumns columnsToDrop rowBelow
289+ in (spanSoFar, thisCell) : g restOfRow (Just newColumnsLeftBelow) (Just restOfRowBelow)
290+
291+ dropColumns :: Integer -> [(a, Cell)] -> (Integer, [(a, Cell)])
292+ dropColumns n [] = (n, [])
293+ dropColumns n cells@((_, Cell gridSpan _ _) : otherCells) =
294+ if n < gridSpan
295+ then (gridSpan - n, cells)
296+ else dropColumns (n - gridSpan) otherCells
297+
245298leftBiasedMergeRunStyle :: RunStyle -> RunStyle -> RunStyle
246299leftBiasedMergeRunStyle a b = RunStyle
247300 { isBold = isBold a <|> isBold b
@@ -587,14 +640,31 @@ elemToRow ns element | isElem ns "w" "tr" element =
587640 do
588641 let cellElems = findChildrenByName ns "w" "tc" element
589642 cells <- mapD (elemToCell ns) cellElems
590− return $ Row cells
643+ let hasTblHeader = maybe NoTblHeader (const HasTblHeader)
644+ (findChildByName ns "w" "trPr" element
645+ >>= findChildByName ns "w" "tblHeader")
646+ return $ Row hasTblHeader cells
591647elemToRow _ _ = throwError WrongElem
592648
593649elemToCell :: NameSpaces -> Element -> D Cell
594650elemToCell ns element | isElem ns "w" "tc" element =
595651 do
652+ let properties = findChildByName ns "w" "tcPr" element
653+ let gridSpan = properties
654+ >>= findChildByName ns "w" "gridSpan"
655+ >>= findAttrByName ns "w" "val"
656+ >>= stringToInteger
657+ let vMerge = case properties >>= findChildByName ns "w" "vMerge" of
658+ Nothing -> Restart
659+ Just e ->
660+ fromMaybe Continue $ do
661+ s <- findAttrByName ns "w" "val" e
662+ case s of
663+ "continue" -> Just Continue
664+ "restart" -> Just Restart
665+ _ -> Nothing
596666 cellContents <- mapD (elemToBodyPart ns) (elChildren element)
597− return $ Cell cellContents
667+ return $ Cell (fromMaybe 1 gridSpan) vMerge cellContents
598668elemToCell _ _ = throwError WrongElem
599669
600670elemToParIndentation :: NameSpaces -> Element -> Maybe ParIndentation
@@ -626,10 +696,9 @@ pNumInfo = getParStyleField numInfo . pStyle
626696elemToBodyPart :: NameSpaces -> Element -> D BodyPart
627697elemToBodyPart ns element
628698 | isElem ns "w" "p" element
629− , (c:_) <- findChildrenByName ns "m" "oMathPara" element =
630− do
631− expsLst <- eitherToD $ readOMML $ showElement c
632− return $ OMathPara expsLst
699+ , (c:_) <- findChildrenByName ns "m" "oMathPara" element = do
700+ expsLst <- eitherToD $ readOMML $ showElement c
701+ return $ OMathPara expsLst
633702elemToBodyPart ns element
634703 | isElem ns "w" "p" element
635704 , Just (numId, lvl) <- getNumInfo ns element = do
@@ -647,13 +716,31 @@ elemToBodyPart ns element
647716 Nothing | Just (numId, lvl) <- pNumInfo parstyle -> do
648717 levelInfo <- lookupLevel numId lvl <$> asks envNumbering
649718 return $ ListItem parstyle numId lvl levelInfo parparts
650− _ -> return $ Paragraph parstyle parparts
719+ _ -> let
720+ hasCaptionStyle = elem "Caption" (pStyleId <$> pStyle parstyle)
721+
722+ hasSimpleTableField = fromMaybe False $ do
723+ fldSimple <- findChildByName ns "w" "fldSimple" element
724+ instr <- findAttrByName ns "w" "instr" fldSimple
725+ pure ("Table" `elem` T.words instr)
726+
727+ hasComplexTableField = fromMaybe False $ do
728+ instrText <- findElementByName ns "w" "instrText" element
729+ pure ("Table" `elem` T.words (strContent instrText))
730+
731+ in if hasCaptionStyle && (hasSimpleTableField || hasComplexTableField)
732+ then return $ TblCaption parstyle parparts
733+ else return $ Paragraph parstyle parparts
734+
651735elemToBodyPart ns element
652736 | isElem ns "w" "tbl" element = do
653− let caption' = findChildByName ns "w" "tblPr" element
737+ let tblProperties = findChildByName ns "w" "tblPr" element
738+ caption = fromMaybe "" $ tblProperties
654739 >>= findChildByName ns "w" "tblCaption"
655740 >>= findAttrByName ns "w" "val"
656− caption = fromMaybe "" caption'
741+ description = fromMaybe "" $ tblProperties
742+ >>= findChildByName ns "w" "tblDescription"
743+ >>= findAttrByName ns "w" "val"
657744 grid' = case findChildByName ns "w" "tblGrid" element of
658745 Just g -> elemToTblGrid ns g
659746 Nothing -> return []
@@ -666,7 +753,7 @@ elemToBodyPart ns element
666753 grid <- grid'
667754 tblLook <- tblLook'
668755 rows <- mapD (elemToRow ns) (elChildren element)
669− return $ Tbl caption grid tblLook rows
756+ return $ Tbl (caption <> description) grid tblLook rows
670757elemToBodyPart _ _ = throwError WrongElem
671758
672759lookupRelationship :: DocumentLocation -> RelId -> [Relationship] -> Maybe Target
src/Text/Pandoc/Readers/Docx/Util.hs+7
@@ -19,6 +19,7 @@ module Text.Pandoc.Readers.Docx.Util (
1919 , elemToNameSpaces
2020 , findChildByName
2121 , findChildrenByName
22+ , findElementByName
2223 , findAttrByName
2324 ) where
2425
@@ -56,6 +57,12 @@ findChildrenByName ns pref name el =
5657 let ns' = ns <> elemToNameSpaces el
5758 in findChildren (elemName ns' pref name) el
5859
60+-- | Like 'findChildrenByName', but searches descendants.
61+findElementByName :: NameSpaces -> Text -> Text -> Element -> Maybe Element
62+findElementByName ns pref name el =
63+ let ns' = ns <> elemToNameSpaces el
64+ in findElement (elemName ns' pref name) el
65+
5966findAttrByName :: NameSpaces -> Text -> Text -> Element -> Maybe Text
6067findAttrByName ns pref name el =
6168 let ns' = ns <> elemToNameSpaces el
test/Tests/Readers/Docx.hs+16
@@ -317,14 +317,30 @@ tests = [ testGroup "document"
317317 "tables with lists in cells"
318318 "docx/table_with_list_cell.docx"
319319 "docx/table_with_list_cell.native"
320+ , testCompare
321+ "a table with a header which contains rowspans greater than 1"
322+ "docx/table_header_rowspan.docx"
323+ "docx/table_header_rowspan.native"
320324 , testCompare
321325 "tables with one row"
322326 "docx/table_one_row.docx"
323327 "docx/table_one_row.native"
328+ , testCompare
329+ "tables with just one row, which is a header"
330+ "docx/table_one_header_row.docx"
331+ "docx/table_one_header_row.native"
324332 , testCompare
325333 "tables with variable width"
326334 "docx/table_variable_width.docx"
327335 "docx/table_variable_width.native"
336+ , testCompare
337+ "tables with captions which contain a Table field"
338+ "docx/table_captions_with_field.docx"
339+ "docx/table_captions_with_field.native"
340+ , testCompare
341+ "tables with captions which don't contain a Table field"
342+ "docx/table_captions_no_field.docx"
343+ "docx/table_captions_no_field.native"
328344 , testCompare
329345 "code block"
330346 "docx/codeblock.docx"
test/docx/sdt_elements.native+6−7
@@ -4,17 +4,16 @@
44 ,(AlignDefault,ColWidth 0.22069570301081556)
55 ,(AlignDefault,ColWidth 0.5586085939783689)]
66 (TableHead ("",[],[])
7− [])
8− [(TableBody ("",[],[]) (RowHeadColumns 0)
9− []
10− [Row ("",[],[])
7+ [Row ("",[],[])
118 [Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
129 [Plain [Strong [Str "col1Header"]]]
1310 ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
1411 [Plain [Strong [Str "col2Header"]]]
1512 ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
16− [Plain [Strong [Str "col3Header"]]]]
17− ,Row ("",[],[])
13+ [Plain [Strong [Str "col3Header"]]]]])
14+ [(TableBody ("",[],[]) (RowHeadColumns 0)
15+ []
16+ [Row ("",[],[])
1817 [Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
1918 [Plain [Str "col1",Space,Str "content"]]
2019 ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
@@ -22,4 +21,4 @@
2221 ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
2322 [Plain [Str "col3",Space,Str "content"]]]])]
2423 (TableFoot ("",[],[])
25− [])]
24+ [])]
test/docx/table_captions_no_field.docxnew file

Binary file not shown.

test/docx/table_captions_no_field.nativenew file+34
@@ -0,0 +1,34 @@
1+[Para [Str "See",Space,Str "Table",Space,Str "5.1."]
2+,Para [Str "Table",Space,Str "5.1"]
3+,Table ("",[],[]) (Caption Nothing
4+ [])
5+ [(AlignDefault,ColWidth 0.7605739372523825)
6+ ,(AlignDefault,ColWidth 0.11971303137380876)
7+ ,(AlignDefault,ColWidth 0.11971303137380876)]
8+ (TableHead ("",[],[])
9+[Row ("",[],[])
10+ [Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
11+ []
12+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
13+ [Plain [Str "Count"]]
14+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
15+ [Plain [Str "%"]]]])
16+ [(TableBody ("",[],[]) (RowHeadColumns 0)
17+ []
18+ [Row ("",[],[])
19+ [Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
20+ [Plain [Str "First",Space,Str "option"]]
21+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
22+ [Plain [Str "242"]]
23+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
24+ [Plain [Str "45"]]]
25+,Row ("",[],[])
26+ [Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
27+ [Plain [Str "Second",Space,Str "option"]]
28+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
29+ [Plain [Str "99"]]
30+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
31+ [Plain [Str "18"]]]])]
32+ (TableFoot ("",[],[])
33+ [])
34+,Header 2 ("section", [], []) []]
test/docx/table_captions_with_field.docxnew file

Binary file not shown.

test/docx/table_captions_with_field.nativenew file+54
@@ -0,0 +1,54 @@
1+[Para [Str "See",Space,Str "Table",Space,Str "1."]
2+,Para []
3+,Table ("",[],[]) (Caption Nothing
4+ [Para [Str "Table",Space,Str "1"]])
5+ [(AlignDefault,ColWidth 0.7605739372523825)
6+ ,(AlignDefault,ColWidth 0.11971303137380876)
7+ ,(AlignDefault,ColWidth 0.11971303137380876)]
8+ (TableHead ("",[],[])
9+[Row ("",[],[])
10+ [Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
11+ []
12+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
13+ [Plain [Str "Count"]]
14+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
15+ [Plain [Str "%"]]]])
16+ [(TableBody ("",[],[]) (RowHeadColumns 0)
17+ []
18+ [Row ("",[],[])
19+ [Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
20+ [Plain [Str "First",Space,Str "option"]]
21+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
22+ [Plain [Str "242"]]
23+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
24+ [Plain [Str "45"]]]
25+,Row ("",[],[])
26+ [Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
27+ [Plain [Str "Second",Space,Str "option"]]
28+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
29+ [Plain [Str "99"]]
30+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
31+ [Plain [Str "18"]]]])]
32+ (TableFoot ("",[],[])
33+ [])
34+,Header 2 ("section", [], []) []
35+,Table ("",[],[]) (Caption Nothing
36+ [Para [Str "Table",Space,Str "2"]])
37+ [(AlignDefault,ColWidth 0.3332963620230701)
38+ ,(AlignDefault,ColWidth 0.3332963620230701)
39+ ,(AlignDefault,ColWidth 0.3334072759538598)]
40+ (TableHead ("",[],[])
41+ [Row ("",[],[])
42+ [Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
43+ [Plain [Str "One"]]
44+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
45+ [Plain [Str "Two"]]
46+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
47+ [Plain [Str "Three"]]]])
48+ [(TableBody ("",[],[]) (RowHeadColumns 0)
49+ []
50+ [])]
51+ (TableFoot ("",[],[])
52+ [])
53+,Para []
54+,Para [Str "See",Space,Str "Table",Space,Str "2."]]
test/docx/table_header_rowspan.docxnew file

Binary file not shown.

test/docx/table_header_rowspan.nativenew file+189
@@ -0,0 +1,189 @@
1+[Table ("",[],[]) (Caption Nothing
2+ [])
3+ [(AlignDefault,ColWidth 0.30701754385964913)
4+ ,(AlignDefault,ColWidth 0.1364522417153996)
5+ ,(AlignDefault,ColWidth 0.10009746588693957)
6+ ,(AlignDefault,ColWidth 9.707602339181287e-2)
7+ ,(AlignDefault,ColWidth 7.719298245614035e-2)
8+ ,(AlignDefault,ColWidth 7.085769980506823e-2)
9+ ,(AlignDefault,ColWidth 7.09551656920078e-2)
10+ ,(AlignDefault,ColWidth 0.14035087719298245)]
11+ (TableHead ("",[],[])
12+[Row ("",[],[])
13+ [Cell ("",[],[]) AlignDefault (RowSpan 2) (ColSpan 1)
14+ [Plain [Str "A"]]
15+ ,Cell ("",[],[]) AlignDefault (RowSpan 2) (ColSpan 1)
16+ [Plain [Strong [Str "B"]]]
17+ ,Cell ("",[],[]) AlignDefault (RowSpan 2) (ColSpan 1)
18+ [Plain [Strong [Str "C"]]]
19+ ,Cell ("",[],[]) AlignDefault (RowSpan 2) (ColSpan 1)
20+ [Plain [Strong [Str "D"]]]
21+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 3)
22+ [Plain [Str "E"]]
23+ ,Cell ("",[],[]) AlignDefault (RowSpan 2) (ColSpan 1)
24+ [Plain [Str "F"]]]
25+,Row ("",[],[])
26+ [Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
27+ [Plain [Strong [Str "G"]]]
28+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
29+ [Plain [Strong [Str "H"]]]
30+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
31+ [Plain [Strong [Str "I"]]]]])
32+ [(TableBody ("",[],[]) (RowHeadColumns 0)
33+ []
34+ [Row ("",[],[])
35+ [Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
36+ [Plain [Str "1"]]
37+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
38+ [Plain [Str "2"]]
39+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
40+ [Plain [Str "3"]]
41+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
42+ [Plain [Str "4"]]
43+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
44+ [Plain [Str "5"]]
45+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
46+ [Plain [Str "6"]]
47+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
48+ [Plain [Str "7"]]
49+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
50+ [Plain [Str "8"]]]
51+ ,Row ("",[],[])
52+ [Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
53+ [Plain [Str "1"]]
54+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
55+ [Plain [Str "2"]]
56+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
57+ [Plain [Str "3"]]
58+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
59+ [Plain [Str "4"]]
60+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
61+ [Plain [Str "5"]]
62+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
63+ [Plain [Str "6"]]
64+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
65+ [Plain [Str "7"]]
66+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
67+ [Plain [Str "8"]]]
68+ ,Row ("",[],[])
69+ [Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
70+ [Plain [Str "1"]]
71+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
72+ [Plain [Str "2"]]
73+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
74+ [Plain [Str "3"]]
75+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
76+ [Plain [Str "4"]]
77+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
78+ [Plain [Str "5"]]
79+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
80+ [Plain [Str "6"]]
81+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
82+ [Plain [Str "7"]]
83+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
84+ [Plain [Str "8"]]]
85+ ,Row ("",[],[])
86+ [Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
87+ [Plain [Str "1"]]
88+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
89+ [Plain [Str "2"]]
90+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
91+ [Plain [Str "3"]]
92+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
93+ [Plain [Str "4"]]
94+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
95+ [Plain [Str "5"]]
96+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
97+ [Plain [Str "6"]]
98+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
99+ [Plain [Str "7"]]
100+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
101+ [Plain [Str "8"]]]
102+ ,Row ("",[],[])
103+ [Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
104+ [Plain [Str "1"]]
105+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
106+ [Plain [Str "2"]]
107+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
108+ [Plain [Str "3"]]
109+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
110+ [Plain [Str "4"]]
111+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
112+ [Plain [Str "5"]]
113+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
114+ [Plain [Str "6"]]
115+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
116+ [Plain [Str "7"]]
117+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
118+ [Plain [Str "8"]]]
119+ ,Row ("",[],[])
120+ [Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
121+ [Plain [Str "1"]]
122+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
123+ [Plain [Str "2"]]
124+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
125+ [Plain [Str "3"]]
126+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
127+ [Plain [Str "4"]]
128+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
129+ [Plain [Str "5"]]
130+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
131+ [Plain [Str "6"]]
132+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
133+ [Plain [Str "7"]]
134+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
135+ [Plain [Str "8"]]]
136+ ,Row ("",[],[])
137+ [Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
138+ [Plain [Str "1"]]
139+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
140+ [Plain [Str "2"]]
141+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
142+ [Plain [Str "3"]]
143+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
144+ [Plain [Str "4"]]
145+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
146+ [Plain [Str "5"]]
147+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
148+ [Plain [Str "6"]]
149+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
150+ [Plain [Str "7"]]
151+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
152+ [Plain [Str "8"]]]
153+ ,Row ("",[],[])
154+ [Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
155+ [Plain [Str "1"]]
156+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
157+ [Plain [Str "2"]]
158+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
159+ [Plain [Str "3"]]
160+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
161+ [Plain [Str "4"]]
162+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
163+ [Plain [Str "5"]]
164+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
165+ [Plain [Str "6"]]
166+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
167+ [Plain [Str "7"]]
168+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
169+ [Plain [Str "8"]]]
170+ ,Row ("",[],[])
171+ [Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
172+ [Plain [Str "1"]]
173+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
174+ [Plain [Str "2"]]
175+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
176+ [Plain [Str "3"]]
177+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
178+ [Plain [Str "4"]]
179+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
180+ [Plain [Str "5"]]
181+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
182+ [Plain [Str "6"]]
183+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
184+ [Plain [Str "7"]]
185+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
186+ [Plain [Str "8"]]]
187+ ])]
188+ (TableFoot ("",[],[])
189+ [])]
test/docx/table_one_header_row.docxnew file

Binary file not shown.

test/docx/table_one_header_row.nativenew file+18
@@ -0,0 +1,18 @@
1+[Table ("",[],[]) (Caption Nothing
2+ [])
3+ [(AlignDefault,ColWidth 0.33302433371958284)
4+ ,(AlignDefault,ColWidth 0.3332560834298957)
5+ ,(AlignDefault,ColWidth 0.33371958285052145)]
6+ (TableHead ("",[],[])
7+ [Row ("",[],[])
8+ [Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
9+ [Plain [Str "One"]]
10+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
11+ [Plain [Str "Row"]]
12+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
13+ [Plain [Str "Table"]]]])
14+ [(TableBody ("",[],[]) (RowHeadColumns 0)
15+ []
16+ [])]
17+ (TableFoot ("",[],[])
18+ [])]
test/docx/table_one_row.docx

Binary file not shown.

test/docx/table_variable_width.native+7−12
@@ -4,7 +4,8 @@
44 ,(AlignDefault,ColWidth 1.9882415820416888e-2)
55 ,(AlignDefault,ColWidth 0.22202030999465527)
66 ,(AlignDefault,ColWidth 0.4761090326028862)
7− ,(AlignDefault,ColWidth 1.0689470871191876e-4)]
7+ ,(AlignDefault,ColWidth 1.0689470871191876e-4)
8+ ,(AlignDefault,ColWidth 0.26178514163548905)]
89 (TableHead ("",[],[])
910 [Row ("",[],[])
1011 [Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
@@ -13,33 +14,27 @@
1314 []
1415 ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
1516 [Plain [Str "h3"]]
16− ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
17+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 2)
1718 [Plain [Str "h4"]]
1819 ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
1920 [Plain [Str "h5"]]]])
2021 [(TableBody ("",[],[]) (RowHeadColumns 0)
2122 []
2223 [Row ("",[],[])
23− [Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
24+ [Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 3)
2425 [Plain [Str "c11"]]
2526 ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
2627 []
27− ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
28− []
29− ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
30− []
31− ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
28+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 2)
3229 []]
3330 ,Row ("",[],[])
3431 [Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
3532 []
36− ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
33+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 2)
3734 [Plain [Str "c22"]]
3835 ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
3936 [Plain [Str "c23"]]
40− ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
41− []
42− ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 1)
37+ ,Cell ("",[],[]) AlignDefault (RowSpan 1) (ColSpan 2)
4338 []]])]
4439 (TableFoot ("",[],[])
4540 [])]