Copyright © The Organization for the Advancement of Structured Information Standards [OASIS] 2001. All Rights Reserved.
This document and translations of it may be copied and furnished to others, and derivative works that comment on or otherwise explain it or assist in its implementation may be prepared, copied, published and distributed, in whole or in part, without restriction of any kind, provided that the above copyright notice and this paragraph are included on all such copies and derivative works. However, this document itself may not be modified in any way, such as by removing the copyright notice or references to OASIS, except as needed for the purpose of developing OASIS specifications, in which case the procedures for copyrights defined in the OASIS Intellectual Property Rights document must be followed, or as required to translate it into languages other than English.
The limited permissions granted above are perpetual and will not be revoked by OASIS or its successors or assigns.
This document and the information contained herein is provided on an "AS IS" basis and OASIS DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
翻訳版の取扱いは原文の取扱いに準じるものとする。訳者の名前及びこのcopyright宣言を取り除かないこと。
本稿は、OASIS RELAX NG作業部会によって、その公開が承認された文書である。本稿に関するコメントはrelax-ng-comment@lists.oasis-open.orgに送ってほしい。
電子メールのアドレス帳を表現した簡単なXML文書を考えてみよう:
<addressBook> <card> <name>John Smith</name> <email>js@example.com</email> </card> <card> <name>Fred Bloggs</name> <email>fb@example.net</email> </card> </addressBook>
これに対応するDTDはこのようになる:
<!DOCTYPE addressBook [ <!ELEMENT addressBook (card*)> <!ELEMENT card (name, email)> <!ELEMENT name (#PCDATA)> <!ELEMENT email (#PCDATA)> ]>
これに対応するRELAX NGパターンは次のように書くことができる:
<element name="addressBook" xmlns="http://relaxng.org/ns/structure/0.9"> <zeroOrMore> <element name="card"> <element name="name"> <text/> </element> <element name="email"> <text/> </element> </element> </zeroOrMore> </element>
もしaddressBookが必ずcardを持つようにしたければ、zeroOrMoreの代わりにoneOrMoreを使う:
<element name="addressBook" xmlns="http://relaxng.org/ns/structure/0.9"> <oneOrMore> <element name="card"> <element name="name"> <text/> </element> <element name="email"> <text/> </element> </element> </oneOrMore> </element>
それぞれのcardが、省略可能なnote要素を持つように書き換えてみるとどうなるだろうか。
<element name="addressBook" xmlns="http://relaxng.org/ns/structure/0.9"> <zeroOrMore> <element name="card"> <element name="name"> <text/> </element> <element name="email"> <text/> </element> <optional> <element name="note"> <text/> </element> </optional> </element> </zeroOrMore> </element>
textパターンは、全くの空文字列も含む任意の文字列にマッチする。また、タグとタグの間にある空白文字も、パターンと照合される際には無視される。
RELAX NGパターンを記述するための要素は、全て必ず次の名前空間に属していなくてはならない。
http://relaxng.org/ns/structure/0.9
今までの例はディフォルト名前空間 xmlns="http://relaxng.org/ns/structure/0.9"を用いているが、勿論プレフィックスを使った次のような書き方も可能である。
<rng:element name="addressBook" xmlns:rng="http://relaxng.org/ns/structure/0.9"> <rng:zeroOrMore> <rng:element name="card"> <rng:element name="name"> <rng:text/> </rng:element> <rng:element name="email"> <rng:text/> </rng:element> </rng:element> </rng:zeroOrMore> </rng:element>
これ以降本稿に登場する全てのサンプルは、便宜上ディフォルト名前空間にRELAX NG名前空間を指定しているものとし、一々名前空間を宣言することはしない。
nameをgivenNameとfamilyNameとに分けて書いてもよいようにパターンを拡張してみよう。すなわち、次のような文書を書けるように拡張するということだ。
<addressBook> <card> <givenName>John</givenName> <familyName>Smith</familyName> <email>js@example.com</email> </card> <card> <name>Fred Bloggs</name> <email>fb@example.net</email> </card> </addressBook>
次のように書くことでこれを表現できる:
<element name="addressBook"> <zeroOrMore> <element name="card"> <choice> <element name="name"> <text/> </element> <group> <element name="givenName"> <text/> </element> <element name="familyName"> <text/> </element> </group> </choice> <element name="email"> <text/> </element> <optional> <element name="note"> <text/> </element> </optional> </element> </zeroOrMore> </element>
同じものをDTDで書くと次のようになる。
<!DOCTYPE addressBook [ <!ELEMENT addressBook (card*)> <!ELEMENT card ((name | (givenName, familyName)), email, note?)> <!ELEMENT name (#PCDATA)> <!ELEMENT email (#PCDATA)> <!ELEMENT givenName (#PCDATA)> <!ELEMENT familyName (#PCDATA)> <!ELEMENT note (#PCDATA)> ]>
card要素が、子要素ではなくて属性を持つようにするにはどうすればよいだろうか。DTDで書くと次のようになる。
<!DOCTYPE addressBook [ <!ELEMENT addressBook (card*)> <!ELEMENT card EMPTY> <!ATTLIST card name CDATA #REQUIRED email CDATA #REQUIRED> ]>
RELAX NGでは、elementパターンをattributeパターンに変えるだけでよい。
<element name="addressBook"> <zeroOrMore> <element name="card"> <attribute name="name"> <text/> </attribute> <attribute name="email"> <text/> </attribute> </element> </zeroOrMore> </element>
XMLでは、属性同士の順序は気にしないのが伝統である。RELAX NGもこの伝統に習い、属性は順序なしであるとして取り扱う。従って、上のパターンは次のどちらにもマッチする。
<card name="John Smith" email="js@example.com"/>
<card email="js@example.com" name="John Smith"/>
対照的に、要素同士の順番は大事なので、次のパターンは
<element name="card"> <element name="name"> <text/> </element> <element name="email"> <text/> </element> </element>
次の文書にマッチしない
<card><email>js@example.com</email><name>John Smith</name></card>
裸のelement要素が必須要素を表すように、attribute要素それ自身は、必須の属性を示す。省略可能な属性を表現するには、これまたelementの時と同じようにoptionalを使う。
<element name="addressBook"> <zeroOrMore> <element name="card"> <attribute name="name"> <text/> </attribute> <attribute name="email"> <text/> </attribute> <optional> <attribute name="note"> <text/> </attribute> </optional> </element> </zeroOrMore> </element>
groupパターンとchoiceパターンは、elementパターンに対してもattributeパターンに対しても全く同じように利用できる。例えば、name属性を書くか、givenName属性とfamilyName属性を書くか、どちらかにしたい場合には、ちょうどelementパターンを使っていた時と同じように、次のように書く。
<element name="addressBook"> <zeroOrMore> <element name="card"> <choice> <attribute name="name"> <text/> </attribute> <group> <attribute name="givenName"> <text/> </attribute> <attribute name="familyName"> <text/> </attribute> </group> </choice> <attribute name="email"> <text/> </attribute> </element> </zeroOrMore> </element>
groupやchoiceを使って、elementやattributeを混ぜて書くことも、やはり同じようにできて、制限はない。例えば、次のパターンを使うと、card要素のnameとemailをそれぞれ要素としてでも属性としてでも書けるようになる。
<element name="addressBook"> <zeroOrMore> <element name="card"> <choice> <element name="name"> <text/> </element> <attribute name="name"> <text/> </attribute> </choice> <choice> <element name="email"> <text/> </element> <attribute name="email"> <text/> </attribute> </choice> </element> </zeroOrMore> </element>
繰り返しになるが、要素の間の順序は重要だが、属性の間の順序は考慮されない。従って、上のパターンは次の全てにマッチする。
<card name="John Smith" email="js@example.com"/> <card email="js@example.com" name="John Smith"/> <card email="js@example.com"><name>John Smith</name></card> <card name="John Smith"><email>js@example.com</email></card> <card><name>John Smith</name><email>js@example.com</email></card>
しかし、次のものとはマッチしない。
<card><email>js@example.com</email><name>John Smith</name></card>
なぜなら、このパターンの書き方は、email要素がnameの後に来る必要があるからである。
但し、attributeとelementには1つ違いがある。すなわち、もしattributeに子パターンが指定されなければ、<text/>とみなされるのに対して、elementではそのような省略が許されていない。例えば
<attribute name="email"/>
と
<attribute name="email"> <text/> </attribute>
は全く同じものを表現している。
これについて、
<element name="x"/>
が、子要素も属性もない空のx要素にマッチするのが自然と考える向きもあるかもしれない。しかしそのようにしてしまうと、「空」の意味に、要素と属性とで一貫性がなくなってしまう。このため、RELAX NGでは、elementの中身を省略することを禁止するようにしてある。子要素も属性も持たないような要素を記述するためには、<empty>を明示的に使う必要がある。
<element name="addressBook"> <zeroOrMore> <element name="card"> <element name="name"> <text/> </element> <element name="email"> <text/> </element> <optional> <element name="prefersHTML"> <empty/> </element> </optional> </element> </zeroOrMore> </element>
element属性は持つが子要素は持たないような要素のパターンを記述する場合には、<empty/>を使う必要はない。例えば、
<element name="card"> <attribute name="email"> <text/> </attribute> </element>
このパターンは次のパターンと一緒である
<element name="card"> <attribute name="email"> <text/> </attribute> <empty/> </element>
ほとんどのRELAX NGパターンでは、パターンに名前を付けて部品として使うと便利なことが多い。次のように書く代わりに、
<element name="addressBook"> <zeroOrMore> <element name="card"> <element name="name"> <text/> </element> <element name="email"> <text/> </element> </element> </zeroOrMore> </element>
次のように書くことができる
<grammar> <start> <element name="addressBook"> <zeroOrMore> <element name="card"> <ref name="cardContent"/> </element> </zeroOrMore> </element> </start> <define name="cardContent"> <element name="name"> <text/> </element> <element name="email"> <text/> </element> </define> </grammar>
grammar要素はstart要素を1つだけ子供に持ち、また複数のdefine要素を子供に持つことができる。startとdefineはどれもパターンを子供に持ち、またそれらのパターンはref要素を含むことができる。このrefは、defineで定義されたパターンをどれでも参照することができる。grammarパターンとXML文書の照合は、startに含まれるパターンと照合することによって行われる。
grammar要素を使うと、パターンをDTDと同じように記述することもできる。
<grammar> <start> <ref name="AddressBook"/> </start> <define name="AddressBook"> <element name="addressBook"> <zeroOrMore> <ref name="Card"/> </zeroOrMore> </element> </define> <define name="Card"> <element name="card"> <ref name="Name"/> <ref name="Email"/> </element> </define> <define name="Name"> <element name="name"> <text/> </element> </define> <define name="Email"> <element name="email"> <text/> </element> </define> </grammar>
再帰的な参照もできる。例えば、次のようなパターンを書ける。
<define name="inline"> <zeroOrMore> <choice> <text/> <element name="bold"> <ref name="inline"/> </element> <element name="italic"> <ref name="inline"/> </element> <element name="span"> <optional> <attribute name="style"/> </optional> <ref name="inline"/> </element> </choice> </zeroOrMore> </define>
ただし、再帰的な参照は、elementをその循環の環に含んでいなければならない。従って、次のようなパターンは許されない。
<define name="inline"> <choice> <text/> <element name="bold"> <ref name="inline"/> </element> <element name="italic"> <ref name="inline"/> </element> <element name="span"> <optional> <attribute name="style"/> </optional> <ref name="inline"/> </element> </choice> <optional> <ref name="inline"/> </optional> </define>
RELAX NGでは、外部で定義されたデータ型(例えばXML Schema Part 2など)を使ったパターンを書くことができる。RELAX NGの処理系によって使えるデータ型は様々であるので、利用可能な処理系と相談しつつ、使うデータ型を決めることになる。
dataパターンは、指定のデータ型に属する文字列にマッチするパターンである。 まず、datatypeLibrary属性を使って、データ型ライブラリのURIを指定する。 例えば、W3C XML Schema Part 2をデータ型ライブラリとして使うには、URIとしてhttp://www.w3.org/2001/XMLSchema-datatypesを使う。 次に、type属性で、datatypeLibraryで指定したライブラリ内のデータ型を指定する。 例えば、もし処理系がW3C XML Schema Part 2をサポートしていれば、次のようなパターンを書ける。
<element name="number"> <data type="integer" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"/> </element>
datatypeLibrary属性を全てのdata要素に一々指定するのは面倒なので、RELAX NGではdatatypeLibrary属性が「継承」されるようになっている。すなわち、datatypeLibraryは任意の要素で指定することが出来、data要素がdatatypeLibrary属性を持たない場合、datatypeLibrary属性を持つ一番近い要素の値が使われる。例えば、よく行われるのは、datatypeLibrary属性をRELAX NGパターンの先頭に指定しておくというやり方である。例えば、
<element name="point" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"> <element name="x"> <data type="double"/> </element> <element name="y"> <data type="double"/> </element> </element>
要素や属性の内容がdataパターンにマッチする場合には、その内容全体がそのdataパターンに一致する必要がある。内容の一部分がdataパターンにマッチして、残りの部分は別なパターンにマッチするようなパターンを書くことはできない。例えば、次のようなパターンを書くことはできない。
<element name="bad"> <data type="int"/> <element name="note"> <text/> </element> </element>
但し次のようなものはよい。
<element name="ok"> <data type="int"/> <attribute name="note"> <text/> </attribute> </element>
なお、この制限はtextパターンには当てはまらない。
また、データ型は「パラメータ」を何個か伴ってもよい。例えば、文字列型(string type)に、文字列の長さを制限するようなパラメータをつける、といった具合である。利用可能なパラメータはデータ型によって異なり、データ型ライブラリの定めるところとする。パラメータを指定するにはparam要素をdata要素の子として配置する。例えば、次のパターンではemail要素の中身を最大127文字までに制限する。
<element name="email"> <data type="string"> <param name="maxLength">127</param> </data> </element>
スキーマ言語において頻繁に出てくるものとして、予め定められた値から1つの値を決められるような属性がある。valueパターンは、指定された値に一致する文字列とマッチするようなパターンである。例えば、
<element name="card"> <attribute name="name"/> <attribute name="email"/> <attribute name="preferredFormat"> <choice> <value>html</value> <value>text</value> </choice> </attribute> </element>
上の例ではpreferredFormat属性はhtmlもしくはtextの値をとることができる。これは、以下のDTDに相当する。
<!DOCTYPE card [ <!ELEMENT card EMPTY> <!ATTLIST card name CDATA #REQUIRED email CDATA #REQUIRED preferredFormat (html|text) #REQUIRED> ]>
valueパターンは属性以外のところでも利用できる。例えば、次のようなパターンを書ける。
<element name="card"> <element name="name"> <text/> </element> <element name="email"> <text/> </element> <element name="preferredFormat"> <choice> <value>html</value> <value>text</value> </choice> </element> </element>
dataパターンに対する「要素・属性の全体が1つのdataパターンと一致する必要がある」という制約は、valueパターンに対しても同じように当てはまる。
また、ディフォルトでは、valueパターンは、文書に登場した文字列とスキーマに登場した文字列を比べて、空白補正(whitespace normalization)を行った結果が一緒であればマッチと判定する。空白補正では、先頭と末尾の空白が切り捨てられ、また空白文字の連続が1つのスペースに置き換えられる。この挙動は、ちょうどXML ParserがCDATA以外の属性を処理するやり方と同じである。従って、上述のパターンは次の全てと一致する。
<card name="John Smith" email="js@example.com" preferredFormat="html"/> <card name="John Smith" email="js@example.com" preferredFormat=" html "/>
valueパターンがスキーマで指定された値と文書で指定された値を比べる方法は、type属性とdatatypeLibrary属性(省略可能)を指定することで変更できる。これらの属性は、dataパターンの時と同じように働く。型が指定された場合、2つの値がデータ型によって同じ値だと見なされればマッチとなる。
もしdatatypeLibrary属性を持つような要素が先祖に存在していない場合、RELAX NG組み込みのデータ型ライブラリが使われる。このライブラリはstringとtokenの2つのデータ型を持つ。tokenは、valueパターンのディフォルトの比較方法に一致しており、string型は空白補正なしで文字列を比較する(XML Parserが自動的に行う、改行や属性の正規化(normalization)の効果を除いて)。例えば、
<element name="card"> <attribute name="name"/> <attribute name="email"/> <attribute name="preferredFormat"> <choice> <value type="string">html</value> <value type="string">text</value> </choice> </attribute> </element>
上のパターンは次のものとはマッチしない
<card name="John Smith" email="js@example.com" preferredFormat=" html "/>
listパターンは、空白で区切られたトークン列にマッチするパターンである。すなわち、listパターンは、トークン列がマッチすべきパターンを子として持つ。listパターンはまず文字列をトークン列に分解し、その列をlistの子パターンとマッチするかを検証する。
例として、次のように浮動小数点数を2つ持つvector要素を考えてみよう。これは、listを使って次のように書ける。
<element name="vector"> <list> <data type="float"/> <data type="float"/> </list> </element>
あるいは、もしvector要素が1個以上何個でも浮動小数点数を含んでよいのなら、次のように書ける。
<element name="vector"> <list> <oneOrMore> <data type="double"/> </oneOrMore> </list> </element>
あるいは、もし偶数個の数を含むようなpathパターンが欲しければ、次のように書く。
<element name="path"> <list> <oneOrMore> <data type="double"/> <data type="double"/> </oneOrMore> </list> </element>
interleaveパターンを使うと、子要素の出現順序を無視することができる。例えば、次のパターンでは、card要素の中にname要素とemail要素を書けるが、その順序は問わない。
<element name="addressBook"> <zeroOrMore> <element name="card"> <interleave> <element name="name"> <text/> </element> <element name="email"> <text/> </element> </interleave> </element> </zeroOrMore> </element>
このパターンをinterleaveと名付けた由縁は、2つ以上の子要素にマッチするようなパターンを使った時の動作から来ている[訳注:トランプを2つに分けて左右からパラパラと混ぜ合わせるようなイメージ(だと思う)]。例えば、HTMLのhead要素についてスキーマを書こうとしているとする。headの中には必ず1つだけtitle要素が存在し、また省略可能なbase要素を書くことも出来、更に任意個のstyle/script/link/metaを書くことができる。今、これらの要素それぞれをdefineで定義してあるとすると、head要素の定義は次のようになる。
<define name="head"> <element name="head"> <interleave> <ref name="title"/> <optional> <ref name="base"/> </optional> <zeroOrMore> <ref name="style"/> </zeroOrMore> <zeroOrMore> <ref name="script"/> </zeroOrMore> <zeroOrMore> <ref name="link"/> </zeroOrMore> <zeroOrMore> <ref name="meta"/> </zeroOrMore> </interleave> </element> </define>
headがあって、中がmeta,title,metaの順になっていたとしよう。これは、上記のパターンにマッチする。なぜなら、{meta,title,meta}は{meta,metaと{title}の2つの列を挟み込んだものであって、更に{meta,meta}は
<zeroOrMore> <ref name="meta"/> </zeroOrMore>
のパターンに一致し、{title}は
<ref name="title"/>
のパターンに一致するからである[訳注:更に、interleaveに含まれているほかのパターンは全て省略可能なので]
つまり、interleaveパターンがマッチするのはどういう時かというと、interleaveの子パターンのそれぞれにマッチする列を適当な順番で挟み込んだ時である。なお、これはSGMLの&コネクタとは動作が異なるので注意して欲しい。SGMLでは、A* & BはA A BやB A AにはマッチするけれどもA B Aにはマッチしない。
よく使われるinterleaveの利用法に、あるパターンpを<text/>とインターリーブするというのがある。このようなパターンは、pで指定されるパターンにマッチする上に子としてテキストが出現するのを許す。[訳注:よく「混在内容モデル」と呼ばれる]。 mixedパターンは、これを省略した書き方である。すなわち、
<mixed> p </mixed>
と書くのは次のように書くのと同じことである。
<interleave> <text/> p </interleave>
externalRefパターンを使うと、他のファイルで定義されたパターンを参照することができる。このパターンにはhref属性が必須で、この属性を使ってそのファイルのURLを指定する。 指定されたURLに書かれているパターンがマッチした時に、externalRefパターン自体がマッチする。 例えば、HTMLのインライン要素にマッチするようなRELAX NGパターンが次のようにinline.rngというファイルに書かれているとしよう。
<grammar> <start> <ref name="inline"/> </start> <define name="inline"> <zeroOrMore> <choice> <text/> <element name="code"> <ref name="inline"/> </element> <element name="em"> <ref name="inline"/> </element> <!-- etc --> </choice> </zeroOrMore> </define> </grammar>
このファイルを使って、「note要素がインラインHTMLを含む」というように書くことができる。
<element name="addressBook"> <zeroOrMore> <element name="card"> <element name="name"> <text/> </element> <element name="email"> <text/> </element> <optional> <element name="note"> <externalRef href="inline.rng"/> </element> </optional> </element> </zeroOrMore> </element>
別な例を考えよう。pattern1.rngとpattern2.rngというファイルに2つのRELAX NGパターンが書かれているとする。次のように書くことで、「どちらかにマッチすればよい」というパターンが書ける。
<choice> <externalRef href="pattern1.rng"/> <externalRef href="pattern2.rng"/> </choice>
文法が同じ名前で定義された複数のパターンを含む場合には、重複している定義がどのように結合して1つにするか、という情報をcombine属性を使って指定する必要がある。combine属性に指定できるのは、choiceかinterleaveのどちらかである。例えば、次のように書くと
<define name="inline.class" combine="choice"> <element name="bold"> <ref name="inline"/> </element> </define> <define name="inline.class" combine="choice"> <element name="italic"> <ref name="inline"/> </element> </define>
次のパターンのように解釈される
<define name="inline.class"> <choice> <element name="bold"> <ref name="inline"/> </element> <element name="italic"> <ref name="inline"/> </element> </choice> </define>
一方、属性を加えるときには、combine="interleave"が良く使われる。例えば、
<grammar> <start> <element name="addressBook"> <zeroOrMore> <element name="card"> <ref name="card.attlist"/> </element> </zeroOrMore> </element> </start> <define name="card.attlist" combine="interleave"> <attribute name="name"> <text/> </attribute> </define> <define name="card.attlist" combine="interleave"> <attribute name="email"> <text/> </attribute> </define> </grammar>
上のパターンは下のパターンと等しい
<grammar> <start> <element name="addressBook"> <zeroOrMore> <element name="card"> <ref name="card.attlist"/> </element> </zeroOrMore> </element> </start> <define name="card.attlist"> <interleave> <attribute name="name"> <text/> </attribute> <attribute name="email"> <text/> </attribute> </interleave> </define> </grammar>
更にこれは次のパターンとも等しい
<grammar> <start> <element name="addressBook"> <zeroOrMore> <element name="card"> <ref name="card.attlist"/> </element> </zeroOrMore> </element> </start> <define name="card.attlist"> <group> <attribute name="name"> <text/> </attribute> <attribute name="email"> <text/> </attribute> </group> </define> </grammar>
なぜなら[訳注:属性は順序がないので]属性をinterleaveで結合するのは、groupで結合するのと全く一緒だからである。
また、同名の2つのパターンが2つともcombineを指定しないのはエラーである。これらの規則によって、文法内でのパターンの宣言の順番を気にしなくてもよいことに注意してほしい。
複数のstart要素も、複数のdefine要素と同じようにしてcombineで結合できる。
include要素を使うと、複数の文法を1つにまとめることができる。include要素はgrammarパターンの中に幾つでも書くことが出来る。include要素にはhref属性が必須で、これを使ってgrammarパターンを持つ別なファイルのURLを指定する。これによって、参照されたgrammarパターンの中の宣言は全て、参照しているgrammarパターンの中にインクルードされる形になる。
combine属性は、include要素と絡むと大変便利になる。例えば、inline.rngというファイルがインライン要素に関するパターンを定義しているとしよう。ここでは、boldとitalicという要素が任意に書けるとする。
<grammar> <define name="inline"> <zeroOrMore> <ref name="inline.class"/> </zeroOrMore> </define> <define name="inline.class"> <choice> <text/> <element name="bold"> <ref name="inline"/> </element> <element name="italic"> <ref name="inline"/> </element> </choice> </define> </grammar>
別なRELAX NGパターンを書いてcodeとemをこれに加えるには、次のように書けばよい。
<grammar> <include href="inline.rng"/> <start> <element name="doc"> <zeroOrMore> <element name="p"> <ref name="inline"/> </element> </zeroOrMore> </element> </start> <define name="inline.class" combine="choice"> <choice> <element name="code"> <ref name="inline"> </element> <element name="em"> <ref name="inline"> </element> </choice> </define> </grammar>
これは、次のパターンと等しい。
<grammar> <define name="inline"> <zeroOrMore> <ref name="inline.class"/> </zeroOrMore> </define> <define name="inline.class"> <choice> <text/> <element name="bold"> <ref name="inline"/> </element> <element name="italic"> <ref name="inline"/> </element> </choice> </define> <start> <element name="doc"> <zeroOrMore> <element name="p"> <ref name="inline"/> </element> </zeroOrMore> </element> </start> <define name="inline.class" combine="choice"> <choice> <element name="code"> <ref name="inline"> </element> <element name="em"> <ref name="inline"> </element> </choice> </define> </grammar>
さらにcombine属性が結合されるので、次のパターンと等しい。
<grammar> <define name="inline"> <zeroOrMore> <ref name="inline.class"/> </zeroOrMore> </define> <define name="inline.class"> <choice> <text/> <element name="bold"> <ref name="inline"/> </element> <element name="italic"> <ref name="inline"/> </element> <element name="code"> <ref name="inline"> </element> <element name="em"> <ref name="inline"> </element> </choice> </define> <start> <element name="doc"> <zeroOrMore> <element name="p"> <ref name="inline"/> </element> </zeroOrMore> </element> </start> </grammar>
同名のパターンが複数ある場合でも、その中の1つはcombine属性を省略できることに注意してほしい。2つ以上のパターンが省略しているとエラーである。
notAllowedパターンは、文法をマージする時に便利である。notAllowedパターン自身は、いかなるものにもマッチしない。ちょうどemptyをgroupの中に加えると何も起こらないように、notAllowedをchoiceの中に加えても全く何も変化がない。この性質を利用して、notAllowedパターンは、別な文法がcombine="choice"を使って選択肢を追加できるようにするのに使うことができる。例えば、inline.rngを次のように書けば、
<grammar> <define name="inline"> <zeroOrMore> <choice> <text/> <element name="bold"> <ref name="inline"/> </element> <element name="italic"> <ref name="inline"/> </element> <ref name="inline.extra"/> </choice> </zeroOrMore> </define> <define name="inline.extra"> <notAllowed/> </define> </grammar>
codeとem要素をインライン要素として書けるようにする拡張は、次のようにするとできる。
<grammar> <include href="inline.rng"/> <start> <element name="doc"> <zeroOrMore> <element name="p"> <ref name="inline"/> </element> </zeroOrMore> </element> </start> <define name="inline.extra" combine="choice"> <choice> <element name="code"> <ref name="inline"> </element> <element name="em"> <ref name="inline"> </element> </choice> </define> </grammar>
RELAX NGではincludeの中にもdefine要素を書くことができる。このようにincludeの中に書かれたdefineは、インクルードされたgrammarの中に含まれる同名のパターンを置き換えているという風に解釈される。
例えば、addressBook.rngが次のように書かれていたとする。
<grammar> <start> <element name="addressBook"> <zeroOrMore> <element name="card"> <ref name="cardContent"/> </element> </zeroOrMore> </element> </start> <define name="cardContent"> <element name="name"> <text/> </element> <element name="email"> <text/> </element> </define> </grammar>
このパターンをいじって、card要素がemail要素ではなくemailAddress要素を含むようにしてみよう。このためには、cardContentの定義を次のように上書きすればよい。
<grammar> <include href="addressBook.rng"> <define name="cardContent"> <element name="name"> <text/> </element> <element name="emailAddress"> <text/> </element> </define> </include> </grammar>
includeは次のように展開される。
<grammar> <start> <element name="addressBook"> <zeroOrMore> <element name="card"> <ref name="cardContent"/> </element> </zeroOrMore> </element> </start> <define name="cardContent"> <element name="name"> <text/> </element> <element name="emailAddress"> <text/> </element> </define> </grammar>
include要素の中にstart要素を書けば、インクルードされた文法に含まれているstart要素を上書きすることもできる。
RELAX NGはXML名前空間に対応しており、要素名や属性名は全てローカル名と名前空間URIの2つから成っているとみなされる。
elementパターンは、ns属性を使って、その要素の名前空間URIを指定することができる。例えば、次のパターンは
<element name="foo" ns="http://www.example.com"> <empty/> </element>
次の全てとマッチする。
<foo xmlns="http://www.example.com"/> <e:foo xmlns:e="http://www.example.com"/> <example:foo xmlns:example="http://www.example.com"/>
しかし、次のものはどれもマッチしない。
<foo/> <e:foo xmlns:e="http://WWW.EXAMPLE.COM"/> <example:foo xmlns:example="http://www.example.net"/>
ns属性に空文字列("")を書くと、それは空のURIかURIが存在しないことを示す。この挙動は、xmlns属性のそれと全く一緒である。従って、次のように書くと
<element name="foo" ns=""> <empty/> </element>
次の全てとマッチするが、
<foo xmlns=""/> <foo/>
次のものは全てダメである。
<foo xmlns="http://www.example.com"/> <e:foo xmlns:e="http://www.example.com"/>
毎回全てのelementパターンにns属性を指定していたらきりがないので、RELAX NGではns属性は省略可能である。もしelementパターンがns属性を持たない場合、親を順に見ていって一番近くのns属性から値を受け継ぐ。もしどの親にもns属性がない場合、""が指定されたものとされる。つまり、
<element name="addressBook"> <zeroOrMore> <element name="card"> <element name="name"> <text/> </element> <element name="email"> <text/> </element> </element> </zeroOrMore> </element>
の省略されたns属性を全て展開すると
<element name="addressBook" ns=""> <zeroOrMore> <element name="card" ns=""> <element name="name" ns=""> <text/> </element> <element name="email" ns=""> <text/> </element> </element> </zeroOrMore> </element>
となる。また、
<element name="addressBook" ns="http://www.example.com"> <zeroOrMore> <element name="card"> <element name="name"> <text/> </element> <element name="email"> <text/> </element> </element> </zeroOrMore> </element>
は次と等しくなる。
<element name="addressBook" ns="http://www.example.com"> <zeroOrMore> <element name="card" ns="http://www.example.com"> <element name="name" ns="http://www.example.com"> <text/> </element> <element name="email" ns="http://www.example.com"> <text/> </element> </element> </zeroOrMore> </element>
attributeパターンにもns属性が指定できるが、省略された場合の動作が異なる。これは、XML名前空間の勧告の仕様で、ディフォルト名前空間が属性には適用されないところに由来する。attributeパターンのns属性が省略された場合には、""が指定されたと見なされる。従って、次のようなパターンを考えると、
<element name="addressBook" ns="http://www.example.com"> <zeroOrMore> <element name="card"> <attribute name="name"/> <attribute name="email"/> </element> </zeroOrMore> </element>
ns属性は次のように指定されたのと同じである。
<element name="addressBook" ns="http://www.example.com"> <zeroOrMore> <element name="card" ns="http://www.example.com"> <attribute name="name" ns=""/> <attribute name="email" ns=""/> </element> </zeroOrMore> </element>
なので、次のものにはマッチするが
<addressBook xmlns="http://www.example.com"> <card name="John Smith" email="js@example.com"/> <example:addressBook xmlns:example="http://www.example.com"> <example:card name="John Smith" email="js@example.com"/> </example:addressBook>
次のものにはマッチしない。
<example:addressBook xmlns:example="http://www.example.com"> <example:card example:name="John Smith" example:email="js@example.com"/> </example:addressBook>
複数の名前空間を一度に使うようなパターンを書くときには、ns属性を使った書き方では、同じ名前空間URIをあちこちで何度も書く羽目になって面倒くさい。このために、RELAX NGではelementやattributeパターンのname属性の中にプレフィックスを指定することで名前空間URIを指定することができるようになっている。この場合、elementやattributeパターンの位置で有効な名前空間宣言を使ってプレフィックスがURIに変換される。例えば、次のように書くと、
<element name="e:addressBook" xmlns:e="http://www.example.com"> <zeroOrMore> <element name="e:card"> <element name="e:name"> <text/> </element> <element name="e:email"> <text/> </element> </element> </zeroOrMore> </element>
次のように書いたのと等しい。
<element name="addressBook" ns="http://www.example.com"> <zeroOrMore> <element name="card" ns="http://www.example.com"> <element name="name" ns="http://www.example.com"> <text/> </element> <element name="email" ns="http://www.example.com"> <text/> </element> </element> </zeroOrMore> </element>
もしプレフィックスがname属性で指定されている場合には、ns属性の指定はあっても無視される。
注意してほしいのは、xmlns属性で指定されるディフォルト名前空間は、要素や属性の名前空間URIを決める際には使われない、という点である。
通常、elementパターンがマッチする要素の名前はname属性で指定される。しかし、実はelementパターンは、name属性を持つ代わりに名前クラスを指定する子要素を持つことができる。この場合、文書中の要素の名前が名前クラスに含まれていた場合に限ってelementパターンがその要素とマッチする。もっとも簡単な名前クラスはanyNameで、あらゆる名前を含む名前クラスである。例えば、次のパターンは任意のXMLドキュメントにマッチする。
<grammar> <start> <ref name="anyElement"/> </start> <define name="anyElement"> <element> <anyName/> <zeroOrMore> <choice> <attribute> <anyName/> </attribute> <text/> <ref name="anyElement"/> </choice> </zeroOrMore> </element> </define> </grammar>
nsName名前クラスは、ns属性で指定される名前空間URIに含まれる名前全てに一致する。ns属性は、elementパターンの時と同じように省略して値を上から継承することができる。
choice名前クラスを使うと、子として指定した名前クラスのどれかに一致すればよいようなものが書ける。
anyNameとnsNameは、except節を含むことができる。例えば、
<element name="card" ns="http://www.example.com"> <zeroOrMore> <attribute> <anyName> <except> <nsName/> <nsName ns=""/> </except> </anyName> </attribute> </zeroOrMore> <text/> </element>
上のパターンは、card要素が、card要素の名前空間以外に属している属性を幾つでも書けるようにしている。
なお、名前クラスが複数の名前に一致していても、1つのattributeパターンは1つの属性にしかマッチしないので注意が必要である。任意個の属性を許すなら、zeroOrMoreパターンを使う必要がある。
name名前クラスは、1つの名前だけからなる名前クラスを作る。ちょうどname属性で名前を指定するのと同じやり方で、name要素の内容でタグ名を指定する。ns属性もelementパターンの時と同じに使える。
スキーマ言語の中には、lax検証といって、要素や属性を定義がある場合にのみ検証して、定義がなければそれでいいという検証をサポートするものがある。RELAX NGで同じことをするには、except節とname名前クラスを使う。 例えば、他の名前空間からの属性を全て許すが、それがxml:spaceだった場合にはその中身がdefaultかpreserveのどちらかである事をチェックしたいとする。次のように書いてしまうと、うまくいかない。
<element name="example"> <zeroOrMore> <attribute> <anyName/> </attribute> </zeroOrMore> <optional> <attribute name="xml:space"> <choice> <value>default</value> <value>preserve</value> </choice> </attribute> </optional> </element>
なぜなら、xml:spaceの値がどんな値でも、以下の部分にマッチしてしまうからである。
<attribute> <anyName/> </attribute>
このため、次の部分がマッチしようがしまいが、結果に影響がなくなってしまう。
<attribute name="xml:space"> <choice> <value>default</value> <value>preserve</value> </choice> </attribute>
正しいやり方は、exceptとnameを使った次のような書き方である。
<element name="example"> <zeroOrMore> <attribute> <anyName> <except> <name>xml:space</name> </except> </anyName> </attribute> </zeroOrMore> <optional> <attribute name="xml:space"> <choice> <value>default</value> <value>preserve</value> </choice> </attribute> </optional> </element>
なお、define要素は名前クラスを持つことは出来ない。ただパターンを中に書けるのみである。
RELAX NGパターンの中には、他の名前空間の要素や属性を自由に書くことができ、そのような要素や属性は全て無視される。このように別な名前空間の要素や属性を書くことで、注釈やその他の情報を自由にRELAX NGに加えることができる。次の例を見てみよう。
<element name="addressBook" xmlns="http://relaxng.org/ns/structure/0.9" xmlns:a="http://www.example.com/annotation"> <zeroOrMore> <element name="card"> <a:documentation>1つの電子メールに関する情報</a:documentation> <element name="name"> <text/> </element> <element name="email"> <text/> </element> </element> </zeroOrMore> </element>
また、RELAX NGのdiv要素を使うと、複数の定義を一まとめに出来るので、そのまとまりに対して注釈などを書ける。例えば、grammarの中の定義をモジュールに分けるには、次のように書ける。
<grammar xmlns:m="http://www.example.com/module"> <div m:name="inline"> <define name="code"> pattern </define> <define name="em"> pattern </define> <define name="var"> pattern </define> </div> <div m:name="block"> <define name="p"> pattern </define> <define name="ul"> pattern </define> <define name="ol"> pattern </define> </div> </grammar>
このようにして書かれた文法から、[訳注:XSLTなどを使って]各種のサブセットなどを簡単に作り出せるようになる。
「RELAX NGのDTD互換のための仕様(RELAX NG DTD Compatibility Annotations)」[Annotation]を使うと、XML DTDの各種機能を真似るための幾つかの機能が利用可能できる。
grammarパターンをパターンの中で定義することによって、自由に入れ子を作ることができる。refパターンは、一番内側のgrammarを参照するが、parentRefパターンを使うと、一番内側の文法ではなくて、その親のgrammarを参照する。
例えば、表を表現するためのパターンを書いているとしよう。そのようなパターンは、表の構造だけを定義し、表のそれぞれの欄に何が入るかは気にしないようにしたい。このためには、まずtable.rngを次のように書く。
<grammar> <define name="cell.content"> <notAllowed/> </define> <start> <element name="table"> <oneOrMore> <element name="tr"> <oneOrMore> <element name="td"> <ref name="cell.content"/> </element> </oneOrMore> </element> </oneOrMore> </element> </start> </grammar>
table.rngをインクルードするパターンは、必ずcell.contentを上書きする必要がある。grammarの入れ子とparentRefを組み合わせると、cell.contentが親文法を参照するようなパターンが書ける。これは、親文法内のパターンを子文法の中で使っている、ということもできる。
<grammar> <start> <element name="doc"> <zeroOrMore> <choice> <element name="p"> <ref name="inline"/> </element> <grammar> <include href="table.rng"> <define name="cell.content"> <parentRef name="inline"/> </define> </include> </grammar> </choice> </zeroOrMore> </element> </start> <define name="inline"> <zeroOrMore> <choice> <text/> <element name="em"> <ref name="inline"/> </element> </choice> </zeroOrMore> </define> </grammar>
勿論、このように単純なケースでは文法を入れ子にするご利益はあまりない。単にtable.rngを直接インクルードしても構わないからである。しかし、インクルードされるtable.rngが複雑で様々な定義を含んでいるような場合には[訳注:またインクルードされるtable.rngがstartパターンを含んでいるような場合には]、文法を入れ子にすることによって、パターンの名前が衝突することを防止できる。
RELAX NGの場合、他のスキーマ言語とは異なり、非決定的(non-deterministic)な文法や曖昧(ambiguous)な文法を書くことができる。
例えば、電子メールのアドレス帳をHTMLで書き、class属性を使って構造を指定するしたいとしよう。
<element name="html"> <element name="head"> <element name="title"> <text/> </element> </element> <element name="body"> <element name="table"> <attribute name="class"> <value>addressBook</value> </attribute> <oneOrMore> <element name="tr"> <attribute name="class"> <value>card</value> </attribute> <element name="td"> <attribute name="class"> <value>name</value> </attribute> <interleave> <text/> <optional> <element name="span"> <attribute name="class"> <value>givenName</value> </attribute> <text/> </element> </optional> <optional> <element name="span"> <attribute name="class"> <value>familyName</value> </attribute> <text/> </element> </optional> </interleave> </element> <element name="td"> <attribute name="class"> <value>email</value> </attribute> <text/> </element> </element> </oneOrMore> </element> </element> </element>
このパターンは、例えば次のようなXMLにマッチする。
<html> <head> <title>Example Address Book</title> </head> <body> <table class="addressBook"> <tr class="card"> <td class="name"> <span class="givenName">John</span> <span class="familyName">Smith</span> </td> <td class="email">js@example.com</td> </tr> </table> </body> </html>
次のようなものはマッチしない。
<html> <head> <title>Example Address Book</title> </head> <body> <table class="addressBook"> <tr class="card"> <td class="name"> <span class="givenName">John</span> <!-- このclass属性の値がおかしい --> <span class="givenName">Smith</span> </td> <td class="email">js@example.com</td> </tr> </table> </body> </html>
RELAX NGの機能は、XML DTDのそれを凌駕する。特に挙げるならば
ID/IDREFの機能はRELAX NG自身にはいが、「RELAX NGのDTD互換のための仕様(RELAX NG DTD Compatibility Annotations)」[Annotation]によって同等の機能が提供される。 より先進的で包括的なクロスリファレンスのサポートは、今後新たな仕様によって行われる予定である。
XML DTDにはXML文書のインフォセット(infoset)を変更するような機能があるが、RELAX NGにはそれらは含まれない。例えば、
また、XML文書をRELAX NG文法と関連付けるための方法もこの仕様では定義されない。
RELAX Coreで書かれた文法は、何ら情報を失うことなく完全にRELAX NGに変換することが出来る[訳注:James Clarkがこの変換を行うXSLTを公開している]。
elementRuleと、それから参照されるtagは、defineの中にelementを持つような形に落とすことができる。例えば、次のelementRule-tagの組は、
<elementRule role="foo" label="bar"> 生垣モデル </elementRule>
<tag role="foo" name="baz"> 属性宣言 </tag>
以下のようなRELAX NGパターンと等しい。
<define name="bar"> <element name="baz"> 生垣モデル 属性宣言 </element> </define>
hedgeRule要素は、define要素にそのまま変換できる。例えば、このhedgeRuleが、
<hedgeRule label="bar"> 生垣モデル </hedgeRule>
RELAX NGでは次のようになる
<define name="bar"> 生垣モデル </define>
attPoolはそのままdefineに変換できる。次の例を参照。
<attPool role="foo"> 属性宣言 </attPool>
これと等価なものがRELAX NGではこう書ける。
<define name="foo"> 属性宣言 </define>
どちらもattributeという要素を使う点では一緒だが、RELAX Coreではrequired="true"と指定されない属性は全て省略可能な属性である。このような属性は、RELAX NGではoptionalパターンで囲む必要がある。
必須な属性の変換方法を次に示す。
<attribute name="foo" type="integer" required="true"/>
RELAX Coreで書かれた上の文法が、RELAX NGでは次のようになる。
<attribute name="foo"> <data type="integer"/> </attribute>
省略可能な属性の変換方法も示す。
<attribute name="foo" type="integer"/>
対応するRELAX NGは次のようになる。
<optional> <attribute name="foo"> <data type="integer"/> </attribute> </optional>
「HOW TO RELAX」のSTEP 7にある例をこれまで説明した方法でRELAX NGに変換したものを以下に示す。最初の段落は脚注を含むことが出来ないが、それ以降の段落では脚注を含むことができる、という例である。
<grammar> <start> <element name="doc"> <ref name="paraWithoutFNotes"/> <zeroOrMore> <ref name="paraWithFNotes"/> </zeroOrMore> </element> </start> <define name="paraWithoutFNotes"> <element name="para"> <text/> </element> </define> <define name="paraWithFNotes"> <element name="para"> <mixed> <zeroOrMore> <element name="fnote"> <text/> </element> </zeroOrMore> </mixed> </element> </define> </grammar>
次のXMLはこのパターンにマッチする。
<doc><para/><para><fnote/></para></doc>
次のXMLはこのパターンにマッチしない例である。
<doc><para><fnote/></para></doc>
同様に、「HOW TO RELAX」のSTEP 8の例をRELAX NGに変換した例を示す。このパターンは、div要素のclass属性の値に応じて、内容モデルが変化するという例である。
<grammar> <start> <element name="html"> <zeroOrMore> <ref name="section"/> </zeroOrMore> </element> </start> <define name="section"> <element name="div"> <attribute name="class"><value>section</value></attribute> <zeroOrMore> <element name="para"> <text/> </element> </zeroOrMore> <zeroOrMore> <ref name="subsection"/> </zeroOrMore> </element> </define> <define name="subsection"> <element name="div"> <attribute name="class"><value>subsection</value></attribute> <zeroOrMore> <element name="para"> <text/> </element> </zeroOrMore> </element> </define> </grammar>
マッチする文書の例として、次のものを挙げる。
<html> <div class="section"> <para/> <div class="subsection"> <para/> </div> </div> <div class="section"> <div class="subsection"> <para/> </div> </div> </html>
次のXMLはマッチしない例である。
<html> <div class="subsection"> <para/> <div class="section"> <para/> </div> </div> </html>
RELAX NGの機能のうち、いくつかはRELAX Coreでは表現できない。
TREXからRELAX NGに至る過程で、次のような修正を加えた。
James Clark, Makoto MURATA, editors. RELAX NG Specification. OASIS, 2001.
James Clark, Makoto MURATA, editors. RELAX NG DTD Compatibility Annotations. OASIS, 2001.
James Clark. TREX - Tree Regular Expressions for XML. Thai Open Source Software Center, 2001.
MURATA Makoto. RELAX (Regular Language description for XML). INSTAC (Information Technology Research and Standardization Center), 2001.