# Merge Layered Revisions with Priority (Accept Inner Author's Edits) ## Scenario (2026-07-03 模特合作协议案) File has two layers of tracked changes: - **Layer 1 (WB)**: Original review modifications - **Layer 2 (华诚-Z)**: User edited on top of WB's tracked changes Result: 华诚-Z's `w:del` elements are **nested inside** WB's `w:ins` elements — meaning 华诚-Z deleted portions of what WB had inserted. User instruction: "以华诚-Z为准" (prioritize 华诚-Z), then unify all author names to WB. ## Three-Step Algorithm ### Step 1: Accept nested deletions (inner author wins) Find all `w:del[author=华诚-Z]` nested inside `w:ins[author=WB]` and remove them (= accept the deletion): ```python def accept_nested_deletions(body, inner_author='华诚-Z', outer_author='WB'): for ins_elem in body.findall(f'.//{W}ins'): if ins_elem.get(f'{W}author') != outer_author: continue for del_elem in ins_elem.findall(f'.//{W}del'): if del_elem.get(f'{W}author') == inner_author: parent = del_elem.getparent() parent.remove(del_elem) ``` ### Step 2: Remove empty outer elements After accepting nested deletions, some WB ins elements may be empty (all their content was deleted by 华诚-Z): ```python def remove_empty_ins(body): for ins_elem in body.findall(f'.//{W}ins'): has_text = False for t in ins_elem.findall(f'.//{W}t'): if t.text and t.text.strip(): has_text = True break if not has_text: parent = ins_elem.getparent() if parent is not None: parent.remove(ins_elem) ``` ### Step 3: Unify author names ```python def rename_author(body, old_author, new_author): count = 0 for elem in body.iter(): author = elem.get(f'{W}author') if author == old_author: elem.set(f'{W}author', new_author) count += 1 return count ``` ## Complete Flow ```python from docx import Document from lxml import etree doc = Document('input.docx') body = doc.element.body # Step 1: Accept 华诚-Z deletions of WB content accept_nested_deletions(body, inner_author='华诚-Z', outer_author='WB') # Step 2: Clean up empty WB ins elements remove_empty_ins(body) # Step 3: Rename 华诚-Z → WB rename_author(body, '华诚-Z', 'WB') doc.save('output.docx') ``` ## After Merge: Additional Modifications After merging, you can continue adding new WB tracked changes on the unified file (e.g., reverting specific clauses to template wording). Use standard tracked change creation: ```python def make_del(text, rPr=None, author='WB', date='2026-07-03T06:00:00Z'): d = etree.Element(f'{W}del') d.set(f'{W}id', str(abs(hash(text)) % 100000)) d.set(f'{W}author', author) d.set(f'{W}date', date) r = etree.SubElement(d, f'{W}r') if rPr is not None: r.append(deepcopy(rPr)) dt = etree.SubElement(r, f'{W}delText') dt.set('{http://www.w3.org/XML/1998/namespace}space', 'preserve') dt.text = text return d def make_ins(text, rPr=None, author='WB', date='2026-07-03T06:00:00Z'): ins = etree.Element(f'{W}ins') ins.set(f'{W}id', str(abs(hash(text + 'ins')) % 100000)) ins.set(f'{W}author', author) ins.set(f'{W}date', date) r = etree.SubElement(ins, f'{W}r') if rPr is not None: r.append(deepcopy(rPr)) t = etree.SubElement(r, f'{W}t') t.set('{http://www.w3.org/XML/1998/namespace}space', 'preserve') t.text = text return ins ``` ## Verification After merge: - `set(elem.get(W+'author') for elem in body.iter() if elem.get(W+'author'))` should return `{'WB'}` only - Count ins/del elements to confirm reasonable numbers - Verify key clauses read correctly in "accepted" view ## Key Distinction from `unify-author-wb.py` The `scripts/unify-author-wb.py` script **only renames authors** — it does NOT handle nested deletions. If 华诚-Z has `w:del` inside WB's `w:ins`, just running unify will rename the del to WB but **leave the deleted content still marked as deleted inside the insertion** — creating a confusing state where WB appears to both insert and delete the same text. **Always run the three-step algorithm** when inner author has modified outer author's tracked changes.