Skip to content

BUG: read_excel(engine="odf") ignores office:value on <table:covered-table-cell> #66579

Description

@oliviercailloux

Pandas version checks

  • I have checked that this issue has not already been reported.

  • I have confirmed this bug exists on the latest version of pandas.

  • I have confirmed this bug exists on the main branch of pandas.

Reproducible Example

from pathlib import Path

import pandas as pd
from odf.opendocument import OpenDocumentSpreadsheet
from odf.table import CoveredTableCell, Table, TableCell, TableRow

path = Path("covered_cell_value.ods")

doc = OpenDocumentSpreadsheet()
sheet = Table(name="Sheet1")
doc.spreadsheet.addElement(sheet)

row0 = TableRow()
sheet.addElement(row0)
row0.addElement(TableCell(valuetype="float", value="1"))
row0.addElement(TableCell(valuetype="float", value="100"))

# A covered cell carrying its own explicit office:value/office:value-type —
# this is exactly what the cells covered by an ODF array/matrix formula
# (table:number-matrix-rows-spanned) hold, per the OpenDocument spec.
row1 = TableRow()
sheet.addElement(row1)
row1.addElement(CoveredTableCell(valuetype="float", value="42"))
row1.addElement(TableCell(valuetype="float", value="200"))

doc.save(str(path))

print(pd.read_excel(path, engine="odf", header=None))
print(pd.read_excel(path, engine="calamine", header=None))


Output:


     0    1
0  1.0  100
1  NaN  200      # engine="odf": the covered cell's value (42) is silently dropped

    0    1
0   1  100
1  42  200       # engine="calamine": reads the same file correctly


(LibreOffice shows correctly the number 42 when opening the file manually.)

Issue Description

read_excel(engine="odf") treats every <table:covered-table-cell> as empty,
without ever inspecting whether it carries its own office:value/
office:value-type. The relevant code, in
pandas/io/excel/_odfreader.py, ODFReader.get_sheet_data:

if sheet_cell.qname == table_cell_name:
    value = self._get_cell_value(sheet_cell)
else:
    value = self.empty_value

Any cell that isn't literally a <table:table-cell> — i.e. any
<table:covered-table-cell> — takes the else branch and is hard-coded to
self.empty_value, regardless of its actual attributes.

This is usually harmless, since a covered cell is normally just a
merged-away placeholder for an ordinary row/column span with no value of its
own. But it's incorrect for the cells covered by an ODF array/matrix
formula
(table:number-matrix-rows-spanned /
table:number-matrix-columns-spanned, OpenDocument v1.2 Part 1, §9.1.5
<table:covered-table-cell>
;
the attribute itself is defined in §19.680, cross-referenced e.g. in
MS-OODF3):
per the OpenDocument spec, those covered cells hold their own evaluated
result, and real spreadsheet applications (verified with LibreOffice:
soffice --headless --convert-to csv recomputes and exports the correct
distinct value for each covered cell) read/recompute them accordingly.

I've confirmed the data is genuinely present and correctly parsed at the
odfpy layer (which pandas' odf engine is built on) — reading the same file
with odfpy directly via cell.getAttrNS(office_ns, "value") returns "42"
for the covered cell — so this is specific to pandas' reader wrapper, not to
odfpy or to how the file was written.

Expected Behavior

read_excel(engine="odf") should read a covered cell's own office:value/
office:value-type when present, the same way engine="calamine" already
does, instead of unconditionally treating it as empty. At minimum, a covered
cell that carries an explicit office:value should not silently lose it.

Installed Versions

pandas          : 3.0.5   (latest PyPI release; also reproduces on 3.0.3)
odfpy           : 1.4.1   (latest PyPI release)
python-calamine : 0.8.2   (latest PyPI release)
python          : 3.13.5
python-bits     : 64
OS              : Linux
OS-release      : 6.12.95+deb13-amd64
machine         : x86_64

(pd.show_versions() itself reports odfpy: None and python-calamine: None
on this install despite both being present and functional — checked directly
via importlib.metadata.version(...).)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions