是否可以告诉ElementTree保留属性的顺序?

dmc*_*kee 22 python xml elementtree

我在python中编写了一个相当简单的过滤器,使用ElementTree来处理某些xml文件的上下文.它或多或少都有效.

但它重新排序各种标签的属性,我希望它不会那样做.

有没有人知道我可以抛出一个开关使它按指定顺序保持?

这个背景

我正在使用粒子物理工具,它具有基于xml文件的复杂但奇怪的有限配置系统.在许多方面,设置方式是各种静态数据文件的路径.这些路径硬编码到现有的xml中,并且没有用于根据环境变量设置或更改它们的工具,在我们的本地安装中,它们必须位于不同的位置.

这不是灾难,因为我们使用的组合源和构建控制工具允许我们使用本地副本隐藏某些文件.但是,即使数据字段是静态的,xml也不是,所以我编写了一个用于修复路径的脚本,但是在本地版本和主版本之间的属性重新排列差异比必要时更难阅读.


这是我第一次使用ElementTree旋转(只有我的第五或第六个python项目),所以也许我只是做错了.

为简单起见,代码看起来像这样:

tree = elementtree.ElementTree.parse(inputfile)
i = tree.getiterator()
for e in i:
    e.text = filter(e.text)
tree.write(outputfile)
Run Code Online (Sandbox Code Playgroud)

合理还是愚蠢?


相关链接:

Sne*_*oda 22

在@ bobince的答案和这两个答案的帮助下(设置属性顺序,覆盖模块方法)

我设法让这只猴子修补它很脏,我建议使用另一个更好地处理这种情况的模块,但是当这不可能时:

# =======================================================================
# Monkey patch ElementTree
import xml.etree.ElementTree as ET

def _serialize_xml(write, elem, encoding, qnames, namespaces):
    tag = elem.tag
    text = elem.text
    if tag is ET.Comment:
        write("<!--%s-->" % ET._encode(text, encoding))
    elif tag is ET.ProcessingInstruction:
        write("<?%s?>" % ET._encode(text, encoding))
    else:
        tag = qnames[tag]
        if tag is None:
            if text:
                write(ET._escape_cdata(text, encoding))
            for e in elem:
                _serialize_xml(write, e, encoding, qnames, None)
        else:
            write("<" + tag)
            items = elem.items()
            if items or namespaces:
                if namespaces:
                    for v, k in sorted(namespaces.items(),
                                       key=lambda x: x[1]):  # sort on prefix
                        if k:
                            k = ":" + k
                        write(" xmlns%s=\"%s\"" % (
                            k.encode(encoding),
                            ET._escape_attrib(v, encoding)
                            ))
                #for k, v in sorted(items):  # lexical order
                for k, v in items: # Monkey patch
                    if isinstance(k, ET.QName):
                        k = k.text
                    if isinstance(v, ET.QName):
                        v = qnames[v.text]
                    else:
                        v = ET._escape_attrib(v, encoding)
                    write(" %s=\"%s\"" % (qnames[k], v))
            if text or len(elem):
                write(">")
                if text:
                    write(ET._escape_cdata(text, encoding))
                for e in elem:
                    _serialize_xml(write, e, encoding, qnames, None)
                write("</" + tag + ">")
            else:
                write(" />")
    if elem.tail:
        write(ET._escape_cdata(elem.tail, encoding))

ET._serialize_xml = _serialize_xml

from collections import OrderedDict

class OrderedXMLTreeBuilder(ET.XMLTreeBuilder):
    def _start_list(self, tag, attrib_in):
        fixname = self._fixname
        tag = fixname(tag)
        attrib = OrderedDict()
        if attrib_in:
            for i in range(0, len(attrib_in), 2):
                attrib[fixname(attrib_in[i])] = self._fixtext(attrib_in[i+1])
        return self._target.start(tag, attrib)

# =======================================================================
Run Code Online (Sandbox Code Playgroud)

然后在你的代码中:

tree = ET.parse(pathToFile, OrderedXMLTreeBuilder())
Run Code Online (Sandbox Code Playgroud)

  • 哇。自从我问这个问题以来的几年里,有问题的工具已经被重新构建,以允许持久的本地覆盖,这样我原来的需求就消失了,我已经转向了不同的,如果不是更环保的牧场,甚至不使用固定的版本不再。尽管如此,我确信*有人*仍然有这种需求。 (2认同)
  • 现在是否有针对python 3.4的解决方案?etree实现是否允许改变? (2认同)
  • “另一个更好地处理这种情况的模块”你有什么具体的想法吗? (2认同)

bob*_*nce 19

不.ElementTree使用字典来存储属性值,因此它本身就是无序的.

即使DOM也不保证您的属性排序,并且DOM公开了XML信息集的更多细节,而不是ElementTree.(有些DOM确实提供了它作为一项功能,但它并不标准.)

可以修复吗?也许.这是对它的一种刺激,它在用有序的一个(collections.OrderedDict())进行解析时替换了字典.

from xml.etree import ElementTree
from collections import OrderedDict
import StringIO

class OrderedXMLTreeBuilder(ElementTree.XMLTreeBuilder):
    def _start_list(self, tag, attrib_in):
        fixname = self._fixname
        tag = fixname(tag)
        attrib = OrderedDict()
        if attrib_in:
            for i in range(0, len(attrib_in), 2):
                attrib[fixname(attrib_in[i])] = self._fixtext(attrib_in[i+1])
        return self._target.start(tag, attrib)

>>> xmlf = StringIO.StringIO('<a b="c" d="e" f="g" j="k" h="i"/>')

>>> tree = ElementTree.ElementTree()
>>> root = tree.parse(xmlf, OrderedXMLTreeBuilder())
>>> root.attrib
OrderedDict([('b', 'c'), ('d', 'e'), ('f', 'g'), ('j', 'k'), ('h', 'i')])
Run Code Online (Sandbox Code Playgroud)

看起来很有希望.

>>> s = StringIO.StringIO()
>>> tree.write(s)
>>> s.getvalue()
'<a b="c" d="e" f="g" h="i" j="k" />'
Run Code Online (Sandbox Code Playgroud)

Bah,序列化器以规范顺序输出它们.

这看起来像是责备,在ElementTree._write:

            items.sort() # lexical order
Run Code Online (Sandbox Code Playgroud)

子类化或猴子修补会让人讨厌,因为它正处于一个大方法的中间.

除非你做了像子类这样令人讨厌的东西OrderedDict而且黑客items返回一个list忽略调用的特殊子类sort().不,可能那更糟,我应该睡觉才能想出比这更可怕的东西.


Din*_*kar 6

最好的选择是使用lxmlhttp://lxml.de/ 安装lxml并只需切换该库就对我产生了魔力。

#import xml.etree.ElementTree as ET
from lxml import etree as ET
Run Code Online (Sandbox Code Playgroud)

  • thdox已经[发布了该建议](/sf/answers/2419228801/)。 (2认同)

Joh*_*hin 5

错误的问题。应该是:“在哪里可以找到diff适合XML文件使用的小工具?

答:Google是您的朋友。搜索“ xml diff” => this的第一个结果。还有更多可能。

  • 在一个完美的世界里,是的。但是,有时我们无法选择工具集的所有组件-例如,如果您的版本控制系统不能被教导在语义上对XML文件进行差异化,并且您不能更改为另一个。 (6认同)
  • 如何将工具与Github,Stash或任何其他Web界面集成到版本控制系统? (3认同)

thd*_*dox 5

是的,使用lxml

>>> from lxml import etree
>>> root = etree.Element("root", interesting="totally")
>>> etree.tostring(root)
b'<root interesting="totally"/>'
>>> print(root.get("hello"))
None
>>> root.set("hello", "Huhu")
>>> print(root.get("hello"))
Huhu
>>> etree.tostring(root)
b'<root interesting="totally" hello="Huhu"/>'
Run Code Online (Sandbox Code Playgroud)

这里是文档的直接链接,上面的示例略有改动。

还要注意,根据设计,lxml与标准xml.etree.ElementTree具有一些良好的API兼容性。

  • 没有冒犯,但问题是关于保留元素属性的顺序。lxml的文档(在您的链接上)说:“属性只是无序的名称/值对...”。我没有找到任何有关保留XML源中元素属性顺序的信息。问题的棘手部分是,与XML格式所保证的需求相比,作者具有更严格的需求-这是可以理解的,但可能不是由lxml实现的。 (3认同)
  • 您确定lxml保留属性顺序吗?该文档似乎相反。 (2认同)

tee*_*s99 5

这已在 python 3.8 中“修复”。我在任何地方都找不到关于它的任何注释,但它现在可以工作了。

D:\tmp\etree_order>type etree_order.py
import xml.etree.ElementTree as ET

a = ET.Element('a', {"aaa": "1", "ccc": "3", "bbb": "2"})

print(ET.tostring(a))
D:\tmp\etree_order>C:\Python37-64\python.exe etree_order.py
b'<a aaa="1" bbb="2" ccc="3" />'

D:\tmp\etree_order>c:\Python38-64\python.exe etree_order.py
b'<a aaa="1" ccc="3" bbb="2" />'
Run Code Online (Sandbox Code Playgroud)