162

「findall」メソッドを使用して、ElementTree モジュール内のソース xml ファイルのいくつかの要素を見つけたいと考えています。

ただし、ソース xml ファイル (test.xml) には名前空間があります。サンプルとして xml ファイルの一部を切り捨てます。

<?xml version="1.0" encoding="iso-8859-1"?>
<XML_HEADER xmlns="http://www.test.com">
    <TYPE>Updates</TYPE>
    <DATE>9/26/2012 10:30:34 AM</DATE>
    <COPYRIGHT_NOTICE>All Rights Reserved.</COPYRIGHT_NOTICE>
    <LICENSE>newlicense.htm</LICENSE>
    <DEAL_LEVEL>
        <PAID_OFF>N</PAID_OFF>
        </DEAL_LEVEL>
</XML_HEADER>

サンプルの python コードは次のとおりです。

from xml.etree import ElementTree as ET
tree = ET.parse(r"test.xml")
el1 = tree.findall("DEAL_LEVEL/PAID_OFF") # Return None
el2 = tree.findall("{http://www.test.com}DEAL_LEVEL/{http://www.test.com}PAID_OFF") # Return <Element '{http://www.test.com}DEAL_LEVEL/PAID_OFF' at 0xb78b90>

動作はしますが、"{http://www.test.com}" という名前空間があるため、各タグの前に名前空間を追加するのは非常に不便です。

「find」、「findall」などのメソッドを使用するときに名前空間を無視するにはどうすればよいですか?

4

11 に答える 11

71

XML ドキュメント自体を変更する代わりに、それを解析して結果のタグを変更することをお勧めします。このようにして、複数の名前空間と名前空間エイリアスを処理できます。

from io import StringIO  # for Python 2 import from StringIO instead
import xml.etree.ElementTree as ET

# instead of ET.fromstring(xml)
it = ET.iterparse(StringIO(xml))
for _, el in it:
    prefix, has_namespace, postfix = el.tag.partition('}')
    if has_namespace:
        el.tag = postfix  # strip all namespaces
root = it.root

これは、ここでの議論に基づいています: http://bugs.python.org/issue18304

更新: 名前空間がない場合でもタグ名を取得するようにするrpartition代わりに。したがって、次のように要約できます。partitionpostfix

for _, el in it:
    _, _, el.tag = el.tag.rpartition('}') # strip ns
于 2014-09-18T19:37:36.667 に答える
46

解析する前に xml から xmlns 属性を削除すると、ツリー内の各タグの先頭に名前空間が追加されません。

import re

xmlstring = re.sub(' xmlns="[^"]+"', '', xmlstring, count=1)
于 2013-03-26T15:44:24.643 に答える
15

属性から名前空間も削除する @nonagon 回答 (タグから名前空間を削除する) の拡張を次に示します。

import io
import xml.etree.ElementTree as ET

# instead of ET.fromstring(xml)
it = ET.iterparse(io.StringIO(xml))
for _, el in it:
    if '}' in el.tag:
        el.tag = el.tag.split('}', 1)[1]  # strip all namespaces
    for at in list(el.attrib.keys()): # strip namespaces of attributes too
        if '}' in at:
            newat = at.split('}', 1)[1]
            el.attrib[newat] = el.attrib[at]
            del el.attrib[at]
root = it.root

明らかに、これは XML の永続的な改ざんですが、一意ではないタグ名がなく、元の名前空間を必要とするファイルを作成しないため、それが許容できる場合は、アクセスがはるかに簡単になります。

于 2015-11-30T11:21:06.370 に答える
14

ericspod による回答の改善:

解析モードをグローバルに変更する代わりに、これを with コンストラクトをサポートするオブジェクトにラップできます。

from xml.parsers import expat

class DisableXmlNamespaces:
    def __enter__(self):
            self.oldcreate = expat.ParserCreate
            expat.ParserCreate = lambda encoding, sep: self.oldcreate(encoding, None)
    def __exit__(self, type, value, traceback):
            expat.ParserCreate = self.oldcreate

これは、次のように使用できます

import xml.etree.ElementTree as ET
with DisableXmlNamespaces():
     tree = ET.parse("test.xml")

この方法の優れた点は、with ブロックの外側にある無関係なコードの動作を変更しないことです。たまたまexpatを使用していたericspodのバージョンを使用した後、無関係のライブラリでエラーが発生した後、これを作成することになりました。

于 2018-12-12T07:52:21.660 に答える
6

洗練された文字列書式設定構造も使用できます。

ns='http://www.test.com'
el2 = tree.findall("{%s}DEAL_LEVEL/{%s}PAID_OFF" %(ns,ns))

または、PAID_OFFがツリーの 1 つのレベルにしか表示されないことが確実な場合:

el2 = tree.findall(".//{%s}PAID_OFF" % ns)
于 2013-10-08T10:18:17.417 に答える
5

Python 3.5 では、名前空間を の引数として渡すことができますfind()。例えば ​​、

ns= {'xml_test':'http://www.test.com'}
tree = ET.parse(r"test.xml")
el1 = tree.findall("xml_test:DEAL_LEVEL/xml_test:PAID_OFF",ns)

ドキュメント リンク :- https://docs.python.org/3.5/library/xml.etree.elementtree.html#parsing-xml-with-namespaces

于 2020-09-04T16:59:19.703 に答える
3

私はこれに遅れるかもしれませんre.subが、良い解決策だとは思いません。

ただし、書き換えxml.parsers.expatは Python 3.x バージョンでは機能しません。

主な原因はxml/etree/ElementTree.py、ソース コードの下部を参照してください

# Import the C accelerators
try:
    # Element is going to be shadowed by the C implementation. We need to keep
    # the Python version of it accessible for some "creative" by external code
    # (see tests)
    _Element_Py = Element

    # Element, SubElement, ParseError, TreeBuilder, XMLParser
    from _elementtree import *
except ImportError:
    pass

ちょっと悲しいです。

解決策は、まずそれを取り除くことです。

import _elementtree
try:
    del _elementtree.XMLParser
except AttributeError:
    # in case deleted twice
    pass
else:
    from xml.parsers import expat  # NOQA: F811
    oldcreate = expat.ParserCreate
    expat.ParserCreate = lambda encoding, sep: oldcreate(encoding, None)

Python 3.6 でテスト済み。

Trytryステートメントは、コードのどこかでモジュールを 2 回リロードまたはインポートすると、次のような奇妙なエラーが発生する場合に役立ちます。

  • 最大再帰深度を超えました
  • 属性エラー: XMLParser

ところで、etree のソース コードは本当に乱雑に見えます。

于 2019-03-20T13:11:31.037 に答える
1

nonagon の回答と、関連する質問に対する mzjnの回答を組み合わせてみましょう。

def parse_xml(xml_path: Path) -> Tuple[ET.Element, Dict[str, str]]:
    xml_iter = ET.iterparse(xml_path, events=["start-ns"])
    xml_namespaces = dict(prefix_namespace_pair for _, prefix_namespace_pair in xml_iter)
    return xml_iter.root, xml_namespaces

この関数を使用して、次のことを行います。

  1. 名前空間と解析されたツリー オブジェクトの両方を取得する反復子を作成します。

  2. find()作成されたイテレータを反復処理して、後でそれぞれに渡すか、 iMom0 によって提案されたfindall()ように呼び出すことができる名前空間 dict を取得します。

  3. 解析されたツリーのルート要素オブジェクトと名前空間を返します。

xml.etree.ElementTreeソース XML の操作も結果の解析済み出力の操作もまったく必要ないため、これが最善のアプローチだと思います。

また、このパズルの本質的な部分を提供することでバルミーの答えを認めたいと思います(イテレータから解析されたルートを取得できます)。それまでは、アプリケーションで実際に XML ツリーを 2 回トラバースしました (1 回目は名前空間を取得するため、2 回目はルートを取得するため)。

于 2019-08-13T09:00:10.420 に答える