feat: export core Hermes skills
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
# 法律意见书审查要点(Legal Opinion Review Checklist)
|
||||
|
||||
审查法律意见书(区别于诉讼文书审核)时,除六维度通用检查外,重点关注以下逻辑和专业性问题:
|
||||
|
||||
## 1. 定性力度与法律后果匹配
|
||||
|
||||
| 情形 | 正确表述 | 错误表述 |
|
||||
|------|----------|----------|
|
||||
| 法律明文禁止 | "不得""法律禁止""违反…的规定" | "不建议""存在风险" |
|
||||
| 风险提示(无明确禁止规范) | "建议审慎评估""合规风险较高" | "不得""法律禁止" |
|
||||
| 操作建议 | "建议…""如…则应…" | 混用禁止性语言 |
|
||||
|
||||
**2026-07-11实证**:苏州新东方学校(非营利性民办非企业单位)从事广告发布,《暂行条例》第25条明确规定处罚后果(警告→撤销登记+没收+1-3倍罚款),属于"法定禁止"而非"风险提示"。意见书原稿用"不建议"被纠正为"不得"。
|
||||
|
||||
**规则**:引用了处罚条款的,定性语言必须匹配处罚力度。有明确法律后果的违法行为,不用"建议"语气。
|
||||
|
||||
## 2. 前后逻辑自洽
|
||||
|
||||
常见矛盾模式:
|
||||
- **先宽后严**:开头说"合作范围可以更宽泛",后文却逐项收窄到几乎不能做。如果结论是从严,开头不要先给宽松预期。
|
||||
- **并列理由实质重复**:第1点和第3点说的是同一件事的不同法条表述(如"非营利性不得经营"和"办学结余全部用于办学"),应合并为一个论点+补强法条,不分列。
|
||||
- **结论与理由脱节**:给了"可行"的结论,但理由中列举的限制条件实际上使该合作不具可操作性。
|
||||
|
||||
**检查方法**:读完全文后,把每个平台/主体的"整体评价"单独提出来,核对与下文具体分析是否一致。
|
||||
|
||||
## 3. 法条引用完整性
|
||||
|
||||
法律意见书中引法条的标准:
|
||||
- 不止引"定义条款"(如第2条),还要引"法律后果条款"(如第25条处罚规定)
|
||||
- 引用链:性质认定(第X条)→ 禁止性规定(第Y条)→ 违反后果(第Z条),三者形成闭环
|
||||
- 涉及非营利性组织的,《民促法》和《暂行条例》要交叉引用(前者是教育法体系专门规范,后者是民非单位通用规范)
|
||||
|
||||
## 4. 主体信息准确
|
||||
|
||||
- 统一社会信用代码必须正确(首位5=民政登记社会组织、9=工商登记企业)
|
||||
- 经营范围/业务范围引用须与公示信息逐字一致
|
||||
- 主体性质定性(有限公司/民办非企业单位/个体工商户)影响适用法律,必须准确
|
||||
|
||||
## 5. 格式/编号统一
|
||||
|
||||
- 建议编号:同一层级用相同编号体系(1)2)3)或1. 2. 3.,不混用)
|
||||
- 错别字高危区:"个人信息"不写"信人信息"、"广告发布"不写"广告发不"
|
||||
|
||||
## 6. 实操建议可行性
|
||||
|
||||
- 给出的合规路径必须在法律上可行(如建议"由A公司签约承担B学校的广告发布者责任"——需核查广告发布者是事实认定还是合同约定,穿透风险)
|
||||
- 民办非企业单位变更业务范围须经业务主管单位+登记管理机关双重审批,不同于企业法人的工商变更
|
||||
- 标明审批难度和不确定性,给客户完整预期
|
||||
@@ -0,0 +1,101 @@
|
||||
# 法条引用核查 — 一手原文逐款验证(recipe + 已核样本)
|
||||
|
||||
来源事件:邹家民事诉讼监督申请书(2026-06-23)。Doro 两次纠正:先"你再认真核对第五十一条是第几款",再"铁证只能是完整的法律规定,而不能是别人的引用"。我犯过的错:(a) 据搜索摘要误判款项,把对的改成错的;(b) 拿律所解读当铁证;(c) 误抓 2001 旧版核现行版。
|
||||
|
||||
## 为什么摘要不可信
|
||||
多个权威站点(gongbao.court.gov.cn 最高法公报、sipf、ipc.court.gov.cn 知产法庭)在 web_search/web_extract 返回里,会把**法条开头的款省略**,直接从中间某款显示。第五十一条第一款"举证期限可以由当事人协商,并经人民法院准许"在所有摘要里都不见了,导致"指定举证期限…十五日"看起来像第一款,实为第二款。
|
||||
|
||||
## 核查 recipe(curl 抓原始 HTML,绕过摘要层)
|
||||
```bash
|
||||
# 1. 抓原始页面(不经 web_extract 的摘要)
|
||||
curl -s --max-time 25 "<官方/权威全文URL>" -A "Mozilla/5.0" | iconv -f utf-8 -t utf-8 -c > /tmp/law.html
|
||||
|
||||
# 2. 正则切目标条,看原文换行/缩进数款
|
||||
python3 -c "
|
||||
import re
|
||||
html=open('/tmp/law.html',encoding='utf-8',errors='ignore').read()
|
||||
txt=re.sub(r'<[^>]+>','',html); txt=re.sub(r'&[a-z#0-9]+;','',txt)
|
||||
m=re.search(r'第五十一条(.*?)第五十二条', txt, re.S) # 改成目标条号
|
||||
seg=m.group(1)
|
||||
# 不要先把空白全删——款与款之间的换行/全角空格( )就是分款标志
|
||||
print(repr(seg[:800])) # 看 \n 和 \u3000 位置判断分款
|
||||
"
|
||||
```
|
||||
- 关键:**不要 `re.sub(r'\s+','')` 把换行抹掉再读**,款的边界就藏在换行里。知产法庭 HTML 里每款以 `\n\n ` 另起,最清楚。
|
||||
- **两个独立一手源交叉验证**,款数与字句都一致才采信。
|
||||
- 抓到后**先核版本**:看页面的施行日期/"根据XXXX年…修正"字样,确认是现行有效版而非旧版。
|
||||
|
||||
## 已核样本(现行有效,本案用到,可直接复用)
|
||||
|
||||
### 最高法《关于民事诉讼证据的若干规定》(2019修正,2020-5-1施行)第五十一条 — 三款
|
||||
- **第一款**:举证期限可以由当事人协商,并经人民法院准许。
|
||||
- **第二款**:人民法院指定举证期限的,适用第一审普通程序审理的案件不得少于十五日,当事人提供新的证据的第二审案件不得少于十日。适用简易程序审理的案件不得超过十五日,小额诉讼案件的举证期限一般不得超过七日。
|
||||
- **第三款**:举证期限届满后,当事人提供反驳证据或者对已经提供的证据的来源、形式等方面的瑕疵进行补正的,人民法院可以酌情再次确定举证期限,该期限不受前款规定的期间限制。
|
||||
- → "不得少于十五日"在**第二款**。注意:2001 旧版第五十一条是"质证顺序"(原告出示→被告质证…),完全不同,别抓错。
|
||||
|
||||
### 民事诉讼法(2023修正,现行)常用条
|
||||
- **第十四条**:人民检察院有权对民事诉讼实行法律监督。
|
||||
- **第六十七条**:当事人对自己提出的主张,有责任提供证据(谁主张谁举证)。
|
||||
- **第七十一条**:证据应当在法庭上出示,并由当事人互相质证。对涉及国家秘密、商业秘密和个人隐私的证据应当保密,需要在法庭出示的,不得在公开开庭时出示。(质证以庭审出示为前提的真正依据)
|
||||
- **第一百二十九条**:人民法院对决定受理的案件,应当在受理案件通知书和应诉通知书中向当事人告知有关的诉讼权利义务,或者口头告知。
|
||||
- **第一百三十七条**:人民法院审理民事案件,除涉及国家秘密、个人隐私或者法律另有规定的以外,应当公开进行。离婚案件,涉及商业秘密的案件,当事人申请不公开审理的,可以不公开审理。→ **这是"公开审理原则",不能用来支撑"开庭→举证→质证的程序顺序"(错配)**。
|
||||
|
||||
### 宪法(2018修正)
|
||||
- **第五条**:实行依法治国,建设社会主义法治国家;一切国家机关都必须遵守宪法和法律,任何组织或者个人都不得有超越宪法和法律的特权。("职权法定"最直接的锚)
|
||||
- **第一百二十八条**:中华人民共和国人民法院是国家的审判机关。
|
||||
|
||||
### 最高检《人民检察院民事诉讼监督规则》(2021-8-1施行)
|
||||
- **第二十八条第二款**:当事人对审判、执行人员违法行为申请监督的,不受前款(先行提异议/复议/起诉)限制。
|
||||
- **第三十条第一款**:当事人认为民事审判程序中审判人员存在违法行为…向人民检察院申请监督的,由审理案件的人民法院所在地同级人民检察院负责控告申诉检察的部门受理。
|
||||
- **第一百条**:检察院发现同级法院民事审判程序中有下列情形之一的,应当提出**检察建议**(第四项"审理案件适用审判程序错误")。→ 对审判程序违法的监督方式是**柔性检察建议**,非抗诉。
|
||||
- **第一百零三条**:认为审判人员违法行为认定依据不足的,作"不支持监督申请决定"。
|
||||
|
||||
## 律师执业 / 利益冲突 / 保密义务(2026-06-24 徐函事件,已核现行原文)
|
||||
|
||||
代表客户向律所/律师问责或审查顾问合同时用。地方律协规则常只有扫描版 PDF(见 SKILL.md「扫描件 PDF 法条提取」用 tesseract OCR)。
|
||||
|
||||
### 中华人民共和国律师法(2017修正,现行)
|
||||
- **第三十八条**:律师应当保守在执业活动中知悉的国家秘密、商业秘密,不得泄露当事人的隐私。律师对在执业活动中知悉的委托人和其他人不愿泄露的有关情况和信息,应当予以保密。但是,委托人或者其他人准备或者正在实施危害国家安全、公共安全以及严重危害他人人身安全的犯罪事实和信息除外。(律师保密义务的法律层级依据——律所擅自把客户盖章文件发给无关第三方=违反此条)
|
||||
- **第三十九条**:律师不得在同一案件中为双方当事人担任代理人,不得代理与本人或者其近亲属有利益冲突的法律事务。
|
||||
|
||||
### 司法部《律师执业管理办法》(司法部令第134号)
|
||||
- **第二十八条**:律师不得在同一案件中为双方当事人担任代理人,或者代理与本人及其近亲属有利益冲突的法律事务。
|
||||
|
||||
### 《广东省律师防止利益冲突规则》(2018版与2025修订版,第八条条号内容一致)
|
||||
2018版(合同2025-8签订时适用,OCR 自广东律协官网 gdlawyers.net 原始扫描 PDF);2025修订版(粤律协〔2025〕115号,2025-11-1施行,现行,文本源 szrlaw.net)。两版核心一致:
|
||||
- **第八条【直接利益冲突】**:在担任法律顾问期间,律师或者同一律师事务所的其他律师又在诉讼、仲裁或者其他非诉讼业务中接受该法律顾问单位或者个人的对方当事人或者有利益冲突的当事人委托的,属于直接利益冲突。
|
||||
- **第十四条**(2025版):直接利益冲突应当主动回避,不得接受委托,已经接受委托的,应当予以解除。
|
||||
- **第十六条**:律师事务所或者律师遇有间接利益冲突情形时,应征得各方委托人的书面同意;不能征得各方当事人书面同意时,应按处理直接利益冲突的规定进行处理。
|
||||
- **第十七条**:除本规则规定的利益冲突情形外,发现有可能产生利益冲突的,应及时告知委托人,并征得受影响的各方委托人书面同意。
|
||||
- → **要点**:① "法律顾问期间为顾问单位对方当事人服务"是**直接利益冲突**(不是间接),须回避/解除,不能靠合同里一纸概括预先豁免消解;② 即便按间接冲突,也须就**具体情形个案知情地征得书面同意**,合同"一揽子预先豁免、不再另行出具豁免函"的格式条款站不住,且对客户(甲方)极不利——预先放弃了利益冲突异议权。
|
||||
|
||||
### 司法部《律师和律师事务所违法行为处罚办法》
|
||||
- **第七条第三项**:担任法律顾问期间,为与顾问单位有利益冲突的当事人提供法律服务 = 应处罚的利益冲突违法行为。
|
||||
|
||||
## 外籍个人住房补贴免税 + 扣缴义务人责任(2026-06-24 平和外教住房补贴通知,已核一手原文)
|
||||
|
||||
审外企/外籍员工津补贴政策、免税承诺条款、扣缴义务相关文件时用。
|
||||
|
||||
### ⚠️ 自己栽过的错:别写"经主管税务机关核准/审批"作为免税前提——该审批 2004 年已取消
|
||||
我审《外教补贴通知》时,第一版意见建议补"免税须经主管税务机关核准确认"。**这是错的。** 国税发〔1997〕54 号原文确有"由主管税务机关核准确认免税",但**国税发〔2004〕80 号自 2004-7-1 起取消了该审批**(国家税务总局政策法规库原文:"取消外籍个人住房、伙食等补贴免征个人所得税审批的后续管理")。免税现在**无前置审批**——只需满足实质要件 + 留存凭证备查。幸亏 Doro 要求"涉及法律法规再核实一次",核一手原文时才发现。**教训:旧规范性文件的程序性条款(审批/备案/核准)极易被后续文件废止,引用前必须查"是否已取消/下放",不能照抄旧文。**
|
||||
|
||||
### 免税实质要件(财税字〔1994〕20号 + 国税发〔1997〕54号,现行有效)
|
||||
- 外籍个人以**非现金形式或实报实销形式**取得的**合理的**住房补贴、伙食补贴、洗衣费,**免征个税**。
|
||||
- 要件三件套:① 非现金/实报实销(凭票);② 金额合理;③ 留存**有效凭证**备查。**无审批环节**(2004 取消)。
|
||||
- 写进文件的合规措辞(已用、经核):"在符合税务法规规定的前提下,可享受免税福利"或展开为"在符合实报实销、凭合规有效凭证、金额合理等免税条件的前提下…;不符合免税条件的部分,依法计入工资薪金所得缴纳个人所得税"。**不要**写绝对化承诺"可享受免税福利"(学校为免税结果背书=风险)。
|
||||
|
||||
### 政策时效:免税延续至 2027-12-31(财政部 税务总局公告〔2023〕29号)
|
||||
- 公告第二条原文:"**本公告执行至2027年12月31日**"(一手源:HR政策库 zc.51shebao.com 等多站一致,全文有效,2023-8-18 发布)。
|
||||
- 居民外籍个人**二选一**:专项附加扣除 OR 八项津补贴免税,不得同时享受,一经选择当年不得变更。
|
||||
- 给文件加"如国家税收政策调整本通知相应调整"弹性条款是合理建议(政策有到期日),但属可选,问用户。
|
||||
|
||||
### 扣缴义务人责任(税收征管法 2015修正,现行)— 用人单位代扣个税
|
||||
- **第三十条**:扣缴义务人依法律/行政法规履行代扣代收义务;纳税人拒绝的,扣缴义务人应及时报告税务机关。
|
||||
- **个税法第九条**:以**支付所得的单位或个人**为扣缴义务人(用人单位=外教工资的扣缴义务人,法定,不可由约定免除)。
|
||||
- **第六十九条**:扣缴义务人应扣未扣、应收不收税款的,由税务机关**向纳税人追缴税款**,对扣缴义务人处应扣未扣税款 50%–3倍罚款。(应扣未扣→税款向纳税人追,扣缴义务人担罚款,一般不加滞纳金——滞纳金只适用"已扣未缴",第三十二条)
|
||||
- **实务要点(站用人单位/学校立场写条款)**:① 法定扣缴义务不能靠合同免除(约定免除无效,仍可能被罚);② 但可约定"纳税人配合扣缴、对所提供凭证信息真实性负责,因其不配合/提供虚假信息致扣缴义务人受损的,由纳税人担责"——这是站扣缴义务人立场的有效防线(来源:锦天城、大成扣缴义务专文)。虚开发票致学校被稽查处罚的穿透责任,按"故意/重大过失 vs 第三方提供"分梯度处理更稳健(学校法务 bittersweet 的写法)。
|
||||
|
||||
## 检察监督概率评估口径(审判程序违法类,非生效裁判再审)
|
||||
当用户要"客观分析检察院监督概率"时,分两层、别混:
|
||||
- **受理/立案审查概率**:定性准+管辖对+不受前置限制+证据齐 → 较高。
|
||||
- **实际发检察建议且法院纠正概率**:审慎偏中/低。减损因素:①监督方式只能是柔性检察建议(规则第100条),可不采纳;②未生效、程序进行中是检察监督冷区,倾向"法院后续可自我纠正"而消极处理(规则第103条不支持决定);③法院有"可补正"回旋空间;④"15日恰为举证下限"是有力旁证但非铁证,可被"巧合/这就是质证准备期"化解;⑤事实主张(如未送达)押在调卷核实上。给区间、点明天花板,别给"很可能成功"的乐观结论。
|
||||
@@ -0,0 +1,269 @@
|
||||
# 修订态 markup 清洁度 + 双视图核验
|
||||
|
||||
来源:2026-06-23 邹家民事诉讼监督申请书审核(Doro 给 7 点修改指示)。教训:第一遍用 `tracked_replace`(字符级 diff)做整句改写和称谓替换,接受态文本正确,但**修订态被 difflib 咬成半字脏标记**(`莲都法~~庭~~院`、`未经~~及~~法庭审理`、`一百二十八条~~切~~国家机关`)。Doro 用 OnlyOffice 看的是修订态且有格式洁癖 → 必须重做。
|
||||
|
||||
## 核心规则
|
||||
|
||||
| 改动类型 | 用什么 | 为什么 |
|
||||
|---|---|---|
|
||||
| 单字错别字、补一个字、删一个字 | `tracked_replace`(字符级,库自带) | 改动点孤立,diff 干净 |
|
||||
| 整词替换(尤其新旧共享字,如 法**庭**→莲都法**院**) | `tracked_block_replace`(整块 del+ins) | 阻止 difflib 咬共享字产生半字 |
|
||||
| 整句/整段改写 | `tracked_block_replace` | 同上,markup 是清爽的[删旧句][插新句] |
|
||||
| 删整段(合并条款、删一个自动编号列表项) | `tracked_delete_paragraph`(段落级标删) | 让自动编号重排(删请求三→请求四自动续成三) |
|
||||
|
||||
判据:**只要新旧文本有共享字符且改动跨多字,就别用 tracked_replace,改用 block。**
|
||||
|
||||
## 两个补充方法(库 contract_docx_lib.py 没有,用 types.MethodType 挂到实例上)
|
||||
|
||||
```python
|
||||
import sys, copy, types
|
||||
sys.path.insert(0,'/home/maggie/contract-work')
|
||||
from contract_docx_lib import ContractEditor, qn, XML_SPACE
|
||||
from lxml import etree
|
||||
|
||||
ed = ContractEditor(SRC); ed._author='小Maggie' # 署文书归属人
|
||||
|
||||
# ---- 整块替换:[del 整段旧][ins 整段新],markup 干净 ----
|
||||
def tracked_block_replace(self, old_text, new_text):
|
||||
for p in self.body.findall(qn('p')):
|
||||
runs=p.findall(qn('r'))
|
||||
if not runs: continue
|
||||
full=''.join(''.join(t.text or '' for t in r.findall(qn('t'))) for r in runs)
|
||||
if old_text not in full: continue
|
||||
start=full.index(old_text); end=start+len(old_text)
|
||||
rpr=None; pos=0
|
||||
for r in runs:
|
||||
rt=''.join(t.text or '' for t in r.findall(qn('t')))
|
||||
if pos+len(rt)>start:
|
||||
rpr=r.find(qn('rPr'))
|
||||
if rpr is not None:
|
||||
rpr=copy.deepcopy(rpr); self._ensure_rfonts_complete(rpr)
|
||||
if rpr.find(qn('sz')) is None and self._body_rpr is not None:
|
||||
bsz=self._body_rpr.find(qn('sz'))
|
||||
if bsz is not None:
|
||||
etree.SubElement(rpr,qn('sz')).set(qn('val'),bsz.get(qn('val')))
|
||||
break
|
||||
pos+=len(rt)
|
||||
pos=0; first=last=None; prefix=suffix=''
|
||||
for idx,r in enumerate(runs):
|
||||
rt=''.join(t.text or '' for t in r.findall(qn('t')))
|
||||
re_=pos+len(rt)
|
||||
if re_>start and pos<end:
|
||||
if first is None: first=idx; prefix=full[pos:start]
|
||||
last=idx; suffix=full[end:re_] if re_>end else ''
|
||||
pos=re_
|
||||
ref=runs[first]; parent=ref.getparent(); ip=list(parent).index(ref)
|
||||
for idx in range(last,first-1,-1): parent.remove(runs[idx])
|
||||
i=ip
|
||||
if prefix: parent.insert(i,self._mk_run(prefix,rpr)); i+=1
|
||||
parent.insert(i,self._mk_del(old_text,rpr)); i+=1
|
||||
parent.insert(i,self._mk_ins(new_text,rpr)); i+=1
|
||||
if suffix: parent.insert(i,self._mk_run(suffix,rpr))
|
||||
return True
|
||||
return False
|
||||
ed.tracked_block_replace=types.MethodType(tracked_block_replace,ed)
|
||||
|
||||
# ---- 段落级修订删除:整段(含段落标记)标删,自动编号重排 ----
|
||||
def tracked_delete_paragraph(self, search_text):
|
||||
p=self.find_para(search_text)
|
||||
if p is None: return False
|
||||
for r in list(p.findall(qn('r'))):
|
||||
rpr=r.find(qn('rPr')); texts=r.findall(qn('t'))
|
||||
d=etree.Element(qn('del')); d.set(qn('id'),self._next_id())
|
||||
d.set(qn('author'),self._author); d.set(qn('date'),self._revision_date)
|
||||
nr=etree.SubElement(d,qn('r')); nr.set(qn('rsidDel'),self._rsid)
|
||||
if rpr is not None: nr.append(copy.deepcopy(rpr))
|
||||
for t in texts:
|
||||
dt=etree.SubElement(nr,qn('delText')); dt.set(XML_SPACE,'preserve'); dt.text=t.text
|
||||
idx=list(p).index(r); p.remove(r); p.insert(idx,d)
|
||||
ppr=p.find(qn('pPr'))
|
||||
if ppr is None: ppr=etree.Element(qn('pPr')); p.insert(0,ppr)
|
||||
rpr=ppr.find(qn('rPr'))
|
||||
if rpr is None: rpr=etree.SubElement(ppr,qn('rPr'))
|
||||
dem=etree.Element(qn('del')); dem.set(qn('id'),self._next_id())
|
||||
dem.set(qn('author'),self._author); dem.set(qn('date'),self._revision_date)
|
||||
rpr.insert(0,dem) # 段落标记标删 → 接受后整段消失,编号重排
|
||||
return True
|
||||
ed.tracked_delete_paragraph=types.MethodType(tracked_delete_paragraph,ed)
|
||||
```
|
||||
|
||||
执行顺序:小改动(tracked_replace) → 称谓/整句(block) → 大改写(block) → 删段(delete_paragraph) → save。
|
||||
|
||||
## 半角括号 → 全角(自动编号子标题 (一)→(一))
|
||||
|
||||
子标题 `(一)(二)` 是自动编号,半角括号根在 numbering.xml 的 lvlText `(%1)`,不在正文。改 numbering.xml 一次性全改最干净(不要去正文里找,找不到):
|
||||
|
||||
```python
|
||||
import zipfile, io
|
||||
from lxml import etree
|
||||
W='{http://schemas.openxmlformats.org/wordprocessingml/2006/main}'
|
||||
with zipfile.ZipFile(SRC) as z:
|
||||
num=etree.fromstring(z.read('word/numbering.xml'))
|
||||
for lt in num.iter(W+'lvlText'):
|
||||
v=lt.get(W+'val')
|
||||
if v and ('(' in v or ')' in v):
|
||||
lt.set(W+'val', v.replace('(','(').replace(')',')'))
|
||||
new=etree.tostring(num,xml_declaration=True,encoding='UTF-8',standalone=True)
|
||||
buf=io.BytesIO()
|
||||
with zipfile.ZipFile(SRC) as zin, zipfile.ZipFile(buf,'w',zipfile.ZIP_DEFLATED) as zout:
|
||||
for it in zin.infolist():
|
||||
zout.writestr(it, new if it.filename=='word/numbering.xml' else zin.read(it.filename))
|
||||
open(OUT,'wb').write(buf.getvalue())
|
||||
```
|
||||
注意:`、`(如 一、二、 和 %1、)不受影响,只动含半角圆括号的 lvlText。
|
||||
|
||||
## 🔴 插入新编号条款前:先判"中文编号是自动编号 还是 手动文字"——`numId=0` = 无编号(2026-06-24 平和外教通知实测,第一版栽了)
|
||||
|
||||
要在文件中**插入一个新的带编号大条款**(如 Doro 要"把追回权抽成单独第五条,囊括所有情形")时,**绝不能假设"四、""五、"这些中文编号是自动编号**。判错会让新条款不显示编号、且原"五、其他说明"不顺延。
|
||||
|
||||
**判别(动手前必做)**:
|
||||
```python
|
||||
# 1. 看标题段的 numPr
|
||||
ppr=p.find(W+'pPr'); numpr=ppr.find(W+'numPr') if ppr is not None else None
|
||||
nid=numpr.find(W+'numId').get(W+'val') if numpr is not None else None
|
||||
# 2. 看 numbering.xml 里这个 numId 到底定义了没
|
||||
# list 出所有 <w:num w:numId=...> 。若标题段挂的 numId 不在其中 → 不是真自动编号
|
||||
```
|
||||
**关键陷阱**:`<w:numId w:val="0"/>` 是 OOXML **特殊值=取消编号/无编号**,numbering.xml 里**根本不会有 numId=0 的定义**。本会话"四、违规处理细则""五、其他说明"段都挂 `numId=0`,我误判成"自动编号、插同级会自动续号",构造了 numId=0 的新段落——结果 OO 渲染里新条款**完全不显示编号**、"五、其他说明"也没变"六、"。一查 numbering.xml:只有 numId=1/2/3,**没有 0**。再查标题段 run 文本:直接是 `"四、违规处理细则"`、`"五、其他说明"`——**"四""五"是手打进正文的文字字符,不是自动编号生成的**。
|
||||
|
||||
**正确判据**:
|
||||
- run 文本里**肉眼能看到**"四、""五、"这些字 + 段落 numId=0/无 numPr/numId 不在 numbering.xml → **手动文字编号**。
|
||||
- run 文本里**看不到**编号字(只有"违规处理细则"无"四、")+ 段落挂了真实存在的 numId → **自动编号**(numFmt=chineseCounting,lvlText='%1、')。
|
||||
|
||||
**手动文字编号的插入做法**(本会话最终正确版):
|
||||
1. 新条款文本**自己写上编号字**:"五、无论发生上述何种违规情形,……追回……全部免税住房补贴。"(复制相邻标题段的 pPr+rPr 做格式模板,但不靠 numPr 出号)。
|
||||
2. 新段落作为整段插入修订:pPr/rPr 里塞 `<w:ins>`(段落标记标插),正文 run 用 `_mk_ins` 包裹,author=自己。
|
||||
3. **手动把后续编号往后顺延**:原"五、其他说明"用 tracked_block_replace 把"五、其他说明"→"六、其他说明"(手动改字,留痕)。
|
||||
4. 渲染**接受态**用 vision 核对:"四、…→ 五、(新条款) → 六、其他说明" 连续无重号——本会话改对后 OO 实测通过。
|
||||
|
||||
**自动编号的插入做法**(对照,仅当确属自动编号时):插一个挂同 numId 的同级段落,编号会自动续;原后续条款自动顺延,**无需手动改编号字**(见本文件"结构性改动技巧"段的 numPr 自动重排)。
|
||||
|
||||
⚠️ 顺带的衔接坑:抽条款时若要删的原文句与他人(bittersweet) ins 用逗号"咬合"("…索赔主张【bittersweet逗号】同时…追回全部【原文】"),**只标删原文那半句、不碰他人 ins 的逗号**(铁律:他人修订不动);接受态以逗号收尾虽不完美,但独立新条款紧随其后能化解观感,宁可这样也不改他人标点。
|
||||
|
||||
## 仿宋全角引号修复(OnlyOffice 把弯引号渲染成 Times)
|
||||
|
||||
**症状**:正文是仿宋,但中文弯引号 “ ”(U+201C/U+201D)显示成又粗又重的西文 Times New Roman 引号,和仿宋正文不协调。Doro 2026-06-23 要求"引号统一为仿宋的全角"。
|
||||
|
||||
**根因**:弯引号是"中西文模糊字符"。这些引号所在 run 的 rFonts 往往 `ascii=Times New Roman, eastAsia=None`(eastAsia 继承样式里的仿宋)。OnlyOffice 对 **eastAsia 未显式设定**的模糊字符按 **ascii 字体**渲染 → 走了 Times。诊断:遍历所有含 U+201C/U+201D 的 run,打印 `rFonts/@ascii` 与 `@eastAsia`,会看到 ascii=Times、eastAsia=None。
|
||||
|
||||
**修法**:给所有含引号的 run 显式设 rFonts。
|
||||
- **纯引号 / 引号+中文的 run**:`ascii=eastAsia=hAnsi=cs=仿宋, hint=eastAsia`(ascii 也设仿宋,彻底消除模糊字符歧义——只设 eastAsia 在某些 OO 版本仍可能走 ascii)。
|
||||
- **引号+拉丁数字混排的 run**(如 `“2026浙1102…`):**拆 run**——引号片段走仿宋,数字片段保留 `ascii=Times New Roman`。否则给整 run 设仿宋会把"2026"也变仿宋。拆法:按字符是否∈`{U+201C,U+201D}`分组,逐组生成新 run(引号组设仿宋、非引号组设 Times),原 run 删除、按序 insert 回去。
|
||||
|
||||
```python
|
||||
W='{http://schemas.openxmlformats.org/wordprocessingml/2006/main}'
|
||||
def qn(t): return W+t
|
||||
FANG='仿宋'
|
||||
def has_latin(s): return any(c.isascii() and c.isalnum() for c in s)
|
||||
for p in list(root.iter(qn('p'))):
|
||||
for r in list(p.findall(qn('r'))):
|
||||
txt=''.join(t.text or '' for t in r.findall(qn('t')))
|
||||
if '\u201c' not in txt and '\u201d' not in txt: continue
|
||||
rpr=r.find(qn('rPr')); rf=rpr.find(qn('rFonts')) if rpr is not None else None
|
||||
if not has_latin(txt): # 纯引号/中文:整 run 设仿宋
|
||||
if rf is not None:
|
||||
for a in ('ascii','eastAsia','hAnsi','cs'): rf.set(qn(a),FANG)
|
||||
rf.set(qn('hint'),'eastAsia')
|
||||
else: # 含数字:按引号/非引号拆 run
|
||||
import copy
|
||||
segs=[]; cur=''; cur_q=None
|
||||
for c in txt:
|
||||
isq=c in '\u201c\u201d'
|
||||
if cur_q is None: cur_q=isq; cur=c
|
||||
elif isq==cur_q: cur+=c
|
||||
else: segs.append((cur,cur_q)); cur=c; cur_q=isq
|
||||
if cur: segs.append((cur,cur_q))
|
||||
parent=r.getparent(); idx=list(parent).index(r); parent.remove(r)
|
||||
for seg,isq in segs:
|
||||
nr=etree.Element(qn('r'))
|
||||
if rpr is not None:
|
||||
nrpr=copy.deepcopy(rpr); nr.append(nrpr)
|
||||
nrf=nrpr.find(qn('rFonts'))
|
||||
if nrf is None: nrf=etree.SubElement(nrpr,qn('rFonts')); nrpr.insert(0,nrf)
|
||||
if isq:
|
||||
for a in ('ascii','eastAsia','hAnsi','cs'): nrf.set(qn(a),FANG)
|
||||
nrf.set(qn('hint'),'eastAsia')
|
||||
else:
|
||||
nrf.set(qn('ascii'),'Times New Roman'); nrf.set(qn('hAnsi'),'Times New Roman')
|
||||
nt=etree.SubElement(nr,qn('t')); nt.set('{http://www.w3.org/XML/1998/namespace}space','preserve'); nt.text=seg
|
||||
parent.insert(idx,nr); idx+=1
|
||||
```
|
||||
**验证**:遍历所有含引号字符的 run,断言 `rFonts/@eastAsia=='仿宋'`,计数应等于全文引号字符数。**OO 渲染确认**:x2t 转 PDF 裁剪含引号区域,目视引号是否纤细、与仿宋协调(不再是重弯钩 Times)——`hint=eastAsia` 不一定够,必须 OO 实渲染验。
|
||||
**字体诊断通法**:正文中文是不是仿宋?查 styles.xml 的正文样式 rFonts(本会话正文 3333 字 eastAsia=None 全继承样式里的"仿宋")。引号异常往往是局部 run 覆盖了样式字体。
|
||||
|
||||
## 全局字体规范化(中文仿宋 / 英数 Times New Roman)
|
||||
|
||||
来源:2026-06-23 邹家 v8→v9,Doro 要求"引号统一仿宋 + 中文仿宋 + 英文/数字 Times New Roman"。这是 Doro/Maggie 的**通用排版规范**,不止引号——整篇都要中文走仿宋、拉丁字母与阿拉伯数字走 Times。引号修法见上一节,本节是**全篇 run 的一次性归一**(上一节是引号专项,本节覆盖全文)。
|
||||
|
||||
**做法**:遍历每个 run,按字符类别分组,混排 run 拆开,逐组设 rFonts。分类口径:
|
||||
- CJK 汉字 + 中文标点(。,、;:)→ `eastAsia=ascii=hAnsi=cs=仿宋, hint=eastAsia`
|
||||
- 拉丁字母 + 阿拉伯数字 → `ascii=hAnsi=Times New Roman`(eastAsia 保留仿宋兜底)
|
||||
- 弯引号/中文全角符号 → 仿宋(同 CJK,见上一节拆法)
|
||||
|
||||
混排 run(如 `浙1102民初2078号`、`HUAXIN XU 徐华鑫`)必须**拆 run**——给整 run 设一种字体会污染另一类字符。拆法同引号节:按字符类别切片,逐片 deepcopy rPr 后改 rFonts,原 run 删除按序 insert 回去。
|
||||
|
||||
**验证(Doro 口径,必做)**:改完统计全文 run 字体属性分布,按四类断言计数:
|
||||
```
|
||||
最终文件字体属性:
|
||||
NNNN CJK 仿宋
|
||||
NNN LATIN Times New Roman
|
||||
NNN PUNCT 仿宋
|
||||
NN QUOTE 仿宋
|
||||
规范达标: ✅ 中文全仿宋 + 英数全 Times
|
||||
```
|
||||
读 word/document.xml 所有 run,逐字符按类别归到该 run 的 rFonts 目标字体;发现"中文 run 的 eastAsia≠仿宋"或"纯拉丁 run 的 ascii≠Times"即为漏网,回去补。
|
||||
|
||||
### ⚠ OnlyOffice 容器没有真仿宋字体——验字体看属性,不看字形
|
||||
用 OO 容器 x2t 渲染来**目视**核验时要警惕:**生产 OO 容器通常没装真·仿宋**,会把"仿宋"字体名回退映射到 Noto Serif(字形偏宋体)。所以渲染图里中文看着像宋体 **≠ 文件错了**——文件 rFonts 的字体名属性是"仿宋"才是关键,Doro 本机有真仿宋会正确显示。
|
||||
- 验"中文是不是仿宋":**查 docx 的 rFonts/@eastAsia 属性值**(程序断言),**不靠 OO 渲染图字形**判断。
|
||||
- OO 渲染图只用来验**布局**(无缺字方框/无字体回退 tofu/无文字重叠/英数确为 Times 衬线)——vision_analyze 看。
|
||||
- **别污染生产容器**:若为渲染临时往 OO 容器塞了字体映射,验完必须移除恢复,不留临时字体污染生产环境。
|
||||
|
||||
## 双视图核验
|
||||
|
||||
### A. 修订态(Doro/莎莎用 OnlyOffice 看痕迹)→ 用 OO 容器 x2t,不用 LibreOffice
|
||||
LibreOffice 与 OnlyOffice 不同源,必须用 OO 口径验 markup。
|
||||
```bash
|
||||
docker cp v_final.docx nextcloud-onlyoffice-1:/tmp/z.docx
|
||||
docker exec nextcloud-onlyoffice-1 bash -lc '
|
||||
cat >/tmp/zx.xml <<XML
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TaskQueueDataConvert xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<m_sFileFrom>/tmp/z.docx</m_sFileFrom><m_sFileTo>/tmp/z.pdf</m_sFileTo>
|
||||
<m_nFormatTo>513</m_nFormatTo><m_bIsNoBase64>false</m_bIsNoBase64>
|
||||
</TaskQueueDataConvert>
|
||||
XML
|
||||
cd /var/www/onlyoffice/documentserver/server/FileConverter/bin && ./x2t /tmp/zx.xml'
|
||||
docker cp nextcloud-onlyoffice-1:/tmp/z.pdf /tmp/markup.pdf
|
||||
pdftoppm -png -r 150 /tmp/markup.pdf /tmp/mk # 转图 vision_analyze 查脏标记
|
||||
```
|
||||
查点:每个 del/ins 是整段配对、无半字、无文字重叠。
|
||||
|
||||
### B. 接受态(成稿)→ 程序剥离修订后渲染,核对编号/括号
|
||||
```python
|
||||
# 接受所有修订:unwrap ins(留子元素)、删 del、删被标删的空列表段
|
||||
for ins in list(doc.iter(W+'ins')):
|
||||
par=ins.getparent(); idx=list(par).index(ins)
|
||||
for ch in list(ins): par.insert(idx,ch); idx+=1
|
||||
par.remove(ins)
|
||||
for d in list(doc.iter(W+'del')):
|
||||
par=d.getparent()
|
||||
if par is not None: par.remove(d)
|
||||
for p in list(body.findall(W+'p')): # 删 tracked_delete_paragraph 留下的空列表项
|
||||
txt=''.join((t.text or '') for t in p.iter(W+'t'))
|
||||
ppr=p.find(W+'pPr'); has_num=ppr is not None and ppr.find(W+'numPr') is not None
|
||||
if has_num and txt.strip()=='': body.remove(p)
|
||||
```
|
||||
核对:删条款后自动编号一二三连续无断号、全角括号生效、U+FFFD=0。
|
||||
|
||||
## 自查清单(交付前)
|
||||
- [ ] 整词/整句改写用了 block,不是 tracked_replace(修订态无半字)
|
||||
- [ ] 删段用 delete_paragraph,接受后编号重排无断号
|
||||
- [ ] 全角括号改在 numbering.xml
|
||||
- [ ] validate() 过(请求项/标题加粗误报已豁免核对)
|
||||
- [ ] ins author = 文书归属人、字体无缺失
|
||||
- [ ] OO 渲染修订态 markup 干净(vision 查)
|
||||
- [ ] 程序剥离后接受态编号/括号正确
|
||||
- [ ] 引号统一仿宋全角(含引号 run eastAsia=仿宋,OO 渲染确认不再是 Times;含数字的拆 run)
|
||||
- [ ] 全局字体规范:全篇中文 run eastAsia=仿宋、拉丁/数字 run ascii=Times(混排已拆 run);用属性断言四类计数核验,**不靠 OO 字形**(容器无真仿宋,会回退宋体;OO 图只验布局)
|
||||
- [ ] 所有法条核权威原文(宪法 2018 修正、民诉法 2023 修正——条号会变,不凭记忆)。**精确到正确款项**:2026-06-23 Doro 自己把"不得少于十五日"写成证据规定第五十一条**第二款**,实际在**第一款**(第二款是补正不受期间限制)。审核职责含纠正委托人的法条款项笔误,逐条核到款。
|
||||
Reference in New Issue
Block a user