41 lines
1.8 KiB
Markdown
41 lines
1.8 KiB
Markdown
# 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)
|
|
```python
|
|
# 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:
|
|
```bash
|
|
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.
|