Unicode で真にやっかいなのは、結合文字列(Combining Character Sequence) と 合成文字(Composite Character)です。
このトピックでは Delphi 2009 で結合文字列を現実的な手段で扱う方法について解説します。
結合文字列
結合文字列と文字列検索
では、文字を検索してみましょう。対象となる文字列は “
” です。
この文字列から、“
” を検索してみます。
procedure TForm1.Button1Click(Sender: TObject);
var
S: String;
begin
S := #$0067#$1EB7#$0070' '#$0073#$0061#$0075' '#$006E#$0068#$00E8;
if Pos(#$1EB7, S) > 0 then
ShowMessage('Found.')
else
ShowMessage('Not Found.');
end;
この結果は当然ながらOKです。しかしながら、ファイルあるいは入力された文字列には結合文字列が含まれる可能性があります。
procedure TForm1.Button1Click(Sender: TObject);
var
S: String;
begin
S := Edit1.Text;
if (Pos(#$1EB7, S) > 0) or (Pos(#$0061#$0323#$0306, S) > 0) then
ShowMessage('Found.')
else
ShowMessage('Not Found.');
end;
このようにコードを変更してみました。しかしながら、このコードでは、今回のような結合文字列を正しく処理したことにはなりません。
「声調記号 (U+0323) と ブレーヴェ記号 (U+0306) が入れ替わる場合があるから?」…いえいえ、そうではありません。結合文字の並びには規則があり、“U+0061 U+0323 U+0306” というシーケンスはあっても、“U+0061 U+0306 U+0323” というシーケンスは有り得ないのです。不正なシーケンスは、通常の入力方法では実現できず、できたとしても 結合文字列として認識されないか、正常なシーケンスに矯正されてしまいます。
では、一体何が問題なのでしょうか?
実はベトナムの文字には、“
+ブレーヴェ記号” の “
(U+0103)” や “
+声調記号” の “
(U+1EA1)” があり、これらの文字に結合文字を加えて 結合文字列 “
” を形成する事も可能なのです。

var
Dmy: String;
begin
Dmy := '';
Dmy := Dmy + 'U+1EB7: ' + #$1EB7 + #$000D#$000A;
Dmy := Dmy + #$000D#$000A;
Dmy := Dmy + 'U+0061 U+0323 U+0306: ' + #$0061#$0323#$0306 + #$000D#$000A;
Dmy := Dmy + Format('( %s + %s + %s )', [#$0061, #$0323, #$0306]) + #$000D#$000A;
Dmy := Dmy + #$000D#$000A;
Dmy := Dmy + 'U+0103 U+0323: ' + #$0103#$0323 + #$000D#$000A;
Dmy := Dmy + Format('( %s + %s )', [#$0103, #$0323]) + #$000D#$000A;
Dmy := Dmy + #$000D#$000A;
Dmy := Dmy + 'U+1EA1 U+0306: ' + #$1EA1#$0306 + #$000D#$000A;;
Dmy := Dmy + Format('( %s + %s )', [#$1EA1, #$0306]);
ShowMessage(Dmy);
end;
上記の結果は、次のようにすべて同じ字形が表示されます。
さて、表示上は同じに見えるこれらの文字列は、コード上では別のものです。では、これらを同じに扱って検索するために、全世界の文字の結合文字列の組み合わせを調べますか?それはあまりにも無謀というものです。
正規化 / 標準化 (Normalize)
そこで正規化です。正規化(標準化)を難しく捉える必要はありません。今までにも、あなたは何らかの正規化を体験しているはずです。
if UpperCase(Edit1.Text) <> UpperCase(Edit2.Text) then
ShowMessage('Error');
これも正規化の一種です。“比較対象となる2つの文字列の比較条件を何らかの方法を用いて揃える”…これが “正規化” です。
Unicode における正規化には、4つの種類があります。
- NFC (Normalization Form Canonical Composition)
正規等価な合成文字があればそれを結合文字列に分解し、正規等価な合成文字に合成する。
- NFD (Normalization Form Canonical Decomposition)
正規等価な合成文字があればそれを結合文字列に分解する。
- NFKC (Normalization Form Compatibility Composition)
互換等価な合成文字があればそれを結合文字列に分解し、正規等価な合成文字に合成する。
- NFKD (Normalization Form Compatibility Decomposition)
互換等価な合成文字があればそれを結合文字列に分解する。
簡単に言えば、“正規等価” とは “見た目が全く同じ” という事で、“互換等価” とは、“ 字形は異なるが、代替が可能” という事です。
Unicode の文字列比較では、これらいずれかの正規化を前処理として行うのが前提となっています。
Delphi 2009 で正規化を行う
残念ながら、Delphi 2009RTL には Unicode 正規化を行う関数やクラスは用意されていません。そこで、正規化を行う関数を用意してみました。
type
TNORM_FORM =
(
NormalizationOther = $00,
NormalizationC = $01,
NormalizationD = $02,
NormalizationKC = $05,
NormalizationKD = $06
);
function MecsNormalize(const Src: WideString; var Dst: WideString; NormForm: TNORM_FORM): Boolean;
var
i: Integer;
O,P :PWideChar;
BufSize: Integer;
FP: TFarProc;
DLLWnd: THandle;
NormalizeString: function(NormForm: Integer; lpSrcString: LPCWSTR; cwSrMecsLength: Integer;
lpDstString: LPWSTR; cwDstLength: Integer):Integer; stdcall;
begin
result := False;
Dst := '';
if Length(Src) = 0 then
begin
result := True;
Exit;
end;
DLLWnd := LoadLibraryW('normaliz.dll');
DLLWnd := LoadLibraryA('normaliz.dll');
try
FP := GetProcAddress(DLLWnd, 'NormalizeString');
if FP <> nil then
begin
@NormalizeString := FP;
BufSize := NormalizeString(Integer(NormForm), PWideChar(Src), Length(Src), nil, 0);
if (GetLastError <> 0) then
Exit;
if (BufSize = 0) then
begin
result := True;
Exit;
end;
SetLength(Dst, BufSize);
P := PWideChar(Dst);
for i:=1 to BufSize do
begin
P^ := #$0000;
Inc(P);
end;
NormalizeString(Integer(NormForm), PWideChar(Src), Length(Src), PWideChar(Dst), Length(Dst));
if (GetLastError <> 0) then
Exit;
P := PWideChar(Dst);
O := P;
while (P^ <> #$0000) do
Inc(P);
SetLength(Dst, P - O);
result := True;
end;
finally
if DLLWnd > 0 then
FreeLibrary(DLLWnd);
end;
end;
この関数はMECSUtils (http://cc.codegear.com/item/26061) から抜粋したもので、Delphi 2009 / Delphi 2007 で利用する事が可能です。詳細な利用方法は以下のリファレンスを参照して下さい。
MecsNormalize() のリファレンス
MecsNormalize 関数はUnicode文字列を正規化(標準化)します。
Delphiの構文:
function MecsNormalize(const Src: WideString; var Dst: WideString; NormForm: TNORM_FORM): Boolean;
説明:
MecsNormalize 関数はUnicode文字列を正規化します。Src に指定されたUnicode 文字列を NormForm で指定した方法で正規化し、Dst に格納します。正規化に成功した場合には戻り値に True , 失敗した場合には False が返ります。正規化の種類については以下を参照してください。
TNORM_FORM |
説明 |
NormalizationOther |
その他の正規化 (標準化)方法。 |
NormalizationC |
Normalization Form Canonical Composition (NFC) |
NormalizationD |
Normalization Form Canonical Decomposition (NFD) |
NormalizationKC |
Normalization Form Compatibility Composition (NFKC) |
NormalizationKD |
Normalization Form Compatibility Decomposition (NFKD) |
|
|
注意:
MecsNormalize 関数を利用する場合、XP / Serever 2003 では“Microsoft Internationalized Domain Names (IDN) Mitigation APIs 1.1” または “Internet Explorer 7.0” 以上が必要となります。Vista 以降の OS では、特に必要とするものはありません。
MecsNormalize() の使い方
以下のようにして使います。
var
S1, S2: WideString;
begin
if not MecsNormalize(Edit1.Text , S1, NormalizationD) then
S1 := Edit1.Text;
if not MecsNormalize(Edit2.Text , S2, NormalizationD) then
S2 := Edit2.Text;
if S1 = S2 then
MessageBox('Equal')
else
MessageBox('Not Equal');
end;
例では NFD で正規化して文字列比較を行っています。
まとめ
Unicode では、結合文字列を使おうが、合成文字を使おうが、それは自由となっています。1文字を表現するのに結合文字列を使ったとしても、それはルール違反でもなければ、特殊な事例でもありません。逆に言えば、Unicode アプリケーションではそれらを普通に扱えるようにする必要があります。
例えば、データベースアプリケーションを作る際に、データに結合文字列と合成文字が混在しそうなのであれば、検索スピードを向上させるため、または検索漏れがないように正規化を行わなくてはなりません。
…今更ですが、ベトナム語の “
” の意味を書いていませんでしたね。英語で “See you later” という意味です。では、またどこかでお会いしましょう。
Connect with Us