From 4f6b2ffdea5817f5ccc3729444bc1ee5a265b8b0 Mon Sep 17 00:00:00 2001 From: chuenchen309 <48723787+chuenchen309@users.noreply.github.com> Date: Sun, 19 Jul 2026 03:16:56 +0800 Subject: [PATCH] fix(html): stop a header-only rowspan table from crashing the backend An HTML table whose only row is a `th` with `rowspan` (no body rows for it to span into) raised `IndexError`. `get_html_table_row_col` counts no rows for an all-header-rowspan row, so `num_rows` is 0 and the grid is empty; the cell-placement `while` loop then reads `grid[row_idx + start_row_span][col_idx]` without a row bound and goes out of range. The write just below it already guards `row_idx + r < num_rows`. Add the same row bound to the read. The cell is kept and the whole document no longer aborts on this table. Co-Authored-By: Claude Opus 4.8 --- docling/backend/html_backend.py | 1 + tests/test_backend_html.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/docling/backend/html_backend.py b/docling/backend/html_backend.py index 42162b429d..35177511bc 100644 --- a/docling/backend/html_backend.py +++ b/docling/backend/html_backend.py @@ -1522,6 +1522,7 @@ def parse_table_data( row_span -= 1 while ( col_idx < num_cols + and row_idx + start_row_span < num_rows and grid[row_idx + start_row_span][col_idx] is not None ): col_idx += 1 diff --git a/tests/test_backend_html.py b/tests/test_backend_html.py index d51ba88d29..fc0141cd15 100644 --- a/tests/test_backend_html.py +++ b/tests/test_backend_html.py @@ -168,6 +168,24 @@ def test_heading_levels(): assert found_lvl_1 and found_lvl_2 +def test_table_header_rowspan_without_body_does_not_crash(): + # A table whose only row is a `th` with rowspan (no body rows to span into) + # used to raise IndexError: get_html_table_row_col counts no rows for an + # all-header-rowspan row, so the grid was empty and the cell-placement read + # went out of bounds. It should not crash and should keep the cell. + src = b"
h
" + in_doc = InputDocument( + path_or_stream=BytesIO(src), + format=InputFormat.HTML, + backend=HTMLDocumentBackend, + filename="t.html", + ) + doc = HTMLDocumentBackend(in_doc=in_doc, path_or_stream=BytesIO(src)).convert() + + assert len(doc.tables) == 1 + assert [cell.text for cell in doc.tables[0].data.table_cells] == ["h"] + + def test_ordered_lists(): test_set: list[tuple[bytes, str]] = []