W3C XML Schemaの少し高度な話題
「今からでも遅くない」シリーズは、学習する機会を失ったけれどもまだ間に合うという技術を取り上げています。したがって、概要を知りたい方は前編を、業務でも使えるようになりたい方は後編を読むことにより習得できるように工夫してきました。前回は中編を挟み、W3C XML Schemaの読み書きが少しでもできることを目標としてきました。したがって、まだ業務でも使用できると言うほどには説明ができていません。このような経緯から、今回の後編では少し高度な話題を取り上げ、業務でも通用するようお役にたてればと考えています。
中編で取り上げたTomcatのweb.xmlのXMLスキーマは、実はその少し高度な知識を必要とします。また、W3C XML Schemaは再利用するにはDTDよりも向いていると説明しましたが、実際に例をもって説明をしていないため煙に巻いた感を残したままになっています。これらの話題を含め、実際に複雑なXMLスキーマを記述できるようになるための足がかりを提供したいと思います。
重複した定義をなくす
中編ではshop2.xmlを紹介しましたが、shop2.xmlは芋焼酎しか扱っていない店舗です。では、ワインを扱いたい場合にはどのようにXMLスキーマを作成すればいいのでしょうか。オープンソースApache Tuscanyで楽しむSOA 第3回「Web2.0から始めましょう(JSONRPC編)」でトスカーナワイン店という架空のネットショップを紹介しましたが、このショップのワインリストには、銘柄、銘柄(読み方)、ヴィンテージ、ランク、価格を明細として示しています。このうち、銘柄はMEIGARA要素、銘柄(読み方)はMEIGARA要素のKANA属性、価格はPRICE要素に置き換えることが可能です。ランクは芋焼酎にはない概念です。shop2.xmlでは芋焼酎しか扱っていませんでしたが、トスカーナワインも扱いたい場合、どのようなXML文書やXMLスキーマ文書を作成すればいいかは、前編や中編を読んでも想像できません。このように重複する要素が存在する場合、同じ要素を繰り返して定義しても冗長になります。まずは冗長さ、つまり重複した要素の定義の仕方を学ぶ必要があります。
それでは、芋焼酎とトスカーナワインを販売すると仮定した場合のXML文書の例から見ていきましょう。リスト1がその例で、shop3.xmlと命名します。
001:<?xml version="1.0" encoding="UTF-8"?> 002:<SHOP> 003: <GREETING OWNER="tomoharu">店主の好みの芋焼酎を厳選しました。</GREETING> 004: <MEIGARAS> 005: <SHOCHU> 006: <NAME KANA="ガンコショウチュウヤ">がんこ焼酎屋</NAME> 007: <ABV UNIT="%">25</ABV><!-- alcohol by volume(アルコール度数)の略 --> 008: <VOLUME UNIT="ml">1800</VOLUME> 009: <PRICE UNIT="円">2630</PRICE> 010: <MANUFACTURER>大石酒造</MANUFACTURER> 011: <KOJI>白</KOJI> 012: </SHOCHU> 013: <WINE> 014: <NAME KANA="ブルネッロ・ディ・モルタルチーノ">Brunello di Montalcino</NAME> 015: <VINTAGE>2004</VINTAGE> 016: <PRICE UNIT="円">10000</PRICE> 017: <RANK>DOCG</RANK> 018: </WINE> 019: <SHOCHU> 020: <NAME KANA="キシンジュ">貴心樹</NAME> 021: <ABV UNIT="%">25</ABV> 022: <VOLUME UNIT="ml">1800</VOLUME> 023: <PRICE UNIT="円">1724</PRICE> 024: <MANUFACTURER>オガタマ酒造</MANUFACTURER> 025: <KOJI>黒</KOJI> 026: </SHOCHU> 027: <SHOCHU> 028: <NAME KANA="トウジジュンペイベニイモゲンシュ">杜氏潤平紅芋原酒</NAME> 029: <ABV UNIT="%">38</ABV> 030: <VOLUME UNIT="ml">500</VOLUME> 031: <PRICE UNIT="円">2050</PRICE> 032: <MANUFACTURER>小玉醸造</MANUFACTURER> 033: </SHOCHU> 034: </MEIGARAS> 035:</SHOP>
shop2.xmlと異なる点は、MEIGARAS要素の子要素を、MEIGARAから芋焼酎の場合はSHOCHU要素、トスカーナワインの場合はWINE要素に変えているところです。芋焼酎とトスカーナワインに共通するNAME、ABV、VINTAGE、VOLUME、PRICE、MANUFACTURER要素を、各々の要素の子要素のはじめの方に持ってきています(これらの要素のうち、NAMEとPRICEだけを必須としています)。したがって、芋焼酎に特有なKOJI要素とトスカーナワイン特有のRANK要素はそれ以降に配置しています。また、NAME要素のKANA属性はカタカナとひらがなで統一がとれていないため、カタカナにしました。このXMLスキーマがリスト2のsample4.xsdになります。
001:<?xml version="1.0" encoding="UTF-8"?> 002:<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"> 003: 004:<xsd:group name="meigaraGroup"> 005: <xsd:annotation> 006: <xsd:documentation xml:lang="jp"> 007: このグループは芋焼酎とトスカーナワインに共通した 008: 要素を集めたもの。置換グループ(substitutionGroup) 009: を使っても要素の塊を再利用することができる。 010: </xsd:documentation> 011: </xsd:annotation> 012: <xsd:sequence> 013: <xsd:element ref="NAME"/> 014: <xsd:element ref="ABV" minOccurs="0" maxOccurs="1"/> 015: <xsd:element ref="VINTAGE" minOccurs="0" maxOccurs="1"/> 016: <xsd:element ref="VOLUME" minOccurs="0" maxOccurs="1"/> 017: <xsd:element ref="PRICE"/> 018: <xsd:element ref="MANUFACTURER" minOccurs="0" maxOccurs="1"/> 019: </xsd:sequence> 020:</xsd:group> 021: 022:<xsd:element name="SHOP" type="ShopType"/> 023: 024:<xsd:complexType name="ShopType"> 025: <xsd:sequence> 026: <xsd:element name="GREETING" type="GreetingType"/> 027: <xsd:element name="MEIGARAS" type="MeigarasType"/> 028: </xsd:sequence> 029:</xsd:complexType> 030: 031:<xsd:complexType name="GreetingType"> 032: <xsd:simpleContent> 033: <xsd:extension base="xsd:string"> 034: <xsd:attribute name="OWNER" type="xsd:string" use="required"/> 035: </xsd:extension> 036: </xsd:simpleContent> 037:</xsd:complexType> 038: 039:<xsd:complexType name="MeigarasType"> 040: <xsd:choice minOccurs="0" maxOccurs="unbounded"> 041: <xsd:element name="SHOCHU" type="ShochuType"/> 042: <xsd:element name="WINE" type="WineType"/> 043: </xsd:choice> 044:</xsd:complexType> 045: 046:<xsd:complexType name="ShochuType"> 047: <xsd:sequence> 048: <xsd:group ref="meigaraGroup"/> 049: <xsd:element ref="KOJI" minOccurs="0" maxOccurs="1"/> 050: </xsd:sequence> 051:</xsd:complexType> 052: 053:<xsd:complexType name="WineType"> 054: <xsd:sequence> 055: <xsd:group ref="meigaraGroup"/> 056: <xsd:element ref="RANK"/> 057: </xsd:sequence> 058:</xsd:complexType> 059: 060:<xsd:element name="NAME" type="NameType"/> 061:<xsd:element name="ABV" type="ABVType"/> 062:<xsd:element name="KOJI" type="KojiType"/> 063:<xsd:element name="VOLUME" type="VolumeType"/> 064:<xsd:element name="PRICE" type="PriceType"/> 065:<xsd:element name="MANUFACTURER" type="xsd:string"/> 066:<xsd:element name="VINTAGE" type="VintageType"/> 067:<xsd:element name="RANK" type="RankType"/> 068: 069:<xsd:complexType name="NameType"> 070: <xsd:simpleContent> 071: <xsd:extension base="xsd:string"> 072: <xsd:attribute name="KANA" type="xsd:string" use="required"/> 073: </xsd:extension> 074: </xsd:simpleContent> 075:</xsd:complexType> 076: 077:<xsd:complexType name="ABVType"> 078: <xsd:simpleContent> 079: <xsd:extension base="xsd:positiveInteger"> 080: <xsd:attribute name="UNIT" type="xsd:string" fixed="%"/> 081: </xsd:extension> 082: </xsd:simpleContent> 083:</xsd:complexType> 084: 085:<xsd:simpleType name="KojiType"> 086: <xsd:restriction base="xsd:string"> 087: <xsd:enumeration value="白"/> 088: <xsd:enumeration value="黒"/> 089: <xsd:enumeration value="黄"/> 090: </xsd:restriction> 091:</xsd:simpleType> 092: 093:<xsd:complexType name="VolumeType"> 094: <xsd:simpleContent> 095: <xsd:extension base="xsd:positiveInteger"> 096: <xsd:attribute name="UNIT" type="xsd:string" fixed="ml"/> 097: </xsd:extension> 098: </xsd:simpleContent> 099:</xsd:complexType> 100: 101:<xsd:complexType name="PriceType"> 102: <xsd:simpleContent> 103: <xsd:extension base="xsd:positiveInteger"> 104: <xsd:attribute name="UNIT" type="xsd:string" fixed="円"/> 105: </xsd:extension> 106: </xsd:simpleContent> 107:</xsd:complexType> 108: 109:<xsd:simpleType name="VintageType"> 110: <xsd:restriction base="xsd:positiveInteger"> 111: <xsd:minInclusive value="1900"/> 112: <xsd:maxInclusive value="2099"/> 113: </xsd:restriction> 114:</xsd:simpleType> 115: 116:<xsd:simpleType name="RankType"> 117: <xsd:restriction base="xsd:string"> 118: <xsd:enumeration value="DOC"/> 119: <xsd:enumeration value="DOCG"/> 120: <xsd:enumeration value="IGT"/> 121: <xsd:enumeration value="スーパーIGT"/> 122: </xsd:restriction> 123:</xsd:simpleType> 124: 125:</xsd:schema>