XMLのスキーマ定義の仕方(3)

っつーことで第3回いきます。今回が最終回っす。前回までで、profiles要素の定義を整備しました。次に進みましょう。

profile要素

  <xs:element name="profile">
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="name" />
        <xs:element ref="email" minOccurs="0" />
        <xs:element ref="skype" minOccurs="0" />
        <xs:element ref="webs" minOccurs="0" />
      </xs:sequence>
      <xs:attribute name="id" type="xs:NMTOKEN" use="required" />
    </xs:complexType>
  </xs:element>
  • profileには必ずid属性が必要で、値は1以上の整数でなければならない
  • profileの子要素は、name(必須), email(省略可), skype(省略可), webs(省略可)が順番に出現


さて、良く見れば、子要素の設定はほぼOK。4つの要素をsequenceで表現し、省略可能なものは minOccurs="0" で表現する。websも省略可能という仕様なので、こいつにも minOccurs を設定して完了です。

次に。xs:attribute で、この要素の属性を設定してます。idっていう属性がrequiredで、その型は xs:NMTOKEN ですよ、ということになっている。

参考 http://www.atmarkit.co.jp/fxml/tecs/021xsd/21.html

内容は整数でなければならんので、型だけ修正すれば良いですね。型は、独自で定義する以外に、XML Schemaで元々定義済みのビルトイン型が豊富に用意されています。simpleTypeだけですけどね。

XML Schema Part 2: Datatypes Second Edition

xs:string や xs:boolean, xs:integer あたりは頻出なので覚えておくと良いですね。xs:NMTOKEN というのは、XMLで「名前」として使えるトーク*1です。

ここでは「1以上の整数」なので、xs:positiveInteger を使いましょう。ちなみに「0以上の整数」だったらxs:nonNegativeIntegerになる。

この状態で、テストケースが通るハズです。そして試しに id に 0 や -1 を設定してみて、テストが失敗することも確認してみてください。

また、10以上20未満なんてのも指定可能です。参考 http://www.atmarkit.co.jp/fxml/tecs/034xsd/34.html

(余談)ユニーク制約

仕様漏れ…というか、わざとなんですがw profile要素のid属性の制約は「1以上の整数」であって、一意制約はつけていません。idとして使うならば、現実問題、この値は一意であるべきです。

そういった場合は、xs:unique という要素で制約をかけられるハズです。

具体的には、profiles要素の子要素に、こんなに書けば良いハズです。

  <xs:element name="profiles">
    ...
    <xs:unique name="uniqueId">
      <xs:selector xpath="profile" />
      <xs:field xpath="@id"/>
    </xs:unique>
  </xs:element>

profiles要素をカレントノードとして、そこからの相対XPathで profile を選択します。その中で、選択要素それぞれのXPath、"@id" が一意であれ、という記述のつもりなんです。

なんで余談なのかって、これ上手くうごかねーんすよww 多分名前空間絡みのミスがあるんじゃないかなぁ、と思ってるんですが、独力で解決できず…。無念なり。

だれか分かったら教えて下さい。超ヘルプ。

参考 http://www.atmarkit.co.jp/fxml/tecs/031xsd/31.html

name要素とemail要素

自動で吐いてくれたのは、こんな定義になっている。何でだか分からんけども。

  <xs:element name="name">
    <xs:complexType mixed="true" />
  </xs:element>

また余談だが、mixedってのは混在内容っつーのを表している。HTMLのように、「これは混在内容です」みたいなスキーマを表現したい時、つまりテキストと要素が混在する型を表現したい時に使う。マークアップ言語的にXMLを使う時に必要で、自分の用途にはあまり必要ない機能なので、詳しい事は知らんです。

まぁ、nameの型は xs:string で良いだろうから、単純に書き直してしまいます。email要素も同様で。

  <xs:element name="name" type="xs:string"/>

skype要素

さて、コイツには正規表現による制約がついていました。

  • skypeの内容は、[A-Za-z0-9]+ の正規表現にマッチしなければならない


しかしまぁ、子要素も属性もないので、まずはsimpleTypeであることは確定。で、正規表現による制約がついていなければ、xs:string で良いですよね。つまり、xs:stringを基底(base)として、ちょいとパターン(pattern)の制約(restriction)がついたsimpleTypeを定義して適用しましょう、というのが以下。

  <xs:element name="skype">
    <xs:simpleType>
      <xs:restriction base="xs:string">
        <xs:pattern value="[A-Za-z0-9]+" />
      </xs:restriction>
    </xs:simpleType>
  </xs:element>

なんて簡単なんだ。そんなん「覚えておかなきゃ書けない!」と思うかもしれないけど、XML SchemaXML Schemaがあることを思い出して下さい。補完でどうにかなります。俺も覚えてなかったけど、補完で書けたw

さて、テストケースは通っていますか?

参考 http://www.atmarkit.co.jp/fxml/tecs/025xsd/25.html

webs要素

  <xs:element name="webs">
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="web" maxOccurs="unbounded" />
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  • webs要素の直下には、web要素が0個以上存在する


一点だけ直せばいいですね。簡単だと思いますので、敢えて解答を書かないで次いきますw

web要素

  <xs:element name="web">
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="title" minOccurs="0" />
        <xs:element ref="url" />
      </xs:sequence>
      <xs:attribute name="type" type="xs:NMTOKEN" use="required" />
    </xs:complexType>
  </xs:element>
  • web要素にはtype属性が必須。値は "blog", "twitter", "wassr", "other" の何れか
  • webの子要素には title(省略可), url(必須) が順番に出現


まず、今までの知識で解決できる、後者の仕様はどうでしょうか。これも解答は省略。考えてみてください。

では前者。今までの知識で何とかしようと思ったら、

  <xs:element name="web">
    <xs:complexType>
      ...
      <xs:attribute name="type" use="required">
        <xs:simpleType>
          <xs:restriction base="xs:string">
            <xs:pattern value="(blog|twitter|wassr)"/>
          </xs:restriction>
        </xs:simpleType>
      </xs:attribute>
    </xs:complexType>
  </xs:element>

こんな所でしょうか。ちょっと複雑ですが。web要素の宣言→web要素の型定義→type属性の宣言→type属性の型定義 が、連鎖しています。一応、分解してみるとこんな感じ。

  <xs:element name="web" type="webType"/>
  
  <xs:complexType name="webType">
    <xs:sequence>
      <xs:element ref="title" minOccurs="0" />
      <xs:element ref="url" />
    </xs:sequence>
    <xs:attribute name="type" type="typeType" use="required"/>
  </xs:complexType>
  
  <xs:simpleType name="typeType">
    <xs:restriction base="xs:token">
      <xs:pattern value="(blog|twitter|wassr)"/>
    </xs:restriction>
  </xs:simpleType>

前者のように、まとめて書いてしまうやり方は、Javaの「無名クラス」のような感覚でしょうか。まさに「名前をつけない」訳ですし。名前をつけて、別に定義しておけば、他の所からも同じ定義を参照して再利用できる、という点も似てます。無名クラスは大きくなりすぎると見づらい、というのも一致していますね。どちらの記法を採用するかは、この辺りと同じ判断基準で良い気がします。

さて、ひとまずtype属性の値は正規表現で制約してみましたが。もっと良い方法があります。Javaで言う enum、つまり列挙型です。記述方法は以下の通り。基底となる型はxs:tokenとしてみました。

  <xs:simpleType name="typeType">
    <xs:restriction base="xs:token">
      <xs:enumeration value="blog" />
      <xs:enumeration value="twitter" />
      <xs:enumeration value="wassr" />
    </xs:restriction>
  </xs:simpleType>

参考 http://www.atmarkit.co.jp/fxml/tecs/032xsd/32.html

url要素

さて、title要素を飛ばして、url要素を考えてみましょう。まぁ、

  <xs:element name="url" type="xs:string" />

これでもいいんですが…。せっかくなので、こんな型を使ってみましょう。

  <xs:element name="url" type="xs:anyURI" />

title要素

というわけで、最後。コイツも、問題ないでしょう。nameやemailと同じです…

  <xs:element name="title" type="xs:string" />

…じゃあつまらないので、一つ無茶振りな仕様変更の宿題。xml:lang 属性を「つけてもよい」というスキーマに書き換えてみてください。

つまり、以下のXMLがvalidになるように修正です。

<?xml version="1.0" encoding="UTF-8"?>
<profiles
    xmlns="http://xet.jp/xml/ns/sample/profiles"
    xmlns:fooNS="http://xet.jp/xml/ns/sample/foo"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xet.jp/xml/ns/sample/profiles ../../main/resources/profiles.xsd">
  <profile id="1">
    <name>都元 ダイスケ</name>
    <email>dai.0304_at_gmail.com</email>
    <skype>cuervo1800</skype>
    <webs>
      <web type="blog">
        <title>都元ダイスケ IT-PRESS</title>
        <url>http://d.hatena.ne.jp/daisuke-m/</url>
      </web>
      <web type="twitter">
        <url>http://twitter.com/daisuke_m/</url>
      </web>
      <web type="wassr">
        <url>http://wassr.jp/user/daisukem/</url>
      </web>
    </webs>
  </profile>
  <profile id="1">
    <name>じゅんいち☆かとう</name>
    <webs>
      <web type="blog">
        <title xml:lang="ja">じゅんいち☆かとうの技術日誌</title>
        <url>http://d.hatena.ne.jp/j5ik2o/</url>
      </web>
      <web type="twitter">
        <url>http://twitter.com/j5ik2o/</url>
      </web>
    </webs>
  </profile>
  <fooNS:foo>
    <fooNS:bar/>
  </fooNS:foo>
  <fooNS:bar/>
</profiles>

ちなみに、解説していないテクニックを結構使います。ぶっちゃけかなり難しいです。以下にヒントを書いておくので、一個ずつ開いてみて、ゴールに近づいてみてください。

  1. xml名前空間の値は "http://www.w3.org/XML/1998/namespace"
  2. しかし、xml名前空間は、ルートタグに定義しなくても暗黙的に定義済みです。
  3. でも、スキーマは必要です。どこかを参照しなければ…?
  4. インポートすればいいんじゃね?
  5. xs:importを、xs:schemaの第一子要素に書こう。参考 http://www.atmarkit.co.jp/fxml/tecs/030xsd/30.html
  6. xs:simpleContentとかいうのを使うらしいよ
  7. xs:restriction(制約)の反対は xs:extension(拡張)らしいよ
  8. http://www6.airnet.ne.jp/manyo/xml/schema/step45-2.html

がんばれーー。

では、XML Schema講座は以上。もっと細かいことは下記のサイトで学べたりします。参考にどうぞ。