XMLの論考: RELAX NGによる逆襲 第1回

W3CのXML Schemaを打ち負かす

RELAX NGのスキーマは、妥当なXMLインスタンスのクラスを記述する手段として、W3CのXML Schemaのスキーマよりも強力で、厳密で、意味の記述が直接的です。十分な実績のあるDTDの意味体系を拡張する一方で、データ型を直交的に拡張でき、関連インスタンス・モデルを簡単に構築できるというのが、このRELAX NGの「売り」です。この3回シリーズの記事では、David Mertz氏がRELAX NGを初めて取り上げます。まずはその第1回をご覧ください。

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

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



2003年 2月 15日

私はW3CのXML Schemaについては、これまでずっと懐疑的でした。そのため、XMLそのものについてもある程度の危惧がありました。W3CのXML Schema仕様の制定には、関心も経歴もさまざまに異なる多様な企業や団体が参加し、それぞれが自らの望むところを少しずつ組み込んで仕様を急いで作り上げたという経緯があります。その結果として出来上がったのは、委員会作業の典型とも言うべき難解な規格でした。実際のところ、私自身もあまりにも多くの懸念材料があるため、基本的には検証用としてDTDを使用し、何かの問題点があればもっぱらアプリケーション・レベルで対応するという手法を勧めてきたわけです。

ところが、1か月ほど前からRELAX NGのことを真剣に調べるようになりました。多くの読者の皆さんと同じように、私自身もこの代替スキーマ言語については前から聞いていましたが、基本的なところは同じで、若干のスペルの違いがあるだけだろうと思い込んでいました。ところが、その思い込みは大間違いでした。RELAX NGは、ほとんどすべての点で、W3CのXML Schemaよりも、さらにはDTDよりも優れているのです。現に、RELAX NGが順序なしの (または半順序付きの) 内容モデルをサポートできるという点は、OOPのデータ型の意味体系とXML要素の直線的な並べ方とがどうしてもなじまないという、私のかねてからの悩みを解消してくれるものでもあったわけです。

この記事は、「XMLの論考」のRELAX NG三部作の第1回です。今回は、RELAX NGの基本的な意味体系を取り上げ、データ型の扱いにも触れます。第2回では、RELAX NGに対応したツールやライブラリーを見ていきます。そして最終回では、RELAX NGの短縮構文をさらに詳しく説明する予定です。

意味体系モデル

RELAX NGの意味体系は非常に直接的であり、この点ではDTDの意味体系の自然な拡張と言えます。RELAX NGスキーマで記述するのは、数量定義、順序、選択肢からなる「パターン」です。また、順序なしコレクションのパターンもサポートしています。この点はDTDもW3CのXML Schemaもサポートしていません(ただし、SGMLはサポートしていますが、柔軟性ではRELAX NGより劣ります)。さらに、RELAX NGでは要素と属性をほとんど同じように扱います。DTDやW3CのXML Schemaのように両者を厳密に区別するよりも、要素と属性を同じように扱うほうがXMLの概念によりよくマッチします。実際の設計では、属性を使うか要素を使うかの選択は、設計上の考慮事項によっていい加減に決められたり、文脈によって決められたりします。

RELAX NGで使用できる数量定義はDTDの場合と同じです。どんなパターンにも、<oneOrMore><zeroOrMore><optional> のいずれかによって条件を設定できます。この書き方は、DTDの数量詞である+*? にそれぞれ相当します (これらの数量詞は、正規表現などでも使用されています)。現に、RELAX NGの短縮文法ではDTDの場合とまったく同じ数量詞を使用します。確かに、このような漠然とした数量詞では、厳密な個数制約を記述するのが困難です(W3CのXML SchemaのminOccurs 属性やmaxOccurs 属性なら厳密に記述できます)。将来的に、より柔軟な個数制約を組み込んだRELAX NGの改訂版が出るとしても特に異論はありませんが、DTDの場合とは異なり、この種の問題は、名前付きパターンを利用して今でも簡単に解決できます。

パターンの順序を設定するには、各パターンをその順序で並べるだけでよいわけですが、同じレベルで並べたパターンには、<choice> 要素、<group> 要素、<interleave> 要素のいずれかを使って、それぞれ異なる意味を与えることができます。まず<group> 要素はDTDの括弧と同じ意味で使います。<group> 要素それ自体には意味はありませんが、<choice> 要素または<interleave> 要素の内部で使用すると、一連のパターンが1つのパターンとして扱われるようになります。また、<choice> 要素を使用すれば、その中に含まれている各パターンが選択肢になります。一方、<interleave> 要素を使った場合は、その中に含まれているパターンの個数は尊重されますが、順序は問わないということになります。たとえば、図書館の利用者を例にとれば、名前、ID番号、貸し出し中の本などの項目が存在するでしょうが、各項目の順序は問題になりません。このうち、本について言えば、本はタイトルかISBNのどちらかで特定できます(両方そろわないと特定できないというケースはまれでしょう)。このような例をRELAX NGで書くと、次のようになります。

リスト1. 図書館利用者のスキーマ
<element name="patron"
         xmnln="http://relaxng.org/ns/structure/1.0">
  <interleave>
    <element name="name"><text/></element>
    <element name="id-num"><text/></element>
    <zeroOrMore>
      <element name="book">
        <choice>
          <attribute name="isbn"/>
          <attribute name="title"/>
        </choice>
      </element>
    </zeroOrMore>
  </interleave>
</element>

このスキーマは、そのまま読み上げるだけで簡単に理解できます。では次に、このXML構文に対応した短縮構文を見てみましょう。

リスト2. 図書館利用者の短縮構文
element patron {
  element name { text }   &
  element id-num { text } &
  element book {
    (attribute isbn { text } |
     attribute title { text } )
  }*
}

実際に、DTDやW3CのXML Schemaでは、このような図書館利用者の妥当なレコードを書けません。少なくとも手の込んだ裏技を使うか、正確さをある程度犠牲にしない限り不可能です。たとえば、次の2つのレコードは妥当です。

リスト3. 図書館利用者の妥当なレコード
<patron>
  <book isbn="0-528-84460-X"/>
  <name>John Doe</name>
  <id-num>12345678</id-num>
  <book title="Why RELAX is Clever"/>
</patron>
<patron>
  <id-num>09876545</id-num>
  <name>Jane Moe</name>
</patron>

しかし、リスト4 は、他のスキーマでは基本的に対応できないという意味で妥当ではありません(DTDとW3CのXML Schemaの場合、属性は必須か省略可能かのいずれかであり、属性の相互関係を記述することはできません)。

リスト4. 図書館利用者の妥当でないレコード
<patron>
  <name>John Doe</name>
  <id-num>12345678</id-num>
  <book title="Why RELAX is Clever" isbn="0-528-84460-X"/>
  <book/>
</patron>

さらに、RELAX NGスキーマで順序なしコレクション(この場合は、名前、ID番号、1冊以上の本) を定義できるというのは、元々順序を問わないデータにXMLは勝手に順序をつけてしまうという私の不満(YAMLの記事などをご覧ください) に対する答えでもあるわけです。

このように、<interleave> 要素の基本的な動作から興味深い結論を導き出せます。つまり、text/PCDATAセクションと副要素を混合することもできれば、テキスト・ブロックの許容数 (個数) を制御することもできるというわけです。たとえば、いくつかの省略可能タグや必須タグをまたいで、1つの連続したPCDATAのフローをどこにでも記述できるということになります。


属性と要素の統一

XMLのごく普通の書き方では、1つの要素の中に1つの特別な属性を組み込むか、子要素のコレクション (PCDATA内容) を組み込むかのいずれかであり、その両方を書くことはありません。たとえば、gnosis.xml.pickle 直列化形式では、次のような異種混合リストを定義しています。

リスト5. gnosis.xml.pickleスタイルのリストの一部
<list>
  <item type="string" value="Bar"/>
  <item type="list">
    <item type="numeric" value="17"/>
    <item type="None"/> <!-- None is singleton w/o value -->
  </item>
</list>

どの<item> についても、子要素が組み込まれているか、value 属性が組み込まれているかのいずれかです。DTDを使った場合は、この規則に近いものを次のように記述できます。

リスト6. pickleの規則に近いものを記述したDTD
<!ELEMENT list (item+)>
<!ELEMENT item (item*)>
<!ATTLIST item
    type  (None | string | numeric | list) #REQUIRED
    value CDATA #IMPLIED >

このDTDは前述のXMLインスタンス文書に対応していますが、間違った対応になることもあります。

リスト7. DTDに間違った対応をする妥当でないリスト
<list>
  <item type="None" value="Some">
    <item type="string" value="More"/>
  </item>
  <item type="list"/>
</list>

W3CのXML Schemaは非常に難解ですが、このケースについて言えば、結局はDTDと同じ程度のものしか記述できません。たとえば、<item> に子要素ではなくPCDATAを組み込む単純なケースに対応したスキーマを次に示します。

リスト8. W3CのXML Schemaで書いた概略スキーマ
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:element name="list">
  <xsd:element name="item" minOccurs="1" maxOccurs="unbounded">
    <xsd:complexType>
      <xsd:simpleContent>
        <xsd:extension base="xsd:string">
          <xsd:attribute name="type" type="xsd:string"/>
          <xsd:attribute name="value" type="xsd:string"
                         use="xsd:optional"/>
        </xsd:extension>
      </xsd:simpleContent>
    </xsd:complexType>
  </xsd:element>
</xsd:element>

DTDでもW3CのXML Schemaでも、属性をあまりにも特別視して、要素とはまったく違った扱いをしているという点に問題があります。一方、RELAX NGではその両方を同じように扱います(ここで単純なPCDATAのケースを示したのは、とりあえず名前付きパターンの説明を後回しにするためです)。

リスト9. pickleに対応したRELAX NGのXML構文
<element name="list" xmlns="http://relaxng.org/ns/structure/1.0>
  <oneOrMore>
    <element name="item">
      <choice>
        <text/>
        <attribute name="value"/>
      </choice>
    </element>
  </oneOrMore>
</elment>

短縮構文では次のように書けます。

リスト10. pickleに対応したRELAX NGの短縮構文
element list { element item { attribute type {text},
                      (attribute value {text} | {text}) }+ }

名前付きパターン

副パターンの入れ子の定義 (しかも文脈によって変わる定義) は、RELAX NGで利用できる唯一のスタイルです。また、文法の中に名前付きパターンを組み込むこともできます。文法を使えば、RELAX NGスキーマで検証用のルート要素を明示的に指定できます。文法には、<grammar> というルート要素、1つの<start> 要素、ゼロ個以上の<define> 要素を入れます。この<define> 要素に再帰的要素を組み込めるのは注目に値します。

定義と参照の書き方の一例としてサンプル文法を次に示します。前述の例で省略した入れ子の<item> 要素を、ここでは定義しています。

リスト11. pickleに対応したRELAX NG文法 (先ほどの例を改善したもの)
<grammar xmlns="http://relaxng.org/ns/structure/1.0>
  <start>
    <element name="list">
      <ref name="items"/>
    </element>
  </start>
  <define name="items">
    <oneOrMore>
      <element name="item">
        <choice>
          <ref name="items"/>
          <attribute name="value"/>
        </choice>
      </element>
    </oneOrMore>
  </define>
</grammar>

これでようやく、属性か子要素かを選択しながら直列化する前述の手法に対応した正確な検証用制約を定義できました。実際の作業では、これよりも多くの名前付きパターンを定義することになります。それぞれの名前付きパターンでは、数量定義や選択肢などの中で他のパターンを自由に参照できます。


データ型

W3CのXML Schemaには複雑なデータ型コレクションが用意されている一方で、DTDではデータ型の扱いが基本的に定義されていません。それに対して、RELAX NGの場合は、完全にモジュラー化された拡張可能なデータ型処理のしくみが用意されています。RELAX NGのユーザーは基本的に、W3CのXML Schemaに付属しているデータ型コレクション全体をそのまま利用します。名前空間を指定する場合と同じく、データ型ライブラリーも、datatypeLibrary 属性を定義した最も近い上位要素の中で指定されているものが適用されます。それで、すべてのデータ値について、データ型ライブラリーをいちいち定義することも不可能ではありません。

リスト12. 冗長なデータ型指定
<element name="foo">
  <choice>
    <datatype="integer"
      datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"/>
    <datatype="float"
      datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"/>
  </choice>
</element>

しかし、最上位レベルでデータ型ライブラリーを指定するほうが、書き方としてはシンプルです。個々のデータ値について別のデータ型ライブラリーを適用する必要があれば、そのつど各<data> 要素で指定を上書きできます。その一例を次に示します。

リスト13. シンプルなデータ型指定
<element name="foo"
      datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"/>
  <choice>
    <datatype="integer"/>
    <datatype="float"/>
  </choice>
</element>

すでに見たとおり、複合的なデータ型の定義にも、要素や属性の定義と同じパターンを利用できるというのは優れた機能です。これに最も適したパターン要素は<choice> でしょうが、<list> (後述) などの要素も利用できる場合があります。

列挙値は、<data> 要素の代わりに<value> 要素で指定できます。その一例を次に示します。

リスト14. choiceを使った列挙値
<element name="foo">
  <choice>
    <value type="integer">1</value>
    <value type="integer">2</value>
    <value type="integer">3</value>
    <value type="string">infinity</value>
  </choice>
</element>

ライブラリーに組み込まれている一部のデータ型では、パラメーター化が可能です。このオプションが存在している場合は、明示的な列挙という手段をわざわざ使わなくても有効値の範囲を絞り込むことができます。この効果は、ライブラリーで見られる効果、つまりW3CのXML Schemaで得られる効果と同じです。その一例を次に示します。

リスト15. パラメーターによるデータ型指定
<element name="foo">
  <datatype="string">
    <param name="maxLength">10</param>
  </data>
</element>

データ型ライブラリーを利用する場合でも、<list> 要素を使って、いくらかカスタマイズしたデータ型を定義できます。要は、それぞれのデータ型に対応するトークンをスペースで区切って並べただけのリストです。他の場合と同じく、このようなリストでも、要素か属性のいずれかの内容を記述できます。たとえば、属性の中に1つ以上の整数値のコレクションを含める場合は、次のようにします。

リスト16. 複合的なデータ型指定
<element name="foo">
  <attribute name="numbers">
    <list>
      <oneOrMore>
        <datatype="integer"/>
      </oneOrMore>
    </list>
  </attribute>
</element>

適合する文書は次のようになります。

<foo numbers="1 2 3 988765"/>

妥当でない文書は次のようになります。

<foo numbers="word"/>

今後の記事

RELAX NGについては、この記事で取り上げたことがすべてではありませんが、実際のところ、触れなかったことはほとんどありません。これほどシンプルな概念をごくわずかに積み重ねただけで、これほど大きな効果を実現できるというのは、実に驚くべきことです。この後の2つの「XMLの論考」では、文法のマージ、情報セットの拡充 (裏を返せば情報セットが限られているということでもあります)、個数制約の定義 (でっち上げ ?) などのほかに、意味体系にかかわる概念をいくつか取り上げる予定ですが、基本的には、ツールと短縮構文が中心になります。

参考文献

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=XML
ArticleID=241023
ArticleTitle=XMLの論考: RELAX NGによる逆襲 第1回
publish-date=02152003