読者です 読者をやめる 読者になる 読者になる

YAMAGUCHI::weblog

土足で窓から失礼いたします。今日からあなたの息子になります。 当年とって92歳、下町の発明王、エジソンです。

minidomを使ってXML解析を行う

動機

PythonXML関係のパーサが標準ライブラリに複数用意されてます。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
ノード (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 オブジェクトで定義されています。読み出し専用の属性です。

8.6.2.2 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))