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

第041回_EncodingDecl1

»
前回
 XMLDecl ::= '<?xml' VersionInfo EncodingDecl? SDDecl? S? '?>'
のVersionInfoまで説明しましたので、今回はXMLDeclの中のEncodingDeclについて解説します。

EncodingDeclの前知識

EncodingDeclはエンコーディング宣言です。エンコーディング宣言は、そのXMLの文字符号化方式について指定します。エンコーディング宣言の説明の前に文字コードに関する知識について書いてみます。
# 私の得意分野でないのでネットに書いてあるようなことです。

コンピュータ上の文字の表示手順
-表示しているのは画像
コンピュータ上に表示する文字は要は一種の画像と考えてみてください。
 例)「あ」という、直線と曲線をもつ線を計算して書いてるわけではなく、ドットの集合として保存しています。
このドットの集合を文字毎に定義し、集めたモノをフォントと言います。Wordなどにフォントは出てきますからお馴染みの用語で説明は不要だと思います。

-文字コード
文字列について考えてみましょう。例えば、txtファイルは文字列から成りますが、文字毎に該当する画像を全部結合した画像データを持つわけではありません。そのようなことをしたらtxtファイルは容量の巨大な画像ファイルになってしまいますから想像に難くないでしょう。そこでOSは文字毎に取り決めた番号を付与しておくことにしました。この取り決めた番号のことを文字コードと呼び、文字コードの羅列をtxtファイルとして保存します。

-デコード処理
OSがアプリケーションを通してtxtファイルの中身を表示するときは、文字コードの羅列に対して該当する文字を示すフォントを表示します。この処理をデコード(復号化、decode)ということもあります。

文字集合(charset)
文字コードを定義するためにコンピュータやOSの開発者は文字集合という概念を作りました。ASCIIは1バイトのうち7ビットを使って128種類の文字コードを規定しています。 ASCIIでは平仮名や片仮名、漢字を取り扱えませんから
 片仮名を追加した1バイトのJIS X 0201

 平仮名、片仮名、漢字、英数字を取り扱うJIS X 0208
 マイクロソフトが作ったシフトJIS
 Unixで作ったEUC
も登場しました。

-問題点(文字化け)
ただし、国際機関で決めたわけではないので
取り扱うことのできる記号の範囲が違う
同じ文字でもWindowsが取り扱う番号、Unixが取り扱う番号、Macで取り扱う番号が異なる。
同じOSでもプロセッサ側のエンディアンが違う〈バイトオーダが違う〉
といった問題が発生しました。

-対策
範囲が違うものについてはどうしようもありません。そこで統一した文字集合をなるべく取り扱うという流れができ、業界規格のUnicodeを規定するようになりました。

余談 ほぼ同時期に国際規格としてISO/IEC 10646が検討されていましたがUnicodeと同じ目的の規格が2つ作られることを避けるためにISO/IEC 10646が正式に国際規格になる前にそれまでの検討内容を捨ててUnicodeの内容に合わせた そうです。ということで正確ではないですが、Unicodeはほぼ国際規格というか国際規格名がISO/IEC 10646と言えると思います。

番号の違いは変換表を用意すれば解決します。統一した番号を用意しても良いのですが、昔の記憶容量が少なかった時代や今でも組み込み系のように限られた記憶容量ではUnicodeでは大きすぎ、ASCIIで事足りる場合も多いです。つまり、Unicodeだけを使うという考え方は冗長です。

エンディアンが違うというのはプロセッサ毎のメモリ番地のアクセス方法であったり通信方式によって違いがあります。これはOSより下層にあるため対策しにくいです。そこで文字集合を文字の集合部分と文字の符号化部分の2つの概念に分けるようになりました。つまり、文字の集合からどのように文字コードへ変換するかの方式を分離することでエンディアンの違いやプロトコル方式の違いに対策できるようにしたわけです。文字の集合のことを符号化文字集合、変換方式のことを文字符号化方式と呼びます。

エンコーディング宣言が指定するのは文字符号化方式です。符号化文字集合については、XMLの構文Charで厳密に規定してありますのでXMLファイル上で指定することはできません。せっかくですので、XMLの符号化文字集合と文字符号化方式についての仕様と合わせてそれぞれの概念について解説してみます。

符号化文字集合
文字集合を2つの概念に分けたものの内、文字の集合を定義したものを符号化文字集合と呼びます。符号化文字集合は単なる文字の集合ですが、結局コンピュータの記憶領域に保存するために数値化します。この数値をコードポイントと呼びます。コードポイントは文字コードとは違います。

-XMLファイルの符号化文字集合
XMLファイルで使うことのできる文字集合はW3Cの勧告によって次のようになっています。

----W3C勧告----
2.2 Characters
[Definition: A parsed entity contains text, a sequence of characters, which may represent markup or character data.] [Definition: A character is an atomic unit of text as specified by ISO/IEC 10646 [ISO/IEC 10646]. Legal characters are tab, carriage return, line feed, and the legal characters of Unicode and ISO/IEC 10646. The versions of these standards cited in A.1 Normative References were current at the time this document was prepared. New characters may be added to these standards by amendments or new editions. Consequently, XML processors must accept any character in the range specified for Char.]

Character Range
[2] Char ::= [#x1-#xD7FF] | [#xE000-#xFFFD]
       | [#x10000-#x10FFFF]
/* any Unicode character, excluding the surrogate blocks, FFFE, and FFFF. */
[2a] RestrictedChar ::= [#x1-#x8] | [#xB-#xC] | [#xE-#x1F]
            | [#x7F-#x84] | [#x86-#x9F]

The mechanism for encoding character code points into bit patterns may vary from entity to entity. All XML processors must accept the UTF-8 and UTF-16 encodings of Unicode [Unicode]; the mechanisms for signaling
which of the two is in use, or for bringing other encodings into play, are discussed later, in 4.3.3 Character Encoding in Entities.
----W3C勧告----
----日本語訳----
2.2 文字
[定義: 解析対象実体はテキスト--文字列--を含み、テキストはマークアップまたは文字データを表現する。]
[定義: 文字は、ISO/IEC 10646 [ISO/IEC 10646]で仕様化したテキストの最小単位である。XMLで正当な文字はタブ、キャリッジリターン、ラインフィード、UnicodeとISO/IEC10646の正当な文字から成る。 A.1 規範的な参考文献 の章で引用しているこれらの標準規格のバージョンは、この文書を用意した時点で最新のものである。これらの規格が修正したり、新しい版になると新しい文字が正当な文字として追加になるかもしれない。従って、XMLプロセッサは構文Charで仕様化してある範囲の文字を許容しなければならない。]

文字の範囲
[2] Char ::= [#x1-#xD7FF] | [#xE000-#xFFFD]
       | [#x10000-#x10FFFF]
/* サロゲートブロック、FFFE、FFFFを除くすべてのUnicode文字。 */
[2a] RestrictedChar ::= [#x1-#x8] | [#xB-#xC] | [#xE-#x1F]
            | [#x7F-#x84] | [#x86-#x9F]

文字コードのコードポイントをビットパターンに符号化する機構は、それぞれの実体で異なっていても良い。すべてのXMLプロセッサはUnicode[Unicode]の文字符号化方式UTF-8とUTF-16を許容しなければならない。;2つの文字符号化方式のどちらを使っているかを知らせる機構やほかの文字符号化方式を使うための機構は 4.3.3 実体で使われる文字符号化方式 の章で議論する。
----日本語訳----

CharはXMLの全体で出てくる最小要素で、RestrictCharは集合演算で除算する文字に使われているので、XMLの符号化文字集合はCharからRestrictedCharを除外したものと言えます。

文字符号化方式
符号化文字集合の持つコードポイントをどのような文字コードに変換するかの方式です。

さて、文字コード・符号化文字集合・文字符号化方式に関する知識について説明が完了したので本題のEncodingDeclに話を戻します。

先のW3Cの勧告の2.2 文字 の章からXMLプロセッサはUTF-8とUTF-16を具備しなければいけません。

現在の自作XMLパーサの実装に関して

これまでの実装
これまで本コラムで実装してきた方法は第35回のコラムで示した通り、DocumentTokenizerの中でFileReaderを使って1行ずつ読み込んでいます。FileReaderクラスは文字符号化方式を指定できないため、その環境のデフォルトの文字符号化方式を使ってファイルの読み込みを行います。この実装には2つの問題点があります。

問題点1
W3Cの勧告の要件を満たすためには、最低限UTF-8かUTF-16のどちらかであるかは判別した上でファイルを読み込む機構が必要です。尚、読み込みクラス自体はInputStreamReaderを使って文字符号化方式を指定してファイルを読み込むことができます。

問題点2
そもそもEncodingDeclを読み込むのは構文解析時です。一方、ファイルを読み込む際にInputStreamReaderを使うのは無条件字句解析を行う場合で構文解析より前です。

これら2つの問題点についての説明もW3Cの勧告にありますので確認していきます。
# 読みやすさのために画面に入る程度のボリュームに分けて読みます。

まずはエンコーディング宣言の仕様をみていきます。

----W3C勧告----
4.3.3 Character Encoding in Entities

Each external parsed entity in an XML document may use a different encoding for its characters. All XML processors must be able to read entities in both the UTF-8 and UTF-16 encodings. The terms "UTF-8" and "UTF-16" in this specification do not apply to character encodings with any other labels, even if the encodings or labels are very similar to UTF-8 or UTF-16.

Entities encoded in UTF-16 must and entities encoded in UTF-8 may begin with the Byte Order Mark described in ISO/IEC 10646 [ISO/IEC 10646] or Unicode [Unicode] (the ZERO WIDTH NO-BREAK SPACE character, #xFEFF). This is an encoding signature, not part of either the markup or the character data of the XML document. XML processors must be able to use this character to differentiate between UTF-8 and UTF-16 encoded documents.

Although an XML processor is required to read only entities in the UTF-8 and UTF-16 encodings, it is recognized that other encodings are used around the world, and it may be desired for XML processors to read entities that use them. In the absence of external character encoding information (such as MIME headers), parsed entities which are stored in an encoding other than UTF-8 or UTF-16 must begin with a text declaration (see 4.3.1 The Text Declaration) containing an encoding declaration:

Encoding Declaration

 [80] EncodingDecl ::= S 'encoding' Eq
            ('"' EncName '"' | "'" EncName "'" )
 [81] EncName ::= [A−Za−z] ([A−Za−z0−9._] | '−')*
 /* Encoding name contains only Latin characters */

In the document entity, the encoding declaration is part of the XML declaration. The EncName is the name of the encoding used.
----W3C勧告----
----日本語訳----
4.3.3 実体で使う文字符号化方式
XML文書の中にある各外部解析実体は文字に対する符号化方式が異なっていても良い。すべてのXMLプロセッサはUTF-8とUTF-16の符号化方式の両方の実体を読むことができなければいけない。この仕様書の中で"UTF-8"と"UTF-16"の用語は他の名前の文字符号化方式を示したりしない。UTF-8やUTF-16によく似た符号化方式や名前であったとしてもよく似た名前を示すためにUTF-8やUTF-16を使うことは
できない。

UTF-16で符号化した実体(の入力ストリーム)は、バイトオーダーマークで始まらなければいけない。UTF-8で符号化した実体(の入力ストリーム)は、バイトオーダーマークで始まっても良い。バイトーダーマークは、ISO/IEC 10646 [ISO/IEC 10646] or Unicode [Unicode]にて規定されていて、ZERO WIDTH NO-BREAK SPACE 文字, つまり、#xFEFFを指す。バイトオーダーマークは、符号化方式のシグネチャであり、XML文書中のマークアップや文字データの一部ではない。
XMLプロセッサはUTF-8とUTF-16で符号化した文書を区別するために、バイトオーダーマークを取り扱うことができなければいけない。

XMLプロセッサの対応しなければならない文字符号化方式はUTF-8とUTF-16を読めることだけであるが、世界中で使われているその他の文字符号化方式があり、XMLプロセッサがそれらの文字符号化方式を使って実体を読みこむことを望まれるかもしれない。外部の文字符号化方式情報(例えば、MIMEヘッダ)が無いのであれば、UTF-8とUTF-16以外の文字符号化方式で保存された解析対象実体は、エンコーディング宣言を含むテキスト宣言(4.3.1 テキスト宣言)で始まらなければいけない。
※説明文では解析対象実体は~とかかれているが、続きの部分で外部実体の始まり以外では
致命的エラーとする必要があるので、適用するのは外部解析対象実体です。

エンコーディング宣言

 [80] EncodingDecl ::= S 'encoding' Eq
            ('"' EncName '"' | "'" EncName "'" )
 [81] EncName ::= [A−Za−z] ([A−Za−z0−9._] | '−')*
 /* ラテン文字だけを含む文字符号化方式名 */

文書実体の中では、エンコーディング宣言はXML宣言の一部である。EncNameは使われている文字符号化方式名である。
----日本語訳----

続きの部分をみていきます。

----W3C勧告----
In an encoding declaration, the values "UTF-8", "UTF-16", "ISO-10646-UCS-2", and "ISO-10646-UCS-4" should be used for the various encodings and transformations of Unicode / ISO/IEC 10646, the values "ISO-8859-1", "ISO-8859-2", ... "ISO-8859-n" (where n is the part number) should be used for the parts of ISO 8859, and the values "ISO-2022-JP", "Shift_JIS", and "EUC-JP" should be used for the various encoded forms of JIS X-0208-1997. It is recommended that character encodings registered (as charsets) with the Internet Assigned Numbers Authority [IANA-CHARSETS], other than those just listed, be referred to using their registered names; other encodings should use names starting with an "x-" prefix. XML processors should match character encoding names in a case-insensitive way and should either interpret an IANA-registered name as the encoding registered at IANA for that name or treat it as unknown (processors are, of course, not required to support all IANA-registered encodings).

In the absence of information provided by an external transport protocol (e.g. HTTP or MIME), it is a fatal error for an entity including an encoding declaration to be presented to the XML processor in an encoding other than that named in the declaration, or for an entity which begins with neither a Byte Order Mark nor an encoding declaration to use an encoding other than UTF-8. Note that since ASCII is a subset of UTF-8, ordinary ASCII entities do not strictly need an encoding declaration.

It is a fatal error for a TextDecl to occur other than at the beginning of an external entity.

It is a fatal error when an XML processor encounters an entity with an encoding that it is unable to process. It is a fatal error if an XML entity is determined (via default, encoding declaration, or higher-level protocol) to be in a certain encoding but contains byte sequences that are not legal in that encoding. Specifically, it is a fatal error if an entity encoded in UTF-8 contains any irregular code unit sequences, as defined in Unicode [Unicode]. Unless an encoding is determined by a higher-level protocol, it is also a fatal error if an XML entity contains no encoding declaration and its content is not legal UTF-8 or UTF-16.
----W3C勧告----
----日本語訳----
エンコーディング宣言の中で、EncNameの値が"UTF-8"、"UTF-16"、"ISO-10646-UCS-2"、"ISO-10646-UCS-4"のとき、XMLプロセッサは、Unicode / ISO/EIC 10646の規定する多種の符号化方式とトランスフォーメーションを指定したとして動作するべきであり、EncNameの値が"ISO-8859-1", "ISO-8859-2", ... "ISO-8859-n"(nはパート番号)のとき、ISO 8859 のパートを指定したとして動作するべきであり、EncNameが"ISO-2022-JP", "Shift_JIS", "EUC-JP"のとき、JIS X-0208-1997の多種の符号化方式を指定したとして動作するべきである。Internet Assigned Numbers Authority [IANA-CHARSETS]が(文字集合として)登録した文字符号化方式をその登録名をEncNameに使えることが推奨されている。ただし、ただリストしただけの登録名は含まない。他の符号化方式は"x-"の接頭辞で始まる名前を使うべきである。XMLプロセッサはEncNameの突き合せするときは大文字小文字を無視するべきである。またXMLプロセッサはIANA登録名をIANAが登録した符号化方式として解釈するか、不明な符号化方式として取り扱うべきである。(もちろん、XMLプロセッサはIANA登録した符号化方式をサポートする要件があるわけではない)

外部転送プロトコル(例えばHTTPやMIME)が提供する情報が無い場合、
XMLプロセッサはエンコーディング宣言を含む実体に対して、指定した名前と異なる符号化方式のデータを受け取ると
致命的エラーとする。また、XMLプロセッサはバイトオーダーマークもエンコーディング宣言もなく始まる実体に対して、
UTF-8以外の符号化方式を使うことは致命的なエラーとする。

ASCIIはUTF-8サブセットなので、一般のASCII実体はエンコーディング宣言を厳密には必要としないということに注意してほしい。

外部実体の始まり以外の場所にテキスト宣言が現れることは致命的なエラーとする。

XMLプロセッサが処理できない符号化方式を持つ実体に遭遇したとき致命的なエラーとする。もし、XML実体が(デフォルトの、もしくはエンコーディング宣言で、もしくはより上位のプロトコルで)決定した符号化方式を持つ場合、しかし、決定した符号化方式で正当でないバイト列を含むときは致命的なエラーとする。具体的に言うと、UTF-8の実体がUnicode [Unicode] で定義した不正なコード単位列を含む場合致命的なエラーとする。より上位のプロトコルで符号化方式が決定されない限り、XML実体がエンコーディング宣言を持たずUTF-8かUTF-16として正当でなければ致命的なエラーとする。
----日本語訳----

Byte Order Markについて
Byte Order Markは通称BOMと呼ばれています。BOMはUnicodeの符号化形式で符号化したテキストの先頭につける数バイトのデータを指します。これはUnicodeのファイル形式を判別するのに利用する情報なので、BOM付のXMLファイルをテキストファイルとして開いても(そのテキストエディタがBOM付のUnicodeの表示に対応しているのならば)テキストエディタはBOMを表示しません。odコマンドで確認すればその存在を確認できます。

文字符号化方式:付加するBOMの値:補足
―――――――――――――――――――――――――――――
UTF-8:0xEF 0xBB 0xBF:
UTF-16(BE):0xFE 0xFF:BEはビックエンディアン
UTF-16(LE):0xFF 0xFE:LEはリトルエンディアン

詳しくはWiki等を参考にしてください。

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

仕様が定義している処理の時期
説明が長くなって判り難くなるのを避けるために処理の時期について分類し、分割統治します。

エンコーディング宣言を含む文字符号化方式に関する仕様は次の4つの時期に集約できます。
 1.文字符号化方式を指定してファイルの先頭を読み込む前の処理
 2.ファイル情報から文字符号化方式を判別する処理
 3.ファイルを読み進めていく途中の処理
 4.EncNameを読み込んだときの処理

文字符号化方式を指定してファイルの先頭を読み込む前の処理
外部転送プロトコルのような上位から文字符号化方式を指定しても良い。
# 文字符号化方式を指定しなければならないわけではない

文字符号化方式を指定しない場合、
XMLプロセッサは少なくてもUTF-8とUTF-16の2つの文字符号化方式に対応しなければならないので少なくともUTF-8とUTF-16のファイル形式を識別し、文字符号化方式に合わせてファイルをオープンする。

つまり、
符号化方式の指定機能 : 呼び出し時の指定の有無 : オープンする方法
―――――――――――――――――――――――――――――――――――――
指定できる : 指定したとき : 指定した文字符号化方式でファイルをオープンする
: 指定しないとき : ファイル情報から文字符号化方式を判別してファイルをオープンする
指定できない : (指定なし) : ファイル情報から文字符号化方式を判別してファイルをオープンする

となるように実装する必要があります。ただし、指定した文字符号化方式が未対応の文字符号化方式の場合は、オープン前に致命的なエラーにします。

ファイル情報から文字符号化方式を判別する処理
少なくともUTF-8とUTF-16,または未対応の文字符号化方式か判定します。細かい処理機構については次回のコラムで議論します。

ファイルを読み進めていく途中の処理
指定または自動判別した文字符号化方式で読むことができない文字コードを発見したとき、致命的なエラーとします。
# 例えば、UTF-8でUnicodeに定義されていない不正なコード単位列を発見した場合は致命的なエラーとします。

EncNameを読み込んだときの処理
-共通の処理
ファイル情報から判定した文字符号化方式 と EncNameに書かれている文字符号化方式が一致しているか確認します。
# 呼び出し側が指定した文字符号化方式 と
# EncNameに書かれている文字符号化方式は比較しません。

-比較処理の詳細
▼EncNameが未指定の場合
ファイル情報から判定した文字符号化方式がUTF-8かUTF-16の場合:OK
ファイル情報から判定した文字符号化方式がUTF-8かUTF-16以外の場合:致命的なエラーにします。

▼EncNameの指定がある場合
比較する場合、EncNameの大文字小文字の違いは無視します。未対応の文字符号化方式の場合は致命的なエラーにします。

※EncNameが未指定でUTF-16のBOM無しの場合、致命的エラーとする処理は必要ありません。なぜなら、EncNameの指定の有無に関わらず、ファイルの先頭にUTF-16のBOMがあるときだけUTF-16として認識するからです。

-比較するときの対応表
EncName:符号化文字集合:文字符号化方式:備考
――――――――――――――――――――――――――――――――――
UTF-8:Unicode:UTF-8:必須
UTF-16:Unicode:UTF-16:必須
ISO-10646-UCS-2:Unicode:UCS-2:オプション
ISO-10646-UCS-4:Unicode:UCS-4:オプション
ISO-8859-1:ISO8859パート1:ISO8859:オプション
ISO-8859-2:ISO8859パート2:ISO8859:オプション
・・・・・・・・・・・・:
ISO-8859-n:ISO8859パートn:ISO8859:オプション
ISO-2022-JP:JISX-0208-1997:ISO-2022-JP:オプション
Shift_JIS:JISX-0208-1997:Shift_JIS:オプション
EUC-JP:JISX-0208-1997:EUC-JP:オプション
・・・・・・・・・・・・・・・・
IANA登録名(その他):登録した文字集合:登録した方式:
IANA未登録(その他):登録した文字集合:登録した方式:x-で始まるべき

次回は、ファイル情報から文字符号化方式を判別する処理の細かい処理機構について議論します。
Comment(0)

コメント

コメントを投稿する