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

第042回_EncodingDecl2

»
前回のコラムの最後に予告した通り、ファイル情報から文字符号化方式を判別する処理の細かい処理機構について議論します。

文字符号化方式の決定方法

W3Cの勧告は次のように説明しています。少し長いので部分毎にわけて説明します。

----W3C勧告----
E Autodetection of Character Encodings (Non-Normative)
The XML encoding declaration functions as an internal label on each entity, indicating which character encoding is in use. Before an XML processor can read the internal label, however, it apparently has to know what character encoding is in use -- which is what the internal label is trying to indicate. In the general case, this is a hopeless situation. It is not entirely hopeless in XML, however, because XML limits the general case in two ways: each implementation is assumed to support only a finite set of character encodings, and the XML encoding declaration is restricted in position and content in order to make it feasible to autodetect the character encoding in use in each entity in normal cases. Also, in many cases other sources of information are available in addition to the XML data stream itself. Two cases may be distinguished, depending on whether the XML entity is presented to the processor without, or with, any accompanying (external) information. We consider the first case first.
----W3C勧告----
----日本語訳----
E 文字符号化方式の自動検知(非規範的)
XMLのエンコーディング宣言はそれぞれの実体で使われている文字符号化方式を示す内部的な名前として機能する。しかし、XMLプロセッサが内部的な名前を読む前に使われている文字符号化方式---内部的な名前がなんであるか---
を知らなけばならないことは明らかである。一般的に、こういった状況は対処のしようがない。といっても、XMLという仕様の中では対処のしようはある。なぜなら、XMLは2つの仕様によって一般的な状況を制限しているからである。:XMLのそれぞれの実装は有限の文字符号化方式のしかサポートしないことを想定していて、一般的な状況でもそれぞれの実体で使われている文字符号化方式を自動検知することが可能になるようにエンコーディング宣言の位置と内容が制限されている。また、多くの場合、XMLのデータストリームに加えて、他の情報源も利用可能である。XML実体が(外部の)情報を伴ってプロセッサに渡されるか否かによって2つの場合に分けることができる。外部情報が渡されない場合について考えてみる。
----日本語訳----

XMLのエンコーディング宣言は、
 プログラムと文字コードの仕様としては矛盾に満ちた仕様
です。矛盾に満ちたといえるのは、
XMLの入力ストリームにエンコーディング宣言を書いたからといってエンコーディング宣言を元に文字符号化方式を決定して入力ストリームの処理を切り替えできない
また、入力ストリームからエンコーディング宣言までの内容が一致したからといってユーザが指定した文字符号化方式と入力ストリーム全体を調べて文字符号化方式を決めるべきである
という仕様だからです。

この後のセクションでは文字符号化方式を決定する方法について説明しています。

----W3C勧告----
E.1 Detection Without External Encoding Information
Because each XML entity not accompanied by external encoding information and not in UTF-8 or UTF-16 encoding must begin with an XML encoding declaration, in which the first characters must be '<?xml', any conforming processor can detect, after two to four octets of input, which of the following cases apply. In reading this list, it may help to know that in UCS-4, '<' is "#x0000003C" and '?' is "#x0000003F", and the Byte Order Mark required of UTF-16 data streams is "#xFEFF". The notation ## is used to denote any byte value except that two consecutive ##s cannot be both 00.

With a Byte Order Mark:

00 00 FE FF UCS-4, big-endian machine (1234 order)
FF FE 00 00 UCS-4, little-endian machine (4321 order)
00 00 FF FE UCS-4, unusual octet order (2143)
FE FF 00 00 UCS-4, unusual octet order (3412)
FE FF ## ## UTF-16, big-endian
FF FE ## ## UTF-16, little-endian
EF BB BF UTF-8
----W3C勧告----
----日本語訳----
E1. 外部の文字符号化方式情報のない検知
それぞれの外部の文字符号化方式情報を伴わず、UTF-8でもUTF-16でもない文字符号化方式のXML実体はXMLエンコーディング宣言で始まらなければならず、つまり最初の文字は'<?xml'であるから、仕様に適合するプロセッサは皆、入力の2オクテットから4オクテット目を見て、後述する場合のいずれを適用するかを検知することができる。このリストを読むとき、UCS-4の'<'は"#x0000003C"で'?'は"#x0000003F"であり、UTF-16のデータストリームのバイトオーダーマークは"#xFEFF"であるということを知っていると役に立つだろう。##という記法は"0000"を除く任意のバイト値を示すために使う。

バイトオーダーマークが付いている場合は次のようになる

00 00 FE FF UCS-4, ビッグエンディアン(1234の順)
FF FE 00 00 UCS-4, リトルエンディアン(4321の順)
00 00 FF FE UCS-4, 普通でないオクテットの並び(2143)
FE FF 00 00 UCS-4, 普通でないオクテットの並び(3412)
FE FF ## ## UTF-16, ビッグエンディアン
FF FE ## ## UTF-16, リトルエンディアン
----日本語訳----

ここでわかることは、前回説明したことを含めると、
文字符号化方式指定しない場合
ファイル情報から文字符号化方式を判別してファイルをオープンします。ファイル情報を知るために文字符号化方式を指定せずにファイルのバイト文字列を読み込みます。そのバイト列の先頭にBOMが付いている場合はBOMのバイト列からどの文字符号化方式かを判別します。また、BOMが付いていない場合は、"<?xml"の先頭文字"<"を示す文字コードを使って文字符号化方式を判別します。これらは完全ではないとしても、サポートする有限個の文字符号化方式の中では問題がありません。

文字集合UCS-4
ISO/IEC 10646 (UCS; Universal Coded Character Set) は、符号化文字集合や文字符号化方式を定めた文字コードの国際規格です。ISO/IEC 10646は31ビットを使います。ただし、Unicodeは21ビットつまり0-10FFFFを使う規格で(前回も余談で説明しましたが、ISO/IEC 10646はUnicodeに乗っ取られた規格のため)ISO/IEC 10646の110000以降は永久に文字として定義されないことになりました。

そのISO/IEC 10646の定義する4オクテット(32ビット)の符号化文字集合です。先に説明した通り、32ビットの内で使うのは0-10FFFFだけです。

UCS-4とは別にUCS-2もあります。

ISO/IEC 10646の文字符号化方式
UTF-1, UTF-8, UTF-16, UTF-32があります。

ISO/IEC 10646のUTFは"UCS Transformation Format"の略
UnicodeのUTFは"Unicode Transformation Format"の略
と異なる意味ですが、UTF-8とUTF-16の符号化方式の内容はほぼ同じです。

ということでW3C勧告の表の2列目は符号化文字集合と文字符号化方式が混在していてわかりにくくなっています。またXMLで扱える符号化文字集合は前回説明した通りUnicodeの範囲内のCharからRestricted Charを除いた差集合ですがXMLファイルを専用のエディタで編集しない限り、ファイルは特定の符号化文字集合と文字符号化方式で保存するXMLプロセッサが保存したファイルをデコードしながら読み込むという順序となります。XMLプロセッサとして許容できるかどうかはXMLプロセッサが判断することです。

そこで表を書き直すと次のようになります。

BOMバイト列:符号化文字集合:文字符号化方式:備考
―――――――――――――――――――――――――――――――――――
00 00 FE FF:UCS-4 :UTF-32:ビッグエンディアン(1234の順)
FF FE 00 00:UCS-4 :UTF-32:リトルエンディアン(4321の順)
00 00 FF FE:UCS-4 :UTF-32:普通でないオクテットの並び(2143)
FE FF 00 00:UCS-4 :UTF-32:普通でないオクテットの並び(3412)
FE FF ## ##:Unicode:UTF-16:ビッグエンディアン
FF FE ## ##:Unicode:UTF-16:リトルエンディアン
EF BB BF :Unicode:UTF-8 :

ここまでの処理の判定は次のようになります。
最初の1バイトが00の場合
3バイト目がFEの場合:UCS-4 ビッグエンディアン(1234の順)
3バイト目がFFの場合:UCS-4 普通でないオクテットの並び(2143)
最初の1バイトがEFの場合:UTF-8
最初の1バイトがFEの場合:
3バイト目が00の場合:UCS-4 普通でないオクテットの並び(3412)
3バイト目が00でない場合:UTF-16 ビッグエンディアン
最初の1バイトがFFの場合:UTF-16 リトルエンディアン

次のセクションを読んでいきましょう。

----W3C勧告----
Without a Byte Order Mark:

00 00 00 3CUCS-4 or other encoding with a 32-bit code unit and ASCII characters encoded as ASCII values, in respectively big-endian (1234), little-endian (4321) and two unusual byte orders (2143 and 3412).The encoding declaration must be read to determine which of UCS-4 or other supported 32-bit encodings applies.
3C 00 00 00
00 00 3C 00
00 3C 00 00
00 3C 00 3FUTF-16BE or big-endian ISO-10646-UCS-2 or other encoding with a 16-bit code unit in big-endian order and ASCII characters encoded as ASCII values (the encoding declaration must be read to determine which)
3C 00 3F 00UTF-16LE or little-endian ISO-10646-UCS-2 or other encoding with a 16-bit code unit in little-endian order and ASCII characters encoded as ASCII values (the encoding declaration must be read to determine which)
3C 3F 78 6DUTF-8, ISO 646, ASCII, some part of ISO 8859, Shift-JIS, EUC, or any other 7-bit, 8-bit, or mixed-width encoding which ensures that the characters of ASCII have their normal positions, width, and values; the actual encoding declaration must be read to detect which of these applies, but since all of these encodings use the same bit patterns for the relevant ASCII characters, the encoding declaration itself may be read reliably
4C 6F A7 94EBCDIC (in some flavor; the full encoding declaration must be read to tell which code page is in use)
OtherUTF-8 without an encoding declaration, or else the data stream is mislabeled (lacking a required encoding declaration), corrupt, fragmentary, or enclosed in a wrapper of some kind
----W3C勧告----
----日本語訳----
バイトオーダーマークが付いていない場合は次のようになる

00 00 00 3CUCS-4または他の32bitコード単位の文字符号化方式でASCII値で符号化したASCII文字。左のそれぞれはビッグエンディアン(1234)、リトルエンディアン(4321),それと普通ではないバイトの並び(2143と3412)。UCS-4か他の32bit符号化方式を適用するかを決定するためにエンコーディング宣言を読まなければならない。
3C 00 00 00
00 00 3C 00
00 3C 00 00
00 3C 00 3F (<?)UTF-16BE またはビッグエンディアンのISO-10646-UCS-2 もしくは、16bitコード単位のビッグエンディアンの文字符号化方式でASCII値で符号化したASCII文字 (3つの内のどれであるかはエンコーディング宣言を読まなければならない。)
3C 00 3F 00 (<?)UTF-16LE またはリトルエンディアンのISO-10646-UCS-2 もしくは、16bitコード単位のリトルエンディアンの文字符号化方式でASCII値で符号化したASCII文字 (3つの内のどれであるかはエンコーディング宣言を読まなければならない。)
3C 3F 78 6D (<?xml)UTF-8、ISO 646、ASCII、ISO 8859の各パート、Shift-JIS、EUC、 他の7ビットの固定長、8ビット固定長、7-8の可変長の符号化方式でASCII値で符号化したASCII文字 (それらの内のどれであるかはエンコーディング宣言を読まなければならない。)
4C 6F A7 94EBCDIC (または、その一部。どのコードページを使うかを知るにはエンコーディング宣言を全部読まなければならない。)
Otherエンコーディング宣言なしのUTF-8か、もしくは間違って名前付けされたデータストリーム(必要なエンコーディング宣言が欠落している)か、損傷しているか、文書の断片か、何らかの形式でラップされている。
----日本語訳----

次のセクションを読みます。

----W3C勧告----
Note:
In cases above which do not require reading the encoding declaration to determine the encoding, section 4.3.3 still requires that the encoding declaration, if present, be read and that the encoding name be checked to match the actual encoding of the entity. Also, it is possible that new character encodings will be invented that will make it necessary to use the encoding declaration to determine the encoding, in cases where this is not required at present.

This level of autodetection is enough to read the XML encoding declaration and parse the character-encoding identifier, which is still necessary to distinguish the individual members of each family of encodings (e.g. to tell UTF-8 from 8859, and the parts of 8859 from each other, or to distinguish the specific EBCDIC code page in use, and so on).

Because the contents of the encoding declaration are restricted to characters from the ASCII repertoire (however encoded), a processor can reliably read the entire encoding declaration as soon as it has detected which family of encodings is in use. Since in practice, all widely used character encodings fall into one of the categories above, the XML encoding declaration allows reasonably reliable in-band labeling of character encodings, even when external sources of information at the operating-system or transport-protocol level are unreliable. Character encodings such as UTF-7 that make overloaded usage of ASCII-valued bytes may fail to be reliably detected.

Once the processor has detected the character encoding in use, it can act appropriately, whether by invoking a separate input routine for each case, or by calling the proper conversion function on each character of input.

Like any self-labeling system, the XML encoding declaration will not work if any software changes the entity's character set or encoding without updating the encoding declaration. Implementors of character-encoding routines should be careful to ensure the accuracy of the internal and external information used to label the entity.
----W3C勧告----
----日本語訳----
注意:
上の場合の中で、文字符号化方式を決定するのにエンコーディング宣言を読み取ることを必要としない場合でも、セクション4.3.3がエンコーディング宣言の仕様に要求するように、もしあるなら、文字符号化方式名を読み込まねばらず、
実際に実体を符号化している文字符号化方式と一致するかをチェックしなければならない。また、現在、既存の文字符号化方式の中には決定するためにエンコーディング宣言を必要としないモノもあるが、新しい文字符号化方式ができることによって既存の文字符号化方式と同じようなバイト列になるためにエンコーディング宣言が必要になる可能性もある。

自動検知のレベルは、エンコーディング宣言を読み込み、文字符号化方式の識別子を解析すれば十分である。ただし、まだ同系統の文字符号化方式のどれになるか区別する必要はある。(例えば、UTF-8を8859から区別する、8859の各パート同士を区別する、または使用している特定のEBCDICコードページを区別する、等々)

エンコーディング宣言の内容は(符号化されているとしても)ASCIIレパートリーの文字に制限してあるので、どの系統の符号化が使用されているかを検知すれば、プロセッサはエンコーディング宣言全体を確実に読み込むことができる。実際には、広く使荒れている文字符号化方式は上記のカテゴリのどれかに当てはまるので、OSや伝送プロトコルレベルが提供する外部情報が信頼できない場合であっても、エンコーディング宣言は文字符号化方式の(入力ストリームの)内部的な名前を使って合理的で信頼性のある手段となる。ASCIIのコード値のバイト列を上書きして使っている例えば、UTF-7のような文字符号化方式は確実に検出できないことがある。

プロセッサが一度、使われている文字符号化方式を検出すれば、それぞれの場合に応じた別の入力ルーチンを呼び出すか入力のそれぞれの文字に正しい変換関数を呼び出すことで、適切に動作することができる。

自分自身にラベルを付けるシステムならどんなものでも同様であるが、もし、エンコーディング宣言を更新せずにソフトウェアが実体の文字集合や文字符号化方式を変えた場合、エンコーディング宣言は機能しない。文字符号化方式のルーチンの実装者は、実体の名前付けに使用する内部と外部の情報の正確さを確保することに配慮すべきである。
----日本語訳----

仕様を整理すると次のようになります。

XMLプロセッサがサポート可能な文字符号化方式
XMLの仕様はサポート可能な文字符号化方式は、ASCII互換を前提にしていると言って良いでしょう。そのため、ASCIIと互換性のないUTF-7のような文字符号化方式は仕様が許す処理では正しく行えないとしています。

仕様が考える実装のヒント
BOMによって確定できる文字符号化方式は現在のところ1つです。一方BOM無で最初の数バイトによって確定できる文字符号化方式は現在においても複数(エンコーディングファミリ)です。

また、今後現れる文字符号化方式がBOMと同じバイト列を持つことによってBOMで確定できる文字符号化方式も1つではなく、複数になりえます。よって、文字符号化方式の処理は常に複数を対象にしてまとめておくほうが拡張性の観点で良いと言えます。

処理パターンは次のようにできます。
1.先頭の4バイトを使って文字符号化方式のファミリ(同一の先頭バイト列を持つ異なる文字符号化方式)を分類する
2.エンコーディング宣言を使ってファミリの中から1つを選択する(ファミリが現在1つの場合もチェックする)

処理の判定の修正
処理パターンの整理を使って処理を修整します。

1.先頭の4バイトを使って文字符号化方式のファミリ(同一の先頭バイト列を持つ異なる文字符号化方式)を分類する
1-1.4バイト分を取得する
1-2.次の一致する文字符号化方式をListに追加する
バイト列:登録する文字符号化:word size
―――――――――――――――――――――――――――――――――
00 00 00 3C:ISO-10646-UCS-4、その他の32bit文字符号化方式:4byte
00 00 3C 00:ISO-10646-UCS-4、その他の32bit文字符号化方式:4byte
00 00 FE FF:ISO-10646-UCS-4:4byte
00 00 FF FE:ISO-10646-UCS-4:4byte
00 3C 00 00:ISO-10646-UCS-4、その他の32bit文字符号化方式:4byte
00 3C 00 3F:UTF-16、ISO-10646-UCS-2、その他の16bitコード単位の文字符号化方式:2byte
3C 00 00 00:ISO-10646-UCS-4、その他の32bit文字符号化方式:4byte
3C 00 3F 00:UTF-16、ISO-10646-UCS-2、その他の16bitコード単位の文字符号化方式:2byte
3C 3F 78 6D:UTF-8、ISO646-US、ISO-8859-1、ISO-8859-2・・・、Shift_JIS、EUC-JP、7bit固定長/8bit固定長/7-8bit固定長のASCIIの文字符号化方式: 1byte
4C 6F A7 94:EBCDIC系のサポートする文字符号化方式:1byte
FF FE 00 00:ISO-10646-UCS-4:4byte
FE FF 00 00:ISO-10646-UCS-4:4byte
FE FF ## ##:UTF-16:2byte
FF FE ## ##:UTF-16:2byte
EF BB BF :UTF-8:1byte※1
その他 :XML宣言/Text宣言をもたないUTF-8、または未知の文字符号化方式:????
2.エンコーディング宣言を使ってファミリの中から1つを選択する(ファミリが現在1つの場合もチェックする)
2-1.指定エンコーディング宣言まで読み込む
2-2.指定エンコーディングの識別子がリスト内にあるかチェックする
2-2-1.一致した場合 :一致した符号化方式を符号化方式として設定する。
2-2-2.一致しない場合:致命的なエラーにする

本コラムで対応する文字符号化方式と処理の修正
対応する文字符号化方式は
文字符号化方式:EncName
―――――――――――
UTF-8 BOM:UTF-8
UTF-8 BOM:UTF-8
UTF-16 BOM付BE:UTF-16
UTF-16 BOM付LE:UTF-16
とします。

処理は次のようになります。
1.先頭の4バイトを使って文字符号化方式のファミリ(同一の先頭バイト列を持つ異なる文字符号化方式)を分類する
1-1.4バイト分を取得する
1-2.次の一致する文字符号化方式をListに追加する
バイト列:文字符号化方式ファミリ:word size
―――――――――――――――――――――――――――――――――
3C 3F 78 6D:UTF-8 BOM無かつXML/Text宣言を持つ:1byte
EF BB BF:UTF-8 BOM:1byte
FE FF ## ##:UTF-16 BOM有BE:2byte
FF FE ## ##:UTF-16 BOM有LE:2byte
その他 :BOM無でXML/Text宣言をもたないUTF-8、または未知の文字符号化方式:????
※未知はサポートしない文字符号化方式を含みます。
※1.0のXML文書の場合、XML宣言は省略可能です。
2.その他の場合:UTF-8にする
3.その他の場合以外の場合、エンコーディング宣言を使ってファミリの中から1つを選択する(ファミリが現在1つの場合もチェックする)
3-1.指定エンコーディング宣言まで読み込む
3-2.指定エンコーディングの識別子がファミリ内にあるかチェックする
3-2-1.ある場合:一致した符号化方式を符号化方式として設定する。
3-2-2.ない場合:致命的なエラーにする

ソフトウェア構成における入力ストリーム作成位置の検討
-ソフトウェア構成の現状
現在の実装では、FileInputStreamとInputStreamReaderの処理はDocumentTokenizerのコンストラクタ内で継承先のBaseFileTokenizerが実行しています。

-実装と修正のための問題点
2つの問題があります。
1つ目は、自動検知機能の処理には、仕様上、致命的なエラーを発生させる場合がありますが、コンストラクタ内で致命的なエラーが発生するとインスタンスは不完全になり、利用してはいけないオブジェクトになることです。

2つ目は、JavaのInputStreamReaderのUTF-8への対応の問題です。残念なことに、InputStreamReaderに文字符号化方式としてUTF-8を指定して、UTF-8にBOM付を指定すると、BOM部分を文字として取得してしまい、これまでの字句解析の実装でWORDトークンとなるため構文解析でエラーになります。

-対策
2つの問題に対処するために、無条件字句解析器の引数はBufferedReaderに変更します。BufferedReaderのインスタンスは、自動検知機能で取得した文字符号化方式のオブジェクトから取得し、無条件字句解析器に渡します。

また、UTF-8BOM付の文字符号化方式のオブジェクトでBufferdReaderを取得する場合は、先頭3バイトを読み捨ててから(BOMをスキップした後に)渡します。

Comment(0)

コメント

コメントを投稿する