32

いくつかのxmlファイルのコンテキストを変更するためにElementTreeを使用して、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)

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


関連リンク:

4

12 に答える 12

25

@bobinceの答えとこれら2つの助けを借りて(属性の順序を設定しモジュールメソッドをオーバーライドします)

私はなんとかこのモンキーにパッチを当てることができました。それは汚れていて、このシナリオをより適切に処理する別のモジュールを使用することをお勧めしますが、それが不可能な場合は次のようになります。

# =======================================================================
# 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())
于 2015-06-17T21:19:07.517 に答える
19

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

于 2010-04-30T01:16:37.903 に答える
13

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

#import xml.etree.ElementTree as ET
from lxml import etree as ET
于 2018-01-23T17:32:40.097 に答える
9

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

于 2016-01-01T21:50:27.500 に答える
6

これはPython3.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" />'
于 2020-02-11T20:25:40.883 に答える
5

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

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

于 2010-04-30T02:01:24.793 に答える
3

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

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

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

于 2010-05-01T08:09:20.887 に答える
3

これは、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を要素ツリーに解析する際の問題は、コードが内部でプレーンを作成dictし、それがElement()に渡されることです。この時点で、順序が失われます。同等の単純なパッチはありません。

于 2017-11-21T21:21:26.843 に答える
2

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

于 2013-06-18T09:03:48.440 に答える
0

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

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)

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

于 2018-07-30T01:23:14.280 に答える
0

LXMLを使用することをお勧めします(他の人もそうです)。c14n v1またはv2標準(https://www.w3.org/TR/xml-c14n2/)に準拠するために属性の順序を保持する必要がある場合(つまり、辞書式順序を増やす必要がある場合)、lxmlはこれを非常にうまくサポートします。出力メソッド( 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-05-04T16:24:11.870 に答える
-2

Python 3.8バージョンでPythonスクリプトを実行することにより、xmlファイル内の属性の順序を保持できます。

于 2020-06-18T11:25:06.030 に答える