python - lxml: 强制属性的特定顺序

15 投票
6 回答
11045 浏览
提问于 2025-04-17 16:08

我有一个用来写XML的脚本,这个XML是为了一个特定的第三方工具生成的。

我使用了原始的XML作为模板,确保我构建了所有正确的元素,但最终生成的XML看起来和原来的不一样。

我虽然按照相同的顺序写属性,但lxml却是按照它自己的顺序来写的。

我不太确定,但我怀疑这个第三方工具可能希望属性按照特定的顺序出现。我想解决这个问题,看看是不是属性的顺序导致了失败,还是其他原因。

源元素:

<FileFormat ID="1" Name="Development Signature" PUID="dev/1" Version="1.0" MIMEType="text/x-test-signature"> 

我的源脚本:

sig.fileformat = etree.SubElement(sig.fileformats, "FileFormat", ID = str(db.ID), Name = db.name, PUID="fileSig/{}".format(str(db.ID)), Version = "", MIMEType = "")

我得到的XML:

<FileFormat MIMEType="" PUID="fileSig/19" Version="" Name="Printer Info File" ID="19">

有没有办法限制它们的写入顺序呢?

6 个回答

16

有序字典的属性

从 lxml 3.3.3 版本开始(可能在更早的版本也适用),你可以把一个 有序字典(OrderedDict) 作为属性传递给 lxml.etree.(Sub)Element 的构造函数,这样在使用 lxml.etree.tostring(root) 时,属性的顺序会被保留:

sig.fileformat = etree.SubElement(sig.fileformats, "FileFormat", OrderedDict([("ID",str(db.ID)), ("Name",db.name), ("PUID","fileSig/{}".format(str(db.ID))), ("Version",""), ("MIMEType","")]))

需要注意的是,ElementTree API(xml.etree.ElementTree)即使你给 xml.etree.ElementTree.(Sub)Element 的构造函数传递了一个 OrderedDict,也不会保留属性的顺序!

更新:还要注意,使用 lxml.etree.(Sub)Element 的构造函数中的 **extra 参数来指定属性时,也不会保留属性的顺序:

>>> from lxml.etree import Element, tostring
>>> from collections import OrderedDict
>>> root = Element("root", OrderedDict([("b","1"),("a","2")])) # attrib parameter
>>> tostring(root)
b'<root b="1" a="2"/>' # preserved
>>> root = Element("root", b="1", a="2") # **extra parameter
>>> tostring(root)
b'<root a="2" b="1"/>' # not preserved
19

看起来lxml会按照你设置属性的顺序来保存这些属性:

>>> from lxml import etree as ET
>>> x = ET.Element("x")
>>> x.set('a', '1')
>>> x.set('b', '2')
>>> ET.tostring(x)
'<x a="1" b="2"/>'
>>> y= ET.Element("y")
>>> y.set('b', '2')
>>> y.set('a', '1')
>>> ET.tostring(y)
'<y b="2" a="1"/>'

需要注意的是,当你使用ET.SubElement()这个构造函数传递属性时,Python会创建一个关键字参数的字典,并把这个字典传给lxml。这就导致了你在源文件中设置的顺序会丢失,因为Python的字典是无序的(或者说,它们的顺序是由字符串的哈希值决定的,而这个哈希值在不同的平台上可能会不同,甚至在同一次执行中也可能会不同)。

6

属性顺序和可读性

正如评论者提到的,XML中的属性顺序没有语义意义,也就是说,它不会改变元素的含义:

<tag attr1="val1" attr2="val2"/>

<!-- means the same thing as: -->

<tag attr2="val2" attr1="val1"/>

在SQL中也有类似的特性,列的顺序不会改变表定义的含义。XML属性和SQL列都是一个集合(而不是有序集合),所以我们只能“官方”地说这些属性或列是否在集合中存在。

不过,属性的顺序对人类的可读性确实有影响。在一些情况下,比如这些结构被写成文本(例如源代码)并需要被理解时,仔细安排顺序是非常有意义的。

典型的解析器行为

任何把属性顺序视为重要的XML解析器都不符合XML标准。这并不是说这种情况不会发生,但根据我的经验,这种情况确实很少见。不过,根据你提到的工具的来源,测试一下这种可能性也是值得的。

据我所知,lxml没有机制来指定属性在序列化的XML中出现的顺序,如果它有这样的功能,我会感到很惊讶。

为了测试这种行为,我会倾向于写一个基于文本的模板,生成足够的XML来进行测试:

id = 1
name = 'Development Signature'
puid = 'dev/1'
version = '1.0'
mimetype = 'text/x-test-signature'

template = ('<FileFormat ID="%d" Name="%s" PUID="%s" Version="%s" '
            'MIMEType="%s">')

xml = template % (id, name, puid, version, mimetype)

撰写回答