前回
XMLのお勉強その11 - XML Catalogs - 競プロ備忘録
読んでいく仕様書
原文:ISO/IEC 19757-2:2008 - Information technology — Document Schema Definition Language (DSDL) — Part 2: Regular-grammar-based validation — RELAX NG
和文:なし
今回はRELAX NGことISO/IEC 19757-2:2008 Part 2: Regular-grammar-based validation - RELAX NGです。
これが何かと言えば、XMLのスキーマ言語です。他にXMLのスキーマ言語といえば、DTD, XML Schema (XSD), Schematronなどがあります。
DTDはXML仕様に組み込まれたスキーマ言語ですが、DTDの苦しいところをマシにしようと色んなスキーマ言語が並び立ち、W3CではXSDが策定され、OASISではRELAX NGが生まれたと聞いています。
OASISの仕様としてのRELAX NGは2001年のものが最新のようですが、その後ISOで仕様策定が引き継がれ、現在最新のものが上にリンクを貼ったものです。
ISOの仕様はだいたい有料での閲覧になる気がするのですが、なぜかRELAX NGは無料でPDFが入手できます。なので、せっかくですから最新の規格を読むことにします。
和文について、ISO規格に対応したJIS X 4177-2:2005という規格があるらしいですが、対応するISO規格が1つ古いISO/IEC 19757-2:2003らしいです。ISO/IEC 19757-2:2008対応のJIS規格は見つけられませんでした。
規格が異なるので上に並べて書いてはいませんが、日本産業標準調査会のホームページからPDFが閲覧できますし、日本規格協会から冊子やPDFの購入もできます。参考にする程度あれば丁度いいかもしれません。
本編
Foreword
あんまり重要な話ではなさそうなので基本的に省略しますが、最後の段で、ISO/IEC 19757という規格がどんな構成になっているのかという話が書かれています。
RELAX NGはISO/IEC 19757という規格群の中のPart 2になり、全部でPart 9まであるらしいです。Part 3がSchematron、Part 4がNVDL、Part 8がDSRL、Part 9がNamespace and datatype in DTDsとなっており、Part 1, 5, 7はこの仕様が作成された時点では準備中というステータスだったようです。
NVDL, DSRLとかは全く聞き馴染みがないので、そのうち調べてみたいです。(ただし仕様書が手に入らない可能性はある)
Introduction
この章もあまり重要な話はありません。この仕様書の章構成について述べているだけの内容になります。
1 Scope
ここからが本当の本編になります。
この仕様が何を策定しているかというと、冒頭でも述べたとおりですが、RELAX NGというXMLのためのスキーマ言語です。
RELAX NG自体もXMLで記述され、文書構造のパターンを木構造で記述します。
また、この章では登場していませんが、XML形式ではない簡易的な記述形式(Compact Syntax)もあります。
この仕様では、RELAX NGへの要求事項が何かと、XML文書がどのようなときにRELAX NGスキーマにマッチするのかの2つを述べる仕様となっています。
2 Normative references
この仕様から参照される各種規格へのリンクです。W3Cの仕様書だとだいたい最後のAppendix的な箇所に書いてある内容ですが、ISOの仕様書では最初に書くんですね。
取り立てて述べる点はないですが、参照仕様が古い気がしますね…ちょっと心配です。
また、XLinkが参照仕様に含まれているのも気がかりです。
3 Terms and definitions
語彙の定義です。W3Cの仕様書だと2章あたりにいつもあるやつです。章番号が細かく振ってありますが、特に章分けせずにずらっと書いていきます。
- 「リソース」とは、識別子を持つものであって、URIによって参照可能なもの
- 「URI」とは、RFC 2396で定義された、抽象・物理問わずリソースを識別する簡潔な文字列
- ここでも古い規格が参照されていますが、今ならRFC 3986になると思います
- 「URI参照」とは、URIもしくは相対URIであり、任意にフラグメント識別子を持つもの
- 「相対URI」とは、ある基底URIを用いて別のURIに解決可能であるようなURI参照
- 「基底URI」とは、相対URIを解決するために用いるURI
- 「フラグメント識別子」とは、あるURIを用いた取得動作の後にユーザエージェントが用いる、URIの付加的な情報
- 「インスタンス」とは、あるRELAX NGスキーマによって検証されるXML文書
- 「スペース文字」とは、#x20のコードを持つ文字(つまりはASCIIのスペース文字)
- 「空白文字」とは、#x20, #x9, #xA, #xDのコードを持つ文字(つまりはXML仕様で定められた空白文字)
- 「名前」とは、URIと局所名の組み合わせ
- 「名前空間URI」とは、名前の一部分であるURI
- 「局所名」とは、名前の一部分であるNCName
- 「NCName」とは、名前空間仕様で定義されたNCNameにマッチする文字列
- 「名前クラス」とは、スキーマの一部であって、名前にマッチするもの
- 「パターン」とは、スキーマの一部であって、属性集合と、要素と文字列からなる列にマッチするもの
- 「外来属性」とは、名前空間URIが空文字列もしくはRELAX NG名前空間URIのいずれでもないような名前を持つ属性
- 「外来要素」とは、名前空間URIがRELAX NG名前空間URIではないような名前を持つ要素
- 「完全な構文」とは、単純化される前のRELAX NG文法の構文
- 「単純な構文」とは、単純化された後のRELAX NG文法の構文
- 「単純化」とは、完全な構文のRELAX NGスキーマを、単純な構文のスキーマに変換すること
- 「データ型ライブラリ」とは、局所名からデータ型へのマッピング
- 「データ型」とは、文字列の集合であって、同値関係をもつもの
- 「公理」とは、無条件に証明可能とみなす命題
- 「推論規則」とは、一つ以上の肯定的または否定的な前提条件と、必ず一つの結論とを持つ規則であって、すべての肯定的な前提条件が証明可能で、かつ、どの否定的な前提条件も証明可能でない場合、結論を証明可能であるとするもの
- 「スキーマに照らして妥当」とは、スキーマによって記述されるXML文書の集合に属すること
- 「スキーマ」とは、XML文書の集合の仕様
- 「文法」とは、NCNameからパターンへの対応付けを伴う開始パターン
- 「正しいスキーマ」とは、ISO/IEC 19757のこのパート(つまりRELAX NG仕様)の要求をすべて満たすスキーマ
- 「検証器」とは、スキーマが正しいか否か、インスタンスがスキーマに照らして妥当か否かを決定するソフトウェアモジュール
- 「パス」とは、NCNameの
/または//で区切られたリスト
- 「情報集合」とは、Infoset仕様で定義されたXML文書の抽象化
- 「情報項目」とは、情報集合の構成要素
- 「EBNF」とは、Extended BNFのことであり、文脈自由文法を記述するための記法
- 「弱いマッチング」とは、9.3.7章で詳細を記述されるマッチングの種類のこと
- 「スコープ内文法」とは、
grammer要素である最近祖先
- 「内容種別」とは、空 (empty)、複合 (complex)、単純 (simple)のいずれか一つの値
- 「混在列」とは、要素と文字列の双方を含むかもしれない列
英語から意味がわからなかったところはJIS規格書を参考にしました。
めちゃくちゃたくさんあって疲れました。公理とか推論規則とか、なんか小難しいことを言っていますが、どんな感じでこのあと出現するでしょうか。
4 Notation
仕様書中の文法や規則の記法の話です。
EBNFは嫌というほど見てきたので特に説明不要でしょうが、推論規則 (4.2.1)、前提条件 (4.2.2)、式 (4.2.3)あたりはあんまり見慣れない記法です。
この時点で細かく見ても何のこっちゃとなるのは目に見えているので、スキップします。遭遇したら見返すことにします。
5 Data Model
RELAX NGのデータモデルについてです。
RELAX NGは抽象データモデルを通じて、XML文書をスキーマ・インスタンスの双方として扱います。XML文書はスキーマ・インスタンスのいずれを表現するにしても、XML仕様・名前空間仕様に従って整形式でなければなりません。
XML文書は「要素」によって表現され、要素は、「名前」、「文脈」、「属性」の集合、0個以上の子の順序付き列からなります。
特に要素のそれぞれの子は、要素か非空文字列であり、非空文字列は列に連続して現れません。
「名前」は、名前空間URIを表す文字列と、局所名を表すNCNameからなります。
名前空間URIが空文字列であれば、名前はいかなる名前空間にも属さないとみなされます。
「文脈」は、基底URIと名前空間のマッピングからなります。
名前空間のマッピングは、接頭辞を名前空間URIに写すものです。デフォルト名前空間を保持することもあります。
「属性」は、名前、値の文字列表現からなります。
文字列は、0個以上の文字からなる列です。この「文字」とは、XML仕様で定められた文字です。
最後の2段はえらいゴチャゴチャと色々書いてありますが、InfosetとRELAX NGデータモデルの関係性についてです。
上記のそれぞれのデータは、Infosetの情報項目及びプロパティから、以下のように対応付けられます。
- 「要素」は、文書情報項目の
document elementプロパティから構成するか、要素情報項目のnamespace name, local nameの組を名前、base URI, in-scope namespacesの組を文脈、attributesを属性集合、childrenを子の順序付き列として構成される。
- 「属性」の集合は、属性情報項目の順序なし集合として構成される。
- 要素の子は、要素情報項目の
childrenプラパティを構成する情報項目について、要素情報項目か文字情報項目であるもの以外を削除し、連続する文字情報項目を可能な限り長く文字列として結合した列として構成される。
- 「属性」は、属性情報項目の
namespace name, local nameの組を名前とし、normalized valueと値として構成される。
- 要素や属性の「名前」は、要素情報項目や属性情報項目の
namespace nameを名前空間URI、local nameを局所名として構成される。namespace nameプロパティがなければ、local nameのみから構成される。
- 文字列は、文字情報項目の
character codeプロパティが示す文字の列として構成される。
DTDが読まれなかった(文書情報項目のall declarations processedプロパティが偽)、展開できなかった実体参照がある(未展開実体参照項目が含まれている)、などの場合、一つの文書に対するInfosetは一意ではありません。
RELAX NGデータモデルいくつかあり得るInfosetのうち、all declarations processedプロパティが真であり、未展開実体参照項目が含まれないようなInfosetを基礎として定義される、と述べられています。
6 Full syntax
「完全な構文」についての詳細の説明です。
この章では構文を示すEBNFが示されていますが、文字列としてマッチしなくてもデータモデル上等しくなるのであれば、マッチするとみなします。
仕様中の例で言えば、<text/>と<text></text>は字面は違えど、データモデルとしてはtextという名前を持ち、文脈、属性、子、のいずれも持たない要素であり、等しいものになります。<text/>はpatternという非終端記号にマッチするので、データモデル上等しい<text></text>もpatternにマッチするとみなすことができます。
また、EBNF上では要素名として接頭辞のない名前が記されていますが、RELAX NGスキーマを表現するXML文書の要素は、http://relaxng.org/ns/structure/1.0という名前空間名で修飾されます。
従って、もしルートレベルでxmlns:rng="http://relaxng.org/ns/structure/1.0"と宣言されたRELAX NGスキーマでは、<rng:text/>がpatternにマッチします。
QName, NCNameは名前空間仕様で定義されたシンボルです。
anyURIはXLink仕様の5.4章で説明された通りに許可されない値をエスケープした後の、RFC 2396準拠のURIである文字列を指します。
エスケープの方法について、XLink仕様5.4章の詳細は将来見るとしますが、ざっと読んだところでは、Catalog仕様でのURIの正規化と同じ手順であると考えれば良さそうです。つまり、非ASCIIおよびRFC 2396 2.4章に定められた除外文字(ただし%,#を除く)をパーセントエンコードするという手順です。
stringは任意の文字列です。
EBNFでは明示されていませんが、RELAX NG文書のあらゆる要素について、ns属性とdatatypeLibrary属性を持つことができます。
ns属性はあらゆる値でよく、datatypeLibrary属性は、anyURIのみを取ります。datatypeLibrary属性の値であるURIは、相対参照やフラグメント識別子をもつURIであってはなりませんが、空文字列ではあってもよいです。
さらに、外来属性、外来要素もEBNFで明示されていませんが、RELAX NG文書中に現れても構いません。
外来属性はあらゆる要素で指定して構いませんが、名前空間が空であったり、RELAX NG名前空間で修飾されてはなりません。
外来要素は、文字列である子を持つことのできないあらゆる要素の子として現れることができます。「文字列である子を持つことのできないあらゆる要素」は、具体的にはvalue, param, nameの3つを除くすべての要素であるとされます。外来要素の名前は、RELAX NG名前空間で修飾されてさえいなければ構いません。他の子との相対的な位置関係に制約はありません。(つまり何番目の子として現れてもよい)
文字列である子を持つことのできない要素も含めて、すべての要素は、空白文字を子として持っても構いません。
空白文字は、子の列のどの位置に現れても構いません。
name属性、type属性、combine属性及びname要素の値である文字列について、先頭と末尾には任意に空白文字を挿入しても構いません。
章の最後で完全な構文のEBNFが示されていますが、ただのコピペになるので割愛します。
RELAX NGには"Compact Syntax"と呼ばれる構文もありますが、これはAnnex Cで説明されるそうです。
7 Simplification
「単純化」の説明です。
要は「完全な構文」を「単純な構文」に変換するルールですが、これが7.22まであり、えらいたくさんルールがあります。心してかかりましょう…
7.1General
「完全な構文」は、このあと述べられる各種ルールを順番に適用することで、「単純な構文」に変換が可能です。
各種ルールは、次のルールに進む前に、スキーマ内のすべての要素に適用されたかのように振る舞います。また、ルールは正しいスキーマが満たすべき制約を示している場合もあります。
変換は、データモデルのレベルで行われるため、変換を行う前に、スキーマをデータモデルとして扱うことのできるようにしておく必要があります。
7.2 Annotations
外来属性、外来要素はすべて削除します。
注意点として、xml:baseも削除してよいことが述べられています。なぜなら、単純化を実施する前段階でデータモデルは構築済であり、データモデルに基底URIが保持されているからです。
相対参照の解決が必要だとしても、データモデルに保持された基底URIを使って解決できるので、もはやxml:baseは不要ということになります。
7.3 Whitespace
value要素、param要素を除き、すべての要素の子の空白文字のみを含む文字列を削除します。
name属性、type属性、combine属性及びname要素のそれぞれの値については、先頭・末尾の空白文字をすべて削除します。
7.4 datatypeLibrary attribute
datatypeLibrary属性の値について、XLink仕様5.4章の方法で、除外文字のエスケープを行います。
datatypeLibrary属性を持たないdata要素、value要素については、datatypeLibrary属性を持つ最近祖先のdatatypeLibrary属性の値を使って、datatypeLibrary属性を追加します。もしそのような祖先がなければ、空文字列を値としてもつdatatypeLibrary属性を追加します。
その後、data要素とvalue要素以外に指定されたdatatypeLibrary属性をすべて削除します。
dataとvalueに適用すべきデータ型ライブラリを確定するプロセスということになりますね。
このルールの結果、各data, valueに適用すべきデータ型ライブラリは、自身を含む最近祖先で指定されたデータ型ライブラリということになります。
7.5 type attribute of value element
type属性を持たないvalue要素について、tokenを値として持つtype属性を追加し、datatypeLibrary属性の値を空文字列に変更します。
解釈としては、valueのデフォルトの型としてはtokenを選択する、といったものになるでしょうか。
7.6 href attribute
externalRef要素及びinclude要素で指定されたhref属性の値であるURI参照について、XLink仕様5.4章の方法でエスケープを行い、また要素の文脈で指定された基底URIを使って解決します。
href属性の値は、5章で述べられた「要素」の構築に使われます。
どのようにするかというと、
href属性値であるURIを用いて、リソースとリソースに付与されるMIMEメディア型を取得する
- メディア型の情報を用いて、要素を構築する。特にメディア型が
application/xmlもしくはtext/xmlであるなら、5章の方法に従って構築する
といったものになります。「5章の方法に従って構築する」というのは、Infosetから要素を構築する方法について言っていると思われます。つまるところ、パースしたXML文書の文書情報項目から要素を取得する方法ということで、すなわち文書要素を5章で説明された「要素」として解釈するということです。
この仕様では、application/xml, text/xml以外の型に対する要素の構築方法は定義されません。
注意点として、href属性の値はフラグメント識別子を含んでいても良いのですが、リソースのメディア型が、リソースから要素を構築する方法についてフラグメント識別子の扱いを定めない場合は、フラグメント識別子を含んではなりません。
特に、application/xml, text/xmlはフラグメント識別子の扱いを定めないので、XML文書を参照するhrefの場合は、href属性値はフラグメント識別子を含んではなりません。
多くの場合XML文書を参照することになるのでしょうし、基本的にはフラグメント識別子を含めないのが無難ということになるのかもしれません。
7.7 externalRef element
externalRefは、この時点で7.6章のルールにしたがって、なんらかの「要素」を取り込んでいるはずです。externalRefはこの要素を用いて変換を行います。
まず、取り込んだ要素は、pattern非終端記号にマッチしなければなりません。マッチするなら、この章までの規則にしたがって、取り込んだ要素に変換を加えます。
変換する要素がexternalRefを含む場合、再帰的に取り込みと変換を行うわけですが、このときリソースのループが起こってはなりません。すなわち、同じhref属性値を参照するexternalRefが取り込んだリソースから構築した要素の中に複数回現れてはならないということになります。
次に、取り込んだ要素にns属性が含まれない場合、このexternalRef要素で指定されたns属性を、取り込んだ要素にコピーします。
最後に、取り込んだ要素をexternalRefと置き換えます。
モノとしては、DTDの外部パラメタ実体参照のようなものになるでしょうか?
なんでもかんでも取り込めるわけではない点は安心できるポイントかもしれないですが、世間的な評価はどうなんでしょうかね。
7.8 include element
include要素も、7.6章のルールに従って取り込んだ「要素」を用いて変換します。
externalRefと異なり、取り込んだ要素は6章で示されたgrammar要素にマッチする要素でなければなりません。
grammar要素もこれまでのルールにしたがって変換を行わなければなりませんが、include要素を含む場合、そのhref属性値は同じURI参照であってはならず、リソースのループを起こしてはなりません。
ここで、「コンポーネント」という用語が定義されています。ある要素の「コンポーネント」とは、その要素の子および要素の子であるdiv要素のコンポーネントです。
その「コンポーネント」という用語を用いてstart要素とdefine要素の扱いが定めされています。
まずinclude要素のコンポーネントであるstart要素について、取り込んだgrammar要素のコンポーネントにもstart要素が含まれていなければならず、grammar要素のコンポーネントであるstart要素は除去されます。
続いて、include要素のコンポーネントであるdefine要素について、取り込んだgrammar要素のコンポーネントにも同じ名前を持ったdefine要素が含まれていなければならず、grammar要素のコンポーネントである同じ名前のdefine要素は除去されます。
最後に、include要素はdiv要素に変換されます。
変換後のdiv要素は、include要素から、href以外のすべての属性を引き継ぎます。
また、div要素の子は、取り込んだgrammar要素に、include要素の子を続けたものです。
そして、grammarからdivに名前を変更することで、変換が完了します。
7.9 name attribute of element and attribute elements
element要素及びattribute要素で指定されたname属性、それらの子要素としてname要素に変換されます。
もしattribute要素がname属性と持っており、かつns属性を持たない場合は、name子要素にns=""とns属性を追加します。
要素の場合は、名前が修飾されないことは既定の名前空間を受け継ぐことと同義ですが、属性の場合はそうではありません。
しかし、7.10の規則を適用すると、attributeの子要素であるnameにも既定の名前空間が受け継がれてしまい、意味が変わってしまうので、この処理が必要になるわけですね。
7.10 ns attribute
name, nsName, valueの3要素について、ns属性を持たない場合はns属性を追加します。
追加されたns属性の値は、ns属性を持つ最近祖先の値を引き継ぎます。そのような祖先がなければ、値は空文字列になります。
その後、name, nsName, valueの3要素を除くすべての要素からns属性を除去します。
注意点として、ns属性の値はなんらエスケープなどの処理を施しません。名前空間仕様でもそのようになっているので当然といえば当然ですが、URIを期待される値であるため、一応注記がなされています。
また、datatypeLibraryとnsの引き継ぎについて、datatypeLibraryはinclude, externalRefによる外部スキーマの取り込み前に引き継ぎを行うため、外部スキーマには引き継がれない一方、nsは取り込み後に引き継ぎを行うため、外部スキーマにも引き継がれます。
7.11 QNames
name要素が接頭辞を含む場合、その接頭辞は除去され、既存のns属性があればそれを置き換える形で、ns属性が挿入されます。
挿入する値は、そのname要素の文脈における接頭辞から名前空間URIへのマッピングに従います。同時に、name要素の文脈は、その接頭辞へのマッピングを保持していなければならないということも示唆しています。
7.12 div element
div要素は、その子の列に置き換えられます。
div要素は単に子をグループとして束ねる以上の意味を持たない、ということを示しています。
7.13 Number of child elements
define, oneOrMore, zeroOrMore, optional, list, mixedの6要素は、2つ以上の子を持つ場合、groupで束ねることによって、それぞれたった1つの子要素だけを持つように変換されます。
element要素は、名前クラスとパターンの2つだけの子要素を持つように変換されます。もし3つ以上の子要素を持つ場合は、パターンをgroupで束ねることによって、2つの子要素だけを持つように変換されます。
except要素は、2つ以上の子要素を持つ場合、choice要素で束ねることによって、たった1つの子要素だけを持つように変換されます。
attribute要素が1つだけの子要素を持つ場合、text要素が追加されて、名前クラスとテキストの2つの要素を持つように変換されます。
choice, group, interleaveの3要素は、それぞれ2つの子要素だけを持つような形に変換されます。
まず、それぞれ子要素が1つしかない場合は、自身を削除してその子要素で置き換えます。これらは複数の子要素を束ねることに意味がある要素なのであって、1つしか子がなければ削除しても意味は変わらないということですね。
そして、子要素が3つ以上ある場合は、最初の2要素を自身と同じ名前の要素で括ることを繰り返し、直接の子要素が2つになるまで子要素数を減らします。ASTを二分木で構築するときのようなノリと似ています。属性については何も触れられていませんが、これらの要素は、この時点までの規則を正しく適用できている限りは属性を1つも持たないので、気にする必要がありません。(外来属性は7.2、ns, datatypeLibraryはそれぞれ7.10, 7.4で削除済であり、それ以外の属性もパターン上持たない)
7.14 mixed element
mixed要素をinterleave要素とtext要素の組に変換します。
<mixed> p </mixed>は、<interleave> p <text/> </interleave>に変換されます。
mixed要素は、意味的には何かしらのパターンとテキストの混合内容を表すという意味になります。DTDで言うところの#PCDATAを選択した要素型宣言のようなものになるでしょうか?(任意のパターンを含みうる分、RELAX NGのmixedの方が強力ではありますが)
7.15 optional element
optional要素をchoice要素とempty要素の組に変換します。
<optional> p </optional>は、<choice> p <empty/> </choice>に変換されます。
任意にあるパターンが現れても現れなくてもよいというパターンを表すので、あるパターンか空の内容のいずれかを選択する、としても同じ意味になります。
7.16 zeroOrMore element
zeroOrMore要素を2つの子を持つchoice要素に変換します。
<zeroOrMore> p </zeroOrMore>は、<choice><oneOrMore> p </oneOrMore> <empty/></choice>に変換されます。
p*という正規表現が(p+)?と同じ意味だと考えるとわかりやすいかもしれません?
7.17 Constraints
ここでは変換は行われず、制約がチェックされます。
注意点として、ここでのチェックはref要素の解決が不要であり、またこの後の規則の適用によって除去される要素にもチェックが及びます。
まず、anyName要素の子であるexcept要素について、子孫にanyName要素を含んではなりません。また、nsName要素の子要素であるexcept要素について、子孫にnsName, anyNameのいずれの要素も含んではなりません。
次に、attribute要素の最初の子かattribute要素の最初の子の子孫として現れ、かつ空文字列を値として持つns属性を持つようなname要素について、その内容がxmlnsであってはなりません。
次に、attribute要素の最初の子かattribute要素の最初の子の子孫として現れるname, nsName要素について、そのns属性がhttp://www.w3.org/2000/xmlnsを値として持っていてはなりません。
data, valueの2要素について、データ型として使用方法として正しくなければなりません。特に、type属性については、その値がdatatypeLibraryで指定されたデータ型のいずれかを指すものでなければなりません。
data要素については、パラメータリストがデータ型によって許容されるものでなければなりません。
7.18 combine attribute
grammar要素に含まれるstart, defineの2要素についてのcombine属性の変換規則です。
同じname属性値を持つstart, define要素はいずれについても同じルールで1つのstart, define要素にまとめられますが、そのためにcombine属性の値が使われます。
取りうる値は6章のEBNFで明示されていますが、choiceかinterleaveのいずれかになります。
前提として、同じname属性値をもつdefine要素について、それらの中に1つより多くcombine属性を持たないものが存在してはならず、またcombine属性値はすべて等しくなければなりません。start要素も基本的に同様ですが、name属性を持たないので、同じgrammar要素に含まれるcombine属性を持つstart要素はすべて同じcombine属性値を持たなければなりません。
上記の制約を満たす場合、同じname属性値を持つ複数のdefine要素およびすべてのstart要素についてcombine属性値が一意に定まるため、これを利用して1つのstart, define要素にまとめます。
例としては以下のような感じになります。
例えば、
<define name="n" combine="choice">p1</define>
<define name="n" combine="choice">p2</define>
という、nという名前を持ち、choiceをcombine属性として持つdefine属性が複数ある場合、
<define name="n">
<choice>
p1
p2
</choice>
</define>
とまとめられます。新たに現れたchoice要素はcombine属性値から決まり、上の例ではchoiceが指定されているのでchoice要素となりましたが、interleaveが指定されているならば、interleave要素となります。
また、要素がまとめられる前にcombine属性は除去されるため、まとめられた後のdefineにはcombine属性はありません。start要素でも同様の方法でまとめが行われます。
7.19 grammar element
この規則では、スキーマの最上位要素がgrammarになり、またそれ以外のいかなるgrammar要素も含まないような形に変換が行われます。
まず、「要素のスコープ上の文法」を、その要素の最近祖先のgrammar要素であると定義します。
ref要素は、その要素のスコープ上の文法に含まれるname属性値が等しいdefine要素を指します。
parentRef要素は、name属性値が等しいdefine要素であり、かつparentRef要素のスコープ上の文法のさらにそのスコープ上の文法と、define要素のスコープ上の文法が等しいようなものを指します。わかりにくい説明ですが、要は自身に適用される文法ではなく、その1つ上の階層の文法のdefineを参照するための要素と言えます。
ref, parentRefは、必ず1つのdefine要素を指さなければなりません。また、grammar要素は必ず1つのstart子要素を持たなければなりません。
ここまでが前置きの話で、ここからが変換の方法です。
まず、最上位のパターンであるPを、<grammar><start>P</start></grammar>の形に置き換えます。
次に、define要素を、スキーマのいかなる箇所にも同じ名前のdefine要素が存在しないように、名前を変更します。名前の変更は、define要素のname属性を変更し、またそのdefine要素を指すref, parentRefの2要素のname属性の値を変更することで行います。どのような名前にするべきかは仕様では書かれていないので、重複さえしなければどんな名前でもいいってことなんですかね?
最後に、define要素を最上位のgrammar要素の直接の子となるように移動し、入れ子になったgrammar要素をその子であるstart要素の子で置換し、parentRefをrefに変更します。
7.20 define and ref elements
この規則では、すべてのelement要素がdefine要素の子となり、またdefine要素の子はelement要素となります。
まず、到達不能なdefine要素を除去します。具体的には、到達可能なref要素によって参照されないdefineが削除されます。
ではその「到達可能なref要素」とは何かというと、start要素の子孫、もしくは到達可能なdefine要素の子孫であるようなref要素を指します。
次に、define要素の子ではないelement要素について、まずdefine要素をgrammar要素の直下に追加し、追加したdefine要素を指すref要素によってelement要素を置換します。
追加したdefine要素の名前は、既存のdefine要素と重複しないものを選択します。
そして、追加したdefine要素の子として、置換されたelement要素を追加します。
ここで、指しているdefine要素の子がelement要素ではないようなref要素を「展開可能である」と定義します。
展開可能であり、start, elementのいずれかの子孫であるようなref要素について、それが指すdefine要素の子で置換することにより、再帰的に展開します。再帰的な展開によってループが発生してはなりません。
最後に、子がelement要素ではないdefine要素を除去します。
7.21 notAllowed element
この規則では、notAllowed要素がstart, elementの子としてのみ現れるように変換します。
notAllowed要素を子として持つattribute, list, group, interleave, oneOrMoreの5要素について、notAllowed要素に変換します。
2つの子がnotAllowed要素であるようなchoice要素も、notAllowed要素に変換します。
2つの子のうち片方がnotAllowed要素であるchoice要素は、notAllowedではない方の子に置換します。
notAllowed要素を子に持つexcept要素は、(置換ではなく)除去します。
これらの変換は、スキーマ内に適用可能な要素がなくなるまで繰り返し行われます。
最後に、到達不可能になったdefine要素は除去されます。
7.22 empty element
ようやく最後の変換規則です。長かった…
この規則では、empty要素がgroup, interleave, oneOrMoreの3要素の子として、あるいはchoice要素の2番めの子として現れないように変換を行います。
group, interleave, choiceの3要素について、2つの子要素が両方ともemptyなら、自身をempty要素に置き換えます。
group, interleaveの2要素について、片方の子がemptyなら、自身をemptyではない子要素に置き換えます。
choice要素について、2つ目の子要素がemptyなら、1つ目の子と順序を入れ替え、1つ目の子がemptyになるようにします。
oneOrMore要素について、子がempty要素なら、自身をempty要素に置き換えます。
この規則も適用可能な限りは繰り返し適用し、適用可能な要素がスキーマ内に存在しないようにします。
8 Simple syntax
7章の規則をすべて正しく適用し、また一部示されていた制約もすべて満たされているならば、スキーマは全体としてこの章で示されているEBNFにマッチするはずです。
例によってEBNFをすべて明示はしませんが、いくらかの要素や属性は除去されていなくなっていますし、子要素のパターンが単純化されています。
また、完全な構文で挿入可能だった空白文字、外来属性、外来要素、ns属性、datatypeLibrary属性などもすべて除去されているため、結果的に、EBNFで明示的に許可される要素・属性以外のものを受け付けなくて良くなっています。
注意点として、単純な構文は、仕様の内部で必要とされるものであって、RELAX NGの代替構文ではありません。
サポートしなければならないのは完全な構文のほうですし、正しいRELAX NG文書が単純な構文にマッチすることは要求されません。
9 Semantics
スキーマの意味論の話です。7章の変換規則で得られた単純な構文がどのような意味を持つのかを示します。
9.1 Inference rules
RELAX NGスキーマの意味論は、どのようなXML文書がスキーマに照らして妥当と言えるのかを規定します。
意味論は形式的に記述されます。形式化は公理と推論規則を用います。「公理」と「推論規則」については3章でも説明があった通りですが、ここでも改めて説明されています。
「公理」とは、無条件に証明可能な命題のことです。
「推論規則」とは、1つ以上の前提条件と、たった1つの結論からなります。「前提条件」は、肯定的であるか否定的であるかのいずれかです。推論規則におけるすべての肯定的な前提条件が証明可能であり、またすべての否定的な前提条件が証明可能ではないなら、推論規則における結論は証明可能であると言えます。
なんかえらい難しいことを言っているようですね…正直完璧に意味はわかっていないのですが、とりあえずフィーリングで理解するしかなさそうです。
個人的には、「前提条件が証明可能でない」の意味がよくわかりません。証明可能ではないことなんてどうやって確かめればよいのでしょうか。
ともかく、XML文書がスキーマに照らして妥当であるというのは、それが妥当である、という命題が、この章で示される形式化の下で証明可能であるときを指します。
続いて、この章での推論規則の記法の話ですが、前提条件と結論を水平線を引っ張って分割することで記述します。見た目的には分数みたいな感じで、分子の部分が前提条件、分母の部分が結論を示します。
前提条件がnot(p)の形で記載されているときにその前提条件は否定的とみなされ、そうでないときは肯定的な前提条件です。
また、公理と推論規則は、変数を用いることがあります。変数は名前と任意に添え字を持ち、イタリック体で表記されます。変数はその名前から決定される値域を持ちます。公理や推論規則が複数の同じ値域を持つ変数を含む場合、識別のために添え字が用いられます。
公理と推論規則には、暗黙にそれらが含む変数についての全称記号が適用されます。…ちょっと意味がわかりませんが、続きはあとで述べられるようです。
最後に、推論規則や公理は特定の変数を複数回含む可能性があるため、変数が取り得る各種類の対象に対して同値関係を定義する必要がある、と述べられています。
どのような種類のオブジェクトの同値関係も値に基づき、2つのオブジェクトは、その構成要素が同一であれば同一とみなします。
例えば、2つの属性は名前と値が同じであれば同一と見なされ、2つの文字は、そのUnicode文字コードが同一であれば同一です。
9.2 Name classes
名前クラスについて主要な意味論は、名前が名前クラスに属することです。
「名前クラス」とは、nameClassにマッチする要素のことです。
「名前」とは、5章でも述べられていたとおり、名前空間URIと局所名の組です。
第一の公理は
と呼びます。
この先も公理や推論規則について記号で記述されていくのですが、ブログでの書き方がわからないので、ここには書きません。内容だけを見ていきます。
が言っていることは、あらゆる名前
は、名前クラス<anyName/>に属する、ということです。言い換えれば、<anyName/>はあらゆる名前にマッチします。
ここで注意点として、先程の全称記号が云々という話が出てきます。
見た目上、変数である
は
としか記述されていないのですが、暗黙的に全称記号が適用されているとみなすので、結果的に、「あらゆる名前にマッチする」という意味を持つとみなされることになります。
続いて第一の推論規則
が示されています。
前提条件は、「名前
が名前クラス
にマッチしない」ことです。結論は「名前は<anyName><except>nc</except></anyName>に属する」ことです。
結局の所、「あらゆる名前クラス
に対して<anyName><except>nc</except></anyName>は、
にマッチしないあらゆる名前
にマッチする」ということを言っています。
,
と似たような感じで、
,
,
,
,
などが定義されます。
は「<nsName ns="u">は名前
にマッチする」という公理、
は「あらゆる名前クラス
に対して<nsName ns="u"><except>nc</except></nsName>は、
にマッチしないあらゆる<nsName ns="u">にマッチする名前
にマッチする」という推論規則です。
は「<name ns="u">ln</name>は名前
にマッチする」という公理です。
は「あらゆる名前クラス
に対して<choice>nc1 nc2</choice>は、
にマッチする名前
にマッチする」という推論規則です。
は「あらゆる名前クラス
に対して<choice>nc1 nc2</choice>は、
にマッチする名前
にマッチする」という推論規則です。
より簡単な言葉で言えば、
- 名前クラス
<anyName>はあらゆる名前にマッチするが、<except>を子に指定することで、マッチさせない名前を指定できる
- 名前クラス
<nsName>はns属性値が名前空間URIと等しいあらゆる名前とマッチするが、<except>を子に指定することで、マッチさせない名前を指定できる
- 名前クラス
<name>はns属性値が名前空間URIと等しく、内容テキストが局所名と等しい名前とマッチする
<name>については<except>が指定可能とは明記されていません。
<choice>は子がともに名前クラスの場合、いずれかの名前クラスに名前がマッチする時にマッチするとみなす
といったことになると思います。
9.3 Patterns
9.3.1 choice pattern
<choice>の意味論です。
は「あらゆるパターン
に対して<choice>p1 p2</choice>は、文脈
においてパターン
にマッチする属性集合
及び列
の組にマッチする」という推論規則です。
は「あらゆるパターン
に対して<choice>p1 p2</choice>は、文脈
においてパターン
にマッチする属性集合
及び列
の組にマッチする」という推論規則です。
つまるところ、<choice>の子であるパターンの片方にマッチするようなものにマッチする、という意味になります。
9.3.2 group pattern
<group>の意味論です。
は「あらゆるパターン
に対して<group>p1 p2</group>は、文脈
においてパターン
にマッチする属性集合
及び列
の組と、同様に
にマッチする
,
について、文脈
において
の和集合と
を順に連結した列にマッチする」という推論規則です。
パターンを順に合成する役割をもつと考えればよいでしょうか。
属性集合は順序なし集合であるため、単に和集合をとるだけで良いですが、子の列は順序付き列なので、指定された順に並べる必要があります。
9.3.3 empty pattern
<empty>の意味論です。
は「<empty/>は、文脈
において、属性集合も列もともに空であるようなパターンにマッチする」という公理です。
DTDの要素型宣言において#EMPTYを指定した場合と同じようなものだと思っていますが、属性集合が空というのがちょっと気になりますね…
あんまり違いがよくわかっていません。
9.3.4 text pattern
<text>の意味論です。
は「<text/>は、文脈
において、属性集合も列もともに空であるようなパターンにマッチする」という公理です。
は「<text/>は、文脈
において、空な属性集合と列
が<text/>にマッチするようなパターンについて、
に文字列
を連結したパターンにもマッチする」という推論規則です。
は、
とほぼ同じ内容です。つまり、空の内容テキストも<text/>にマッチします。
それを公理として定めた上で、空の内容テキストにあるテキストを内容テキストとして追加したものも、また<text/>にマッチするのだ、というのが
の規則になります。
9.3.5 oneOrMore pattern
<oneOrMore>の意味論です。
は「パターン
に対して<oneOrMore>p</oneOrMore>は、文脈
においてパターン
にマッチする属性集合
及び列
の組にマッチする」という推論規則です。
は「パターン
に対して<oneOrMore>p</oneOrMore>は、文脈
においてパターン
にマッチする属性集合
及び列
と、<oneOrMore>p</oneOrMore>にマッチする
と
の組であって、
と
が互いに素であるようなものについて、
の和集合と
をこの順番で連結した列の組にマッチする」という推論規則です。
は簡単で、パターン
にマッチするものは<oneOrMore>p</oneOrMore>にもマッチするという意味です。one or moreのoneの部分ということです。
があんまりピンと来ないのですが、列はおおよそイメージ通り、
にマッチするものはなんべんでも連結してよい、ということを言っています。属性のほうがピンとこない点で、
と
が互いに素でなければならないとはどういうことかと。私の理解では、単に重複して同じ属性を指定できないから互いに素という条件をつけているだけなのだと思っています。
9.3.6 interleave pattern
<interleave>の意味論です。
まず1つの公理と2つの推論規則があります。
は「空の列どうしをinterleaveしたものは空の列である」という公理です。
は「
が
をinterleaveしたものであるなら、
を連結したものは、
を連結したものと
をinterleaveしたものである」という推論規則です。
は「
が
をinterleaveしたものであるなら、
を連結したものは、
と
を連結したものをinterleaveしたものである」という推論規則です。
2つ目と3つ目はあまりピンと来ないのですが、親切なことに下に例が書いてあり、<a/><a/>と<b/>をinterleaveした結果は、<a/><a/><b/>, <a/><b/><a/>, <b/><a/><a/>であると言っています。つまるところ、元の列の順序を保ったまま、まぜこぜにして併合するという操作だと言えます。
<a/><a/>と<b/><b/>をinterleaveした結果として<a/><b/><a/><b/>があり得る結果なのかは、ちょっとわかりませんでした。あり得る気がするのですが、推論規則からそれは読み取れませんでした。libxml2のテストとかを見る限り、たぶんOKなのだと思います。
上記のinterleaveの操作を踏まえて、
が定義されます。
は「パターン
に対して<interleave>p1 p2</interleave>は、文脈
において
にマッチする属性集合
と子の列の組
と、同じく
にマッチする
の組について、
が[tex m _ {1}, m _ {2}]をinterleaveした列であるとき、
の和集合と
の組にマッチする」という推論規則です。
9.3.7 element and attribute pattern
初めに「弱いマッチング」が定義されます。この動機としては、要素と属性の空文字列や値なしの考え方の微妙な違いを吸収するというものがあります。
具体的に言えば、属性は空文字列も含めて常に値を持ちますが、値が空の列になることはありません。一方で、要素の子は空の列になりうる一方、データモデル上、空の文字列のみからなる列とはなりえません。このような微妙な違いはあるものの、意味的にはおおよそ同じであるような表現を一貫して検証できるように、「弱いマッチング」を定義すると便利です。
「弱いマッチング」は要素・属性の検証に用いられ、次のような推論規則からなります。
は「文脈
において属性集合
と列
の組がパターン
にマッチするなら、
において
の組は
に弱くマッチする」という推論規則
は「文脈
において属性集合
と空である列の組がパターン
にマッチするなら、
において
と空白文字のみからなる文字列、または空の列の組は
に弱くマッチする」という推論規則
は「文脈
において属性集合
と空文字列の組がパターン
にマッチするなら、
において
と空の列の組は
に弱くマッチする」という推論規則
そして、「弱いマッチング」を用いて<attribute>, <element>の意味論を定義します。
まず、
は「文脈
において空の属性集合と文字列
の組がパターン
に弱くマッチし、また名前
が名前クラス
に属するならば、
において属性
と空の列の組は<attribute>nc p</attribute>にマッチする」という推論規則です。
続いて、
は「文脈
において属性集合
と混在列
の組がパターン
に弱くマッチし、名前
が名前クラス
に属し、
が要素の子として現れることのできる列であり、局所名
に対して文法が<define name="ln"><element>nc p</element></define>を含むならば、文脈
において空の属性集合と空文字列
によって
を挟んだ列の組は<ref name="ln">にマッチする」という推論規則です。
うーん…むずかしいですね。
9.3.8 data and value pattern
まず冒頭では、データ型ライブラリ及びデータ型の特定方法と、データ型ライブラリが提供する機能の話が述べられています。
データ型ライブラリはこれまで述べられてきたとおり、URIによって識別されます。そして、データ型はNCNameで識別されます。
結果的には、ある型はURIと局所名の組によって一意に識別できることになり、要素や属性と同じように名前で識別ができます。
データ型ライブラリが提供する機能は以下の2つです。
- 文字列がデータ型の正しい表現であるかを判定する。この機能は任意の個数引数を取ることがあり、データ型によって何を引数に取れるかが特定される。
- 2つの文字列があるデータ型として等しいかを判定する。この機能は引数を取らない。
いずれも文字列が現れる文脈を用いることができ、例えばQNameを表現する型は、名前空間マッピングを用いるでしょう。
2つ目の機能を表す
という関数が定義されていますが、この関数は反射的、推移的、かつ対称的でなければなりません。
この制約は、以下の3つの推論規則で表現されます。
は「文脈
において、文字列
が引数
を適用することでURI
, 局所名
で特定されるデータ型として正しい表現であると判定されたならば、
で特定されるデータ型として
と
は等しい」という推論規則
は「文脈
における
と
における
が
で特定されるデータ型として等しく、
における
と
における
も同様であるなら、
で特定されるデータ型として
における
と
における
は等しい」という推論規則
は「文脈
における
と
における
が
で特定されるデータ型として等しいならば、
における
と
における
も等しい」という推論規則
えらい長ったらしい文言になってしまいましたが、プログラミング言語的に書くなら以下のassert!が常にすべて通るということです。
let s1: T, s2: T, s3: T;
assert!(s1 == s1);
if s1 == s2 && s2 == s3 {
assert!(s1 == s3);
}
if s1 == s2 {
assert!(s2 == s1);
}
Tが整数型や文字列型のような一般的ななら、通って当たり前じゃんと思ってしまいますが、そういった当たり前の性質を具備するような型を定義しなければならないということです。
等値比較について、これを満たさないような例はパッと思いつきませんでしたが、どんなのがあるんですかね?
さて、上記を前提として、<data>, <value>の意味論が定義されます。
は「
で特定されるデータ型として、
における
と
にデフォルト名前空間
を加えた文脈における
が等しいならば、空の属性集合と
の組は<value datatypeLibrary="u1" type="ln" ns="u2" [cx2]>s2</value>にマッチする」という推論規則です。
開始タグに含まれる[cx2]は、4.2.3章で定義されていた記法で、「そのパターン要素における文脈」と定義されています。
にデフォルト名前空間として
を加えるのはなぜかというと、
がns属性で指定されているからです。もしns属性が空文字列なのであれば、それは単に
と同じ内容をもつ文脈になります。
続いて、
は「文脈
において、文字列
が引数
を適用することで
で特定されるデータ型として正しい表現であると判定されたならば、空の属性集合と
の組は<data datatypeLibrary="u" type="ln">params</data>にマッチする」という推論規則です。
最後に、
は「文脈
において、文字列
が引数
を適用することで
で特定されるデータ型として正しい表現であると判定され、属性集合
と文字列
の組がパターン
にマッチしないならば、空の属性集合と
の組は<data datatypeLibrary="u" type="ln">params<except>p</except></data>にマッチする」という推論規則です。
データ型ライブラリが文字列が型に適合するか検査するときに引数が渡せませすが、その引数は<data>の子として与えることができます。
また、名前クラスと同じように、<except>を子に追加することで、除外したいパターンを指定することができます。
9.3.9 Built-in datatype library
組み込みデータ型についての話です。
データ型ライブラリはURIで識別されますが、もしURIが空文字列なら、それは組み込みデータ型を指しているとみなされます。
組み込みデータ型ライブラリは、string, tokenの2つのデータ型だけを提供します。2つの組み込み型について、3つの公理と1つの推論規則が定義されます。
は「任意の文脈
において、任意の文字列
は、引数として何も適用せず、空文字列であるURIとstringという局所名で特定される型として正しい表現である」という公理です。
は「任意の文脈
において、任意の文字列
は、空文字列であるURIとstringという局所名で特定される型として、
と等しい」というという公理です。
は「任意の文脈
において、任意の文字列
は、引数として何も適用せず、空文字列であるURIとtokenという局所名で特定される型として正しい表現である」という公理です。
は「文字列
の正規化値が等しいのであれば、文脈
における
と
における
は、空文字列であるURIとstringという局所名で特定される型として等しい」という推論規則です。
stringもtokenも、許可される値はまったく同じで、任意の文字列を受け付けます。
しかし、推論規則からも見て取れる通り、stringは完全一致で等値比較をする一方、tokenは正規化値で等値比較を行います。
文字列の正規化とは何ぞやという話ですが、これは4.2.3章で定義されており、「先頭と末尾の空白文字を除去し、それぞれの極大な空白文字列を1つのスペース文字に置換する」処理です。つまり、DTDにおけるCDATA型以外の属性型の属性値の正規化と同じ処理のことです。
9.3.10 list pattern
ようやく最後のパターンです。<list>の意味論です。
は「空の属性集合と文字列
を空白文字で分割した列の組がパターン
にマッチするなら、空の属性集合と
の組は<list>p</list>にマッチする」という推論規則です。
要は空白区切りのトークン列をマッチさせるためのパターンです。
9.4 Validity
9章の最後の推論規則は、「スキーマに照らして妥当」であることを形式的に述べるものです。
空の属性集合を伴う要素が文法のstartパターンにマッチするとき、要素は妥当であるとみなされます。
はこれを形式的に述べた「文法がパターン
を含むstart要素<start>p</start>を含み、空の属性集合と要素
の組が
にマッチするなら、
は妥当である」という推論規則です。
10 Restrictions
最後の章です。ここまででPDF25枚分ですが、Annexの直前までで29枚なので、あとPDFを4枚読めば終わりです。
10.1 General
10章では諸々の制約が述べられていますが、すべて単純な構文に変換されたあとで検査すべき制約です。
10.2 Prohibited paths
10.2.1 General
この章では、スキーマの要素として許容されない垂直関係について述べられています。
/, //といった記号で垂直関係を示していますが、おおよそXPathにおけるロケーションパスのような構文なので、容易に理解できると思います。
この章で示された許容されない垂直関係を含むスキーマ文書は、正しいものとはみなされません。
本当は章分けして書いてあるのですが、似た内容を並べるだけになるので、ズラッと列挙します。
許容されない垂直関係は以下のとおりです。
attribute//ref
attribute//attribute
oneOrMore//group//attribute
oneOrMore//interleave//attribute
list//list
list//ref
list//attribute
list//text
list//interleave
data/except//attribute
data/except//ref
data/except//text
data/except//list
data/except//group
data/except//interleave
data/except//oneOrMore
data/except//empty
start//attribute
start//data
start//value
start//text
start//list
start//group
start//interleave
start//oneOrMore
start//empty
attributeについて言えば、属性の子に要素や属性が来るのはおかしいということでしょう。
dataについて、すべてexceptと絡んだときのパターンです。data/exceptの子孫としては、data, value, choice以外指定できないということを暗に示しています。
startについて、子孫になれるのはref, choice, notAllowedの3つだけになります。トップレベルに要素以外のものがあるのはおかしいので、それはそうかもしれません。
choiceが選択できるということは、文書要素の型を複数許可できたりするのでしょうか?
10.3 String sequences
章の冒頭で、「以下のパターンは許容されない」といったことが書いてありますが、それを見ただけでは具体的に何がダメなのかさっぱりわかりません。
続けて、要素や属性の子として、「子にマッチするパターン」と「単一の文字列」は二者択一の関係である、といったことが書かれています。
しかし、これもここだけ読んだだけではよくわかりません。ちなみに、listパターンの中はルールの対象外とのことです。
この章の内容は、この後の形式的な説明を読むほうがわかりやすいです。
まず「内容種別」という概念を取り入れます。要素の子として許容されるパターンは、3つの「内容種別」のいずれかを持ち、その3つの「内容種別」とは
である、と定義します。
これらの内容種別について、グループ化可能な組み合わせは、任意の内容種別と
、
どうしの計4つの組み合わせだけです。
これを形式的に表したのが次の3つの公理です。
は「
と任意の内容種別はグループ化可能である」という公理
は「任意の内容種別と
はグループ化可能である」という公理
は「
どうしはグループ化可能である」という公理
記号だともっとシンプルになっていますが、文字にすると前段で言っていることと同じことを言っています。
また、内容種別には大小関係があるということが4.2.3章で定められており、小さい方から大きい方へ、
の順になります。
内容種別
について、
はこの大小関係にしたがって、大きい内容種別を返します。例えば、
は
になります。
これらの公理を使ったり使わなかったりすることで、各パターンについて、内容種別を定めます。
は「<value datatypeLibrary="u1" type="ln" ns="u2">s</value>は
である」という公理
は「<data datatypeLibrary="u" type="ln">params</data>は
である」という公理
は「パターン
が任意の内容種別
を持つなら、<data datatypeLibrary="u" type="ln">params<except>p</except></data>は
である」という推論規則
は「<list>p</list>は
である」という公理
は「<text/>は
である」という公理
は「<ref name="ln"/>は
である」という公理
は「<empty/>は
である」という公理
は「パターン
が任意の内容種別
を持つなら、<attribute>nc p</attribute>は
である」という推論規則
は「パターン
がそれぞれ任意の内容種別
を持ち、
がグループ化可能であるなら、<group>p1 p2</group>は
のうち大きい方の内容種別を持つ」という推論規則
は「パターン
がそれぞれ任意の内容種別
を持ち、
がグループ化可能であるなら、<interleave>p1 p2</interleave>は
のうち大きい方の内容種別を持つ」という推論規則
は「パターン
が任意の内容種別
を持ち、
どうしがグループ化可能である(すなわち
は
ではない)なら、<oneOrMore>p</oneOrMore>は
を内容種別として持つ」という推論規則
は「パターン
がそれぞれ任意の内容種別
を持つなら、<choice>p1 p2</choice>は
のうち大きい方の内容種別を持つ」という推論規則
ここまでの公理、推論規則によって、内容種別を持つパターン、持たないパターンが明らかになりました。
そしてこの章の冒頭の話に戻るわけですが、要素内容として現れるパターンはすべて内容種別を持つものでなければならない、という制約が課されます。
は「局所名
が要素パターン<element>nc p</element>を指しており、パターン
が要素内容を持たないなら、スキーマとして正しくない」という推論規則です。
冒頭の例を見直しておくと、1つ目は以下のパターンでした。
<element name="foo">
<group>
<data type="int"/>
<element name="bar">
<empty/>
</element>
</group>
</element>
内側から見ていくと、まずdataパターンは公理
より
です。一方elementパターンは、単純化によりrefパターンになりますから、公理
より、
です。
その外側はgroupパターンなので、推論規則
を適用すると、これが内容種別を持つためには2つの子が内容種別を持ち、かつグループ化可能な組み合わせでなければなりません。しかし、
の組はグループ化可能ではないため、この例のgroupパターンは内容種別を持ちません。
そして、最も外側のelemenパターンの内容として現れたgroupが内容種別を持たないため、推論規則
より、スキーマとしては正しくない、という結論が得られます。
2つ目の例は以下のパターンでした。
<element name="foo">
<group>
<data type="int"/>
<text/>
</group>
</element>
これも同様に内側から見ていくと、data, textはそれぞれ、公理
より
, 公理
より
です。
これはグループ化可能な組ではないので、groupパターンは推論規則
より、内容種別を持ちません。
従って、推論規則
より、やはりスキーマとしては正しくない、という結論が得られます。
10.4 Restrictions on attributes
属性に関する制約です。
まず、属性は重複してはなりません。具体的には、<group>p1 p2</group>, <interleave>p1 p2</interleave>の2つのパターンにおいて、
に現れるattributeの名前クラスと
に現れるattributeの名前クラスの両方に属する名前があってはなりません。
あるパターン
(
も含めて、前の文章の
とは関係ない)が
の中に現れる、というのはどういうことかというと、それは
が
である
がchoice, interleave, group, oneOrMoreのいずれかであって、
は
の片方もしくは両方の子の中に現れる
のいずれかです。
続いて、無限名前クラスを使うattributeパターンは、繰り返されなければならない、と述べられています。
「無限名前クラス」とは何ぞやということですが、anyName, nsNameを指すと読み取れます。つまり具体的に言えば、anyNameかnsNameを子孫に持つattribute要素は、祖先要素にoneOrMoreを持たなければならない、という制約になります。
最後に、無限名前クラスを使うattributeパターンは、それらの値としてtextを持たなければならない、と述べられています。
つまり、anyNameかnsNameを子孫に持つattribute要素は、子要素としてtextを持たなければなりません。
最後の2つの制約は、「否定の下での閉包のために必要だ」と記載されていますが、どういう意味なんですかね?
10.5 Restrictions on interleave
最後はinterleaveに関する制約です。
実装を容易にするため、特定の名前の要素は、interleaveパターンにおいて高々一度までしか現れてはならず、text要素も同様に高々一度までしか現れてはなりません。
より具体的に言えば、<interleave>p1 p2</interleave>というパターンがあるとし、
に含まれるrefパターンと
に含まれるrefパターンのそれぞれが指すelementの子孫の名前クラスの両方に属する名前があってはならない
textが
と
の両方で現れてはならない
という制約になります。
あるパターン
が
の中に現れるというのがどういうことなのかは、10.4章での定義のとおりです。
ようやく最後の章です…適合性の話です。
適合するRELAX NG検証器は、どのようなXML文書も、正しいRELAX NGスキーマとして正しいかを判定できなければならず、またどのようなXML文書と正しいRELAX NGスキーマの組でも、文書がスキーマに照らして妥当かを判定できなければなりません。
ただし、組み込み型以外の検証器がサポートしていないデータ型ライブラリを使用する場合や、取得できないリソース及び要素を構築できないリソースへのexternalRef, includeを含む場合には、その限りではありません。
適合するRELAX NG検証器は、どのデータ型ライブラリをサポートしているのか、どのようなリソースをサポートできるのか、ドキュメントで明示することが望ましいです。
あとがき
超長かったです…RELAX NGは比較的シンプルなスキーマ言語になっていると聞いていましたが、それでこの長さとは驚きです。たぶん今まで読んだ仕様では、XML仕様の次くらいに長かったのではないでしょうか。
また、長いだけではなく、内容も難しかったです。
論理学とかなんかそういう系の学問を全く知らないので、あの記号の羅列や謎の用語に圧倒されてしまいました。たぶん、途中書いている公理や推論規則も大いにウソを含んでいるので、まあこのメモはアテにせずに、ISOの仕様の原文を読みに行ったほうが良いです。
ちなみに、JIS X 4177-2も並行して読んでいた(というか英語非対応なので、和訳版として活用していた)のですが、私が読んでいた範囲では特に差はわかりませんでした。
微妙に違う点があったりするのかもしれませんが、対応するISO仕様が異なるとはいえ、大いに参考になりました。
仕様の内容は終始形式的な説明に徹底していて、正直それだけを読んでも意味がさっぱりだったのですが、一応RELAX NGの生みの親ともいえるお二人が執筆したと思われるRELAX NG Tutorialという文書もあります。
こちらは、検証器を実装する側というよりは、ユーザとしてRELAX NGスキーマを活用する側の目線でスキーマの機能を説明しており、直感的で非常にわかりやすかったです。
正直なところ、仕様を読むよりも先にこっちを読んでおくべきだったかもしれないとすら思います。何もイメージがついていない状態で仕様書を読んでも、学がある人でないとたぶんチンプンカンプンです(私もそう)。
実装も難しそうですね。まったくパフォーマンス度外視なら、木を構築して、ルールを片っ端から当てはめて20回くらい木を再構築して、形式的な制約をXMLの要素や値に片っ端から当てはめて、とやればいいのでしょうが、それではちょっと微妙ですよね。
あとデータ型ライブラリをどんな感じにするか。XSDのデータ型も使いたくなりますが、XSDの仕様は未読なので、どうすればいいやら。流石にstring, tokenだけというのは味気ない気がしなくもないです。
RELAX NG仕様を読み切るのも1週間近くかかりましたが、これがXSDだとどうなってしまうんですかね…
パラパラと読み流してみましたが、トンデモなく長い文書なので、気が滅入りそうです。
RELAX NGを実装した後は、XSLTの実装をしようと思っているので、たぶん次はXSLTの仕様を読みます。
XPath 1.0までしかサポートしていないので、XSLTも1.0を読みます。
XPath 2.0以降もサポートしたいのですが、XSDの型のサポートが入っていると聞いており、それこそRELAX NG以上にXSDとの連携が必須です。故にたぶんだいぶ先の話になってしまうので、とりあえずXSLT 1.0で妥協です。
XSLTの次はXPointerかC14Nにでも行こうかと思っています。
C14Nはいいとして、XPointerはXIncludeの実装に必要っぽいので、避けて通れません。