[技術講座]
ここ最近、XML がデータの交換形式やソフトウェアの設定ファイル等で使用されるケースが多くなってきました。XML はオブジェクト指向と親和性が高い技術だといわれます。そこで、UML を使って分析されたドメインのモデルから、XML のスキーマ設計を行うためのノウハウをまとめてみました。なお、UML と XML の基礎知識は既にあるものとして話を進めますので、不明な点がありましたら 参考文献 等をご参照ください。XML のスキーマ定義については、初版で使用した DTD に加え、RELAX と XML Schema での記述も追加しました。 XML Schema も W3C 勧告となり、徐々に使われ始めていますので、参考にしていただければと思います。
念のためお断りしておきますが、目下各種団体により XML のスキーマ定義の標準化が進んでいます。独自に XML の設計をする前に、まずは使用できる標準規格がないか調べてみることをお勧めします。その上で、利用できそうな標準規格がない、あるいはローカルに使用するため、独自の XML ファイルの形式を作成する必要があるような場合に、このドキュメントを参考にして設計を行っていただければと思います。
注文管理システムの「注文伝票」を例題として、以後の説明を行いたいと思います。ドメインを分析した結果、以下のモデルが作成されました。注文伝票 (OrderSlip) には複数の注文 (Order) が含まれます。1つの注文で複数の商品 (Product) を注文することができ、各商品に対する注文明細 (OrderLineItem) が作られます。また、注文は顧客 (Customer) との間に関連があります。顧客は個人顧客 (PersonalCustomer) と法人顧客 (CorporateCustomer) に分類できます。
まず最初はクラスの設計です。UML の各クラスに対して、基本的に XML の要素を1つ対応させます。例えば、顧客クラス Customer でしたら次のようになります。スキーマの定義は属性が入ったところで説明したいと思います。
<customer/>
少し補足しておくと、XML の要素となるのはクラス自身ではなく、そのインスタンスの変数であると考えるのが最近の傾向のようです。したがって、タグ名の先頭は大文字ではなく小文字で始めることにします。また、複合語の場合の2番目以降の各単語の区切りは大文字とします。これらは Java でよく使われる命名規則にしたがったものです。
続いて属性の設計です。大きく分けて XML の属性で表す方法と子要素で表す方法があります。顧客クラス Customer を例に見ていきます。
一番簡単な方法です。XML の属性を用いると、ID 型や IDREF 型、NMTOKEN 型などを指定することができますが、3.2 の形式のように複雑な構造を持たせることはできません。また、SAX パーサを使用した場合、3.2 の形式よりアクセスが簡単にできます。一意キーや、バージョン情報などのメタ情報はこの形式を用いるとよいでしょう。
<customer
id="C001"
name="山田 太郎"
address="東京都大田区XXXX-XX"
phone="03-1234-XXXX"/>
<!ELEMENT customer EMPTY>
<!ATTLIST customer id ID #REQUIRED
name CDATA #REQUIRED
address CDATA #REQUIRED
phone CDATA #REQUIRED>
<tag name="customer">
<attribute name="id" required="true" type="ID"/>
<attribute name="name" required="true" type="string"/>
<attribute name="address" required="true" type="string"/>
<attribute name="phone" required="true" type="string"/>
</tag>
<elementRule role="customer" type="emptyString"/>
<xsd:element name="customer" type="Customer"/>
<xsd:complexType name="Customer">
<xsd:attribute name="id" type="xsd:ID" use="required"/>
<xsd:attribute name="name" type="xsd:string" use="required"/>
<xsd:attribute name="address" type="xsd:string" use="required"/>
<xsd:attribute name="phone" type="xsd:string" use="required"/>
</xsd:complexType>
RELAX は DTD の構造をほぼそのまま XML で置き換えた感じですが、XML Schema では、まず Customer という型を定義してから、それを要素 customer に割り当てるという方式をとっています。このあたりはオブジェクト指向的な考え方を取り入れているようです。
なお、XML Schema においてプレフィクス xsd は名前空間 "https://www.w3.org/2001/XMLSchema" に割り当てられているとします (以下同じ)。
属性をクラスに対応する要素の子要素とし、その値をコンテンツで表す方法です。例のように、属性自体にさらに複雑な構造を持たせることができます。DOM パーサや XSLT などからの使い勝手は、3.1 の形式でも 3.2 の形式でもそれほど変わりありません。一般の属性値は 3.1、3.2 どちらの方法で表すこともできますが、個人的には汎用性が高いこちらをお勧めします。
<customer>
<id>C001</id>
<name>
<familyName>山田</familyName>
<givenName>太郎</givenName>
</name>
<address>東京都大田区XXXX-XX</address>
<phone>03-1234-XXXX</phone>
</customer>
<!ELEMENT customer (id, name, address, phone)>
<!ELEMENT id (#PCDATA)>
<!ELEMENT name (familyName, givenName)>
<!ELEMENT familyName (#PCDATA)>
<!ELEMENT givenName (#PCDATA)>
<!ELEMENT address (#PCDATA)>
<!ELEMENT phone (#PCDATA)>
<tag name="customer"/>
<elementRule role="customer">
<sequence>
<element name="id" type="string"/>
<ref label="name"/>
<element name="address" type="string"/>
<element name="phone" type="string"/>
</sequence>
</elementRule>
<tag name="name"/>
<elementRule role="name">
<sequence>
<element name="familyName" type="string"/>
<element name="givenName" type="string"/>
</sequence>
</elementRule>
<xsd:element name="customer" type="Customer"/>
<xsd:complexType name="Customer">
<xsd:sequence>
<xsd:element name="id" type="xsd:string"/>
<xsd:element name="name" type="Name"/>
<xsd:element name="address" type="xsd:string"/>
<xsd:element name="phone" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="Name">
<xsd:sequence>
<xsd:element name="familyName" type="xsd:string"/>
<xsd:element name="givenName" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
3.1 と 3.2 の中間的な方法です。後で関連の設計に似た形式を使用しています。
<customer>
<id value="C001"/>
<name value="山田 太郎"/>
<address value="東京都大田区XXXX-XX"/>
<phone value="03-1234-XXXX"/>
</customer>
<!ELEMENT customer (id, name, address, phone)>
<!ELEMENT id EMPTY>
<!ATTLIST id value NMTOKEN #REQUIRED>
<!ELEMENT name EMPTY>
<!ATTLIST name value CDATA #REQUIRED>
<!ELEMENT address EMPTY>
<!ATTLIST address value CDATA #REQUIRED>
<!ELEMENT phone EMPTY>
<!ATTLIST phone value CDATA #REQUIRED>
<tag name="customer"/>
<elementRule role="customer">
<sequence>
<ref label="id"/>
<ref label="name"/>
<ref label="address"/>
<ref label="phone"/>
</sequence>
</elementRule>
<tag name="id">
<attribute name="value" required="true" type="NMTOKEN"/>
</tag>
<elementRule role="id" type="emptyString"/>
<tag name="name">
<attribute name="value" required="true" type="string"/>
</tag>
<elementRule role="name" type="emptyString"/>
<tag name="address">
<attribute name="value" required="true" type="string"/>
</tag>
<elementRule role="address" type="emptyString"/>
<tag name="phone">
<attribute name="value" required="true" type="string"/>
</tag>
<elementRule role="phone" type="emptyString"/>
<xsd:element name="customer" type="Customer"/>
<xsd:complexType name="Customer">
<xsd:sequence>
<xsd:element name="id" type="CustomerID"/>
<xsd:element name="name" type="Name"/>
<xsd:element name="address" type="Address"/>
<xsd:element name="phone" type="Phone"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CustomerID">
<xsd:attribute name="value" type="xsd:NMTOKEN" use="required"/>
</xsd:complexType>
<xsd:complexType name="Name">
<xsd:attribute name="value" type="xsd:string" use="required"/>
</xsd:complexType>
<xsd:complexType name="Address">
<xsd:attribute name="value" type="xsd:string" use="required"/>
</xsd:complexType>
<xsd:complexType name="Phone">
<xsd:attribute name="value" type="xsd:string" use="required"/>
</xsd:complexType>
以上を整理すると、顧客クラス Customer のお勧めの設計は以下のようになります。id は属性にしましたが、ドメイン的な要素が強い場合は子要素にしてもよいでしょう。
<customer id="C001">
<name>山田 太郎</name>
<address>東京都大田区XXXX-XX</address>
<phone>03-1234-XXXX</phone>
</customer>
<!ELEMENT customer (name, address, phone)>
<!ATTLIST customer id ID #REQUIRED>
<!ELEMENT name (#PCDATA)>
<!ELEMENT address (#PCDATA)>
<!ELEMENT phone (#PCDATA)>
<tag name="customer">
<attribute name="id" required="true" type="ID"/>
</tag>
<elementRule role="customer">
<sequence>
<element name="name" type="string"/>
<element name="address" type="string"/>
<element name="phone" type="string"/>
</sequence>
</elementRule>
<xsd:element name="customer" type="Customer"/>
<xsd:complexType name="Customer">
<xsd:sequence>
<xsd:element name="name" type="xsd:string"/>
<xsd:element name="address" type="xsd:string"/>
<xsd:element name="phone" type="xsd:string"/>
</xsd:sequence>
<xsd:attribute name="id" type="xsd:ID" use="required"/>
</xsd:complexType>
次は関連の設計です。ネットワーク構造の UML と、ツリー構造の XML の違いが最も顕著に表れる部分です。
一方のクラスに対応する XML の要素が他方のクラスに対応する XML の要素を含む形 (階層構造) にします。含まれる側 (子要素) は値オブジェクトになるので、複数のオブジェクト間で共有することはできません。モデル上では次のように集約 (あるいはコンポジション) で表すとよいでしょう。
<order>
<product>ノート</product>
<quantity>20</quantity>
<customer>
<name>山田 太郎</name>
<address>東京都大田区XXXX-XX</address>
<phone>03-1234-XXXX</phone>
</customer>
</order>
<!ELEMENT order (product, quantity, customer)>
<!ELEMENT customer (name, address, phone)>
...
<tag name="order"/>
<elementRule role="order">
<sequence>
<element name="product" type="string"/>
<element name="quantity" type="decimal"/>
<ref label="customer"/>
</sequence>
</elementRule>
<tag name="customer"/>
<elementRule role="customer">
<sequence>
<element name="name" type="string"/>
<element name="address" type="string"/>
<element name="phone" type="string"/>
</sequence>
</elementRule>
<xsd:element name="order" type="Order"/>
<xsd:complexType name="Order">
<xsd:sequence>
<xsd:element name="product" type="xsd:string"/>
<xsd:element name="quantity" type="xsd:decimal"/>
<xsd:element name="customer" type="Customer"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="Customer">
<xsd:sequence>
<xsd:element name="name" type="xsd:string"/>
<xsd:element name="address" type="xsd:string"/>
<xsd:element name="phone" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
一方のクラスに ID 型の属性を設け、他方のクラスにそれへの参照を持たせます。どちらのオブジェクトも複数の他のオブジェクトから参照することができます。オブジェクトを一意に識別できれば、必ずしも ID 型を使わなくてもかまいませんが、XML の ID 型を使用することで、ファイル内での一意性が保証され、XPath による指定が簡単になります。
<order>
<customerRef ref="C001"/>
<product>ノート</product>
<quantity>20</quantity>
</order>
<customer id="C001">
<name>山田 太郎</name>
<address>東京都大田区XXXX-XX</address>
<phone>03-1234-XXXX</phone>
</customer>
<!ELEMENT order (customerRef, product, quantity)>
<!ELEMENT customerRef EMPTY>
<!ATTLIST customerRef ref IDREF #REQUIRED>
<!ELEMENT customer (name, address, phone)>
<!ATTLIST customer id ID #REQUIRED>
...
<tag name="order"/>
<elementRule role="order">
<sequence>
<ref label="customerRef"/>
<element name="product" type="string"/>
<element name="quantity" type="decimal"/>
</sequence>
</elementRule>
<tag name="customerRef">
<attribute name="ref" required="true" type="IDREF"/>
</tag>
<elementRule role="customerRef" type="emptyString"/>
<tag name="customer">
<attribute name="id" required="true" type="ID"/>
</tag>
<elementRule role="customer">
<sequence>
<element name="name" type="string"/>
<element name="address" type="string"/>
<element name="phone" type="string"/>
</sequence>
</elementRule>
<xsd:element name="order" type="Order"/>
<xsd:complexType name="Order">
<xsd:sequence>
<xsd:element name="customerRef" type="CustomerRef"/>
<xsd:element name="product" type="xsd:string"/>
<xsd:element name="quantity" type="xsd:decimal"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="CustomerRef">
<xsd:attribute name="ref" type="xsd:IDREF" use="required"/>
</xsd:complexType>
<xsd:element name="customer" type="Customer"/>
<xsd:complexType name="Customer">
<xsd:sequence>
<xsd:element name="name" type="xsd:string"/>
<xsd:element name="address" type="xsd:string"/>
<xsd:element name="phone" type="xsd:string"/>
</xsd:sequence>
<xsd:attribute name="id" type="xsd:ID" use="required"/>
</xsd:complexType>
DTD ではファイル内での一意性しか指定できませんが、XML Schema ではある範囲内での一意性や、参照の制約を記述することもできます。このあたりはデータベース技術からの影響を感じます。
<xsd:key name="customer.key">
<xsd:selector xpath=".//customer"/>
<xsd:field xpath="@id"/>
</xsd:key>
<xsd:keyref name="customer.ref" refer="customer.key">
<xsd:selector xpath=".//customerRef"/>
<xsd:field xpath="@ref"/>
</xsd:keyref>
なお、order から customer への参照は、子要素の属性ではなく、直接 order の属性にしてしまう方法もあります。
<order customer="C001">
<product>ノート</product>
<quantity>20</quantity>
</order>
関連のロール名の扱い方です。
ロール名のみを XML の要素とします。クラス名が必要ならば XML の属性として追加することもできます。1つのクラスを複数の役割で使用する場合、例のようにパラメータエンティティを用いると DTD の記述が簡単になります。
<order>
<product>ノート</product>
<quantity>20</quantity>
<customer type="Person">
<name>山田 太郎</name>
<address>東京都大田区XXXX-XX</address>
<phone>03-1234-XXXX</phone>
</customer>
</order>
<!ENTITY % Person.conts "(name, address, phone)">
<!ENTITY % Person.attrs "type NMTOKEN #FIXED 'Person'">
<!ELEMENT order (product, quantity, customer)>
<!ELEMENT customer %Person.conts;>
<!ATTLIST customer %Person.attrs;>
...
RELAX でパラメータエンティティに相当するのは attPool
と hedgeRule
です。
<attPool role="Person.attrs">
<attribute name="type" type="NMTOKEN">
<enumeration value="Person"/>
</attribute>
</attPool>
<hedgeRule label="Person.conts">
<sequence>
<element name="name" type="string"/>
<element name="address" type="string"/>
<element name="phone" type="string"/>
</sequence>
</hedgeRule>
<tag name="order"/>
<elementRule role="order">
<sequence>
<element name="product" type="string"/>
<element name="quantity" type="decimal"/>
<ref label="customer"/>
</sequence>
</elementRule>
<tag name="customer">
<ref role="Person.attrs"/>
</tag>
<elementRule role="customer"/>
<hedgeRef label="Person.conts"/>
</elementRule>
XML Schema では型定義と要素定義を分けて書くことができますので、この方法を自然な形で使うことができます。
<xsd:complexType name="Person">
<xsd:sequence>
<xsd:element name="name" type="xsd:string"/>
<xsd:element name="address" type="xsd:string"/>
<xsd:element name="phone" type="xsd:string"/>
</xsd:sequence>
<xsd:attribute name="type" type="xsd:NMTOKEN" fixed="Person"/>
</xsd:complexType>
<xsd:element name="order" type="Order"/>
<xsd:complexType name="Order">
<xsd:sequence>
<xsd:element name="product" type="xsd:string"/>
<xsd:element name="quantity" type="xsd:decimal"/>
<xsd:element name="customer" type="Person"/>
</xsd:sequence>
</xsd:complexType>
クラス名のみを XML の要素とし、ロール名はその属性で表します。1つのクラスが複数の役割を持っており、その構造を再利用したい場合に便利な方法ですが、意味的に若干不適切な気がしますし、他の方法で代用可能なので、あまりお勧めしません。
<order>
<product>ノート</product>
<quantity>20</quantity>
<person role="customer">
<name>山田 太郎</name>
<address>東京都大田区XXXX-XX</address>
<phone>03-1234-XXXX</phone>
</person>
</order>
<!ELEMENT order (product, quantity, person)>
<!ELEMENT person (name, address, phone)>
<!ATTLIST person role NMTOKEN #IMPLIED>
...
<tag name="order"/>
<elementRule role="order">
<sequence>
<element name="product" type="string"/>
<element name="quantity" type="decimal"/>
<ref label="person"/>
</sequence>
</elementRule>
<tag name="customer">
<attribute name="role" type="NMTOKEN"/>
</tag>
<elementRule role="person"/>
<sequence>
<element name="name" type="string"/>
<element name="address" type="string"/>
<element name="phone" type="string"/>
</sequence>
</elementRule>
<xsd:element name="order" type="Order"/>
<xsd:complexType name="Order">
<xsd:sequence>
<xsd:element name="product" type="xsd:string"/>
<xsd:element name="quantity" type="xsd:decimal"/>
<xsd:element name="person" type="Person"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="Person">
<xsd:sequence>
<xsd:element name="name" type="xsd:string"/>
<xsd:element name="address" type="xsd:string"/>
<xsd:element name="phone" type="xsd:string"/>
</xsd:sequence>
<xsd:attribute name="role" type="xsd:NMTOKEN"/>
</xsd:complexType>
ロール名、クラス名とも XML の要素で表し、クラスに対応する要素をロール名に対応する要素の子要素とします。クラスに対応する要素の再利用が可能ですが、構造が若干複雑になります。
<order>
<product>ノート</product>
<quantity>20</quantity>
<customer>
<person>
<name>山田 太郎</name>
<address>東京都大田区XXXX-XX</address>
<phone>03-1234-XXXX</phone>
</person>
</customer>
</order>
<!ELEMENT order (product, quantity, customer)>
<!ELEMENT customer (person)>
<!ELEMENT person (name, address, phone)>
...
<tag name="order"/>
<elementRule role="order">
<sequence>
<element name="product" type="string"/>
<element name="quantity" type="decimal"/>
<ref label="customer"/>
</sequence>
</elementRule>
<tag name="customer"/>
<elementRule role="customer">
<ref label="person"/>
</elementRule>
<tag name="person"/>
<elementRule role="person"/>
<sequence>
<element name="name" type="string"/>
<element name="address" type="string"/>
<element name="phone" type="string"/>
</sequence>
</elementRule>
<xsd:element name="order" type="Order"/>
<xsd:complexType name="Order">
<xsd:sequence>
<xsd:element name="product" type="xsd:string"/>
<xsd:element name="quantity" type="xsd:decimal"/>
<xsd:element name="customer">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="person" type="Person"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="Person">
<xsd:sequence>
<xsd:element name="name" type="xsd:string"/>
<xsd:element name="address" type="xsd:string"/>
<xsd:element name="phone" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
関連の多重度が n の場合の表し方を考えます。
リスト自体を1つの要素にします。まとまりとして意識することができ、他に属性や関連がある場合区別しやすいので、個人的にはこの方法をお勧めします。
<order>
<customer>山田 太郎</customer>
<date>2001-05-01</date>
<time>10:05:00</time>
<orderLineItemList>
<orderLineItem>
<product>ノート</product>
<quantity>20</quantity>
</orderLineItem>
<orderLineItem>
<product>鉛筆</product>
<quantity>10</quantity>
</orderLineItem>
</orderLineItemList>
</order>
<!ELEMENT order (customer, date, time, orderLineItemList)>
<!ELEMENT orderLineItemList (orderLineItem)+>
<!ELEMENT orderLineItem (product, quantity)>
...
<tag name="order"/>
<elementRule role="order">
<sequence>
<element name="customer" type="string"/>
<element name="date" type="date"/>
<element name="time" type="time"/>
<ref label="orderLineItemList"/>
</sequence>
</elementRule>
<tag name="orderLineItemList"/>
<elementRule role="orderLineItemList">
<ref label="orderLineItem" occurs="+"/>
</elementRule>
<tag name="orderLineItem"/>
<elementRule role="orderLineItem"/>
<sequence>
<element name="product" type="string"/>
<element name="quantity" type="decimal"/>
</sequence>
</elementRule>
DTD で (orderLineItem)+
の + の部分は下限値が0の場合 * を指定します。RELAX も occurs="+"
の + は下限値が0のときは * にします。
<xsd:element name="order" type="Order"/>
<xsd:complexType name="Order">
<xsd:sequence>
<xsd:element name="customer" type="xsd:string"/>
<xsd:element name="date" type="xsd:date"/>
<xsd:element name="time" type="xsd:time"/>
<xsd:element name="orderLineItemList">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="orderLineItem" type="OrderLineItem"
minOccurs="1" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="OrderLineItem">
<xsd:sequence>
<xsd:element name="product" type="xsd:string"/>
<xsd:element name="quantity" type="xsd:decimal"/>
</xsd:sequence>
</xsd:complexType>
リスト自体は XML の要素にせず、各要素を単純に並べます。階層構造は単純になりますが、他に属性や関連がある場合区別しにくく、リスト全体をまとまりとして扱うことはできません。
<order>
<customer>山田 太郎</customer>
<date>2001-05-01</date>
<time>10:05:00</time>
<orderLineItem>
<product>ノート</product>
<quantity>20</quantity>
</orderLineItem>
<orderLineItem>
<product>鉛筆</product>
<quantity>10</quantity>
</orderLineItem>
</order>
<!ELEMENT order (customer, date, time, orderLineItem+)>
<!ELEMENT orderLineItem (product, quantity)>
...
<tag name="order"/>
<elementRule role="order">
<sequence>
<element name="customer" type="string"/>
<element name="date" type="date"/>
<element name="time" type="time"/>
<ref label="orderLineItem" occurs="+"/>
</sequence>
</elementRule>
<tag name="orderLineItem"/>
<elementRule role="orderLineItem"/>
<sequence>
<element name="product" type="string"/>
<element name="quantity" type="decimal"/>
</sequence>
</elementRule>
<xsd:element name="order" type="Order"/>
<xsd:complexType name="Order">
<xsd:sequence>
<xsd:element name="customer" type="xsd:string"/>
<xsd:element name="date" type="xsd:date"/>
<xsd:element name="time" type="xsd:time"/>
<xsd:element name="orderLineItem" type="OrderLineItem"
minOccurs="1" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="OrderLineItem">
<xsd:sequence>
<xsd:element name="product" type="xsd:string"/>
<xsd:element name="quantity" type="xsd:decimal"/>
</xsd:sequence>
</xsd:complexType>
最後に汎化 (継承) の表し方です。
スーパークラスに対応する要素をサブクラスに対応する要素が保有する形にします。スーパークラスの構造が再利用できますが、階層が深くなるとスーパークラスの属性へのアクセスが面倒になります。
<personalCustomer>
<customer id="C001">
<name>山田 太郎</name>
<address>東京都大田区XXXX-XX</address>
<phone>03-1234-XXXX</phone>
</customer>
<birthday>1969-01-23</birthday>
</personalCustomer>
<corporateCustomer>
<customer id="C002">
<name>○×商事</name>
<address>東京都港区XXXX-XX</address>
<phone>03-5678-XXXX"/</phone>
</customer>
<charge>佐藤 花子</charge>
</corporateCustomer>
<!ELEMENT customer (name, address, phone)>
<!ATTLIST customer id ID #REQUIRED>
<!ELEMENT personalCustomer (customer, birthday)>
<!ELEMENT corporateCustomer (customer, charge)>
...
<tag name="customer">
<attribute name="id" required="true" type="ID"
</tag>
<elementRule role="customer">
<sequence>
<element name="name" type="string"/>
<element name="address" type="string"/>
<element name="phone" type="string"/>
</sequence>
</elementRule>
<tag name="personalCustomer"/>
<elementRule role="personalCustomer">
<sequence>
<ref label="customer"/>
<element name="birthday" type="date"/>
</sequence>
</elementRule>
<tag name="corporateCustomer"/>
<elementRule role="corporateCustomer">
<sequence>
<ref label="customer"/>
<element name="charge" type="string"/>
</sequence>
</elementRule>
<xsd:complexType name="Customer">
<xsd:sequence>
<xsd:element name="name" type="xsd:string"/>
<xsd:element name="address" type="xsd:string"/>
<xsd:element name="phone" type="xsd:string"/>
</xsd:sequence>
<xsd:attribute name="id" type="xsd:ID" use="required"/>
</xsd:complexType>
<xsd:element name="personalCustomer" type="PersonalCustomer"/>
<xsd:complexType name="PersonalCustomer">
<xsd:sequence>
<xsd:element name="customer" type="Customer"/>
<xsd:element name="birthday" type="xsd:date"/>
</xsd:sequence>
</xsd:complexType>
<xsd:element name="corporateCustomer" type="CorporateCustomer"/>
<xsd:complexType name="PersonalCustomer">
<xsd:sequence>
<xsd:element name="customer" type="Customer"/>
<xsd:element name="charge" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
スーパークラスの属性を各サブクラスに下げ、サブクラスのみ XML の要素として定義します。共通な属性が少なくサブクラスごとの違いが大きい場合や、サブクラス間の違いを意識して利用したい場合に、この形が便利です。スーパークラス部分の定義にパラメータエンティティを用いると、DTD の記述が簡単になります。
<personalCustomer id="C001">
<name>山田 太郎</name>
<address>東京都大田区XXXX-XX</address>
<phone>03-1234-XXXX</phone>
<birthday>1969-01-23</birthday>
</personalCustomer>
<corporateCustomer id="C002">
<name>○×商事</name>
<address>東京都港区XXXX-XX</address>
<phone>03-5678-XXXX</phone>
<charge>佐藤 花子</charge>
</corporateCustomer>
<!ENTITY % Customer.conts "(name, address, phone)">
<!ENTITY % Customer.attrs "id ID #REQUIRED">
<!ELEMENT personalCustomer (%Customer.conts;, birthday)>
<!ATTLIST personalCustomer %Customer.attrs;>
<!ELEMENT corporateCustomer (%Customer.conts;, charge)>
<!ATTLIST corporateCustomer %Customer.attrs;>
...
<attPool role="Customer.attrs">
<attribute name="id" required="true" type="ID"
</attPool>
<hedgeRule label="Customer.conts">
<sequence>
<element name="name" type="string"/>
<element name="address" type="string"/>
<element name="phone" type="string"/>
</sequence>
</hedgeRule>
<tag name="personalCustomer">
<ref role="Customer.attrs"/>
</tag>
<elementRule role="personalCustomer">
<sequence>
<hedgeRef label="Customer.conts"/>
<element name="birthday" type="date"/>
</sequence>
</elementRule>
<tag name="corporateCustomer">
<ref role="Customer.attrs"/>
</tag>
<elementRule role="corporateCustomer">
<sequence>
<hedgeRef label="Customer.conts"/>
<element name="charge" type="string"/>
</sequence>
</elementRule>
XML Schema では拡張による派生を用いて継承関係を記述することができます。
<xsd:complexType name="Customer">
<xsd:sequence>
<xsd:element name="name" type="xsd:string"/>
<xsd:element name="address" type="xsd:string"/>
<xsd:element name="phone" type="xsd:string"/>
</xsd:sequence>
<xsd:attribute name="id" type="xsd:ID" use="required"/>
</xsd:complexType>
<xsd:element name="personalCustomer" type="PersonalCustomer"/>
<xsd:complexType name="PersonalCustomer">
<xsd:complexContent>
<xsd:extension base="Customer">
<xsd:sequence>
<xsd:element name="birthday" type="xsd:date"/>
</xsd:sequence>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:element name="corporateCustomer" type="CorporateCustomer"/>
<xsd:complexType name="CorporateCustomer"
<xsd:complexContent>
<xsd:extension base="Customer">
<xsd:sequence>
<xsd:element name="charge" type="xsd:string"/>
</xsd:sequence>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
サブクラスの属性を全てスーパークラスに上げ、スーパークラスのみ XML の要素として定義します。7.2 とは反対に、共通な属性が多くサブクラスごとの違いが少ない場合や、サブクラス間の違いをあまり意識せず統一的に扱いたい場合、この形が便利です。サブクラスの区別が必要ならば属性で指定します。
<customer id="C001" type="PersonalCustomer">
<name>山田 太郎</name>
<address>東京都大田区XXXX-XX</address>
<phone>03-1234-XXXX</phone>
<birthday>1969-01-23</birthday>
</customer>
<customer id="C002" type="CorporateCustomer">
<name>○×商事</name>
<address>東京都港区XXXX-XX</address>
<phone>03-5678-XXXX</phone>
<charge>佐藤 花子</charge>
</customer>
<!ELEMENT customer (name, address, phone, birthday?, charge?)>
<!ATTLIST customer id ID #REQUIRED
type (PersonalCustomer | CorporateCustomer) #REQUIRED>
...
<tag name="customer">
<attribute name="id" required="true" type="ID"/>
<attribute name="type" required="true" type="NMTOKEN">
<enumeration value="PersonalCustomer"/>
<enumeration value="CorporateCustomer"/>
</attribute>
</tag>
<elementRule role="customer">
<sequence>
<element name="name" type="string"/>
<element name="address" type="string"/>
<element name="phone" type="string"/>
<element name="birthday" type="date" occurs="?"/>
<element name="charge" type="string" occurs="?"/>
</sequence>
</elementRule>
RELAX では属性の値によって、要素の内容を変化させることができます。以下の例では、属性 type の値が PersonalCustomer なら子要素 birthday を、属性 type の値が CorporateCustomer なら子要素 charge をもつと定義しています。
<attPool role="Customer.attrs">
<attribute name="id" required="true" type="ID"
</attPool>
<hedgeRule label="Customer.conts">
<sequence>
<element name="name" type="string"/>
<element name="address" type="string"/>
<element name="phone" type="string"/>
</sequence>
</hedgeRule>
<tag name="customer" role="PersonalCustomer">
<ref role="Customer.attrs"/>
<attribute name="type" required="true" type="NMTOKEN">
<enumeration value="PersonalCustomer"/>
</attribute>
</tag>
<elementRule role="PersonalCustomer" label="customer">
<sequence>
<hedgeRef label="Customer.conts"/>
<element name="birthday" type="date"/>
</sequence>
</elementRule>
<tag name="customer" role="CorporateCustomer">
<ref role="Customer.attrs"/>
<attribute name="type" required="true" type="NMTOKEN">
<enumeration value="CorporateCustomer"/>
</attribute>
</tag>
<elementRule role="CorporateCustomer" label="customer">
<sequence>
<hedgeRef label="Customer.conts"/>
<element name="charge" type="string"/>
</sequence>
</elementRule>
<xsd:element name="customer" type="Customer"/>
<xsd:complexType name="Customer">
<xsd:sequence>
<xsd:element name="name" type="xsd:string"/>
<xsd:element name="address" type="xsd:string"/>
<xsd:element name="phone" type="xsd:string"/>
<xsd:element name="birthday" type="xsd:date" minOccurs="0"/>
<xsd:element name="charge" type="xsd:string" minOccurs="0"/>
</xsd:sequence>
<xsd:attribute name="id" type="xsd:ID" use="required"/>
<xsd:attribute name="type" type="CustomerType" use="required"/>
</xsd:complexType>
<xsd:simpleType name="CustomerType">
<xsd:restriction base="xsd:NMTOKEN">
<xsd:enumeration value="PersonalCustomer"/>
<xsd:enumeration value="CorporateCustomer"/>
</xsd:restriction>
</xsd:simpleType>
XML Schema ではスーパークラスで宣言された要素にサブクラスの内容を記述することができます。その場合、XML ファイルにおいて、属性 xsi:type で実際の型を指定する必要があります。なお、プレフィクス xsi は名前空間 "https://www.w3.org/2001/XMLSchema-instance" に割り当てられているとします。
<xsd:element name="customer" type="Customer">
<xsd:complexType name="Customer">
<xsd:sequence>
<xsd:element name="name" type="xsd:string"/>
<xsd:element name="address" type="xsd:string"/>
<xsd:element name="phone" type="xsd:string"/>
</xsd:sequence>
<xsd:attribute name="id" type="xsd:ID" use="required"/>
</xsd:complexType>
<xsd:complexType name="PersonalCustomer">
<xsd:complexContent>
<xsd:extension base="Customer">
<xsd:sequence>
<xsd:element name="birthday" type="xsd:date"/>
</xsd:sequence>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:complexType name="PersonalCustomer">
<xsd:complexContent>
<xsd:extension base="Customer">
<xsd:sequence>
<xsd:element name="charge" type="xsd:string"/>
</xsd:sequence>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<customer id="C001" xsi:type="PersonalCustomer">
<name>山田 太郎</name>
<address>東京都大田区XXXX-XX</address>
<phone>03-1234-XXXX</phone>
<birthday>1969-01-23</birthday>
</customer>
<customer id="C002" xsi:type="CorporateCustomer">
<name>○×商事</name>
<address>東京都港区XXXX-XX</address>
<phone>03-5678-XXXX</phone>
<charge>佐藤 花子</charge>
</customer>
ここまで紹介したガイドラインにしたがって、例題の「注文伝票」を XML で設計した結果が次のクラス図とファイルです。
今回の設計のポイントは次の通りです。
なお、今回の説明で XML Schema の定義は型ベースで行いましたが、DTD や RELAX に似た形式で書くこともできます。OrderSlip2.xsd はその形式で記述したものです。
以上、XML 特有の構造による変更を加えることによって、UML の分析モデルから XML の設計モデルが作成可能なことがお分かりいただけたかと思います。このガイドラインが、みなさんの UML-XML を用いたソリューションのお役に立てば幸いです。Let's UML & XML !
UML 関係
XML 関係
© 2001-2002 OGIS-RI Co., Ltd. |
|