Files
hermes-skills/skills/legal/contract-editor/references/merge-layered-revisions-with-priority.md
T

4.2 KiB

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):

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):

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

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

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:

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.