项目地址:
- GitHub: Andrew82106/GroundSkills/detailed-docx →
my_skills/detailed-docx/ - 依赖:
pip install python-docx
问题
用 Python 改 Word 文档里的内容,最怕的就是格式丢失。
python-docx 是 Python 生态里操作 .docx 的标准库,功能很全,但它提供的是底层能力------你得自己处理文本碎片、格式保留、合并单元格等问题。对于"改几个字但格式不能动"这种需求,裸用 python-docx 其实不太行。
detailed-docx 就是在 python-docx 之上封装的一层,专门解决格式保留问题。
Word 的内部结构:理解 Run
要理解为什么"改个字"这么难,需要先了解 Word 文档的内部结构。
一个段落(Paragraph)并不是一整段文字,而是由多个 Run 拼接而成。每个 Run 是一段格式一致的文本片段:
段落: "北京公司在2023年取得了非常优秀的业绩"
Run[0]: "北京公司" → 粗体, 蓝色, 14pt
Run[1]: "在2023年取得了" → 常规, 黑色, 12pt
Run[2]: "非常优秀" → 粗体, 斜体, 红色
Run[3]: "的业绩" → 常规, 黑色, 12pt
每个 Run 在 XML 层面有独立的格式节点 <w:rPr>,这就是 Word 实现"一段话里混合多种格式"的机制。
问题在于:Run 的拆分是不可控的 。Word 会因为拼写检查、编辑历史、中英文切换等原因,自动把文字拆成碎片。你以为 "北京公司" 在一个 Run 里,实际上可能被拆成 "北京" 和 "公司" 两个 Run。这导致直接搜索 paragraph.text 能找到,但逐个 Run 遍历却找不到完整匹配。
如果你直接写 paragraph.text = "新内容",python-docx 会把所有 Run 清空再重建------格式全没了。
核心算法:跨 Run 映射与替换
detailed-docx 的核心是一个字符位置映射算法:
1. 拼接所有 Run 的文本 → full_text = "北京公司在2023年取得了非常优秀的业绩"
2. 构建映射表:每个字符位置属于哪个 Run
3. 在 full_text 上搜索目标文本
4. 将匹配区间映射回各个 Run,逐个修改其 text 属性
5. 关键:只改 text,不动 <w:rPr> 格式节点
替换 "北京公司" → "上海分公司" 的实际操作:
- Run[0] 的 text 从
"北京公司"改成"上海分公司",<w:rPr>(粗体+蓝色+14pt)完全不动 - 如果匹配横跨多个 Run,后续 Run 只清空被命中的文字部分
这样就可以做到文本变了但是格式原封不动。
格式修改:增量叠加
修改格式同样是,保证只动你指定的,不碰其他的:
python
# "非常优秀" 原来是 {粗体, 斜体, 红色}
editor.modify_format("非常优秀", {"underline": True})
# 结果:{粗体, 斜体, 红色, 下划线} ← 只新增,不覆盖
API 概览
python
from my_docx import DocxEditor
# ── 读取 ──
editor = DocxEditor("input.docx")
editor.get_structural_map() # 段落 + 表格 + 节的完整结构
editor.get_run_details(para_idx=1) # 查看某段的 Run 碎片和精确格式
# ── 替换(自动跨 Run,自动保格式)──
editor.replace_text("旧词", "新词")
editor.replace_in_table(0, 1, 3, "旧值", "新值")
# ── 格式(增量叠加)──
editor.modify_format("重点内容", {"bold": True, "color": "FF0000"})
# ── 写入空单元格 ──
editor.set_table_cell_text(0, 0, 3, "张三", {"name": "黑体", "size": 12})
# ── 创建 ──
editor = DocxEditor.create_new()
editor.add_heading("标题", level=1)
editor.add_paragraph("正文", fmt={"name": "宋体", "size": 12})
# ── 删除 ──
editor.delete_paragraph(3)
editor.delete_table_row(0, 2)
editor.save("output.docx")