【问题标题】:Using python, sort XML alphabetically except one element使用python,按字母顺序对XML进行排序,除了一个元素
【发布时间】:2017-09-06 16:34:01
【问题描述】:

我正在尝试按字母顺序对 XML 进行排序,同时确保特定元素位于顶部。我已设法按字母顺序对其进行排序,但我无法保留该元素。这是我目前所拥有的:

from lxml import etree

data = """
<Example xmlns="http://www.example.org">
    <E>
        <A>A</A>
        <B>B</B>
        <C>C</C>
    </E>
    <B>B</B>
    <D>D</D>
    <A>A</A>
    <C>C</C>
    <F>F</F>
</Example>
"""
doc = etree.XML(data,etree.XMLParser(remove_blank_text=True))

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

print etree.tostring(doc,pretty_print=True)

结果是:

<Example xmlns="http://www.example.org">
  <A>A</A>
  <B>B</B>
  <C>C</C>
  <D>D</D>
  <E>
    <A>A</A>
    <B>B</B>
    <C>1</C>
  </E>
  <F>F</F>
</Example>

无论如何我可以阻止&lt;E&gt;&lt;/E&gt; 部分及其内容移动吗?

【问题讨论】:

  • &lt;E&gt; 是什么使它成为不应该排序的元素?是因为它有子节点吗?
  • @James Nope,子节点无关紧要。我想让 XML 符合给定的模式,这要求 保持在顶部,但我希望按字母顺序对其余部分进行排序。

标签: python xml sorting alphabetical


【解决方案1】:

您至少可以通过两种方式处理此问题。您可以对所有内容进行排序,然后通过自定义排序功能将&lt;E&gt; 强制到顶部。此外,您可以拆分要排序的元素,对其进行排序,并将它们附加到未排序元素的末尾。

自定义排序:

使用渐进式代码点对文本进行排序。您可以使用ord() 获取单个字符的代码点。最低的打印字符是制表符。因此,对于排序,我们可以告诉 python 对所有元素进行正常排序,除非 tag&lt;E&gt;,然后使用 tab 进行排序,它将首先排序。

有一些额外的代码来处理命名空间。

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

for parent in doc.xpath('//*[./*]'):
    parent[:] = sorted(parent,key=lambda x: x.tag if x.tag!='{'+ns[None]+'}E' else '\t')

print(etree.tostring(doc,pretty_print=True).decode('ascii'))

<Example xmlns="http://www.example.org">
  <E>
    <A>A</A>
    <B>B</B>
    <C>C</C>
  </E>
  <A>A</A>
  <B>B</B>
  <C>C</C>
  <D>D</D>
  <F>F</F>
</Example>

拆分、应用、合并

这里我们将父级拆分为两个列表,对第二个列表进行排序,然后将它们合并。

doc = etree.XML(data,etree.XMLParser(remove_blank_text=True))
ns = doc.nsmap
for parent in doc.xpath('//*[./*]'):
    to_sort = (e for e in parent if e.tag!='{'+ns[None]+'}E')
    non_sort = (e for e in parent if e.tag=='{'+ns[None]+'}E')
    parent[:] = list(non_sort) + sorted(to_sort, key=lambda e: e.tag)
print(etree.tostring(doc,pretty_print=True).decode('ascii'))

<Example xmlns="http://www.example.org">
  <E>
    <A>A</A>
    <B>B</B>
    <C>C</C>
  </E>
  <A>A</A>
  <B>B</B>
  <C>C</C>
  <D>D</D>
  <F>F</F>
</Example>

【讨论】:

  • 太棒了。感谢这两种方法!我喜欢第二个。当我尝试第二种方法时,它还会对 non_sort 列表中的子节点进行排序。它应该对该列表进行排序吗?我认为它不会,因为它不包含在 sorted() 函数中。我忘了把它包括在问题中,但我实际上并不想对 中的子节点进行排序,所以这是理想的。
【解决方案2】:

它可以使用以下方式,但似乎无法到达简单标签,因此它使用长标签,包括xmlns部分:

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

    for parent in doc.xpath('//*[./*]'):
        parent[:] = sorted(parent,
                           key=lambda x: (not x.tag =='{http://www.example.org}E', x.tag))

    print(etree.tounicode(doc,pretty_print=True))

此代码将输出:

<Example xmlns="http://www.example.org">
  <E>
    <A>A</A>
    <B>B</B>
    <C>C</C>
  </E>
  <A>A</A>
  <B>B</B>
  <C>C</C>
  <D>D</D>
  <F>F</F>
</Example>
   </Example>\n'

以下代码只是输出这些长标签以了解它们的外观:

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

    for parent in doc.xpath('//*[./*]'):
        for item in parent:
            print(item.tag)

    {http://www.example.org}E
    {http://www.example.org}B
    {http://www.example.org}D
    {http://www.example.org}A
    {http://www.example.org}C
    {http://www.example.org}F
    {http://www.example.org}A
    {http://www.example.org}B
    {http://www.example.org}C

另一种方法是使用辅助函数来解析标签以使其更具可读性:

def normalize(name):
    if name[0] == "{":
        uri, tag = name[1:].split("}")
        return tag
    else:
        return name

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

for parent in doc.xpath('//*[./*]'):
    parent[:] = sorted(parent,
                       key=lambda x: (not normalize(x.tag) == 'E', x.tag))

【讨论】:

  • 太棒了,谢谢!是否有自定义排序 内的排序?忘了包括这样一个事实,即里面的子节点的顺序必须是特定的,而不是按字母顺序。
猜你喜欢
  • 1970-01-01
  • 2015-04-04
  • 2015-12-03
  • 1970-01-01
  • 1970-01-01
  • 2016-12-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多