diff --git a/docling/backend/msword_backend.py b/docling/backend/msword_backend.py index c72b4db478..75b8063b16 100644 --- a/docling/backend/msword_backend.py +++ b/docling/backend/msword_backend.py @@ -2044,6 +2044,18 @@ def _get_paragraph_text(self, paragraph: Paragraph) -> str: for text, _format, _hyperlink in self._iter_paragraph_content(paragraph) ) + def _get_cell_text(self, cell: _Cell) -> str: + """Return a table cell's text, one line per paragraph. + + ``_Cell.text`` only reads runs and hyperlinks that are direct children + of a paragraph, so a cell would lose the text of an inline content + control, a simple field such as a mail-merge field, a smart tag or a + tracked insertion. Read each paragraph the way body paragraphs are read. + """ + return "\n".join( + self._get_paragraph_text(paragraph) for paragraph in cell.paragraphs + ) + def _get_paragraph_elements(self, paragraph: Paragraph): """ Extract paragraph elements along with their formatting and hyperlink @@ -3301,13 +3313,14 @@ def _handle_tables( continue cell = _Cell(tc, table) + cell_text = self._get_cell_text(cell) # Detect equations in cell text text, equations = self._handle_equations_in_text( - element=cell._element, text=cell.text + element=cell._element, text=cell_text ) if len(equations) == 0: - text = cell.text + text = cell_text else: text = text.replace("", "$").replace("", "$") diff --git a/tests/test_backend_msword.py b/tests/test_backend_msword.py index 42401639a5..fede25419e 100644 --- a/tests/test_backend_msword.py +++ b/tests/test_backend_msword.py @@ -906,6 +906,53 @@ def test_block_sdt_tables_are_extracted(): assert phase_2_idx < table_idxs[1] +def test_table_cell_keeps_text_of_wrapped_runs(tmp_path): + """A plain table cell keeps runs nested in inline wrappers. + + Body paragraphs already read the runs inside content controls, simple + fields, smart tags and tracked insertions; a plain cell dropped them, so a + form filled in with content controls came out with empty answers. + """ + w = 'xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"' + wrapped_runs = { + "Name": ( + f"" + "Jane Doe" + ), + "Customer": ( + f'' + "ACME Corp" + ), + "City": ( + f'Paris' + ), + "Amount": ( + f'' + "1200" + ), + } + + document = Document() + table = document.add_table(rows=len(wrapped_runs), cols=2) + for row_idx, (label, wrapped_run) in enumerate(wrapped_runs.items()): + table.cell(row_idx, 0).text = label + table.cell(row_idx, 1).paragraphs[0]._p.append(etree.fromstring(wrapped_run)) + + doc = _convert_built(document, tmp_path) + + assert [cell.text for cell in doc.tables[0].data.table_cells] == [ + "Name", + "Jane Doe", + "Customer", + "ACME Corp", + "City", + "Paris", + "Amount", + "1200", + ] + + def _table_with_grid_before( tmp_path, *,