Skip to content

fix(xlsx): clip merged-cell and hyperlink ranges while loading workbooks - #4979

Merged
PeterStaar-IBM merged 2 commits into
mainfrom
fix/xlsx-clip-merged-ranges
Oct 23, 2026
Merged

PeterStaar-IBM merged 2 commits into
mainfrom
fix/xlsx-clip-merged-ranges

Conversation

@dolfim-ibm

Copy link
Copy Markdown
Member

Summary

While openpyxl loads a worksheet, it creates one MergedCell object for each covered cell of a merged range, and one cell with its own Hyperlink copy for each cell of a hyperlink range. A hyperlink also sets its target as the value of each empty cell, so these cells become table data. One short element such as <mergeCell ref="A1:XFD1048576"/> or <hyperlink ref="A1:J100000"/> makes openpyxl and the table detection do work for millions of cells.

Changes

  • Load workbooks through openpyxl's ExcelReader, with per-workbook budgets of 100,000 merged cells and 10,000 hyperlink cells.
  • Ranges within the budget load unchanged. A range that does not fit is clipped to the cells with values in its worksheet, and to its top-left cell if it still does not fit. A warning is logged.
  • The hooks (_clean_merge_range, iter_rows, iter_cols) are set on the worksheet instances of this load only and removed afterwards. No openpyxl class or module attribute changes.

Review note

The hooks rely on openpyxl 3.1.x internals (ExcelReader.read_worksheets, Workbook.create_sheet, Worksheet._clean_merge_range). The tests with a whole-sheet merged range and a large hyperlink range measure the memory, so they fail if a future openpyxl version stops calling these hooks.

Tests

  • A whole-sheet merged range converts with bounded memory and keeps the merged content.
  • A merged range partly beyond the data is clipped; normal merged cells keep their spans.
  • A large hyperlink range converts with bounded memory and keeps the link on its top-left cell; small hyperlink ranges are unchanged.
  • The existing XLSX ground-truth tests are unchanged.

openpyxl creates one MergedCell object for each covered cell of a merged
range while it loads a worksheet. A single short <mergeCell> element such
as A1:XFD1048576 can make it create billions of objects, and the table
detection then widens to the same range.

Load workbooks through openpyxl's ExcelReader with a per-workbook budget
of covered merged cells. Ranges within the budget load unchanged. A range
that does not fit is clipped to the cells with values in its worksheet,
and to its top-left cell if it still does not fit. The hook is set on the
workbook and worksheet instances of this load only.

Signed-off-by: Michele Dolfi <dol@zurich.ibm.com>
openpyxl binds a ranged <hyperlink ref> by looking up the range on the
worksheet. This creates a cell and a Hyperlink copy for every cell of the
range, and sets the link target as the value of each empty cell, so the
cells also become table data. A short element such as A1:J100000 makes
openpyxl and the table detection do work for one million cells.

Clip the range lookups that openpyxl does while a worksheet loads with
the same approach as merged ranges, and a separate budget of 10,000
hyperlink cells per workbook. Ranges within the budget load unchanged.
The worksheet methods are shadowed on the instances of this load only.

Signed-off-by: Michele Dolfi <dol@zurich.ibm.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ DCO Check Passed

Thanks @dolfim-ibm, all your commits are properly signed off. 🎉

@mergify

mergify Bot commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

Merge Protections

🟢 Merge protection satisfied — ready to merge.

Show 1 satisfied protection

🟢 Enforce conventional commit

Make sure that we follow https://www.conventionalcommits.org/en/v1.0.0/

  • title ~= ^(fix|feat|docs|style|refactor|perf|test|build|ci|chore|revert)(?:\(.+\))?(!)?:

@github-actions github-actions Bot added bug Something isn't working xlsx issue related to xlsx backend labels Oct 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

AI triage (advisory)

Checked commit 9f4f486398. The analysis read the diff only. It did not run code from this pull request.

Warning

The AI analysis did not finish. Only the deterministic checks are shown.

Topic labels added: bug, xlsx.

CI safety: not assessed.

Possibly related pull requests

PR State Signal Assessment
#4977 open changes the same code not assessed

The ai:* labels are advisory. They do not approve CI and they do not add tests:full. A new push runs this check again.

@codecov

codecov Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 85.71429% with 14 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
docling/backend/msexcel_backend.py 85.71% 9 Missing and 5 partials ⚠️

📢 Thoughts on this report? Let us know!

@PeterStaar-IBM
PeterStaar-IBM merged commit a63ec34 into main Oct 23, 2026
33 checks passed
@PeterStaar-IBM
PeterStaar-IBM deleted the fix/xlsx-clip-merged-ranges branch October 23, 2026 06:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working xlsx issue related to xlsx backend

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants