web-dev-qa-db-ja.com

ElementTreeに属性の順序を保持するように指示できますか?

ElementTreeを使用していくつかのxmlファイルのコンテキストを変更するpython)で、かなり単純なフィルターを作成しました。これは、多かれ少なかれ機能します。

ただし、さまざまなタグの属性を並べ替えるので、そうしないようにします。

指定された順序に保つために投げることができるスイッチを誰かが知っていますか?

このためのコンテキスト

私は、xmlファイルに基づく複雑ですが奇妙に制限された構成システムを持つ素粒子物理学ツールを使用して作業しています。そのように設定された多くのものの中には、さまざまな静的データファイルへのパスがあります。これらのパスは既存のxmlにハードコードされており、環境変数に基づいてパスを設定または変更する機能はありません。ローカルインストールでは、これらのパスは必然的に別の場所にあります。

私たちが使用しているソース制御ツールとビルド制御ツールを組み合わせることで、特定のファイルをローカルコピーでシャドウイングできるため、これは問題ではありません。しかし、データフィールドが静的であるとはいえ、xmlは静的ではないため、パスを修正するためのスクリプトを作成しましたが、属性の再配置により、ローカルバージョンとマスターバージョンの違いが必要以上に読みにくくなります。


ElementTreeをスピンするのはこれが初めてです(そして5番目または6番目のpythonプロジェクトのみ))ので、間違っているだけかもしれません。

簡単にするために抽象化すると、コードは次のようになります。

tree = elementtree.ElementTree.parse(inputfile)
i = tree.getiterator()
for e in i:
    e.text = filter(e.text)
tree.write(outputfile)

合理的または愚かですか?


関連リンク:

いいえ。 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をハックして、sort()の呼び出しを無視するlistの特別なサブクラスを返さない限り。いや、おそらくそれはさらに悪いことであり、それよりも恐ろしいことを思い付く前に私は寝るべきです。

19
bobince

最良のオプションは、lxmlライブラリを使用することです http://lxml.de/ lxmlをインストールし、ライブラリを切り替えるだけで私にとっての魔法。

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

間違った質問。 「XMLファイルで適切に機能するdiffガジェットはどこにありますか?

回答:Googleはあなたの友達です。 「xmldiff」での検索の最初の結果=> this 。さらにいくつかの可能性があります。

5
John Machin

はい、 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には、設計上、標準との優れたAPI互換性があることにも注意してください xml.etree.ElementTree

5
thdox

XML推奨事項 のセクション3.1から:

Start-tagまたはempty-elementタグの属性指定の順序は重要ではないことに注意してください。

XML要素の属性の順序に依存するシステムはすべて機能しなくなります。

3
Robert Rossney

これは、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()に渡されることです。この時点で、順序が失われます。同等の単純なパッチはありません。

2
Marvin

あなたの問題がありました。最初にいくつかのPython正規化するスクリプトを探しましたが、誰も見つかりませんでした。次に、作成することを考え始めました。最後に xmllint 解決しました。

2
1737973

これはpython 3.8で「修正」されました。どこにもメモが見つかりませんが、現在は機能しています。

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

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

私は上記の受け入れられた答えを両方のステートメントで使用しました:

_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)は、メモリ内の属性を並べ替えます。属性のアルファソートされたdictが得られない可能性がありますが、期待される順序がない可能性もあります。

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

これで、順序が維持されます。

0
TinCupChalice