round-5 bisection: updateFields-path / mini-28-distinct / full-1field probes

This commit is contained in:
bm
2026-09-23 10:43:15 -07:00
parent 24ee7c34f5
commit d2ad88166a

View File

@@ -19,6 +19,9 @@ feature family:
19 no_validate - +w:doNotValidateAgainstSchema in settings.xml
20 shared_hf_parts - every section points at section 1's 6 header/footer parts
21 mini_28fields - the MINI book with the TOC entry replicated to 28 fields
22 updatefields_nodirty - w:dirty off + <w:updateFields w:val="true"/> in settings
23 mini_28distinct - mini with 28 fields at 28 DISTINCT bookmarks
24 full_1field - full book with exactly 1 dirty TOC field
Open each in Word; whichever show the error dialog pinpoint the feature
family at fault.
@@ -61,6 +64,9 @@ NAMES = [
'bisect_19_no_validate.docx',
'bisect_20_shared_hf_parts.docx',
'bisect_21_mini_28fields.docx',
'bisect_22_updatefields_nodirty.docx',
'bisect_23_mini_28distinct.docx',
'bisect_24_full_1field.docx',
]
@@ -358,6 +364,90 @@ def make_mini_28fields(doc):
return mini
def make_updatefields_nodirty(doc):
"""Full book: remove w:dirty from every field run AND set
<w:updateFields w:val="true"/> in settings (Word updates all fields on
open through the settings path instead of per-field dirty flags)."""
for fc in doc.element.body.iter(qn('w:fldChar')):
r = fc.getparent()
rpr = r.find(qn('w:rPr'))
if rpr is not None:
d = rpr.find(qn('w:dirty'))
if d is not None:
rpr.remove(d)
settings = doc.settings.element
el = etree.Element(qn('w:updateFields'))
el.set(qn('w:val'), 'true')
# canonical CT_Settings order: ... alwaysMergeEmptyNamespace, updateFields,
# hdrShapeDefaults, footnotePr, endnotePr, compat, rsids, mathPr
anchor = None
for tag in ('w:hdrShapeDefaults', 'w:footnotePr', 'w:endnotePr',
'w:compat', 'w:rsids', 'w:mathPr'):
anchor = settings.find(qn(tag))
if anchor is not None:
break
if anchor is not None:
anchor.addprevious(el)
else:
settings.append(el)
def make_mini_28_distinct(doc):
"""Return a copy of the MINI book with 28 dirty PAGEREF fields pointing at
28 DISTINCT bookmarks: replicate the first TOC entry 25 more times with
rewritten targets, plus 25 copies of the chapter-1 title paragraph each
carrying a renumbered, renamed bookmark (_UF_ch_3..27). Un-confounds the
same-target multiplicity in bisect_21."""
mini = Document(os.path.join(HERE, 'word_test_output.docx'))
body = mini.element.body
paras = [c for c in body if c.tag == qn('w:p')]
first_entry = None
for p in paras:
if p.find(".//" + qn('w:instrText')) is not None:
first_entry = p
break
title_p = None
for p in paras:
ppr = p.find(qn('w:pPr'))
st = ppr.find(qn('w:pStyle')) if ppr is not None else None
if st is not None and st.get(qn('w:val')) == 'WAChapter':
title_p = p
break
if first_entry is None or title_p is None:
return mini
used = {int(b.get(qn('w:id'))) for b in body.iter(qn('w:bookmarkStart'))}
nid = (max(used) + 1) if used else 0
for k in range(3, 28):
ec = deepcopy(first_entry)
ec.find(".//" + qn('w:instrText')).text = f" PAGEREF _UF_ch_{k} "
body.insert(list(body).index(first_entry) + 1, ec)
tc = deepcopy(title_p)
for bs in tc.iter(qn('w:bookmarkStart')):
bs.set(qn('w:name'), f"_UF_ch_{k}")
bs.set(qn('w:id'), str(nid))
nid += 1
for be in tc.iter(qn('w:bookmarkEnd')):
be.set(qn('w:id'), str(nid))
nid += 1
body.insert(list(body).index(title_p) + 1, tc)
return mini
def make_first1_toc(doc):
"""Full book: keep only the FIRST TOC entry's field; plain-text the rest
(count threshold probe: does full + exactly 1 dirty field open?)."""
seen = 0
for p in doc.element.body.iter(qn('w:p')):
if p.find(".//" + qn('w:instrText')) is None:
continue
if seen < 1:
seen += 1
continue
for r in list(p.findall(qn('w:r'))):
if r.find(qn('w:fldChar')) is not None or r.find(qn('w:instrText')) is not None:
p.remove(r)
MAKERS = [
make_no_running,
make_no_oddpage,
@@ -380,6 +470,9 @@ MAKERS = [
make_no_validate,
make_shared_hf_parts,
make_mini_28fields,
make_updatefields_nodirty,
make_mini_28_distinct,
make_first1_toc,
]