Repository navigation
fix(docx): read table rows and cells wrapped in customXml or sdt - #4393
ebrahimeolakey wants to merge 2 commits into
Conversation
Table.rows only yields direct w:tr children of w:tbl, so a row Word wrapped in w:customXml or w:sdt was dropped and num_rows came out short. Walk the table's children and unwrap both wrappers before building the rows; do the same for w:customXml around a w:tc in _row_cells (w:sdt was already handled there). Resolves docling-project#4382 Signed-off-by: ebrahimeolakey <oiver.sham@gmail.com>
|
✅ DCO Check Passed Thanks @ebrahimeolakey, all your commits are properly signed off. 🎉 |
Merge Protections🟢 Merge protection satisfied — ready to merge. Show 1 satisfied protection🟢 Enforce conventional commitMake sure that we follow https://www.conventionalcommits.org/en/v1.0.0/
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
@ebrahimeolakey This fixes #4382 as filed, and I would merge it. Walking w:tbl yourself instead of patching _Row is the right call: docling reads only grid_cols_before and _tr off a row, and both work on a wrapped one, while _Row._index raises AttributeError: 'lxml.etree._Element' object has no attribute 'tr_lst' because the parent is no longer the table.
non-blocking, and it is yours to take or leave: the interaction most likely to regress is a vertical merge whose continuation row is wrapped. It is right on 8bef881 and wrong on the base, so a case would pin what you already fixed. The XML and the two assertions are below if you want them.
$ PYTHONPATH=. ~/.venvs/docling/bin/python repro_4382.py # my four-row case from the issue
2d5c590c 2 x 2 missing: customXml cell D2, customXml row B1, customXml row B2, sdt row C1, sdt row C2
8bef881 4 x 2 missing: none
$ PYTHONPATH=. ~/.venvs/docling/bin/python repro_4393_edges.py
2d5c590c vMerge continuation in w:customXml 2 x 2, 'merged' row_span=1, B2 lost
8bef881 vMerge continuation in w:customXml 3 x 2, 'merged' row_span=2, spanning r0-r2
<w:tr> <w:tc><w:tcPr><w:vMerge w:val="restart"/> ... merged <w:tc> ... A2
<w:customXml w:element="Tagged"><w:tr>
<w:tc><w:tcPr><w:vMerge w:val="continue"/> ... <w:tc> ... B2
<w:tr> <w:tc> ... C1 <w:tc> ... C2
assert data.num_rows == 3
assert next(c for c in data.table_cells if c.text == "merged").row_span == 2
$ PYTHONPATH=. ~/.venvs/docling/bin/python repro_trpr_ins.py
2d5c590c w:tr/w:trPr/w:ins 2 x 2, both inserted cells read
8bef881 w:tr/w:trPr/w:ins 2 x 2, both inserted cells read
$ ~/.venvs/docling/bin/python -m pytest tests/test_backend_msword.py -q
2d5c590c 65 passed, 1 skipped in 66.36s
8bef881 66 passed, 1 skipped in 57.71s
# 32 of the 35 documents under tests/data/docx/sources are compared here: drawingml, textbox and
# test_emf_docx are skipped locally because LibreOffice is not installed.
$ git show 2d5c590c:docling/backend/msword_backend.py > docling/backend/msword_backend.py
$ ~/.venvs/docling/bin/python -m pytest tests/test_backend_msword.py -k wrapped -q # your test kept
> assert data.num_rows == 4
E AssertionError: assert 2 == 4
1 failed, 1 passed, 65 deselected in 2.51s
$ ~/.venvs/docling/bin/ruff check docling/backend/msword_backend.py tests/test_backend_msword.py
All checks passed! # ruff 0.15.12
Collated from two runs per commit on this machine, and no CI result is quoted. ty is a pre-commit hook on docling/*.py and was not run, since it is not installed here.
@dolfim-ibm this touches msword_backend.py, which you and @ceberam merge most often. @ebrahimeolakey, want that vMerge case as a patch against your branch, or would you rather write it?
Suggested in review on docling-project#4393: the continuation row used to be dropped, so the merge ended early and the wrapped row's other cell was lost. Fails on main, passes with the fix. Signed-off-by: ebrahimeolakey <oiver.sham@gmail.com>
|
Thanks for running it against both commits. Wrote the vMerge case myself and pushed it, with the same three-row shape as yours: restart row, continuation row inside |
chrikrah
left a comment
There was a problem hiding this comment.
@ebrahimeolakey Still approve at 8a75684. The wrapped-row test fails on the base backend and passes here, so it guards the merge case from my last review.
$ PYTHONPATH=. ~/.venvs/docling/bin/python -m pytest tests/test_backend_msword.py -q -p no:cacheprovider
8a75684 67 passed, 1 skipped in 71.66s # 8bef881 was 66 passed, 1 skipped
$ git show 2d5c590c:docling/backend/msword_backend.py > docling/backend/msword_backend.py # tests kept
$ PYTHONPATH=. ~/.venvs/docling/bin/python -m pytest tests/test_backend_msword.py -q -p no:cacheprovider -k wrapped
E AssertionError: assert 2 == 4
E AssertionError: assert 2 == 3
2 failed, 1 passed, 65 deselected in 2.90s
$ git merge-tree --write-tree upstream/main 8a75684 # main at ed04377
CONFLICT (content): Merge conflict in docling/backend/msword_backend.py
Main moved under the docx imports, so GitHub shows a conflict. Could you rebase? Keeping both CharacterStyle and _Row resolves it. @PeterStaar-IBM once that is in, this is ready for a look.
Issue resolved by this Pull Request:
Resolves #4382
_handle_tablesread rows through python-docxTable.rows, which only yields directw:trchildren ofw:tbl. A row that Word wrapped inw:customXmlor in a content control (w:sdt) was never visited, so its text was lost andnum_rowscame out short. Cells had the same gap forw:customXml; thew:sdtcell case was already handled by_row_cells(#3951).Changes:
_table_rows()returns a table'sw:trelements in document order, unwrappingw:customXmlandw:sdt/w:sdtContenton the way down._handle_tablesbuilds its_Rowobjects from that list instead ofTable.rows, sogrid_cols_beforeand the rest of the row handling are unchanged._row_cells()now also unwrapsw:customXmlaround aw:tc.customXmlrow,sdtrow, plain row with acustomXmlcell) and checks the 4×2 grid cell by cell.Result on the issue's reproduction:
2 x 2with the last row showing onlyplain D1before,4 x 2with every cell in place after.This is the "extension of
_row_cellsplus a row-level equivalent" option from the issue. It does not overlap with #4369: that change is in_walk_linearat body level, this one is in_row_cells/_handle_tables, and the two touch different parts ofmsword_backend.py.tests/test_backend_msword.py: 66 passed, 1 skipped (LibreOffice not installed). No groundtruth files changed.Checklist: