feat: export core Hermes skills
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env python3
|
||||
"""编号链诊断探针 — 一次性看清 docx 的自动编号/手动编号全貌。
|
||||
|
||||
用途:合同编号疑似错乱(重复/跳号/双号)时,动手改之前必跑此脚本。
|
||||
它把三件事一次性摊开,让你判断「是我们WB改错的 / 他人修订重排的 / 还是源文件自带的潜伏自动编号」:
|
||||
1. 每个段落:是否带 <w:numPr>(自动编号)、numId、ilvl、是否整段ins/del
|
||||
2. numbering.xml 解析:numId→abstractNum→(numFmt, lvlText, start) ——
|
||||
⚠️ start≠1 的 decimal 列表会渲染出「6、」之类的可见编号,但 run 里没有这个字!
|
||||
这是最隐蔽的坑:源文件起草人给某段挂了 numId(start=6),OnlyOffice 自动显示「6、售后服务」,
|
||||
而你在末尾新增条款时只数了手打的「1 2 3 4 5」,顺手编成「6」→ 与潜伏的自动6撞号。
|
||||
3. 每段「接受所有修订后」的可见文本(去w:del、保w:ins),近似 OnlyOffice 接受后视图
|
||||
|
||||
用法: python numbering-diagnose.py <contract.docx>
|
||||
.doc 先转换: soffice --headless --convert-to docx <file>.doc
|
||||
"""
|
||||
import sys, zipfile
|
||||
from lxml import etree
|
||||
|
||||
W = '{http://schemas.openxmlformats.org/wordprocessingml/2006/main}'
|
||||
|
||||
def text_mode(p, mode):
|
||||
"""mode='final': 接受所有修订后(去del,保ins). mode='orig': 修订前(去ins,保del)."""
|
||||
parts = []
|
||||
for node in p.iter():
|
||||
if node.tag == W + 't':
|
||||
anc, skip = node, False
|
||||
while anc is not None:
|
||||
if mode == 'final' and anc.tag == W + 'del':
|
||||
skip = True; break
|
||||
if mode == 'orig' and anc.tag == W + 'ins':
|
||||
skip = True; break
|
||||
anc = anc.getparent()
|
||||
if not skip:
|
||||
parts.append(node.text or '')
|
||||
elif node.tag == W + 'delText' and mode == 'orig':
|
||||
parts.append(node.text or '')
|
||||
return ''.join(parts).strip()
|
||||
|
||||
def parse_numbering(z):
|
||||
"""返回 numId -> (numFmt, lvlText, start) 仅 lvl0(够用于条款标题层)."""
|
||||
out = {}
|
||||
if 'word/numbering.xml' not in z.namelist():
|
||||
return out
|
||||
num = etree.fromstring(z.read('word/numbering.xml'))
|
||||
n2a = {}
|
||||
for n in num.findall(W + 'num'):
|
||||
ab = n.find(W + 'abstractNumId')
|
||||
if ab is not None:
|
||||
n2a[n.get(W + 'numId')] = ab.get(W + 'val')
|
||||
a2fmt = {}
|
||||
for ab in num.findall(W + 'abstractNum'):
|
||||
l0 = ab.find(W + 'lvl')
|
||||
if l0 is not None:
|
||||
fmt = l0.find(W + 'numFmt')
|
||||
txt = l0.find(W + 'lvlText')
|
||||
st = l0.find(W + 'start')
|
||||
a2fmt[ab.get(W + 'abstractNumId')] = (
|
||||
fmt.get(W + 'val') if fmt is not None else '?',
|
||||
txt.get(W + 'val') if txt is not None else '',
|
||||
st.get(W + 'val') if st is not None else '1',
|
||||
)
|
||||
for nid, aid in n2a.items():
|
||||
out[nid] = a2fmt.get(aid, ('?', '', '1'))
|
||||
return out
|
||||
|
||||
def main(path):
|
||||
z = zipfile.ZipFile(path)
|
||||
root = etree.fromstring(z.read('word/document.xml'))
|
||||
numinfo = parse_numbering(z)
|
||||
|
||||
print(f"### {path}\n")
|
||||
print("=== numbering.xml: numId -> (numFmt, lvlText, start) ===")
|
||||
if not numinfo:
|
||||
print(" (无 numbering.xml — 全文应为手动文本编号)")
|
||||
for nid, (fmt, txt, st) in sorted(numinfo.items()):
|
||||
warn = ' ⚠️start≠1 会渲染潜伏编号!' if (fmt == 'decimal' and st != '1') else ''
|
||||
print(f" numId={nid}: fmt={fmt}, lvlText='{txt}', start={st}{warn}")
|
||||
print()
|
||||
print("idx | numPr(自动) | rendered | ins/del | 文本(接受修订后)")
|
||||
print("-" * 92)
|
||||
for i, p in enumerate(root.findall('.//' + W + 'p')):
|
||||
tf = text_mode(p, 'final')
|
||||
if not tf:
|
||||
continue
|
||||
npr = p.find('.//' + W + 'numPr')
|
||||
npinfo, rendered = '—', ''
|
||||
if npr is not None:
|
||||
nid_el = npr.find(W + 'numId')
|
||||
il_el = npr.find(W + 'ilvl')
|
||||
nid = nid_el.get(W + 'val') if nid_el is not None else '?'
|
||||
il = il_el.get(W + 'val') if il_el is not None else '0'
|
||||
npinfo = f"numId={nid},lvl={il}"
|
||||
fmt, txt, st = numinfo.get(nid, ('?', '', '1'))
|
||||
if fmt == 'decimal':
|
||||
rendered = (txt or '%1、').replace('%1', st) # 该项首个渲染值(近似)
|
||||
elif fmt == 'bullet':
|
||||
rendered = '•'
|
||||
elif fmt == 'none':
|
||||
rendered = '(无)'
|
||||
has_ins = p.find('.//' + W + 'ins') is not None
|
||||
has_del = p.find('.//' + W + 'del') is not None
|
||||
mk = ('INS' if has_ins else '') + ('/' if has_ins and has_del else '') + ('DEL' if has_del else '')
|
||||
print(f"{i:3d} | {npinfo:18s} | {rendered:8s} | {mk:7s} | {tf[:46]}")
|
||||
print()
|
||||
print("判读要点:")
|
||||
print(" - rendered 列非空 = OnlyOffice 会自动加这个编号(run里没有这串字)")
|
||||
print(" - 手动编号: rendered='—' 且文本以「N、」开头 = 编号是写死的文字")
|
||||
print(" - 若末尾新增条款(INS)的手打编号 与 上方某段 rendered 自动编号 相同 → 撞号")
|
||||
print(" 正确做法: 新增手打编号应接续【rendered 自动值】往下编, 不是接续最后一个手打数字")
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) < 2:
|
||||
print(__doc__); sys.exit(1)
|
||||
main(sys.argv[1])
|
||||
Reference in New Issue
Block a user