使用lxml增量编写XML时,如何避免命名空间重复?
解决lxml增量写入XML时的命名空间重复问题
你遇到的这个命名空间重复问题,在lxml增量处理XML时确实很常见——尤其是生成大量条目时,冗余的声明会显著增加未压缩文件的体积。先回顾下你当前的问题:
你生成的XML里,oneEntry标签重复了根节点data已经声明过的命名空间:
<?xml version='1.0' encoding='utf-8'?> <data xmlns="nsURI1" xmlns:second="nsURI2"> <oneEntry xmlns:second="nsURI2" xmlns="nsURI1"> <subEntry1/> <second:subEntry2/> </oneEntry> </data>
对应的生成脚本是基于lxml增量XML生成功能写的,但问题出在每个oneEntry元素都单独指定了完整的nsmap,导致lxml误以为这些命名空间需要重新声明。
核心解决方案:让子元素继承根节点的命名空间上下文
要避免重复声明,关键是不要给子元素单独指定nsmap,而是让它们在根节点的命名空间上下文中创建,或者直接使用带命名空间URI的标签名。这里有两种实用的写法:
写法一:在xmlfile上下文内创建子元素(推荐)
这种方式最贴合lxml增量写入的设计,直接在根元素的上下文里创建子元素,lxml会自动处理命名空间的继承,完全不会出现重复声明:
from lxml import etree from io import BytesIO class XmlWriter(object): ROOT_TAG = 'data' ENCODING = 'utf-8' NSMAP = { None: 'nsURI1', 'second': 'nsURI2' } def write(self): f = BytesIO() w = self.writer(f) next(w) # 模拟生成数千条数据的循环 for _ in range(1000): # 传递创建条目的参数给生成器,让它在上下文内创建元素 w.send(('oneEntry', [('subEntry1',), ('second:subEntry2',)])) w.close() print(f.getvalue().decode('utf-8')) @classmethod def writer(cls, out_stream): with etree.xmlfile(out_stream, encoding=cls.ENCODING) as xf: xf.write_declaration() # 进入根元素的命名空间上下文 with xf.element(cls.ROOT_TAG, nsmap=cls.NSMAP) as root_ctx: while True: try: tag, subtags = yield # 在根上下文内创建子元素,自动继承命名空间 with root_ctx.element(tag): for subtag in subtags: root_ctx.element(subtag).write() xf.flush() except GeneratorExit: break
写法二:外部构建元素但不指定nsmap
如果你需要在外部逻辑里构建元素,只要不给子元素指定nsmap,而是直接使用带命名空间URI的标签名,再配合strip_ns_declarations=True参数写入,也能避免重复:
from lxml import etree from io import BytesIO class XmlWriter(object): ROOT_TAG = 'data' ENCODING = 'utf-8' NSMAP = { None: 'nsURI1', 'second': 'nsURI2' } def create_entry(self): # 直接使用带命名空间URI的标签名,不指定nsmap entry = etree.Element(f'{{{self.NSMAP[None]}}}oneEntry') etree.SubElement(entry, f'{{{self.NSMAP[None]}}}subEntry1') etree.SubElement(entry, f'{{{self.NSMAP["second"]}}}subEntry2') return entry def write(self): f = BytesIO() w = self.writer(f) next(w) # 模拟生成数千条数据的循环 for _ in range(1000): entry = self.create_entry() w.send(entry) w.close() print(f.getvalue().decode('utf-8')) @classmethod def writer(cls, out_stream): with etree.xmlfile(out_stream, encoding=cls.ENCODING) as xf: xf.write_declaration() with xf.element(cls.ROOT_TAG, nsmap=cls.NSMAP): while True: try: el = yield # 写入时移除重复的命名空间声明 xf.write(el, strip_ns_declarations=True) xf.flush() except GeneratorExit: break
效果验证
运行修改后的脚本,生成的XML会变成这样,完全没有重复的命名空间:
<?xml version='1.0' encoding='utf-8'?> <data xmlns="nsURI1" xmlns:second="nsURI2"> <oneEntry> <subEntry1/> <second:subEntry2/> </oneEntry> <!-- 更多oneEntry条目... --> </data>
两种写法都保留了流处理的能力,适合生成数千条数据的场景,同时完美解决了命名空间重复的问题。
内容的提问来源于stack exchange,提问作者David
相关产品推荐
相关产品推荐

