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

python-lxml:强制属性的特定顺序,python,xml,lxml,Python,Xml,Lxml,我有一个XML编写脚本,可以为特定的第三方工具输出XML 我使用了原始XML作为模板,以确保生成了所有正确的元素,但最终的XML与原始XML不一样 我以相同的顺序写入属性,但lxml以自己的顺序写入属性 我不确定,但我怀疑第三部分工具希望属性以特定的顺序出现,我想解决这个问题,这样我可以看看是attrib顺序导致它失败,还是其他原因 源元素: <FileFormat ID="1" Name="Development Signature" PUID="dev/1" Version="1.0"

我有一个XML编写脚本,可以为特定的第三方工具输出XML

我使用了原始XML作为模板,以确保生成了所有正确的元素,但最终的XML与原始XML不一样

我以相同的顺序写入属性,但lxml以自己的顺序写入属性

我不确定,但我怀疑第三部分工具希望属性以特定的顺序出现,我想解决这个问题,这样我可以看看是attrib顺序导致它失败,还是其他原因

源元素:

<FileFormat ID="1" Name="Development Signature" PUID="dev/1" Version="1.0" MIMEType="text/x-test-signature"> 
我的结果XML:

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


有没有办法限制它们的书写顺序

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

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

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

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

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"/>'
>>从lxml导入etree作为ET
>>>x=等元素(“x”)
>>>x.set('a','1')
>>>x.set('b','2')
>>>ET.tostring(x)
''
>>>y=等元素(“y”)
>>>y.set('b','2')
>>>y.set('a','1')
>>>ET.tostring(y)
''
请注意,当您使用ET.SubElement()构造函数传递属性时,Python会构造一个关键字参数字典,并将该字典传递给lxml。这会丢失源文件中的任何顺序,因为Python的字典是无序的(或者说,它们的顺序是由字符串散列值决定的,这些值可能因平台而异,或者实际上因执行而异)。

OrderedDict属性 从lxml 3.3.3开始(可能也在早期版本中),您可以将一组属性传递给
lxml.etree.(Sub)元素
构造函数,并且在使用
lxml.etree.tostring(root)
时将保留顺序:

请注意,ElementTree API(
xml.etree.ElementTree
)不
保留属性顺序,即使您向
xml.etree.ElementTree.(Sub)元素
构造函数提供了
OrderedDict

更新:还请注意,使用
lxml.etree.(Sub)元素的
**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
来自lxml.etree导入元素tostring的
>>
>>>从集合导入订单
>>>root=Element(“root”,OrderedDict([((“b”,“1”),(“a”,“2”)])#attrib参数
>>>tostring(根)
b‘#保留
>>>root=元素(“root”,b=“1”,a=“2”)#**额外参数
>>>tostring(根)
b“”未保留

我已经看到,在XML的消费者希望规范化XML的地方,顺序是很重要的。规范XML指定对属性进行排序:

以名称空间URI为主的字典顺序递增 密钥和本地名称作为辅助密钥(使用空命名空间URI 词典编纂(最少)。(第2.6节)

因此,如果您的应用程序期望从规范化XML中获得某种顺序,那么lxml确实支持使用
方法=
参数以规范化形式输出。(见本章标题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)

属性顺序在XML中没有意义。值得检查一下这个工具是否真的那么蹩脚。您可以使用一个模板系统,比如cheetah,在这里您可以控制属性顺序。lxml有XSLT,它的优点是您可以将现有的lxml文档传递给它。所以你的问题可能在其他地方。我理解它在XML中毫无意义,问题是它对工具是否有意义(因此,它在lxml中是否可管理)。不管怎样,我可以根据源代码伪造一些东西。如果你不提到这个工具,我们就没有希望找到它的问题。我继续说,发现问题只是它的扩展名是大写的.xml。。。我研究了attrib顺序,可以确认它对工具没有影响。“属性顺序在XML中没有语义意义”-嗯,这可能是真的,但如果您想要实现自定义规范化方法(c14n),例如对元素签名,那么属性的顺序就很重要。有趣的是,PHPDOM使用所谓的“属性节点”,可以在特定属性之前/之后插入。在lxml中,这是不可能的-(“属性顺序在XML中没有语义意义”注意,XML不仅适用于计算机,也适用于人类(不幸的是)还必须阅读它…当你有一个配置文件时,顺序很可能很重要。在这种情况下编写时保持顺序是一个需要的功能。一个有趣的实验是尝试将属性的
orderedict
传递给
子元素
。我相信我曾经尝试过传递orderedic通过
f(**od)
将ct转换为函数,但不幸的是,Python将OrderedDict转换为常规dict,并丢失所有订单信息。@Marcin:这确实有效,请参见我几个月前的答案(我现在终于有足够的声誉在这里发表评论)@Marius:
子元素
构造函数实现不会解包已传递的
attrib
字典(但是,是的,有一个可选的
**额外的
参数,它允许“逐个”指定属性,并且不保留规范顺序)。我相信这是问题的正确答案。请注意,lxml文档建议使用“element.set(name,value)”而不是“attrib[name]=value”,这也保持了秩序,并且似乎不会改变我的性能测试(在500k文件上的项目上下文中)与**kwargs的使用。参考:“属性库:元素属性字典。在可能的情况下,使用get()、set()、keys()、values()和items()访问元素属性。”似乎它不适用于
子元素
,仅适用于
元素
sig.fileformat = etree.SubElement(sig.fileformats, "FileFormat", OrderedDict([("ID",str(db.ID)), ("Name",db.name), ("PUID","fileSig/{}".format(str(db.ID))), ("Version",""), ("MIMEType","")]))
>>> 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
from lxml import etree as ET 
element = ET.Element('Test', B='beta', Z='omega', A='alpha') 
val = ET.tostring(element, method="c14n") 
print(val)