XSLT转换XML时如何保留–等自定义实体不被转义
问题根因
当前自定义实体被异常转义、输出结构不符合预期是两个独立问题叠加导致:
- 实体转义问题:你定义的
ndash实体替换值为–,解析后会得到字面量字符串–。当解析器开启resolve_entities=True时,lxml会在加载阶段就把所有实体替换为对应文本值,后续XSLT处理的是已经解析完成的DOM树,此时文本节点中存储的是裸&字符,按照XML序列化规范,裸&必须转义为&,最终就输出了–的错误结果。 - 结构缺失问题:你写的XSLT中匹配
begin节点的模板直接复制data子节点,没有保留外层<begin>标签,导致输出结构和预期不符。
可行解决方案
以下两个方案均适配大文件处理场景,不需要改动原始XML的实体定义:
方案1:关闭预解析实体保留原始写法(性能最优,推荐超大文件场景使用)
不在解析阶段展开实体,让解析器将自定义实体保留为内部实体节点,序列化时会自动输出原始实体写法,无需额外替换逻辑。
首先修改Python执行代码:
import lxml.etree as ET from lxml import etree # 核心修改:将resolve_entities设为False,不提前展开实体 parser = etree.XMLParser(load_dtd=True, resolve_entities=False, huge_tree=True) dom = ET.parse('test.xml', parser) xslt = ET.parse('test.xsl') transform = ET.XSLT(xslt) newdom = transform(dom) processed_file = 'processed.xml' # 不要用str()强转结果,用tostring方法指定参数保证转义、编码规则一致 with open(processed_file, 'wb') as file: file.write(etree.tostring(newdom, encoding='utf-8', indent=True)) print(etree.tostring(newdom, encoding='utf-8', pretty_print=True).decode('utf-8')) print('Task Done')
再修正XSLT模板逻辑,保留外层<begin>标签:
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"> <xsl:output encoding="utf-8" method="xml" indent="yes" /> <xsl:template match="@*|node()"> <xsl:copy> <xsl:apply-templates select="@*|node()"/> </xsl:copy> </xsl:template> <xsl:template match="begin"> <xsl:copy> <xsl:copy-of select="data" /> </xsl:copy> </xsl:template> </xsl:stylesheet>
该方案解析速度快、内存占用低,可支持GB级XML文件处理,所有自定义实体都会原样保留,不会被转义修改。
方案2:占位符替换(兼容需要读取实体解析值的场景)
如果XSLT逻辑中需要读取实体解析后的文本内容做处理,不能关闭实体解析,可以通过占位符绕开转义逻辑:
- 读取原始XML文本时,先将所有自定义实体(比如
–)替换为不会和正常内容冲突的特殊占位串,例如__NDASH_ENTITY_PLACEHOLDER__ - 正常执行XML解析、XSLT转换、序列化流程
- 拿到最终序列化的文本结果后,将所有占位串替换回原始的
–再写入文件
处理超大文件时可以用流式逐行读写的方式做替换,避免全量加载占用过高内存。
注意事项
- 不要使用
str(newdom)的方式获取序列化结果,lxml的XSLT结果对象直接转字符串在不同版本下存在编码、转义规则不一致的问题,统一用etree.tostring()指定输出参数稳定性更高。 - 如果XML中存在多种自定义实体,方案1无需额外适配,关闭
resolve_entities即可全部保留原始写法;方案2需要为每个自定义实体配置互不冲突的占位串。
内容的提问来源于stack exchange,提问作者Sai Prasad Mukthapuram
相关产品推荐
相关产品推荐

