YAMAGUCHI::weblog

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

lxmlでXMLパースしたらnamespaceではまった

はじめに

こんにちは、Python界の情弱代表です。最近あるXMLを別のXMLに変換するっていうコード書いてます。XSLT使うっていうのも一つの手なんですけど、めんどくさい上に時間が経つと自分でも読めなくなっちゃうという大問題があるのでPythonで書いてます。
で、XMLいじるならlxmlだろ、っつんでコード書いてたらめっちゃはまったのでメモ。

ソース

例えばこんなXMLがあったとする。

<?xml version="1.0" encoding="UTF-8"?>
<root xmlns="http://example.com/test" version="1.0">
  <language>ja</language>
  <provider>John Doe</provider>
  <entries>
    <entry>
      <title>title 1</title>
      <date>2011-07-27</date>
      <body>今日はいい天気です</body>
    </entry>
    <entry>
      <title>title 2</title>
      <date>2011-07-28</date>
      <body>今日は雨です</body>
    </entry>
    <entry>
      <title>title 3</title>
      <date>2011-07-29</date>
      <body>明日はどんな天気でしょうか</body>
    </entry>
  </entries>
</root>

で、適当な感じでlxml使ってこんなコードを書くとハマる。

  • fail_xmlparse.py
from lxml import etree

def get_entry(filename):
  xml = etree.fromstring(open(filename,'rb').read())
  entries = xml.xpath('//entry')
  print len(entries)


if __name__ == '__main__':
  get_entry("body.xml")

結果はこうなる。

$ python fail_xmlparse.py
0

あれ?3じゃないの?ってはまる。これはnamespaceを指定してないせいで、各タグは細かく書けば

<?xml version="1.0" encoding="UTF-8"?>
<ns:root xmlns:ns="http://example.com/test" version="1.0">
  <ns:language>ja</ns:language>
  <ns:provider>John Doe</ns:provider>
  <ns:entries>
    <ns:entry>
      <ns:title>title 1</ns:title>
      <ns:date>2011-07-27</ns:date>
      ...

となってることをうっかりしちゃいました。というわけで、fail_xmlparse.pyを変更して

  • success_xmlparse.py
from lxml import etree

def get_entry(filename):
  xml = etree.fromstring(open(filename,'rb').read())
  entries = xml.xpath('//ns:entry', namespaces={'ns':"http://example.com/test"})
  print len(entries)
  for e in entries:
    print e.xpath('./ns:title/text()', namespaces={'ns':"http://example.com/test"})[0]

if __name__ == '__main__':
  get_entry("body.xml")

これならうまくいく。

$ python success_xmlparse.py
3
title 1
title 2
title 3