競プロ備忘録

競プロerの備忘録

XMLのお勉強その14 - XML Inclusions (XInclude) Version 1.0 (Second Edition)

前回

XMLのお勉強その13 - XPointer Frameworkなど - 競プロ備忘録

読んでいく仕様書

原文:XML Inclusions (XInclude) Version 1.0 (Second Edition)
和文:なし

いわゆるXIncludeです。外部リソースを取り込んで文書の一部を置換できる機能ということで、実体参照と似ているような気がしなくもないですが、何が違うんでしょうか?それは仕様を読み切れば明らかになると信じています。

ちなみに、MDNによれば「主要なブラウザーは、XInclude の仕組みを標準ではサポートしていません。」とのことです。不遇すぎる…
まあ実体参照と似たような機能なので、上手いこと使わないと脆弱になるのでしょうし、致し方ないのかもしれませんね。

シンプルな仕様だと思っていましたが、思っていたよりは長い仕様のようです。まあ良心的なほうではあるレベルだと思いますが。

本編

1 Introduction

この仕様の目的として、文書のモジュール化を挙げています。
一般的なプログラミング言語でも、コードをモジュールに分割することで再利用性を上げたり依存性を下げたりしていると思いますが、マークアップ言語でもそういう機能が必要だろう、ということのようです。
XInclude仕様は、XML文書に対するモジュール化の機構を提供します。

XLink仕様との関連の話で、XIncludeはXLinkshow="embed"とは異なり、メディアタイプ固有の(より具体的にはXMLからXMLへの)変換を規定するものだと述べられています。

と言ってもまだXLink仕様を読んでいないので、何のこっちゃという感じです。ともかく、XLinkとは棲み分けがあるのだということのようです。

1.2 Relationship to XML External Entities

個人的には一番気になるところですが、外部実体との関係性です。結論からいえば、これらは異なるものであって、相互に補完的な技術ということです。

まず、外部実体の処理はXMLの解析時に発生する一方、XIncludeの機構はInfosetに対して動作します。
つまり、外部実体の処理は、XIncludeの機構に先行して発生するということです。

次に、外部実体にはDTDが必要であり、これによって取り込みに一連の依存関係が生じます。例えば、文書要素の型も一意でなければなりません。さらに、妥当性検証を行うパーサの場合、(外部実体の内容も含めた、という意味だと思いますが)完全な文書モデルを事前に定義する必要があります。
一方でXIncludeにはそれらの制約はありません。文書要素の型とは独立ですし、(Infosetに対して動作するので)妥当性検証とも独立しています。

参照方式に着目すると、外部実体は間接参照で呼び出されます。事前に宣言され、命名され、それを参照することによって実体を呼び出すことができます。
一方でXIncludeは直接参照による呼び出しです。事前の宣言・命名は不要です。

読み込み時のエラーに着目すると、外部実体の読み込み失敗は致命的なエラーである一方、XIncludeにはフォールバックの方式があります。

最後に、外部実体はDTDの記法で宣言されるため、XMLの文法とは別個に習得が必要になりますが、XIncludeはXML構文に基づきます。

こうしてみてみると、だいぶ違いがあります。
取り込むリソースが解析時の検証の対象にならないというのは一長一短な感じはしますが、取り込むリソースの内容が動的で予測が難しい、あるいは特定の型を持ち得ないようなデータである場合には嬉しいのかもしれません。
また、フォールバックの方式があるのは嬉しいと思います。外部実体の取り込みはその点で予測可能とは言い難い性質があったので、それが改善されていると見ることができます。

1.3 Relationship to DTDs

1.2の内容とも関連しますが、XIncludeはInfosetからInfosetへの変換の仕組みであるため、DTDによる検証との関連性は定義されません。

1.4 Relationship to XML Schemas

XIncludeの機構は、XSDによって生成されるPSVIとの関連性も定義されません。

1.5 Relationship to Grammar-Specific Inclusions

特定のXML文法による専用の取り込み機構との関連については、何か言っているようで言っていないことしか書いていませんが、XIncludeは汎用的な機構を提供する、とだけ述べられています。

2 Terminology

助動詞の用法はいつものです。(RFC2119)

"Information set"は、XMLプロセッサの解析結果である、Infoset仕様で定義される情報項目とプロパティの集合です。"Infoset"も同義語として扱います。

"fatal error"は「正常な処理の継続を妨げる要因の存在」、"resource error"は「URLからリソースを取得する試みが失敗したこと」、をそれぞれ指します。以降は「致命的なエラー」「リソースエラー」などと表記します。
リソースエラー以外のエラーが発生した場合は処理を停止しなければならず、リソースエラーが発生した場合は4.4章で説明されるフォールバックを処理しなければなりません。

3 Syntax

XInclude仕様は、http://www.w3.org/2001/XIncludeという名前空間名に関連付けられた名前空間(この記事では以降「XInclude名前空間」と呼びます)を定義します。この名前空間にはinclude, fallbackの局所名をそれぞれ持つ2つの要素が含まれます。
仕様では便宜上、XInclude名前空間が束縛する接頭辞をxiで固定していますが、あくまで便宜上なので、別にxiを使う必要はないです。Wikipediaxiを使わなければならないとか抜かしていますが、それは明確にウソです。

3.1 xi:include Element

まずxi:includeについて、以下の属性を持ちます。

  • href
    • 4.1.1章で定義されるエスケープ処理を施すことで、取り込むリソースを指すURI参照、IRI参照のいずれかになる。ただし、フラグメント識別子を指定することは致命的なエラーである。
    • hrefを指定しない、あるいは空文字を指定した場合、参照先は現在の文書になる。
    • hrefを指定せず、かつparse="xml"を指定した場合、xpointer属性が必須である。
    • URI,IRIが構文的に無効な場合は致命的なエラーである。(ただし、実装によってはリソースエラーと区別できない場合がある)
  • parse
    • "xml", "text"のみを指定可能であり、それ以外を指定することは致命的なエラーである。
    • parseを指定しない場合、暗黙に"xml"が指定されたとみなす。
    • "xml"を指定した場合、リソースをXMLとして解析し、Infosetにマージする。
    • "text"を指定した場合、リソースを文字情報項目としてInfosetにマージする。
  • xpointer
    • parse="xml"が指定されている場合、xpointer属性値をXPointerとみなして部分リソースを特定する。
    • parse="text"が指定されている場合にxpointerを指定することは致命的なエラーである。
    • xpointerを指定しない場合、リソース全体を取得すべきリソースとみなす。ただし、hrefが指定されなければならない。
    • xpointer属性値はURI参照ではないので、%は文字通り%であって、エスケープ用の特殊文字ではない。
  • encoding
  • accept
    • XIncludeプロセッサがHTTP経由でリソース取得を試みるとき、この属性の値をリクエストヘッダのAcceptの値として設定する。
    • #x20-#x7Eの範囲外の文字(つまりUS-ASCII外、もしくはC0,C1制御文字)を含む場合は致命的なエラーである。
  • accept-language
    • XIncludeプロセッサがHTTP経由でリソース取得を試みるとき、この属性の値をリクエストヘッダのAccept-Languageの値として設定する。
    • #x20-#x7Eの範囲外の文字を含む場合は致命的なエラーである。

大量の属性があるわけではないですが、NGな組み合わせがいくつもあります。

上記の属性以外の属性を付与しても構いませんが、接頭辞のない属性名は将来用に予約されています。XIncludeプロセッサはこれらを無視しなければなりません。

xi:include要素の子には1つだけxi:fallback要素を含めることができます。複数のxi:fallbackや、それ以外のXInclude名前空間に属する要素が現れた場合は致命的なエラーです。
XInclude名前空間に属さない要素や、処理命令、コメント、テキストなどは、自由に現れても構いませんが、XIncludeプロセッサには無視され、結果のInfosetにも現れません。

3.2 xi:fallback Element

xi:fallback要素は、xi:include要素の子として現れ、xi:includeがリソースエラーを発した場合、自身の子要素でxi:includeをまるごと置き換えることでフォールバックを提供します。
もしxi:fallbackを子に持たないxi:include要素でリソースエラーが発生した場合は、致命的なエラーです。

xi:fallbackxi:includeの子以外として文書に現れた場合は致命的なエラーです。また、xi:fallbackxi:include以外のXInclude名前空間の要素を含むことも致命的なエラーです。
逆に言うと、xi:fallbackxi:includeを含むことができるということですね。この中に含まれたxi:includeの扱いは後で言及があるのでしょう。

xi:fallbackには属性を付与できますが、現時点で特別な意味を持つ属性はないので、すべて無視されます。xi:include同様、接頭辞のない属性名はすべて予約されています。

xi:fallbackの内容は、それを含むxi:includeがリソースエラーを起こさない限り無視されます。無視される内容は、要素や属性の存在・欠如・内容によって引き起こされる見かけ上の致命的なエラーを報告してはなりません。

4 Processing Model

Infosetの変換の具体的な方法について述べられた章です。この仕様で最も大事な章と言えるでしょう。

まず変換の入力・出力についてですが、入力は"source infoset"、出力は"result infoset"と呼ばれます。result infosetは、source infosetとxi:include要素内のURI/IRIで識別されるリソースのInfosetをマージしたものとなります。
XIncludeにおいては、入出力はともに定義されたInfosetを持つことが前提なので、例えば複数の最上位要素ないしテキストを持つ整形式の外部実体などは、source infosetとしてもresult infosetとしても、この仕様で動作は定義されません。

続いても用語の定義です。xi:include要素で位置を特定されるInfosetは、"top-level included items"と呼ばれます。また、top-level included itemsとその属性、名前空間、子孫要素の集合は、"included items"と呼ばれます。
result infosetは本質的にsource infosetの複製であり、すべてのxi:include要素及びその子孫要素が、対応するincluded itemsに置換されたものであると言えます。

4.1 The Include Location

"include location"という用語が定義されています。これは、href属性値を4.1.1章のルールに従ってエスケープしてURI/IRIとして扱い、xi:xincludeの基底URIを用いて解決したものである、と定義されます。

もしhref属性が指定されなかったり、空文字だったりした場合、実装はこれをリソースエラーとして扱うことを選択できます。もしリソースエラーとして扱う場合は、そのようなリソースエラーが発生する条件を文書化すべきとされています。
3.1章では、現在の文書を指すといった記述がありましたが、仕様では例えば、parse="text"の場合にsource infosetのテキスト情報を持っていなかったり(Infosetのみが存在していて元の文書のテキスト表現が特定できないなど?)、parse="xml"であってもストリーム処理を行う都合、XPointerでの部分リソース検索ができなかったりといった、実装依存の都合がありうるため、という理由が述べられています。

4.1.1 Escaping of href attribute values

「仕様ではXML1.1のリソース識別子」と記述されていますが、該当箇所を見に行くと外部識別子を指していることがわかり、XML1.0でも同じものを指しています。
エスケープ処理については、要はURI/IRIの仕様に従ってエスケープせよという話です。XML仕様では可能な限り処理の後段でエスケープを実施せよ、としていましたが、XInclude仕様では、エスケープ処理を施したあとにURI/IRIとして解釈されると記載されています。

4.1.2 Using XInclude with Content Negotiation

あんまり理解できていませんが、HTTP経由でのリソースはコンテンツネゴシエーションによって、同じURIから得られるリソースがXMLであったとしても異なる形式を持つものであるかもしれない、とのことです。
これを可能な限り期待した形式でリソース解決できるようにするためには、accept, accept-languageを明示するようにするべきである、と述べられています。

ライブラリ側としては単にリースリゾルバにパラメータを投げつけるだけなので、文書作成者向けの情報ということになります。

4.2 Included Items when parse="xml"

parse="xml"が指定された場合、解決されたリソースはapplication/xmlの型が付与されたものとみなしてInfosetを構築します。
Infosetの構築方法は実装依存です。例えば、DTDスキーマによる検証が行われるか、などの点で差異が出るかもしれないと述べられています。

何らかの理由でリソースが利用できない場合、リソースエラーを引き起こします。リソースをXMLとみなしてwell-formedではなかった場合は、致命的なエラーを引き起こします。
注記で、このリソースエラーと致命的なエラーの区別は実装依存になりうると述べられています。「リソースがXMLではない」はリソースエラーですが、「リソースはXMLだがwell-formedではない」は致命的なエラーです。MIME型を調べることでこれらは区別可能かもしれませんが、そのような機能を持たなかったり、MIME型が提供されなかったりする場合は困難でしょう。

生成されたInfosetは、さらにその中に含むxi:include再帰的に展開することで、"acquired infoset"を生成します。もし文書内参照の場合は、source infosetがそのまま"acquired infoset"となります。
acquired infosetのうち、取り込まれる部分を"inclusion target"と呼びます。もしxpointer属性によって部分リソースのみを取得するのでない限り、inclusion targetには文書情報項目も含まれます。

xpointer属性について、最低限XPointer Frameworkと、XPointerの各種スキームのうち、elementスキームはサポートされなければなりません。他のスキームは任意です。XPointerのエラーはリソースエラーとして扱われます。
xpointerスキームのXPointerについて、もしacquired infosetに未展開実体参照情報項目が存在する場合、致命的なエラーです。これはxpointerスキームがInfoset仕様策定前にXPath1.0データモデルをベースに規定されたことに由来しており、そのデータモデルは、すべての実体参照が展開済であることを前提とするためであることが原因です。

top-level included itemsを、acquired infosetから導出する方法は、続く章で述べられます。

4.2.1 Document Information Items

inclusion targetが文書情報項目である場合は、その子要素の集合がtop-level included itemsになります。ただし、文書型宣言情報項目は除きます。

なお、文書要素の外側の空白の保持についての規定はありません。Infosetがそのような規定を持たないので、XInclude仕様としてもそれは規定しません。

4.2.2 Multiple Nodes

inclusion targetが複数ノードから構成されるとき、top-level included itemsはacquired infosetのうち、XPointerが参照するノードに対応する情報項目の集合をacquired inforsetにおける文書順でソートしたものです。

4.2.3 Range Locations

XPointerの評価結果によっては、inclusion targetが範囲を含むロケーションセットである場合もあります。

この場合は、各範囲の始点より後、終点より前に現れる情報項目の集合を重複なく、文書順で並べたものをtop-level included itemsをして扱います。

範囲が情報項目の子要素の一部分だけを含むようなものは、その含まれている部分だけを選択します。

4.2.4 Point Locations

inclusion targetであるロケーションセットが点の集合である場合、included itemsは空です。

4.2.5 Element, Comment, and Processing Instruction Information Items

inclusion targetが要素ノード、コメントノード、処理命令ノードであるなら、acquired infoset内の対応する要素情報項目、コメント情報項目、処理命令情報項目の集合がtop-level included itemsになります。

4.2.6 Attribute and Namespace Declaration Information Items

inclusion targetが属性ノード、名前空間ノードであるなら、致命的なエラーです。

4.2.7 Inclusion Loops

外部リソースを取り込む仕様であるあるなリソース循環の問題への対処の話です。

端的に言えば、同じ値のhrefを持つxi:includeもしくはその祖先を2回以上処理しようとしたときに致命的なエラーとなります。

parse="text"のリソースは単なるテキストなので、必ず処理できます。また、parse="xml"であり同じhrefを持つxi:includeを複数回処理する場合も、XPointerを用いてこれまでに処理したのと同じxi:includeを含まないよう回避できている限りは合法です。

4.3 Included Items when parse="text"

parse="text"を指定したxi:includeの処理方法についてです。

取得したリソースは、一連の文字情報項目に変換されます。

このとき、エンコーディングがわかっていないと適切に文字情報項目を構築できないかもしれません。
エンコーディングの決定は、

  • 利用可能な外部エンコーディング情報がある場合はそれを用いる
  • 外部エンコーディング情報がない場合、リソースのメディア型がRFC3023に基づくXMLメディア型であるとき、XML仕様の4.3.3章に規定される通り決定される
  • 上記2つとも当てはまらないとき、encoding属性が指定されているならばそれを用いる
  • 上記いずれも当てはまらない場合、UTF-8であるとみなす。

確定したエンコーディング方式に照らして、許可される範囲外のバイト列に遭遇した場合は致命的なエラーです。また、XML仕様で許可されない文字に遭遇した場合も致命的なエラーです。

リソースを正しくデコードできた場合、各文字はUCS文字としての文字コードを設定し、element content whitespaceプロパティをfalseに設定した文字情報項目として、top-level included itemsの1つとみなされます。

最初の文字がBOMであるとき、BOMは破棄されます。UTF-8, UTF-16, UTF-32ではBOMは解釈されるべきですが、UTF-16LE/BE, UTF-32-LE/BEではBOMは解釈されません。

4.4 Fallback Behavior

フォールバックの動作についてです。

すでに述べられている通り、xi:includeは、高々1個のxi:fallback要素を含むことで、xi:includeの処理で発生したリソースエラーに対するフォールバックを提供することができます。
フォールバックは、xi:fallbackの子要素に対してXInclude処理を施すことで行われます。このとき、xi:fallbackの親であるxi:include要素のparse属性の影響は受けません。parse="xml"が設定されていたとしてもテキストを含んでよいですし、parse="xml"が設定されていたとしてもマークアップを含んで構いません。

xi:fallbackに対するXInclude処理の結果であるInfosetは、そのままtop-level included itemsになります。

もしリソースエラーを発生させたxi:include要素の子として、xi:fallbackが1個以上、あるいは1個未満含まれている場合は、致命的なエラーです。

4.5 Creating the Result Infoset

result infosetの構成方法です。

result infosetはsource infosetのコピーであり、含まれているxi:include要素を順に処理したものです。
より具体的には、各xi:include要素の親である情報項目について、そのchildrenプロパティに含まれるxi:include要素に対応する情報項目を、XInclude処理によって得られたtop-level included itemsで置き換えます。

文書要素であるxi:includeについて、ゼロ個以上のコメント及び処理命令と、1つの要素からなる列以外のもので置換することは、致命的なエラーです。

各top-level included itemsの取り込み履歴は、(Infosetに対する)拡張プロパティである"include history"に格納されます。これは再帰的な取り込みレベルにおけるxi:include要素を示す要素情報項目のリストです。
"include history"プロパティがすでにtop-level included itemに存在する場合、xi:include要素情報項目がリストの先頭に追加され、まだ存在しないならxi:include要素情報項目を唯一の値として含むプロパティが追加されます。

included itemsは、未展開実体参照情報項目も含め、すべてresult infosetに表示されます。

文書内参照は、source infosetに対して解決されるため、処理順序は結果に影響を与えません。
より具体的に言えば、コピーされる前のInfosetに対して参照解決します。source infosetは処理を通じて変更されることはないので、文書内参照を解決した結果は、常に同じものになります。

また、XIncludeプロセッサは、ユーザの選択によってxml:base, xml:langの修正処理を抑制することが可能、とのことです。
具体的にどう修正するのかはここでは述べられていませんが、4.5.5章、4.5.6章に記述があるようです。

4.5.1 Unparsed Entities

included items自身もしくはその子孫に現れる属性の"reference"プロパティに現れる解析対象外実体情報項目は、既存のメンバと重複しない場合のみresult infosetの文書情報項目の"unparsed entities"プロパティに追加されます。

どのようなときに重複しているとみなされるかについてですが、以下のプロパティが同じであるときに重複とみなします。

  • name
  • system identifier
  • public identifier
  • declaration base URI
  • notation name
  • notation

あるいは、他の方法で検出してもよく、例えばシステム識別子と基底URIを解決した値で検出することもできます。

同じ名前を持つにもかかわらず、重複とみなされない解析対象外情報項目を含めることは致命的なエラーです。

4.5.2 Notations

解析対象外情報項目と同じで、included items及びその子孫の属性に現れた場合は、重複しないときのみresult infosetの文書情報項目の"notations"プロパティに追加されます。
同じ名前を持つにもかかわらず重複しないと判定された記法情報項目を含めることは、致命的なエラーです。

重複判定は、以下のプロパティで行います。

  • name
  • system identifier
  • public identifier
  • declaration base URI

4.5.3 references Property Fixup

IDREF/IDREFSの型を持つ属性の修正についての話です。
ごちゃごちゃと書いてありますが、要は文書の一部を切り出してきたこと、あるいは文書を混ぜたによるIDへの参照不整合が起こることがあるので、それを修正しなければならないという話です。

IDREF/IDREFSの型を持つ属性情報項目のreferencesプロパティは、参照先の要素情報項目のリストとなります。IDREFであれば1つしか含まれませんし、IDREFSであれば属性の正規化値のトークン数に等しい数の情報項目を含むリストであるはずです。result infosetに含まれる各属性情報項目について、この制約を満たすようにreferencesプロパティを修正してあげなければなりません。
ただし、妥当でない文書で起こりうるように、参照先のIDがないとか、逆に複数あるとか、そういった場合にはreferencesプロパティは値を持ちません。これはInfoset仕様と一貫しています。

4.5.4 Namespace Fixup

Infosetにおけるin-scope namespacesプロパティは、その要素に関連する名前空間情報項目をすべて保持しているので、取り込まれたInfosetにおいても名前空間に関する情報が失われることはありません。
ただし、namespace attributesプロパティの情報が完全ではないことがあります。これに対して、「XIncludeプロセッサが結果にnamespace attributesを公開することは推奨されない」と述べているのですが、具体的にどういうことを推奨しているのかはちょっとよくわかりません。とはいえ、やむを得ない場合はnamespace attributeプロパティの内容を、in-scope namespacesプロパティが示す情報に近似するように変更することも許可されているので、実装上困ることはおそらくないでしょう。

4.5.5 Base URI Fixup

取り込まれたInfosetについて、含まれる要素の基底URIは変化しません。
もしxi:includeの親と異なる基底URIを持つtop-level included itemsである要素については、xml:baseを追加する必要があります。既存なら置き換えます。

外見上はxml:base="..."を追加するだけですが、具体的にInfosetのプロパティとして何を追加しなければならないのか、仕様に詳細が書いてあります。まあ、見たまんまなので省略です。

4.5.6 Language Fixup

xml:langの修正についてです。

XML仕様としては、xml:langは引き継がれることが意図されていますが、Infoset仕様では特にxml:langの引き継ぎについての仕様を定めていません。
XInclude仕様としては、Infosetの要素情報項目の拡張プロパティとして、"language"プロパティを規定しています。Infosetの生成時、xml:langが自身か祖先要素に空の値以外で指定されている場合は、指定された値で設定されます。それ以外の場合は値を持ちません。

xi:includeの親と異なるlanguageプロパティを持つtop-level included itemsである要素については、languageプロパティを追加することで、xml:langの情報を引き継ぎます。
基底URIの場合と同様、既存のプロパティがあるなら上書きします。

詳しいパラメータは基底URI同様、仕様を見てください。

注釈で述べられていますが、xml:spaceについては特別な取り扱いをしません。
こっちもXML仕様では引き継がれることが意図されていたと思いますが、なぜなんですかね?理由はよくわかりませんでした。

4.5.7 Properties Preserved by the Infoset

Infoset仕様で規定された各種プロパティ及びXInclude仕様で規定された拡張プロパティは、特段の記述がない限り、XInclude処理の前後で変化せずに維持されます。

これらの範囲外の別仕様での拡張プロパティは、既定では破棄されることになっています。ただし、ユーザが選択する場合にはその限りではありません。
Xinclude処理によって拡張プロパティの情報は不正確になる可能性がありますが、その修正方法は仕様の規定外です。

5 Conformance

毎度おなじみの適合性の話です。

5.1 Markup Conformance

要素情報項目は、この仕様で定義されるインクルード要素(これが何を指しているかはよくわかりません)の構造条件を満たすとき、仕様に準拠します。

5.2 Application Conformance

アプリケーションは、以下の場合に仕様に準拠します。

  • XML1.0もしくはXML1.1とそれに対応する名前空間仕様、Infoset仕様、XML Base仕様、XPointer Framework仕様及びXPointer elementスキーム仕様をサポートすること
  • 致命的なエラーが発生した場合に処理を停止すること
  • 仕様で定義された必須条件(must)を遵守し、任意条件(should, may)のうち遵守することを選択したものについて規定された方法で処理すること
  • 仕様に記載されるすべての適合性制約に従ってマークアップ適合性テストを実施すること

なお、XPointer仕様のうち、xpointerスキーム仕様のサポートは必須ではありません。文書作成者は、すべてのXInclude実装で必ずxpointerスキームが使用可能であるとは仮定できないことに留意しなければなりません。

5.3 XML Information Set Conformance

この仕様はInfoset仕様に準拠します。正しい処理を可能にするには、入力であるInfosetに以下の情報項目が存在しなければなりません。

  • children, base URIプロパティを含む文書情報項目
  • namespace name, local name, children, attributes, base URI, parentプロパティを含む要素情報項目
  • namespace name, local name, normalized valueプロパティを持つ属性情報項目

XIncludeプロセッサは、以下の情報項目を出力であるInfosetに生成する可能性があります。

  • character code, element content whitespace, parentプロパティを含む文字情報項目

また、XIncludeプロセッサは、include historyプロパティでInfosetを拡張します。これは以下の情報項目に現れる可能性があります。

  • 要素情報項目
  • 処理命令情報項目
  • コメント情報項目
  • 文字情報項目

さらに、XIncludeプロセッサは、languageプロパティでInfosetを拡張します。これは要素情報項目に現れる可能性があります。

あとがき

そこまで長くない仕様でしたが、集中力が続かなくて読むのに時間がかかりました。最近は英文を読んでばかりなので本当に疲れてしまい、やはり自分は英文非対応だなと思わされます。

なんとなく、外部実体参照の置換と同じようなものだろうというイメージでしたが、だいたい合っているところもありつつ、ちゃんと違いがありました。
当初はSAXパーサにどうやって取り込もうかなあなどと考えていましたが、Infosetに作用するものだと考えると、解析の最中に取り込むのはちょっと違う気がしないでもないです。Infosetの情報項目をインクリメンタルにコールバックを通じて渡しているのだと考えるとおかしくない気がしないでもないですが、xi:include要素の子孫に自由に要素を含められるとなると、それをどうやってアプリケーションに渡すのかという問題が残ります。
さらに厄介なのはaccept, accept-language属性の存在です。EntityResolverのメソッドにそんなものを渡せる引数はありませんから、どうすればいいか非常に悩ましいです。

文書木を操作するAPIなら自然に実装できそうですが、accept, accept-languageをどうしようか問題は残ります。これに関しては専用のリソースリゾルバを作るしかないですかね。EntityResolverInputSourceを返すわけですが、InputSourceXML文書リソースの構築に特化してしまっているので、parse="text"を捌くのがちょっと面倒という問題もありますから、丁度いいといえば丁度いいのかもしれません。

libxml2やJavaAPIのインタフェースもチラッと覗いてみましたが、libxml2ではSAXパーサに組み込むオプションや関数はなさそうでした。オプションはあるにはあるのですが、コメントを信用するなら、たぶんSAXパーサには作用しません。
JavaAPIでも、やはりSAXパーサにそのようなオプションはありません。DocumentBuilderFactoryにはそういうオプションがあるので、やはり文書木操作APIで使うことが想定されているのでしょう。

とはいえ、ストリーム処理したいという需要もある気がするんですよね。まあxi:includeの要素内容をどうするのか問題はやはりあるので難しいでしょうが。
それは将来気が向いたら考えることにしようかと思います。

次はどうしようか迷っていますが、C14NかXML SchemaXSLTのどれかだと思います。そもそも、まだ読んでいなくて、かつ当初読む想定だった仕様って、忘れていなければこれで全部だと思うんですよね。(DOMやXLinkは今の所読む気はありません)
いまはXPath 2.0とかXQuery 1.0に興味があります。そのためにはXML Schemaをなんとかしないといけないので、XML Schemaに手を付けようかなと思うのですが、どう考えてもボリュームが小さそうなC14Nが延々と後回しになっていくのはどうなのかという懸念もあります。悩ましいですね。たぶん気分で決まります。

XMLのお勉強その13 - XPointer Frameworkなど

前回

XMLのお勉強その12 - RELAX NG - 競プロ備忘録

読んでいく仕様書

原文:

和文:たぶんない

XSLTを読んでいくつもりでしたが、v1.0でもだいぶ長くて読み終わる気がせず…
XPointerをチラッと見てみるとこっちのほうが簡単に読み終わりそうだったので、先にこっちからやります。たぶんXIncludeまで読み終わるのを含めても、XSLTより楽だと思います。

XPointer仕様というのはどうも4つのパートに分かれているようで、それが原文として示した4つです。
上3つはRecommendationになっていますが、最後のやつはまだWorking Draftです。2002年の仕様なので、もう金輪際Recommendationになることはないのでしょう…
そんな事情で最後のは読まなくてもいいかなと思ったのですが、まあそんなめっちゃ長い仕様というわけでもないので、とりあえず読んでおくことにします。

XPointerが何かといえば、XML文書に対するURIフラグメント識別子の意味付けをするための仕様だと思います。
XLink仕様のベースになる技術だと思いますが、XIncludeでもXPointerを指定して文書を取り込んだりできるように活用されているっぽいです。
とはいえ、現代でもこの仕様をサポートしているものって何があるんですかね?XLinkはかろうじてSVGの中にひっそり残っているのを見るくらいのイメージですが…一部の仕様がWorking Draftとして放置されている現状を見ると、あんまり活用されていないのでしょう。

本編

XPointer Framework

XPointerというものの概要や汎用的な構文を示した文書です。この文書の内容を踏まえた上で、具体的なスキームの意味付けを他の3つの仕様で与えています。

1 Introduction

XML Pointer Language (XPointer)とは何かという話で、冒頭でも少し述べましたが、XML文書に対するフラグメント識別子の意味付けを与えるものです。
より具体的にはtext/xml, application/xml, text/xml-external-parsed-entity, application/xml-external-parsed-entityのいずれかをMIME型として持つようなリソースに対するフラグメント識別子の意味付けを与えるものです。これら以外のXML文書型に対する意味付けはそれぞれの型の定義においてされるものですが、この仕様の成果を活かすことは可能でしょう。

1.1 Notation

記法の定義です。

助動詞の用法はいつものやつです。(RFC2119準拠)

EBNFの記法については、XML仕様で示すとおりです。

1.2 Terminology

次に用語の定義です。

"pointer"とは、この仕様における意味的・構文的な定義に従った文字列のことです。この記事内では「ポインタ」と記します。

"pointer part"とは、スキーム名とこの仕様に適合したポインタデータを提供するポインタの部分です。XPointerプロセッサはpointer partを評価することで、ゼロ個以上のXMLリソース内の部分リソースを識別します。この記事内では「ポインタパート」と記します。

"scheme"とは、この仕様で定義された特定のポインタデータのフォーマットであり、また名前を持ちます。この記事内では「スキーム」と記します。

"XPointer processor"とは、この仕様で定義された動作によってポインタを適用することで、XMLリソース内の部分リソースを識別するソフトウェアです。この記事内では「XPointerプロセッサ」と記します。

"application"とは、XML部分リソースへのアクセスにXPointerプロセッサを利用したり組み込んだりするソフトウェアです。XIncludeプロセッサなどが例となります。この記事内では「アプリケーション」と記します。

"error"とは、この仕様で定義された構文への違反や、部分リソース識別の失敗です。この記事内では「エラー」と記します。

"namespace binding context"とは、XML名前空間名に関連する接頭辞の束縛です。

2 Conformance

いつもは最後に現れる章ですが、この仕様では早くもここで現れます。

まず、この仕様はフレームワークを定義するものであって、XPointerプロセッサの最小適合レベルを定義するものではない、と述べています。したがって、ここでいう適合性は、フレームワーク部分に対する適合性を指します。

XPointerプロセッサは、フラグメント識別子に対するデコードとエスケープ外しができることに依存しています。

また、XPointerプロセッサの動作は、XMLリソースから特定の情報が取得できることに依存しています。
具体的には、Infoset仕様で定義された以下の情報項目及びプロパティが必要です。

  • 文書情報項目の"document element"プロパティ
  • 要素情報項目の"attributes"プロパティ、"children"プロパティ
  • 属性情報項目の"attribute type"プロパティ、"normalized value"プロパティ

また、XML Schemaの処理により生成されたPSVIから活用可能な、要素情報項目、属性情報項目のプロパティも挙げられています。
これらが利用可能な場合、XPointerプロセッサは短縮ポインタ記法の処理に活用可能ですが、XPointerプロセッサは必ず処理することまでは要求されません。

XPointerプロセッサはこのフレームワークとXPointerの最小適合レベルを定義するその他の仕様へ最低限適合しなくてはならず、追加のXPointerスキーム仕様に適合していても良いです。
また、XPointerプロセッサは、具体的にどのスキームに適合しているのかをドキュメントで明示しなければなりません。XPointerの処理に依存する仕様は、何のスキームが必要であるのかを明示するべきです。

適合するXPointerプロセッサは、フレームワークに関するエラーをアプリケーションに報告しなくてはなりません。報告を受けたアプリケーションが処理を打ち切るか回復するかは自由です。

3 Language and Processing

XPointerフレームワークと、そのフレームワークにおけるXPointerプロセッサの動作の説明です。

XPointerプロセッサは、入力としてXMLリソース及びポインタとして使用する文字列を取ります。ポインタ文字列は例えば、エスケープ外しされたフラグメント識別子などでしょう。
そして、ポインタ文字列をXML文書に対するポインタとして評価し、その部分リソースを識別子、出力します。エラーに遭遇した場合は、エラーを報告します。

3.1 Syntax

どのような文字列がポインタとして識別されるのかという、構文レベルの説明です。ここで定義された構文に従わない文字列をポインタとして評価することはエラーです。

(いつものことですが、EBNFはここには書かないので、気になる方は仕様を読みにいってください。)

ポインタは、短縮記法と、スキームに基づく記法の2通りがあるようです。
短縮記法は単にNCNameを記述するだけです。そうではない場合、ポインタパートを空白区切りで記述します。(ここでいう空白とは、XML仕様における空白文字なので、改行なども含む)
ポインタパートはスキーム名を先頭に置き、カッコでスキームデータを囲みます。カッコは対応が取れている必要があります。

スキームデータにはカッコが現れる可能性がありますが、その場合は^でエスケープします。つまり、^(, ^)などと記述すれば、データにカッコを含められます。
では^を記述したいときはどうするかというと、^^とします。
これ以外で^が現れるのは構文エラーです。

3.2 Shorthand Pointer

短縮記法のポインタの説明です。

短縮記法のポインタは、構文としては上記の通り、単にNCNameを記述するだけのものです。

このポインタは、指定されたNCNameをIDとして識別できる高々1つの要素を指します。複数の要素がマッチするなら、そのうち文書順で最初のものが選択されます。
具体的には、

  1. 要素情報項目が"attributes"プロパティの中に、"schema-determined ID"である属性情報項目を持つ場合、その属性情報項目の"schema normalized value"プロパティの値で識別する
  2. 要素情報項目が"children"プロパティの中に、"schema-determined ID"である要素情報項目を持つ場合、その要素情報項目の"schema normalized value"プロパティの値で識別する
  3. 要素情報項目が"attributes"プロパティの中に、"DTD-determined ID"である属性情報項目を持つ場合、その属性情報項目の"normalized value"プロパティの値で識別する
  4. 外部で決定された"externaly-determined ID"である値がある場合、その値で識別する

という順序で識別します。

1, 2, 3は、リソースに適用したDTDXML Schemaがなければ識別できませんが、2章のところでチラッと話が出てきたように、それはそれで構いません。
また、スキーマDTDで別の値をIDと指定しているなど、状況によっては同じポインタが異なる要素を指す可能性もあります。

xml:idの扱いが気になるところですが、DTDでIDと指定されたように振る舞う、と記載されていたと思うので、3.のルールに従うと考えればいいでしょうか。まあ4.のルールに従うと考えてもさほど問題はないでしょうが。

もしマッチする要素がない場合、そのポインタはエラーです。

さて、上記の"schema-determined ID", "DTD-determined ID", "externally-determined ID"については、字面で想像が付きそうな用語ではあるのですが、ちゃんと定義があります。

まず「要素情報項目および属性情報項目が"schema-determined ID"である」とは、要素情報項目および属性情報項目が

  1. "member type definition"、もしくは"type definition"を持ち、その値の"name"がIDに等しく、"target namespace"がhttp://www.w3.org/2001/XMLSchemaに等しい
  2. "base type definition"を持ち、その値が"name"と"target namespace"を持つ
  3. "base type definition"を持ち、その値が"base type definition"を持ち、その値が"name"と"target namespace"を持ち…といった感じで再帰的に"base type definition"を発見できる
  4. IDに等しい"type definition name"とhttp://www.w3.org/2001/XMLSchemaに等しい"type definition namespace"を持つ
  5. IDに等しい"member type definition name"とhttp://www.w3.org/2001/XMLSchemaに等しい"member type definition namespace"を持つ

のいずれかを満たすことです。
まだXML Schema仕様を読んでいないのであんまりよくわからないですね…将来の自分に期待します。

次に「属性情報項目が"DTD-determined ID"である」とは、属性情報項目が"type definition"プロパティを持ち、その値がIDに等しいことです。
要はID属性を示す属性リスト宣言があることと同義でしょう。

最後に"externally-determined ID"とは、この仕様で定めた範囲外で、アプリケーションが定めた規則で要素を識別する文字列のことです。

3.3 Scheme-Based Pointer

スキームに基づく記法のポインタの説明です。

構文についての説明は先述のとおりですが、0個以上の空白で区切られたポインタパートが並んだものであり、それぞれのパートがスキーム名とカッコに囲まれたデータを持つ、といったものです。

0個以上の空白で区切られた各ポインタパートについて、XPointerプロセッサは、前から後ろに評価します。原文では"left-to-right order"と書いてありますが、まあこういう意味ですよね。右から左に書く言語の場合はどうするんだ、的な話はあるでしょうが…
評価するポインタパートについて、サポート外のスキームを用いるものや、いかなる部分リソースも指さないものについては、無視して次のパートの評価に進みます。
もしある1つ以上の部分リソースを指すものが見つかった場合は、それが全体の結果となります。そのようなものがない場合は、エラーです。

仕様では以下の例が出ています。

#xpointer(id('boy-blue')/horn[1])element(boy-blue/3)

これはxpointerスキームとelementスキームのポインタパートが1つずつ順に並んだポインタです。
まずxpointerスキームを用いる方を評価します。もしxpointerスキームがサポート外であったり、評価の結果部分リソースが見つからない場合は、elementスキームを用いる方を評価します。こちらで部分リソースが見つかればそれが結果となりますし、elementスキームもサポート対象外であったり、部分リソースが見つからない場合は、全体としてエラーが結果となります。
もしxpointerスキームがサポートされており、評価の結果部分リソースが発見できた場合は、これが全体の結果となり、elementスキームを用いるポインタパートは評価されません。

スキーム名はQNameなので、名前空間名と局所名で識別されます。QNameの接頭辞に関連付けられた名前空間名は、文脈上の名前空間束縛から導出します。
もし文脈上の名前空間束縛から接頭辞に関連付けられた名前空間名が導出できなかったり、あるいは名前空間名と局所名のペアがXPointerプロセッサがサポートするスキーム名ではないとき、そのスキーム名をもつポインタパートは無視されます。

この仕様では、すべての無修飾であるスキーム名が予約されています。もしこのフレームワークを用いて他の仕様がスキームを策定する場合は、名前空間名によって適用すべき仕様を識別できます。

3.4 Namespace Binding Context

各スキームの仕様は、名前空間を特定の接頭辞に束縛する方法を提供する場合があります。例えば、xmlnsスキームなどはそのような機能を提供します。

このようなスキームが束縛した接頭辞は、特に仕様で定めがない限り、後続するポインタパートでも名前空間に束縛されたままとなります。そしてこれが、文脈上の名前空間束縛として機能します。

仕様では以下の例が示されています。

#xmlns(img=http://example.org/image)img:rect(10,10,50,50)

上の例では、1つ目のポインタパートでxmlnsスキームの機能によってimghttp://example.org/imageを束縛しています。
xmlnsスキームでは特にその場限りの名前空間束縛となるような決まりはないので、後続のポインタパートでもimghttp://example.org/imageに束縛されたままです。
したがって、2つ目のポインタパートにおけるimg:rectは、名前空間名がhttp://example.org/imageであり、局所名がrectであるような名前として識別されることになります。

初期状態での文脈上の名前空間束縛は、xmlhttp://www.w3.org/XML/1998/namespaceの関連付けのみであり、名前空間仕様と一貫しています。
また、

  • xmlhttp://www.w3.org/XML/1998/namespace以外に束縛してはならない
  • http://www.w3.org/XML/1998/namespacexml以外を束縛してはならない
  • xmlnsはいかなる名前空間にも束縛されてはならない
  • http://www.w3.org/2000/xmlns/はいかなる接頭辞も束縛してはならない
  • (x|X)(m|M)(l|L)のパターンに当てはまる文字列から始まる接頭辞は予約されており、XML及びその関連仕様を除いていかなる名前空間にも束縛されないべきである

といった点も、名前空間仕様と一貫しています。(xmlnshttp://www.w3.org/2000/xmlns/に束縛されているべきな気もしますが、古い名前空間仕様しかなかった頃の仕様だと思うので、その点ではやはり一貫しているというべきでしょう)

4 Character Escaping

XPointerで使用される文字セットはUnicodeですが、XML文書などに出現するURI, IRIのフラグメント識別子として使用されることを想定しているため、必要に応じてエスケープされなければならないかもしれません。
この章では、エスケープが必要になる場面の説明や例が示されています。

4.1 Escaping Contexts

以下のようなそれぞれの文脈で、固有の方法でエスケープが必要となります。

  1. XPointerの構文上のエスケープ
  2. 3.1章で明示されたとおり、データに出現する丸括弧(, )とサーカムフレックス^の3つの文字は、それぞれ^でエスケープしなければなりません。
  3. IRIで予約された文字のエスケープとエンコーディング
  4. IRIでもパーセントエンコーディング機能があるので、%はエスケープが必要です。その他の文字もエスケープして良いですが、推奨はされません。
  5. エスケープする文字はまずUTF-8の形式に変換し、その後URIの方法に従ってパーセントエンコーディングされます。
  6. URIで予約された文字のエスケープとエンコーディング
  7. URIの場合、非ASCII文字や、RFC2396の2.4章で除外された文字をエスケープする必要があります。(ただし、%, #とRFC2732で再許可された[, ]は除く)
  8. エスケープの方法はIRIと同じで、まず対象の文字をUTF-8形式に変換し、パーセントエンコーディングします。
  9. XMLのエスケープ
  10. XML文書や外部一般解析対象実体の構文上、直接現れることのできない文字がポインタに含まれる場合があります。このような文字は、文字参照や、必要に応じて実体参照によるエスケープが必要です。
  11. (IRIではなく)URIXML文書内に配置することは推奨されません。

XPointerプロセッサが自ら行うエスケープ外しは1.のXPointerの構文上のエスケープに対するものだけですので、それ以外のエスケープ外しはアプリケーション側で行う必要があります。

この頃はIRIの仕様がまだまとまっていなかった頃のようで、IRI仕様への導線がありません。
今であれば、URIがRFC3986を参照すべきであるように、IRIについてはRFC3987を参照すべきなのかなと思います。

4.2 Examples of Escaping

エスケープの例です。ダラダラここに書いてもしょうもないので、省略します。

XPointer element() Scheme

続いてはelementスキームのポインタの仕様です。

これが何かといえば、短縮記法のポインタと同じようにIDで選択した要素からの相対位置で要素を選択するポインタです。
非常にシンプルな機能しか持たないので、仕様書もこれまで見てきたものの中でトップレベルに短いです。

1 Introduction

取り立てて述べる点はないですが、これまで見てきたXPointer Frameworkの内容を用いるという話が書いてあります。

2 Conformance

この仕様はXPointer Frameworkに依存しており、またInfosetと必要に応じてXML Schemaから情報を抽出できる必要があります。

適合するXPointerプロセッサは、この仕様が定義したelementスキームのポインタの挙動に従わなければなりません。

3 Language and Processing

elementスキームのポインタの構文及び意味を述べた章です。

スキーム名はelementです。データとして何を含むかは仕様書にEBNFで記述されていますが、高々1つのNCNameに、スラッシュと1以上の数値をいくつか連ねたものとなっています。

データに含まれるNCNameは、フレームワークで示されていた短縮記法と同じ方法で要素を選択するものです。そのため、elementスキームはXML Schemaの情報を活用する可能性があるわけです。
スラッシュと数値Nの組は、その前に特定された要素のN番目の子(1-basedなので、最初の子が1番目)を指します。だいたいXPathのようなものを思い浮かべればイメージが合うはずです。

仕様では3つの例が示されています。

element(intro)
element(/1/2)
element(intro/3/1)

1つ目は、'intro'というIDで識別される要素を、短縮記法のポインタと同じ方法で選択するポインタです。
2つ目は、文書の1番目の最上位要素(整形式のXML文書であれば、文書要素のこと)の2番目の子を選択するポインタです。
3つ目は、'intro'というIDで識別される要素と、短縮記法のポインタと同じ方法で選択し、その3番目の子の1番目の子を選択するポインタです。

以下の文書で言えば、それぞれe1, e6, e5という名前の要素を選択することになるでしょうか。

<root>
    <e1 xml:id="intro">
        <e2 />
        <e3 />
        <e4>
            <e5 />
        </e4>
    </e1>
    <e6 />
</root>

選択すべき要素がなかった場合は、単にこのポインタパートによって識別される部分リソースがない状態となります。エラーではありません。
ポインタ全体としてリソースが発見できなかったときに初めてエラーになるだけなので、この仕様との一貫性を改めて述べたということでしょう。

また、このスキームは文脈上の名前空間束縛を用いません。これまで述べた通りIDで要素を識別するので、名前空間の情報は必要ないわけです。

XPointer xmlns() Scheme

続いて、xmlnsスキームの説明です。

この仕様もelementスキームに負けず劣らずの短さで、提供する機能も至ってシンプルです。
何の機能を提供するスキームなのかというと、文脈上の名前空間束縛を追加するためのスキームです。このスキームのポインタパート自体はいかなる部分リソースの識別・選択もせず、ただ名前空間束縛のリストに新たな関連付けの追加だけを行います。

1章、2章はだいたいelementスキームと同じことを述べているだけなので、省略します。

3 Language and Processing

xmlnsスキームのポインタの構文及び意味を述べた章です。

構文はいたってシンプルで、NCNameとエスケープされた名前空間名を(任意に空白で区切って)=で結ぶだけです。

意味としては、左辺のNCNameを名前空間接頭辞、右辺を名前空間名として、左辺の名前空間接頭辞を右辺の名前空間名で識別される名前空間に束縛します。
このスキームで関連付けられた接頭辞と名前空間名は文脈上の名前空間束縛に追加されます。もし指定された接頭辞がすでに文脈上の名前空間束縛に存在する場合は上書きします。

このスキームがやることは冒頭で述べたとおり、リソースの識別も選択もしませんが、文脈上の名前空間束縛に変化を与えます。
ただし、フレームワークで述べられていたとおり、xml, xmlnsの2つの接頭辞及びhttp://www.w3.org/XML/1998/namespace, http://www.w3.org/2000/xmlns/の2つの名前空間について、決まった組み合わせ以外での関連付けを与えることができません。これらの接頭辞や名前空間に対するxmlnsスキームのポインタパートの評価は、文脈上の名前空間束縛になんら変化を与えない、と述べられています。つまり、エラーではありません。

仕様では以下の例が挙げられています。

xmlns(c=http://example.org/customer) xmlns(p=http://example.org/personal-info)
xpointer(/c:customer/p:name)

1行目の2つのxmlnsスキームのポインタパートは、c, pの2つの接頭辞を、それぞれhttp://example.org/customer, http://example.org/personal-infoの2つの名前空間で束縛しています。
2行目のxpointerスキームのポインタを評価する時点では、文脈上の名前空間束縛にc, pの2つの接頭辞が含まれているので、データに含まれているc:customer, p:nameの2つのQNameを、名前空間名と局所名の組として問題なく認識することができます。

XPointer xpointer() Scheme

最後はxpointerスキームの説明です。

たぶんこれが一番の目玉スキームだと思うのですが、2002年にWorking Draftになって以来、2025年現在もWorking Draftのままです。なので、あくまでも仕様ではなくて参考文書扱いだと思います。
とはいえ、23年も更新がないので、実質的にはこれが最終版で、ある意味安定しているとも言えるのかもしれません(?)

このスキームは、簡単にいえばXPathで部分リソースを識別して返す機能を提供するものです。
ただ、XPointer仕様でのXPathは(XSLT仕様でのそれと同じように)独自に拡張されていて、文書内の点や範囲で部分リソースを識別することができます。実装は非常に面倒くさそうです…そのせいで標準化が進まなかったという背景があるのかもわかりませんが、これをちゃんと正しく実装しているライブラリはほぼないらしいです。libxml2ですら匙を投げたのか、最新版ではその辺りのXPath拡張は削除されています。まあ正式に仕様の固まっていない機能を実装するメリットがないというのもあるでしょうが。

xpointerスキームの仕様は前2つと比べると幾分か長いです。とはいえそれでもまだ良心的な量ではあるように見えます。

1 Introduction

上で少し述べた、xpointerスキームとはなんぞやという話です。
XPathを文字列や点、範囲でアドレッシングできるように拡張した言語によってXML文書の部分リソースを識別するもので、どうやらDOM Level 2のRange仕様に影響を受けているようです。

elementスキームや短縮記法のポインタでは、要素がIDによって識別可能な必要がありましたが、xpointerスキームではXPathの記法によってノードを検査して識別するので、IDによって識別可能な必要はありませんし、なんなら要素ではなくて文字列や属性のみを参照することもできるでしょう。
その点で、他のスキームより段違いに強力な表現力をもつスキームであると言えます。

1.1, 1.2章はそんな重要なことを述べているわけでもないので、省略です。

2 Terms and Concepts

xpointerスキームの理解に必要ないくつかの用語と概念の説明です。

導入される用語と概念は4つあります。

まず"point"とは、XML Infoset内の、内容や子を持たない位置のことです。隣接する2つのノードの間、テキストノードの特定の文字の直後、などがその例です。この記事では「点」と呼びます。
次に"range"とは、一対の点の間のすべてのXML Infosetの情報の内容の識別ことです。この記事では「範囲」と呼びます。

点と範囲を用いて、"location"と"location-set"の2つの概念が定義されます。

"location"とは、XPathのノードに、点と範囲を追加して一般化したものです。この記事では「ロケーション」と呼びます。
"location-set"とは、locationの順序なしリストです。点と範囲を含みうる点を除けば、XPathのノードセットと同じです。この記事では「ロケーションセット」と呼びます。

XPathのノードは軸に応じた順序付けがありましたが、ロケーションにも軸に応じた順序付けがあります。これは後で定義されます。

3 Conformance

適合性の話ですが、この仕様の他、XPointer Framework, xmlnsスキーム、XPath仕様のそれぞれに準拠しなければならないことと、xpointerスキームのデータは仕様で定義されたエラーを発生させない場合に準拠するということの2点が述べられています。

また、もしこの仕様で定義されるオブジェクトの名前空間を参照する必要がある場合、http://www.w3.org/2001/05/XPointerを使わなければなりません。
なんか不自然な数字が入っているのはWorking Draftだからなんですかね?Recommendationになったタイミングで変更されるのかもしれませんが、まあ金輪際そんなことはないでしょうから、心配することはないでしょう。

4 Language and Processing

ここからが本筋の内容です。

冒頭色々書いてありますが、結局の所XPathの処理の流れを改めて述べているに過ぎません。なので、すぐ4.1章から見ていきます。

4.1 Syntax

スキームデータの構文についてですが、単にXPath式である、というだけです。ただし、このあとの章で一部拡張が加えられる箇所があります。

構文に誤りがある場合は、エラーであり、エラーを含むポインタパートの処理は失敗します。

4.2 Additions to XPath Terms and Concepts

XPointer仕様において拡張されたXPathの機能や概念の話です。

詳細は後で述べられるのでそんなに詳しく見る必要はないのですが、まとめると

  • ノード、ノード型、ノードセットはそれぞれ、ロケーション、ロケーション型、ロケーションセットに拡張される
  • ロケーション型として、点("point")、範囲("range")が追加される
  • ルートノードの子ノードが文書要素の1つとは限らなくなる
  • 評価文脈の確立ルールが変わる
  • 新しい関数がいくつか追加される(詳細は後述)

と言った感じです。

ルートノードの子ノードの個数については、外部一般解析対象実体に対してもXPointerを適用する場合に発生する要請です。
実装上はDocumentFragmentで束ねて特別扱いしてやるとか、若干工夫が必要になるかもしれません。

4.3 Evaluation Context Initialization

まずはXPathの評価文脈の初期化の話です。基本的にはXPath一般での初期化方法と同じですが、ロケーションへの一般化によって、若干の差が出ます。

評価文脈は、以下のように初期化されます。

  • 文脈ロケーション(初期値は文書か外部一般解析対象実体のルートノード)
  • 非ゼロ正整数である文脈位置(初期値は1)
  • 非ゼロ正整数である文脈サイズ(初期値は1)
  • 変数束縛の集合(初期値は空集合
  • 関数ライブラリ(初期値はXPath及びXPointer仕様の定義する関数のみを含むライブラリ)
  • 文脈上の名前空間束縛(初期値はXPointer Frameworkで定義されたプリセット(つまりxml)とxmlnsスキームで追加された名前空間束縛の集合)
  • 使用できるなら、origin関数及びhere関数が特定するロケーションのプロパティ

変数束縛の集合について、xpointerスキームにおいて変数束縛を追加する方法はないので、常に空です。
したがって、スキームデータであるXPathが変数を参照した場合、直ちにエラーです。

関数ライブラリについて、変数同様、関数を追加する方法はないので、定義済の関数以外を使おうとすればエラーです。

orign, hereの両関数の話はまた後で出てくると思うので、ここではスキップです。

4.4 The point and range Location Types

拡張された(XPath仕様でノード型には含まれない)ロケーション型である"point", "range"の話です。

DOM Level 2との差異についてですが、DOM Level 2がUTF-16に基づくのに対し、XPathおよびxpointerスキームはUCSに基づきます。
Rustでは自然な扱いなので嬉しいですね。DOMはUTF-16なので鬱陶しいです。Javaにベッタリな仕様なので致し方なしですが。

点と範囲は、文脈ロケーションとして用いることができ、[]演算子を用いてロケーションセットからロケーションを選択可能です。
紛らわしいですが、「文脈位置」と「文脈ロケーション」は違います。そのためにわざわざ"position"と"location"(また"point"も)を使い分けているということでもあります。「文脈位置」とはロケーションセットの中で文脈ロケーションが何番目のロケーションであるかを示す正整数で、「文脈ロケーション」とは本来のXPathの評価文脈における「文脈ノード」に対応するものです。

そして、点と範囲に適用可能な関数として、range-toが説明されています。
この関数は、文脈位置の開始点を起点とし、引数で指定したロケーションの終点を終点とするような範囲を返します。

local-name, namespace-uri, nameは、文書順で最初のロケーションに適用します。ノードでもあるような最初のロケーションに対してではありません。
と書いてはみたものの、意味がよくわかりません。たぶん、引数のロケーションセットについて、ノードでもあるようなロケーションを探索する必要はなくて、単に一番最初のロケーションに対して実行すればいいということが言いたいのだろうと理解しました。

4.4.1 Definition of Point Location

まずは点("point")型のロケーションの説明です。

点は、以下の2つのデータから構成されます。

  1. コンテナノード
  2. インデックス

「コンテナノード」とは、点を直接含むノードのことです。と言われてもこの時点では何のこっちゃという感じですが、先に4.5章の図を見ておくとわかりやすいです。
ある1つ点は、いずれかのノードの子ノードリストを構成するノードの直前、もしくは直後に存在するか、テキストノードであるならデータである文字列を構成する文字の直前、直後に存在します。これを直接含むノードということなので、ノードNの前後を指す点ならノードNの親ノードPがコンテナノードですし、文字の前後を指す点であれば、その文字を含む文字列を保持するテキストノードがコンテナノードです。

「インデックス」は文字通りの意味ですが、コンテナノードが直接含む前から何番目の点を指すのかを示す非負整数値です。
XPathにおけるノードセットのインデックスや部分文字列を示すインデックスは1-basedでしたが、点を構成するインデックスは0-basedです。0番目の点とは、最初の子ノード(テキストノードなら最初の文字)の直前を示す点です。そこから順にN番目のノードの直後を示す点がN番目の点として定義されます。
イテレータの考え方とかと似ているので、プログラマ的には馴染みのある数え方になっていると思います。

2つの点は、コンテナノードとインデックスが等しいときに等しいとみなされます。

そして、点に対する名前、文字列値、軸については、以下の通り定義されます。

  • 展開名は持たない
  • 文字列値は空である
  • child, descendant, preceding-sibling, following-sibling, preceding, following, attribute, namespace軸はすべて空である
  • descendant-of-self, self軸は点自身のみを含む
  • parent軸はコンテナノードのみを含む
  • ancestor軸はコンテナノードとその祖先を含む
  • ancestor-or-selfは自身、コンテナノード、コンテナノードの祖先を含む
4.4.2 Definition of Range Location

続いて範囲("range")型のロケーションの説明です。

2章でも少し述べられていたとおり、範囲とは2つの点からなります。1つは始点("start point")、もう1つは終点("end point")と呼ばれます。
範囲は単なるノードや文字のリストとは異なり、ノードの一部のみを含んでも構いませんが、始点と終点が同じ文書に現れなければならず、また始点と終点が文書順で逆転してもいけません。
始点と終点が同じ点なのは構いません。このような2点からなる範囲は折りたたまれた範囲("collapsed range")と呼ばれます。(適切な和訳がわからなくてDeepLの和訳をそのまま採用したのですが、あんまりしっくり来ていません…)

始点、終点のコンテナノードについて、片方が要素、テキスト、ルート以外の型のノードなら、もう片方も同じ型のコンテナノードを持たなければなりません。
属性や処理命令の途中から始まって、テキストノードの途中で止まる、といった範囲は違法です。

最後に、範囲に対する名前、文字列値、軸についての定義です。

  • 展開名は持たない
  • 文字列値は始点から終点までに存在するテキストノードを構成する文字からなる文字列である
  • 軸の含むノードは、始点に軸を適用したときに含む点と同じである
4.4.3 Covering Ranges for All Location Types

「カバー範囲」という概念の説明です。

「カバー範囲」とは、あるロケーションを完全に覆う範囲のことです。といってもピンと来ないのですが、各ロケーション型に対するカバー範囲の定義を見ると理解しやすいです。
各ロケーション型に対するカバー範囲の特定方法は以下のとおりです。

  • 範囲の場合、その範囲そのものがカバー範囲である
  • 点の場合、始点と終点がその点である範囲がカバー範囲である(つまりcollapsed rangeになります)
  • 要素、テキスト、コメント、処理命令の各ノード型の場合、親ノードを始点と終点のコンテナノードとし、始点のインデックスは先行兄弟ノードの数、終点のインデックスはそれより1大きい数である
  • ルートノードの場合、自身を始点及び終点のコンテナノードとし、始点のインデックスは0、終点のインデックスはルートノードの子ノードの個数である
  • 属性、名前空間の各ノード型の場合、自身を始点及び終点のコンテナノードとし、始点のインデックスは0、終点のインデックスは値の文字列長である

XPath式の中では、ロケーションに対してcovering-range関数を呼び出すことで、そのロケーションのカバー範囲を特定できます。

4.4.4 Tests for point and range Locations

XPointer仕様がXPathに施すいくつかの拡張の1つで、ノードテストで選択できるノード型を拡張します。

XPath仕様としては、ノード型でノードをフィルタする方法としてnode, comment, text processing-instructionの4つの疑似関数?的なものを使えるように用意してくれていますが、XPointerではさらに、pointrangeの2つを追加しています。
それぞれ、任意の点と範囲を選択できます。

4.4.5 Document order

XPathのノード型には軸に応じた文書順序が存在しましたが、点や範囲にも前後関係が定義されます。

範囲と範囲の前後関係は点の前後関係から定義されます。
まず始点が異なる場合は、始点の前後関係がそのまま範囲の前後関係となります。始点が等しい場合は、終点の前後関係が範囲の前後関係となります。終点も等しい場合、2つの範囲は等しいとされます。

そして点と点の間の前後関係についてですが、簡単にいえばノードの前後関係とほぼ同じで、文書の外見上の前後関係とだいたい一致します。とはいえ、厳密な定義もちゃんと書いてあるので、読んでいきます。
まず点の記法として、(正確な記法は仕様を読んでほしいのですが)elementスキームのような感じで/区切りの数値を並べ、最後に.と数値を記述するというものを導入します。例えば、ルートノードの1番目の子の2番めの子であるノードが直接含む(すなわちそのノードがコンテナノードである)1番目の点は、point(1/2.1)といった感じで記述します。
このとき、以下のように比較します。

  1. 2つの点を示すパスについて、最上位コンポーネント(/で区切られた数値)から順にコンポーネントを比較し、同じなら破棄することを繰り返す
  2. 両方の点にコンポーネントがなくなったなら、単にオフセット(.の後ろの数値)の大小で前後関係を比較する
  3. 両方の点にまだコンポーネントが存在するなら、現時点で最上位のコンポーネントの大小で前後を比較する
  4. 片方の点(P1とする)のみもうコンポーネントが存在しない場合、もう片方の点(P2とする)は、P1のコンテナノードの子孫である。したがって、P1のインデックスとP2の現時点の最上位コンポーネントで大小関係を比較する

4.の場合は、P1のインデックスを示す数値が、P2の最上位コンポーネントの数値未満であるとき、P1はP2に先行します。そうでないとき、P1はP2に後続します。

注意点として、属性ノード内の点や名前空間ノード内の点は、その属性・名前空間ノードの親である要素ノードの属性・名前空間ノード内の点と比較してはなりません。なぜなら、属性・名前空間ノードの前後関係は定義されないからです。
ただし、同じ属性・名前空間ノードがコンテナノードである点どうしは単にインデックスで比較できるため、比較可能です。また、親要素が異なる属性・名前空間ノード内の点は、親要素の位置関係で比較が可能なため、比較可能です。

4.5 Functions Added by the xpointer() Scheme

XPointer仕様で追加されるXPathの関数群の説明です。

4.5.1 range-to Function

文脈ロケーションにstart-point関数を適用した結果を始点、文脈ロケーションに引数である式を適用した結果であるロケーションセットにend-point関数を適用した結果を終点とするような範囲を返します。

range-to関数専用に、XPathの構文であるところのロケーションステップが拡張されます。拡張の結果合法になる式として、仕様では以下のような例が挙げられています。

xpointer(id("chap1")/range-to(id("chap2")))
xpointer(descendant::REVST/range-to(following::REVEND[1]))

1行目の例は、"chap1"というIDを持つ要素の始点から、"chap2"というIDを持つ要素の終点までを範囲として返します。
2行目の例は、"REVST"という名前の要素から、その兄弟である"REVEND"のうち最初に見つかったものまでの範囲を返します。

元のXPathの構文では、ロケーションステップに関数を置くことはできませんでしたが、range-toに限り、それができるように拡張されます。

4.5.2 string-range Function

引数であるロケーションセットに含まれる各ロケーションの文字列値について、同じく引数である文字列値が一致する点からなる範囲をロケーションセットとして返します。
文字列値に対して作用するので間にマークアップが挟まっていてもマッチし得ます。

3つ目と4つ目のオプション引数は、マッチした範囲のうちどこからどこまでを切り出すかを選択するための数値です。
これらはマッチした箇所からはみ出るような範囲を指定するように設定することができますが、文書の範囲を超えた範囲を指定することはできません。

範囲に関する追加関数です。

4.5.3.1 covering-range Function

引数であるロケーションセットに含まれる各ロケーションのカバー範囲の集合を新たなロケーションセットとして返します。

4.5.3.2 range-inside Function

引数であるロケーションセットに含まれる各ロケーションについてカバー範囲を返します。

一見covering-rangeと同じに見えますが(そして点と範囲については実際同じなのですが)、それ以外のノードの場合が少し違います。
点と範囲以外のノードの場合、そのノードのカバー範囲ではなく、そのノードをコンテナノード、始点のインデックスが0、終点のインデックスがノードの子の数(もしくは文字列数)であるような範囲を返します。
名前の通り、自身の(表示上の)内側をカバーする範囲を返すという関数になります。

4.5.3.3 start-point Function

引数であるロケーションセットに含まれる各ロケーションについて、以下のルールで点に変換し、新たなロケーションセットにまとめて返します。

  • 点の場合、その点をそのまま返す
  • 範囲の場合、始点を返す
  • ルート、要素、テキスト、コメント、処理命令の場合、コンテナノードをそのノード、インデックスを0とする点を返す
  • 属性、名前空間の場合、エラーである
4.5.3.4 end-point Function

start-pointがロケーションを始点に変換する関数だとすると、こちらは終点に変換する関数です。

以下のルールで各ロケーションを点に変換します。

  • 点の場合、その点をそのまま返す
  • 範囲の場合、終点を返す
  • ルート、要素の場合、コンテナノードをそのノード、インデックスをそのノードの子の数とする点を返す
  • テキスト、コメント、処理命令の場合、コンテナノードをそのノード、インデックスを文字列値の文字数とする点を返す
  • 属性、名前空間の場合、エラーである
4.5.4 here Function

この関数はXML文書、外部一般解析対象実体の中に現れる式で使われるときだけ成功し、それ以外はエラーです。

XML文書、外部一般解析対象実体においては、以下のように評価されます。

  • 式が要素ノードの内側のテキストノードに存在する場合は、要素ノードを返す
  • それ以外の場合、式を直接含むノードを返す

「要素ノードの内側のテキストノード」が何を指すか微妙にわかりにくいですが、仕様中の例によれば、属性値であるテキストノードに含まれるような場合は、テキストノード自体や、その親である属性ノードではなく、属性ノードを含む要素ノードを返すようです。
とすると、それ以外の場合ってなんなんですかね?

注釈として、「here関数はテキスト、属性、処理命令中の式に現れる可能性が高く、要素内容に現れる式は要素内部のテキストノードに現れるため、要素ノードを返さない」と書いてあるのですが、定義と矛盾していませんか?
上では「式が要素ノードの内側のテキストノードに存在する場合は要素ノードを返す」と言っているので全く正反対のことを言っているようにしか見えず、意味不明です。未だにこの文章の意味するところはわかっていません。

4.5.5 origin Function

XML文書内で表現されたリンクを走査するとき、プログラムがリンクの走査を開始した位置を返す、とのことです。あんまり意味はよくわかっていません…

リンクの走査をしていない場面であるとか、フラグメント識別子内のXPointerにorigin関数が使用されており、かつURIが指定されており、さらにそのURIが走査開始元リソースとは異なるリソースを識別する場合はエラーになるそうです。

うーん…よくわかりません。

4.6 Root Node Children

ルートノードの子の数について、本来であればルートノードの子は整形式XML文書では1つしかないはずで、実際XPathのデータモデルでも文書要素に当たる要素の1つしかルートノードの子にはなりえません。

しかし、XPointerでは外部一般解析対象実体も扱わなければならない都合、ルートノードが複数の子を持つことを許可しています。

あとがき

流石に4つもの仕様をまとめて書いたので、長かったです。

element, xmlnsの2つのスキームは実装が簡単そうなので実装しても良さそうですが、xpointerスキームは非常に迷います。
複雑で実装がツラそうというのもありますし、Recommendationじゃないというのもあります。XIncludeではXPointerを使うことができますが、elementと短縮記法がサポートできていればいいという話も聞くので、なおのことモチベーションがありません。そもそもXPointerもXIncludeも誰が使ってるんだというレベルの仕様ですし、まあロマンで実装してみる以外の意味はないですよね。
とりあえずelement, xmlnsを実装し、XIncludeを実装し、他の仕様を一通り実装しきった後にオマケで使えるようにするくらいが妥当なのかもしれません。

実装者側に経つとはた面倒くさそうな仕様ではありますが、フラグメント識別子XPathで記述できるというのは夢があります。
IDが設定されていなくても特定箇所の指定をURIのレベルでできるというのは、まあ便利そうですよね。REST APIのリソースを絞る用のクエリパラメータとかはありがちですが、XPathが使えるならAPI側で頑張ってそのようなクエリパラメータを生やす必要もないです。(サーバの負荷もあるでしょうからやっぱり必要かもしれませんが)
とはいえ、現実的にはDOMとJavascriptで超頑張るとかでも対応できちゃうあたり、あんまり需要がなかったのかもしれません。だから20年も経ってもなおRecommendationまで漕ぎ着けられなかったのでしょう。XLinkが全然普及しなかったのもそうですし。

今の所、次回はXIncludeにしようと思っています。その次はXSLTを考えてはいますが、また心が折れたら別のをやるかもしれません。
あと、XML Schemaを実装したいなという欲も最近は出てきていたり…ただこれもクソ長い仕様書ですからね…それもXSLTなんぞ目じゃないくらいのやつです。欲はありますが、仕様書を読み始めたら一気に欲が消え失せるかもわかりません。

B'z LIVE-GYM 2025 FYOP

題名を見ればわかると思いますが、今回は技術系の話は皆無です。本当はこういうのはツイッターでつぶやきたいのですが、セトリをネタバレされると嫌な気持ちになる人もいるかもしれないなーと思うと呟けず。
でも感想を吐き出したいんですよね。なので(こっちはこっちで場違いとは思いつつ)積極的にネタバレされたい人以外はネタバレせずに済むこっちに感想を書きます。

今回は福岡公演の1日目(11/29)に参加できました。ちなみにB'zのライブはたぶん今回で4回目だと思います。記憶にある限りはEPIC NIGHT, DINOSAUR, HINOTORIで3回は行ったことがあります。B'z以外はついこの間Eric Claptonのライブに行った以外経験がないので、まったくライブ慣れしていません。

以降本編です。ライブ自体の感想以外の旅行の話も含めてダラダラ書きます。

ライブ前日以前

とりあえずチケットを取るあたりから遡ります。

なにかの拍子にチケットぴあの会員登録をしていたら、ある日B'zがライブをやるらしいというメルマガが届き、久々に応募しようと決めました。
チケットのとり方を調べていると、B'z Party, Club-Gymの会員の後に一般抽選があると知ったので、それまで待機しました。会員以外はSS席とかは応募すらできないので、会員になっちゃおうかな〜と今は思っています(が、ズボラなのでまだ何もしていない)

ようやく抽選応募期間が来たのですが、2通りの応募方法がありました。1つは席種別や公演に優先順位をつけて選択する方法、もう1つは(複数公演に参加する前提で)優先順位をつけずに並行して選択する方法です。
優先順位はいくらでも選べたので、私はとりあえずどこか当たってくれ〜という気持ちで、日程的に行けない公演を除いてすべての公演の全席種の18個を希望しました。
ここでライブ慣れしていないポイントなのですが、当選しても金さえ払わなければ勝手にキャンセルされるので、優先順位をつけるよりも複数公演全部応募して、当たったやつの中から良い席種・公演を選ぶようにしたほうがよかったと後で知りました。まあB'zレベルで複数公演ないし席種が当たることなんてないのかもしれませんけども。

当選発表の連絡が来るまでドキドキしましたが、見事当選。当たったのは冒頭で書いたとおり福岡の1日目で、席種は注釈付きS席でした。
ちなみに第17希望でした。B'zもまだまだすごい集客力なんだな〜などとムダに関心してしまいました。

すぐに宿を押さえにかかりましたが、値段にビビってなかなか動けず。もう社会人になって数年経ちますが、未だに大学生のような金銭感覚で生きているので、宿が一泊2万近いと聞いて卒倒しそうでした。
結局なんだかんだあり、日本旅行のサイトを徘徊していたら、奇跡的に新幹線と宿一泊がセットで4万5千円くらいのプランを発見。新幹線往復だけで4万くらいなはずなのにどういう仕組みなのかよくわかりませんでしたが、まあJR西日本と提携しているプランだ、的なことが書いてあったので、怪しい宿とかではないだろうと判断して予約しました。実際泊まってみても、宿は普通に満足いくレベルのシングルルームで、ますますどういう仕組みでこんな安かったのかワケがわかりませんでした。

チケットや宿を取ったのが10月下旬くらいで、しばらくして、11月も中頃になった頃、B'zが新アルバムを出すと知りました。最初にライブツアーの名前を聞いたとき、「FYOPってなんやねん」と正直思ったものですが、アルバムの名前がFYOPだったので、あーこれのツアーってことなのねとわかりました。
発売を知った瞬間、タワーレコードで即ポチしました。ライブがこのアルバムのツアーだということはわかったものの、「で結局FYOPってなんやねん」というのは何もわからなかったのですが、アルバムが届いて開封してみると、大きく"Follow Your Own Passion"と書いてあり、ああそういう意味か、とようやく納得いきました。もっと言えば、表題曲みたいな存在にあたる「FMP」が"Follow My Passion"なので、それに対する"Follow Your Own Passion"なのかというのも納得しました。
ちなみに、初回限定版で、メタルスピーカーつきのやつでした。文鎮みたいな見た目で、電源がないのですが、スマホを挿すとなぜか音が若干大きく聴こえます。仕組みはさっぱりわかりませんが、仕事しながら音楽を流すのに使っています。

届いたアルバムは早速スマホに取り込んで、無限にひたすら繰り返し再生して頭に刻み込みました。どれもいい曲ばかりですが、個人的には「イルミネーション」と「恐るるなかれ灰は灰に」の2つが気に入りました。
B'zってハードロックがイメージされがちではありますが、おとなしめの曲も全然ハズレがないですよね。激しい曲よりおとなしめの曲のほうが、松本さんのギターが特に冴えて聴こえます。「TIME」とか「きみをつれて」とか「愛しい人よGood Night...」とか、結構好きです。「イルミネーション」もその例に漏れないアタリ具合です。
「恐るるなかれ灰は灰に」はやっぱり最初のアレですね。「あと何回日が昇るのを見られるかぁ、あなた考えたことありますかぁ?」ってやつ。ライブでぜひ聴きたいと思いました。

FYOPを聴いているうちに、「そういえば持ってないアルバムも揃えておかなきゃな…」と思い立ち、ブックオフをハシゴして、持っていないアルバムを買い込みました。
「B'z」から「DINOSAUR」までのアルバムと「BAD COMMUNICATION」から「FriendsⅡ」までのミニアルバムは持っていたので、「Highway X」「NEW LOVE」「FriendsⅢ」の3つのアルバムと、あと「STARS」も買っておきました。
結局この中からライブでやったのは「兵、走る」の1曲だけだったのですが、どれも良かったです。「ペインキラー」と「ミダレチル」が特にお気に入りです。

ライブ当日

ライブ前

新幹線旅は超疲れました…新幹線の中ではヒマだったので、当日の予定を考えていました。
そこで気づいたのですが、なんとグッズ販売には整理券が必要とのこと…マジか〜と絶望しましたが、一応整理券販売の時間のあとに整理券不要のフリー販売があるらしいと知り、ちょっと安心しました。とはいえ、フリー販売のときまでちゃんとグッズの在庫は残っているのだろうか?というのは非常に心配でした。以前Eric Claptonのライブにいったとき、ライブの1時間前くらいの時点でグッズがほぼ完売しており、パンフレットくらいしか買えないという悲しいことがあったからです。(なのになぜ整理券販売のことを事前に調べておかないのかというのは突っ込まないでほしい)
フリー販売は15時からとのことなので、まあ14時半くらいに行けばいいかあと考え、それまでは昼食をとりつつ、テキトーに博多の街をぶらついてみることに決めました。

博多には特に何事もなく、無事に到着しました。
昼食はとりあえずラーメンでも食べたいなと思って色々回ってみたのですが、流石に名物なだけあって、どこもとんでもない行列でした。結局駅中をさまよっていると、名代ラーメンという店を見つけました。ここもかなり並んでいましたが、回転が早そうなので並んでみることに。
順番が訪れたとき、特に何も聞かれずに相席に案内されてちょっと困惑しましたが、こういうもんか〜と受け入れてラーメンとチャーハンのセットを頼みました。味は普通に美味しかったですし、値段もなぜかやたら安くて、790円とかだったと記憶しています。それは繁盛するわけですね。

その後はとりあえずテキトーに博多をぶらつき、聖福寺なるところにたどり着いたりしていました。なんでも廣田弘毅菩提寺とのことで、廣田弘毅って福岡の人だったのかと初めて知りました。名前の「弘毅」の由来もここにあるらしいです。
あとは街中になぜか磁器がショーケースに入れて展示してあったりして、こんな貴重そうなものを街中に置いといて大丈夫なのか?と思ったりもしました。

そうこうしているうちに良い時間になったので、福岡ドームに向かいました。予定通り14時半くらいには着きそうです。ただこのあたりで若干不穏な感触があり、B'zのLINEアカウントから「フリー販売の列は5ゲートから6ゲートに向けて形成されています」というような連絡が。「え、もう並んでるの?」と思いました。
そして福岡ドームについてみると、なんと5から6ゲートの1つ分どころではなくて、(具体的なゲート名は忘れましたが)2つか3つ分くらいのゲートに跨って列ができているではありませんか…いやはや見込みが甘すぎました。これはグッズ買えないだろうなと半ば諦めつつ、ダメ元で並んでみました。
フリーのグッズ販売は15時からだったのですが、その10分後くらいから列が動き始めました。すると、思った以上にスルスルと列が流れていき、なんと2, 30分も経つか経たないかのうちにグッズ売り場にたどり着くことができました。遅滞なく買い物を済ませて抜けていくファンの民度に感謝ですね。
グッズの在庫も(1日目だからという事情もあるのかもわかりませんが)かなり潤沢にあったようで、狙っていた品物はすべて手に入りました。私はパーカー、シャツ、タオルを買いました。こういうのを身に着けてライブに挑むの、地味に憧れでした。ちなみに、デカイかばんを持っていたはずが、なぜかグッズが入り切らず、ショッピングバックも買っておくべきだったと少し後悔しました。

そうこうしているうちに開演の1時間前なので、早速ゲートから入場しました。福岡ドームなんて来るのは初めてなので、チケットに書いてある席になかなか辿り着けずに迷子になりましたが、スタッフの方に案内してもらいながら辿り着き、購入したパーカーを着用しつつ着席しました。
席は「注釈付きS席」だったので、どんな場所だろうなと思いましたが、ステージの真横よりちょっと前寄りくらいで、スピーカーの柱が若干かぶってバックバンドは見えにくいものの、B'zの2人は肉眼で捉えられるくらいの悪くない位置でした。(実はあとで問題がでるのですが)

B'zのライブでは、ライブ前にはBGMとして洋楽が流れるのですが、以前よりだいぶ洋楽を聴くようになったおかげで知っている曲が増えていて、成長を感じました。
知っている曲の中では、Black SabbathとかLED ZEPPELINが多かったような気がしました。

ライブ

開演の瞬間ってめちゃくちゃ興奮しますよね。開演の前はスクリーンに(FYOPのアルバムジャケットを見たことがある人ならどんなんだか想像がつくかもしれませんが)ちょっとレトロなカセットレコーダーが写っていて、会場のBGMがそのレコーダーから流れているような演出になっていました。
このレコーダーに装着されたカセットが開演の瞬間ガチャンとFYOPのカセットに入れ替わり、(これもアルバムジャケットを見たことがあるなら想像がつくような感じで)カセットを起点にレコーダーが炎上し、演奏が始まる、という演出でした。

ちょっと曲の順番までは記憶が確かじゃないので、曲の感想の順番はテキトーです。

冒頭は「FMP」でした。一曲目から今日は絶好調なんだろうなというのが伝わってくる演奏で、これから続く演奏がより楽しみになるというか。
今まで見たことあるライブ映像では、松本さんはあんまりライブ中に着替えたり脱衣したりすることはないイメージなんですが、稲葉さんはよく衣装が変わりますよね。今回も途中でちょくちょく衣装が変わるのですが、最初は派手な柄の分厚いコートを羽織って登場しました。

その後は何曲か既存曲が続きました。順番は覚えていないですが、「兵、走る」「MY LONELY TOWN」「DIVE」「声明」「Still Alive」あたりです。
「兵、走る」はめちゃくちゃ盛り上がってましたね。やっぱり観客的にも、掛け合いがある曲は盛り上がりやすいのかもしれません。「兵、走る」はわかりやすい掛け合いがあるので、ライブ経験が貧弱な私でも安心してノれました。知名度もあるので、あんまりB'z詳しくない人でもノリやすかったんじゃないかと思います。
MY LONELY TOWN」はちょっと驚きました。ライブでは結構やる曲だったりするんですかね?あんまりそういうイメージがなかったです。私はこの曲かなり好きで、ライブで聴けると思ってなかったので嬉しかったです。
「DIVE」も同様です。2連続で「MAGIC」のあたりの曲が来たので、今日はそういう日なのかなとちょっと思いました。せいのでダーイブってやりたかったですが、びっくりしてタイミングは掴み損ねました。
「声明」はもしかしたらDINOSAURツアーで聴いたことがあったかもしれません。DINOSAURに収録されてる曲ですしね。その頃と遜色ない力強い演奏でした。
「Still Alive」は聴いたことあったかな…「声明」との両A面だったはずなので同じ時期のはずですが、DINOSAURで聴いたという記憶はあんまりないです。

その後はMCです。(もしかしたら「声明」「Still Alive」はそのあとだったかも)夢にまで見た「B'zのLIVE-GYMにようこそ」です。これを聞くためにLIVE-GYMに来ていると言っても過言ではありません。
いつもヒネリの効いたようこそをしてくれるのですが、今回は松本さんが「俺にやらせてくれないかなあ?」なんて言って待ったをかける驚きの展開で会場も大盛り上がりでした。松本さんがたくさん喋ってくれると嬉しいですよね。何度か「B'zの!!」を繰り返して会場の反応に満足したようで、最後はいつもどおり稲葉さんが「B'zのLIVE-GYMにようこそ〜〜!!!」と締めて再度演奏が始まりました。

既存曲のあとは「恐るるなかれ灰は灰に」「鞭」「INTO THE BLUE」「The ⅢRD Eye」の4曲がFYOPの中から演奏されました。「恐るるなかれ灰は灰に」は確実にこの位置だったと思いますが、「鞭」「INTO THE BLUE」「The ⅢRD Eye」は間に既存曲が挟まっていたかもしれません。
「恐るるなかれ灰は灰に」はライブでぜひ聴きたいと切望していましたが、ちゃんとやってくれました。冒頭の口上もキッチリやってくれました。ライブだと特に、重たい音が耳や脳だけじゃなくて、体に直で伝わってくるような感覚があるものですが、この曲はその感覚とマッチして、円盤で聴く以上の迫力がありました。
「INTO THE BLUE」はライブでやると思っていなかったのでノーマークでした。ライブならではのスクリーンの映像を活かした視覚的な情報も相まって、雰囲気は出ていました。惜しむらくは私が英語非対応なため、キッチリ歌詞を把握していないのもあって歌詞の内容も踏まえて何かを感じるということができなかった点でしょうか…
「鞭」は初めて聴いたときはナンダコレってちょっと思ってしまったものですが、印象的な掛け合いのしやすいフレーズがあるので、個人的にはライブ向きだな〜と感じたタイプの曲でした。
「The ⅢRD Eye」は円盤で聴いているときは正直あんまり印象のない曲だったのですが、ライブで聴くと印象に残りますね。ちょっとジャズっぽい音の軽快な曲ですが、円盤だとどうしても出にくかった雰囲気がライブだと感じやすかったです。語彙力が足りなくてうまく言えないのですが…

アンコール前のFYOPからの曲はこれで全部で、ほかは既存曲でした。「FAITH?」とかはやるかなと思っていましたが、やりませんでした。聴いてみたかったですが、今後に期待です。

途中でMCが入って、サポートメンバーの紹介や、松本さん、稲葉さんのコメントなどがありました。
Y.Tさんはなぜか「Y.TことY.T」という謎の紹介でした(?)ギターに加えてバックボーカル的なこともやっている方のようです。
清さんはずっと「せい」とかそんな読み方だと思いこんでいて、メンバー紹介で初めて読み方が「きよし」だと知りました。紅一点ですが、激しいプレイスタイルで印象に残ります。
川村ケンさんはキーボードの方ですが、ちょくちょく観客から「ケーン!!」と声援が飛んでいて、ファン人気もあるようでした。
Shane Gaalaasさんはずっと昔からB'zのサポートメンバーをやっていたドラマーで、私の印象では今回のメンバーの中で一番馴染みがあります。歴が長いからか声援も一番多かった気がします。

途中で会場が暗転したところで、ケンさんと稲葉さんだけがスポットを浴びる形で、「ライブで一度もやったことがない曲をやる」ということを言い出しました。会場大盛り上がりです。
なんで松本さんいないんだろうな?と思いましたが、このあと松本さんのソロも来る(これは記憶違いの可能性もある)のでそれの準備を裏でやっていたのか、はたまた体調を気遣って小休止的な間を取るためなのか、よくわかりませんでした。
やった曲は「Shower」でした。30年弱前のアルバムである「SURVIVE」に収録されていたバラード曲です。やったことなかったんだ〜って初めて知りました。福岡に縁がある曲らしいですが、「海沿いの球場見下ろせば」ってのも福岡にある球場なんでしょうか(平和台球場とか?)
続いて「消えない虹」もやったのですが、これもやったことなかったんでしょうか?ULTRA TREASUREとかに収録されていたファン人気も高い曲なので、こっちは流石に初めてではないですかね。
いずれにしても、珍しい曲をライブで聴けて大満足です。大昔のトンデモない歌い方をしていた頃(それこそSURVIVEとかBrotherhoodくらいの頃)の曲だと、さすがの稲葉さんでもちょっとキツそうだな〜と感じる場面もありがちですが、今回はいい味が出ていました。
稲葉さんは「みんなあんまり知らなかったかな?」とちょっと会場の雰囲気に不安げでしたが、どうだったんでしょう。確かにある程度ドップリ浸かってる人でないと知らない可能性はありそうですが…まあSURVIVEなら歴長くない人でも持ってる人は多い可能性もありそうです。なんせ私が生まれる前のアルバムなので、感覚がよくわかりません。

そして松本さんのソロで「#1090 Thousand Dreams」です。どの曲とどの曲の間でやったのか記憶が定かでないのですが、ちょっとせり上がったステージの上で松本さんが演奏していました。私の記憶では「Shower」とかで松本さんがいなくなって、そのあと「#1090 Thousand Dreams」のタイミングで稲葉さんがいなくなって、という流れだったと思うので、たぶんこの辺りのタイミングじゃないかなと思います。
会場はそれはもう大盛り上がりです。会場にいる人もれなくほぼ全員知っているレベルの曲ですしね。スクリーンには昔(90年代前半から半ばあたりくらい?上裸ジャケットに短パンスタイルの頃)の松本さんの映像も写っていたのですが、中央のスクリーンにしか写っていなくて、私の席からだとあんまり見えませんでした(ステージ上の松本さんはちゃんと見えている)
やっぱりギターソロだと、手元を見たくなります。流石に生の松本さんの指は見えないのでスクリーンの方を見るわけですが、釘付けになってしまいました。私はギターを一切弾けないので、見たところで参考になったりするわけではないのですが、すごいスピードで細かく弦やら何やらを捌いていく職人芸には憧れます。

その後はド定番中のド定番「ultra soul」「LOVE PHANTOM」の2曲です。これはもういうまでもなくその日一番の盛り上がりシーンです。初見さんだとしても間違いなく楽しめた2曲でしょう。
LOVE PHANTOM」が始まるとき、ステージ上のスクリーンがガバッと引き戸のように開き、中から「B'z」という形をとったどデカい電飾付きの台が現れ、その上に稲葉さんがいる状態から曲が始まる(松本さんはこれまで通りステージ上で演奏)という演出がありましたが、その「B'z」の形の台が奥まったところにあり、注釈付きS席の私の席からでは、開いたスクリーンに遮られてしまって何も見えませんでした。席種が席種なので致し方なしではありますが、ちょっと残念でした。せめて一瞬だけ全体像をスクリーンに映してくれるとかすると、演出に気づくまでのタイムラグが減ったのにな〜などとも思うわけですが、まあタイミングが難しくはありますよね。
このタイミングで、稲葉さんの衣装がちょっと変わりました。派手な柄のシャツに、炎のような柄の謎の腰布を巻き付けて登場したのですが、あの腰布が何だったのか、そしてあの腰布の下がどうなっていたのかは未だに謎ですw

そして、アンコール前最後の3曲は「juice」「愛のバクダン」「Brotherhood」でした。「愛のバクダン」「Brotherhood」のどっちかが最後だったと思うのですが、記憶が飛んでしまいました…
「juice」は、個人的には円盤を聴いているとそんなに印象がない曲なのですが、ライブだと大化けしますね。「BURN―フメツノフェイス―」も個人的にはその部類の曲と思っていますが、ライブになるとノリやすさとか演出の乗せやすさとか、色々あるのかもしれませんが、大化けします。
「juice」の合間には稲葉さんが観客と掛け合いで声を出す場面がありました。私もめっちゃ声を出して楽しかったのですが、普段正統派陰キャとして喉を使わない生活をしているため、突然の酷使により喉がおかしくなってしまいました。
「愛のバクダン」もやはり盛り上がっていました。これもライブの定番曲だと思います。大きなゴムボールみたいなやつが会場中を跳ね回っていましたが、私は触ることができませんでした。最初はスタンドの方にもあったと思うのですが、どうしてもアリーナのほうに転がっていってしまうので、やはり次はアリーナに潜り込むことを目標にしたいところです。
「Brotherhood」もいつか生で聴きたいとずっと思っていましたが、ようやく叶いました。稲葉さんのながーーいシャウトもお目にかかることができました。やっぱり「Brotherhood」は名曲なんですよね。普通にファンのなかでもトップレベルに人気の曲だと思っていますが、私も好きです。
「Brotherhood」といえば途中で「We'll be alright」と何度も掛け合いが入り、当然私もそこで声を出そうとするわけですが、ここで「juice」でおかしくなった喉が牙を向きました。全く喉がコントロールできなくて、「We'll be alright」が超絶音痴になっていました。自分でわかるレベルなので、たぶん相当音痴になっています。まあ誰も聴いちゃいないので気にせず声を出せば良いんですけど、流石に隣の人には聴こえてるだろと思うと、私が勝手に音を外しているだけなら良いんですが、外れた音に釣られてその人も音外れたら可愛そうだなと考えてしまい、全力では声が出せませんでした。これは悔しいですね。次の機会までの課題です。

このあとはしばらく会場が暗転して、観客はウェーイってやりながら波をつくったり、手拍子でアンコールの催促をしたりしていました。

そして聴き慣れたイントロで一瞬で何が来るのかを察するわけですが、「いつかのメリークリスマス」です。冬も近いし、絶対やるだろと思っていましたが、やってくれました。
いつかのメリークリスマス」も、B'zファンではない人に対しても知名度がある程度高い、ド定番曲の1つだと思っています。もちろん、私も好きです。ハズレのないバラード曲の1つですよね。
この曲も、ステージの奥まったところにクリスマスツリーみたいな形の電飾が設置されていましたが、やはり直接姿を拝むことはできませんでした。

そして最後は「イルミネーション」です。「いつかのメリークリスマス」で登場した電飾によるクリスマスツリーが、まるでイルミネーションで飾り付けられたかのように色を変えていました。(これも同様に直接は拝めないわけですが、端っこの方は見えていたので、なんとなく何が起こっているのかはわかります)
いつかのメリークリスマス」が来た時点でなんとなく、クリスマスツリーがイルミネーションで飾り付けられる感じで「イルミネーション」につながるんだろうなあと思っていましたが、まさしくその通りになりました。
FYOPの個人的イチオシ曲の1つでしたが、ライブで聴けて安心しました。「いつかのメリークリスマス」は暗い雰囲気の歌詞ですが、「イルミネーション」は幾分か希望が感じられる歌詞になっていて、どっちも冬っぽい曲ではありますが対局的でした。

ライブの演目が終わると、いつもの名前がわからない曲で締めて、B'zの2人からコメントがありつつ、終演となりました。
追い出し曲はFYOPのトリである「その先へ」でした。そんなわけで、FYOPからライブで登場した曲は、7曲となりました。やっていないのは「濁流BOY」「FAITH?」「片翼の風景」の3つでした。いつかライブで聴けるといいですね。

ライブ後

ライブが終わったあとは、規制退場に従ってドームの外に出ました。その足で、またグッズ販売場に向かいました。
ライブ前にショッピングバックを買わなかったことを後悔していたので、それを入手しつつ、荷物がかさばるのを嫌って買っていなかったバッジやキーホルダーなどの小物を追加で入手しました。
ちなみにショッピングバッグは、このあとの観光で入手した土産などをしまうのにも役に立ったので、買っておいて本当に良かったです。

一通り会場を満喫したあと、途轍もない人波に飲まれながら駅に向かい、博多の宿に向かいました。
宿についてはすでに軽く触れましたが、特に何の問題もない、普通のシングルルームでした。ほんとなんでこんな安かったんでしょうねえ…まあ得な分には嬉しいので一向に構わないのですが。

宿に重い荷物を置いたあと、軽装で博多駅に向かい、地下でモツ鍋を食べました。ついでに牛すじ煮込みも食べたのですが、すごく美味しかったです(小並感)
モツ鍋はおすすめと書いてあった味噌鍋で食べ、最後はちゃんぽん麺で締めました。モツはとても柔らかく、それでいて噛みごたえもあり、いつまでも口に含んでいられる感触でした。ゴマと赤いやつ(あれは何ですかね?)とニラも重要ですね。家で食べる機会があったら必ず用意しようと思いました。

翌日

チェックアウトのあとは、櫛田神社住吉神社に寄り、最後はまたラーメンを食べて、お土産を買い込んで博多を後にしました。

博多は紛うことなき大都市でしたが、古い街並みも残されていて、まだまだ1日では見足りないなと感じました。
そのうち山笠とかも見てみたいですね。

あとがき

技術関連の話題は1ミリもありませんでしたが、かなりの分量になってしまい、疲れました。

次のライブをいつやってくれるのかはわかりませんが、ぜひまた参加したいです。できれば近場だと楽だと思いつつ、一人旅なら遠出するのも楽しいなと若干考えが改まりました。
ともあれ、グダグダだったところも多いので、経験値をためてスムーズに参加できるようにもしたいところです。

ABC433G - Substring Game

完全に解けていたはずだったのですが、ライブラリがバグっていることに気づかずヒドイことに…

解法

結論から言えば、接尾辞木の上でよくあるゲームをしていると考えると解ける。
結局の所、問題文のゲームが何をしているかというと、

  1. 文字列 Sの接尾辞木を用意する
  2. ゲーム開始時点ではコマが接尾辞木の根にあり、Alice, Bobの順に任意の子にコマを移動させることを繰り返す
  3. コマが動かせなくなったら負け、負けなかったほうが勝ち

というものになる。
当然ながら、辿ってきた順にノードが示す文字を連結すれば Sの部分文字列になるので、元のゲームと同じ結果が得られる。

あとはよくあるゲームの解法で、葉では必ず偽(つまり負け)を返し、中間ノードではすべての子が真を返すなら偽(つまりどの子に進んでも相手が勝つので自分が負ける)、そうでないなら真を返すようなDFSをすれば、どちらが勝つかわかる。

接尾辞木の構築方法は色々あると思うが、私はそういうライブラリを持っていなかったので、SA-ISで接尾辞配列を構築し、接尾辞配列の上で二分探索をして代用した。

提出コード例は以下の通り。

Submission #71178305 - AtCoder Beginner Contest 433

あとがき

我ながらキレイに解いたな〜などと悠々コードを書いて提出したら謎のREがでて、本当に焦りました。
どう考えても間違っていないはずなのに全然合わず、EFが解けなくて時間がなかったのでランダムテストを書く暇もなく、混乱しているだけでコンテストが終わってしまい反省です。

結果的にはSA-ISのライブラリがバグっており、ac-library-rsなら通りました。久々にこの手の絶望を味わいました。
コンテスト後にめっちゃランダムテストをかけてもなかなかキラーケースに出会えなくて難儀でした。

ともあれ、ライブラリのテストも強化できましたし、たぶんもうバグってないはずです。(フラグ)

XMLのお勉強その12 - RELAX NG

前回

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などがあります。
DTDXML仕様に組み込まれたスキーマ言語ですが、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のいずれでもないような名前を持つ属性
  • 「外来要素」とは、名前空間URIRELAX NG名前空間URIではないような名前を持つ要素
    • 名前空間URIが空文字なだけなら、外来要素には含まれません。
  • 「完全な構文」とは、単純化される前のRELAX NG文法の構文
  • 「単純な構文」とは、単純化された後のRELAX NG文法の構文
  • 「単純化」とは、完全な構文のRELAX NGスキーマを、単純な構文のスキーマに変換すること
  • 「データ型ライブラリ」とは、局所名からデータ型へのマッピング
    • データ型ライブラリはURIで識別されます
  • 「データ型」とは、文字列の集合であって、同値関係をもつもの
  • 「公理」とは、無条件に証明可能とみなす命題
  • 「推論規則」とは、一つ以上の肯定的または否定的な前提条件と、必ず一つの結論とを持つ規則であって、すべての肯定的な前提条件が証明可能で、かつ、どの否定的な前提条件も証明可能でない場合、結論を証明可能であるとするもの
  • スキーマに照らして妥当」とは、スキーマによって記述される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名前空間URIlocal 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名前空間仕様で定義されたシンボルです。

anyURIXLink仕様の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属性をすべて削除します。

datavalueに適用すべきデータ型ライブラリを確定するプロセスということになりますね。
このルールの結果、各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章で述べられた「要素」の構築に使われます。
どのようにするかというと、

  1. href属性値であるURIを用いて、リソースとリソースに付与されるMIMEメディア型を取得する
  2. メディア型の情報を用いて、要素を構築する。特にメディア型が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を期待される値であるため、一応注記がなされています。
また、datatypeLibrarynsの引き継ぎについて、datatypeLibraryinclude, 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 NGmixedの方が強力ではありますが)

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で明示されていますが、choiceinterleaveのいずれかになります。

前提として、同じ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という名前を持ち、choicecombine属性として持つ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要素の子で置換し、parentRefrefに変更します。

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 1)と呼びます。
この先も公理や推論規則について記号で記述されていくのですが、ブログでの書き方がわからないので、ここには書きません。内容だけを見ていきます。

 (anyName 1)が言っていることは、あらゆる名前 nは、名前クラス<anyName/>に属する、ということです。言い換えれば、<anyName/>はあらゆる名前にマッチします。

ここで注意点として、先程の全称記号が云々という話が出てきます。
見た目上、変数である n nとしか記述されていないのですが、暗黙的に全称記号が適用されているとみなすので、結果的に、「あらゆる名前にマッチする」という意味を持つとみなされることになります。

続いて第一の推論規則 (anyName 2)が示されています。
前提条件は、「名前 nが名前クラス ncにマッチしない」ことです。結論は「名前は<anyName><except>nc</except></anyName>に属する」ことです。
結局の所、「あらゆる名前クラス ncに対して<anyName><except>nc</except></anyName>は、 ncにマッチしないあらゆる名前 nにマッチする」ということを言っています。

 (anyName 1),  (anyName 2)と似たような感じで、 (nsName 1),  (nsName 2),  (name),  (name choice 1),  (name choice 2)などが定義されます。

 (nsName1)は「<nsName ns="u">は名前 name(u, ln)にマッチする」という公理、 (nsName 2)は「あらゆる名前クラス ncに対して<nsName ns="u"><except>nc</except></nsName>は、 ncにマッチしないあらゆる<nsName ns="u">にマッチする名前 name(u, ln)にマッチする」という推論規則です。

 (name)は「<name ns="u">ln</name>は名前 name(u, ln)にマッチする」という公理です。

 (name choice 1)は「あらゆる名前クラス nc _ {1}, nc _ {2}に対して<choice>nc1 nc2</choice>は、 nc _ {1}にマッチする名前 nにマッチする」という推論規則です。

 (name choice 2)は「あらゆる名前クラス nc _ {1}, nc _ {2}に対して<choice>nc1 nc2</choice>は、 nc _ {2}にマッチする名前 nにマッチする」という推論規則です。

より簡単な言葉で言えば、

  • 名前クラス<anyName>はあらゆる名前にマッチするが、<except>を子に指定することで、マッチさせない名前を指定できる
  • 名前クラス<nsName>ns属性値が名前空間URIと等しいあらゆる名前とマッチするが、<except>を子に指定することで、マッチさせない名前を指定できる
  • 名前クラス<name>ns属性値が名前空間URIと等しく、内容テキストが局所名と等しい名前とマッチする
    • <name>については<except>が指定可能とは明記されていません。
  • <choice>は子がともに名前クラスの場合、いずれかの名前クラスに名前がマッチする時にマッチするとみなす

といったことになると思います。

9.3 Patterns

9.3.1 choice pattern

<choice>の意味論です。

 (choice 1)は「あらゆるパターン p _ {1}, p _ {2}に対して<choice>p1 p2</choice>は、文脈 cxにおいてパターン p _ {1}にマッチする属性集合 a及び列 mの組にマッチする」という推論規則です。

 (choice 2)は「あらゆるパターン p _ {1}, p _ {2}に対して<choice>p1 p2</choice>は、文脈 cxにおいてパターン p _ {2}にマッチする属性集合 a及び列 mの組にマッチする」という推論規則です。

つまるところ、<choice>の子であるパターンの片方にマッチするようなものにマッチする、という意味になります。

9.3.2 group pattern

<group>の意味論です。

 (group)は「あらゆるパターン p _ {1}, p _ {2}に対して<group>p1 p2</group>は、文脈 cxにおいてパターン p _ {1}にマッチする属性集合 a _ {1}及び列 m _ {1}の組と、同様に p _ {2}にマッチする a _ {2},  m _ {2}について、文脈 cxにおいて a _ {1}, a _ {2}の和集合と m _ {1}, m _ {2}を順に連結した列にマッチする」という推論規則です。

パターンを順に合成する役割をもつと考えればよいでしょうか。
属性集合は順序なし集合であるため、単に和集合をとるだけで良いですが、子の列は順序付き列なので、指定された順に並べる必要があります。

9.3.3 empty pattern

<empty>の意味論です。

 (empty)は「<empty/>は、文脈 cxにおいて、属性集合も列もともに空であるようなパターンにマッチする」という公理です。

DTDの要素型宣言において#EMPTYを指定した場合と同じようなものだと思っていますが、属性集合が空というのがちょっと気になりますね…
あんまり違いがよくわかっていません。

9.3.4 text pattern

<text>の意味論です。

 (text 1)は「<text/>は、文脈 cxにおいて、属性集合も列もともに空であるようなパターンにマッチする」という公理です。

 (text 2)は「<text/>は、文脈 cxにおいて、空な属性集合と列 m<text/>にマッチするようなパターンについて、 mに文字列 sを連結したパターンにもマッチする」という推論規則です。

 (text 1)は、 (empty)とほぼ同じ内容です。つまり、空の内容テキストも<text/>にマッチします。
それを公理として定めた上で、空の内容テキストにあるテキストを内容テキストとして追加したものも、また<text/>にマッチするのだ、というのが (text 2)の規則になります。

9.3.5 oneOrMore pattern

<oneOrMore>の意味論です。

 (oneOrMore 1)は「パターン pに対して<oneOrMore>p</oneOrMore>は、文脈 cxにおいてパターン pにマッチする属性集合 a及び列 mの組にマッチする」という推論規則です。

 (oneOrMore 2)は「パターン pに対して<oneOrMore>p</oneOrMore>は、文脈 cxにおいてパターン pにマッチする属性集合 a _ {1}及び列 m _ {2}と、<oneOrMore>p</oneOrMore>にマッチする a _ {2} mの組であって、 a _ {1} a _ {2}が互いに素であるようなものについて、 a _ {1}, a _ {2}の和集合と m _ {1}, m _ {2}をこの順番で連結した列の組にマッチする」という推論規則です。

 (oneOrMore 1)は簡単で、パターン pにマッチするものは<oneOrMore>p</oneOrMore>にもマッチするという意味です。one or moreのoneの部分ということです。
 (oneOrMore 2)があんまりピンと来ないのですが、列はおおよそイメージ通り、 pにマッチするものはなんべんでも連結してよい、ということを言っています。属性のほうがピンとこない点で、 a _ {1} a _ {2}が互いに素でなければならないとはどういうことかと。私の理解では、単に重複して同じ属性を指定できないから互いに素という条件をつけているだけなのだと思っています。

9.3.6 interleave pattern

<interleave>の意味論です。

まず1つの公理と2つの推論規則があります。

  •  (interleave 1)は「空の列どうしをinterleaveしたものは空の列である」という公理です。
  •  (interleave 2)は「 m _ {1} m _ {2}, m _ {3}をinterleaveしたものであるなら、 m _ {4}, m _ {1}を連結したものは、 m _ {4}, m _ {2}を連結したものと m _ {3}をinterleaveしたものである」という推論規則です。
  •  (interleave 3)は「 m _ {1} m _ {2}, m _ {3}をinterleaveしたものであるなら、 m _ {4}, m _ {1}を連結したものは、 m _ {2} m _ {4}, m _ {3}を連結したものを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)が定義されます。

 (interleave)は「パターン p _ {1}, p _ {2}に対して<interleave>p1 p2</interleave>は、文脈 cxにおいて p _ {1}にマッチする属性集合 a _ {1}と子の列の組 m _ {1}と、同じく p _ {2}にマッチする a _ {2}, m _ {2}の組について、 m _ {3}が[tex m _ {1}, m _ {2}]をinterleaveした列であるとき、 a _ {1}, a _ {2}の和集合と m _ {3}の組にマッチする」という推論規則です。

9.3.7 element and attribute pattern

初めに「弱いマッチング」が定義されます。この動機としては、要素と属性の空文字列や値なしの考え方の微妙な違いを吸収するというものがあります。
具体的に言えば、属性は空文字列も含めて常に値を持ちますが、値が空の列になることはありません。一方で、要素の子は空の列になりうる一方、データモデル上、空の文字列のみからなる列とはなりえません。このような微妙な違いはあるものの、意味的にはおおよそ同じであるような表現を一貫して検証できるように、「弱いマッチング」を定義すると便利です。

「弱いマッチング」は要素・属性の検証に用いられ、次のような推論規則からなります。

  •  (weak match 1)は「文脈 cxにおいて属性集合 aと列 mの組がパターン pにマッチするなら、 cxにおいて a, mの組は pに弱くマッチする」という推論規則
  •  (weak match 2)は「文脈 cxにおいて属性集合 aと空である列の組がパターン pにマッチするなら、 cxにおいて aと空白文字のみからなる文字列、または空の列の組は pに弱くマッチする」という推論規則
  •  (weak match 3)は「文脈 cxにおいて属性集合 aと空文字列の組がパターン pにマッチするなら、 cxにおいて aと空の列の組は pに弱くマッチする」という推論規則

そして、「弱いマッチング」を用いて<attribute>, <element>の意味論を定義します。

まず、 (attribute)は「文脈 cxにおいて空の属性集合と文字列 sの組がパターン pに弱くマッチし、また名前 nが名前クラス ncに属するならば、 cxにおいて属性 attribute(n, s)と空の列の組は<attribute>nc p</attribute>にマッチする」という推論規則です。

続いて、 (element)は「文脈 cx _ {1}において属性集合 aと混在列 mの組がパターン pに弱くマッチし、名前 nが名前クラス ncに属し、 mが要素の子として現れることのできる列であり、局所名 lnに対して文法が<define name="ln"><element>nc p</element></define>を含むならば、文脈 c _ {2}において空の属性集合と空文字列 ws _ {1}, ws _ {2}によって element (n, cx _ {1}, a, m)を挟んだ列の組は<ref name="ln">にマッチする」という推論規則です。

うーん…むずかしいですね。

9.3.8 data and value pattern

まず冒頭では、データ型ライブラリ及びデータ型の特定方法と、データ型ライブラリが提供する機能の話が述べられています。

データ型ライブラリはこれまで述べられてきたとおり、URIによって識別されます。そして、データ型はNCNameで識別されます。
結果的には、ある型はURIと局所名の組によって一意に識別できることになり、要素や属性と同じように名前で識別ができます。

データ型ライブラリが提供する機能は以下の2つです。

  1. 文字列がデータ型の正しい表現であるかを判定する。この機能は任意の個数引数を取ることがあり、データ型によって何を引数に取れるかが特定される。
  2. 2つの文字列があるデータ型として等しいかを判定する。この機能は引数を取らない。

いずれも文字列が現れる文脈を用いることができ、例えばQNameを表現する型は、名前空間マッピングを用いるでしょう。

2つ目の機能を表す datatypeEqualという関数が定義されていますが、この関数は反射的、推移的、かつ対称的でなければなりません。
この制約は、以下の3つの推論規則で表現されます。

  •  (datatypeEqual reflexive)は「文脈 cxにおいて、文字列 sが引数 paramsを適用することでURI u, 局所名 lnで特定されるデータ型として正しい表現であると判定されたならば、 u, lnで特定されるデータ型として s sは等しい」という推論規則
  •  (datatypeEqual transitive)は「文脈 cx _ {1}における s _ {1} cx _ {2}における s _ {2} u, lnで特定されるデータ型として等しく、 cx _ {2}における s _ {2} cx _ {3}における s _ {3}も同様であるなら、 u, lnで特定されるデータ型として cx _ {1}における s _ {1} cx _ {3}における s _ {3}は等しい」という推論規則
  •  (datatypeEqual symmetric)は「文脈 cx _ {1}における s _ {1} cx _ {2}における s _ {2} u, lnで特定されるデータ型として等しいならば、 cx _ {2}における s _ {2} cx _ {1}における s _ {1}も等しい」という推論規則

えらい長ったらしい文言になってしまいましたが、プログラミング言語的に書くなら以下の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)は「 u _ {1}, lnで特定されるデータ型として、 cx _ {1}における s _ {1} cx _ {2}にデフォルト名前空間 u _ {2}を加えた文脈における s _ {2}が等しいならば、空の属性集合と s _ {1}の組は<value datatypeLibrary="u1" type="ln" ns="u2" [cx2]>s2</value>にマッチする」という推論規則です。

開始タグに含まれる[cx2]は、4.2.3章で定義されていた記法で、「そのパターン要素における文脈」と定義されています。
 cx _ {2}にデフォルト名前空間として u _ {2}を加えるのはなぜかというと、 u _ {2}ns属性で指定されているからです。もしns属性が空文字列なのであれば、それは単に cx _ {2}と同じ内容をもつ文脈になります。

続いて、 (data 1)は「文脈 cxにおいて、文字列 sが引数 paramsを適用することで u, lnで特定されるデータ型として正しい表現であると判定されたならば、空の属性集合と sの組は<data datatypeLibrary="u" type="ln">params</data>にマッチする」という推論規則です。

最後に、 (data 2)は「文脈 cxにおいて、文字列 sが引数 paramsを適用することで u, lnで特定されるデータ型として正しい表現であると判定され、属性集合 aと文字列 sの組がパターン pにマッチしないならば、空の属性集合と sの組は<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つの推論規則が定義されます。

  •  (string allows)は「任意の文脈 cxにおいて、任意の文字列 sは、引数として何も適用せず、空文字列であるURIstringという局所名で特定される型として正しい表現である」という公理です。
  •  (string equal)は「任意の文脈 cx _ {1}, cx _ {2}において、任意の文字列 sは、空文字列であるURIstringという局所名で特定される型として、 sと等しい」というという公理です。
  •  (token allows)は「任意の文脈 cxにおいて、任意の文字列 sは、引数として何も適用せず、空文字列であるURItokenという局所名で特定される型として正しい表現である」という公理です。
  •  (token equal)は「文字列 s _ {1}, s _ {2}の正規化値が等しいのであれば、文脈 cx _ {1}における s _ {1} cx _ {2}における s _ {2}は、空文字列であるURIstringという局所名で特定される型として等しい」という推論規則です。

stringtokenも、許可される値はまったく同じで、任意の文字列を受け付けます。
しかし、推論規則からも見て取れる通り、stringは完全一致で等値比較をする一方、tokenは正規化値で等値比較を行います。
文字列の正規化とは何ぞやという話ですが、これは4.2.3章で定義されており、「先頭と末尾の空白文字を除去し、それぞれの極大な空白文字列を1つのスペース文字に置換する」処理です。つまり、DTDにおけるCDATA型以外の属性型の属性値の正規化と同じ処理のことです。

9.3.10 list pattern

ようやく最後のパターンです。<list>の意味論です。

 (list)は「空の属性集合と文字列 sを空白文字で分割した列の組がパターン pにマッチするなら、空の属性集合と sの組は<list>p</list>にマッチする」という推論規則です。

要は空白区切りのトークン列をマッチさせるためのパターンです。

9.4 Validity

9章の最後の推論規則は、「スキーマに照らして妥当」であることを形式的に述べるものです。

空の属性集合を伴う要素が文法のstartパターンにマッチするとき、要素は妥当であるとみなされます。
 (valid)はこれを形式的に述べた「文法がパターン pを含むstart要素<start>p</start>を含み、空の属性集合と要素 eの組が pにマッチするなら、 eは妥当である」という推論規則です。

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つの「内容種別」とは empty, complex, simpleである、と定義します。

これらの内容種別について、グループ化可能な組み合わせは、任意の内容種別と empty complexうしの計4つの組み合わせだけです。
これを形式的に表したのが次の3つの公理です。

  •  (group empty 1)は「 emptyと任意の内容種別はグループ化可能である」という公理
  •  (group empty 2)は「任意の内容種別と emptyはグループ化可能である」という公理
  •  (group complex)は「 complexどうしはグループ化可能である」という公理

記号だともっとシンプルになっていますが、文字にすると前段で言っていることと同じことを言っています。

また、内容種別には大小関係があるということが4.2.3章で定められており、小さい方から大きい方へ、 empty, complex, simpleの順になります。
内容種別 ct _ {1}, ct _ {2}について、 max(ct _ {1}, ct _ {2})はこの大小関係にしたがって、大きい内容種別を返します。例えば、 max(empty, complex) complexになります。

これらの公理を使ったり使わなかったりすることで、各パターンについて、内容種別を定めます。

  •  (value)は「<value datatypeLibrary="u1" type="ln" ns="u2">s</value> simpleである」という公理
  •  (data 1)は「<data datatypeLibrary="u" type="ln">params</data> simpleである」という公理
  •  (data 2)は「パターン pが任意の内容種別 ctを持つなら、<data datatypeLibrary="u" type="ln">params<except>p</except></data> simpleである」という推論規則
  •  (list)は「<list>p</list> simpleである」という公理
  •  (text)は「<text/> complexである」という公理
  •  (ref)は「<ref name="ln"/> complexである」という公理
  •  (empty)は「<empty/> complexである」という公理
  •  (attribute)は「パターン pが任意の内容種別 ctを持つなら、<attribute>nc p</attribute> emptyである」という推論規則
  •  (group)は「パターン p _ {1}, p _ {2}がそれぞれ任意の内容種別 ct _ {1}, ct _ {2}を持ち、 ct _ {1}, ct _ {2}がグループ化可能であるなら、<group>p1 p2</group> ct _ {1}, ct _ {2}のうち大きい方の内容種別を持つ」という推論規則
  •  (interleave)は「パターン p _ {1}, p _ {2}がそれぞれ任意の内容種別 ct _ {1}, ct _ {2}を持ち、 ct _ {1}, ct _ {2}がグループ化可能であるなら、<interleave>p1 p2</interleave> ct _ {1}, ct _ {2}のうち大きい方の内容種別を持つ」という推論規則
  •  (oneOrMore)は「パターン pが任意の内容種別 ctを持ち、 ctどうしがグループ化可能である(すなわち ct simpleではない)なら、<oneOrMore>p</oneOrMore> ctを内容種別として持つ」という推論規則
  •  (choice)は「パターン p _ {1}, p _ {2}がそれぞれ任意の内容種別 ct _ {1}, ct _ {2}を持つなら、<choice>p1 p2</choice> ct _ {1}, ct _ {2}のうち大きい方の内容種別を持つ」という推論規則

ここまでの公理、推論規則によって、内容種別を持つパターン、持たないパターンが明らかになりました。
そしてこの章の冒頭の話に戻るわけですが、要素内容として現れるパターンはすべて内容種別を持つものでなければならない、という制約が課されます。

 (element)は「局所名 lnが要素パターン<element>nc p</element>を指しており、パターン pが要素内容を持たないなら、スキーマとして正しくない」という推論規則です。

冒頭の例を見直しておくと、1つ目は以下のパターンでした。

<element name="foo">
    <group>
        <data type="int"/>
        <element name="bar">
            <empty/>
        </element>
    </group>
</element>

内側から見ていくと、まずdataパターンは公理 (data 1)より simpleです。一方elementパターンは、単純化によりrefパターンになりますから、公理 (ref)より、 complexです。
その外側はgroupパターンなので、推論規則 (group)を適用すると、これが内容種別を持つためには2つの子が内容種別を持ち、かつグループ化可能な組み合わせでなければなりません。しかし、 simple, complexの組はグループ化可能ではないため、この例のgroupパターンは内容種別を持ちません。
そして、最も外側のelemenパターンの内容として現れたgroupが内容種別を持たないため、推論規則 (element)より、スキーマとしては正しくない、という結論が得られます。

2つ目の例は以下のパターンでした。

<element name="foo">
    <group>
        <data type="int"/>
        <text/>
    </group>
</element>

これも同様に内側から見ていくと、data, textはそれぞれ、公理 (data)より simple, 公理 (text)より complexです。
これはグループ化可能な組ではないので、groupパターンは推論規則 (group)より、内容種別を持ちません。
従って、推論規則 (element)より、やはりスキーマとしては正しくない、という結論が得られます。

10.4 Restrictions on attributes

属性に関する制約です。

まず、属性は重複してはなりません。具体的には、<group>p1 p2</group>, <interleave>p1 p2</interleave>の2つのパターンにおいて、 p _ {1}に現れるattributeの名前クラスと p _ {2}に現れるattributeの名前クラスの両方に属する名前があってはなりません。
あるパターン p _ {1} p _ {2}も含めて、前の文章の p _ {1}, p _ {2}とは関係ない)が p _ {2}の中に現れる、というのはどういうことかというと、それは

  •  p _ {1} p _ {2}である
  •  p _ {2}choice, interleave, group, oneOrMoreのいずれかであって、 p _ {1} p _ {2}の片方もしくは両方の子の中に現れる

のいずれかです。

続いて、無限名前クラスを使うattributeパターンは、繰り返されなければならない、と述べられています。
「無限名前クラス」とは何ぞやということですが、anyName, nsNameを指すと読み取れます。つまり具体的に言えば、anyNamensNameを子孫に持つattribute要素は、祖先要素にoneOrMoreを持たなければならない、という制約になります。

最後に、無限名前クラスを使うattributeパターンは、それらの値としてtextを持たなければならない、と述べられています。
つまり、anyNamensNameを子孫に持つattribute要素は、子要素としてtextを持たなければなりません。

最後の2つの制約は、「否定の下での閉包のために必要だ」と記載されていますが、どういう意味なんですかね?

10.5 Restrictions on interleave

最後はinterleaveに関する制約です。

実装を容易にするため、特定の名前の要素は、interleaveパターンにおいて高々一度までしか現れてはならず、text要素も同様に高々一度までしか現れてはなりません。
より具体的に言えば、<interleave>p1 p2</interleave>というパターンがあるとし、

  •  p _ {1}に含まれるrefパターンと p _ {2}に含まれるrefパターンのそれぞれが指すelementの子孫の名前クラスの両方に属する名前があってはならない
  • text p _ {1} p _ {2}の両方で現れてはならない

という制約になります。

あるパターン p _ {1} p _ {2}の中に現れるというのがどういうことなのかは、10.4章での定義のとおりです。

11 Conformance

ようやく最後の章です…適合性の話です。

適合する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の実装に必要っぽいので、避けて通れません。

XMLのお勉強その11 - XML Catalogs

前回

XMLのお勉強その10 - xml:id Version 1.0 - 競プロ備忘録

読んでいく仕様書

原文:XML Catalogs (OASIS Standard V1.1, 7 October 2005)
和文:なし

ちょっと寄り道で、XML Catalogsを見ていきます。何気に初めてのOASISの仕様書です。
なんか調べている仕様が古いからなのかよくわかりませんが、OASISの仕様書は探しにくくてツライです。
今見ている仕様書も最新なのか正直よくわかっていないのですが、あっているだろうということでこれで行きます。

ちなみに、PDF版ではDraftと書かれているのですが、同じ日に発行されたHTML版はOASIS Standardとなっています。ただ、すでにリンク切れしていて、Wayback Machineを使わないとアクセスできません。

XML Catalogs (これもWayback Machineアーカイブのリンク)

一応現存しているほうのPDFのリンクをメインで書きましたが、たぶん消滅したHTML版も同じものだと思いますし、HTML版のほうが機械翻訳も使いやすいのでこっちで読んでいこうかと思っています。

2005年の仕様らしく、Sunが存在した時代らしいです。私はまだ小学生にもなっていません。(今まで見てきた仕様はだいたいそうなので何を今更という感じですが)

本編

1. Introduction

この仕様が提供するものは何かという話ですが、以下2つが挙げられています。

  1. 外部実体の公開識別子及びシステム識別子をURI参照にマッピングする方法
  2. あるリソースのURI参照を別のURI参照にマッピングする方法

つまるところ、XML文書中の何らかのリソースを指し示すURIや外部識別子をリダイレクトする方法を提供するということです。
特に、公開識別子はXML仕様では使い方が定まっておらず、正直何に使えるのかという感じでしたが、XML Catalogsを使用する場合は、何らかのリソースを示すURIを取得するのに役立てることができます。

2. Terminology

W3Cの仕様書でもおなじみの章ですが、用語の定義です。

助動詞についてはいつものやつです。(RFC2119)

URI」, 「URI参照」などはRFC2396準拠です。(今扱うならRFC3986ですかね?)

「外部識別子」,「公開識別子」, 「システム識別子」などはXML仕様に従います。

ここまでは見慣れたものですが、残るはこの仕様独特の用語です。

まず、カタログプロセッサへの論理的な「入力」とは、公開識別子とシステム識別子のペアであるところの外部識別子か、あるいはURI参照です。
カタログプロセッサの論理的な「出力」とは、URI参照です。

また、「カタログ」とは、マッピング情報を含む論理的な構造であり、物理的には1つ以上の「カタログエントリファイル」に格納される場合があります。
「カタログエントリファイル」とは、カタログのエントリの集合を含む文書です。

3. An Entity Catalog

1章の話をちょっと詳しくして繰り返しているだけの内容な気がしますが、XML Catalogsとはどのようなもので、何の機能を提供するのかという話です。

まず冒頭では、OASISがこの仕様で定義するカタログとは、

  1. 外部識別子やURI参照を、ある別のURI参照に写すものであり
  2. アプリケーション非依存であり
  3. XML(およびXML名前空間)のフォーマットで定義されるものである

と述べています。

1.に関連して、カタログプロセッサの主な役割とは、入力に一致するカタログ内のエントリを検索し、出力としてマッピング先のURI参照を返すことだと述べられています。
もし一致するエントリが複数あっても、最初に検索に引っかかったエントリのみが返されます。

2.に関連して、カタログを使用した実体の検索のタイミングはアプリケーション次第であり、この仕様で定義するところではない、と述べられています。
libxml2のデフォルトの実体検索用のルーチンでは、まずカタログを検索し、見つかったらそれを試す、見つからなかったらローカルファイルを検索する、というような挙動を取っていたと記憶しています。
実際のところ、何らかの理由でリソース解決のプロセスを捻じ曲げたくてカタログを定義するものだと思いますし(例えば外部ネットワークに出てほしくないとか)、多くの場合は生の識別子よりカタログでの解決を先に試すのが妥当なんじゃないかという気がしています。

また「カタログは実質的に1つ以上のカタログエントリファイルの順序付きリストであるが、論理カタログとして使用するカタログエントリファイルの順序付きリストを決定するのはアプリケーション次第である」と述べられています。
あんまりこの文章の意味はよくわかっていません。順序付きリストの構成方法を決めるのがアプリケーション次第ということなのか、あるエントリの集合のどの部分集合を順序付きリストとして構成するのかがアプリケーション次第ということなのか、目的語がよくわかりません。
ただ続いて、「カタログエントリファイル」はあくまで論理カタログの構成要素の1つを示す用語であって、例えばデータベース内の表やURI参照で識別されるオブジェクトなど、必ずしも名前通りファイルではないかもしれない、といったことが述べられているので、「カタログエントリファイルの順序付きリストを物理的な構造としてどのように構成するのかがアプリケーション次第である」ということが言いたいのかな?と今のところは思っています。

3.に関連して、公開識別子をURI参照に写す文法の例が示されています。

<public publicId="ISO 8879:1986//ENTITIES Added Latin 1//EN"
        uri="iso-lat1.gml"/>
<public publicId="-//USA/AAP//DTD BK-1//EN"
        uri="aapbook.dtd"/>
<public publicId="-//Example, Inc.//DTD Report//EN"
        uri="http://www.example.com/dtds/report.dtd"/>

なんとなく見た感じでどういうことをしているかはわかりそうです。

こんな見た目のXML文書がXML Catalogsとなるわけですが、その構成要素として、仕様ではcatalog, delegatePublic, delegateSystem, delegateURI, group, nextCatalog, public, rewriteSystem, rewriteURI, system, systemSuffix, uri, uriSuffixというエントリタイプを定義すると述べています。
この仕様に準拠するには、これらのすべてのエントリタイプを、仕様で定義される意味論とともに実装する必要があります。

また、XML Catalogsの名前空間としてurn:oasis:names:tc:entity:xmlns:xml:catalog、公開識別子として-//OASIS//DTD XML Catalogs V1.1//ENが定義されます。
この名前空間内のすべての要素と属性及び、この名前空間に属する要素で指定される修飾されていないすべての属性は、この仕様で予約されます。後者については、要はカタログ仕様準拠の要素について余計な属性を指定するなら、名前空間をはっきりさせよ、ということかなと理解しました。

将来の拡張のため、この仕様の名前空間以外の名前空間に属する要素、属性で示される「その他の情報」は許容される、と述べられています。
名前空間がはっきりしているなら)必ずしもカタログ内の要素は上記エントリタイプだけで占められるべきというわけではない、ということだと思うので、実装上はキチンと展開名で要素名・属性名を特定する必要があります(名前空間仕様でそうせよと書いてあるのでそうするのが当然ではありますが)。

4. Using Catalogs

カタログの使用方法について、以下の2つの方法があり、それぞれ異なるカタログのエントリを操作します。

  1. 外部実体の置換テキストの検索
  2. リソースの代替URI参照の検索

いずれの方法で使うにしても共通する部分として、

  1. catalogエントリがカタログエントリファイルのルートであること
  2. groupエントリはprefer, xml:baseの2つの属性を指定できること以外、他の要素の意味や走査順序に影響を与えないこと
  3. nextCatalogエントリは現在のカタログエントリファイルの後に処理するカタログエントリファイルを指定すること

の3点があります。

nextCatalogは複数回指定でき、nextCatalogエントリが指すカタログエントリファイルは、指定された順に走査されます。
また、nextCatalogエントリの指すカタログエントリファイルは、現在のカタログエントリファイルのエントリをすべて走査した上で、入力と一致するものが見当たらないときに走査されます。

もう1点、カタログによるURI参照の解決は再帰的ではありません。入力に一致するエントリが発見された時点でエントリから得られた値を返します。
要は、あるエントリからURI Aが得られたらAを使ってカタログからさらなるURI Bを検索して…ということはせず、単にAを返すだけである、ということです。

4.1. External Identifier Entries

まずは外部識別子の解決に関連するエントリの話です。前の章で言えば、冒頭の1番(外部実体の置換テキストの検索)の用途にあたるものです。

外部識別子の解決に関連するエントリは以下のとおりです。

エントリ名 説明
public 指定された公開識別子をもつ実体の置換テキストを、関連するURI参照で検索することを指示する
system 指定されたシステム識別子をもつ実体の置換テキストを、関連するURI参照で検索することを指示する
rewriteSystem 指定された接頭辞を伴うシステム識別子をもつ実体の置換テキストを、別の接頭辞で置換したシステム識別子で検索することを指示する
systemSuffix 指定された接尾辞を伴うシステム識別子をもつ実体の置換テキストを、関連するURI参照で検索することを指示する
delegatePublic 指定された接頭辞を伴う公開識別子をもつ実体の置換テキストを、関連するURI参照で示されたカタログで解決することを指示する
delegateSystem 指定された接頭辞を伴う識別子をもつ実体の置換テキストを、関連するURI参照で示されたカタログで解決することを指示する

この時点では具体的な動作が想像つきにくいものもありますが、詳細は後で説明があるようなので、ここはざっくりイメージできたところでスルーします。

少し話が変わって、カタログの検索モードの話です。
XML仕様では、システム識別子がリソース解決に使用されることを意図しているところ、例えばXML文書や対象リソースが別の場所に移動してしまう場合など、システム識別子がリソース解決の目的には不十分な場合があります。このような事情から、ときにシステム識別子よりも公開識別子がリソース解決には有用な場合があります。
カタログ仕様ではこの事情を反映して、システム識別子か公開識別子のどちらを優先して検索に使用するかで2つのモードが用意されています。

検索モードの具体的な動作としては、以下のようなものになります。

モード 説明
システム識別子優先モード システム識別子が提供され、かつ一致するsystem, rewriteSystem, systemSuffixがない場合、公開識別子を無視してシステム識別子を使用する
公開識別子優先モード 公開識別子が提供され、かつ一致するsystem, rewirteSystem, systemSuffixがない場合、カタログ内で公開識別子に一致するエントリがない場合に限ってシステム識別子を使用する

公開識別子優先モードは名前の割に、カタログ内のシステム識別子関連エントリが無視される(あるいは公開識別子関連エントリに劣後するように扱われる)というわけではないようです。
具体的な動作の詳細は次の4.1.1章で述べられています。

4.1.1. The prefer attribute

prefer属性の話です。

これは前の章で出てきた、システム識別子・公開識別子の優先モードを切り替える属性で、catalog, groupエントリで指定可能です。
指定したモードは子孫エントリにおいて有効です。
初期モードは仕様では指定されておらず、どちらでも構いませんが、アプリケーションが指定できるような仕組みをAPIに備えておく必要があります。

指定できる値は"system", "public"の2通りです。それぞれ、システム識別子優先モード、公開識別子優先モードに対応します。

さて具体的な動作ですが、"public"の場合は外部識別子にシステム識別子が明示されているか否かにかかわらずpublic, delegatePublicが一致候補エントリとして認識されるようになります。
一方で"system"の場合は外部識別子にシステム識別子が明示されているときにpublic, delegatePublicが無視されるようになります。

注意点ですが、1つ目は動作変更対象エントリについてで、これは上記の通りpublic, delegatePublicの2つだけです。他のエントリの動作・認識は変わりません。
2つ目は"public"モードのときのエントリの認識の優先度についてで、名前のイメージに反して、publicdelegatePublicエントリが他のエントリに優先して認識されるわけではありません。公開識別子とシステム識別子の両方が明示されている場合には、明示されたシステム識別子にマッチするエントリがカタログ内で発見できなかった場合に限って公開識別子を優先するという動作になります。仕様中の表でも、公開識別子・システム識別子の双方が発見された場合はシステム識別子を採用すると記載されています。
3つ目は"system"の設定時にpublic, delegatePublicが無視される条件として、システム識別子が明示されていることが必要です。記法宣言に限っては公開識別子だけを指定するということが可能であり、システム識別子が明示されない場合があります。このときは、モードにかかわらずpublic, delegatePublicが認識されます。

最後に、あるカタログエントリファイルでの一致は、後続のカタログエントリファイルでの一致に優先する、と述べられています。
指定モードに限らず、あるカタログエントリファイル内で使用すべきURI参照が確定した場合はそれを使用する、ということを言いたいのかと思います。

4.2. URI Entries

続いてはあるURI参照の代替URI参照検索についての話です。4章冒頭の2番(リソースの代替URI参照の検索)にあたるものです。

代替URI参照の検索に関連するエントリは以下のとおりです。

エントリ名 説明
uri 関連するURI参照を代替とすることを指示する
rewriteURI 指定された接頭辞を伴うURI参照について、別の接頭辞で置換したURI参照を代替とすることを指示する
uriSuffix 指定された接尾辞を伴うURI参照について、関連するURI参照を代替とすることを指示する
delegateURI 指定された接頭辞を伴うURI参照について、関連するURI参照で示されたカタログで解決することを指示する

4.1章末尾での説明と同様、あるカタログエントリファイルで一致するエントリを発見できたなら、後続のカタログエントリファイルを走査する必要はありません。

4.3. Rewrite Entries

システム識別子(であるところのURI参照)やその他のURI参照を書き換える、rewriteSystem, rewriteURIの説明です。

これらはURIの接頭辞を別の接頭辞に置き換えて返すエントリです。
用途はなんとなく想像が付く通り、あるホストやパスを別のホストやパスにリダイレクトすることが主なものだと思います。

仕様中の例では、http://example.com/というリモートホスト上に存在するすべてのリソースへの要求を、ローカルホスト上のfile:///share/mirrors/example/にリダイレクトするためのrewriteSystemの使い方が紹介されています。

<rewriteSystem systemIdStartString="http://www.example.com/"
               rewritePrefix="file:///share/mirrors/example/"/>

リソースへの要求をリダイレクトする方法としては他にも、1つ1つのリソースに対してすべてsystemuriを書き込んでいくという方法もあるわけですが、数が多くなってきたり、動的に変化したりすると対応しきれなくなります。
これに対応する方法としても、rewriteSystem, rewriteURIは有用です。

4.4. Suffix Entries

システム識別子やURIの接尾辞でのマッチングを行う、systemSuffix, uriSuffixの説明です。

典型的には、完全なURIには興味がなく、単にファイル名に基づくマッチングがしたい場合に使用されると述べられています。

仕様中の例では、"xhtml1-strict.dtd"や"docbook.rng"のようなスキーマを一致させる用途が挙げられています。
文書中でこれらの名前をもつURI参照を発見した場合、例えばローカルホスト上のあるファイル(下の例ではfile:///share/mirrors/w3c/xhtml1/xhtml1-strict.dtd)を代わりに返すようにカタログを設定することができます。

<systemSuffix systemIdSuffix="html1-strict.dtd"
              uri="file:///share/mirrors/w3c/xhtml1/xhtml1-strict.dtd"/>

当然ですが、設定する接尾辞があるリソースを一意に特定する手段として妥当なものでないと、思わぬリダイレクトを生み出してしまい、単に混乱を招くだけの設定になり得ます。

4.5. An XML Catalog Example

カタログファイルの例が2つ挙げられています。が、今ここで見てもあんまり意味がない気もするので、省略です。

(どうでもいいことですが、stylesheet.xmlのほうに記載のあったhttp://www.oasis-open.org/committes/entity/stylesheets/base/tr.xslはWeb上では発見できませんでした)

5. Catalog Entry Files

カタログエントリファイルの話です。

この仕様に準拠するアプリケーションは、ユーザがカタログエントリファイルの初期リストを設定できるなんらかの方法を提供する必要があります。
具体的にどんな方法であるかは仕様の定めるところではありませんが、例としては設定ダイアログ、環境変数、プロパティファイルなどが挙げられています。

また、すべての準拠プロセッサは、この仕様で定めた形式で記述したカタログエントリファイルの受け入れができなければなりません。他の形式の受け入れは任意で可能です。
もしアプリケーションが受け入れできない形式のカタログエントリファイルに遭遇した場合は「リソースエラー」です。
「リソースエラー」が何かはこの時点ではわかりませんが、8章に記述があるので、ここではなんしかエラーが起こるんだなと思っておきます。

5.1. Document Control of Catalog Entry Files

解析対象のXML文書内でカタログを指定する方法の話です。

この仕様では、oasis-xml-catalogという処理命令を予約し、唯一の疑似属性であるcatalog属性にURIを指定することで、XML文書からカタログを指定することを可能にしています。
以下の例では、http://example.com/catalog.xmlというカタログファイルを、カタログエントリファイルのリストに加えるように、アプリケーションに指示しています。

<?xml version="1.0"?>
<?oasis-xml-catalog catalog="http://example.com/catalog.xml"?>
<!DOCTYPE doc PUBLIC "-//Example//DTD Document V1.0//EN"
                     "http://www.example.com/schema/doc.dtd">

ユーザやシステム定義のカタログとの優劣については、仕様で「システムもしくはユーザ定義カタログエントリファイルのリストの末尾に加える」と記載があるため、処理命令で指定したカタログが劣後することになります。

oasis-xml-catalog処理命令には以下のように、いくつかの制約があります。

  • XML宣言から文書型宣言の前の、プロローグの中に現れなければならない
  • 内部サブセットや文書型宣言の後に現れた処理命令はエラーであり、指定されたカタログを無視してエラーから回復するべきである
  • スタイルシート処理命令など、URI参照を含む他の処理命令の後に現れた場合もエラーであり、指定されたカタログを無視してエラーから回復するべきである
  • 複数のoasis-xml-catalog処理命令がある場合、それぞれカタログエントリファイルリストの末尾に追加される
  • oasis-xml-catalog処理命令中の相対参照は、処理命令を含む文書の基底URIを基底URIとして解決する
  • カタログエントリファイルを識別するURIは、カタログ解決の対象とならない

3つ目の制約を守るのは難しそうですね。処理命令のデータの解析方法は処理命令依存なので、何をもってURI参照を含む処理命令とみなすべきか不明です。
現実的には、XMLプロセッサやライブラリがサポートする範囲の処理命令について、制約を満たすように実装することになるのかなと思っています。

また、oasis-xml-catalog処理命令をサポートする場合は、機能を無効化する手段をユーザに提供する必要があります。
この機能を識別するURIとして、http://www.oasis-open.org/committees/entity/features/catalog-piを定義しています。JavaAPIなどはFeature/Propertyの指定をURIで行うので、そういうAPIを念頭に置いた定義でしょう。
もしこの機能が無効化されている場合は、oasis-xml-catalog処理命令は無視されなければなりません。

5.2. "Bootstrapping" Catalog Resolution

カタログの読み込みにまつわるカタログ内のURI参照の扱いの話です。

カタログファイル自体がXML文書であることから、URI参照を含み得ます。例えば、カタログの文書型宣言でシステム識別子が指定されるとき、そのURIはどうやって解決するべきでしょうか?

この仕様では、カタログが含むURI参照の指す実体(ないしURI参照)をカタログを通じて解決する機能を備える必要はない、と述べられています。
しかし現実的には上述の例のように、文書型宣言が外部識別子を含む場合もあります。そのような場合に備えて、アプリケーションがカタログエントリファイルを読み込むのに必要な外部識別子やURIを解決するためのブートストラップ機能を備えることが望ましいとされています。
例えば、標準的な外部識別子についてはXMLプロセッサが自らリダイレクトしてリソースを読み込むような方法が考えられます。

また、ユーザの観点でも、可能な限りそのような問題が起こらないような文書の作成方法を選択することが望まれ、例えば文書型宣言の外部識別子にはローカルホストのパスを記述する、などの手段が考えられます。

5.3. Catalog Circularities

カタログの循環についての話です。

これまで何度か述べられている通り、カタログによるリソースないしそれを指すURIの解決は再帰的ではありません。
仕様中の例でもはっきり例をつけて述べられているところですが、http://example.com/path/resourceの代替URI参照であるところのhttp://example.com/alternate/resourceを取得できたとき、カタログからhttp://example.com/alternate/resourceの代替URIが取得可能だったとしても、それ以上の検索をしてはならないということです。(つまり、この例での最終的なカタログの出力はhttp://example.com/alternate/resourceである)

この挙動はあくまでもカタログプロセッサの挙動であって、アプリケーションが望んで再帰的にカタログを検索することは任意です。
アプリケーションが自主的に再帰的にカタログを検索するとき、カタログの出力の循環が起こり得ますが、それを検出してハンドリングするのはアプリケーションの責任です。

このような仕組みにはなっているものの、なおカタログの循環は起こりうると述べられています(delegatePublic, delegateSystemnextCatalogを濫用すればできそうですが、そういうことでしょうか?)
これは可能な限り検出すべきで、検出された場合はエラーとして扱い、一致するエントリを発見できなかったことを報告して回復できます。
しかし、インターネット上のリソースの動的な性質から、循環の検出はなおも難しいと述べられており、この仕様では、循環参照を検出できないこと自体は仕様への準拠違反とみなさないとされています。

6. XML Catalog Entries

各カタログエントリと属性の文法の説明です。

ここで定義されるエントリはすべて上述の名前空間名で識別されます。定義外の要素・属性は含まれていても良いですが、子孫のすべての要素を含めて無視され、意味は定義されません。

6.1. Common Attributes

ほとんどの要素に共通して定義される2つの属性であるid, xml:baseの話です。

xml:baseは、XML Base仕様で定義されている通り、要素(ここではカタログエントリ)の基底URIを変更する意味を持ちます。
idは、カタログエントリを一意に識別するための値を提供する属性です。xml:idとは無関係で、単にカタログの文法を指示するDTDでID属性を付与された属性です。

他の属性にも共通する注意点ですが、属性はカタログ仕様の名前空間に属するように修飾してはなりません
仕様中の例でいえば、xmlns:cat="urn:oasis:names:tc:entity:xmlns:xml:catalog"と定義されているとき、<cat:group id="groupId">は合法ですが、<cat:group cat:id="groupId">は違法です。あくまでもそれぞれの属性はエントリタイプの中で意味づけられるものであって、名前空間の中で共通して意味づけられるものではないと考えることができそうです。
結果として修飾されていない名前のカタログ仕様定義外の属性はすべて違法ということになるわけですね。

6.2. Public Identifier Normalization

公開識別子の正規化の話です。

公開識別子の正規化はXML仕様でも規定されているルールなのでその通りやればいいだけの話なのですが、カタログファイル内では一般の属性値に公開識別子として認識されるべきものがあるため、そのような属性に対しても同様の正規化を行うことを求められています。

6.3. System Identifier and URI Normalization

システム識別子とURIの正規化の話です。

おおよそ、UTF-8文字列として解釈した上でパーセントエンコーディングを実施して比較せよ、という内容になります。

ちなみに、この正規化プロセスはべき等であるということになっています。
%エンコードしたらべき等ではなくなるのでは?という気がしたのですが、この仕様では%エンコード対象の文字ではありません。
RFC3986などに適合した既存の実装でのエンコード%エンコードされるので、挙動が違うことになるかと思います。

6.4. URN "Unwrapping"

"publicid" URN名前空間の「アンラッピング」の話です。"Unwrapping"をなんと訳せばいいのかわからなかったので、そのまま「アンラッピング」と呼びます。

そもそも"publicid" URN名前空間とはなんぞやという話ですが、RFC3151(仕様中でもリンクされています)で規定されたURN名前空間です。
公開識別子をURNの文法で記述するための意義や変換(アンラップの反対なのでラップ?)する方法を述べているRFCだと理解しています。

カタログプロセッサは、公開識別子として指定されたラップされたURNを、公開識別子として比較する前にアンラップし、公開識別子の記法に戻してあげる必要があります。
まだあまり詳しくRFC3151の内容を読めてはいませんが、基本的にはurn:publicid:接頭辞を除去し、文字の適切な置き換えを行うことで実現できるものだと思います。

RFC3151での処理方法から、"publicid" URNをアンラップした結果である公開識別子は正規化済であるはずです。(なぜなら、ラップする前に正規化すると定められているから)
しかし、もしアンラップした結果が未正規化済の公開識別子であった場合は、カタログプロセッサが正規化を行います。

"publicid" URNをアンラップせずにカタログ内のエントリを検索してはいけません。

6.5. Catalog Elements

ここからは、カタログエントリである要素の定義です。

6.5.1. The catalog Entry

catalog要素は、カタログエントリファイルのルート要素であり、カタログエントリファイルに1つだけ存在します。
先述の通り、prefer, xml:base, idが設定されるかもしれません。

属性によってモードや基底URIが設定される可能性がある以外では、他のカタログエントリを格納する以外の意味は特にありません。

6.5.2. The group Entry

group要素は、preferxml:baseをあるエントリの集合にまとめて適用するためのラッパーとなる要素です。これ以外に特に意味はありません。

6.5.3. The public Entry

public要素は、publicId属性値にマッチする公開識別子を、uri属性で指定されたURI参照に関連付けます。

公開識別子の一致判定は、正規化済値の一致で判定します。
URIが相対参照である場合は、現在有効な基底URIに対して解決しなければならず、またフラグメントを含めてはなりません。(要は絶対URIとして返す必要がある)

6.5.4. The system Element

system要素は、systemId属性値にマッチするシステム識別子を、uri属性で指定されたURI参照に関連付けます。

システム識別子の一致判定は、6.3章で述べられた正規化済値の一致で判定します。
相対参照の解決については、public要素と同様です。

6.5.5. The rewriteSystem Element

rewriteSystem要素は、システム識別子のsystemIdStarting属性値にマッチする接頭辞を、rewritePrefixで置換します。

システム識別子の接頭辞の一致判定は、6.3章で述べられた正規化済値の一致で判定し、複数マッチするものがあれば、最長一致で優劣をつけます。
rewritePrefixURI参照でなければならず、相対参照であるなら現在有効な基底URIに対して解決しなければなりません。

システム識別子のsystemIdStartingにマッチする部分は除去され、rewirtePrefixで置換した結果の全体を新しいURI参照として返します。

ところで、たとえばsystemIdStarting属性値がhtt(httpではなく)だったとして、それをhttp://www.example.com/にマッチさせてもいいものなんですかね?
それが意味をもつことはあまりないと思いますが(例えばrewritePrefixftなら意味があるかもしれませんが)、特に何も言及がないので、URIコンポーネントには関心を持たず、単に文字列置換でよいということだろうと理解しています。

6.5.6. The systemSuffix Element

systemSuffix要素は、接尾辞がsystemIdSuffix属性値にマッチするシステム識別子を、uri属性で指定されたURI参照に関連付けます。

システム識別子の一致判定を正規化済値で行うこと、URIが相対参照であるなら現在有効な基底URIで解決しなければならないこと、返すURIが絶対URIでなければならないこと(フラグメントを含めないこと)、などはこれまでと同様です。
また、複数一致するエントリがあるときに、最長一致で優劣をつけることは、rewriteSystemと同様です。

6.5.7. The delegatePublic Element

delegatePublic要素は、接頭辞がpublicIdStartString属性値にマッチするシステム識別子を解決するための代替カタログを、catalog属性で指定されたURI参照で指示します。

公開識別子の一致判定は正規化済値で行うこと、URI参照は絶対URIとして解決すべきことは今までどおりです。

今まで見てきた要素と異なる点として、最長一致を指定されていない点があります。
これについて、詳しい動作は7章の各項目で見ていくことになるのですが、簡単に言えば、マッチする委譲先カタログはすべて所定の順序で検索されることになります。

6.5.8. The delegateSystem Element

delegateSystem要素は、接頭辞がsystemIdStartString属性値にマッチするシステム識別子を解決するための代替カタログを、catalog属性で指定されたURI参照で指示します。

システム識別子は正規化済値で比較するというのは今までどおりで、その他はdelegatePublicと同じです。

6.5.9. The uri Element

uri要素は、name属性値にマッチする外部識別子の一部ではないURI参照を、uri属性値で指定された代替URI参照に関連付けます。

URI参照は6.3章で示されている正規化を行った値で比較します。
代替URI参照が相対参照であるなら、現在有効な基底URIで解決した絶対URIが返されるというのは今までの要素と同じです。

6.5.10. The rewriteURI Element

rewriteURI要素は、外部識別子の一部ではないURI参照のuriStartString属性値にマッチする接頭辞を、rewritePrefixで置換します。

URIの比較、置換の方法、最長一致による優劣の判定、返り値の絶対URIへの解決は、rewriteSystemなどと同様です。

6.5.11. The uriSuffix Element

uriSuffix要素は、接尾辞がuriSuffix属性値にマッチする外部識別子の一部ではないURI参照を、uri属性で指定されたURI参照に関連付けます。

動作はsystemSuffixと同様です。

6.5.12. The delegateURI Element

delegateURI要素は、接頭辞がuriStartString属性値にマッチするURI参照を解決するための代替カタログを、catalog属性で指定されたURI参照で指示します。

動作はdelegateSystemと同様です。

6.5.13. The nextCatalog Element

nextCatalog要素は、実体ないしURI参照の解決のために走査すべきである追加のカタログエントリファイルを、catalog属性で指定されたURI参照で指示します。

catalog属性値が相対参照であるなら、現在有効な基底URIで解決した絶対URIを元にカタログエントリファイルを指示します。

この属性によって読み込まれたカタログエントリファイルの基底URIは、そのファイルの場所を元に決定され、現在のカタログのxml:base情報は継承されません。

7. Catalog Resolution Semantics

カタログ解決の方法の話です。それぞれのエントリの優先度やカタログエントリファイルの順序付きリストの構成法など、具体的な解決の仕組みを述べる章になっています。

解決を開始するには、まず1つカタログエントリファイルが必要であり、また解決の対象となる外部識別子かURI参照のいずれか一方がカタログへの入力として必要となります。

7.1. External Identifier Resolution

まずは外部識別子が対象である場合の解決方法です。

7.1.1. Input to the Resolver

カタログリゾルバへの入力は、公開識別子かシステム識別子かの一方、または両方です。

システム識別子については、入力が相対参照であっても絶対化はされません。相対参照を与えられたのであれば、まず相対参照での解決を試みます。
しかし、解決が失敗した場合には、絶対化して再試行しても構いません。

公開識別子については、6.4章で説明されたURNのアンラッピングを行う必要があります。

特殊なパターンとして、システム識別子がpublicid名前空間のURNである場合があります。このときは、まずそのURNを6.4章の手順に従ってアンラップし、その上で公開識別子の状態に従って、以下3パターンのどれかを選択します。

  1. 公開識別子がなければ、アンラップされたシステム識別子を公開識別子として扱い、システム識別子は提供されていないかのように扱う
  2. 公開識別子が提供されており、アンラップされた結果がアンラップ済のシステム識別子と一致するならば、システム識別子は提供されていないかのように扱う
  3. 公開識別子が提供されており、アンランプされた結果がアンラップ済のシステム識別子と異なるならば、エラーとする

3.の場合は、システム識別子を破棄し、公開識別子として提供された値のみを採用して処理を続行することで、エラーから回復できます。

7.1.2. Resolution of External Identifiers

入力が定まったところで、以下のように解決を進めます。

  1. 現在のカタログエントリファイルリストの最初のファイルから解決を開始する
  2. システム識別子が指定され、かつ一致するsystemエントリがあるなら、最初に一致するエントリの絶対化されたuri属性値を返す
  3. システム識別子が指定され、かつ一致するrewriteSystemエントリがあるなら、最長一致するエントリのsystemStartString属性で接頭辞を書き換えた値を返す
  4. システム識別子が指定され、かつ一致するsystemSuffixエントリがあるなら、最長一致するエントリの絶対化されたuri属性値を返す
  5. システム識別子が指定され、かつ一致するdelegateSystemエントリがあるなら、委譲を行う。委譲は以下のルールで行う

    • 一致したすべてのエントリの絶対化されたcatalog属性値から、新しいカタログエントリファイルリストを作成する
    • リスト内での順序は、systemStartString属性が最長一致するものを先頭、それを除いた集合の中で最長一致するものを2番目、と決める
    • 新しく作成されたリストには、現在のリストは委譲のプロセスでは考慮しない
    • ("These are the only..."の段は何を言っているのかわかりませんでした。"entity"が何を指すのかわかりませんが、委譲されたリストは一致したシステム識別子用のものであってそれ以外のもの、すなわち公開識別子を検索する目的で使われない、的な意味でしょうか?)
    • 委譲先のカタログエントリファイルリストと、このリストを作成する元となったシステム識別子のみを入力とし、1.の手順から検索を再開する
  6. 公開識別子が指定され、かつ一致するpublicエントリがあるなら、最初に一致するエントリの絶対化されたuri属性値を返す。なお、システム識別子も指定されている場合、処理されるのはprefer"public"と設定されているときだけである

  7. 公開識別子が指定され、かつ一致するdelegatePublicエントリがあるなら、委譲を行う。なお、システム識別子も指定されている場合、処理されるのはprefer"public"と設定されているときだけである。委譲は以下のルールで行う

    • 一致したすべてのエントリの絶対化されたcatalog属性値から、新しいカタログエントリファイルリストを作成する
    • 順序の決定方法はdelegateSystemと同様、最長一致するものから先頭に配置する
    • 新しく作成したリストでdelegateSystemの場合と同様に検索を開始する。入力は公開識別子のみで、システム識別子は無視する。
  8. 現在のカタログエントリファイルにnextCatalogエントリがあるなら、そのエントリのcatalog属性が指すカタログエントリファイルを、出現順に現在のカタログエントリファイルリストの、現在のカタログエントリファイルの直後に挿入する

  9. 現在のカタログエントリファイルリストに読み残しがあるなら、次のカタログエントリファイルを読み、2.の手順から検索を再開する
  10. ここまで到達した場合は、マッチするエントリがなかったことをアプリケーションに報告する

つまるところ、

system->rewriteSystem->systemSuffix->delegateSystem
->(委譲先のリストでシステム識別子を再帰的に検索)
->public->delegatePublic
->(委譲先のリストで公開識別子を再帰的に検索)
->nextCatalog
->(リスト内の次のカタログエントリファイル)

という順序で検索が進むことになります。

7.2. URI Resolution

続いては、URI参照が対象である場合の解決方法です。

7.2.1. Input to the Resolver

入力は常に1つのURI参照です。

システム識別子もそうでしたが、相対参照が入力であるなら、相対参照をそのまま使用します。それが失敗した場合は、参照を解決し、絶対URIを入力として再試行します。

入力のURI参照がpublicid名前空間のURNである場合は、公開識別子にアンラップし、外部識別子の解決手順にリダイレクトします。このとき、システム識別子は指定されなかったものと扱います。

7.2.2. Resolution of URI references

外部識別子の解決手順にリダイレクトされなかった場合、以下のように解決を進めます。

  1. 現在のカタログエントリファイルリストの最初のカタログエントリファイルから解決を開始する
  2. uriエントリがあるなら、最初に一致したエントリの絶対化されたuri属性値を返す
  3. rewriteURIエントリがあるなら、最長一致するエントリのuriStartString属性値で接頭辞を書き換えた値を返す
  4. delegateURIエントリがあるなら、委譲を行う(委譲の手順はdelegateSystem, delegatePublicと共通なので、割愛)
  5. 現在のカタログエントリファイルにnextCatalogエントリがあるなら、そのエントリのcatalog属性が指すカタログエントリファイルを、出現順に現在のカタログエントリファイルリストの、現在のカタログエントリファイルの直後に挿入する
  6. 現在のカタログエントリファイルリストに読み残しがあるなら、次のカタログエントリファイルを読み、2.の手順から検索を再開する
  7. ここまで到達した場合は、マッチするエントリがなかったことをアプリケーションに報告する

手順としては外部識別子の解決手順において、公開識別子が指定されなかった(あるいはシステム識別子が指定されてかつprefersystemで設定されている)場合の手順と同じになります。

8. Resource Failures

カタログ処理におけるエラーの話です。

まず、読み込み対象となったカタログエントリファイルが何らかの問題で読み込めなかった場合です。これは、読み込みに失敗したカタログエントリファイルを無視して処理を続行することで回復しなければなりません。

対象ファイルの取得はできたが、カタログエントリファイルとして理解不能な形式であった場合も、読み込みに失敗した場合と同様に、対象のカタログエントリファイルを無視して処理を続行して回復しなければなりません。
「カタログエントリファイルとして理解可能である」ためには、以下の条件を満たす必要があります。

  1. XML名前空間仕様に準拠した整形式XML文書である
  2. 文書要素の型がcatalogである
  3. 文書要素の名前空間名がurn:oasis:names:tc:entity:xmlns:xml:catalogである

この仕様で定められた形式以外のカタログを受理することはエラーではありません

9. Changes in XML Catalogs V1.1

XML Catalogs V1.0と何が変わったかという話ですが、私はV1.0の仕様を知らないので、割愛です。

あとがき

そんな大した仕様ではないだろうと思っていたのですが、読んでみるとかなり長くて疲れました。

大部分についてやるべきことは単純ですが、URIの正規化の部分は普通のRFC3986のパーセントエンコーディングとは違う方法なので、ちょっと工夫がいるかもしれません。
RFC3986準拠のエンコード方法はべき等ではないので、システム識別子やカタログの属性値が仮に生データであるものと1回エンコードされたものとで混ざっている場合とかを考えるとどうすればいいやら。
とはいっても、そんなことを言っていては何もできないのも事実で、現実的には、システム識別子もカタログの属性値も、それが生データである(一度もエンコードされていないURIである)とみなして、このカタログ仕様に従ったエンコードをやるしかないのでしょう。

あとは委譲のプロセスがよくわかっていないです。たぶん単純に再帰検索すればいいだけだと思いますが、本文中でも書いたとおり"entity"が何を指すのかわからなかったり、リストを識別する形容詞が"original"とか"normal"とか、それぞれ何を指すのか不明瞭だったり、非常にモヤモヤします。
古い仕様だからなのか、ググってもあまり細かい挙動に触れた記事とかも見つかりませんでした。(英語記事ならあるのかもしれませんが、英語非対応の私には厳しい)
悩んでもしょうがないので、libxml2の実装でも見ながら自己解決しようと思います。

実装するとなるとテストスイートが欲しくなるのですが、これもざっと探した感じでは見当たりません。困りました。
仕様の文章を読んだだけでは先述のように不明瞭な部分も出がちで、テストスイートから想定挙動を把握することが多いので、テストスイートが見当たらない仕様に対する実装の挙動の担保は難しいです。
これもlibxml2とかの既存実装から漁ってみるしかないのでしょうね。あるいは例によってWayback MachineからOASISのホームページを漁ると掘り出せるのか…

次は何に手を出そうか悩んでいます。
せっかくXPathを実装したのだから、それが活かせそうなXSLT, XPointerあたりの実装に手を出したい思いもありつつ、XML SchemaやRelaxNGのようなスキーマ検査の実装も必要なのではないかと思ったり。
これらの中なら、XSLT, XML Schemaが実用上役に立つ機能なので、そのどっちかかなと思いますが、XML Schemaのデータ型は他の仕様でも共通して使われることがあり、他の仕様をみてからじゃないと実装上の設計が難しくないかなという心配もあります。
非常に悩ましいですが、XML Catalogsの実装をやりながらのんびり考えようかなと思います。

XMLのお勉強その10 - xml:id Version 1.0

前回

XMLのお勉強その9 - XML Path Language (XPath) Version 1.0 - 競プロ備忘録

読んでいく仕様書

原文:xml:id Version 1.0
和文:なし

また前回から時間があきました。この間に木の実装を済ませ、XPathを実装していました。

XPathの実装を進めるうちに、id関数の実装で必要なID検索の実装をしていないことに気づき、一通り実装をしたあとに、そういえばこんな仕様もあったなと思い出して読むことにしました。

この仕様は大変ググらビリティが悪く、"xml:id"と打ち込むとURLと勘違いされてまともに検索ができず。クオーテーションで囲むと見つかりましたが、なんと和文がありません。
ざっと目を通した感じではXML Base仕様と似たような感じで非常に小さい仕様なので、まあいいか…ということで英文オンリーでいくことにします。

本編

1 Introduction

いつもどおり、まずはxml:idが何者かという話と、その動機についてです。

XMLでは、DTDにID型の属性を宣言することで、要素を一意に識別する値を与えることができます。もちろん、妥当でないXML文書では必ずしもその値が一意であるという保証はないわけですが、妥当なXML文書ではその属性値を用いて、ある要素を特定することが可能になります。

しかし、特に外部サブセットの検査は、完全に仕様に準拠したXMLプロセッサであっても任意(妥当性を検証しないプロセッサ)ですし、あるいは他の仕様によってDTD自体が消去される場合もあります(仕様ではSOAPが例として挙げられています)。

DTD以外の方法としては、XML Schemaなどのスキーマ言語を使用することで、DTDと同じように属性にID型を与えることができますが、結局外部から与えるものである以上、常に使用できると仮定するのは難しいでしょう。

そこで、xml:idという新たな属性を定義し、検証の有無、宣言の有無などに関わらず要素を特定する識別子を与える方法を提供する、というのがこの仕様の目的となっています。

この仕様に従うプロセッサを使う限り、xml:id属性は常にID型を与えられているという期待のもと、XML文書を作成できるようになります。

2 Terminology

毎度おなじみですが、助動詞の用法はRFC2119準拠です。

また、いくつかの用語も定義されています。

xml:idプロセッサ」とは、XMLプロセッサと連携して動作し、XML文書内のIDへのアクセスを提供するソフトウェアモジュールです。
「xm:idエラー」とは、xml:idプロセッサがこの仕様の制約への違反を発見したときに発生する、致命的なエラーではないエラーです。

「検証」については、XML文書を文法、または規則セットと比較し、文書の実際の構造が文法や規則の制約を満たすか判断する手順のこと、と述べられています。基本的にはXML仕様の検証と同義だと思いますが、XML仕様外の検証スキーム(XML SchemaやRelaxNGなど)も念頭に置くと、こういうまどろっこしい説明になるのかもしれません。
また、検証においては属性の型割り当てが行われ、どの属性がID属性であるかを判定する、とも述べられています。DTDで言えばID型を割り当てる属性リスト宣言を指し、XML Schemaであればxs:ID型で属性型を宣言することを指しているのでしょう。

しかし、型割り当ては必ずしも検証とセットで行われるとは限らず、妥当性検証を行わないXMLプロセッサが、文書の妥当性に関する情報を持たずに、DTDで属性に型を割り当てることもできる、とも述べられています。
これを踏まえての定義だと思いますが、「ID型割り当てプロセスにより、xml:id属性値はID型となる」と定義されています。xml:id属性がID型として割り当てられるプロセスには必ずしも検証が必要ない、ということが言いたいんですかね?

3 Syntax

この仕様で意味を定義して使用を許可する文法というのは、xml:idという、ここまでで散々見てきた属性ただ1つです。
名前空間仕様ではxml接頭辞はXML関連仕様での活用のために予約されており、XML仕様でもxmlやその大文字小文字違いから開始する名前は同様予約されているわけですが、この仕様はその予約名のうちの1つを割り当てる仕様でもあります。

名前空間をサポートするXMLプロセッサであれば、名前空間仕様に従ってxml:id属性はhttp://www.w3.org/XML/1998/namespaceに属するものとみなされます。
名前空間をサポートしないのであれば、単にxml:idという名前として識別されるだけです。

イントロでも述べられていたとおり、DTDや外部スキーマ言語によるID型割り当ては相互運用性に難があるため、この仕様はID属性を持つ属性の名前にxml:idを使用することを推奨しています。

4 Processing xml:id Attributes

xml:idをどのように処理するかの話です。

それぞれのxml:id属性は、以下の流れで処理されます。

  1. ID型属性のルールで属性値の正規化を行う
  2. Infosetの"normalized value"プロパティを正規化値で更新する
  3. 正規化値にID型割り当てを行う

xml:idプロセッサは、以下の制約がすべてのxml:id属性で保たれることを保証しなければなりません。

  • 正規化値が名前空間仕様で定義されるNCNameにマッチすること
  • 属性の型が宣言されているのであれば、それがIDであること

さらに、xml:idプロセッサは以下の制約も保たれることを保証すべきであると述べられています(上の制約は"MUST"だが、こちらは"SHOULD")

  • ID型属性の値が文書中で一意であること

制約を満たさないxml:id属性はxml:idエラーを発生させます。

xml:idプロセッサは、制約を満たさないものも含めてすべてのxml:id属性にID型割り当てをしなければなりません。
割り当ての結果として、InfosetのAttribute Information Itemのreferenceプロパティを更新するべきである、とも述べられています。

こういった処理をどのタイミングで実施するかは、アプリケーション依存になります。
しかし、アプリケーション利用者は、ID値に変更が加えられるたびにxml:id属性の処理が行われることを期待してもよいとされています。

5 Informing the Application

xml:idにID型割り当てが発生した場合、xml:idプロセッサはそれをアプリケーションに報告しなければなりませんが、その方法は実装依存であると述べられています。

例としては、

  • InfosetのAttribute Information Itemのattribute typeプロパティを更新する
  • PSVIのtype definitionプロパティを更新する

などが挙げられています。
何にしても重要な要件は、アプリケーションがID型割り当ての結果を認識できるようにする、という点になります。

6 Errors

前のいくつかの章でも述べられていますが、この仕様の制約に違反すると、xml:idエラーが発生します。

xml:idエラーは致命的なエラーではないですが、xml:idプロセッサによって報告されるべきであると述べられています。
また、相互運用性から、エラーは握りつぶさないことを強く推奨されています。

7 Conformance

7.1 Conformance to xml:id

何らかの検証技術を使用するXMLプロセッサに依存するアプリケーションの適合性は、4章で説明される属性の処理と構文の使用と、この仕様の制約と検証技術の規則の両方への適合性によって構成されます。

検証を行わないXMLプロセッサに依存するアプリケーションの適合性は、4章で説明される属性の認識と、この仕様の制約への適合性で定義されます。

本文で”MUST”と指定された制約は、仕様への適合するためには満たすことが義務づけられます。併せて、他の制約も保証することが望ましいとされます。

文書がxml:idエラーを起こさない場合、その文書は仕様に適合しています。

7.2 XML Information Set Conformance

XML Infoset仕様への適合性の話です。

正しい処理を可能にするためには、入力情報セットには以下の情報項目が含まれている必要があります。

  • Element Information Itemのattributesプロパティ
  • Attribute Information Itemのnamespace name, local name, normalized valueプロパティ

さらに、出力情報セットには以下の情報項目が含まれる場合があります。

  • Attribute Information Itemのattribute typeプロパティ

8 Extensibility

この仕様の拡張性についての話ですが、拡張は一切できない、と述べられています。

xml:id属性の名前、IDとみなされる属性値の集合、それらが出現可能な場所などを変更したり、その他の拡張を行ったりするための拡張は一切ありません。

あとがき

全体的に、なんとなく機械翻訳感のある文章になってしまいましたが、まあ実際機械翻訳を使いまくった結果です。
私は英語非対応なので、基本的には機械翻訳された文章を読んでから原文と照らし合わせるような感じで読んでいました。

仕様の内容としては難しいことは一切言っていなくて、要はxml:idという属性を使えばどんなスキーマ言語を使っても(あるいは一切スキーマ言語を使わなくても)常に相互運用できる一意な要素の識別子を与えられるので、みんな使ってね、ということだと理解しています。

既存のSAXパーサに加えるべき処理があるとすれば、xml:id属性についてID属性としての宣言があるかのように扱う場合分けを仕込むことと、xml:id属性の宣言がID型ではなかったときにエラーを出し、ID型として宣言されたかのように宣言をすり替えること、の2点になるのかなと思います。
宣言自体をすり替えると書き戻しができなくなるので、属性リスト宣言を取得するメソッド側でxml:id属性の場合分けをする、とかでもいいのかもしれません。

xml:id仕様サポートをパーサオプションとして提供するかは一瞬悩みましたが、まあxml接頭辞は仕様で予約されてるわけなので、それを無視して仕様と違う使い方をしているのであればそれは文書作成者が悪いという割り切りで、オプションでなく常時サポートにするのが楽(というかそれがあるべき実装)な気はしています。