Repository navigation
Conversation
Add each table to the document first and then add its cells one by one with `add_table_cell`. A cell can then refer to an item whose parent is the table, as a rich table cell needs. A small `_cell_prov` helper builds the provenance of a cell range, which was written out three times. The output does not change. Signed-off-by: chaiwon.hwang <chaiwon.hwang.dev@gmail.com>
Excel 365 can place an image in a cell (Insert > Pictures > Place in Cell). Such an image is not a drawing: Excel stores it as a rich value in `xl/richData`, and the cell keeps only the error value `#VALUE!` as a fallback for older readers. openpyxl does not read rich values, so the image was lost and the cell came out as the text `#VALUE!`. The backend now follows the rich-value chain from the cell's `vm` attribute through `xl/metadata.xml`, `rdrichvalue.xml`, `rdrichvaluestructure.xml` and `richValueRel.xml` to the image. Only the `_localImage` structure is read; other rich values, such as linked data types, are left as they are. - An image cell in a table becomes a `RichTableCell` with empty text, which refers to a `PictureItem` whose parent is the table. - With `treat_singleton_as_text`, a lone image cell becomes a picture. - The alternative text that Excel stores with the image becomes the picture description (`meta.description`). The archive is read only when the workbook relationships name all the rich-data parts, so other workbooks do not change. Signed-off-by: chaiwon.hwang <chaiwon.hwang.dev@gmail.com>
Add `xlsx_10_place_in_cell.xlsx` with its groundtruth. The "Products" sheet has two images placed in cells, each with alternative text, and a plain `#N/A` error cell without an image. The "Logo" sheet has one image cell without alternative text, which uses the same media file as one of the cells on the first sheet. The tests check that an image cell becomes a `RichTableCell` that refers to a picture under the table, that the alternative text becomes the picture description, that an ordinary error value keeps its text, and that a lone image cell becomes a picture with `treat_singleton_as_text`. Excel opened and saved a copy of a file built the same way and kept the images in their cells. Signed-off-by: chaiwon.hwang <chaiwon.hwang.dev@gmail.com>
|
✅ DCO Check Passed Thanks @uommou, all your commits are properly signed off. 🎉 |
Merge Protections🔴 1 of 2 protections blocking · waiting on 👀 reviews
🔴 Require two reviewer for test updatesWaiting for
This rule is failing.When test data is updated, we require two reviewers
Show 1 satisfied protection🟢 Enforce conventional commitMake sure that we follow https://www.conventionalcommits.org/en/v1.0.0/
|
AI triage (advisory)Checked commit The PR adds support for images placed in Excel cells via the 'Place in Cell' feature by reading the rich-value chain ( CI safety: no concern found. A maintainer must still approve CI. 1 related pull request(s) in the same code
Reference data changes
Assessment: expected. The three added groundtruth files correspond to the new The |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
DanielNg0729
left a comment
There was a problem hiding this comment.
Hi @uommou, thank you for this PR. The rich-value chain is well explained, and it reads the images correctly in my tests. I found one case that loses an image, and I have one small question about the tree shape.
An image next to a section label is dropped. The PR now clears #VALUE! from image cells, so _split_leading_section_label no longer counts an image cell as content. When row 0 has a merged label and an image, the label is split off as text, and the data keeps only rows > 0. The image cell and its picture disappear:
import xlsxwriter
workbook = xlsxwriter.Workbook("section_image.xlsx")
sheet = workbook.add_worksheet()
sheet.merge_range("A1:B1", "Fruit list")
sheet.embed_image("C1", "icon.png", {"description": "Section icon"})
sheet.write_row("A2", ["name", "qty", "note"])
sheet.write_row("A3", ["apple", 1, "x"])
sheet.write_row("A4", ["pear", 2, "y"])
workbook.close()| Result | |
|---|---|
main |
one 4x3 table; C1 is #VALUE! |
| this PR | text "Fruit list" and a 3x3 table; no picture |
| with the change below | one 4x3 table; C1 is a RichTableCell with the picture |
Would it make sense to count image cells as content there? This change fixed it for me, and tests/test_backend_msexcel.py still passes (33 passed, 2 skipped):
first_row_text_cells = [
cell for cell in first_row_cells if cell.text.strip() or cell.image is not None
]A small test for this case could help too. Thanks!
An image cell has no text, so the section-label split did not count it as content. A merged label and an image in the first row made the label split off, and the image cell was lost. Count image cells as content. Signed-off-by: chaiwon.hwang <chaiwon.hwang.dev@gmail.com>
|
Thanks @DanielNg0729 for the review. |
Excel 365 can place an image in a cell (Insert → Pictures → Place in Cell).
Excel stores such an image as a rich value in
xl/richData, not as a drawing.The cell keeps only the error value
#VALUE!as a fallback. openpyxl does notread rich values, so the XLSX backend lost the image and wrote
#VALUE!intothe table.
This PR follows the rich-value chain from the cell's
vmattribute to theimage:
RichTableCellwith empty text. It refersto a
PictureItemwhose parent is the table, like images in table cells inthe DOCX and ODF backends.
description (
meta.description).treat_singleton_as_text, a lone image cell becomes a picture, not thetext
#VALUE!.#N/A, keeps its text.Issue resolved by this Pull Request:
Resolves #5014
Changes
The PR has three commits:
refactor(xlsx): add the table before its cells: the table is added first,then its cells one by one with
add_table_cell, so that a cell can refer toan item under the table. A
_cell_provhelper replaces three copies of theprovenance code. The output does not change.
fix(xlsx): read images placed in cells: the rich-data reader.openpyxl has already parsed. A workbook without all four parts
(
sheetMetadata,rdRichValue,rdRichValueStructure,richValueRel)opens no extra archive member, so other workbooks do not change.
vmattribute, so the sheet XML is read againwith
iterparse. This occurs only for a sheet that has error cells in aworkbook with rich data. Parsed cells and rows are removed during the
parse, so memory does not grow with the sheet.
ExcelCellwhen the cell is read, so the table,the singleton and the section-label paths all see the same data. The
#VALUE!text is removed there, in one place.EMF/WMF image falls back to the existing LibreOffice conversion.
options.
test(xlsx): cover images placed in cells: fixture, groundtruth and tests.RichTableCell.refpoints to thePictureItemdirectly, without a group. AnExcel cell holds one image and nothing else, so a group would hold one item.
docling-core accepts this, because the picture's parent is the table.
Not in scope
IMAGE()function or linked data types. Their cells keep
#VALUE!, as before.Checklist:
Review evidence:
Before and after, for a workbook saved by Excel 365 with two images placed in
A1:A2(the sample file in the issue):doc.picturesTableCell#VALUE!× 2RichTableCell× 2, each referring to a picture under the tablemeta.description: "Cow in pasture", "Basketball player dribbling ball on court"New fixture
tests/data/xlsx/sources/xlsx_10_place_in_cell.xlsx:and number cells, and an
#N/Aerror cell without an image.file as a cell on the first sheet.
third-party content. Its structure is the same as the Excel 365 sample. Excel
for Mac (16.110) opened and saved a copy of a file built the same way, kept
the images in their cells, and docling reads that saved copy the same way.
Document tree of the fixture (
.itxtgroundtruth):Exports: the fixture was written to every CLI output format (
md,json,yaml,html,html_split_page,text,doctags,vtt,doclang,dclx,latex,chunks) with theplaceholder,embeddedandreferencedimagemodes. This used the same
DoclingDocumentmethods asdocling convert; theCLI itself was not run.
RichTableCell.refresolves.<td><figure>in HTML,<fcel><picture>in DocTags,<picture>with<description>in DocLang.embedded, the image data is in the cell. Withreferenced, the linkedfiles exist.
#VALUE!.Reference data: only the new fixture's groundtruth is added. No existing
groundtruth changes.
Tests and checks:
uv run pytest tests/test_backend_msexcel.py: 35 passed.main(3 failures) and passwith this change.
groundtruth.
prek run --from-ref upstream/main, thehooks of
make validate): passed (Ruff, ty, Tach, license headers, max lines).ty check: the same warnings asmainfor the changed files, no new ones.make testsuite. Only the XLSX backend changed.