ElementTree可以被告知保存属性的顺序吗?

我已经写了一个相当简单的filter在python中使用ElementTree模仿一些xml文件的上下文。 它或多或less起作用。

但它重新sorting各种标签的属性,我希望它不这样做。

有谁知道一个开关,我可以扔,使它们保持在指定的顺序?

上下文为此

我正在使用一个粒子物理工具,它有一个复杂的,但奇怪的基于xml文件的configuration系统。 设置这种方式的许多事情是各种静态数据文件的path。 这些path被硬编码到现有的xml中,并且没有设置或者根据环境variables来改变它们,在我们的本地安装中它们必然在不同的地方。

这不是一场灾难,因为我们使用的源码和构build控制工具可以让我们用本地副本来遮盖某些文件。 但即使认为数据字段是静态的xml不是,所以我写了一个脚本来修复path,但与属性重新排列之间的本地和主版本比较难以阅读比必要的。


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

简单起见,代码如下所示:

tree = elementtree.ElementTree.parse(inputfile) i = tree.getiterator() for e in i: e.text = filter(e.text) tree.write(outputfile) 

合理还是愚蠢?


相关链接:

  • 我如何使用Python xml.sax获取元素属性列表的顺序?
  • 用minidom修改时保留属性的顺序

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

我设法得到这个猴子补丁是肮脏的,我build议使用另一个模块,更好地处理这种情况,但是当这不是一个可能性:

 # ======================================================================= # 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) # ======================================================================= 

然后在你的代码中:

 tree = ET.parse(pathToFile, OrderedXMLTreeBuilder()) 

不。 ElementTree使用字典来存储属性值,所以它本身是无序的。

即使DOM不能保证你的属性sorting,而且DOM比ElementTree公开了XML信息集的更多细节。 (有一些DOM提供它作为一个function,但它不是标准的。)

它可以修复吗? 也许。 这是一个刺戳它,取代了字典时parsing一个有序的( 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('<ab="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')]) 

看起来很有希望。

 >>> s = StringIO.StringIO() >>> tree.write(s) >>> s.getvalue() '<ab="c" d="e" f="g" h="i" j="k" />' 

Bah,串行器以规范的顺序输出它们。

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

  items.sort() # lexical order 

子类化或猴子修补,这将是一个恼人的,因为它是在一个大的方法中。

除非你像子类OrderedDict那样做了一些令人讨厌的事情,并且hack items返回一个特殊的子类,而忽略了对sort()调用。 不,那可能更糟了,我应该上床睡觉,然后再拿出比这更可怕的东西。

错误的问题。 应该是:“我在哪里可以find与XML文件合理工作的diff小工具?

答:Google是你的朋友。 首先search“xml diff”=> 这个结果 。 还有更多的可能。

从XMLbuild议的第3.1节:

请注意,起始标签或空白标签中的属性规格顺序并不重要。

任何依赖于XML元素中的属性顺序的系统都将中断。

有你的问题。 首先寻找一些Python脚本来封锁,没有find任何人。 然后开始考虑制作一个。 最后xmllint解决了。

是的,用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"/>' 

这里是直接链接到文档,从上面的例子稍微适应。

还要注意,lxml在devise上与标准的xml.etree.ElementTree具有良好的API兼容性