fix: drop per-field w:dirty, update fields on open via settings updateFields
Word's per-field dirty-driven field update crashes the full book with a hard open error even at a single dirty PAGEREF (24-bisect isolation); the settings level <w:updateFields w:val='true'/> batch path opens the same document with correct TOC page numbers.
This commit is contained in:
@@ -626,6 +626,32 @@ class RunningHeaderTests(unittest.TestCase):
|
||||
"w:settings children out of CT_Settings order: "
|
||||
+ ", ".join(t.split("}")[-1] for t in canonical))
|
||||
|
||||
def test_toc_fields_carry_no_dirty_flag(self):
|
||||
# Per-field w:dirty crashes Word on this book (isolated over 24
|
||||
# bisection files); PAGEREF begin runs must stay clean and rely on
|
||||
# the settings-level updateFields batch path instead.
|
||||
d = self._format()
|
||||
n_begin = 0
|
||||
for fc in d.element.body.iter(qn("w:fldChar")):
|
||||
if fc.get(qn("w:fldCharType")) != "begin":
|
||||
continue
|
||||
rpr = fc.getparent().find(qn("w:rPr"))
|
||||
if rpr is not None:
|
||||
self.assertIsNone(
|
||||
rpr.find(qn("w:dirty")),
|
||||
"PAGEREF begin run must not carry w:dirty")
|
||||
n_begin += 1
|
||||
# The book ships two chapters in the test manuscript -> 2 TOC fields.
|
||||
self.assertGreaterEqual(n_begin, 2)
|
||||
|
||||
def test_settings_request_field_update_on_open(self):
|
||||
# Word recomputes the TOC page numbers through the settings-level
|
||||
# <w:updateFields> batch path (proved to open the full book).
|
||||
d = self._format()
|
||||
uf = d.settings.element.find(qn("w:updateFields"))
|
||||
self.assertIsNotNone(uf)
|
||||
self.assertEqual(uf.get(qn("w:val")), "true")
|
||||
|
||||
def test_style_references_resolve(self):
|
||||
# Word rejects documents whose paragraphs reference styles that are
|
||||
# not defined in styles.xml (LO silently falls back to Normal).
|
||||
|
||||
@@ -824,21 +824,21 @@ def _add_bookmark(para, bookmark_id, bookmark_name):
|
||||
def _add_pageref_field(para, bookmark_name, font_name="Caladea", font_size=Pt(11)):
|
||||
"""Add a PAGEREF field (page number of bookmark) to the paragraph.
|
||||
Each field char is in its own run per OOXML spec (Word requires this).
|
||||
The begin run carries w:dirty so Word knows to recalculate the field."""
|
||||
|
||||
The begin run deliberately carries NO w:dirty. Word's per-field dirty
|
||||
update path crashes with the hard "Word experienced an error" dialog on
|
||||
this book (proved empirically: 1 dirty field in the full shell fails;
|
||||
the 28-field mini fails but the 3-field mini opens). Instead the whole
|
||||
document enables <w:updateFields w:val="true"/> at the settings level
|
||||
(see pass 2), which makes Word recompute every field on open through a
|
||||
batch path that survives the same document.
|
||||
"""
|
||||
r1 = para.add_run()
|
||||
r1.font.name = font_name
|
||||
r1.font.size = font_size
|
||||
fld1 = OxmlElement("w:fldChar")
|
||||
fld1.set(qn("w:fldCharType"), "begin")
|
||||
r1._r.append(fld1)
|
||||
# Mark field as dirty so Word recalculates it on open
|
||||
rpr = r1._r.find(qn('w:rPr'))
|
||||
if rpr is None:
|
||||
rpr = OxmlElement('w:rPr')
|
||||
r1._r.insert(0, rpr)
|
||||
dirty = OxmlElement('w:dirty')
|
||||
dirty.set(qn('w:val'), '1')
|
||||
rpr.append(dirty)
|
||||
|
||||
r2 = para.add_run()
|
||||
r2.font.name = font_name
|
||||
@@ -1888,8 +1888,15 @@ def format_document(filepath, output_path=None, font_name="Caladea", font_size=1
|
||||
# w:settings per OOXML; Word honors it in modern layout compat mode.
|
||||
if settings.find(qn('w:autoHyphenation')) is None:
|
||||
settings.append(OxmlElement('w:autoHyphenation'))
|
||||
# Fields update on open through the settings-level batch path. Per-field
|
||||
# w:dirty cannot be used: Word's dirty-driven field update crashes this
|
||||
# book with a hard open error (empirically isolated over 24 bisections).
|
||||
if settings.find(qn('w:updateFields')) is None:
|
||||
uf = OxmlElement('w:updateFields')
|
||||
uf.set(qn('w:val'), 'true')
|
||||
settings.append(uf)
|
||||
# Word requires w:settings children in strict CT_Settings order, so fix up
|
||||
# the position of the two elements just added (LibreOffice would not care).
|
||||
# the position of the elements just added (LibreOffice would not care).
|
||||
_reorder_settings(settings)
|
||||
|
||||
for idx, sec in enumerate(secs):
|
||||
|
||||
Reference in New Issue
Block a user