Pythonは、多くの点でXML文書処理用の理想的言語と言えます。Perl、REBOL、REXX、TCLのように、強力なテキスト操作機能を備えた柔軟なスクリプト言語です。しかも、XML文書にコード化されるデータ構造は通常、ほとんどのタイプのテキスト・ファイル(またはストリーム)より大規模で複雑です。
一般に、「何行か読み取ってそれを正規の表現と比較する」というおなじみのスタイルのテキスト処理は、XMLを適切に構文解析して処理するのにはあまり適していません。Pythonは、幸いにも(他の一般的言語と比べると)、複雑なデータ構造(通常はクラスと属性を有する)を簡単に処理する手段と、XMLの構文解析、処理、および生成に役立つ一連のXML関連モジュールを兼ね備えています。
Python用各種XMLツールのメインテナンス作業は、その大半がXML-SIG(Special Interest Group)のメンバーによって行われています。XML-SIGは、他のPython Special Interest Group同様、メーリング・リスト、リスト・アーカイブ、役に立つリファレンス、ドキュメンテーション、標準パッケージその他リソース(この記事末尾の参考文献 を参照)のメインテナンスを行っています。
Python 2.0から、Python標準ディストリビューションにはXML-SIGプロジェクトのほとんどが含まれるようになりました。最新XML-SIGパッケージには、Python標準ディストリビューションに含まれていない「最先端」機能の一部が含まれているかもしれませんが、Python 2.0におけるXMLサポートは、この記事の考察を始めとする大部分の目的に対して、読者の皆さんが関心を抱いているものと言えそうです。幸いなことに、Python 2.0+は、旧バージョンのPythonにおけるxmllib の基本サポートより大きく前進しています。最近では、PythonユーザーがDOM、SAXおよびexpat の各XML処理技法からかなり自由に選べるようになっています(他のプログラミング言語の使用経験があるXML開発者なら、これら技法についてすべてご存じでしょう)。
xmllib は妥当性検査をしない低水準パーサーです。xmllib は、アプリケーション・プログラマーがクラスXMLParserをオーバーライドすることによって動作し、固有タグか汎用タグ、または文字エンティティーなどの文書エレメントを処理するメソッドを提供します。Python 2.0+での xmllib の使用法は、Python 1.5xでの使用法と変わっていません。ストリーム指向ですが、言語間でも開発者間でもより標準的であることから、ほとんどの場合はSAX技法を使用する方がよいでしょう。
この記事で紹介する例は最初のコラムと同じファイルで、quotations.dtd というDTDと、このDTDのsample.xml という文書です (この記事で言及するファイルのアーカイブについては参考文献 を参照)。以下のコードは、sample.xml に含まれる各引用の最初の数行を表示し、未知のタグとエンティティーのごく単純なASCII標識を生成します。構文解析されたテキストは、順次ストリームとして処理されます。タグ内の文字ストリング(#PCDATA)、検出されたタグのリスト/ディクショナリーなど、どのようなアキュムレーターを使用するかはプログラマーの仕事になります。
リスト1: try_xmllib.py
import xmllib, string
class
QuotationParser(xmllib.XMLParser):
"""Crude xmllib extractor for quotations.dtd document"""
def
__init__(self):
xmllib.XMLParser.__init__(self)
self.thisquote = '' # quotation accumulator
def
handle_data(self, data):
self.thisquote = self.thisquote + data
def
syntax_error(self, message):
pass
def
start_quotations(self, attrs): # top level tag
print '--- Begin Document ---'
def
start_quotation(self, attrs):
print 'QUOTATION:'
def
end_quotation(self):
print string.join(string.split(self.thisquote[:230]))+ '...',
print '('+str(len(self.thisquote))+ ' bytes)\n'
self.thisquote = ''
def
unknown_starttag(self, tag, attrs):
self.thisquote = self.thisquote + '{'
def
unknown_endtag(self, tag):
self.thisquote = self.thisquote + '}'
def
unknown_charref(self, ref):
self.thisquote = self.thisquote + '?'
def
unknown_entityref(self, ref):
self.thisquote = self.thisquote + '#'
if __name__ == '__main__':
parser = QuotationParser()
for c in open( "sample.xml").read():
parser.feed(c)
parser.close()
|
標準のXMLサポートを超えるものが求められる理由の1つとして、構文解析と同時に妥当性検査の実行が必要な場合が挙げられます。残念ながら、標準Python 2.0 XMLパッケージには妥当性検査実行パーサーは含まれていません。
xmlproc は、ほぼ完全な妥当性検査を実行するPythonネイティブ・パーサーです。妥当性検査を実行するパーサーが必要な場合は、現在のところ xmlproc がPythonにおける唯一の選択肢です。さらに、xmlproc は他のパーサーが提供していない各種高水準検査用インターフェースを提供しています。
Simple API for XML(SAX)を使用する場合(他のほとんどのツールがSAXをベースに作成されていることから、何か高度なものを求めるなら当然これを使用すべきですが)、パーサーから得られるデータの分類作業の多くをプログラマーに代わって実行させることができます。モジュールxml.sax には、「最適」パーサーを自動選択する機能があります。Python 2.0の標準インストールで選択できるパーサーは、C言語で記述された高速エクステンションexpatだけです。ただし、$PYTHONLIB/xml/parsers に別のパーサーをインストールすれば、それを選択対象にできます。パーサーのセットアップは、次のように簡単です。
リスト2: 最適パーサー選択のPythonコード行
import xml.sax
parser = xml.sax.make_parser()
|
引数を渡して特定パーサーを選択することもできますが、移植性を持たせるために、いずれ登場するさらに優れたパーサーに対する上位互換性を維持するうえでも、おそらくmake_parser() に任せるのが最善の方法と思われます。
xml.parsers.expat は直接インポートすることができます。その場合、SAXインターフェースでは提供されないいくつかの特殊技法が提供されます。この意味で、xml.parsers.expat はSAXと比べてやや「低水準」です。しかし、SAX技法はきわめて標準的なもので、ストリーム指向処理に最適です。ほとんどの場合、操作レベルとしてはSAXが最適です。make_parser() 関数は、一般的にexpat が実現しているパフォーマンスをどうにか達成できるため、現実の速度差はほとんど無視できる程度と思われます。
基本に戻りますが、そもそもSAXとは何でしょうか。適切な答えは以下のとおりです。
SAX(Simple API for XML)は、XMLパーサーの共通パーサー・インターフェースです。これによってアプリケーション作成者は、どのパーサーを現実に使用するかに関係なく、XMLパーサーを使用するアプリケーションを作成することができます(XML用のJDBCと考える)。? Lars Marius Garshol氏の「SAX for Python」より
SAXは、APIを提供するパーサー・モジュール同様、本質的にはXML文書の順次処理プログラムです。SAXの使用法はxmllib の例とほぼ同様ですが、抽象度のレベルはいくぶん高くなります。アプリケーション・プログラマーは、パーサー・クラスを定義する代わりに、使用するすべてのパーサーに登録するハンドラー ・クラスを定義します。DocumentHandler、DTDHandler、EntityResolverおよびErrorHandlerの4つのSAXインターフェース(それぞれいくつかメソッドがある)を定義する必要があります。また、パーサーを作成すると、オーバーライドしない限りデフォルトのインターフェースが接続されます。以下に、xmllib の例と同じタスクを実行するコードを示します。
リスト3: try_sax.py
"Simple SAX example, updated for Python 2.0+"
import string
import xml.sax
from xml.sax.handler import *
class
QuotationHandler(ContentHandler):
"""Crude extractor for quotations.dtd compliant XML document"""
def
__init__(self):
self.in_quote = 0
self.thisquote = ''
def
startDocument(self):
print '--- Begin Document ---'
def
startElement(self, name, attrs):
if name == 'quotation':
print 'QUOTATION:'
self.in_quote = 1
else:
self.thisquote = self.thisquote + '{'
def
endElement(self, name):
if name == 'quotation':
print string.join(string.split(self.thisquote[:230]))+ '...',
print '('+str(len(self.thisquote))+ ' bytes)\n'
self.thisquote = ''
self.in_quote = 0
else:
self.thisquote = self.thisquote + '}'
def
characters(self, ch):
if self.in_quote:
self.thisquote = self.thisquote + ch
if __name__ == '__main__':
parser = xml.sax.make_parser()
handler = QuotationHandler()
parser.setContentHandler(handler)
parser.parse( "sample.xml")
|
上記例ではxmllib と若干異なる点が2つあることに注意してください。1つは、.parse() メソッドがストリーム/ストリングをすべて処理するため、パーサーにデータを渡すループを作成する必要がないという点です。もう1つは、.parse() がファイル名でも、ファイル・オブジェクトでも、ほとんどのファイル系オブジェクト(.read() メソッドを備えたもの)でも受け入れるほど柔軟であるという点です。
DOMは、XML文書ツリー・ベースの高水準表現です。このモデルはPython固有のものではなく、一般XMLモデルです(詳細については参考文献 を参照)。PythonのDOMパッケージはSAXをベースに作成されたもので、Python 2.0の標準XMLサポートに含まれています。誌面の都合上、この記事でコード例を紹介することはできませんが、その概要はXML-SIGの「Python/XML HOWTO」に次のようにわかりやすく説明されています。
文書オブジェクト・モデル(DOM)とは、XML文書のツリー・ベース表現を規定するものです。最上位文書インスタンスがツリーのルートとなり、その下に最上位エレメント・インスタンスである単一の子エレメントがあります。この子エレメントには、コンテンツおよび他のサブエレメントを表す子ノードがあります。それにはさらに子がある場合があり、これが繰り返されます。結果のツリーを好みの方法で検討したり、エレメントや属性値にアクセスしたり、ノードの挿入や削除を行ったり、ツリーをXMLに変換し直したりする機能が定義されています。
文書オブジェクト・モデル(DOM)とは、XML文書のツリー・ベース表現を規定するものです。最上位文書インスタンスがツリーのルートとなり、その下に最上位エレメント・インスタンスである単一の子エレメントがあります。この子エレメントには、コンテンツおよび他のサブエレメントを表す子ノードがあります。それにはさらに子がある場合があり、これが繰り返されます。結果のツリーを好みの方法で検討したり、エレメントや属性値にアクセスしたり、ノードの挿入や削除を行ったり、ツリーをXMLに変換し直したりする機能が定義されています。
モジュールxml.dom を使用する構文は、以前の記事から若干変わっています。Python 2.0に付属するDOMの実装はxml.dom.minidomと呼ばれ、DOMの小型・軽量版です。確かに、xml.dom.minidomはXML-SIGのDOMの完全版からいくつかの検査用機能を削除したものですが、その違いに気づく人はほとんどいないはずです。
DOMオブジェクトの生成は、次のコードを使用するだけで簡単にできます。
リスト4:XMLファイルからのPython DOMオブジェクトの作成
from xml.dom.minidom import parse, parseString
dom1 = parse( 'mydata.xml') # parse an XML file by name
|
DOMオブジェクト操作は、かなり簡単なOOPスタイルの作業です。ただし、すぐに見分けがつかない多くのリスト状の属性が階層内に含まれる傾向があります(ループ内の列挙を除く)。例として、ごく一般的なDOM Pythonコードの一部分を示します。
リスト5:Python DOMノード・オブジェクトによる繰返し
for node in dom_node.childNodes:
if node.nodeName == '#text': # PCDATA is a kind of node,
PCDATA = node.nodeValue # but not a new subtag
elif node.nodeName == 'spam':
spam_node_list.append(node) # Create list of <spam> nodes
|
Python標準ドキュメンテーションには、さらに詳しいDOMの例がいくつか記載されています。以前の記事で紹介したDOMオブジェクトの操作例(参考文献を参照)は、今もなお正しい方向を示していますが、その後一部メソッドと属性名が変更されているため、Pythonドキュメンテーションを確認してください。
pyxie モジュールは、Pythonの標準XMLサポートをベースに作成されており、XML文書に対する追加の高水準インターフェースを提供するものです。pyxie の基本的機能は次の2つです。1つはXML文書を、さらに構文解析しやすい行指向形式に変換すること、もう1つはXML文書を、追跡できるツリーとして扱うメソッドを提供することです。pyxie で使用される行指向PYX形式は言語には依存せず、いくつかの言語ツールを使用することができます。一般に、grep、sed、awk、bash、perlのようなおなじみの行指向テキスト処理ツール、あるいはstring 、re などの標準Pythonモジュールを使用して文書のPYX表現を処理する方が、XML表現を処理するよりもはるかに簡単です。ダウンストリーム次第では、XMLからPYXに変換することによって多くの作業が省ける場合もあります。
XML文書をツリー状に扱うというpyxieの概念は、DOMの概念とよく似ています。DOM標準は数多くのプログラム言語にわたって幅広い支持を集めつつあるため、XML文書のツリー表現が必要な場合、ほとんどのプログラマーは pyxie よりもDOM標準に目を向けた方がよいでしょう。
その他モジュール: xml_pickle と xml_objectify
私も、xml_pickle とxml_objectifyという、XML処理用の独自高水準モジュールを作成しています。これらについては、他の場所(参考文献を参照)でも十分に説明しているため、ここでは詳しく述べません。しかし、「XMLで考える」のではなく「Pythonで考える」必要がある場合は、これらモジュールが有効なことがよくあります。特に、xml_objectify はXML自体のほぼすべての痕跡を隠してPythonプログラマーに見えないようにし、プログラム内で完全に「ネイティブ」Pythonオブジェクトを扱えるようにします。基礎をなす実際のXMLデータ・フォーマットは、ほぼ不可視というところまで抽象化されます。同様に、xml_pickle を使用すれば、Pythonプログラマーは、データ・ソースに関係なく「ネイティブ」Pythonオブジェクトから取りかかり、他のユーザーがダウンストリームとして望むXMLフォーマットにダンプ(シリアル化)することができます。
- Python 2.0+のXML処理用モジュールの詳細なドキュメンテーションについては、まずPython Library ReferenceのStructured Markup Processing Toolsセクションをご覧いただくのが最善です。そこでネームスペースが
xmlで始まるすべてのパッケージをお探しください。 - The PythonSpecial Interest Group on XML では、XML処理にPythonを使用するためのツールに関する議論と実装に関するフォーラムを開いています。その他に、Python Software Foundationでは「Pythonに関する特定リソースの開発、改善、または保守専門の協調的活動」を行ういくつかのSpecial Interest Groups (SIG) を管理しています。
-
The Vaults of Parnassus XML ページには、素晴らしいPythonコード/ツール・リポジトリーがあります。
- この記事で取り上げたファイル(quotations.dtd, sample.xml, try_sax.py, try_sax.pyc, try_xmllib.py)は、gnosis.cx/download/charming_python_1r.zipから入手できます。
- この記事全体で引用している著者David Mertzによる「魅力的なPython」 の前2回の記事は、「XMLとPythonのあれこれ」 (2000年6月)と「DOMの動的性」 (2000年7月)です。この著者による「魅力的なPython」 コラムのその他の記事もお読みください。
- Pythonにおけるフルテキスト・インデクサーの開発 (2001年5月)
- Pythonでの関数プログラミング 第1回 (2001年3月)、第2回 (2001年4月)
- V2.0の入手 (2001年2月)
- Python関連書籍の最新情報(2001年2月)
- JPythonとPython for .NETの内幕 (2000年12月)
- PythonでのTKプログラミング (2000年12月)
- 実行時に再ロードを行う (2000年11月)
- Python実装に駆り立てたもの (2000年10月)
- cursesプログラミング (2000年9月)
- Pythonにおけるテキスト処理 (2000年9月)
- ステート・マシンの使い方 (2000年8月)
- 私の最初のWebベース・フィルター・プロキシー (2000年7月)
- 次の記事もご覧ください。
- XMLの論考 第1回:XML文書をオブジェクトとして「Python的に」取り組む (2000年8月)
- XMLの論考 第2回:XML文書をオブジェクトとして「Python風」に処理する方法(II) (2000年8月)
