是否可以指示 ElementTree 保留属性的顺序?

2025-03-21 09:06:00
admin
原创
38
摘要:问题描述:我用 Python 编写了一个相当简单的过滤器,使用 ElementTree 来整理一些 xml 文件的上下文。它或多或少能起作用。但是它会重新排序各种标签的属性,而我希望它不要这样做。有人知道我可以按下一个开关让它们保持指定的顺序吗?背景我正在使用并开发一个粒子物理工具,它有一个基于 xml 文件...

问题描述:

我用 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)

合理还是愚蠢?


相关链接:

  • 如何使用 Python xml.sax 获取元素属性列表的顺序?

  • 使用 minidom 修改时保留属性的顺序


解决方案 1:

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

解决方案 2:

不是。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

子类化或 monkey-patching 将会很烦人,因为它就在一个大方法的中间。

除非你做了一些肮脏的事情,比如子类化OrderedDict和 hack items,返回一个list忽略对的特殊子类sort()。不,那可能更糟糕,我应该睡觉,以免想出比这更可怕的事情。

解决方案 3:

最好的选择是使用lxmlhttp://lxml.de/
安装 lxml 并切换库对我来说就产生了神奇的效果。

#import xml.etree.ElementTree as ET
from lxml import etree as ET

解决方案 4:

是的,使用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 兼容性

解决方案 5:

此问题已在 Python 3.8 中“修复”。我找不到任何关于此问题的说明,但现在可以正常工作了。

D:    mpetree_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:    mpetree_order>C:Python37-64python.exe etree_order.py
b'<a aaa="1" bbb="2" ccc="3" />'

D:    mpetree_order>c:Python38-64python.exe etree_order.py
b'<a aaa="1" ccc="3" bbb="2" />'

解决方案 6:

问题错误。应该是:“我在哪里可以找到一个diff可以合理地处理 XML 文件的小工具?”

答案:Google 是你的朋友。搜索“xml diff”=> this 的第一个结果。还有其他一些可能。

解决方案 7:

来自XML 建议书第 3.1 节:

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

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

解决方案 8:

对于发出 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 解析为元素树的问题在于,代码会在内部创建普通的dicts 并将其传递给 Element(),此时顺序就会丢失。没有等效的简单补丁。

解决方案 9:

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

解决方案 10:

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

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)在内存中对属性进行重新排序。这可能不会产生按字母顺序排序的属性字典,但也可能不具有预期的顺序。

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

现在,该顺序仍然存在。

解决方案 11:

我建议使用 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)

解决方案 12:

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

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   2482  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1533  
  PLM(产品生命周期管理)项目对于企业优化产品研发流程、提升产品质量以及增强市场竞争力具有至关重要的意义。然而,在项目推进过程中,范围蔓延是一个常见且棘手的问题,它可能导致项目进度延迟、成本超支以及质量下降等一系列不良后果。因此,有效避免PLM项目范围蔓延成为项目成功的关键因素之一。以下将详细阐述三大管控策略,助力企业...
plm系统   0  
  PLM(产品生命周期管理)项目管理在企业产品研发与管理过程中扮演着至关重要的角色。随着市场竞争的加剧和产品复杂度的提升,PLM项目面临着诸多风险。准确量化风险优先级并采取有效措施应对,是确保项目成功的关键。五维评估矩阵作为一种有效的风险评估工具,能帮助项目管理者全面、系统地评估风险,为决策提供有力支持。五维评估矩阵概述...
免费plm软件   0  
  引言PLM(产品生命周期管理)开发流程对于企业产品的全生命周期管控至关重要。它涵盖了从产品概念设计到退役的各个阶段,直接影响着产品质量、开发周期以及企业的市场竞争力。在当今快速发展的科技环境下,客户对产品质量的要求日益提高,市场竞争也愈发激烈,这就使得优化PLM开发流程成为企业的必然选择。缺陷管理工具和六西格玛方法作为...
plm产品全生命周期管理   0  
热门文章
项目管理软件有哪些?
曾咪二维码

扫码咨询,免费领取项目管理大礼包!

云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用