99 lines
3.5 KiB
Python
99 lines
3.5 KiB
Python
"""
|
|
Document-agnostic WB INS font verification script.
|
|
Compares each WB insertion's rPr against adjacent original runs in the same paragraph.
|
|
Works for any font (宋体, 仿宋, etc.) and any sz value.
|
|
|
|
Usage: python wb-ins-font-verify.py <docx_path>
|
|
Or import verify_wb_ins_fonts(path) → returns (issues: list[str], total: int)
|
|
"""
|
|
import zipfile, io, sys
|
|
from lxml import etree
|
|
|
|
def qn(tag):
|
|
return '{http://schemas.openxmlformats.org/wordprocessingml/2006/main}' + tag
|
|
|
|
def get_rpr_attrs(rpr):
|
|
if rpr is None:
|
|
return {'ea': None, 'ascii': None, 'hint': None, 'sz': None, 'bold': False}
|
|
rfonts = rpr.find(qn('rFonts'))
|
|
sz = rpr.find(qn('sz'))
|
|
b = rpr.find(qn('b'))
|
|
return {
|
|
'ea': rfonts.get(qn('eastAsia')) if rfonts is not None else None,
|
|
'ascii': rfonts.get(qn('ascii')) if rfonts is not None else None,
|
|
'hint': rfonts.get(qn('hint')) if rfonts is not None else None,
|
|
'sz': sz.get(qn('val')) if sz is not None else None,
|
|
'bold': b is not None,
|
|
}
|
|
|
|
def verify_wb_ins_fonts(docx_path):
|
|
with open(docx_path, 'rb') as f:
|
|
raw = f.read()
|
|
with zipfile.ZipFile(io.BytesIO(raw)) as z:
|
|
tree = etree.fromstring(z.read('word/document.xml'))
|
|
body = tree.find(qn('body'))
|
|
|
|
issues = []
|
|
total = 0
|
|
|
|
for p in body.findall(qn('p')):
|
|
# Collect WB INS runs
|
|
wb_runs = []
|
|
for ins in p.findall('.//' + qn('ins')):
|
|
if ins.get(qn('author')) != 'WB':
|
|
continue
|
|
for r in ins.findall(qn('r')):
|
|
t = r.find(qn('t'))
|
|
if t is not None and (t.text or '').strip():
|
|
wb_runs.append((t.text[:50], r))
|
|
|
|
if not wb_runs:
|
|
continue
|
|
|
|
# Collect original (non-tracked) runs for comparison
|
|
orig_attrs = None
|
|
for r in p.findall(qn('r')):
|
|
parent = r.getparent()
|
|
if parent.tag in [qn('ins'), qn('del')]:
|
|
continue
|
|
t = r.find(qn('t'))
|
|
if t is not None and (t.text or '').strip():
|
|
orig_attrs = get_rpr_attrs(r.find(qn('rPr')))
|
|
break
|
|
|
|
for text, r in wb_runs:
|
|
total += 1
|
|
wb = get_rpr_attrs(r.find(qn('rPr')))
|
|
|
|
# Font name check: must have eastAsia and ascii set (not None)
|
|
if wb['ea'] is None:
|
|
issues.append(f"MISSING eastAsia: '{text}'")
|
|
if wb['ascii'] is None:
|
|
issues.append(f"MISSING ascii: '{text}'")
|
|
if wb['hint'] != 'eastAsia':
|
|
issues.append(f"MISSING hint=eastAsia: '{text}'")
|
|
if wb['sz'] is None:
|
|
issues.append(f"MISSING sz: '{text}'")
|
|
|
|
# Cross-check with original runs in same paragraph
|
|
if orig_attrs and orig_attrs['ea']:
|
|
if wb['ea'] and wb['ea'] != orig_attrs['ea']:
|
|
issues.append(f"FONT MISMATCH: '{text}' wb={wb['ea']} orig={orig_attrs['ea']}")
|
|
if wb['sz'] and orig_attrs['sz'] and wb['sz'] != orig_attrs['sz']:
|
|
issues.append(f"SIZE MISMATCH: '{text}' wb={wb['sz']} orig={orig_attrs['sz']}")
|
|
|
|
return issues, total
|
|
|
|
if __name__ == '__main__':
|
|
if len(sys.argv) < 2:
|
|
print("Usage: python wb-ins-font-verify.py <docx_path>")
|
|
sys.exit(1)
|
|
issues, total = verify_wb_ins_fonts(sys.argv[1])
|
|
if issues:
|
|
print(f"FAIL: {len(issues)} issues in {total} WB INS runs")
|
|
for i in issues:
|
|
print(f" - {i}")
|
|
sys.exit(1)
|
|
else:
|
|
print(f"PASS: all {total} WB INS runs font-consistent")
|