本家「@IT」にはない内容をエンジニアライフで技術紹介するコラム。広く議論する場になることを目指します。

第055回_EntityDecl_前回の補足_置換テキストの構築_定義済み実体

»

前3回に引き続いてEntityDeclについて議論します。

今回は、前回の仕様検討に残された議論の余地と構文解析器が置換テキストを構築する処理と定義済み実体の扱いについて読んでいきます。

前回の仕様の議論の余地

今回、議論の余地があると思われる場所は、4.4.9によって必要になった以下の処理です。
---------------再掲---------------
-EntityValueの処理
2.一般実体の参照を認識したとき
2-1.実体名をキーに一般実体テーブルを検索する
2-2.実体名がテーブルに無い場合
 仕様なし
2-3.実体名がテーブルに有る場合
2-3-1.実体が内部・解析対象・一般実体または、外部・解析対象・一般実体の場合
2-3-1-1.バイパスする(何もしない)
2-3-2.その他(外部・解析対象外・一般実体)の場合
 エラーとする
---------------再掲---------------

この処理の中で2-2で空振りが起きる仕様は、
 整形式性制約の"宣言した実体"
 妥当性制約の"宣言した実体"
の両方に一般実体が入っていないことに起因します。
この仕様によって、
実体宣言AのEntityValueの中にその実体宣言より後ろに実体宣言のある実体Bの参照
を書くことができる利点がある一方で
単純に参照を認識した時点でエラーにしようとしてもパターン漏れがあるので処理を工夫しなければならない
という欠点があります。

というのも、ここで空振りした実体参照の実体Bを今処理している実体AのEntityValueより後ろに実体宣言し、それが外部・解析対象・一般実体としたときこれをエラーとすることが、現在の処理ではできません。

話をややこしくするのが、Content内に実体Aの参照があればその展開処理の中で実体Bの参照を展開しようとして、致命的エラーを検出できるところです。しかしながらこのことが逆説的に、実体Aの参照がContent内に現れなければエラーとして検出できないことも示しています。

■チェック漏れがないようにする工夫
正しくチェックする方法はいくつかありますが、簡単に思いついたのは次の3つです。
案1.DTDをすべて読み込んだ後に、実体値のすべての置換テキストを再スキャンする方法
案2.2-2に到達したkey名を記憶しておき、DTDをすべて読み込んだ後に、再チェックする方法
案3.案1と案2の折衷案としてEntityValueの中に現れた一般実体のkey名をすべて記憶しておき、DTDをすべて読み込んだ後に、再チェックする方法

案1のデメリットは、一度構文解析したものをもう一度構文解析する無駄が生じる点です。
案2は、再構文解析するデメリットはなくなりますが、せっかくチェックしているのに2-2の場合が漏れてしまい、DTD読み込み後に漏れた分をチェックし直す点に、チェック処理の設計として分散するデメリットがあります。

結局、案1と案2のデメリットが無い案3を採用するのが良いということが判ります。

■再整理した結果の処理
-EntityValue
2.一般実体参照を認識したとき
2-1.参照名を参照チェックテーブルに登録する
2-2.バイパスする(何もしない)

-DTDをすべて読み込み終わったとき
1.参照チェックテーブルの実体名を一般実体テーブルから取得する
2.取得した一般実体が未定義の場合
2-1.何もしない(参照が呼び出されたときにエラーになる、もし呼び出しがなければエラーにはならない)
3.取得した一般実体が定義済みの場合
3-1.取得した一般実体が外部・解析対象外・一般実体の場合
3-1-1.エラー(4.4.9に対応、呼び出しがなくてもエラーにする)
3-2.取得した一般実体が外部・解析対象外・一般実体でない場合
3-2-1.何もしない

置換テキストの構築のW3Cの勧告内容

次に、前回までの説明で実体参照を実体値に入れ替える時と場合について説明してきました。
W3Cの勧告では、次に「実体テーブルの値の構築処理」の説明がありますので確認します。
----W3C勧告----
4.5 Construction of Entity Replacement Text

In discussing the treatment of entities, it is useful to distinguish two forms of the entity's value. [Definition: For an internal entity, the literal entity value is the quoted string actually present in the entity declaration, corresponding to the non-terminal EntityValue.] [Definition: For an external entity, the literal entity value is the exact text contained in the entity.] [Definition: For an internal entity, the replacement text is the content of the entity, after replacement of character references and parameter-entity references.] [Definition: For an external entity, the replacement text is the content of the entity, after stripping the text declaration (leaving any surrounding white space) if there is one but without any replacement of character references or parameter-entity references.]

The literal entity value as given in an internal entity declaration (EntityValue) may contain character, parameter-entity, and general-entity references. Such references must be contained entirely within the literal entity value. The actual replacement text that is included (or included in literal) as described above must contain the replacement text of any parameter entities referred to, and must contain the character referred to, in place of any character references in the literal entity value; however, general-entity references must be left as-is, unexpanded. For example, given the following declarations:

<!ENTITY % pub "&#xc9;ditions Gallimard" >
<!ENTITY  rights "All rights reserved" >
<!ENTITY  book "La Peste: Albert Camus,
&#xA9; 1947 %pub;. &rights;" >

then the replacement text for the entity "book" is:

La Peste: Albert Camus,
© 1947 Éditions Gallimard. &rights;

The general-entity reference "&rights;" would be expanded should the reference "&book;" appear in the document's content or an attribute value.

These simple rules may have complex interactions; for a detailed discussion of a difficult example, see C Expansion of Entity and Character References.
----W3C勧告----
----日本語訳----
4.5 実体の置換テキストの構築

実体の取り扱いについて論じる際、 実体値の2つの形態を区別すると便利である。
定義: 内部実体について
リテラル実体値は実体宣言中に実際に存在する引用符で囲われた文字列とする。
リテラル実体値は(内部実体の)非終端EntityValueに対応したものとする。

定義: 外部実体について、リテラル実体値は実体が含んだテキストそのものとする。
定義: 内部実体について、置換テキストは文字参照とパラメータ実体参照を置換した後のその実体の内容である。
定義: 外部実体について、置換テキストはテキスト宣言を取り去った後の実体の内容である。
(両端のホワイトスペースはそのまま残す。)
もし文字参照やパラメータ実体参照が置換テキスト中にあってもそのままにする。

内部実体宣言中のリテラル実体値(EntityValue)は、文字参照、パラメータ実体、一般実体参照を含むことができる。それらの参照は、リテラル実体値の中に完全に含まれる必要がある。インクルードした、(もしくはリテラルの中でインクルードした)実際の置換テキストは、上記で説明したように、すべてのパラメータ実体の参照した置換テキストを含む。リテラル実体値中の文字参照に参照した文字を含む。しかしながら、一般実体参照は展開されないままにしておかなければならない。例えば、次のような宣言があるとする。

<!ENTITY % pub "&#xc9;ditions Gallimard" >
<!ENTITY  rights "All rights reserved" >
<!ENTITY  book "La Peste: Albert Camus,
&#xA9; 1947 %pub;. &rights;" >

この時、実体"book"の置換テキストは;

La Peste: Albert Camus,
© 1947 Éditions Gallimard. &rights;

一般実体参照の "&rights;" を展開するのは "&book;"の参照が文書の内容または属性値として現れた場合となる。

これらの簡単な規則は、複雑な相互作用をもたらすことがある;
難しい例をつかった詳しい議論は、C 実体参照と文字参照の展開 を参照せよ。
----日本語訳----
■仕様整理
-リテラル実体値
EntityValueで示す文字列の両端の引用符を外したものをリテラル実体値と呼びます。
一般実体テーブルまたはパラメータ実体テーブルのKeyに対応する値は置換テキストと呼びます。
つまり、
内部実体の置換テキストは、EntityValueの値に対して、文字参照とパラメータ実体参照を置換した後の値
外部実体の置換テキストは、外部識別子が指定した物理構造が含む文字列からテキスト宣言を取り除いた後の値
となります。

定義済み実体のW3Cの勧告内容

前回少しだけ登場した定義済み実体について説明します。
----W3C勧告----
4.6 Predefined Entities

[Definition: Entity and character references may both be used to escape the left angle bracket, ampersand, and other delimiters. A set of general entities (amp, lt, gt, apos, quot) is specified for this purpose. Numeric character references may also be used; they are expanded immediately when recognized and must be treated as character data, so the numeric character references "<" and "&" may be used to escape < and & when they occur in character data.]

All XML processors must recognize these entities whether they are declared or not. For interoperability, valid XML documents should declare these entities, like any others, before using them. If the entities lt or amp are declared, they must be declared as internal entities whose replacement text is a character reference to the respective character (less-than sign or ampersand) being escaped; the double escaping is required for these entities so that references to them produce a well-formed result. If the entities gt, apos, or quot are declared, they must be declared as internal entities whose replacement text is the single character being escaped (or a character reference to that character; the double escaping here is optional but harmless). For example:

<!ENTITY lt "&#38;#60;">
<!ENTITY gt "&#62;">
<!ENTITY amp "&#38;#38;">
<!ENTITY apos "&#39;">
<!ENTITY quot "&#34;">
----W3C勧告----
----日本語訳----
4.6 定義済み実体

[定義: 実体参照と文字参照を始め山括弧(小なり)やアンパサンド、またその他の区切り子をエスケープするために
使うことができる。一般実体のセット(amp, lt, gt, apos, quot)はこの目的のために定められたものである。
数値文字参照を使うこともできる。数値文字参照は認識されたときに直ちに展開し、文字データとして扱わなければ
ならない。よって、"<" と "&" の文字参照は、<と&を文字データとして扱いたいときにエスケープするため
に使われる。]

すべてのXMLプロセッサは、これらが宣言してるかいないかに関わらず、その実体を認識しなければならない。相互運用性のために、妥当なXML文書はこれらの実体を他の実体と同じように、その参照を使う前に、宣言すべきである。もし、ltやampの実体を宣言した場合、置換テキストはエスケープされたそれぞれの文字(小なり記号やアンパサンド)への文字参照となる内部実体として宣言されなければいけない; それらへの参照が整形式の結果を得るために二重のエスケープが必要となる。もし、gtやaposやquotの実体を宣言した場合、置換テキストはエスケープされた1つの文字として宣言しなければならない。(もしくは、その文字への文字参照として宣言しなければならない。; 二重エスケープは任意であるが、無害である。)
次に例を示す。

<!ENTITY lt "&#38;#60;">
<!ENTITY gt "&#62;">
<!ENTITY amp "&#38;#38;">
<!ENTITY apos "&#39;">
<!ENTITY quot "&#34;">
----日本語訳----
■内容の分析
定義済みの内部・解析対象・一般実体として、lt, gt, amp, apos, quotを用意します。XMLプロセッサの実装にとって、lt, gt, amp, apos, quotの実体宣言がなくても宣言した実体として使える必要があります。また、lt, gt, amp, apos, quotの実体宣言を相互運用性のために定義しても構いません。

実体テーブル上の置換テキストは次のようになります。
---------------実体値テーブル---------------
Key置換テキスト
lt&#60;
gt>
amp&#38;
apos'
quot"
---------------実体値テーブル---------------

もしgt, apos, quotに二重エスケープがあったとした場合は次のようになっていても構いません。
---------------実体値テーブル---------------
Key置換テキスト
lt&#60;
gt&#62;
amp&#38;
apos&#39;
quot&#34;
---------------実体値テーブル---------------

-定義済み実体を再定義する場合の制限
定義済み実体を再定義する場合、「それぞれ該当の文字として定義しなければならない」とありますから、
---------------実体値テーブル---------------
Key置換テキスト
lt&#60;
gt&#62;または >
amp&#38;
apos&#39;または '
quot&#34;または "
---------------実体値テーブル---------------

とすればわかるように、置換テキストの値が上記でなければエラーにします。

-関連内容
4.2 実体宣言で述べたように、実体宣言で同じ名前の実体名を複数回宣言した場合は最初の実体宣言が有効になります。オプションで指定した場合は、2回目以降は重複宣言の警告を出しても良いとあります。これらの仕様は定義済み実体を実装するうえで影響があります。

-問題点 重複宣言に対する扱い
4.2 実体宣言 にあるように、ユーザのオプションで同名の実体が重複宣言になった場合、警告を出しても良いとあります。

これを普通に実装しようとすると、
1.実体宣言の処理
1-1.keyと置換テキストの取得
1-2.実体テーブルのチェック
 実体テーブルに対してkeyで検索する
1-3.実体データが無い場合(未だ登録していない場合)
1-3-1.実体テーブルに登録する(key, 置換テキスト)
1-4.実体データが有る場合(既に登録してある場合)
1-4-1.オプションが有効の場合
1-4-1-1.警告を出す
1-4-2.オプションが無効の場合
1-4-2-1.何もしない

となります。

ここで、定義済み実体を先に実体テーブルに登録しておくと、
相互運用性のためlt, gt, amp, apos, quotの実体宣言を行った場合、1回目の宣言であっても警告を出してしまう
という問題があります。

●回避案1 DTD読み取り後に定義済み実体を登録する
一般実体の参照を処理するタイミングはDTDの作成後ですから、DTDをすべて処理したあとに定義済み実体を該当keyがない場合だけ登録するインタフェースを用意する方法です。

●回避案2 初期化時に定義済み実体を登録するが、上書きする場合は初回だけにする
定義済み実体という名前に合わせて、テーブルのコンストラクタと同時に登録する方法です。
もし、登録時に定義済み実体と同名のkeyの場合は、
最初の一回:登録データが内部・解析対象一般実体の場合かつ正しい置換文字列の場合は、その値を置換文字列にする
登録データが内部・解析対象一般実体の場合かつ正しい置換文字列でない場合は、エラーとする
登録データが内部・解析対象一般実体でない場合は、エラーとする

2回目以降 :他と同じ処理(オプションの場合だけ警告を出す)を行う

この回避案1は一見よさそうですが、一般実体は内部・外部・解析対象外の3種類の実体があり、定義済み実体と同じkeyで外部実体や解析対象外実体も登録できてしまいます。よって、今回は回避案2で実装します。

■仕様整理
構文解析の流れ

・実体テーブルの初期化処理
直接テーブルに定義済み実体を登録する

・構文解析(Visitor)
1.一般実体宣言から実体データを作成する
2.実体データを実体テーブルに登録する

・実体テーブルの登録処理
1.一般実体名(key)を取得する
2.テーブルをkeyで検索し、登録済みデータを取得する
3.登録済みデータが無い場合
3-1.一般実体を登録する
4.登録済みデータが有る場合
4-1.登録済みのデータに合わせて、マージ処理をする

・登録済みデータが一般実体のマージ処理
既に同名のkeyが登録してあるのでエラーとする

・登録済みデータが定義済み実体のマージ処理(一般実体のマージ処理をせずに定義済み実体はこちらを実行する)
1.内部・解析対象・一般実体データが未だ無い場合
1-1.登録側のデータが内部・解析対象・一般実体でない場合
1-1-1.エラーとする
1-2.登録側のデータが内部・解析対象・一般実体の場合
1-2-1.内部・解析対象・一般実体データとして保存する
1-2-2.候補リストの文字列の場合
1-2-2-1.置換文字列に設定する
1-2-3.候補リストの文字列でない場合
1-2-3-1.エラーとする
2.内部・解析対象・一般実体データが既に有る場合
内部・解析対象・一般実体データにマージする(定義済み実体でないマージ処理へ)

以下の処理では、定義済み実体を個別に処理する部分を削除します。

-Contentの処理(修正:定義済み実体の判定処理を削除)
2.一般実体の参照を認識したとき、
2-1.実体名をキーに一般実体テーブルを検索する
2-2.実体名がテーブルに無い場合
2-2-1.スタンドアロン文書宣言がyesのとき
2-2-1-1.整形式性制約違反エラー
2-2-2.スタンドアロン文書宣言がnoのとき
2-2-2-1.外部サブセットが無い場合
2-2-2-1-1.内部サブセットが無い場合
2-2-2-1-1-1.整形式性制約エラー
2-2-2-2-1-2.内部サブセットが有る場合
2-2-2-2-1-2-1.パラメータ実体の参照が有る場合
2-2-2-2-1-2-1-1.妥当性制約違反エラー
2-2-2-2-1-2-2.パラメータ実体の参照が無い場合
2-2-2-2-1-2-2-1.整形式性制約エラー
2-2-2-2.外部サブセットが有る場合
2-2-2-2-1.妥当性制約違反エラー
2-3.実体名がテーブルに有る場合
2-3-1.実体が内部・解析対象・一般実体または、外部・解析対象・一般実体の場合
2-3-1-1.再帰用のスタックに既に同名の実体名があるかチェックする
2-3-1-2.再帰用のスタックに既に同名の実体名が有る場合
2-3-1-2-1. 整形式性制約エラー
2-3-1-3.再帰用のスタックに既に同名の実体名が無い場合
2-3-1-3-1.再帰用のスタックに実体名を追加する
2-3-1-3-2.置換テキストRepTextを取得する
2-3-1-3-3.一般実体のトークン列の代わりにCharData(RepText)を返す。
2-3-2.その他(実体が解析対象外実体)の場合
2-3-2-1.整形式性制約エラー/致命的エラーとする

-属性リスト宣言のデフォルト値の中での処理
2.一般実体の参照を認識したとき
2-1.実体名をキーに一般実体テーブルを検索する
2-2.実体名がテーブルに無い場合
2-2-1.スタンドアロン文書宣言がyesのとき
2-2-1-1.整形式性制約違反エラー
2-2-2.スタンドアロン文書宣言がnoのとき
2-2-2-1.外部サブセットが無い場合
2-2-2-1-1.内部サブセットにパラメータ実体の参照が有る場合
2-2-2-1-1-1.妥当性制約違反エラー
2-2-2-1-2.内部サブセットにパラメータ実体の参照が無い場合
2-2-2-1-2-1.整形式性制約エラー
2-2-2-2.外部サブセットが有る場合
2-2-2-2-1.妥当性制約違反エラー
2-3.実体名がテーブルに有る場合
2-3-1.実体が内部・解析対象・一般実体の場合
2-3-1-1.再帰用のスタックに既に同名の実体名があるかチェックする
2-3-1-2.再帰用のスタックに既に同名の実体名が有る場合
2-3-1-2-1. 整形式性制約エラー
2-3-1-3.再帰用のスタックに既に同名の実体名が無い場合
2-3-1-3-1.再帰用のスタックに実体名を追加する
2-3-1-3-2.置換テキストRepTextを取得する
2-3-1-3-3.一般実体のトークン列の代わりに
NORMAL_TOKEN_A1(RepText)または、NORMAL_TOKEN_A2(RepText)を返す。
2-3-2.その他(実体が外部・解析対象・一般実体または、外部・解析対象外・一般実体)の場合
2-3-2-1.整形式性制約エラー/致命的エラーとする

-開始タグの中の属性値の中での処理
2.一般実体の参照を認識したとき
2-1.実体名をキーに一般実体テーブルを検索する
2-2.実体名がテーブルに無い場合
2-2-1.スタンドアロン文書宣言がyesのとき
2-2-1-1.整形式性制約違反エラー
2-2-2.スタンドアロン文書宣言がnoのとき
2-2-2-1.外部サブセットが無い場合
2-2-2-1-1.内部サブセットが無い場合
2-2-2-1-1-1.整形式性制約エラー
2-2-2-2-1-2.内部サブセットが有る場合
2-2-2-2-1-2-1.パラメータ実体の参照が有る場合
2-2-2-2-1-2-1-1.妥当性制約違反エラー
2-2-2-2-1-2-2.パラメータ実体の参照が無い場合
2-2-2-2-1-2-2-1.整形式性制約エラー
2-2-2-2.外部サブセットが有る場合
2-2-2-2-1.妥当性制約違反エラー
2-3.実体名がテーブルに有る場合
2-3-1.実体が内部・解析対象・一般実体の場合
2-3-1-1.再帰用のスタックに既に同名の実体名があるかチェックする
2-3-1-2.再帰用のスタックに既に同名の実体名が有る場合
2-3-1-2-1. 整形式性制約エラー
2-3-1-3.再帰用のスタックに既に同名の実体名が無い場合
2-3-1-3-1.再帰用のスタックに実体名を追加する
2-3-1-3-2.置換テキストRepTextを取得する
2-3-1-3-3.一般実体のトークン列の代わりに
NORMAL_TOKEN_A1(RepText)または、NORMAL_TOKEN_A2(RepText)を返す。
2-3-2.その他(実体が外部・解析対象・一般実体または、外部・解析対象外・一般実体)の場合
2-3-2-1.整形式性制約エラー/致命的エラーとする
Comment(0)

コメント

コメントを投稿する