XML は、Standard Generalized Markup Language (SGML) を単純化した言語です。読者の多くは、HTML をとおして SGML に精通していることでしょう。XML および HTML 文書はどちらも、不等号括弧で囲んだマークアップ・タグがテキストの中に点在し、それらのタグによって構造化されています。しかし XML は、タグを使うことにより、多くの目的で XML 文書を扱えるので様々なシステムで利用されています。例えば次のようなものがあります。
- 雑誌の記事およびユーザー文書
- 構造化されたデータのファイル (CSV または EDI ファイルなど)
- プログラム間のプロセス間通信用のメッセージ
- 建築図面 (CAD 形式など)
XML では、表現したいあらゆる種類の構造化された情報を取り込めるよう、一式のタグを作成することができます。このことは、さまざまな情報を表現するための共通規格として XML が人気を博してきた理由です。
Python は、Guido van Rossum が開発した、非常に高水準のインタープリター言語であり、無償で入手できます。この言語は明快な構文と、強力なオブジェクト指向の意味構造 (オプション) を結合したものです。Python は広範囲のコンピューター・プラットフォームで利用でき、プラットフォーム間の強力な移植性を提供します。
Python で XML 文書を扱うには、たくさんの技法とツールがあります。(参考文献セクションには、2 つのdeveloperWorks の記事へのリンクがあり、そこでは一般的な技法が論じられています。また、XML/Python トピックに関する他の文書へのリンクもあります。) しかし、ほとんどの既存の XML/Python ツールに共通なのは、それらが Python 中心というより XML を中心としていることです。特定の機能やコーディング技法は、あるプログラミング言語において自然に感じられても、他の機能やコーディング技法は、別の領域から借りてきたもののように感じることがあります。しかし理想的な環境では、すべての機能がそれぞれの領域に直感的に適合し、各領域は継ぎ目なく組み合わされます。それが実現すれば、プログラマーは単にプログラムを機能させることにとどまらず、プログラムの詩人の域に達するまでになれます。
私は XML と Python とのさらに継ぎ目のない自然な統合を創り出すための調査プロジェクトを始めました。この記事と、この連載の続きの記事で、このプロジェクトの目標、決定事項、および制限の幾つかについて論じ、プログラミングの目標に適合する簡単な方法に焦点を合わせた、役立つモジュールや技法を紹介したいと思います。プロジェクトの一部として作成されたすべてのツールは、パブリック・ドメインとしてリリースされる予定です。
Pythonは、柔軟なオブジェクト・システムとビルトインのタイプが数多く用意された言語です。この Python の豊かさは、プロジェクトにとって利点にも欠点にもなり得ます。つまり、広範囲のネイティブな機能を Python がもっていることで、XML 構造の広範囲の表記が簡単になる一方、ネイティブのタイプおよび構造の範囲が広いので、ネイティブの Python オブジェクトを XML でどうやって表記したらよいのかという問題がどんどん出てくるのです。このような XML と Python の非対称性の結果、プロジェクトには (少なくとも初期の段階では) 2 つの別個のモジュールが含まれることになりました。すなわち、任意の Python オブジェクトを XML で表記するxml_pickle と、Python オブジェクトとして XML オブジェクトをネイティブに表現するxml_objectify です。この記事では、xml_pickle を取り上げることにします。
Python の標準pickle モジュールは、Python オブジェクトの直列化を行う単純で便利な手段をすでに提供してきました。これは永続ストレージやネットワーク上の伝送に役立ちます。しかし場合によっては、pickle にはないいくつかのプロパティーを持つ フォーマットへの直列化を実行したい場合もあります。つまり、次のような形式です。
- 人間が判読できるもの
- 構文解析、および操作が行えて、そのオブジェクトが Python 以外の言語でインポートされたもの
- 保管された直列化オブジェクトの妥当性検査をサポートするもの
xml_pickle は、これらの機能を提供する一方で、pickle とのインターフェース互換性を維持します。しかしxml_pickle はpickle を汎用的に置き換えるものではありません。pickle には、より高速なオペレーション(特にcPickle を介する場合)や、はるかにコンパクトなオブジェクトの表記など、それ自体幾つかの利点があるからです。
xml_pickle のインターフェースはpickle とほとんど同じですが、Python やpickle にあまり精通していない方のために、xml_pickle の使用法を(極めて簡単に)例示することにします。
[xml_pickle] を例示する Python コード
import xml_pickle# import the module
# declare some classes to hold some attributes
class
MyClass1
:pass
class
MyClass2
:pass
# create a class instance, and add some basic data members to it
o = MyClass1()
o.num = 37
o.str ="Hello World"
o.lst = [1, 3.5, 2, 4+7j]
# create an instance of a different class, add some members
o2 = MyClass2()
o2.tup = ("x","y","z")
o2.num = 2+2j
o2.dct = {"this":"that","spam":"eggs", 3.14:"about PI" }
# add the second instance to the first instance container
o.obj = o2
# print an XML representation of the container instance
xml_string = xml_pickle.XML_Pickler(o).dumps()
print xml_string
|
最初の行と、最後から 2 番目の行を除くすべての行は、オブジェクト・インスタンスを使って作業する場合の汎用の Python です。これはややわざとらしく単純すぎるように見えますが、インスタンス・データ・メンバー(これには、コンテナー・データとしてのネスト・インスタンスが含まれており、これは Python に組み込まれた最も複雑な構造です)で行うことは本質的にすべて上記の例に含まれています。Python のプログラマーは、XML としてオブジェクトをエンコードする 1 つのメソッド呼び出しを作成するだけで十分です。
もちろん、いったんオブジェクトを "加工" した後でそれらを "復元" したくなるかもしれません (または他のどこかでそれらを使用することになるかもしれません)。上記の少数の行がすでに実行されたと仮定すると、オブジェクト表現の復元は以下のように単純です。
new_object = xml_pickle.XML_Pickler().loads(xml_string) |
明らかに、実際の場面で XML 文書を作成する時、実行時にメモリー内にそれを保持するだけでなく、なにか別の面白いことをしたくなるかもしれません。たとえば、XML 文書をディスクに保管したり(おそらくXML_Pickler.dump() メソッドを 使用して)、それを通信チャネルに送信するといったことなどです。実際、その例を紙に印刷すれば、それはそれとして耐久性のある保管形式と言えます。
上記のサンプル・コードを実行は、Python オブジェクトのxml_pickle 表現の機能を例示する、たいへん良い例です。しかし、次の例は私が開発した手作りのテスト・ケースで、文書型として定義可能なすべての XML 構造、タグ、および属性を含んでいます。特定のデータは私が作成したものですが、これらのデータがどんなアプリケーションを想定しているかはすぐにお分かりになるでしょう。
xml_pickle の特徴を示すサンプル
<?xml version="1.0"?>
<!DOCTYPE PyObject SYSTEM "PyObjects.dtd">
<PyObject class="Automobile">
<attr name="doors" type="numeric" value="4" />
<attr name="make" type="string" value="Honda" />
<attr name="tow_hitch" type="None" />
<attr name="prev_owners" type="tuple">
<item type="string" value="Jane Smith" />
<item type="tuple">
<item type="string" value="John Doe" />
<item type="string" value="Betty Doe" />
</item>
<item type="string" value="Charles Ng" />
</attr>
<attr name="repairs" type="list">
<item type="string" value="June 1, 1999: Fixed radiator" />
<item type="PyObject" class="Swindle">
<attr name="date" type="string" value="July 1, 1999" />
<attr name="swindler" type="string" value="Ed's Auto" />
<attr name="purport" type="string" value="Fix A/C" />
</item>
</attr>
<attr name="options" type="dict">
<entry>
<key type="string" value="Cup Holders" />
<val type="numeric" value="4" />
</entry>
<entry>
<key type="string" value="Custom Wheels" />
<val type="string" value="Chrome Spoked" />
</entry>
</attr>
<attr name="engine" type="PyObject" class="Engine">
<attr name="cylinders" type="numeric" value="4" />
<attr name="manufacturer" type="string" value="Ford" />
</attr>
</PyObject>
|
PyObjects.dtd 文書の構造を調べるのは難しくありません。(公式の文書型定義 (DTD) は、参考文献より入手できます。) DTD はすぐには明確にならない問題もすべてはっきりさせます。
サンプル XML 文書を見ると、xml_pickle の前述の 3 つの設計上の目標に適合していることが分かります。
- 書式は人が読みやすいものであること。
- XML 表現は
xml_pickle以外の手段によって操作されることがあること。それらは、関連のない Python/XML モジュールか、他のプログラム言語の XML ライブラリー、XML 拡張エディターおよびユーティリティー、または単純なテキスト・エディター (サンプルはこれで作成した) かもしれません。 - Python オブジェクトの XML 表現は、標準の XML 検証プログラムと
PyObjects.dtdを使用して妥当性検査できること。
DTD に従う文書はすべて、そして DTD に従う文書だけ が、有効な Python オブジェクトの表現ということになります。
Python と XML の内容モデルには、いくつかの面で相違があります。重要な相違点の一つは、XML 文書が本質的に順序を持つリニアな形であることです。Python のオブジェクト属性と Python ディクショナリーでは、定義上の順序がありません。(ただし、インプリメンテーションに入り込むと、ハッシュ・キーなどの任意の順序付けが作成されます。) この点で、Python オブジェクト・モデルはリレーショナル・モデルに似ています。リレーショナル表の行には「自然の」順序というものがなく、1 次キーや 2 次キーによって意味上の順序が表に提供されることも、提供されないこともあります。キーは常に比較演算子によって順序付けできますが、この順序はキーのセマンティクスとは無関係の場合もあります。
XML 文書では、常にそのタグ・エレメントを特定の順序で列挙します。その順序が特定のアプリケーションには意味のないものであっても、XML 文書の順序というものは常に存在します。Python と XML のキー順序の相違の影響として、xml_pickle で生成された XML 文書は、"加工"/"復元" サイクルを経たときにエレメントの順序が変わってしまうこともあり得ます。たとえば、手作業で作成した PyObjects.dtd という XML 文書は、前述のように、"復元" されて Python オブジェクトに入れられることがあります。次いで、結果として生じるオブジェクトを "加工" した場合、<attr> タグの順序は、おそらく元の文書の順序とは異なったものになります。これは特徴であってバグではありませんが、知っておくべき事柄です。
現行バージョン (0.2) としてのxml_pickle には、いくつかの制限があることが知られています。潜在的な問題の原因として、複合 / コンテナー・オブジェクトにおける循環参照をトラップする努力が払われていない、という点があります。オブジェクト属性がコンテナー・オブジェクトに戻る形で参照する場合 (あるいは、これが再帰的に行われる場合)、xml_pickle は Python スタックを使い果たしてしまいます。循環参照は初期のオブジェクト設計における欠陥を示すものでしたが、xml_pickle の後期のバージョンでは、より理知的な方法でその問題に対処する試みが見られます。
別の制限として、XML の属性値 (<attr name="123"> における "123" など) のネームスペースが、有効な Python の変数およびインスタンス・メンバーのネームスペースよりも大きい、という点があります。Python ネームスペースの外部で手作業で作成された属性は、インスタンスの.__dict__ マジック属性に存在するという奇妙な状態になります。しかし、通常の属性構文でそれにアクセスすることはできません (たとえば、"obj.123" は構文エラーになります)。これは、XML 文書がxml_pickle 自体以外の手段によって作成または修正される場合にのみ、問題になります。現時点では、私はこの (やや分かりにくい) 問題にどう対処してよいか善い方法が見つかりません。
3 番目の制限は、xml_pickle が Python オブジェクトのすべての属性を処理するわけではないという点です。すべての「通常の」データ・メンバー (ストリング、数値、ディクショナリーなど) は、正しく変換されます。しかし、インスタンス・メソッドや、属性としてのクラスおよび関数オブジェクトは処理されません。pickle に関しては、メソッドは変換の際に単に無視されます。クラスまたは関数オブジェクトが属性として存在する場合、XMLPicklingError が出されます。おそらくこれが正しい動作と思われますが、確信は持てません。
XML 文書設計において実にあいまいな点は、タグ属性をいつ使用し、副エレメントをいつ使用するかの選択です。この設計上の問題についての意見には相違があり、XML プログラマーたちはしばしば、彼らの見解に対立があると強く感じます。おそらくこの点は、xml_pickle 文書構造の決定における最大の問題でした。
決定された一般原則は、本来「複数」であるもの は副エレメントで表現すべきであるということです。たとえば、Python リストには項目を好きなだけ含めることができるので、それは一連の<item> 副エレメントとして表現されます。一方、数はです (値自体は 1 より大きいかもしれませんが、数自体は 1 つのもの です)。その場合は、"value" (値) と呼ばれる XML 属性を使う方が、より論理的であろうと思われます。本当に難しいケースは Python ストリングに関係しています。基本的に、それらはリストと同様のシーケンス・オブジェクトです。ストリング内の各文字を、仮に<char> タグを使って表現するなら、人間にとっての読み易さという目標を損なうことになり、XML 表現の量がふくれ上がるということになってしまいます。そこでストリングは、数の場合と同様に、XML の "value" (値) 属性に入れるという決定がなされました。しかしながら、見た目の美しさと言う観点からすると、タグで挟んで表現した方がよいと思われます。複数行のストリングの場合は特にそうです。しかし、仕様には "ただの文字列である" #PCDATA しかなかったことからすれば、この決定は一貫性のあるものと思われます。
ストリングが XML の "value" (値) 属性に格納されるため、さらには XML 文書のシンタックス上の性質を維持するため、Python ストリングを "安全な" 形で格納する必要がありました。Python ストリングに生じる可能性のある、安全性を損なう要因がいくつかあります。第 1 のタイプは不等号記号 (< と >) のような基本マークアップ文字です。第 2 のタイプは引用符とアポストロフィで、これらは属性を開始させる文字です。第 3 のタイプはヌル文字など、対応の難しい ASCII 値です。考慮した 1 つの方法は、Python ストリング全体を、base64 エンコード方式のような何らかの方法でエンコードすることでした。こうすればストリングは "安全" なものになりますが、全く人が読むことのできないものになってしまいます。それで、混合アプローチを使用することが決定されました。基本 XML 文字は「&」、「>」、「"」といったスタイルで逃げます。対応の難しい ASCII 値は「\000」のような Python スタイルで逃げます。このような組み合わせにより、人が読みやすい XML 表現にはなりますが、格納されているストリングのデコードにはややめんどうなアプローチが求められることになります。
xml_pickle を活用できる多くの用途が考えられており、ユーザーからのフィードバックによると、それは使用準備段階に入っています。いくつかのアイデアを以下に紹介します。
- Python オブジェクトの XML 表現を、既存の XML 指向のツール (必ずしも Python で書かれていなくてよい) を使って、索引付けおよびカタログ化することができます。これにより、Python オブジェクト・データベース (ZODB、PAOS、あるいは単純なshelve) の索引付けのための手段が整います。
- Python オブジェクトの XML 表現を、他の OOP 言語 (特に、類似した範囲の基本タイプをもつもの) のオブジェクトとして保管することができます。これは、まだ行われていないことの 1 つです。これは CORBA、XML-RPC、SOAP といった、ずっと「重い」プロトコルとも目的が重なっていますが、xml_pickle はオブジェクト・トランスポート仕様としては見事に「軽量」です。
- XML 文書の印刷と表示のためのツールを使って、Python オブジェクトの便利で読みやすい表現を、XML 中間形式を介して提供することができます。
- Python オブジェクトを手作業で「デバッグする」ことができます。これは、XML エディターまたは単純なテキスト・エディターを使って、Python オブジェクトの XML 表現を使って行います。手作業で変更を加えたオブジェクトをいったん "復元" したら、プログラムで編集を行ったのと同じ効果を調べることができます。これは、他の既存の Python デバッガーおよびラッパーに、追加のオプションが提供されたようなものです。
xml_pickle の新たな使用法を開発した場合や、モジュールに新しい用途をもたらすような機能強化が得られた場合には、お知らせ下さい。
- Python 用の XML ツールの紹介 :魅力的な Python、第 1 回
- Python の
xml.domモジュールのさらに詳しい説明:魅力的な Python、第2回 -
XML に関する Python Special Interest Group
-
World Wide Web Consortium (W3C) の DOM ページ
-
DOM レベル 1 勧告
-
この記事で使用または言及されたファイル
- Sean McGrath 氏による XML Processing with Python。XML のバックグラウンドを持つプログラマーのための Python の親しみやすい紹介です。McGrath 氏は彼の著書を大いに使って彼の
pyxieモジュールと関連ツールの価値について論じ、それらの技法を XML 処理のための最適なアプローチであるとしています。pyxieが読者個人の問題にとって最適のアプローチであってもそうでなくても、McGrath 氏の著書は Python の有用な入門書と言えます (XML についてはそれほどでもありませんが)。
