75 lines
7.9 KiB
Markdown
75 lines
7.9 KiB
Markdown
# 诉讼文书的修订态技法(contract-editor 库在合同以外文书上的复用)
|
|
|
|
ContractEditor 库不止用于合同——审/改诉讼文书(监督申请书、起诉状、答辩状等)同样适用。本文记录 2026-06-23 邹家民事诉讼监督申请书审改中验证过的几招,都用 OnlyOffice x2t(Doro/Maggie 实际引擎)渲染核对过。
|
|
|
|
## 1. 大段改写用「整块 del+ins」,不用字符级 diff(markup 可读性铁律)
|
|
`tracked_replace` 是字符级 diff(CJK 每字一 token + difflib)——**补字/小改**(错别字、补一个"在"字、称谓换词)用它,markup 干净。
|
|
但**大段改写**(整句重写、换论证)若用字符级 diff,新旧文本大量字符重合,markup 会交错成一团("未经~~及~~法庭审理""一百二十八条~~切~~国家机关"),Doro 在 OnlyOffice 看修订态根本读不下去。**接受修订后的最终文本虽正确,但修订态不可读 = 不合格交付**(Doro 有格式洁癖,看的就是 markup)。
|
|
- **正解**:对整句/整段改写,做「整块删 + 整块插」——`[<w:del>旧整句</w:del>][<w:ins>新整句</w:ins>]`,markup 显示为一条删除线旧句紧跟一条下划线新句,清清楚楚。
|
|
- 实现:复制 `tracked_replace` 的定位逻辑,但不跑 difflib,直接 `_mk_del(old_text)` + `_mk_ins(new_text)` 整块插。判据:**新旧文本相似度高、改动跨度大 → 整块;纯增删几个字 → 字符级**。
|
|
|
|
## 2. 称谓/词替换也要整词块替换,别让共享字符碎裂
|
|
把"法**庭**"改"莲都法**院**"时,"法"字共享,字符级 diff 会渲染成"莲都法~~庭~~院"(接受后对,markup 脏)。
|
|
- **正解**:整词 `tracked_block_replace("本案法庭向申请人送达", "莲都法院向申请人送达")` → markup 是干净的[删旧短语][插新短语]。
|
|
- 同理坑:替换前先分类全文每处目标词——actor 指代(要改)vs 法条/术语原文(如"法庭审理""在法庭上出示"=不能动)。grep 出所有命中,逐个判,别一刀切 replace_all。
|
|
|
|
## 3. 整段删除(让自动编号重排)——库没有,需自加 `tracked_delete_paragraph`
|
|
合并两个自动编号请求项(删一项、后项自动续号)时,要的是**段落级修订删除**:段内每个 run 包进 `<w:del>`,**且段落标记也要标删**——在 `pPr/rPr` 里插一个 `<w:del>`。这样接受修订后整段连段落标记一起消失,自动编号从 一二三四 重排成 一二三。
|
|
```python
|
|
def tracked_delete_paragraph(self, search_text):
|
|
p = self.find_para(search_text)
|
|
for r in list(p.findall(qn('r'))):
|
|
# 每个run的w:t搬进新建<w:del><w:r><w:delText>
|
|
...
|
|
ppr = p.find(qn('pPr')) or 新建
|
|
rpr = ppr.find(qn('rPr')) or 新建
|
|
rpr.insert(0, <w:del author=... date=...>) # 段落标记删除标记
|
|
```
|
|
缺了"段落标记删除"那一步,接受后会残留一个空的编号项。
|
|
|
|
## 4. 半角括号→全角:改 numbering.xml 的 lvlText,一次性根治
|
|
子标题 `(一)(二)…` 是自动编号时,半角括号来自 `word/numbering.xml` 里 `<w:lvlText w:val="(%1)">`。逐段改文档没用(那是渲染出来的)。
|
|
- **正解**:遍历 numbering.xml 所有 `<w:lvlText>`,`val` 里的 `(`→`(`、`)`→`)`,一次改全文所有同源编号。本例 37 处 lvlText 一次改完。
|
|
- 注意只动含 `()` 的 lvlText,`、`分隔的(如请求"一、二、三"用 `%1、`)不受影响。
|
|
|
|
## 5. 引号"统一为仿宋全角"——根因是引号 run 的字体不是中文字体
|
|
现象:正文中文是仿宋(继承样式),但弯引号 `“”`(U+201C/U+201D) 的 run `ascii=Times New Roman, eastAsia=None`。因为弯引号是**中西文模糊字符**,OnlyOffice 对没有 eastAsia 设定的字符按 ascii 字体渲染 → 引号显示成西文 Times 的粗重样式,和仿宋正文不协调。
|
|
- **正解(彻底版)**:对**纯引号/中文 run**,把 rFonts 的 `ascii/eastAsia/hAnsi/cs` 全设为「仿宋」+ `hint="eastAsia"`,消除歧义。对**引号+数字混排 run**(如 `“2026…`),按字符**拆 run**:引号段走仿宋、数字段保留 Times New Roman。
|
|
- 只设 eastAsia 不够稳——某些渲染下仍可能按 ascii 走 Times。纯引号 run 连 ascii 一起设仿宋最保险(数字 run 才需要保留 Times)。
|
|
- 核对:OnlyOffice x2t 渲染后裁剪含引号区域,确认引号纤细、与仿宋协调(不是又粗又重的衬线引号)。
|
|
|
|
## 6. 验证三件套(同合同终审,文书一样适用)
|
|
- `ed.validate()` 返回空。**注意**:诉讼文书的「请求项」原文常是加粗的(与合同正文不加粗体例不同),validate 的"不应加粗"规则会**误报**——先读原文该段普通 run 的 `<w:b>` 状态,若原文请求项本就加粗、INS 继承同样加粗=格式一致=误报,可放行。
|
|
- 逐个 `w:ins` 核 author 正确(诉讼文书署当前文书归属人,如本例 Doro 文书上署"小Maggie"修订;合同历史署"WB"——按文书归属定)、eastAsia 字体不缺。
|
|
- OnlyOffice x2t 渲两版:**修订态**(看 markup 干净)+ **接受态**(删 w:del、解包 w:ins、删段落标记被删的空段后重渲,看编号连续、全角括号生效、无乱码)。接受态自己生成:解包所有 ins、删所有 del、删 numPr 空段。
|
|
|
|
## 一句话
|
|
合同库的修订能力对所有 docx 文书通用;诉讼文书审改的差异点是:①大改写要整块 del+ins 保 markup 可读 ②引号/括号这类「字体/编号源」问题改 styles/numbering 层不改文档层 ③validate 加粗规则对加粗请求项会误报。
|
|
|
|
## 7. 接受所有修订 → 干净版 docx(反向操作,2026-06-24 徐函任务验证)
|
|
用户给一份**带修订痕迹+批注**的 docx,要「先接受现有修订、让我看干净版本」时——不是用 Word 手点"接受全部",用 zipfile+lxml 一次处理:
|
|
```python
|
|
W='http://schemas.openxmlformats.org/wordprocessingml/2006/main'
|
|
def w(t): return f'{{{W}}}'+t
|
|
# 1) w:del → 整个元素删掉(连 delText 一起没)
|
|
for d in root.findall('.//'+w('del')): d.getparent().remove(d)
|
|
# 2) w:ins → 解包:用其子元素替换它本身(保留插入内容,去掉ins包裹)
|
|
for ins in root.findall('.//'+w('ins')):
|
|
parent=ins.getparent(); idx=list(parent).index(ins)
|
|
for child in reversed(list(ins)): parent.insert(idx, child)
|
|
parent.remove(ins)
|
|
# 3) 属性变更追踪一并清:pPrChange/rPrChange/sectPrChange/tblPrChange/tcPrChange/trPrChange
|
|
for tag in ('pPrChange','rPrChange','sectPrChange','tblPrChange','tcPrChange','trPrChange'):
|
|
for el in root.findall('.//'+w(tag)): el.getparent().remove(el)
|
|
# 4) 批注三处一起拆(用户要"干净版"= 连批注也清):
|
|
# document.xml: 删 commentRangeStart/End,删含 commentReference 的整个 run
|
|
# settings.xml: 删 <w:trackRevisions/>(让文件退出跟踪模式)
|
|
# 打包时跳过 word/comments*.xml,并从 [Content_Types].xml 和 document.xml.rels 删 comments 的 Override/Relationship
|
|
```
|
|
要点:
|
|
- **w:del 删整块、w:ins 解包**——方向别搞反(del 是要丢弃的,ins 是要保留的)。
|
|
- **务必清 settings.xml 的 trackRevisions**,否则文件仍处于"跟踪修订"模式,用户继续编辑会又开始记修订。
|
|
- **批注要三处协同删**(comments.xml 本体 + document.xml 的 range/reference 锚点 + Content_Types/rels 注册),漏一处 OnlyOffice/Word 打开可能报损坏。
|
|
- 验证:解包后 `root.findall('.//w:ins')`/`w:del`/`w:commentReference` 全为 0;`'word/comments.xml' in zip.namelist()` 为 False;python-docx 能打开;OnlyOffice x2t 渲染核对无修订痕迹无批注无错位。
|
|
- vision 核干净版时顺带抓**残留内部标记**:黄色高亮(内部校对标记)、留白占位(编号"第 号"、日期" 日")、标题英文双连字符`--`应为中文破折号`——`——这些不是修订痕迹但属"未清的内部审核稿"特征,正式交付前要清。
|