XMLツリーのタグへの参照を属性アクセスで実現する
PythonでXMLを読んで辞書オブジェクトの木に変換するプログラムを書いたけどなんかいけてないので、タグへのアクセスを属性アクセスで実現する方法を考えてみました。要は
<root> <children> <child>A</child> <child>B</child> <child>C</child> <child>D</child> </children> </root>
root['children']['child'][0]
こうアクセスできるようにしたけど、さらに手を加えて
root.children.child[0]
こう書きたい。
Pythonでは、__getattr__と__setattr__をオーバーライドすることで、オブジェクトに対する属性アクセスの挙動を変更できます。
3.4.2 属性値アクセスをカスタマイズする
参考:Pythonのクラスシステム
class A(object): def __init__(self): self.lst = ['alice', 'bob', 'carol'] def __getattr__(self, name): if name[:2] == 'at': return self.lst[int(name[2:])] return self.__dict__[name] def __setattr__(self, name, value): if name[:2] == 'at': self.lst[int(name[2:])] = value self.__dict__[name] = value a = A() print a.lst # => ['alice', 'bob', 'carol'] print a.at0 # => 'alice' print a.at1 # => 'bob' a.at1 = 'bump' print a.at1 # => 'bump'
これを利用して、属性にアクセスされた時に子ノードを探索し対応するオブジェクトを返して、タグ名による属性アクセスを可能にします。そのようなクラスをNodeクラスとして、タグ参照の結果として戻る値を常にNodeクラスまたはプリミティブ型とすれば、a.b.cのようにどんどん木を下に下りていくこともできるはずです。
以下のプログラムソースをlibxmlload.pyというファイル名で保存します。
# coding: utf-8 import urllib from xml.etree.ElementTree import * class Node(object): def __init__(self, name, info): self.__dict__['_info'] = info self.__dict__['_name'] = name def __getattr__(self, name): if name in self.__dict__['_info']: return self.__dict__['_info'][name] return self.__dict__[name] def __setattr__(self, name, value): self.__dict__['_info'][name] = value; def __iter__(self): return self._info.iteritems() def __repr__(self): args = self._name, ','.join(self._info.keys()) return "<%s keys=%s>" % args def xmltrans(xmlnode): node = Node(xmlnode.tag, {}) for child in xmlnode: key = child.tag if len(child) == 0: val = child.text if val is not None: val = val.strip() assign(node._info, key, val) else: assign(node._info, key, xmltrans(child)) return node def assign(info, key, value): if key in info: if isinstance(info[key], list): info[key].append(value) else: info[key] = [info[key], value] else: info[key] = value def urlopen(url): # http access conn = urllib.urlopen(url) xmlstr = conn.read() # create xml xmldoc = fromstring(xmlstr) # xml to object tree otree = xmltrans(xmldoc) return otree def parse(filepath): fobj = open(filepath) xmlstr = fobj.read() xmldoc = fromstring(xmlstr) otree = xmltrans(xmldoc) return otree
XMLファイルを読み込んでみます。
まず、下のXMLをtest.xmlとして保存します。
<root> <state>0</state> <timestamp>1234567890</timestamp> <userlist> <user> <name>A</name> <lang>Japanese</lang> </user> <user> <name>B</name> <lang>Swedish</lang> </user> <user> <name>C</name> <lang>English</lang> </user> <user> <name>D</name> <lang>French</lang> </user> <user> <name>E</name> <lang>Chinese</lang> </user> </userlist> </root>
$ python >>> import libxmlload >>> root = libxmlload.parse('test.xml') >>> root.timestamp '1234567890' >>> root.state '0' >>> root.userlist <userlist keys=user> >>> root.userlist.user [<user keys=lang,name>, <user keys=lang,name>, <user keys=lang,name>, <user keys=lang,name>, <user keys=lang,name>] >>> root.userlist.user[0].name 'A' >>> map(lambda usr:usr.name, root.userlist.user) ['A', 'B', 'C', 'D', 'E'] >>> map(lambda usr:usr.lang, root.userlist.user) ['Japanese', 'Swedish', 'English', 'French', 'Chinese'] >>>
XMLノードへのアクセスを属性アクセスで実現できているのがわかります。
このプログラムではこのほか、ネットワーク上からファイルを持ってくる関数urlopenを定義しました。
最後に、これを使ってTwitterAPIのpublic_timelineからつぶやきを取得してみます。
$ python >>> import libxmlload >>> statuses = libxmlload.urlopen('http://api.twitter.com/1/statuses/public_timeline.xml') >>> statuses <statuses keys=status> >>> len(statuses.status) 20 >>> statuses.status[0] <status keys=favorited,contributors,truncated,text,created_at,retweeted,coordinates,source,in_reply_to_status_id,in_reply_to_screen_name,in_reply_to_user_id,place,retweet_count,geo,id,user> >>> for s in statuses.status: ... print s.user.screen_name ... pcgirl65 Xornvestite windsurfingnews co2levels _SOCIAIS_ Bedford76021 Pamposa04 Rachhh_xo kiyoohara iyodenden m0n3y_bag5 VisaVis_theater cordies_ AARTYinc babygr33ney3s mai_geek manoelagnoronha SaSSy_AsH SuperRenatoo _thaisrawr >>>