第10回 XMLの難物、DTDとパラメタ実体参照に挑む

XML 1.0は、1998年にW3Cから勧告として公開された。当然中身は英語で、しかもEBNFと呼ばれる式によって重要な部分が記述してある。この連載では、XML 1.0を深く理解するために、そのXML 1.0勧告の最新版「Extensible Markup Language (XML) 1.0 (Second Edition)」をだれでも分かるように、やさしく読み解きながら解説していくことを目指している。(編集局)

川俣 晶
株式会社ピーデー
2003/5/30



XMLの難物への挑戦
今回の主な内容
XMLの難物への挑戦
DTDに関するEBNF
セカンドエディションの正誤表
文書型宣言の定義
非終端記号とは何のことか
パラメタ実体参照を記述できる場所
妥当性制約と整形式制約

 前回「XML宣言と文書型宣言」では、XML 1.0勧告の中で、「2 Documents」(2 文書)の中の「2.8 Prolog and Document Type Declaration」(2.8 前書き及び文書型宣言)を解説してきたが、まだ解説していない部分が残っている。今回はその続きである。

 「2 Documents」は、XML文書の基本的な構造について述べている部分である。その中で、「2.8 Prolog and Document Type Declaration」(前書き及び文書型宣言)は、XML宣言や文書型宣言を扱っている部分である。

 前回は、XML宣言の役割と、そこで宣言される内容の詳細、そして文書型宣言とは何か、といったことを解説した。

 今回は文書型定義(DTD:Document Type Definition)に関するEBNFや、それに関連する妥当性制約、整形式制約などを読んでいく。特にDTDのマクロ機能ともいえる「パラメタ実体参照」という、XMLでも最大級の難物を理解する手掛かりがここにある。XMLの深部にチャレンジするには必須の知識が含まれている部分である。

編集注:この連載では、XML 1.0勧告であるW3Cの「Extensible Markup Language (XML) 1.0 (Second Edition)」(英語)を参照し、その日本語訳として、日本工業規格 JIS X 4159:2002(リンク先は該当規格の原案ですが、最終版とほぼ同等の内容です)を参照しています。本文中のピンクの地の部分は、XML 1.0勧告の原文を示しています。

DTDに関するEBNF

 前回の最後に説明した文書型宣言(document type declaration)の、EBNFを解説するところから始めよう。文書型宣言は、具体的な要素や配列の並び順などを宣言するマーク付け宣言(markup declarations)を含むか参照するものである。これによって定義される文法が、いわゆるDTD、つまり文書型定義(document type definition)と呼ばれるものであるが、ここでは文書型宣言と文書型定義が入り交じっているので注意していただきたい。

 下記の一連のEBNFの標題は「Document Type Definition」、つまり文書型定義であるが、ここで最初に記述されるEBNFの[28] doctypedeclは文書型宣言の書式を定義するものである(EBNFの詳細については、第2回「XML勧告読解に必須のEBNF」を参照)。

Document Type Definition
[28]    doctypedecl    ::=    '<!DOCTYPE' S Name (S ExternalID)? S? ('[' (markupdecl | DeclSep)* ']' S?)? '>' [VC: Root Element Type]
[WFC: External Subset]
/* */
[28a]    DeclSep    ::=    PEReference | S [WFC: PE Between Declarations]
/* */
[29]    markupdecl    ::=    elementdecl | AttlistDecl | EntityDecl | NotationDecl | PI | Comment [VC: Proper Declaration/PE Nesting]
[WFC: PEs in Internal Subset]

 なお、DeclSepは[28a]という妙な番号を与えられているが、これは正誤表と関連した特殊な事情によるものである。

セカンドエディションの正誤表

 この[28a]について調べてみると、XML 1.0勧告の最初のバージョンではdoctypedeclは、以下のようになっていた。

[28] doctypedecl ::= '<!DOCTYPE' S Name (S ExternalID)? S? ('[' (markupdecl | PEReference | S)* ']' S?)? '>'

 これが2つのEBNF表記に分離されたのが、最新のXML 1.0勧告であるSecond Editionの中の[28]と[28a]である。

 ところが、JISによるXML仕様のこの部分を見ると、[28a] で終わりではなく、[28b]まで記述されている。英語のオリジナルに存在しない[28b]がどこからきたかというと、XML 1.0 Second Edition Specification Errata、つまり正誤表である。XML 1.0 Second Editionは、XML 1.0勧告の最初のバージョンの細かな間違いなどを修正した結果であるが、Second Editionにも、その後さらに正誤表が作成されたわけだ。

 その正誤表から該当部分を引用すると以下のようになる。

Document Type Definition
[28]    doctypedecl    ::=    '<!DOCTYPE' S Name (S ExternalID)? S? ('[' intSubset ']' S?)? '>' [VC: Root Element Type]
[WFC: External Subset]
[28a]    DeclSep    ::=    PEReference | S [WFC: PE Between Declarations]
[28b]    intSubset    ::=    (markupdecl | DeclSep)*
[29]    markupdecl    ::=    elementdecl | AttlistDecl | EntityDecl | NotationDecl | PI | Comment [VC: Proper Declaration/PE Nesting]
[WFC: PEs in Internal Subset]

 本連載でも、このような正誤表の内容を本当はすべて把握して反映したうえで記述するのが理想とは思うが、必ずしもそれはできていない。その点は筆者として申し訳ないと思うが、基本的なXMLの構造やあり方が正誤表で変わるわけではないので、大筋では本連載の内容で問題ないはずだ。

 さて、正誤表によってこれらの変更が行われたことにはどんな意味があるのだろうか。[28a]は、[WFC: PE Between Declarations](整形式制約:宣言間のパラメタ実体)を記述するために必要とされたと思われる。この内容は後で説明が出てくる。[28b]の方は、internal subset(内部サブセット)の意味を明確化するためのようだ(内部サブセットについては、第9回「XML宣言と文書型宣言」を参照)。具体的には、内部サブセットに角カッコ“[...]”を含まないことを特に示すためである。

文書型宣言の定義

 話を元に戻そう。これら一連のEBNFは何を表現しようとしているのだろうか。そのためにまず、文書型宣言を定義したdoctypedeclを見てみよう。省略可能な空白などを簡略的に表記して大ざっぱにいうと、doctypedeclとは、こんな流れになっている。

  <!DOCTYPE 名前 省略可能な外部ID 省略可能な内部サブセット >

 これによって文書型宣言には、まず名前(つまりルート要素の要素名)が必須であること。外部ID(つまり外部のDTDの参照など)と内部サブセット(内部に記述するDTDなど)はどちらも省略可能である、ということが分かると思う。

 次は[28a] DeclSepだが、その前に[28b] intSubsetを見てみよう。これは内部サブセットを意味している。中身は、マーク付け宣言(markupdecl)と[28a]のDeclSepのいずれかを0回以上繰り返すことを許している。マーク付け宣言は、[29] markupdeclを見て分かるとおり、要素型宣言や属性リスト宣言などの総称である。DeclSepの方は、パラメタ実体参照(PEReference)または空白(S)である。もちろん、パラメタ実体参照は実際には、間接的にマーク付け宣言を指し示すことになるので、直接または間接的に記述されたマーク付け宣言を許すことになる。

 さて、次は、EBNFに付記された補足説明である。

Note that it is possible to construct a well-formed document containing a doctypedecl that neither points to an external subset nor contains an internal subset.

 「外部サブセットを参照せず、内部サブセットを含まないdoctypedeclを持つ整形式文書を作成できることに注意すること」とある。EBNFから当然の結論として導き出せることだが、あらためて示されている。

 余談だが、ここでは「整形式なXML文書を作成できる」と書かれているが、妥当なXML文書を「外部サブセットを参照せず内部サブセットを含まない」文書型宣言によって扱うことは不可能である。というのは、XML文書には少なくとも1つの要素が必要であり、その要素に関する要素型宣言がなければ、妥当なXML文書にならないためである。

非終端記号とは何のことか

 次は、パラメタ実体と関係した少しややこしい話を説明している。パラメタ実体とは、DTDで使用できる一種のマクロとして機能するものである。これを理解することは、ここで説明しようとしているDTDの理解と深いつながりがある。

The markup declarations may be made up in whole or in part of the replacement text of parameter entities. The productions later in this specification for individual nonterminals (elementdecl, AttlistDecl, and so on) describe the declarations after all the parameter entities have been included.

 ここでは、「マーク付け宣言は、その全体または一部をパラメタ実体の置換テキストで構成してもよい」とある。これは次の文章への前振りである。次は、「おのおのの非終端記号(elementdecl、AttlistDeclなど)のための生成規則は、この規格の後半にあり、すべてのパラメタ実体を取り込んだ後の宣言について記述する」とある。つまり、要素型宣言や属性リスト宣言などの生成規則(EBNFで記述されたもの)は、すべてパラメタ実体を取り込んだ後の状態に対して適用されるということである。これにより、宣言の一部分だけをパラメタ実体参照で記述するということも可能になり、実際にそういうことがよく行われる。

 ここで気になるのは、非終端記号という言葉だろう。これは初めて出現する言葉であるが、それほど難しく考える必要はない。記号といっても、“!”や“?”という文字のことをいっているわけではない。要するに、elementdecl、AttlistDeclなどのEBNFの定義(生成規則)を意味していると思えばよい。非終端記号の反対の意味を持つ言葉は終端記号である。このあたりの説明は筆者の専門外の世界なので深入りはしないが、ここを読むに当たっては、「非終端記号」を「elementdecl、AttlistDeclなど」と読み替えれば十分に読み通せるだろう。

 さて、「取り込んだ」という表記も特別な意味がある。これは要するに、パラメタ実体参照として“%ref;”のように書いたものが、その実体の内容で置き換えられて、“<!ELEMENT sample (#PCDATA)>”のような文字列になることである。その厳密な定義は、XML 1.0勧告の「4.4.2 Included」(4.4.2 取込み)にあるので、ここでは触れないでおく。

パラメタ実体参照を記述できる場所

 次は、パラメタ実体参照を記述できる場所についての説明である。

Parameter entity references are recognized anywhere in the DTD (internal and external subsets and external parameter entities), except in literals, processing instructions, comments, and the contents of ignored conditional sections (see 3.4 Conditional Sections). They are also recognized in entity value literals. The use of parameter entities in the internal subset is restricted as described below.

 まず、「パラメタ実体参照は、DTD(内部および外部サブセットならびに外部パラメタ実体)のどの場所でも認識される」としている。パラメタ実体はDTD内部で使うものだから、当然のことだが、これには続きがある。続いて、DTD内部でもパラメタ実体参照が認識されない場所を説明している。「リテラル、処理命令、コメントおよび無視される条件セクション(3.4 Conditional Sections(条件付きセクション))を除く」とある。

 リテラル(Literals)については、「2.3 Common Syntactic Constructs」(2.3 共通の構文構成子)で登場して、第6回「XMLにおける名前、トークン、リテラルデータ」の回で解説済みのものである。引用符でくくられた“We love XML”のようなものだ。ここで、リテラルは引用符でくくられた内部をそのまま指定したいから引用符でくくるものであり、その内部でパラメタ実体参照が認識されない(つまり有効にならない)のは当然のことである、と考えると、実は間違いである。XMLでは引用符でくくった中であるからマーク付けが有効にならない、というルールはない

 例えば、“Let's Go!”という値を持つ属性attrを、引用符を'(シングルクオーテーション)として記述する場合、attr='Let's Go!'と記述する。すると、この場合はリテラルデータ中の実体参照は有効である。ここでパラメタ実体参照はリテラルでは認識されない、としていることには、重い意味が存在するのである。

 話はこれで終わりではない。もう1つ注意すべきことがある。前述した「2.3 Common Syntactic Constructs」のリテラルの定義を見ると、実は非常に悩ましい記述に出合うことになる。内部実体の内容を定義する[9] EntityValueは、PEReferenceつまりパラメタ実体参照を含んでいるのである。

 リテラルの中では、パラメタ実体参照は認識されないにもかかわらず、ここではパラメタ実体参照を含んでいるというのは、どういうことだろうか。XML 1.0勧告を作成したW3C XMLワーキンググループのメンバーとして名を連ねていた村田真氏によれば、現在のXML Coreワーキンググループに確認を取っていない個人的な解釈だと断ったうえで、以下のようなことだと説明してくれた。

 「internal entityについては、replacement textとliteral entity valueの2つを区別する必要があります。前者は、パラメタ実体および文字参照を展開した後の形で、後者は展開前の形です。つまり、literal entity valueを展開してreplacement textを作った後で、再びparameter entityを理解することはしないというのが、原文の意図だと思います」

 internal entityつまり内部実体には、パラメタ実体および文字参照を展開した後の形であるreplacement text(置換テキスト)と、展開する前の形であるliteral entity value(リテラル実体値)があるという。ここで「リテラルの中では、パラメタ実体参照は認識されない」ということの意図は、「置換テキスト」に対して、パラメタ実体は作用しない、ということであるとしている。つまり、[9] EntityValueに対応するリテラル実体値の中にはパラメタ実体を書いてよく、それは解釈される。しかし、解釈して置換されたテキストにパラメタ実体と同じ表記の文字列があったとしても、パラメタ実体は解釈されない、ということのようだ。

妥当性制約と整形式制約

 リテラル実体値の中でパラメタ実体参照が認識されることは、次の文章からも分かる。次は、「パラメタ実体は実体値リテラル(entity value literals)でも同様に認識される」としている。このentity value literalsは、literal entity valueの誤りだろうと村田氏はコメントしているが、ここではっきりとパラメタ実体は解釈されると記されている。そして、「内部サブセットで使用されるパラメタ実体は、次に示す制限を受ける」としている。

 これ以後は、内部サブセットで有効な、妥当性制約と整形式制約が続く。念のため付け加えると、「DTDが与えられ、それに対して完全に適合したXML文書」が妥当なXML文書であり、「DTDはなくともXML文書としての形式が整っているもの」が整形式なXML文書である。これもDTDにかかわってくるため、ここでそれぞれの制約について解説している。

Validity constraint: Root Element Type

The Name in the document type declaration must match the element type of the root element.

 まず、Root Element Type(ルート要素型)という名前の妥当性制約(Validity constraint)である。これは、「文書型宣言におけるNameはルート要素の型とマッチしなければならない」としている。

 要素型宣言はある要素の内容となる内容モデルを明確にすることができ、ある要素の子要素として正しいものを示すことができる。しかし、ルート要素が何かを示すことはできない。ルート要素は文書型宣言内で示されるわけである。そのため、この名前がルート要素の名前(ここではtype/型と呼んでいるが)と一致することは、妥当なXMLでは必須の条件となるわけである。

Validity constraint: Proper Declaration/PE Nesting

Parameter-entity replacement text must be properly nested with markup declarations. That is to say, if either the first character or the last character of a markup declaration (markupdecl above) is contained in the replacement text for a parameter-entity reference, both must be contained in the same replacement text.

 次は、Proper Declaration/PE Nesting(宣言およびパラメタ実体が厳密に入れ子をなすこと)という名前の妥当性制約である。これは、パラメタ実体が入れ子になっている必要性を説明したものである。まず、「パラメタ実体の置換テキストはマーク付け宣言内において,厳密に入れ子になっていなければならない」としている。そしてこの後で、よりかみ砕いていい換えている。つまり、「マーク付け宣言(markupdecl)の最初または最後の文字がパラメタ実体参照の指し示す置換テキストに含まれれば、両方とも同じ置換テキストに含まれなければならない」ということだ。

 例えば、<!ELEMENT sample (#PCDATA)>という要素型宣言をパラメタ実体で記述する際、最初の文字から最後の文字までは同じパラメタ実体内になければならないことを示す。「<!ELEMENT」という部分だけpeという名前のパラメタ実体として、「%pe; sample (#PCDATA)>」と記述することはできないわけである。

Well-formedness constraint: PEs in Internal Subset

In the internal DTD subset, parameter-entity references can occur only where markup declarations can occur, not within markup declarations. (This does not apply to references that occur in external parameter entities or to the external subset.)

 次は、PEs in Internal Subset(内部サブセット内のパラメタ実体)という名前の整形式制約である。まず「DTDの内部サブセットでは、パラメタ実体参照はマーク付け宣言が出現可能な場所だけに出現できる」としている。さらに、「マーク付け宣言の一部としては出現できない」と続いている。つまり、内部サブセットではパラメタ実体を表記できる位置に制限が設けられているということである。その後に、「この制約は外部パラメタ実体または外部サブセットでの参照には適用しない」と補足されている。

 つまり、この制約はあくまで内部サブセットだけに限定されたものであり、外部サブセットには適用されないということを意味する。おそらく、DTDを部分的にしか解釈しないケースで意味がある規定だろう。整形式として処理する場合にもDTDを解釈する必要があるが、DTDのすべてを解釈する必要はない。解釈しない宣言を正しく読み飛ばすには、このような規定があるとよいのだろう。

Well-formedness constraint: External Subset

The external subset, if any, must match the production for extSubset.

 次は、External Subset(外部サブセット)という名前の整形式制約である。まず、「外部サブセットがある場合、その外部サブセットは,生成規則 extSubsetにマッチしなければならない」としている。extSubsetは、この後説明が出てくるのでここで詳しくは書かない。しかし、整形式として扱う場合には外部サブセットはデタラメに書いてはだめ、という制約であることは意識しておく必要がある。外部サブセットを参照しないで動作するプログラムにXML文書を読み込ませるだけならいいかげんな外部サブセットを指定してもよいように思えるが、それは正しい使い方ではないとしているわけである。すべてのプログラムが外部サブセットを参照しないわけではなく、それらを使用する場合に困るのである。

Well-formedness constraint: PE Between Declarations

The replacement text of a parameter entity reference in a DeclSep must match the production extSubsetDecl.

 次は、PE Between Declarations(宣言間のパラメタ実体)という名前の整形式制約である。ここでは、「DeclSepでのパラメタ実体参照の置換テキストは,生成規則extSubsetDeclにマッチしなければならない」としている。DeclSepは今回解説した[28a]の生成規則である。extSubsetDeclは、この直後に出てくるもので、後で説明を行う。この規則は、パラメタ実体参照を解釈しないXMLプロセッサでも、問題なく処理を完了可能とするために意味があると思われる。もしもこの規定がなく、何かの宣言の一部だけがパラメタ実体参照で参照されるとすると、XMLプロセッサはうまく宣言を扱えない可能性が出てくる。解釈せず読み飛ばすことすらうまくできない可能性も出てくるのである。

 さて、「2.8 Prolog and Document Type Declaration」にはまだ続きがあるが、今回はここまでである。あと一息で終わりなので、次回は、この続きを一気に解説する予定である。

連載 やさしく読む「XML 1.0勧告」


XML & SOA フォーラム 新着記事
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

HTML5+UX 記事ランキング

本日月間