LXML - 排序标签顺序

时间:2011-12-05 12:33:00

标签: python xml lxml

我有一个遗留的文件格式,我正在将其转换为XML进行处理。结构可以概括为:

<A>
    <A01>X</A01>
    <A02>Y</A02>
    <A03>Z</A03>
</A>

标签的数字部分可以从01到99,并且可能存在间隙。作为处理的一部分,某些记录可能添加了其他标记。处理完成后,我通过追踪树将文件转换回传统格式。文件相当大(约150,000个节点)。

这方面的一个问题是,某些使用传统格式的软件假定标签(或转换时的字段)将按字母顺序排列,但默认情况下,新标签会添加到标签的末尾。然后导致它们以错误的顺序从迭代器中出来的分支。

每次添加新标记时,我都可以使用xpath根据标记名称查找前面的兄弟,但我的问题是在导出之前是否有一种更简单的方法对树进行排序?

编辑:

我想我总结了结构。

记录可以包含如上所述的几个级别,以提供类似的内容:

<X>
    <X01>1</X01>
    <X02>2</X02>
    <X03>3</X03>
    <A>
        <A01>X</A01>
        <A02>Y</A02>
        <A03>Z</A03>
    </A>
    <B>
        <B01>Z</B02>
        <B02>X</B02>
        <B03>C</B03>
    </B>
</X>

3 个答案:

答案 0 :(得分:17)

可以编写一个辅助函数来在正确的位置插入一个新元素,但是如果不了解更多关于结构的信息,很难使它成为通用的。

以下是在整个文档中对子元素进行排序的简短示例:

from lxml import etree

data = """<X>
    <X03>3</X03>
    <X02>2</X02>
    <A>
        <A02>Y</A02>
        <A01>X</A01>
        <A03>Z</A03>
    </A>
    <X01>1</X01>
    <B>
        <B01>Z</B01>
        <B02>X</B02>
        <B03>C</B03>
    </B>
</X>"""

doc = etree.XML(data,etree.XMLParser(remove_blank_text=True))

for parent in doc.xpath('//*[./*]'): # Search for parent elements
  parent[:] = sorted(parent,key=lambda x: x.tag)

print etree.tostring(doc,pretty_print=True)

产量:

<X>
  <A>
    <A01>X</A01>
    <A02>Y</A02>
    <A03>Z</A03>
  </A>
  <B>
    <B01>Z</B01>
    <B02>X</B02>
    <B03>C</B03>
  </B>
  <X01>1</X01>
  <X02>2</X02>
  <X03>3</X03>
</X>

答案 1 :(得分:4)

你可以像这样对xml元素进行排序:

from operator import attrgetter
from lxml import etree

root = etree.parse(xmlfile)
children = list(root)
sorted_list = sorted(children, key=attrgetter('tag'))

如果运行速度太慢,您可能只需对标记名称进行排序并使用xpath获取节点:

tag_list = [item.tag for item in root]
sorted_taglist = sorted(tag_list)

答案 2 :(得分:0)

谷歌搜索 XML 排序器,我到了这里。基于@MattH的工作,我做了一个更完整和可调的功能:

#!python3
from lxml import etree
import sys
if len(sys.argv) < 3:
    print("usage : xml_sorted.py file_in.xml file_out.xml")
    exit(0)
    
filename_in=sys.argv[1]
filename_out=sys.argv[2]

def getSortValue(elem):
    if isinstance(elem,etree._Comment):
        # sort comment by its content
        return elem.text
    else:
        # sort entities by tag and then by name
        return elem.tag + elem.attrib.get('name','')

doc=etree.parse(filename_in)

for parent in doc.xpath('//*[./*]'): # Search for parent elements
    parent[:] = sorted(parent,key=lambda x: getSortValue(x))

with open(filename_out,"wb") as file:
    file.write(etree.tostring(doc,pretty_print=True))