Summary
Loading an existing ODF document with load() and saving it produces a META-INF/manifest.xml containing two <manifest:file-entry manifest:full-path="/"> entries. LibreOffice then reports the resulting file as "damaged … repair?". Only the root / entry is duplicated; all other entries are correct.
Version
- odfpy 1.4.2 (PyPI / openSUSE
python313-odfpy)
- Python 3.13.13, Linux
- Confirmed against LibreOffice 26.2
Minimal reproduction (self-contained, no external file)
from odf.opendocument import OpenDocumentText, load
from odf.text import P
import zipfile
d = OpenDocumentText()
d.text.addElement(P(text="hello"))
d.save("min1.odt") # manifest: 1x full-path="/" (OK)
load("min1.odt").save("min2.odt") # manifest: 2x full-path="/" (BUG)
def roots(p):
return zipfile.ZipFile(p).read("META-INF/manifest.xml").decode().count('full-path="/"')
print(roots("min1.odt"), roots("min2.odt")) # -> 1 2
min2.odt opens in LibreOffice with a "the file is corrupt and cannot be opened … repair?" dialog.
Expected vs actual
- Expected: exactly one root
file-entry after a round-trip.
- Actual: two
full-path="/" entries → LibreOffice flags the file as corrupt.
Root cause
In load() (opendocument.py, ~L1004–1008), every manifest entry that isn't otherwise handled is appended to doc._extra as an OpaqueObject. The manifest's root entry full-path="/" falls into this else branch (its path ends in /), so it is added to _extra.
On save, __manifestxml() emits the root entry itself (opendocument.py L672: FileEntry(fullpath="/", mediatype=...)) and re-emits the copy collected in _extra → duplicate.
Suggested fix
Skip the root entry when populating _extra during load, e.g. guard the else branch with and mentry != "/", so the root is produced only once by __manifestxml().
Impact
Any load → modify → save workflow (e.g. filling an existing template) produces files LibreOffice considers corrupt.
Summary
Loading an existing ODF document with
load()and saving it produces aMETA-INF/manifest.xmlcontaining two<manifest:file-entry manifest:full-path="/">entries. LibreOffice then reports the resulting file as "damaged … repair?". Only the root/entry is duplicated; all other entries are correct.Version
python313-odfpy)Minimal reproduction (self-contained, no external file)
min2.odtopens in LibreOffice with a "the file is corrupt and cannot be opened … repair?" dialog.Expected vs actual
file-entryafter a round-trip.full-path="/"entries → LibreOffice flags the file as corrupt.Root cause
In
load()(opendocument.py, ~L1004–1008), every manifest entry that isn't otherwise handled is appended todoc._extraas anOpaqueObject. The manifest's root entryfull-path="/"falls into thiselsebranch (its path ends in/), so it is added to_extra.On save,
__manifestxml()emits the root entry itself (opendocument.pyL672:FileEntry(fullpath="/", mediatype=...)) and re-emits the copy collected in_extra→ duplicate.Suggested fix
Skip the root entry when populating
_extraduring load, e.g. guard theelsebranch withand mentry != "/", so the root is produced only once by__manifestxml().Impact
Any load → modify → save workflow (e.g. filling an existing template) produces files LibreOffice considers corrupt.