使用lxml etree获取XML嵌套元素时保留结构与命名空间的咨询
问题根因
你之前手动遍历后代节点逐个创建元素的写法存在本质缺陷:遍历descendant::*拿到的是拍平的所有后代节点,没有保留原有的父子层级关系;同时你只赋值了text属性,完全没处理元素属性、tail文本(即闭合标签后跟随的文本,比如示例中</italic>后的"some brief description, visit ")、命名空间映射,自然会出现结构错乱、内容丢失、命名空间缺失的问题。后续加的p标签硬编码判断,更是把逻辑和特定文档结构绑定,完全没有通用性。
最优解决方案
lxml原生支持节点深拷贝,不需要自己写遍历逻辑,一行代码就能完整保留所有嵌套结构、属性、文本、命名空间,实现1:1节点复制:
首先导入依赖:
import copy from lxml import etree
替换原有abstract处理逻辑即可:
abstract_source = tree.find(".//abstract") if abstract_source is not None: # 深拷贝整个节点,包含所有层级子节点、属性、文本、命名空间 abstract_clone = copy.deepcopy(abstract_source) # 将拷贝完成的节点挂载到目标父节点下 mods.append(abstract_clone)
方案优势
- 无任何硬编码逻辑,不依赖特定标签结构,不管节点嵌套多少层、子节点是什么标签名都能正常复制
- 自动保留所有元素属性,比如示例中
uri标签的xlink:href属性不会丢失 - 自动同步原节点的命名空间映射,不需要手动处理nsmap
- 自动完整保留
text和tail文本,不会出现节点间文本丢失的问题 - 代码量极小,没有自定义遍历逻辑,几乎不会出bug
特殊场景手动实现方案
如果有定制需求不能直接用深拷贝,需要手动遍历节点的话,必须通过递归维护正确的父子层级,同时同步属性、文本、命名空间,参考实现如下:
def clone_element(source, target_parent): # 创建新节点,同步原节点命名空间 new_elem = etree.SubElement(target_parent, source.tag, nsmap=source.nsmap) # 复制所有属性 for attr_k, attr_v in source.attrib.items(): new_elem.set(attr_k, attr_v) # 复制节点起始标签后的文本 new_elem.text = source.text # 递归处理所有直接子节点 for child in source: clone_element(child, new_elem) # 复制节点闭合标签后的尾部文本 new_elem.tail = source.tail return new_elem # 调用方式 abstract_source = tree.find(".//abstract") if abstract_source is not None: clone_element(abstract_source, mods)
注意:手动实现版本默认只处理普通元素节点,如果需要兼容注释、处理指令等特殊XML节点,还要额外加类型判断,无特殊需求优先使用深拷贝方案。
内容的提问来源于stack exchange,提问作者joesch
相关产品推荐
相关产品推荐

