Python中使用lxml通过XSLT标识转换时如何保留CDATA?
刚好我之前在处理大体积单行XML格式化时也踩过CDATA丢失的坑,结合你用Python 2.7+XSLT的场景,给你一套可行的解决方案,既能格式化XML,又能完整保留CDATA:
解决方案:XSLT显式保留CDATA + Python流式处理
1. 调整XSLT模板,确保CDATA不被转义
默认的XSLT转换会把CDATA节点转成普通文本,我们需要在模板里做特殊处理,分两种场景:
场景A:知道CDATA所在的元素名
如果你的XML里CDATA固定出现在某些元素(比如<description>、<content>)中,直接在XSLT的输出配置里指定这些元素即可,这是最可靠的方式:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <!-- 开启格式化输出,同时指定需要保留CDATA的元素 --> <xsl:output method="xml" indent="yes" cdata-section-elements="description content"/> <!-- 通用复制模板,保留所有节点和属性 --> <xsl:template match="@*|node()"> <xsl:copy> <xsl:apply-templates select="@*|node()"/> </xsl:copy> </xsl:template> </xsl:stylesheet>
场景B:CDATA可能出现在任意元素中
如果CDATA的位置不固定,需要匹配文本中的CDATA标记,手动保留:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" indent="yes" encoding="UTF-8"/> <xsl:template match="@*|node()"> <xsl:copy> <xsl:apply-templates select="@*|node()"/> </xsl:copy> </xsl:template> <!-- 匹配包含CDATA的文本节点,手动输出CDATA标记 --> <xsl:template match="text()"> <xsl:choose> <!-- 检查是否是合法的CDATA内容 --> <xsl:when test="starts-with(., '<![CDATA[') and ends-with(., ']]>')"> <xsl:text disable-output-escaping="yes"><xsl:value-of select="."/></xsl:text> </xsl:when> <xsl:otherwise> <xsl:value-of select="."/> </xsl:otherwise> </xsl:choose> </xsl:template> </xsl:stylesheet>
2. Python 2.7流式处理大XML文件
因为是GB级的单行XML,绝对不能一次性加载到内存,要用流式解析配合lxml库(Python2.7下推荐用lxml 4.6.5版本,新版本不再支持Python2):
from lxml import etree def format_large_xml(input_xml_path, output_xml_path, xslt_path): # 加载XSLT转换模板 xslt_tree = etree.parse(xslt_path) xslt_transformer = etree.XSLT(xslt_tree) # 配置XML解析器:保留CDATA,去掉空白文本 parser = etree.XMLParser(strip_cdata=False, remove_blank_text=True) with open(output_xml_path, 'wb') as output_file: # 流式解析XML,只处理元素结束事件 for event, elem in etree.iterparse(input_xml_path, events=('end',), parser=parser): # 跳过注释和处理指令 if elem.tag not in (etree.Comment, etree.ProcessingInstruction): # 应用XSLT格式化 formatted_elem = xslt_transformer(elem) # 写入格式化后的内容 output_file.write(etree.tostring(formatted_elem, pretty_print=True, encoding='utf-8')) # 清理当前节点,释放内存(关键!处理大文件必须做) elem.clear() # 删除父节点中已处理的兄弟节点 while elem.getprevious() is not None: del elem.getparent()[0] # 调用示例 if __name__ == '__main__': format_large_xml( input_xml_path='large_single_line.xml', output_xml_path='formatted_with_cdata.xml', xslt_path='format_with_cdata.xsl' )
注意事项
- 先安装适配Python2.7的lxml:
pip install lxml==4.6.5 - 优先使用场景A的XSLT配置,场景B的文本匹配可能会误判包含CDATA格式的普通文本
- 流式处理时的节点清理步骤不能省略,否则内存会快速耗尽
内容的提问来源于stack exchange,提问作者George
相关产品推荐
相关产品推荐

