IBM®
本文へジャンプ
    Japan [変更]    ご利用条件
 
 
検索範囲検索:    
    ホーム    製品    サービス & ソリューション    サポート & ダウンロード    マイアカウント    
skip to main content

developerWorks Japan  >  XML  >

XMLの論考: gnosis.xml.validityライブラリーによって妥当性を確保する

OOPデータをXMLの規則に押し込む

developerWorks
ページオプション

JavaScript を要するドキュメントオプションは表示されません

原文はこちら

原文はこちら


レベル: 中級

David Mertz, Ph.D (mertz@gnosis.cx), Author, Gnosis Software, Inc.

2002年 7月 01日

既存のXML APIの大半は、整形式であるかどうかの検証作業をプログラム的に実現できますが、妥当性を保証できるものはほとんどありません。XML処理の世界では、これが深刻な弱点になっています。この記事では、著者が開発したgnosis.xml.validity ライブラリーを取り上げます。このライブラリーによって、XMLの直列化を意図したPythonオブジェクトの妥当性を実現してみましょう。

筆者がdeveloperWorks で書いた以前のヒントでは、オブジェクト指向プログラミング (OOP) のテクニックによってXMLの妥当性制約を表現するための考え方を説明しました。今回のXMLの論考 では、そのためのPythonモジュールを実際に見てみましょう (ただし、完成版ではありません)。これと同じような機能は他の言語でも実現できますが、Pythonには特に汎用性の高いリフレクションのしくみが用意されているので、妥当性制約をきれいに表現できるようになっています。

表面的に見れば、このような込み入った型付けシステムを実装するために、型付けのしくみが (確かに厳密ではあっても) 非常に動的なPythonを選ぶというのは、いささか奇妙な感じがするかもしれません。しかし、これは単なる表面的な印象にすぎません。Java、C++、C# などの言語の型付けシステムは、確かに静的ではありますが、XMLの妥当性制約を表現するにはあまりにもお粗末です。純粋な関数型言語 (Haskellなど) の中には、型階層、区別された和集合、数量、実存型などの機能を提供しているものもありますが、OOP言語には一般にこうした機能が用意されていません。静的な型付けをするOOP言語を使うとすれば、今取り上げているPythonライブラリーと同じほどのカスタム検証機能をライブラリーの中に組み込む必要があるはずです。

gnosis.xml.validity モジュールを他のXML関連のモジュールと比べてみると、このモジュールの特長がよく理解できます。著者のgnosis.xml パッケージには、あと2つのライブラリーが組み込まれており、それについては以前のいくつかの記事で取り上げました(参考文献を参照)。そのうちの1つであるgnosis.xml.pickle は、Pythonオブジェクトなどの特殊なXML直列化を生成します。また、Pythonの標準的なpickle モジュールやcPickle モジュールと同じように、オブジェクトを保存して復元する機能を備えています。もう1つのgnosis.xml.objectify は、これとは逆の方向に処理を進めます。つまり、任意のXML文書からPythonのようなオブジェクトを生成するわけです(ただし、わずかながら元のXMLの情報が失われます)。

また、Pythonの標準ライブラリーには、XML文書のDOM/SAX処理のサポートが組み込まれています。さらに、幅広い実績のあるサード・パーティーのPythonパッケージの中には、XSLT処理のための拡張サポートを組み込んでいるものもあります。

  • DOM (特にxml.dom.minidom) には、OOPスタイルのXML文書操作に適した重量感のあるAPIが用意されており、多くのプログラミング言語のDOM実装で共通して使えるメソッド群が組み込まれています。
  • SAXはXML文書を一連の解析イベントとして扱うので、基本的に手続き型のプログラミング・スタイルに適しています。
  • XSLTは、XML文書を何か別のもの (たとえば別のXML文書)に変換するための一連の規則を宣言するものです。

ところが、このようなライブラリーはいずれも便利ではありますが、アプリケーションがXML表現オブジェクトを操作するときに、元のXMLの妥当性を崩してしまうのを防ぐことができません。たとえば、DOMノードを削除したり、追加したり、移動したりすると、妥当なXML文書として取り込めないようなDOM階層が出来上がってしまうというケースがよくあります。

妥当性を構成するものは何か

XMLの妥当性の基本的な考え方は、ある要素の内側に何が 出現してよいか、それが何回 出現してよいか、そして出現してよいものの選択肢 には何があるか、ということです。さらに、ある要素の内側に複数のものが出現できる場合には、その出現順序を指定できます (必要なら、順序を指定しないこともできます)。何を表現できるかということについて言えば、DTDとW3CのXML Schemaはいくぶん異なっていますが、その趣旨たることは同じです。ではここで、ごく簡略化した架空のdissertation.dtdを見てみましょう。


リスト1. 基本的な制約をすべて含んだ論文 (dissertation) 用のDTD
                
<!ELEMENT dissertation (dedication?, chapter+, appendix*)>
<!ELEMENT dedication (#PCDATA)>
<!ELEMENT chapter (title, paragraph+)>
<!ELEMENT title (#PCDATA)>
<!ELEMENT paragraph (#PCDATA | figure | table)+>
<!ELEMENT figure EMPTY>
<!ELEMENT table EMPTY>
<!ELEMENT appendix (#PCDATA)>

この場合、論文には 1つの献辞 (dedication) を含めることができ、1つ以上の章 (chapter) を含めなければならず、0個以上の付録 (appendix) を含めることができる、という意味になります。それぞれのサブ要素は、指定されている順序で出現します (もし存在する場合)。一部の要素は、文字データだけを含みます。<paragraph> タグには、文字データまたは<figure>サブ要素または<table>サブ要素のいずれか(あるいはそれらの任意の組み合わせ) を含めることができます。構造は入れ子になる場合もありますが、この例には、基本的な妥当性の概念がすべて含まれています。

さて、ここでgnosis.xml.validity の特長に関する話になります。つまり、このモジュールを使えば、妥当な論文だけ を表すdissertation などのPythonオブジェクトを作成できるわけです。しかも、print コマンドやstr() 関数を使って、そのオブジェクトをXMLに変換すれば、そのXMLは該当するDTDと自動的に一致するようになります。




上に戻る


妥当性の実験

gnosis.xml.validity の働きを理解するには、実際に動かしてみるのが一番です。gnosis.xml.validity は、Spark パーサーから基本的な考え方を引き継いでいます。要するに、従来の順次処理のプログラミングではなく、Pythonのリフレクション機能を使って、妥当性クラス を定義していくわけです。ここには、興味深い左右対称の関係があります。ある意味では、Sparkgnosis.xml.validity は、まったく正反対の処理を行うと言ってもよいでしょう。前者は外部テキストの中に規則ベースの構造を想定するのに対し、後者は内部オブジェクトの中に規則ベースの構造を実現するからです。

妥当性クラスは、対応するDTDやXML Schemaとの直接的なつながりを持っています。つまり、妥当性の型を継承し、必要に応じてクラス属性を追加して特化するようになっているわけです。なお、対応するタグを持たない構造を表すクラスの名前には、先頭に下線を付けるというきまりがよく使われています。たとえば、論文の<paragraph> 要素には、PCDATA<figure> 要素、<table> 要素を組み込むことができるわけですが、<paragraph> 要素にコレクションとして組み込まれる離接型は、それ自体XMLタグを持ちません。したがって、下の例では、この離接型に_mixedpara という名前を付けています。


リスト2. dissertation.py
                
                from gnosis.xml.validityimport *
class appendix(PCDATA):pass
                class table(EMPTY):pass
                class figure(EMPTY):pass
                class _mixedpara(Or):     _disjoins = (PCDATA, figure, table)
class paragraph(Some):    _type = _mixedpara
class title(PCDATA):pass
                class _paras(Some):       _type = paragraph
class chapter(Seq):       _order = (title, _paras)
class dedication(PCDATA):pass
                class _apps(Any):         _type = appendix
class _chaps(Some):       _type = chapter
class _dedi(Maybe):       _type = dedication
class dissertation(Seq):  _order = (_dedi, _chaps, _apps)

DTDの場合と同じように、オブジェクトやXML文書の最上位レベルは、何かの規則を指定したどんなタグでもかまいません。ここでは、たまたまdissertation が最上位レベルになっていますが、下位レベルの文書を取り出して作成することもできます。たとえば、次のようになります。


リスト3. 妥当な論文の章を作成する
                
>>> from dissertation import chapter, title, _paras, paragraph, PCDATA
>>> chap1 = chapter(( title(PCDATA('About Validity')),
..                   _paras([paragraph(PCDATA('It is a good thing'))])
..                ))
>>> print chap1
<chapter><title>About Validity</title>
<paragraph>It is a good thing</paragraph>
</chapter>

<chapter> は、<title>_parasリストを含んだタプルで初期化されます。次のその<title> は、PCDATA で初期化され、その文字データそのものも (Unicode) 文字列で初期化されます。同じように、_paras リストに含まれている段落も、PCDATA で初期化されます。目的のオブジェクトが出来上がったら、それが妥当なXMLとして表示されるわけです。

このような入れ子構造の初期化項目は、指定のDTDの妥当性規則に厳密に従っていますが、方法としてはあまりスマートではありません。この点、gnosis.xml.validity は、はるかに フレンドリーな初期化を実行します。つまり、何かの型が必要になると、その型に対応する初期化関数がその型そのものの中に自然な形でリフトされるわけです。しかも、通常は正しい型の項目のリストで初期化されるような数量型の場合にも、1つの項目を指定することによって、その項目が長さ1の項目リストにリフト されることになります。このリフト処理は再帰的です。なお、リフト処理を行うSeq 型は、ファクトリー関数のLiftSeq() を使用する必要がありますが、他の型の場合はそれぞれ独自の初期化引数をリフトできます(詳細は、Pythonの不変の型からの新しいスタイルの継承に関係します)。このように説明すると複雑そうに聞こえるかもしれませんが、実際にコードを書いてみると単純明快です。

>>> from dissertation import LiftSeq
>>> chap1 = LiftSeq(chapter,('About Validity','It is a good thing'))
>>> print chap1
<chapter><title>About Validity</title>
<paragraph>It is a good thing</paragraph>
</chapter>




上に戻る


妥当性の確保

こうして妥当なXMLとオブジェクトを作成したわけですが、まだ何となくピンとこないかもしれません。この程度の妥当なXMLテキストであれば、手作業でも簡単に書けるからです。ところが、オブジェクトに対して修正を加える段になると、それが妥当性を守った修正であれ、崩した修正であれ、gnosis.xml.validity の真価を実感できるはずです。たとえば、次に示すのは、妥当性を保持した修正です。


リスト4. 段落を追加する (妥当性を保持した操作)
                
>>> paras_ch1 = chap1[1]
>>> paras_ch1 += [paragraph('OOP can enforce it')]
>>> print chap1
<chapter><title>About Validity</title>
<paragraph>It is a good thing</paragraph>
<paragraph>OOP can enforce it</paragraph>
</chapter>

では、制約に違反したことをしようとすると、どうなるでしょうか。たとえば、論文には献辞を1つしか含めることができません(リスト1 でそう指定されています)。


リスト5. 献辞をもう1つ追加する
                
>>> from dissertation import _dedi, dedication
>>> Maybe_dedication = _dedi([])
>>> print Maybe_dedication
>>> Maybe_dedication.append(dedication("To Mom."))
>>> print Maybe_dedication
<dedication>To Mom.</dedication>
>>> Maybe_dedication.append(dedication("Also to Dad."))
Traceback (most recent call last):
  File "<pyshell#71>", line 1, in ?
    Maybe_dedication.append(dedication("Also to Dad."))
  File "validity.py", line 140, in append
    raise LengthError, self.length_message % self._tag
LengthError: List <_dedi> must have length zero or one

さらに、数量の長さは正しくても、間違った型の項目を入れることはできません。


リスト6. 間違った型の項目を追加する
                
>>> from gnosis.xml.validity import ValidityError
>>> try:
..     paras_ch1.append(dedication("To my advisor"))
.. except ValidityError, x:
...    print x
Items in _paras must be of type <class 'dissertation.paragraph'>
(not <class 'dissertation.dedication'>)

制約違反から発生するすべての例外は、ValidityError に由来します。gnosis.xml.validity ライブラリーを使ってプログラミングする場合は、おそらくtry/exceptブロックの中にたくさんの処理を組み込むことになるので、制約違反の操作によって妥当でないオブジェクトを作成するのは不可能なはずです。




上に戻る


実装に関する注意点

gnosis.xml.validity は、厳密にはPython 2.2+ に対応しています。Pythonのそれより前のバージョンで実装することも不可能ではありませんが、やはりPythonの新しい機能をテストするための教材として使うのがよいと思います。特に、このライブラリーは、型/クラスの統一機能や、新しいスタイルのクラスを利用しています。このライブラリーをバージョンアップするときには、メタクラスで何か面白いことをしてみたいと思っていますし、プロパティーやスロットを組み入れる構想もあります。

gnosis.xml.validity は設計上、Pythonのイントロスペクション/リフレクション機能に大きく依存しています。主な機能性を担当しているのは、いくつかの抽象クラスです。それぞれの抽象クラスには、実際に何かを実行する 具体的な子クラスが必要ですが、それぞれの子クラスには1つのクラス属性を実装するだけで十分です。XMLタグがクラスに対応する場合、そのタグの名前はクラスの名前から直接取られることになります。すでに述べたとおり、クラス名の先頭に下線が付いている場合、そのクラスに対応するXMLタグはありません。したがって、ここで基本的な規則を整理すると、まず、何かのタグが対応している妥当性クラスは、その開始/終了タグでそれ自体を直列化することになり、タグのないクラスは、その生の内容を直列化することになるわけです(ただし、その内容には、タグの付いた項目が含まれている場合もあります)。もちろん、このしくみには1つの制限があります。つまり、gnosis.xml.validity は、先頭に下線の付いたXMLタグを含んだDTDを処理できません。この制限は、将来のバージョンで解決できる でしょうが、特にユーザーからの要望がなければ、そのままになる可能性もあります。

基本的な抽象クラスは、次のとおりです。

  • PCDATA は直接使用できるので、その意味では抽象クラスではありません。PCDATA を含んだ XML要素は、このクラスから継承するはずですが、それ以上の直列化を提供する必要はありません。ただし、Or 型の代替リストでは、PCDATA を挙げておく必要があります。この点は、DTD構文で厳密にモデル化されています。そのようなリストでは、PCDATA を (DTDの指定のとおりに) 最初に挙げることをお勧めしますが、現在、そのような指定が必須になっているわけではありません。
  • EMPTY も、DTD構文でモデル化されています。PCDATA と同じように、このクラスからも継承が行われますが、それ以上の直列化は必要ありません。
  • Or の子クラスには、クラス属性として_disjoins タプルを追加する必要があります。普通は、その1つの属性が実装全体に適用されます。その他の妥当性クラスは、そのタプルの中に列挙します。考え方としては、この離接型には複数の項目が必要ですが、現在の動作では、1つしか項目がない場合でもエラーは発生しません。
  • Seq の子クラスには、クラス属性として_order タプルを追加する必要があります。普通は、その1つの属性が実装全体に適用されます。そのタプルの中には、その他の妥当性クラスを複数指定する必要がありますが、Or の場合と同じく、現在の動作では、タプルの長さのチェックは行われません。ファクトリー関数であるListSeq() を利用するよりも、Seq の子クラスのインスタンスを生成するほうが普通は安全です。
  • Quantification 抽象クラスは、ある意味で特殊です。この記事のサンプルでは、Quantification の代わりに、その子クラス (やはり抽象クラス) を使っています。たとえば、次に示すのはSome クラスの実装です。

リスト7. Quantification抽象クラスの子クラスであるSome
                
class Some(Quantification):
    length_message = "List <%s> must have length >= 1"
    min_length = 1
    max_length = maxint
    

  • Maybe クラスとAny クラスの実装も、基本的に同じです。この2つもQuantification の子クラスであり、DTDに関しては数量オプションをすべてカバーしていますが、XML Schemaの場合は、その他のオプション (Three_to_Seven など) も利用でき、いずれも単純明快な実装が可能です。それらのオプションからはスマートなlength_message を生成できますが、特に複数の項目がある場合のメッセージは、プログラマーが書いたほうがきれいになるようです。
  • Quantification の具体的な子クラスには、別の妥当性クラスをポイントする_typeクラス属性を追加する必要があります。具体的な子クラスには、原則として独自のmin_lengthmax_lengthlength_message も追加できますが、中間的な属性を使用するほうが設計としては優れているように思います。



上に戻る


今後の課題

この記事を書いている時点で、gnosis.xml.validity は概念が先行しており、実際に欠けている機能がいくつかあります。一番大きな欠陥は、XMLタグの属性を追加する機能がないということでしょう。ということは、属性の妥当性を確保する機能もないわけです。構造という点からすれば、属性は (順序のない) サブ要素のようなものなので、gnosis.xml.validity の今後のバージョンでは、同じような妥当性確保のしくみを追加できると思います。これがまさに、現在の最優先課題です。

gnosis.xml.validity には、さらに次のような利便性も追加できそうです。

  • DTDやXML SchemaからPythonの妥当性クラスを自動的に生成する機能があれば、便利でしょう。ただし、DTDの場合とは違い、Pythonの妥当性クラスは、一定の順序で定義する必要があります。つまり、少なくとも、最初に各クラスを定義してからでなければ、別のクラスの属性としてそのクラスを指定できない、という順序の制約があります。
  • 既存の妥当なXML文書から読み込むことができれば確かに便利ですが、そのための最適な方法は何かという問題になると、これがなかなか複雑です。文書内のメンバー項目をより大きな構造の中に組み込むには、その項目自体がまず妥当なオブジェクトでなければならないので、項目を再帰的にたどっていく単純な方法ではうまくいきません。しかし、XML文書を直列化解除して、対応する妥当性クラスに落とし込むという方法なら可能でしょう。
  • 最後に、生成される妥当性クラスを簡単に処理するための高度なインターフェースも欲しいところです。このライブラリーの現在の動作では、あらゆる制約違反に対して例外が発生するようになっていますが、これをさらに便利なAPIに組み込むことも可能です。おそらく、サイレント障害やフラグの戻り値といった機能を組み込めるでしょうし、エラーに対する別のフォールバック処理も必要かもしれません。いずれにしても、最適なインターフェースを実装するには、ユーザー (著者自身も含む) による実験がさらに欠かせません。

gnosis.xml.validity の今後の方向性については、読者のみなさんのご意見がどうしても必要です。もちろん、この初期段階の機能だけでも、XMLプログラミングのいろいろな作業に役立つのは間違いないと思いますが、この種のライブラリーの開発がほとんど行われてこなかった現状からして、何が一番便利なのかが今ひとつつかみ切れないというのが正直なところです。



参考文献



著者について

Photo of David Mertz

David Mertz氏は多くの分野で活躍しています。ソフトウェア開発や、それについて著述もしています。その他、学術政策理念について分野を問わず、関係する雑誌に記事も書いています。かなり以前には、超限集合論、ロジック、モデル理論などを研究していました。その後、労働組合組織者として活動していました。そして、David Mertz氏自身は人生の半ばにもまだ達していないと思っているので、これから何かほかの仕事をするかもしれません。




記事の評価


サイト改善のため、ご意見をお寄せください。こちらのフォームからお願いいたします。



 


 


不充分・不完全である大変素晴らしい
 


この記事を共有する

del.icio.us del.icio.us newsing newsing FC2ブックマーク FC2ブックマーク
Choix! Choix! ニフティクリップ ニフティクリップ Yahoo!ブックマーク Yahoo!ブックマーク
MM/memo MM/memo CZブックマーク CZブックマーク livedoorクリップ livedoorクリップ
はてなブックマーク はてなブックマーク Buzzurl(バザール) Buzzurl(バザール)




上に戻る


    日本IBMについて プライバシー お問い合わせ