XML Schema: やるべきこと、やってはいけないこと

Kohsuke KAWAGUCHI
なんばりょうすけ

原文はこちら

[]内は訳者による注である。

はじめに

XML Schema は学ぶのも使うのも簡単だ … 落とし穴をよける方法を 知っていればね。「やるべきこと」はこれだ。最低これだけおぼえて おけばいい。

何を言ってるかわからない? そういう人は チュートリアルを読んで欲しい。

そして「やってはいけないこと」がこれだ。

長くておぼえられない? それならこの一行バージョンをおぼえてほしい。

「XML Schema = DTD + データタイプ + 名前空間」だと思え!

実際「やってはいけない」ことをやらないことで失うものは何もない。 信じて欲しい。ここに書いたルールに従うなら XML Schema と一緒でも ハッピーに暮せるはずだ。

この文書を書いた動機

すでにWeb上には似たような文書がいくつかある。しかし僕はそれら の文書は特殊な人たちによって書かれている、ということ気 が付いた。彼らはものごとの限界に挑戦する素晴らしい人たちだ。彼 らは XML Schema ワーキンググループのメンバーでさえ想像もしなかっ たようなクールな裏技を発明せずにはいられないんだ。

彼らにしてみれば XML Schema は新しい素敵なオモチャというわけだ。

そういうのとは違う文書が必要だ。ビジネスのためにXML Schema を 使う人たち、使おうとしているが途方にくれている人たち、のための 文書が。

そういうわけで、この文書の目的は、あなたがなにをすべきで、なに をすべきでないかについての堅実なガイドラインを示すことにある。

コメントはいつでも歓迎。一つでもあれば是非しらせて欲しい。
[ Kohsuke KAWAGUCHI は日本語をとてもよく理解するので彼に直接メールするのがいいだろう。 日本語が変だとか誤字脱字の類いを見つけた場合は なんばりょうすけまでどうぞ。]

なぜ複合型を避けるべきか

あなたが複合型が何か知らないなら幸いである。なぜならあなたが 書きたいスキーマには複合型なんて必要ないからだ。実際、複合型 を使って書かれたスキーマはいつでも複合型を使わずに書くことができる。
だとすれば、そんな役に立たないものを学ぶために貴重な時間を費やす 必要などあるのだろうか?

納得した? それならここから先は読まなくていい。。。

複合型はモデルグループに継承を足し算して使いやすさを引き算したも のだ。どちらも内容モデルを定義するのに使われるという意味では兄弟 みたいなものだ。複合型は他の複合型やモデルグループの中から使えな いので使いやすさの点で難がある。一方モデルグループはそういう制限 なしで使える。

複合型のアドバンテージは「継承」だけだ。そこで、なぜあなたが継承 を使わなくてよいか、を説明しよう。継承には二種類ある。 拡張(extension)制限(restriction)の二つだ。

拡張によって基底型の内容モデルの後ろに要素を追加することができる。 ということは、こんなモデルグループを書けば拡張と意味的に同じこと ができるんだ。


<xs:group name="extendedType">
  <xs:sequence>
    <xs:group ref="baseType"/>
    
    <!-- append things that you want -->
    ....
  </xs:sequence>
</xs:group>

制限によって基底型の内容モデルを制限することができる。しかし、複 合型[と制限]を使っても、内容モデル全体を書かなくてはいけないこと に変りはないんだ[差分で書けるわけではないということ]。基本的に複 合型を使おうがモデルグループを使おうが、 同じものを書かなくてはならない。

それなら制限を使うと何が嬉しいんだろう。嬉しいのはたった一つ、エ ラーチェックができることだ。バリデータは内容モデルが正しく制限さ れていなければ、おそらくエラーを報告するだろう。

しかし不幸なことに、これはほとんどアドバンテージにはならないんだ。

第一に、バリデータにとってこのチェックを厳密に実施することは大変 なことなんだ。 仕様書の この制約を定義している箇所をちょっと見て欲しい。何が許され何 が許されないかを指定するのに 3.9.6 章が丸ごと割かれている。[バリ データを実装する]開発者がこの制約チェックを実施しないで済ませた い、という強い誘惑に駆られたとしても不思議じゃないのがわかるだろ う。このあたりは XML Schema ワーキンググループのメンバーが開発し た XSV でさえ、部分的にしかサポートしていないんだ。

そういうわけで、あなたが使うバリデータがこの[制限による継承に関する] 制約チェックを完全に実施しないということは、多いにありそうなことだ。 これで制限の持っていた唯一のアドバンテージは消え去ってしまったわけだ。

第二に、せっかく制限を正しく書いても、あなたの使うバリデータでは エラーになることがあるんだ。次の例で考えよう。


基底型:
<xs:all>
  <xs:element name="a" />
  <xs:element name="b" />
  <xs:element name="c" />
</xs:all>

制限による派生型:
<xs:all>
  <xs:element name="b" />
  <xs:element name="a" />
</xs:all>

後者は前者の正しい制限のように見える。しかし XML Schema はこれを 禁止している。こんなのは氷山の一角だ。この件に興味があるならMSL の最後のページにあたって欲しい。

モデルグループを複合型のかわりに使っていればこんな問題とは無縁で いられるんだ。

なぜ属性宣言を避けるべきか

正確に言うと、避けるべきなのはグローバル属性宣言であって、 ローカル属性宣言は使ってよい。グローバル属性宣言というの は例えばこういうものだ。


<xs:schema xmlns:xs="http://www.w3.org/2001/XMSchema"
      targetNamespace="http://best.practice.com">
  <!-- attribute whose name is foo -->
  <xs:attribute name="foo" type="xs:float" />
  
  <xs:element name="root">
    <xs:complexType>
      <!-- content model -->
      ....
      
      <xs:attribute ref="foo" />
    </xs:complexType>
  </xs:element>
</xs:schema>

実は、このスキーマはこんな文書インスタンスを受理しない

<root xmlns="http://best.practice.com" foo="5.12">
   ...
</root>

属性グループならこんな問題はない。 だから属性宣言のかわりに属性グループを使うべきだ。


<xs:schema xmlns:xs="http://www.w3.org/2001/XMSchema"
      targetNamespace="http://best.practice.com">
  <xs:attributeGroup name="root.attributes">
    <!-- attribute whose name is foo -->
    <xs:attribute name="foo" type="xs:float" />
  </xs:attributeGroup>
  
  <xs:element name="root">
    <xs:complexType>
      <!-- content model -->
      ....
      
      <xs:attributeGroup ref="root.attributes" />
    </xs:complexType>
  </xs:element>
</xs:schema>

属性グループからは他の属性グループを参照できる。こうやって、共通属性 を一つの属性グループに書いて、よそから参照することができるんだ。

なぜ記法宣言を避けるべきか

記法のことなんて聞いたことなかったとしても大丈夫。それによってあ なたは何も失ってはいないんだから。記法は[SGMLとの]後方互換性のた めだけにある。いまさら学ぶ必要なんて全くない。

敢えて記法のことを知りたいなら、XML Schema の記法は DTD のそれと は互換性がないことは知っておいた方がいい。なぜ互換性がないかとい えば、XML Schema の記法は QName なんだ。

仕様書にある例を見てみよう。


<xs:notation name="jpeg"
             public="image/jpeg" system="viewer.exe" />

<xs:element name="picture">
 <xs:complexType>
  <xs:simpleContent>
   <xs:extension base="xs:hexBinary">
    <xs:attribute name="pictype">
     <xs:simpleType>
      <xs:restriction base="xs:NOTATION">
       <xs:enumeration value="jpeg"/>
       <xs:enumeration value="png"/>
       . . .
      </xs:restriction>
     </xs:simpleType>
    </xs:attribute>
   </xs:extension>
  </xs:simpleContent>
 </xs:complexType>
</xs:element>

<picture pictype="jpeg">...</picture>

この例はオッケーだ。しかし次の例は上のスキーマには受理されない。 たとえプレフィクス "pic" が正しく宣言されていてもダメだ。


<pic:picture pictype="jpeg"> ... </pic:picture>

頭が混乱してきた? 記法は QName だから正しくはこう書かなくちゃいけ ないんだ。


<pic:picture pictype="pic:jpeg"> ... </pic:picture>

明らかにこれは唯一の存在意義[後方互換性]を失わせている。

記法にこだわる理由なんて全く何もない。記法は SGML のためのものだ。

なぜローカル宣言を避けるべきか

XML Schema では要素宣言の内側にさらに要素宣言が書ける。


<xs:schema xmlns:xs="http://www.w3.org/2001/XMSchema"
      targetNamespace="http://best.practice.com">
  <xs:element name="person">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="familyName" type="xs:string" />
        <xs:element name="lastName" type="xs:string" />
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

しかし一般には可能な限りこれを避けるべきだ。なぜなら上のスキーマは こんな文書インスタンスを受理しないからだ。


<person xmlns="http://best.practice.com">
  <familyName> KAWAGUCHI </familyName>
  <lastName> Kohsuke </lastName>
</person>

かわりにこんなふうに書かなくてはいけない。


<foo:person xmlns:foo="http://best.practice.com">
  <familyName> KAWAGUCHI </familyName>
  <lastName> Kohsuke </lastName>
</foo:person>

これはタイプ量が増えるだけではなく、XML 名前空間の悪い使い方だ。 この問題を避けるには、こう書くとよい。


<xs:schema xmlns:xs="http://www.w3.org/2001/XMSchema"
      targetNamespace="http://best.practice.com">
  <xs:element name="person">
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="familyName" />
        <xs:element ref="lastName" />
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  
  <xs:element name="familyName" type="xs:string"/>
  <xs:element name="lastName" type="xs:string"/>
</xs:schema>

他に選択肢がなくて、どうしてもローカル要素宣言を使う必要があるのなら、 elementFormDefault="qualified"schema 要素に追加するといい。でもこれが何を意味する のか理解しようなんて思っちゃいけない。それこそ時間の無駄ってやつだ。

なぜ置換グループを避けるべきか

TBD.

なぜカメレオンスキーマを避けるべきか

XML Schema は targetNamespace 属性なしの schema 要素を許している。そしてそんなスキーマを カメレオンスキーマと呼ぶ人たちがいる。
なぜカメレオンスキーマが不適切か… いや、知らなくちゃいけないのは それを避けるべきということだ。

一つの理由は、ここでバリデータ間の相互運用性の問題がでてくる可能 性が大きいからだ。

もう一つの理由はカメレオンスキーマを使ったクールな裏技を発明する 人がいるからだ。だけどそんな裏技に手をだしちゃいけない。それはス キーマハッカーのやることだ。善良なる市民がやることじゃない。

それでもなお、カメレオンスキーマをなぜ避けるべきなのか正確な理由 を知りたいなら、それが何かを学ぶ必要がある。

こんなカメレオンスキーマを考えてみよう。


<xs:schema xmlns:xs="http://www.w3.org/2001/XMSchema">
  <!-- note that targetNamespace attribute is absent. -->
  
  <xs:element name="person">
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="familyName" />
        <xs:element ref="lastName" />
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:element name="familyName" type="xs:string"/>
  <xs:element name="lastName" type="xs:string"/>
</xs:schema>

ここで他のスキーマファイルを書いて上のスキーマを include 要素を使ってインクルードするとしよう。


<xs:schema xmlns:xs="http://www.w3.org/2001/XMSchema"
           targetNamespace="http://best.practice.com">
  
  <xs:include schemaLocation="above.xsd" />
  
  <xs:element name="root">
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="person" maxOccurs="unbounded" />
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

これでオッケーのようだが、実はこれではダメだ。赤で書かれた行を見 てほしい。これは familyName 要素を参照しているよう に見える。しかしこれは間違いだ。
このカメレオンスキーマは targetNamespace="http://best.practice.com/" のスキー マからインクルードされているので、familyName 要素は この名前空間の要素になっているんだ。だからこの要素宣言を参照するには、 赤い行をこんなふうに書き換えなくちゃいけない。


<xs:element ref="bp:familyName" xmlns:bp="http://best.practice.com" />

ここで対象名前空間がhttp://www.foo.comのスキーマか らこのカメレオンスキーマを再利用したかったらどうしたらいいだろう? 答えは「それは不可能」だ。

これでカメレオンスキーマの唯一のメリットが失われたのがわかった だろう。

さらに困るのは、このエラーを検出しないバリデータがあるということ だ。そのようなバリデータは見つからない部品が後から出てくるかもし れないと考えるからだ。