From 284ebad6af5cf3a306e4191e95ee68da2775cc0c Mon Sep 17 00:00:00 2001 From: bm Date: Wed, 23 Sep 2026 07:57:21 -0700 Subject: [PATCH] Round 2 Word bisection: isolate the field-run co-factor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit bisect_03 (fields removed) was the only round-1 file Word opened, and document.xml becomes byte-identical between the full book and bisect_03 once field runs are removed — but the mini book opens with identical PAGEREF fields, so the fields interact with a second factor. Add six candidates that keep the 28 PAGEREF fields while peeling other layers: - 07 drop deep-copied chapter prose (keep titles/bookmarks/fields) - 08 drop numbering properties - 09 inline hyperlink wrappers - 10 remove the whole TOC section (cleaner no-fields control) - 11 rewrite all PAGEREF targets to a single bookmark - 12 keep fields only for the first 3 TOC entries --- tests/make_bisect_files.py | 91 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/tests/make_bisect_files.py b/tests/make_bisect_files.py index 8ea0371..312cee3 100644 --- a/tests/make_bisect_files.py +++ b/tests/make_bisect_files.py @@ -41,6 +41,12 @@ NAMES = [ 'bisect_04_no_styles.docx', 'bisect_05_first8.docx', 'bisect_06_no_images.docx', + 'bisect_07_no_prose.docx', + 'bisect_08_no_numbering.docx', + 'bisect_09_no_hyperlinks.docx', + 'bisect_10_no_toc_section.docx', + 'bisect_11_same_target_fields.docx', + 'bisect_12_first3_toc.docx', ] @@ -116,6 +122,85 @@ def make_no_images(doc): p.getparent().remove(p) +def _sections(doc): + """Return list of [start, end) kid-slices, one per body section, + using the in-paragraph sectPr holder paragraphs as boundaries.""" + body = doc.element.body + holders = [] + for s in body.iter(qn('w:sectPr')): + par = s.getparent().getparent() + if par is not None and par.getparent() is body: + holders.append(par) + kids = [c for c in body if c.tag == qn('w:p')] + secs, start = [], 0 + for h in holders: + hi = kids.index(h) + secs.append((start, hi + 1)) + start = hi + 1 + if start < len(kids): + secs.append((start, len(kids))) + return kids, secs + + +def make_no_prose(doc): + """Drop the deep-copied chapter prose (sections 5..32) while keeping each + chapter's title paragraph (with its bookmark) and section holder.""" + kids, secs = _sections(doc) + body = doc.element.body + drop = [] + for ci, (a, b) in enumerate(secs): + if 5 <= ci <= 32: # chapters 1..28 + drop.extend(kids[a + 1:b - 1]) # keep title [a], holder [b-1] + for k in drop: + body.remove(k) + + +def make_no_numbering(doc): + """Remove all list-numbering properties (keeps fields).""" + for np_ in list(doc.element.body.iter(qn('w:numPr'))): + np_.getparent().remove(np_) + + +def make_no_hyperlinks(doc): + """Remove w:hyperlink wrappers, inlining their runs (keeps fields).""" + for hl in list(doc.element.body.iter(qn('w:hyperlink'))): + par = hl.getparent() + anchor = hl + for r in list(hl): + par.addnext(r) + par.remove(hl) + + +def make_no_toc_section(doc): + """Remove the whole TOC section (heading + entries + its section holder).""" + kids, secs = _sections(doc) + body = doc.element.body + a, b = secs[4] # TOC section + for k in kids[a:b]: + body.remove(k) + + +def make_same_target_fields(doc): + """Rewrite every PAGEREF target to _UF_ch_0 (uniform targets, fields kept).""" + for it in doc.element.body.iter(qn('w:instrText')): + if (it.text or '').strip().startswith('PAGEREF'): + it.text = ' PAGEREF _UF_ch_0 ' + + +def make_first3_toc(doc): + """Keep only the first 3 TOC entries' fields; plain-text the rest.""" + seen = 0 + for p in doc.element.body.iter(qn('w:p')): + if p.find(".//" + qn('w:instrText')) is None: + continue + if seen < 3: + 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, @@ -123,6 +208,12 @@ MAKERS = [ make_no_styles, make_first8, make_no_images, + make_no_prose, + make_no_numbering, + make_no_hyperlinks, + make_no_toc_section, + make_same_target_fields, + make_first3_toc, ]