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
タグを見つけるためにコードを変更する方法を教えてください。
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
属性で名前空間を収集します。
名前空間をハードコーディングしたり、名前空間のテキストをスキャンしたりせずに、lxmlでこれを行う方法は次のとおりです(Martijn Pietersが述べているように)。
from lxml import etree
tree = etree.parse("filename")
root = tree.getroot()
root.findall('owl:Class', root.nsmap)
注:これは、ハードコーディングされた名前空間を使用しない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)
私はこれと同様のコードを使用していますが、いつものようにドキュメントを読む価値があることがわかりました!
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()は要素の属性にアクセスします: "
名前空間を名前空間形式で取得するには、たとえば{myNameSpace}
、次のことができます:
root = tree.getroot()
ns = re.match(r'{.*}', root.tag).group(0)
このように、コード内で後で使用して、たとえば文字列補間を使用してノードを見つけることができます(Python 3)。
link = root.find(f'{ns}link')
数年遅れていることは知っていますが、名前空間を使用して辞書を有効な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>
これが将来の人々に役立つことを願っています
私の解決策は@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)
デフォルトの名前空間の要素にはプレフィックスを使用しません。