動機
PythonはXML関係のパーサが標準ライブラリに複数用意されてます。2.5.2で使えるものだと、Expat, SAX2, DOMなんかがあります。
その中でminidomを使っていたときにはまったのでメモ。
サンプル
こんなXMLがあったとして、entryを要素を得たいとします。
<?xml version="1.0" encoding="utf-8"?> <feed xmlns="http://www.w3.org/2005/Atom"> <title>hoge</title> <updated>2008-11-28T08:15:02Z</updated> <author> <name>ymotongpoo</name> <uri>http://d.hatena.ne.jp/ymotongpoo</uri> </author> <entry> <link rel="alternate" href="http://twitter.com/ymotongpoo" type="text/html"/> <title>ぴよぴよ</title> <updated>2008-11-28T08:15:02Z</updated> </entry> <entry> <link rel="alternate" href="http://twitter.com/ymotongpoo" type="text/html"/> <title>ふがふが</title> <updated>2008-11-27T04:10:00Z</updated> </entry> </feed>
このとき各entry要素の中身を辞書としてリスト型で返すことを考えると
from xml.dom import minidom doc = minidom.parseString(data) # dataに上記のテキストが入っているとする entries = [] for n = doc.getElementsByTagName('entry') link = n.getElementByTagName('link').item(0).getAttribute('href') title = n.getElementByTagName('title').item(0).childNodes[0].data updated = n.getElementByTagName('updated').item(0).childNodes[0].data entries.append(dict(link=link, title=title, updated=updated))
ここでtilte要素やupdated要素から文字列を取り出すのに凄く手間取ったわけですが、よく考えれば当たり前のことなんですよね。title要素で考えてみます。
n.getElementByTagName('title').item(0).childNodes[0].data
まずパーサは事前にentry要素の中にいくつtitle要素があるかなんて知らないですから、n.getElementByTagName('title')はNodeList型で返すのが賢明です。で、entryの中にtitleは1個しかないので0番目の要素を返します。
同様に今度はtitle要素の中に何が入っているかパーサは知りません。とりあえずこれも中身をNodeList型にしておくのが良さそうです。で、title要素の中には文字列が1つしかないので、childNodes[0]は文字列を表すTextオブジェクトとなります。
ここで注意したいのはまだこれは文字列それ自身ではないことです。中身を取り出したいときはdataプロパティを参照します。
イディオム
もともとXMLの構造が分かってれば上記で対応できますが、そうでない場合はNode型が具体的にどの型なのかを判断して、Textだったらデータを取り出してあげれば良さそうです。
型の確認はNodeオブジェクトの中にある定数で判断します。
nodeType
8.6.2.2 Node オブジェクト
ノード (node) の型を表現する整数値です。型に対応する以下のシンボル定数: ELEMENT_NODE 、 ATTRIBUTE_NODE 、 TEXT_NODE 、 CDATA_SECTION_NODE 、 ENTITY_NODE 、 PROCESSING_INSTRUCTION_NODE 、 COMMENT_NODE 、 DOCUMENT_NODE 、 DOCUMENT_TYPE_NODE 、 NOTATION_NODE 、が Node オブジェクトで定義されています。読み出し専用の属性です。
これを使って適当に上のサンプルXMLを使うとするとざっくりこんな感じです。
def getText(node): for n in node.childNodes: if n.nodeType in [node.TEXT_NODE, node.COMMENT_NODE]: return n.data else: return '' entries = [] for n = doc.getElementsByTagName('entry') link = n.getElementByTagName('link').item(0).getAttribute('href') title = getText(n.getElementByTagName('title').item(0)) updated = getText(n.getElementByTagName('updated').item(0)) entries.append(dict(link=link, title=title, updated=updated))