unstructured
25f405c2 - fix(docx): bound the layout grid DOCX tables can declare (#4519)

Commit
26 days ago
fix(docx): bound the layout grid DOCX tables can declare (#4519) ## Summary `w:gridBefore`, `w:gridAfter` and `w:gridSpan` values were expanded into one matrix entry per layout-grid position when building `text_as_html`. A small document declaring millions of positions per row therefore used GB of memory. python-docx's `row.cells` also repeats a spanned cell once per position, and the HTML, nested-table and emphasis paths re-read that cell's content every time. - **Grid budget.** Each table's grid size is computed from the declared values without expanding them, against a document-wide `DOCX_TABLE_MAX_CELLS` budget (new env setting, default 5,000,000). A table over the remaining budget gets `text_as_html=None` and a warning. Its text is still extracted, because `_iter_table_texts` already walks physical `tc` elements. - **Each cell read once.** The HTML path computes each cell's text once per `tc` instead of once per grid position. - **Physical `tc` walk.** Nested-table text and emphasis now walk physical `tc` elements, skipping `vMerge="continue"`, as `_iter_table_texts` already does. Neither expands spans any more. - **Failure containment.** A `w:gridSpan`/`w:gridBefore`/`w:gridAfter` value that can't be parsed omits only that table's `text_as_html`, with a warning, without expanding it or using budget; its text is still extracted. A table paragraph whose `w:b`/`w:i` can't be read contributes no emphasis, with a warning; each paragraph's emphasis is collected before any is emitted, so contents and tags stay aligned. ## Measurements (2-column tables in a ~37 KB DOCX) | Case | Before | After | |---|---|---| | 2 rows, `gridSpan=100000` | 11.2s, 200,000 emphasis entries | 0.4s, 2 entries | | 20 rows, `gridSpan` or `gridBefore` = 5,000,000 | not run (≈100M grid positions) | 0.3s, no `text_as_html`, text extracted | ## Behaviour change `emphasized_text_contents`/`_tags` no longer repeat entries for a cell that spans several grid positions or merges vertically. Previously a bold cell spanning N positions was reported N times. ## Tests - A table declaring 10M positions per row (via `gridSpan` and via `gridBefore`) gets no HTML, but keeps its text and its de-duplicated emphasis. - The budget is summed across tables: the first table gets HTML and the second doesn't. - A spanned cell renders as one `colspan` cell and reports its emphasis once. - An invalid or missing `gridSpan` value keeps the table's text, omits its HTML and uses none of the budget, while a later table still gets HTML. - Unreadable bold/italic values drop that paragraph's emphasis, with and without `infer_table_structure`, and extraction continues. - A vertically merged cell's emphasis is reported once, and a 10M-position span inside a nested table is not expanded. ODT and DOC go through this code after conversion to DOCX. Their tests weren't run locally, because they need pandoc and LibreOffice. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/Unstructured-IO/unstructured/pull/4519?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Author
Parents
Loading