Word邮件合并直接读取XML需配置MSXML提供者、绝对XPath路径及正确编码;Python用python-docx+ElementTree替换占位符更稳定可控,尤其处理嵌套结构或中文路径时。

Word 用 MailMerge 处理 XML 数据最稳
XML 不是 Word 原生支持的邮件合并数据源,但通过 MailMerge.OpenDataSource 指定 Connection 和 SQLStatement,可以绕过 Excel 中转直接读取 XML。关键不是“导入”,而是让 Word 把 XML 当成结构化记录集来查。
常见错误现象:Run-time error '5922'(无法打开数据源),大多因为没启用 MSXML 支持或路径/查询语法错;或者字段名对不上,合并域显示 ««FirstName»» 而不是真实值——其实是 XML 节点名和 Word 域名大小写/层级不一致。
- XML 必须有根节点,且每条记录应为同级子节点(如
)... ... - Word 只认 XPath 1.0,不支持
//person这种简写,得用绝对路径,例如/people/person - 连接字符串里必须指定
Provider=MSDataShape或Provider=Microsoft.MSDAOSP,否则 XML 解析器不加载 - 在 VBA 中调用时,
SQLStatement参数要写成"SELECT * FROM OPENXML(?, '/people/person', 2)"这类格式,?占位符对应 XML 文件路径
Python 用 python-docx + xml.etree.ElementTree 替代模板填充
如果你控制整个流程,且 XML 结构固定、Word 模板只是占位文本(比如「{{name}}」),别硬套 Word 自带的邮件合并——它太重、报错难 debug,直接用 Python 解析 XML 再批量替换更可控。
使用场景:生成合同、报告、证书等批量文档,XML 是业务系统导出的标准数据包,Word 模板由运营人员维护,不希望他们学 VBA 或改连接设置。
-
python-docx不能直接读取 XML 数据,但能遍历段落、表格单元格里的text,用str.replace()或正则匹配占位符(如{{customer_name}}) - XML 解析推荐
xml.etree.ElementTree,不用额外装库;避免用lxml除非你真需要 namespace 或复杂 XPath - 注意 Word 文档中占位符可能跨
run节点(比如加粗一半文字),简单paragraph.text.replace()会失效,得进paragraph.runs逐个处理 - 性能上,100 份文档以内基本无感;超 500 份建议缓存 XML 解析结果,别每次循环都
ET.parse()
XML 中有嵌套或列表时,MailMerge 直接失效
Word 的邮件合并只支持一维扁平记录,遇到 这类结构,/order/items/item 能读到两条记录,但没法把它们“收拢”进同一个订单页里——Word 没有子报表概念。
这时候硬用 MailMerge 会导致一页一个 item,而不是一页一个 order 带多个 item 表格。
- 解决方案只有两个:提前把 XML 展平(用 XSLT 或脚本生成新 XML,每个
item行带上所属order_id),或彻底放弃邮件合并,改用python-docx手动构建表格 - 如果必须保留 Word 模板编辑能力,可在模板里预留表格行,用 VBA 遍历 XML 子节点动态插入行并填值——但这就等于写个小引擎,调试成本远高于 Python 方案
- 别信“用 OLE 对象嵌入 XML”的方案,Word 会把它当二进制 blob,无法参与字段替换
中文路径、编码、命名空间让 MailMerge.OpenDataSource 静默失败
XML 文件路径含中文、文件本身是 GBK 编码、XML 声明带 xmlns,这三个问题任何一个出现,OpenDataSource 都可能不报错但数据源为空——ActiveDocument.MailMerge.DataSource.RecordCount 返回 0,且不提示原因。
最容易被忽略的是 XML 声明里的编码声明和实际文件编码不一致,比如文件是 UTF-8 但写成了 ,Word 就会乱码解析,字段全空。
- 强制用 UTF-8 保存 XML,并在声明中写
- 路径尽量用英文,实在不行,在 VBA 中用
FileSystemObject.GetAbsolutePathName()转成短路径(8.3 格式),避开 Unicode 路径解析问题 - 有命名空间的 XML,XPath 必须前缀绑定,例如
xmlns:ns="http://example.com",查询就得写/ns:root/ns:item,且OpenDataSource的Connection字符串里要加XMLNamespaces="ns=http://example.com"
真正卡住人的从来不是“怎么连上”,而是连上了却没数据——多打一行 Debug.Print ActiveDocument.MailMerge.DataSource.RecordCount,比翻三天文档有用。










