はじめに
Unicode が一般的になる以前、ベンダー依存の文字コード(IBM、JEF[富士通]、KEIS[日立]、JIPS[NEC])と、パッケージソフトが動作する環境の文字コードのデータ交換で、苦労することがありました。
そのようなことを経験したので、Unicode に関わる情報を記載しようと思います。
参考情報
Unicode
まずは、Unicode(ISO/IEC 10646, JIS X 0221)について記載します。
基本思想
Unicode の基本思想は、「全世界のあらゆる文字を1つの符号化文字集合の下統一する」ことで、下記内容を達成することを目的としています。
- 自国語コンテンツを Unicode で作成すれば、他言語環境のコンピュータでも、ちゃんと自国語で表示される。
- Unicode でデータを作成すれば、他言語環境でもデータ交換ができる。
表意文字
「全世界のあらゆる文字を1つの文字コード」を行う上で、コード空間(格納できる文字数)には限りがあるし、複数言語でのコミュニケーション性(語彙に重きを置いています)が必要なため「表意文字」という概念で統一(Unified)を行いました。
- 似たような字形でも別の意味や使われ方をするものは別の文字として区別されています。
このため ハイフン/マイナス/ダッシュ のように意味が異なる横棒文字が、100文字以上収録されています。 - CJK統合漢字(CJK Unified Ideographs)
- 中国語/日本語/韓国語の漢字を集めると膨大になってしまうため、字形ではなく、出典が同じで同一の意味を持つ文字はひとつに統一してコード化を行いました。
改版を重ねるにつれて、収録文字数が増加し、字形の軽微な相違のみの文字が多くなり、「表意文字」という基本概念は薄れていると感じています。
文字に関しては、以下の事柄を理解しているとより理解が深まります。
- 字形(グリフ)
- 個々の文字の見た目、形状を指します
- 字種
- 同じ音訓、意味を持ち、語や文章を書き表す際に互換性があるものとして用いられてきた漢字のまとまりです
- 壺と壷、島と嶋、国と國
- 字種が同じで、字形が異なるモノを異体字と呼びます
- 元々の字形(旧字体)「國」 を簡易化して、一般的になった字形「国」を新字体と呼びます
日本語処理として、公的書類の氏名などは、字形が重要視されるので、異体字の扱いはシビアです。
文字符号化方式
文字コードは、「符号化文字集合」を「文字符号化方式」でコード化したものです。
- 符号化文字集合(CCS:Coded Charcter Set)
- 字形の集合を一意の符号(区点コードなど)で定義したもの
- JIS X0201、JIS X0208、JIS X0212、Unicode(ISO/IEC 10646, JIS X 0221) など
- 文字符号化方式(CSE:Character Encoding Scheme)
- 文字集合をコード化するための一定の規則
- JIS、SJIS、EUC、UTF-8、UTF-16、UTF-32 など
Unicode は「符号化文字集合」で、対応する「文字符号化方式」として、UTF-8、UTF-16、UTF-32 が存在します。
- UTF-8
- Unicode で一番よく利用される形式
- ASCIIは1バイト、その他の部分は2~4バイトという可変長で表現
- 日本語漢字は、ほとんどが3バイトですが、第3・第4水準漢字の大半は4バイト
- UTF-16
- Unicode を 16ビットで表現
- C# string、char は UTF-16
- 収録文字数が増加し、16ビットでは表現しきれなくなり、「サロゲートペア」(一部コード領域で、2つペア[16 + 16ビット]で使って1文字を表す方法)導入
上位サロゲートのコード範囲は D800 〜 DBFF で、下位サロゲートのコード範囲は DC00 〜 DFFF
- UTF-32
- Unicode を 32ビットで表現
恩恵
「符号化文字集合」が、JIS X0201/JIS X0208 のみで構成されていた時代は、登録されている漢字が少なく、氏名などを正確な字形とする目的などで、ユーザ毎にユーザ定義文字(外字)を作成/登録することが多々ありました。
ユーザ定義文字として登録された文字を、他コンピュータで表示させるためには、同一コードに対象ユーザ定義文字を登録する必要があります。
また、ベンダー毎に、JIS X0201/JIS X0208 では足りていない字形を、拡張文字(システム外字)として標準搭載することもありました。
1993年に Microsoft は Windows 3.1 をリリースするにあたり、日本で Windowsマシンを販売していた双璧 NEC と IBM、双方で標準搭載されていた拡張文字を統合して、現在の CP932(MS932, Windows-31J)となりました。
CP932では、SJISコード空間の下記に拡張文字が標準搭載されています。
- 8740-879E NEC特殊文字
- ED40-EEFC NEC認定IBM拡張文字
- FA40-FCFC IBM拡張文字
データ交換に携わったことがあるので、ユーザ定義文字、拡張文字の呪縛から解放されて、利便性向上したことが、Unicode 最大の恩恵と実感できています。
厄介事
基本概念「表意文字」、基本機能「合成文字」、および、後から追加された「サロゲートペア」「異体字セレクタ」に関して、手間がかかる局面があります。
表意文字
PDFからの文字抽出ソフトを利用したことがあり、取得した文字データの中に「No-Break Space(U+00A0)」「En Space(U+2002)」「Em Space(U+2003)」などが含まれていました。
これらは、日本語としてデータ処理(比較/並び替え)する上では「Space(U+0020)」に集約が必要となります。
Char.IsWhiteSpace を使い、集約する処理を追加する手間が増えました。
PDF作成ソフト、もしくは、PDFからの文字抽出ソフトでの不具合かもしれませんが、横棒文字、スペースなどが「表意文字」として多数存在する故に発生しています。
PDF作成ソフトで、文書ファイル(Word、Excel、テキストファイルなど)からPDFを作成した場合、PDF内に文字列オブジェクトとして、対象文書の文字コードデータが格納されます。
PDFからの文字抽出ソフトは、PDF内の文字列オブジェクトから文字コードデータを抽出するもので、文字認識を行うものではありません。
PDFビューアからのコピペも、PDF内の文字列オブジェクトから文字コードデータを抽出して利用します。
合成文字
Unicode では、複数の文字からひとつの文字を合成するしくみがあります。
- 「ポ」U+30DD は、「ホ」に半濁音を合成済みの文字です。
- 「ホ」U+30DB と半濁音 U+309A を連続させると1文字として合成されて表示されます。
- Unicode のコンセプトとしては「合成済み文字と合成文字(基底文字+結合文字)は等価」というスタンスですが、現実には、そのままだとデータ処理(比較/並び替え)で等価にすることはできず、以下の何れかの正規化が必要となります。
- 合成(合成文字を合成済み文字に揃える)
- 分解(合成済み文字を合成文字に揃える)
C# での振舞いを確認した結果を記載します。
string hoge = "\u30DB\u309A"; // ホ + 半濁音合成文字
// 文字列の長さ
int n1 = hoge.Length; // 2
// 文字数(合成文字を1文字として判断)
System.Globalization.StringInfo si = new System.Globalization.StringInfo(hoge);
int n2 = si.LengthInTextElements; // 1
// サロゲートペア、合成文字、IVS のインデックス取得 → idx.Length = 1, idx[0] = 0
int [] idx = System.Globalization.StringInfo.ParseCombiningCharacters(hoge);
文字列の長さ取得と文字数取得は使い分けが必要で、場合によっては合成文字確認も必要になります。
サロゲートペア
C# での振舞いを確認した結果を記載します。
string hoge = "\uD842\uDF9F"; // 叱 UTF-16で(16 + 16ビット)ペアで1文字
// 文字列の長さ
int n1 = hoge.Length; // 2
// 文字数(サロゲートペアを1文字として判断)
System.Globalization.StringInfo si = new System.Globalization.StringInfo(hoge);
int n2 = si.LengthInTextElements; // 1
// サロゲートペア1文字目、2文字目確認
bool b1 = Char.IsHighSurrogate(hoge, 0); // true
bool b2 = Char.IsLowSurrogate(hoge, 1); // true
// サロゲートペア、合成文字、IVS のインデックス取得 → idx.Length = 1, idx[0] = 0
int [] idx = System.Globalization.StringInfo.ParseCombiningCharacters(hoge);
文字列の長さ取得と文字数取得は使い分けが必要で、場合によってはサロゲートペア確認も必要になります。
異体字セレクタ
日本語、特に人名に使用される漢字には、細かなデザイン違いの異体字が多く存在します。
このように字形の相違がある異体字全てに、文字コードを付与することは無理があります。
このため、親字の後ろに異体字セレクタ(U+E0100~)と呼ばれるコードを付加して字形を表現する手法(IVS:Ideographic Variation Sequence)が導入されました。
異体字セレクタは、UTF-16 ではサロゲートペアとして表現されます。
C# リテラル記述 \U は UTF-32 表記で \u は UTF-16 表記なので、「葛󠄀」は下記いずれかで表現できます。
"葛\U000E0100"
"葛\uDB40\uDD00"
C# での振舞いを確認した結果を記載します。
string hoge = "葛\U000E0100"; // "葛\uDB40\uDD00" と等価
// 文字列の長さ
int n1 = hoge.Length; // 3(UTF-16としての文字列の長さ)
// 文字数(IVSを1文字として判断)
System.Globalization.StringInfo si = new System.Globalization.StringInfo(hoge);
int n2 = si.LengthInTextElements; // 1
// サロゲートペア1文字目、2文字目確認
bool b1 = Char.IsHighSurrogate(hoge, 1); // true
bool b2 = Char.IsLowSurrogate(hoge, 2); // true
// サロゲートペア、合成文字、IVS のインデックス取得 → idx.Length = 1, idx[0] = 0
int [] idx = System.Globalization.StringInfo.ParseCombiningCharacters(hoge);
文字列の長さ取得と文字数取得は使い分けが必要で、場合によってはIVS確認も必要になります。