LXML - 排序标签顺序

7 投票
3 回答
11324 浏览
提问于 2025-04-17 07:43

我有一个旧的文件格式,现在想把它转换成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 个回答

1

我在网上搜索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))

5

你可以这样对你的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)
23

可以写一个辅助函数来把新元素放到正确的位置,但如果不了解具体的结构,就很难写得通用。

下面是一个简单的例子,展示如何对整个文档中的子元素进行排序:

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>

撰写回答