MSXML に XHTML 1.1 を解析させる

English translation is ./xhtml11-msxml.en.

目次

概要

少なくともバージョン 3.0 までの MSXML は、通常の XHTML 1.1 適合文書を正しく解析することができません。以下では、その具体的な不具合と対処法について解説します。

ただし、DTD の解析が (すなわち PSVI が) 不要ならば、はじめから DTD を読ませないという対処法もあるそうです (参照: MSXML で XHTML ファイルを読み込む際の注意点)。もちろん文字実体参照を利用している場合などは DTD を解析する必要があるのですが、可能であればこちらの対処法の方が好ましいでしょう。

# なお、筆者は Windows ユーザでないため、MSXML 4.0 での対応状況や、細かいバージョンによる実装状況などはチェックできていません。その辺り、興味のある方にフォローしていただけると助かります。あと、パラメタ実体参照の解析に関するエラーメッセージとか提供していただけるとうれしいかも。

IGNORE 区間内の解析 (MSXML 2.x/3.0)

XML 1.0 第二版 には次のような記述があります (以下、仕様書の文言は便宜上 JIS XML から引用します)。

無視される条件付きセクションの内容を解析するとき, キーワードに続く "[" の後のすべての文字は, 条件付きセクションを開始する "<![" と終了する "]]>" を除き, この条件付きセクションの終了が見つかるまで無視する。この処理において, パラメタ実体参照は認識されない。

この規則を利用して、XHTML 1.1 DTD では Modular Framework Module の前後の IGNORE 区間内に未定義のパラメタ実体参照が記述されています。

<!ENTITY % xhtml-prefw-redecl.module "IGNORE" >
<![%xhtml-prefw-redecl.module;[
%xhtml-prefw-redecl.mod;
<!-- end of xhtml-prefw-redecl.module -->]]>

(中略)

<!ENTITY % xhtml-framework.module "INCLUDE" >
<![%xhtml-framework.module;[
<!ENTITY % xhtml-framework.mod
     PUBLIC "-//W3C//ENTITIES XHTML Modular Framework 1.0//EN"
            "http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-framework-1.mod" >
%xhtml-framework.mod;]]>

(中略)

<!ENTITY % xhtml-postfw-redecl.module "IGNORE" >
<![%xhtml-postfw-redecl.module;[
%xhtml-postfw-redecl.mod;
<!-- end of xhtml-postfw-redecl.module -->]]>

%xhtml-prefw-redecl.mod; 及び %xhtml-postfw-redecl.mod; という二つのパラメタ実体参照は、モジュールの追加を簡易にするためのものです。本来これらのパラメタ実体参照は、%xhtml-prefw-redecl.module; または %xhtml-postfw-redecl.module;INCLUDE に上書きされた時のみ解析されることになっているわけです。

しかし、MSXML 2.x/3.0 はこの仕様を正しく実装しておらず、IGNORE 区間内の実体参照も (内部的に) 展開しようとします。このため、XHTML 1.1 文書を解析すると、上記の箇所で未定義のパラメタ実体参照を認知してエラーを返すことになるのです。

使用する前にパラメータ エンティティを定義しなければなりません。リソース 'http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd' の実行エラーです。ライン 85、位置 2

%xhtml-prefw-redecl.mod;
-^

なお、IGNORE 区間の処理に関しては、SGML では明確に(IGNORE 区間では) そのマーク区間に文字がないものとして扱うと述べられています。従って、SGML のサブセットである XML もこの規定に則る必要があるのですが、XML 1.0 の初版では、IGNORE 区間内のパラメタ実体参照の扱いに関しての明確な規定が存在しませんでした。

ですから、この実装を「バグ」の一言で片付けるのは、少々酷ではないかとも思うのですが、やはり不具合には違いありません。この不具合を回避するには、DTD でこれらの実体に仮の値を与えて下さい。

<!ENTITY % xhtml-prefw-redecl.mod "" >
<!ENTITY % xhtml-postfw-redecl.mod "" >

相対パスの解決 (MSXML 2.x/3.0)

XHTML 1.1 DTD には次のような記述があります。

<!-- declare Document Model module instantiated in framework
-->
<!ENTITY % xhtml-model.mod
     PUBLIC "-//W3C//ENTITIES XHTML 1.1 Document Model 1.0//EN"
            "xhtml11-model-1.mod" >

ここで記述されているシステム識別子は、次の通り、実体宣言の記述されている実体、すなわち http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd を基底とする相対パスとして解釈することになっています (参照: システム識別子の基底)。

この規格の適用範囲外の情報 (例えば, ある特定の DTD の特別な XML 要素又は特定の応用プログラムの仕様によって定義された処理命令) によって上書きされない限り, 相対的な統一資源識別子 (URI) は, その実体の位置, すなわち, その実体宣言が現れる資源に相対的とする。実体宣言が現れる資源とは, この実体宣言を宣言として構文解析した時点で, 先頭の '<' を含む外部実体とする。したがって, 統一資源識別子 (URI) は, 文書実体・外部 DTD サブセットを含む実体・なんらかの外部パラメタ実体に対して相対的とする。

ところが MSXML 2.x/3.0 は、このシステム識別子を実体参照の記述されている実体、すなわち http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-framework-1.mod を基底とする相対パスとして解釈して、次のようなエラーを返します。

指定されたオブジェクトは見つかりません。リソース 'http://www.w3.org/TR/xhtml-modularization/DTD/xhtml11-model-1.mod' の実行エラーです。ライン 89、位置 18

%xhtml-model.mod;]]>
-----------------^

この不具合を回避するためには、xhtml-model.mod のシステム識別子を絶対パスで宣言し直す必要があります。

<!ENTITY % xhtml-model.mod
     PUBLIC "-//W3C//ENTITIES XHTML 1.1 Document Model 1.0//EN"
            "http://www.w3.org/TR/xhtml11/DTD/xhtml11-model-1.mod" >

ただし、この対処方法では取得するリソースの数が多過ぎるためか、Internet Explorer がビジー状態になってしまう場合があるようです。その場合には、参照する DTD を xhtml11.dtd ではなく xhtml11-flat.dtd にすれば、一応この不具合を回避できます。

<!DOCTYPE html
     PUBLIC "-//W3C//DTD XHTML 1.1//EN"
            "http://www.w3.org/TR/xhtml11/DTD/xhtml11-flat.dtd" >

しかしながら、フラット DTD を利用する場合には、%xhtml-qname.redecl; などのプレースホルダーが展開されているため、DTD の拡張に支障をきたす場合もあり、痛し痒しというところです。

# まあ、現実的にはフラット DTD を利用する方が幾分ましだと思いますが…。

パラメタ実体参照の解析 (MSXML 2.x)

MSXML 2.x は XHTML 1.1 DTD の次のような記述に対してエラーを返します (この不具合は MSXML 3.0 では修正されているようです)。

<!ENTITY % XHTML.pfx  "" >
<!ENTITY % caption.qname   "%XHTML.pfx;caption" >
<!ENTITY % table.content
     "( %caption.qname;?, ( %col.qname;* | %colgroup.qname;* ),
      (( %thead.qname;?, %tfoot.qname;?, %tbody.qname;+ ) 
      | ( %tr.qname;+ )))"
>
<!ELEMENT %table.qname;  %table.content; >

エラーメッセージが手元にないのがアレですが…とりあえず、エラー内容は「要素型宣言中の %caption.qname;? という記述は不正である」といった具合のものです。

このエラーは、パラメタ実体 foo.content の実体値内の %bar.qname;? といった記述を (%bar.qname;)? と括弧で括ってやれば解消します。この挙動から察するに、恐らく MSXML 2.x にはパラメタ実体参照の展開上の不備があるものと思われます。

本来、実体値内に記述されたパラメタ実体参照は、実体宣言の解析の際に直ちに展開されることになっています。

パラメタ実体への参照がリテラル実体値の中で現れたとき, 置換テキストは, 参照自体の代わりに, 参照があった位置に文書の一部としてあったものとして処理される。

従って、上の例の末尾の要素型宣言は、正しくは次のように解析されなければなりません。

  1. 実体宣言 <!ENTITY % XHTML.pfx ... > を解析。パラメタ実体 XHTML.pfx の値を "" (空文字列) として保持。
  2. 実体宣言 <!ENTITY % caption.qname ... > を解析。実体値中のパラメタ実体参照 %XHTML.pfx; を空文字列に展開。パラメタ実体 caption.qname の値を "caption" として保持。
  3. 実体宣言 <!ENTITY % table.content ... > を解析。実体値中のパラメタ実体参照 %caption.qname;"caption" に展開。パラメタ実体 table.content の値を " "( caption?, ... )" として保持。
  4. 要素型宣言 <!ELEMENT %table.qname; %table.content; > を解析。宣言内のパラメタ実体参照 %table.content;" "( caption?, ... )" に展開。table を、内容モデル ( caption?, ... ) の要素型として定義。

つまり、末尾の要素型宣言を、あたかも <!ELEMENT table ( caption?, ... ) > と宣言されていたかのように解析する必要があるわけです。

ところが、MSXML 2.x は恐らく次のような手順でパラメタ実体参照の解析を行っているものと思われるのです。

  1. 実体宣言 <!ENTITY % XHTML.pfx ... > を解析。パラメタ実体 XHTML.pfx の値を "" (空文字列) として保持。
  2. 実体宣言 <!ENTITY % caption.qname ... > を解析。パラメタ実体 caption.qname の値を "%XHTML.pfx;caption" として保持。
  3. 実体宣言 <!ENTITY % table.content ... > を解析。パラメタ実体 table.content の値を " "( %caption.qname;?, ... )" として保持。
  4. 要素型宣言 <!ELEMENT %table.qname; %table.content; > を解析。宣言内のパラメタ実体参照 %table.content;"( %caption.qname;?, ... )" に展開。
  5. 要素型宣言 <!ELEMENT %XHTML.pfx;table ( %caption.qname;?, ... ) > を解析。宣言内のパラメタ実体参照 %caption.qname;"%XHTML.pfx;caption" に展開。
  6. 要素型宣言 <!ELEMENT table ( %XHTML.pfx;caption?, ... ) > を解析。宣言内のパラメタ実体参照 %XHTML.pfx; を空文字列に展開。caption? が同じ実体にないため、エラーを返す。

<!ELEMENT table ( %caption.qname;?, ... ) > という宣言に対してならば、前述のようなエラーが返るのも納得が行くというものです (参照: 要素字句とパラメタ実体参照)。ともかく、table.contentRuby.content.complex を前述の通り上書きすれば対処は可能です。

<!ENTITY % table.content
     "( (%caption.qname;)?, ( (%col.qname;)* | (%colgroup.qname;)* ),
      (( (%thead.qname;)?, (%tfoot.qname;)?, (%tbody.qname;)+ )
      | ( (%tr.qname;)+ )))"
>
<!ENTITY % Ruby.content.complex 
     "| ( %rbc.qname;, %rtc.qname;, (%rtc.qname;)? )"
>

Modularization of XHTML のエラー

# 注意: この節に記載のあるエラーはすでに修正されました。

これは MSXML のバグではなく、XHTML 1.1 の DTD 側のバグに由来するエラーです。

XHTML 1.1 が参照している Modularization of XHTML の版の文字実体モジュール -//W3C//ENTITIES Special for XHTML//EN (システム識別子 http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-special.ent) には、次のような typo があります。

<!ENTITY amp     "&#38;&#38;"> <!--  ampersand, U+0026 ISOnum -->
<!ENTITY lt      "&#38;&#60;"> <!--  less-than sign, U+003C ISOnum -->

これらの実体は、正しくは "&#38;#38;" "&#38;#60;" として宣言されなければならないことになっています。

実体 lt 又は amp を宣言する場合, 内部実体として宣言し, その置換テキストは, 別扱いされる文字 (不等号 (より小) 又はアンパサンド) への文字参照としなければならない

つまり、(少なくとも現在の) XHTML 1.1 DTD には XML として不正な記述が含まれているため、MSXML のように外部パラメタも解析するプロセサはエラーを返すのです。

無効な文字で名前が始まりました。リソース 'http://www.satoshii.org/markup/samp/2003/xhtml11-msxml-m12n' の実行エラーです。ライン 1、位置 2

&&

このエラーを解消するためには、パラメタ実体 xhtml-specialtypo のない版の -//W3C//ENTITIES Special for XHTML//EN を値とするように、システム識別子を上書きする必要があります。

<!ENTITY % xhtml-special
     PUBLIC "-//W3C//ENTITIES Special for XHTML//EN"
            "http://www.w3.org/TR/xhtml1/DTD/xhtml-special.ent" >

ちなみに、元々フラット版の XHTML 1.1 DTD にはこの問題はありません。これは、パラメタ実体を展開する際に、カタログによって適切な版のシステム識別子が関連付けられていたためでしょう。

なお、この問題は現在策定が進められている Modularization of XHTML™ 1.0 - Second Edition (Working Draft 2004-02-18) では修正される予定です。

# 実際には、MSXML がエラーを返すのにはもう少し曲折した事情があることが後日判明したのですが、その辺については M12N のバグと MSXML を参照して下さい。

# このエラーは、2006年2月13日に XHTML Modularization 1.1 PR が XHTML Modularization の最新版になったのに伴って修正されました。

MSXML 対応 XHTML 1.1 DTD

結局、MSXML 2.x/3.0 に XHTML 1.1 文書を解析させるためには、次のように文書型を宣言する必要があります (なお、以下の宣言内の &#37; という記述の意味については bubble hour, 2001-12-18 を参照して下さい)。

モジュール DTD ベース
<!DOCTYPE html
     PUBLIC "-//W3C//DTD XHTML 1.1//EN"
            "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" [
<!ENTITY % xhtml-prefw-redecl.mod "" >
<!ENTITY % xhtml-postfw-redecl.mod "" >

<!ENTITY % xhtml-model.mod
     PUBLIC "-//W3C//ENTITIES XHTML 1.1 Document Model 1.0//EN"
            "http://www.w3.org/TR/xhtml11/DTD/xhtml11-model-1.mod" >

<!ENTITY % xhtml-special
     PUBLIC "-//W3C//ENTITIES Special for XHTML//EN"
            "http://www.w3.org/TR/xhtml1/DTD/xhtml-special.ent" >

<!ENTITY % table.content
     "( (&#37;caption.qname;)?, ( (&#37;col.qname;)* | (&#37;colgroup.qname;)* ),
      (( (&#37;thead.qname;)?, (&#37;tfoot.qname;)?, (&#37;tbody.qname;)+ )
      | ( (&#37;tr.qname;)+ )))" >
<!ENTITY % Ruby.content.complex 
     "| ( &#37;rbc.qname;, &#37;rtc.qname;, (&#37;rtc.qname;)? )" >
]>
フラット DTD ベース
<!DOCTYPE html
     PUBLIC "-//W3C//DTD XHTML 1.1//EN"
            "http://www.w3.org/TR/xhtml11/DTD/xhtml11-flat.dtd" [
<!ENTITY % xhtml-prefw-redecl.mod "" >
<!ENTITY % xhtml-postfw-redecl.mod "" >

<!ENTITY % table.content
     "( (&#37;caption.qname;)?, ( (&#37;col.qname;)* | (&#37;colgroup.qname;)* ),
      (( (&#37;thead.qname;)?, (&#37;tfoot.qname;)?, (&#37;tbody.qname;)+ )
      | ( (&#37;tr.qname;)+ )))" >
<!ENTITY % Ruby.content.complex 
     "| ( &#37;rbc.qname;, &#37;rtc.qname;, (&#37;rtc.qname;)? )" >
]>

外部 DTD にまとめると、次のようになります。

なお、これらの DTD は (W3C のライセンスに抵触しない範囲で) 自由に複製・ミラーリングなどして下さって構いません。

制作者側で対応するのであれば、文書型宣言のシステム識別子で上記の DTD を参照して下さい。また、利用者側で対応するのであれば、Proxomitron を用いて DTD をリダイレクトさせる方法もあるそうです (参照: Hatena::agenda, 2003-09-21, Xalan-Java の DTD 取得行程にイライラ)。

参考文献

この文書のステータス

URI
http://www.satoshii.org/markup/dtd/xhtml11-msxml (HTML, XHTML, English Translation)
初出
bubble hour, 2001-12-18
IE に XHTML 1.1 をパージングさせる
WML 2.0 のエラーについて #2
初版
2003-12-12
最終更新
2006-02-23
著者
石川哲志
Copyright © 2001-2004, 2006 Satoshi ISHIKAWA, All Rights Reserved.