Python ElementTree如何保留XML非根节点的命名空间定义
保留XML命名空间的原始节点位置(Python 2.7 ElementTree)
问题概述
使用Python 2.7的xml.etree.ElementTree处理含多命名空间的XML时,要求输出XML的命名空间必须和输入XML定义在完全相同的节点上,但ElementTree默认会将所有命名空间声明提升到根节点,需要找到规避方法。
示例文件
读写脚本(demo_input_xml.py)
import sys import xml.etree.ElementTree as ET infile = sys.argv[1] outfile = sys.argv[2] tree = ET.parse(infile) root = tree.getroot() # 此处添加XML修改逻辑 ofile = open(outfile, 'w') tree.write(ofile) ofile.close()
输入XML(demo_temp_input.xml)
<?xml version="1.0" encoding="UTF-8"?> <tag0 xmlns="namespace0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="someLocation0"> <tag1a>someText</tag1a> <tag1b xmlns="namespace1" xmlns:n1="namespace2" xsi:schemaLocation="someLocation1"> <tag2> <tag3 xmlns="namespace3" xsi:schemaLocation="someLocation2"> </tag3> </tag2> </tag1b> </tag0>
实际输出(demo_temp_output.xml)
<ns0:tag0 xmlns:ns0="namespace0" xmlns:ns2="namespace1" xmlns:ns3="namespace3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="someLocation0"> <ns0:tag1a>someText</ns0:tag1a> <ns2:tag1b xsi:schemaLocation="someLocation1"> <ns2:tag2> <ns3:tag3 xsi:schemaLocation="someLocation2"> </ns3:tag3> </ns2:tag2> </ns2:tag1b> </ns0:tag0>
期望输出
<ns0:tag0 xmlns:ns0="namespace0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="someLocation0"> <ns0:tag1a>someText</ns0:tag1a> <ns2:tag1b xmlns:ns2="namespace1" xsi:schemaLocation="someLocation1"> <ns2:tag2> <ns3:tag3 xmlns:ns3="namespace3" xsi:schemaLocation="someLocation2"> </ns3:tag3> </ns2:tag2> </ns2:tag1b> </ns0:tag0>
限制条件
- Python版本固定为2.7.18
- 无法安装或使用lxml库
- 输入含多个默认命名空间,理想情况保留,但ElementTree生成的前缀(如ns0、ns2)可接受
- ElementTree会丢弃未使用的命名空间(如示例中的n1/namespace2),此行为可接受
- 输出可省略XML版本头
解决方案
ElementTree的序列化逻辑会自动将命名空间声明提升到根节点,这是内置优化,无官方API直接禁用。要保留原始节点的命名空间声明,需手动为目标节点添加xmlns:*属性,步骤如下:
修改后的脚本
import sys import xml.etree.ElementTree as ET infile = sys.argv[1] outfile = sys.argv[2] tree = ET.parse(infile) root = tree.getroot() # 1. 注册所有需要保留的命名空间,固定前缀避免随机生成 ET.register_namespace('ns0', 'namespace0') ET.register_namespace('ns2', 'namespace1') ET.register_namespace('ns3', 'namespace3') ET.register_namespace('xsi', 'http://www.w3.org/2001/XMLSchema-instance') # 2. 定位目标节点,手动添加命名空间声明 # 找到tag1b节点(namespace1下的tag1b) tag1b = root.find('{namespace1}tag1b') if tag1b: tag1b.set('xmlns:ns2', 'namespace1') # 找到tag3节点(namespace3下的tag3) tag3 = root.find('.//{namespace3}tag3') if tag3: tag3.set('xmlns:ns3', 'namespace3') # 3. 写入输出文件 with open(outfile, 'w') as ofile: tree.write(ofile, encoding='utf-8')
关键说明
- 固定前缀:通过
ET.register_namespace()绑定命名空间URI和前缀,避免ElementTree自动生成前缀时跳过ns1(原因见下文)。 - 手动添加属性:直接为需要保留命名空间声明的节点添加
xmlns:前缀="URI"属性,ElementTree序列化时会保留这些属性,同时不会在根节点重复声明(因为前缀已注册,根节点的冗余声明会被自动处理)。 - 默认命名空间限制:ElementTree不支持在非根节点保留无前缀的默认命名空间(
xmlns="..."),只能接受带前缀的声明,这是其内部命名空间处理逻辑的限制。
附带问题解答:为何ElementTree不使用ns1前缀?
输入XML中的n1前缀对应namespace2,但该命名空间下没有任何元素或属性被使用,ElementTree会自动丢弃未使用的命名空间声明。而namespace1被分配ns2前缀,是因为ElementTree的前缀生成从ns0开始,跳过未使用的命名空间对应的编号(n1未被使用,所以不占用ns1),按顺序为有效命名空间分配前缀。
内容的提问来源于stack exchange,提问作者silence_of_the_lambdas
相关产品推荐
相关产品推荐

