round-4 bisection: no-validate / shared-hf-parts / mini-28fields probes

This commit is contained in:
bm
2026-09-23 09:15:20 -07:00
parent 9f08de1e5d
commit 24ee7c34f5

View File

@@ -16,6 +16,10 @@ feature family:
05 first8 - body truncated after the 8th section boundary
06 no_images - drawing paragraphs removed
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
Open each in Word; whichever show the error dialog pinpoint the feature
family at fault.
"""
@@ -54,6 +58,9 @@ NAMES = [
'bisect_16_no_titles.docx',
'bisect_17_page_fields.docx',
'bisect_18_no_dirty.docx',
'bisect_19_no_validate.docx',
'bisect_20_shared_hf_parts.docx',
'bisect_21_mini_28fields.docx',
]
@@ -285,6 +292,72 @@ def make_no_dirty(doc):
rpr.remove(d)
def make_no_validate(doc):
"""Add <w:doNotValidateAgainstSchema/> to settings: Word skips schema
validation on open. If the full book then opens, the failure is a
schema-level violation hit during the dirty-field update/repagination
pass, not a content issue."""
settings = doc.settings.element
el = etree.Element(qn('w:doNotValidateAgainstSchema'))
# canonical CT_Settings order: ... savePreviewPicture,
# doNotValidateAgainstSchema, saveInvalidXml, ... compat, rsids, mathPr
anchor = None
for tag in ('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_shared_hf_parts(doc):
"""Point every section at the first section's 6 header/footer parts, so
the 216 part *package* collapses to 6 referenced parts while keeping the
36-section body structure and dirty fields."""
first = None
for sectPr in doc.element.body.iter(qn('w:sectPr')):
if sectPr.getparent().tag == qn('w:pPr'):
first = sectPr
break
if first is None:
return
mapping = {}
for ref in first:
if ref.tag in (qn('w:headerReference'), qn('w:footerReference')):
mapping[(ref.tag, ref.get(qn('w:type')))] = ref.get(qn('r:id'))
for sectPr in doc.element.body.iter(qn('w:sectPr')):
if sectPr is first:
continue
for ref in list(sectPr):
if ref.tag in (qn('w:headerReference'), qn('w:footerReference')):
key = (ref.tag, ref.get(qn('w:type')))
if key in mapping:
ref.set(qn('r:id'), mapping[key])
def make_mini_28fields(doc):
"""Return a copy of the MINI book (which opens in Word with 3 dirty
PAGEREF fields) with the first TOC entry paragraph replicated 25 more
times -> 28 dirty fields in the mini shell. Tests pure field density:
if this fails while the 3-field mini opens, the trigger is field count
interacting with something, not the full shell."""
mini = Document(os.path.join(HERE, 'word_test_output.docx'))
body = mini.element.body
first_entry = None
for p in body.iter(qn('w:p')):
if p.find(".//" + qn('w:instrText')) is not None:
first_entry = p
break
if first_entry is None:
return mini
idx = list(body).index(first_entry)
for k in range(25):
body.insert(idx + 1 + k, deepcopy(first_entry))
return mini
MAKERS = [
make_no_running,
make_no_oddpage,
@@ -304,6 +377,9 @@ MAKERS = [
make_no_titles,
make_page_fields,
make_no_dirty,
make_no_validate,
make_shared_hf_parts,
make_mini_28fields,
]
@@ -314,7 +390,7 @@ def main():
"Regenerate it via the API first, or pass its path as argv[1].")
for maker, name in zip(MAKERS, NAMES):
doc = Document(base)
maker(doc)
doc = maker(doc) or doc
path = _save(doc, name)
# sanity: it must still open via python-docx
Document(path)