# DOCX 批注注入（免 python-docx）

当 python-docx 超时或不可用时，直接操作 OOXML zip 包注入彩色批注段落。

## 使用场景

- 合同审核批注
- 制度修订批注
- 员工答卷点评

## 核心代码

```python
import zipfile, io
from xml.etree import ElementTree as ET

NS = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main'
ET.register_namespace('w', NS)

# 读取
with zipfile.ZipFile(SRC, 'r') as zf:
    doc_xml = zf.read('word/document.xml')

root = ET.fromstring(doc_xml)
body = root.find(f'{{{NS}}}body')

# 创建批注段落
def make_rev(level, text):
    colors = {'red': 'CC0000', 'blue': '0066CC', 'brown': '993300'}
    p = ET.Element(f'{{{NS}}}p')
    r = ET.SubElement(p, f'{{{NS}}}r')
    rPr = ET.SubElement(r, f'{{{NS}}}rPr')
    ET.SubElement(rPr, f'{{{NS}}}color').set(f'{{{NS}}}val', colors.get(level, '993300'))
    ET.SubElement(rPr, f'{{{NS}}}sz').set(f'{{{NS}}}val', '18')
    if level == 'brown': ET.SubElement(rPr, f'{{{NS}}}i')
    t = ET.SubElement(r, f'{{{NS}}}t')
    t.text = text
    t.set('{http://www.w3.org/XML/1998/namespace}space', 'preserve')
    return p

# 按关键词匹配段落 → 后插批注
insertions = {}
for i, p in enumerate(list(body)):
    txt = ''.join(t.text or '' for t in p.iter(f'{{{NS}}}t'))
    if '关键词' in txt:
        insertions[i] = [('brown', '【批注】内容')]

# 逆序插入
for idx in sorted(insertions.keys(), reverse=True):
    ref = list(body)[idx]
    for ann in reversed(insertions[idx]):
        body.insert(idx + 1, make_rev(ann[0], ann[1]))

# 写回
new_xml = ET.tostring(root, encoding='UTF-8', xml_declaration=True)
new_zip = io.BytesIO()
with zipfile.ZipFile(SRC, 'r') as zin:
    with zipfile.ZipFile(new_zip, 'w', zipfile.ZIP_DEFLATED) as zout:
        for item in zin.infolist():
            if item.filename == 'word/document.xml':
                zout.writestr(item, new_xml)
            else:
                zout.writestr(item, zin.read(item.filename))
with open(OUT, 'wb') as f:
    f.write(new_zip.getvalue())
```

## 批注颜色规范

| 颜色 | 用途 |
|------|------|
| 红色 strikethrough | 删除的原内容 |
| 蓝色 | 新增/修改内容 |
| 棕色斜体 | 批注说明 |
