競プロ備忘録

競プロerの備忘録

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の実装をやりながらのんびり考えようかなと思います。