15

我有一个 XML 编写脚本,它为特定的第 3 方工具输出 XML。

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

我以相同的顺序编写属性,但 lxml 以自己的顺序编写它们。

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

源元素:

<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">

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

4

6 回答 6

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 的字典是无序的(或者,它们的顺序是由字符串哈希值确定的,这可能因平台而异,或者实际上因执行而异)。

于 2013-07-15T12:43:42.503 回答
16

OrderedDict 属性

从 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","")]))

请注意,即使您向构造函数提供 an ,ElementTree API ( xml.etree.ElementTree) 也不会保留属性顺序!OrderedDictxml.etree.ElementTree.(Sub)Element

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

>>> 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
于 2014-03-23T19:38:37.720 回答
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)
于 2013-02-17T22:58:09.107 回答
1

我已经看到 XML 的使用者期望规范化 XML 的顺序很重要。规范 XML 指定对属性进行排序:

按字典顺序递增,名称空间 URI 作为主键,本地名称作为辅助键(空的名称空间 URI 在字典上是最少的)。(https://www.w3.org/TR/xml-c14n2/第 2.6 节)

因此,如果您的应用程序期望从规范 XML 中得到的那种顺序,lxml 确实支持使用method=打印参数以规范形式输出。(见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-03-12T00:04:53.917 回答
0

您需要封装一个新字符串,它在比较时给出顺序,在打印和获取字符串时给出值。

这是一个例子:

class S:
    def __init__(self, _idx, _obj):
        self._obj = (_idx, _obj)

    def get_idx(self):
        return self._obj[0]

    def __le__(self, other):
        return self._obj[0] <= other.get_idx()

    def __lt__(self, other):
        return self._obj[0] < other.get_idx()

    def __str__(self):
        return self._obj[1].__str__()

    def __repr__(self):
        return self._obj[1].__repr__()

    def __eq__(self, other):
        if isinstance(other, str):
            return self._obj[1] == other
        elif isinstance(other, S):
            return self._obj[
                       0] == other.get_idx() and self.__str__() == other.__str__()
        else:
            return self._obj[
                0] == other.get_idx() and self._obj[1] == other

    def __add__(self, other):
        return self._obj[1] + other

    def __hash__(self):
        return self._obj[1].__hash__()

    def __getitem__(self, item):
        return self._obj[1].__getitem__(item)

    def __radd__(self, other):
        return other + self._obj[1]

list_sortable = ['c', 'b', 'a']
list_not_sortable = [S(0, 'c'), S(0, 'b'), S(0, 'a')]
print("list_sortable ---- Before sort ----")
for ele in list_sortable:
    print(ele)
print("list_not_sortable ---- Before sort ----")
for ele in list_not_sortable:
    print(ele)
list_sortable.sort()
list_not_sortable.sort()
print("list_sortable ---- After sort ----")
for ele in list_sortable:
    print(ele)
print("list_not_sortable ---- After sort ----")
for ele in list_not_sortable:
    print(ele)

运行结果:

list_sortable ---- Before sort ----
c
b
a
list_not_sortable ---- Before sort ----
c
b
a
list_sortable ---- After sort ----
a
b
c
list_not_sortable ---- After sort ----
c
b
a
dict_sortable ---- After sort ----
a 3
b 2
c 1
dict_not_sortable ---- After sort ----
c 1
b 2
a 3
于 2021-08-31T01:28:45.017 回答
0

lxml 在后台使用 libxml2。它保留了属性顺序,这意味着对于单个元素,您可以像这样对它们进行排序:

x = etree.XML('<x a="1" b="2" d="4" c="3"><y></y></x>')
sorted_attrs = sorted(x.attrib.items())
x.attrib.clear()
x.attrib.update(sorted_attrs)

如果您希望它们全部排序,则不是很有帮助。如果您希望它们全部排序,您可以使用c14n2输出方法(XML 规范化版本 2):

>>> x = etree.XML('<x a="1" b="2" d="4" c="3"><y></y></x>')
>>> etree.tostring(x, method="c14n2")
b'<x a="1" b="2" c="3" d="4"><y></y></x>'

这将对属性进行排序。不幸的是,它有 ignoring 的缺点,pretty_print如果您想要人类可读的 XML,这不是很好。

如果您使用c14n2,那么 lxml 将使用自定义 Python 序列化代码来编写sorted(x.attrib.items()为所有属性调用自身的 XML。如果您不这样做,那么它将改为调用xmlNodeDumpOutput()不支持排序属性但支持漂亮打印的 libxml2 函数。

因此,唯一的解决方案是手动遍历 XML 树并对所有属性进行排序,如下所示:

from lxml import etree

x = etree.XML('<x a="1" b="2" d="4" c="3"><y z="1" a="2"><!--comment--></y></x>')
for el in x.iter(etree.Element):
    sorted_attrs = sorted(el.attrib.items())
    el.attrib.clear()
    el.attrib.update(sorted_attrs)

etree.tostring(x, pretty_print=True)

# b'<x a="1" b="2" c="3" d="4">\n  <y a="2" z="1">\n    <!--comment-->\n  </y>\n</x>\n'
于 2022-01-14T11:19:52.613 回答