web-dev-qa-db-ja.com

'ElementTree'を介したPythonの名前空間を持つXMLの解析

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

<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タグを見つけるためにコードを変更する方法を教えてください。

140
Sudar

ElementTreeは名前空間についてあまりスマートではありません。 .find()findall()iterfind()メソッドに明示的な名前空間辞書を与える必要があります。これはあまり文書化されていません:

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

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

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

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

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

195
Martijn Pieters

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

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

:これは、ハードコーディングされた名前空間を使用しない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)
23
Davide Brunato

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

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
MJM

名前空間を名前空間形式で取得するには、たとえば{myNameSpace}、次のことができます:

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

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

link = root.find(f'{ns}link')
2
Bram Vanroy

数年遅れていることは知っていますが、名前空間を使用して辞書を有効なXMLに変換するパッケージを作成しました。パッケージはPyPi @ https://pypi.python.org/pypi/xmler でホストされています。

このパッケージを使用すると、次のような辞書を取得できます。

myDict = {
    "RootTag": {                        # The root tag. Will not necessarily be root. (see #customRoot)
        "@ns": "soapenv",           # The namespace for the RootTag. The RootTag will appear as <soapenv:RootTag ...>
        "@attrs": {                     # @attrs takes a dictionary. each key-value pair will become an attribute
            { "xmlns:soapenv": "http://schemas.xmlsoap.org/soap/envelope/" }
        },
        "childTag": {
            "@attrs": {
                "someAttribute": "colors are Nice"
            },
            "grandchild": "This is a text tag"
        }
    }
}

次のようなXML出力を取得します。

<soapenv:RootTag xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
    <childTag someAttribute="colors are Nice">
        <grandchild>This is a text tag</grandchild>
    </childTag>
</soapenv:RootTag>

これが将来の人々に役立つことを願っています

1
watzon

私の解決策は@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)

デフォルトの名前空間の要素にはプレフィックスを使用しません。

0
peter.slizik