代码之家  ›  专栏  ›  技术社区  ›  dmckee --- ex-moderator kitten

元素树可以被告知保留属性的顺序吗?

  •  19
  • dmckee --- ex-moderator kitten  · 技术社区  · 16 年前

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

    合理还是愚蠢?


    相关链接:

    9 回复  |  直到 7 年前
        1
  •  19
  •   Community Mohan Dere    8 年前

    在@bobince的回答和这两个问题的帮助下( setting attribute order , overriding module methods )

    我设法修补了这只猴子,它很脏,我建议使用另一个模块来更好地处理这种情况,但如果不可能的话:

    # =======================================================================
    # 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
  •  17
  •   MattDMo    11 年前

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

    子类化或猴子修补将是恼人的,因为它正处于一个大方法的中间。

    除非你做了一些下流的事 OrderedDict 黑客 items 返回的特殊子类 list 忽略了对 sort() . 不,可能更糟了,我应该在想到比这更可怕的事情之前上床睡觉。

        3
  •  4
  •   John Machin Santi    16 年前

    错误的问题。应该是:“我在哪里能找到 diff 对XML文件敏感的小工具?

    答:谷歌是你的朋友。搜索“xml diff”的第一个结果=gt; this . 还有一些可能性。

        4
  •  4
  •   thdox    10 年前

    是的,带着 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"/>'
    

    这里是直接的 link 对上述示例稍作修改的文档。

    另外请注意,根据设计,LXML与标准具有良好的API兼容性。 xml.etree.ElementTree

        5
  •  3
  •   Robert Rossney    16 年前

    来自第3.1节 the XML recommendation :

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

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

        6
  •  2
  •   1737973    12 年前

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

        7
  •  2
  •   Marvin    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解析为元素树的问题是,代码在内部创建的是plain dict 传递到element()的,此时顺序将丢失。不可能有等效的简单补丁。

        8
  •  2
  •   Dinesh Jeyasankar    8 年前

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

    #import xml.etree.ElementTree as ET
    from lxml import etree as ET
    
        9
  •  0
  •   TinCupChalice    7 年前

    我使用了上面接受的答案,两种说法都是:

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

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

    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排序dict,但也可能没有预期的顺序。

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

    现在命令仍然存在。