Repository navigation
Open
Description
Summary
Two problems with ODT, both seen on files Word 365 saves as ODT and reproduced below with minimal hand-made files:
- Nested list numbering continues across parents. A second-level list under item 2 starts at 3 instead of 1:
2.1.renders as2.3.. Word writes every nestedtext:listwithtext:continue-numbering="true"; anydoc appears to continue the level-2 counter from the previous sibling's sublist. In ODF a lower level restarts when a higher level advances (LibreOffice and Word both show2.1.here). For contracts this turns a clause number into a different, existing clause number. office:annotationis inlined into the text. The comment's creator, date and body are pasted into the middle of the word it is anchored to:Lamb+Author2026-01-01T00:00:00COMMENTTEXT+daword.
Reproduction (anydoc 0.2.4)
--- odt-list
1. Subject
- 1.1. Duty A
- 1.2. Duty B
2. Price
- 2.3. Payment
--- odt-annotation
Text with a comment on LambAuthor2026-01-01T00:00:00COMMENTTEXTdaword here.
Expected: 2.1. Payment, and Text with a comment on Lambdaword here. (with the annotation dropped, as DOCX comments are today — #191 — or rendered separately).
The script is the same as in the tracked-changes issue; the relevant part builds an ODT from content.xml only:
repro.py
"""Minimal reproductions for anydoc 0.2.4: tracked changes and ODT lists/annotations."""
import io, zipfile, anydoc
W = 'xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"'
def docx(body):
b = io.BytesIO()
with zipfile.ZipFile(b, "w") as z:
z.writestr("[Content_Types].xml", '<?xml version="1.0"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/></Types>')
z.writestr("_rels/.rels", '<?xml version="1.0"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="r1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/></Relationships>')
z.writestr("word/document.xml", f'<?xml version="1.0"?><w:document {W}><w:body>{body}</w:body></w:document>')
return b.getvalue()
tracked = ('<w:p><w:r><w:t xml:space="preserve">The parties agreed </w:t></w:r>'
'<w:del w:id="1" w:author="A" w:date="2026-01-01T00:00:00Z"><w:r><w:delText xml:space="preserve">OLDTERM </w:delText></w:r></w:del>'
'<w:ins w:id="2" w:author="A" w:date="2026-01-01T00:00:00Z"><w:r><w:t xml:space="preserve">NEWTERM </w:t></w:r></w:ins>'
'<w:r><w:t>terms.</w:t></w:r></w:p>')
rtf = (r"{\rtf1\ansi\deff0{\fonttbl{\f0 Times New Roman;}}{\*\revtbl{Unknown;}{A;}}"
r"\f0 The parties agreed {\deleted\revauthdel1 OLDTERM }{\revised\revauth1 NEWTERM }terms.\par}").encode()
ODT_NS = ('xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" '
'xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" '
'xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" '
'xmlns:dc="http://purl.org/dc/elements/1.1/"')
def odt(text):
styles = (f'<office:automatic-styles><text:list-style style:name="L1">'
'<text:list-level-style-number text:level="1" style:num-format="1" style:num-suffix="."/>'
'<text:list-level-style-number text:level="2" style:num-format="1" style:num-suffix="." text:display-levels="2"/>'
'</text:list-style></office:automatic-styles>')
content = (f'<?xml version="1.0" encoding="UTF-8"?><office:document-content {ODT_NS} office:version="1.2">'
f'{styles}<office:body><office:text>{text}</office:text></office:body></office:document-content>')
b = io.BytesIO()
with zipfile.ZipFile(b, "w") as z:
z.writestr(zipfile.ZipInfo("mimetype"), "application/vnd.oasis.opendocument.text")
z.writestr("META-INF/manifest.xml", '<?xml version="1.0"?><manifest:manifest xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0"><manifest:file-entry manifest:full-path="/" manifest:media-type="application/vnd.oasis.opendocument.text"/><manifest:file-entry manifest:full-path="content.xml" manifest:media-type="text/xml"/></manifest:manifest>')
z.writestr("content.xml", content)
return b.getvalue()
# The shape Word writes when it saves a two-level list as ODT: every nested list
# carries text:continue-numbering="true".
lists = ('<text:list text:style-name="L1" text:continue-numbering="true">'
'<text:list-item><text:p>Subject</text:p><text:list text:continue-numbering="true">'
'<text:list-item><text:p>Duty A</text:p></text:list-item><text:list-item><text:p>Duty B</text:p></text:list-item></text:list></text:list-item>'
'<text:list-item><text:p>Price</text:p><text:list text:continue-numbering="true">'
'<text:list-item><text:p>Payment</text:p></text:list-item></text:list></text:list-item></text:list>')
annot = ('<text:p>Text with a comment on Lamb<office:annotation office:name="0"><dc:creator>Author</dc:creator>'
'<dc:date>2026-01-01T00:00:00</dc:date><text:p>COMMENTTEXT</text:p></office:annotation>daword'
'<office:annotation-end office:name="0"/> here.</text:p>')
from importlib.metadata import version
print("anydoc", version("firecrawl-anydoc"))
for name, data in [("docx", docx(tracked)), ("rtf", rtf), ("odt-list", odt(lists)), ("odt-annotation", odt(annot))]:
path = f"repro-{name}." + name.split("-")[0]
open(path, "wb").write(data)
print(f"--- {name}\n{anydoc.to_markdown(path)}")Metadata
Metadata
Assignees
Labels
No labels
Activity