32

我使用 ElementTree 在 python 中编写了一个相当简单的过滤器来处理一些 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)

合理还是愚蠢?


相关链接:

4

12 回答 12

25

在@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)

# =======================================================================

然后在您的代码中:

tree = ET.parse(pathToFile, OrderedXMLTreeBuilder())
于 2015-06-17T21:19:07.517 回答
19

没有。ElementTree 使用字典来存储属性值,因此它本质上是无序的。

甚至 DOM 也不能保证您的属性排序,并且 DOM 比 ElementTree 公开了更多的 XML 信息集细节。(有一些 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')])

看起来很有希望。

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

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

这看起来应该归咎于ElementTree._write

            items.sort() # lexical order

子类化或猴子修补会很烦人,因为它就在一个大方法的中间。

除非你做了一些讨厌的事情,比如 subclassOrderedDict和 hackitems来返回一个特殊的子类,list它会忽略对sort(). 不,可能情况更糟,我应该在想出比这更可怕的事情之前上床睡觉。

于 2010-04-30T01:16:37.903 回答
13

最好的选择是使用lxmlhttp://lxml.de/ 安装 lxml 并切换库对我来说很神奇。

#import xml.etree.ElementTree as ET
from lxml import etree as ET
于 2018-01-23T17:32:40.097 回答
9

是的,使用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 与标准xml.etree.ElementTree具有一些良好的 API 兼容性

于 2016-01-01T21:50:27.500 回答
6

这已在 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" />'
于 2020-02-11T20:25:40.883 回答
5

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

答:谷歌是你的朋友。在“xml diff”=> this上搜索的第一个结果。还有几种可能。

于 2010-04-30T02:01:24.793 回答
3

XML 建议的第 3.1 节:

请注意,开始标签或空元素标签中属性规范的顺序并不重要。

任何依赖于 XML 元素中属性顺序的系统都会崩溃。

于 2010-05-01T08:09:20.887 回答
3

这是部分解决方案,适用于发出 xml 并且需要可预测顺序的情况。它不解决往返解析和写入。2.7 和 3.x 都使用sorted()强制属性排序。因此,此代码结合使用 OrderedDictionary 来保存属性将保留 xml 输出的顺序以匹配用于创建元素的顺序。

from collections import OrderedDict
from xml.etree import ElementTree as ET

# Make sorted() a no-op for the ElementTree module
ET.sorted = lambda x: x

try:
    # python3 use a cPython implementation by default, prevent that
    ET.Element = ET._Element_Py
    # similarly, override SubElement method if desired
    def SubElement(parent, tag, attrib=OrderedDict(), **extra):
        attrib = attrib.copy()
        attrib.update(extra)
        element = parent.makeelement(tag, attrib)
        parent.append(element)
        return element
    ET.SubElement = SubElement
except AttributeError:
    pass  # nothing else for python2, ElementTree is pure python

# Make an element with a particular "meaningful" ordering
t = ET.ElementTree(ET.Element('component',
                       OrderedDict([('grp','foo'),('name','bar'),
                                    ('class','exec'),('arch','x86')])))
# Add a child element
ET.SubElement(t.getroot(),'depend',
              OrderedDict([('grp','foo'),('name','util1'),('class','lib')]))  
x = ET.tostring(n)
print (x)
# Order maintained...
# <component grp="foo" name="bar" class="exec" arch="x86"><depend grp="foo" name="util1" class="lib" /></component>

# Parse again, won't be ordered because Elements are created
#   without ordered dict
print ET.tostring(ET.fromstring(x))
# <component arch="x86" name="bar" grp="foo" class="exec"><depend name="util1" grp="foo" class="lib" /></component>

将 XML 解析为元素树的问题在于,代码在内部创建了dict传递给 Element() 的纯 s,此时顺序丢失。没有等效的简单补丁是可能的。

于 2017-11-21T21:21:26.843 回答
2

有你的问题。首先寻找一些Python脚本来规范化,没有找到任何人。然后开始考虑做一个。终于xmllint解决了。

于 2013-06-18T09:03:48.440 回答
0

我使用了上面接受的答案,两个陈述:

ET._serialize_xml = _serialize_xml
ET._serialize['xml'] = _serialize_xml

虽然这固定了每个节点中的排序,但从现有节点的副本插入的新节点上的属性排序无法在没有深度复制的情况下保留。注意重用节点来创建其他节点......在我的例子中,我有一个具有多个属性的元素,所以我想重用它们:

to_add = ET.fromstring(ET.tostring(contract))
to_add.attrib['symbol'] = add
to_add.attrib['uniqueId'] = add
contracts.insert(j + 1, to_add)

fromstring(tostring)重新排序内存中的属性。它可能不会导致属性的 alpha 排序字典,但它也可能没有预期的顺序。

to_add = copy.deepcopy(contract)
to_add.attrib['symbol'] = add
to_add.attrib['uniqueId'] = add
contracts.insert(j + 1, to_add)

现在排序仍然存在。

于 2018-07-30T01:23:14.280 回答
0

我建议使用 LXML(其他人也有)。如果您需要保留属性的顺序以遵守 c14n v1 或 v2 标准(https://www.w3.org/TR/xml-c14n2/)(即增加字典顺序),lxml 通过传递很好地支持这一点一种输出方法(参见https://lxml.de/api.html的标题 C14N )

例如:

from lxml import etree as ET 
element = ET.Element('Test', B='beta', Z='omega', A='alpha') 
val = ET.tostring(element, method="c14n") 
print(val)
于 2021-05-04T16:24:11.870 回答
-2

通过在 python 3.8 版本中运行 python 脚本,我们可以保留 xml 文件中属性的顺序。

于 2020-06-18T11:25:06.030 回答