【问题标题】:LXML - Sorting Tag OrderLXML - 排序标签顺序
【发布时间】:2011-12-05 12:33:00
【问题描述】:

我有一个旧文件格式,我将其转换为 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>

【问题讨论】:

  • 我不太确定 XML 模式是否经过深思熟虑。 A01和A02不是同一种东西吗?它们应该共享相同的元素名称。数字可能应该是一个属性,而不是标签名称的一部分。此外,标签名称当然应该比这更易读,但我意识到它们可能只是一个示例。
  • 不幸的是,我无法控制旧格式,这是它如何将数据存储在键/值对中的直接翻译。在原始文件中,它可能会显示“A01=Bob”,然后应用程序就会知道该数字包含名字。
  • 有很多方法可以在 XML 中实现这一点,但您在此处展示的方法并不是非常语义化的翻译。您的架构将是复杂且不断变化的。我建议 &lt;item key="A01"&gt;value&lt;/item&gt; 其中 item 是 A01, A02 代表的东西。
  • 当我说键/值对时,我的意思是一个静态的键列表,这些键已知意味着什么 - 所以它说 &lt;A01&gt;Bob&lt;A01&gt; 它实际上是在说 &lt;Forename&gt;Bob&lt;/Forename&gt; 但只是遗留应用程序(约 20 岁)是用这些不透明的数字字段名称编写的。

标签: python xml lxml


【解决方案1】:

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

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

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>

【讨论】:

  • 谢谢 - lamba 功能正是我所需要的。
  • 我不明白你为什么在作业中使用parent[:] =
  • @Sdwdaw 因为我想修改现有的类序列对象的内容,parent。不创建该名称的新列表。
  • 如果您得到TypeError: '&lt;' not supported between instances of 'cython_function_or_method' and 'str',则将, remove_comments=True 添加到XMLParser 调用中
【解决方案2】:

您可以像这样对 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)

【讨论】:

    【解决方案3】:

    谷歌搜索一个 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))
    
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-02-19
      • 2010-09-18
      • 1970-01-01
      相关资源
      最近更新 更多