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,
*,