今回は概要を掴む為、極めてシンプルな処理を例にします。
次のようなXML形式の設定ファイルを仮定します。
<?xml version="1.0" encoding="UTF-8"?>
<root>
<startup>http://www.wxpython.org/</startup>
<frame-width>500</frame-width>
<frame-height>400</frame-height>
</root>
これをsetup.xmlとします。
これを次のような辞書に変換することを考えます。
{
u'startup': u'http://www.wxpython.org/',
u'frame-width': u'500',
u'frame-height': u'400'
}
import xml.sax as sax
from xml.sax.saxutils import DefaultHandler
以降、xml.saxパッケージはsaxという名前で参照できることになります。xml.sax.utilsからDefaultHandlerクラスもインポートしておきます。ImportErrorが発生する場合、PyXML辺りをインストールします。
DefaultHandlerクラスのサブクラス(MyHandler)を作成する殆どの場合、このDefaultHandlerクラスのサブクラスを作成することになります。
class MyHandler(DefaultHandler):
pass
ここではこのサブクラスを月並みにMyHandlerと名づけました。
DefaultHandlerは、ContentHandler、DTDHandler、EntityResolver、ErrorHandlerという四つのインターフェイスを全て実装した便利なクラスです。この内ContentHandlerはその名の通り「XMLの内容に関する通知を受け取って処理を行う者」を意味します。
予想されるとおり、Pythonではこの四つのインターフェイスは全てクラスです。従ってContentHandlerのサブクラスを作成する方法を採ることもできますが、Java等、他のSAX実装を利用する場合著しく「意味」が変わってしまうので止めておいた方が良いと(今のところ)私は考えています。
DefaultHandlerのメソッドを上書きする
MyHandlerクラスをDefaultHandlerクラスのサブクラスとして作成したのは、DefaultHandlerの持っているメソッドを上書きして使用できるようにする為です。上書きする必要のあるメソッドだけを上書きすれば良いのです。
data_dict = {}
class MyHandler(DefaultHandler):
def __init__(self, root='root'):
self.root = root
self.current_node = root
def startElement(self, name, attrs):
self.current_node = name
def endElement(self, name):
self.current_node = self.root
def characters(self, char):
if self.current_node != self.root:
data_dict[self.current_node] = char
startElmentメソッドは、要素の開始タグが検出された際に呼び出されます。Pythonの場合、startElementメソッドはXML名前空間に対応していないことに注意すべきです。Java版と同等のそれはstartElementNSメソッドが対応しています。
charactersメソッドは、文字データが検出された際に呼び出されます。endElementは要素の終了タグが検出された際に呼び出されます。
単純なsetup.xmlでは、処理中の要素名(self.current_node)がルート要素のそれでない場合に、data_dictに対して要素名をkey、文字データをvalueとして与える、という簡単な(悪く言うと愚直な)ロジックで辞書を作ることが出来ます。
def get_serupdata(path):
parser = sax.make_parser()
parser.setFeature(sax.handler.feature_namespaces, 0)
parser.setContentHandler(MyHandler())
file = file(path, 'r')
try:
parser.parse(file)
finally:
file.close()
return data_dict
Pythonの場合、xml.saxにmake_parserという関数があります。これを呼び出せば適切なXMLパーサのインスタンスが得られるそうです。このXMLパーサとは、具体的にはXMLReaderインターフェイスを実装したオブジェクトです。従って、このXMLパーサの各種メソッドについては、XMLReaderインターフェイスを調べればよいわけです:
parser = sax.make_parser()
今回、XML名前空間は使用しないので、setFeatureメソッドを使用して予め明示的にそれをXMLパーサに伝えておきます。
parser.setFeature(sax.handler.feature_namespaces, 0)
第一引数に与えているsax.handler.feature_namespacesというのは、名前空間サポートを意味する定数('http://xml.org/sax/features/namespaces')の参照です。第二引数にFalseを与えることでこのfeatureを使用しないことを示しています。この他にも様々なfeatureを示すことが出来ますが、殆ど実装依存で、実装者はURIの形式で独自にfeature名を定義できます。http://xml.org/sax/features/namespaces 及び http://xml.org/sax/features/namespace-prefixes の二つのfeatureを与えた場合のみ、まともなSAX2実装が例外を投げることはありません。この二つ以外のfeature名を与えた場合、SAXNotRecognizedException(そんなfeature名は知らない)あるいは、SAXNotSupportedException(知っているけれどサポートしていない)の何れかの例外が投げられる可能性があることに注意しておくべきです。Pythonの場合、SAXに関する例外はxml.saxパッケージに入っています。
次に、setContentHandlerメソッドで先程作成したMyHandlerのインスタンスをパーサに登録します:
parser.setContentHandler(MyHandler())
最後に、ファイルオブジェクトをパーサに与えてパースします。try finally節を使用しているのはパースの失敗に関わらずファイルを閉じる為です。
file = file(path, 'r')
try:
parser.parse(file)
finally:
file.close()
この関数(get_setupdata)を呼び出せば、グローバル変数data_dictに各key, valueが格納されます。ただ、グローバル変数を利用したのはハンドラクラスMyHandlerの簡素化の為であって、実際には辞書オブジェクトをMyHandlerの__init__メソッドの引数として与える形にするでしょう。