使用lxml/ElementTree时如何禁用XML实体转换保留原始输入符号
问题场景
使用XML解析库处理包含非ASCII变音字符的文本时,序列化输出阶段会自动将非ASCII字符转换为数字字符实体,而非保留原始Unicode字符形式。
复现代码:
import xml.etree.ElementTree as ET root = ET.fromstring("<r><s>Die Häuser haben Dächer.</s></r>") as_text = ET.tostring(root).decode("utf-8") print(as_text)
实际输出:<r><s>Die Häuser haben Dächer.</s></r>
期望输出:<r><s>Die Häuser haben Dächer.</s></r>
目标是全场景禁用这类无意义的实体转换,直接保留原始输入的Unicode符号,同时确保实体还原过程足够安全,不破坏XML结构、不存在解析风险。
解决方案
序列化阶段从根源禁用实体转换
非ASCII字符被自动转成实体的根本原因是,xml.etree.ElementTree(包括lxml)默认的序列化输出编码为ASCII,为了适配ASCII编码的字符范围,解析库会自动将所有非ASCII字符转换为数字实体。
仅需调整序列化时的编码参数,即可从根源关闭这类转义,不需要额外打补丁:
- 若需要直接输出字符串形式的XML,调用
tostring时传入encoding="unicode"参数,解析库会直接以Unicode形式输出所有字符,不会触发非ASCII字符的实体转义:
输出结果完全匹配期望,直接保留原始变音字符。as_text = ET.tostring(root, encoding="unicode") print(as_text) - 若需要输出二进制形式的XML流,调用
tostring时传入encoding="utf-8"并关闭XML声明,输出的二进制流经过UTF-8解码后同样不会生成实体:as_bytes = ET.tostring(root, encoding="utf-8", xml_declaration=False) as_text = as_bytes.decode("utf-8") - lxml的处理逻辑与原生ET完全一致,只要传入相同的编码参数,就可以自动关闭非ASCII字符的实体转换,无额外配置要求。
已生成实体的安全还原方法
如果处理流程中已经生成了带数字实体的XML字符串,不推荐直接使用全局html.unescape方法做还原。这类全局方法会将XML语法必需的保留实体(比如<、>、&)也一并还原成对应符号,最终破坏XML标签结构,甚至引发解析注入风险。
安全的还原流程:
- 将带实体的字符串作为合法XML片段,重新喂给XML解析器处理。XML解析器会自动识别并把数字实体转成对应的Unicode字符,全程严格保留XML语法层面的保留实体,不会被错误转换。
- 解析完成后按照上文的UTF-8/Unicode模式重新序列化,即可得到结构完整、字符正确的输出结果,完全避免手动还原实体带来的结构破坏风险。
注意:禁止对完整XML文档直接执行无差别实体反转义,这类操作会直接将转义过的文本内容还原成真实的XML标签,彻底破坏文档结构。
内容的提问来源于stack exchange,提问作者user3033490
相关产品推荐
相关产品推荐

