1.8 KiB
ContractEditor Operation Ordering: Add Clauses Before Renumbering
2026-07-13 练塘硬件购销合同教训
Problem
When interleaving add_clause_before() and tracked_replace() for renumbering, lxml throws:
ValueError: Element is not a child of this node.
in tracked_replace() → parent.remove(runs[idx]).
Root Cause
add_clause_before() / add_clause() mutate the XML tree (insert new <w:p> elements). After insertion, previously-found element references held by later tracked_replace() calls may point to nodes whose parent relationship has shifted. The parent.remove() call inside tracked_replace fails because the run's parent <w:p> is no longer the same object the code expects.
Fix: Two-Phase Approach (铁律)
Phase 1 — All structural additions:
add_clause()/add_clause_before()for new clauses- Content-level
tracked_replace()that don't touch clause titles being renumbered
Phase 2 — Renumbering (after all additions are done):
tracked_replace('第七条不可抗力', '第九条不可抗力')etc.
Example (correct order)
# Phase 1: add new clauses
editor.add_clause_before('第七条 转包与分包\n...', before_search='第七条不可抗力')
editor.add_clause_before('第八条 第三方侵权\n...', before_search='第七条不可抗力')
# Phase 2: renumber old clauses (all additions done)
editor.tracked_replace('第七条不可抗力', '第九条不可抗力')
editor.tracked_replace('第八条争议解决', '第十条争议解决')
WPS/DOC File Handling
WPS .wps and .doc files must be converted to .docx before ContractEditor can process them:
libreoffice --headless --convert-to docx input.wps --outdir /tmp/contract-review/
Then copy to a simple ASCII filename to avoid python-docx path issues with Chinese characters.