目次


魅力的なPython: DOMの動的性

Pythonのxml.domモジュールの分析

Comments

Python とは? XML とは?

Python は、無料で提供される高レベルのインタープリター言語で、Guido van Rossum の開発になるものです。このソフトウェアは、明瞭な構文と、強力なオブジェクト指向セマンティクス (オプション) とを結合します。Python は、ほとんどすべてのコンピューター・プラットフォームで使用でき、各プラットフォーム間での強力な移植性を持っています。

XML は、SGML (Standard Generalized Markup Language) を単純化した言語です。HTML 文書タイプを使用していれば、SGML にはよくなじんでいるでしょう。XML 文書は、不等号記号を使用したマークアップ・タグが使用され、それによって構造化されたテキストから構成されているという点で、HTML と類似しています。しかし XML は、多くのタグ・システムを含んでいますので、XML 文書を、次のような多くの目的に使用することができます。すなわち、雑誌の記事やユーザー文書、構造化データのファイル (CSV ファイルや EDI ファイルなど)、各プログラム同士のプロセス間通信用のメッセージ、建築用の図表 (CAD フォーマットなど)、およびその他多くの目的。タグ・セットを作成することによって、表示したいと考えている任意の種類の情報を取り込むことができます。そのため、XML は、各種の情報を表すための共通な標準として人気が高まってきています。

文書オブジェクト・モデル

xml.dom モジュールは、おそらく、Python プログラマーが XML 文書を処理するときに使用できる最も強力なツールです。残念ながら、XML-SIG によって提供される文書は、現在は少数です。このギャップは、W3C の言語に中立な DOM 仕様によって多少穴埋めされます。しかし、Python プログラマーが、Python 言語に固有な DOM のクィック・スタート・ガイドを手にできるとしたら、それは価値のあることです。この記事は、このようなガイドを提供することを目的にしています。 前号のコラム の場合と同様、サンプルの quotations.dtd ファイルがサンプルの一部で使用され、記事のコード・サンプル・アーカイブで使用できます。

DOM とは何かを正確に把握するのは、意味のあることです。以下の公式的な説明がよい参考になります。

文書オブジェクト・モデルは、プラットフォームや言語に依存しないインターフェースであり、プログラムやスクリプトが、文書の内容、構造、およびスタイルを動的にアクセスしたり、更新したりするのを可能にします。文書をさらに加工することができ、その結果は、提示された元のページに組み込むことができます。(World Wide Web Consortium DOM Working Group)

DOM は、XML 文書をツリー表記に変換することができます。World Wide Web Consortium (W3C) 仕様は、HTML テーブルの DOM 版を図で提供します。

DOM ツリー
DOM ツリー

DOM は、基本をなす XML 文書の線形性よりも高い便利なレベルで、このようなツリーを横断、枝取り、再編成、出力、および操作するためのメソッドのセットを定義します。

HTML から XML への変換

有効な HTML は、ほとんどの場合、有効な XML です。主な違いが 2 つあります。1 つは、XML タグが大文字小文字の区別をするということであり、他の 1 つは、すべての XML タグに明示的なクローズが必要であるということです (いくつかの HTML タグではオプションのクローズ・タグを使用するように。たとえば、<img src="X.png" />)。xml.dom を使用する簡単な例として、HtmlBuilder() クラスの使用による HTML から XML への変換があります。

try_dom1.py
"""Convert a valid HTML document to XML
   USAGE: python try_dom1.py < infile.html > outfile.xml
"""
import sys
from xml.dom import core
from xml.dom.html_builder import HtmlBuilder

# Construct an HtmlBuilder object and feed the data to it
b = HtmlBuilder()
b.feed(sys.stdin.read())

# Get the newly-constructed document object
doc = b.document

# Output it as XML
print doc.toxml()

HtmlBuilder() クラスは、継承した基本の xml.dom.builder テンプレート機能をインプリメントしてくれますので、そのソースは調べる価値があります。ただし、テンプレート機能をわれわれがインプリメントする場合でも、DOM プログラムのアウトラインは似たようなものになります。一般には、何らかの手段によって DOM インスタンスを組み立ててから、そのインスタンスについて操作します。DOM インスタンスの .toxml() メソッドは、DOM インスタンスのストリング表現を作り出すための簡単な方法です (上記の場合は、生成時に単に印刷されるだけです)。

Python オブジェクトの XML への変換

Python プログラマーは、任意の Python オブジェクトを XML としてエクスポートすることにより、大きな能力と普遍性を引き出すことができます。こうすれば、使い慣れた方法で Python オブジェクトを処理することができ、最終的にはオプションとして、生成された XML でインスタンス属性をタグとして使用できるようになります。数行 (building.py 例から取りこんだもの) だけを使用して、Python "ネイティブ" オブジェクトを DOM オブジェクトに変換し、オブジェクトに含まれている属性について再帰することができます。

try_dom2.py
"""Build a DOM instance from scratch, write it to XML
   USAGE: python try_dom2.py > outfile.xml
"""
import types
from xml.dom import core
from xml.dom.builder import Builder

# Recursive function to build DOM instance from Python instance
def object_convert(builder, inst):    
     # Put entire object inside an elem w/ same name as the class.
     builder.startElement(inst.__class__.__name__)
    
     for attr in inst.__dict__.keys():        
          if attr[0] == '_':      # Skip internal attributes
               continue
          value = getattr(inst, attr)        
          if type(value) == types.InstanceType:
               # Recursively process subobjects
               object_convert(builder, value)        
          else:            
               # Convert anything else to string, put it in an element
               builder.startElement(attr)
               builder.text(str(value))
               builder.endElement(attr)

     builder.endElement(inst.__class__.__name__)

if __name__ == '__main__':
     # Create container classes    
     class quotations: pass
      class quotation: pass # Create an instance, fill it with hierarchy of attributes inst = quotations() inst.title = "Quotations file (not quotations.dtd conformant)" inst.quot1 = quot1 = quotation() quot1.text = """'"is not a quine" is not a quine' is a quine""" quot1.source = "Joshua Shagam, kuro5hin.org" inst.quot2 = quot2 = quotation() quot2.text = "Python is not a democracy. Voting doesn't help. "+\
                   "Crying may..." quot2.source = "Guido van Rossum, comp.lang.python" # Create the DOM Builder builder = Builder() object_convert(builder, inst) print builder.document.toxml()

object_convert() 関数には、いくつかの制限があります。たとえば、上記のプロシージャーでは、quotations.dtd に準拠した XML 文書を作成することはできません。#PCDATA テキストを直接quotation クラスの中に入れることはできませんが、そのクラスの属性の中に入れることはできます (たとえば、.text)。1 つの簡単な解決策は、特殊なやり方でobject_convert() に、たとえば.PCDATA という名前の属性を処理させることです。DOM への変換は、いろいろな方法でさらに複雑にすることができますが、このアプローチの利点は、完全に "Python 的な" オブジェクトから開始して、それらを簡単に XML 文書に変換できることです。

作成された XML 文書内の同じレベルの各エレメントは、明白な順序では出現しないという点にも注目してください。たとえば、筆者のシステムの場合、特定のバージョンの Python を使用すると、ソース内に定義された 2 番目の引用符が出力では最初に表示されます。しかし、この現象はバージョンやシステム間で異なる場合があります。Python オブジェクトの属性は、最初は順序付けられていませんので、この振る舞いは理解できます。この振る舞いは、データベース・システム関連のデータでは望ましいものですが、XML としてマークアップした小説の場合は明らかに望ましくないものです (ただし、William Burroughs の "カット・アップ" メソッドの更新をしたい場合を除きます)。

XML 文書の Python オブジェクトへの変換

XML 文書から Python オブジェクトを作り出すのは、その逆のプロセスと同様、容易です。多くの場合、xml.dom メソッドを使用することに満足しているでしょう。しかし、他の状況では、"一般的な" Python オブジェクトの場合と同じように、XML 文書から生成されたオブジェクトにも同じ技法を使用するのが適切です。たとえば、以下のコードでは、pyobj_printer() 関数は、任意の Python オブジェクトを処理するために使用した関数かもしれません。

try_dom3.py
"""Read in a DOM instance, convert it to a Python object
"""
from xml.dom.utils import FileReader

class PyObject: pass
def pyobj_printer(py_obj, level=0): """Return a "deep" string description of a Python object"""
      from string import join, split import types descript = '' for membname in dir(py_obj): member = getattr(py_obj,membname) if type(member) == types.InstanceType: descript = descript + (' '*level) + '{'+membname+'}\n' descript = descript + pyobj_printer(member, level+3) elif type(member) == types.ListType: descript = descript + (' '*level) + '['+membname+']\n' for i in range(len(member)): descript = descript+(' '*level)+str(i+1)+': '+ \ pyobj_printer(member[i],level+3) else: descript = descript + membname+'=' descript = descript + join(split(str(member)[:50]))+'...\n' return descript defpyobj_from_dom(dom_node): """Converts a DOM tree to a "native" Python object""" py_obj = PyObject() py_obj.PCDATA = '' for node in dom_node.get_childNodes(): if node.name == '#text': py_obj.PCDATA = py_obj.PCDATA + node.value elif hasattr(py_obj, node.name): getattr(py_obj, node.name).append(pyobj_from_dom(node)) else: setattr(py_obj, node.name, [pyobj_from_dom(node)]) return py_obj # Main test dom_obj = FileReader("quotes.xml").document py_obj = pyobj_from_dom(dom_obj) if __name__ == "__main__": print pyobj_printer(py_obj)

ここでの焦点は、pyobj_from_dom() 関数であり、具体的には、実際の作業が行われる xml.dom メソッド.get_childNodes() です。pyobj_from_dom() では、タグによって直接折り返された任意のテキストを取り出し、それを予約属性.PCDATA に入れます。ネストされたタグに出会った場合、そのタグと一致する名前を持った新規の属性を作成し、その属性にリストを割り当てます。こうすることで、そのタグの複数の出現を親ブロック内に組み込めるようにします。もちろん、リストを使用することにより、XML 文書内でタグに出会った順序を維持しています。

以前のpyobj_printer() 汎用関数 (あるいは、より通りのよい言い方をすれば、より高性能で堅固な関数) を使用できるほか、普通の属性表記を使ってpy_obj のエレメントにもアクセスできるようになりました。

Python 対話式セッション
>>> from try_dom3 import *
>>> py_obj.quotations[0].quotation[3].source[0].PCDATA
'Guido van Rossum, '

DOM ツリーの再配置

DOM の長所の 1 つは、それを使用することで、プログラマーが XML 文書を非線形方式で操作できることです。一致したオープン・タグおよびクローズ・タグによって囲まれた各ブロックは、DOM ツリー内の単なる "ノード" に過ぎません。順序情報を保持するためにノードがリストのような形で維持されている間は、順序について、特別のものや不変のものはありません。ノードの枝取りをし、それを DOM ツリー内の別の場所へ (DTD が許せば、別のレベルにさえ) 枝付けを容易に行うことができます。あるいは、新規のノードを追加したり、既存のノードを削除したりできます。

try_dom4.py
"""Manipulate the arrangement of nodes in a DOM object
"""
from try_dom3 import *

#-- Var 'doc' will hold the single <quotations> "trunk"
doc = dom_obj.get_childNodes()[0]

#-- Pull off all the nodes into a Python list
# (each node is a <quotation> block, or a whitespace text node)
nodes = []
while 1:
     try: node = doc.removeChild(doc.get_childNodes()[0])
     except: break
     nodes.append(node)

#-- Reverse the order of the quotations using a list method
# (we could also perform more complicated operations on the list:
# delete elements, add new ones, sort on complex criteria, etc.)
nodes.reverse()

#-- Fill 'doc' back up with our rearranged nodes
for node in nodes:    
     # if second arg is None, insert is to end of list
     doc.insertBefore(node, None)

#-- Output the manipulated DOM
print
 dom_obj.toxml()

XML 文書を単なるテキスト・ファイルとして表示した場合、あるいは xmllib や xml.sax などの順次指向モジュールを使用した場合でさえ、上記の数行で引用語句を再配置したら、大きな問題を起こしたでしょう。DOM の場合、この問題は、Python リストについて行われる他の操作の場合ほどは困難でありません。


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux
ArticleID=230447
ArticleTitle=魅力的なPython: DOMの動的性
publish-date=07012000