'ElementTree'を介してPythonで名前空間を持つXMLを解析する


163

Pythonを使用して解析したい次のXMLがありますElementTree

<rdf:RDF xml:base="http://dbpedia.org/ontology/"
    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
    xmlns:owl="http://www.w3.org/2002/07/owl#"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
    xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
    xmlns="http://dbpedia.org/ontology/">

    <owl:Class rdf:about="http://dbpedia.org/ontology/BasketballLeague">
        <rdfs:label xml:lang="en">basketball league</rdfs:label>
        <rdfs:comment xml:lang="en">
          a group of sports teams that compete against each other
          in Basketball
        </rdfs:comment>
    </owl:Class>

</rdf:RDF>

すべてのowl:Classタグを検索して、タグrdfs:label内のすべてのインスタンスの値を抽出します。次のコードを使用しています。

tree = ET.parse("filename")
root = tree.getroot()
root.findall('owl:Class')

名前空間のため、次のエラーが発生します。

SyntaxError: prefix 'owl' not found in prefix map

http://effbot.org/zone/element-namespaces.htmにあるドキュメントを読んでみましたが、上記のXMLには複数の名前空間がネストされているため、これを機能させることができません。

すべてのowl:Classタグを見つけるためにコードを変更する方法を教えてください。

回答:


226

ElementTreeは名前空間についてあまりスマートではありません。.find()findall()およびiterfind()メソッドに明示的な名前空間ディクショナリを指定する必要があります。これはあまり文書化されていません:

namespaces = {'owl': 'http://www.w3.org/2002/07/owl#'} # add more as needed

root.findall('owl:Class', namespaces)

プレフィックスは、渡したパラメーターでのみ検索されnamespacesます。つまり、任意の名前空間プレフィックスを使用できます。APIはowl:パーツを分割し、namespaces辞書で対応する名前空間URLを検索してから、{http://www.w3.org/2002/07/owl}Class代わりにXPath式を検索するように検索を変更します。もちろん、同じ構文を自分で使用することもできます。

root.findall('{http://www.w3.org/2002/07/owl#}Class')

lxmlライブラリに切り替えることができれば、状況は改善されます。そのライブラリは同じElementTree APIをサポートしますが、.nsmap要素の属性に名前空間を収集します。


7
ありがとうございました。名前をハードコーディングせずに、XMLから直接名前空間を取得するにはどうすればよいですか?またはどうすれば無視できますか?findall( '{*} Class')を試しましたが、私の場合はうまくいきません。
コスタノス2013年

7
xmlns自分で属性のツリーをスキャンする必要があります。答えで述べたように、lxmlこれはあなたのために行いますが、xml.etree.ElementTreeモジュールはしません。ただし、特定の(すでにハードコードされている)要素を照合する場合は、特定の名前空間の特定の要素を照合することもできます。その名前空間は、要素名ほどドキュメント間で変化することはありません。要素名でハードコーディングすることもできます。
Martijn Pieters

14
@ジョン:register_namespaceシリアル化にのみ影響し、検索には影響しません。
Martijn Pieters

5
役に立つかもしれない小さな追加:のcElementTree代わりに使用する場合ElementTreefindall名前空間をキーワード引数としてではなく、単に通常の引数として使用しますctree.findall('owl:Class', namespaces)。つまり、を使用します。
egpbos 2014

2
@Bludwarf:ドキュメントにはそれが記載されていますが(書いていない場合は、そうではありませんが)、慎重に読む必要があります。名前空間を使用したXML解析セクション参照してください。引数findallなしとnamespace引数の使用を対比する例がありますが、引数はElementオブジェクトセクションのメソッドメソッドの引数の1つとして言及されていません。
ウィルソンF

57

(Martijn Pietersが言及しているように)名前空間をハードコーディングしたり、テキストをスキャンしたりすることなく、lxmlでこれを行う方法は次のとおりです。

from lxml import etree
tree = etree.parse("filename")
root = tree.getroot()
root.findall('owl:Class', root.nsmap)

更新

5年経った今でも、この問題のバリエーションに遭遇しています。lxmlは上で示したように役立ちますが、すべての場合に役立つわけではありません。ドキュメントのマージに関しては、コメンターがこの手法に関して妥当なポイントを持っている可能性がありますが、ほとんどの人は単にドキュメントを検索するのが難しいと思います。

これは別のケースと私がそれをどのように処理したかです:

<?xml version="1.0" ?><Tag1 xmlns="http://www.mynamespace.com/prefix">
<Tag2>content</Tag2></Tag1>

接頭辞のないxmlnsは、接頭辞のないタグがこのデフォルトの名前空間を取得することを意味します。つまり、Tag2を検索するときは、それを見つけるために名前空間を含める必要があります。しかし、lxmlはNoneをキーとしてnsmapエントリを作成し、それを検索する方法を見つけることができませんでした。だから、私はこのような新しい名前空間辞書を作成しました

namespaces = {}
# response uses a default namespace, and tags don't mention it
# create a new ns map using an identifier of our choice
for k,v in root.nsmap.iteritems():
    if not k:
        namespaces['myprefix'] = v
e = root.find('myprefix:Tag2', namespaces)

3
完全な名前空間URL 、ハードコードする予定の名前空間識別子です。ローカルプレフィックス(owl)はファイルごとに変更できます。したがって、この答えが示唆することをするのは本当に悪い考えです。
Matti Virkkunen 2016年

1
@MattiVirkkunen正確にフクロウの定義がファイルごとに変わる可能性がある場合、ハードコーディングする代わりに各ファイルで定義された定義を使用するべきではありませんか?
ロイック・フォーレ-ラクロワ

@LoïcFaure-Lacroix:通常、XMLライブラリを使用すると、その部分を抽象化できます。ファイル自体で使用されている接頭辞を知る必要も、気にする必要もありません。解析のために独自の接頭辞を定義するか、完全な名前空間名を使用するだけです。
Matti Virkkunen、2016

この答えは、少なくとも検索機能を使用できるようにするのに役立ちました。独自のプレフィックスを作成する必要はありません。key = list(root.nsmap.keys())[0]を実行し、キーをプレフィックスとして追加しました:root.find(f '{key}:Tag2'、root.nsmap)
Eelco van Vliet

30

:これは、ハードコードされた名前空間を使用しないPythonのElementTree標準ライブラリに役立つ回答です。

名前空間のプレフィックスとURIをXMLデータから抽出するには、ElementTree.iterparse関数を使用して、名前空間の開始イベントのみを解析します(start-ns)。

>>> from io import StringIO
>>> from xml.etree import ElementTree
>>> my_schema = u'''<rdf:RDF xml:base="http://dbpedia.org/ontology/"
...     xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
...     xmlns:owl="http://www.w3.org/2002/07/owl#"
...     xmlns:xsd="http://www.w3.org/2001/XMLSchema#"
...     xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
...     xmlns="http://dbpedia.org/ontology/">
... 
...     <owl:Class rdf:about="http://dbpedia.org/ontology/BasketballLeague">
...         <rdfs:label xml:lang="en">basketball league</rdfs:label>
...         <rdfs:comment xml:lang="en">
...           a group of sports teams that compete against each other
...           in Basketball
...         </rdfs:comment>
...     </owl:Class>
... 
... </rdf:RDF>'''
>>> my_namespaces = dict([
...     node for _, node in ElementTree.iterparse(
...         StringIO(my_schema), events=['start-ns']
...     )
... ])
>>> from pprint import pprint
>>> pprint(my_namespaces)
{'': 'http://dbpedia.org/ontology/',
 'owl': 'http://www.w3.org/2002/07/owl#',
 'rdf': 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
 'rdfs': 'http://www.w3.org/2000/01/rdf-schema#',
 'xsd': 'http://www.w3.org/2001/XMLSchema#'}

次に、辞書を引数として検索関数に渡すことができます。

root.findall('owl:Class', my_namespaces)

1
これは、lxmlにアクセスせず、名前空間をハードコードしたくない私たちにとって便利です。
delrocco 2016

1
エラーが発生しました:ValueError: write to closedこの行の場合filemy_namespaces = dict([node for _, node in ET.iterparse(StringIO(my_schema), events=['start-ns'])])。どんな考えも間違って欲しがっていますか?
Yuli

おそらく、エラーはクラスio.StringIOに関連しており、ASCII文字列を拒否します。私はPython3でレシピをテストしました。Unicode文字列の接頭辞 'u'をサンプル文字列に追加すると、Python 2(2.7)でも機能します。
Davide Brunato 2017

代わりに、dict([...])口述内包表記を使用することもできます。
Arminius

代わりにStringIO(my_schema)、XMLファイルのファイル名を置くこともできます。
JustAC0der

6

私はこれと同様のコードを使用してきましたが、いつものように、ドキュメントを読む価値は常にあることがわかりました!

findall()は、現在のタグの直接の子である要素のみを検索します。だから、本当に全部ではない。

特に大きくて複雑なxmlファイルを処理していて、そのサブサブ要素(など)も含まれている場合は、コードを次のように動作させようとしているときに価値があるかもしれません。要素がxmlのどこにあるかご存知の場合は、問題ないと思います。これを覚えておく価値があると思っただけです。

root.iter()

ref:https : //docs.python.org/3/library/xml.etree.elementtree.html#finding-interesting-elements "Element.findall()は、現在の要素の直接の子であるタグを持つ要素のみを検索します。 Element.find()は特定のタグを持つ最初の子を検索し、Element.textは要素のテキストコンテンツにアクセスします。Element.get()は要素の属性にアクセスします: "


6

名前空間をその名前空間形式で取得するには、たとえば{myNameSpace}、次のようにします。

root = tree.getroot()
ns = re.match(r'{.*}', root.tag).group(0)

このようにして、後でコード内でそれを使用してノードを見つけることができます(例:文字列補間(Python 3))。

link = root.find(f"{ns}link")

0

私の解決策は@Martijn Pietersのコメントに基づいています:

register_namespace 検索ではなく、シリアル化にのみ影響します。

したがって、ここでの秘訣は、シリアル化と検索に異なる辞書を使用することです。

namespaces = {
    '': 'http://www.example.com/default-schema',
    'spec': 'http://www.example.com/specialized-schema',
}

次に、解析と書き込みのためにすべての名前空間を登録します。

for name, value in namespaces.iteritems():
    ET.register_namespace(name, value)

検索のために(find()findall()iterfind())私たちは、空でない接頭辞を必要としています。これらの関数に変更されたディクショナリを渡します(ここでは元のディクショナリを変更していますが、これは名前空間が登録された後でのみ行う必要があります)。

self.namespaces['default'] = self.namespaces['']

これで、find()ファミリの関数をdefaultプレフィックスで使用できるようになりました。

print root.find('default:myelem', namespaces)

だが

tree.write(destination)

デフォルトの名前空間の要素には接頭辞を使用しません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.