Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions docling/backend/msword_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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("<eq>", "$").replace("</eq>", "$")

Expand Down
47 changes: 47 additions & 0 deletions tests/test_backend_msword.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"<w:sdt {w}><w:sdtPr/><w:sdtContent>"
"<w:r><w:t>Jane Doe</w:t></w:r></w:sdtContent></w:sdt>"
),
"Customer": (
f'<w:fldSimple {w} w:instr=" MERGEFIELD Customer ">'
"<w:r><w:t>ACME Corp</w:t></w:r></w:fldSimple>"
),
"City": (
f'<w:smartTag {w} w:uri="urn:schemas-microsoft-com:office:smarttags" '
'w:element="City"><w:r><w:t>Paris</w:t></w:r></w:smartTag>'
),
"Amount": (
f'<w:ins {w} w:id="1" w:author="Reviewer">'
"<w:r><w:t>1200</w:t></w:r></w:ins>"
),
}

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,
*,
Expand Down
Loading