UTF-8を直接TStringListに持つ
Unicode文字列をUTF-8に変換してTStringListに直接持たせて利用しようというページ。
Delphi 6でUTF-8
Delphi 6はUTF-8を取り扱うことができます。
Utf8Encdoe関数とUtf8Decode関数とでWideString(UTF-16)との相互変換ができます。
とはいえCopyやPos(あるいはAnsiPos)などの文字列関数はUTF-8対応ではないので使われる状況というのはコンバート用程度だと思うのですが、もしかしたらこのUTF-8を利用すればTStringsをUnicode対応にできるのではないかと思いつきました。
というのもDelphi 6の場合UTF-8の文字列はUtf8Stringという型で扱います。
このUtf8Stringというのは実はAnsiStringと同じです。
ということはWideStringをUTF-8に変換したものはそのままTStringsに持てるのではなかろうか?
そう思ったわけです。
結果
実際やってみた結果TStringListやTStringGridには問題なく持てるけれどもTListBoxやTComboBoxでは不都合が出るということが分かりました。
TStringListやTStringGridはDelphiが用意したものにデータを保持するけれども、TListBoxやTComboBoxはWindowsが用意したものにデータを保持することからの違いなのだろうかなぁというところです。
UTF-8に変換した文字列をStringGrid(上段)とListBox(下段)にそれぞれ持たせて表示させてみたもの。
下段のリストボックスは日本語の一部(「シャンティ」、「深紅.」)が文字化けしています。
procedure TForm1.Button2Click(Sender: TObject);
var
lsl_List: TStrings;
i: Integer;
begin
if (OpenDialog1.Execute) then begin
lsl_List := TStringList.Create;
try
lsl_List.LoadFromFile(OpenDialog1.FileName);
StringGrid1.RowCount := lsl_List.Count-1;
ListBox1.Items.Clear;
for i := 0 to lsl_List.Count-1 do begin
//UTF-8に変換して持たせてみる
StringGrid1.Cells[0, i] := Utf8Encode(WideString(lsl_List[i]));
ListBox1.Items.Add(Utf8Encode(WideString(lsl_List[i])));
end;
finally
lsl_List.Free;
end;
end;
end;
procedure TForm1.StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer; Rect: TRect; State: TGridDrawState);
begin
with StringGrid1.Canvas do begin
FillRect(Rect);
//UTF-8に変換済みのデータをWideStringにする
DrawTextW(Handle, PWideChar(Utf8Decode(StringGrid1.Cells[ACol,
ARow])), -1, Rect, DT_NOPREFIX or DT_VCENTER or DT_SINGLELINE);
end;
end;
procedure TForm1.ListBox1DrawItem(Control: TWinControl; Index: Integer; Rect: TRect;
State: TOwnerDrawState);
begin
with ListBox1.Canvas do begin
FillRect(Rect);
//UTF-8に変換済みのデータをWideStringにする
DrawTextW(Handle, PWideChar(Utf8Decode(ListBox1.Items[Index])), -1, Rect, DT_NOPREFIX or DT_VCENTER or DT_SINGLELINE);
end;
end;
TListBoxやTComboBoxでは文字化けしてしまうので使えなかったUTF-8ですが、それでお役御免かというとそうでもありません。
というのもUTF-8に変換した文字列をTStringListに持たせておき、そのままSaveToFileするとBOMなしのUTF-8のファイルになるからです。
Unicodeな中身のファイルがお手軽に作れてしまうのです。
slList.Text := Utf8Encode(sWStr); //sWStrはWideString
if (SaveDialog1.Execute) then begin
//保存
slList.SaveToFile(SaveDialog1.FileName); //BOMなしのUTF-8で保存される
end;
BOMありのUTF-8ファイルとして保存するには #$EF#$BB#$BF を頭に付け足して保存します。
slList.Text := Utf8Encode(sWStr); //sWStrはWideString
if (SaveDialog1.Execute) then begin
//保存
slList.Text := #$EF#$BB#$BF + slList.Text; //UTF-8のBOM付加
slList.SaveToFile(SaveDialog1.FileName); //BOMありのUTF-8で保存される
end;
割とお手軽にUnicode文字列を保存できます。
UTF-8で保存されているファイルは普通にLoadFromFileで読み込めばUTF-8にエンコードされている文字列がそのままTStringList格納されます。
WideStringにするにはUtf8Decode関数を使います。
BOMなしのUTF-8ファイルならLoadFromFileで読み込んでUtf8Decode関数でWideStringに変換するだけでOKです。
if (OpenDialog1.Execute) then begin
lsl_List := TStringList.Create;
try
lsl_List.LoadFromFile(OpenDialog1.FileName); //UTF-8で保存されているファイルを読み込む
sWStr := Utf8Decode(lsl_List.Text); //sWStrはWideString
finally
lsl_List.Free;
end;
end;
BOMつきのUTF-8ファイルの場合変換後もBOMが残ってしまいうまくないこともあるので、必要ならBOMを取り除いてからUtf8Decode関数でWideStringに変換します。
if (OpenDialog1.Execute) then begin
lsl_List := TStringList.Create;
try
lsl_List.LoadFromFile(OpenDialog1.FileName); //UTF-8で保存されているファイルを読み込む
if (lsl_List[0][1] = #$EF)
and (lsl_List[0][2] = #$BB)
and (lsl_List[0][3] = #$BF)
then begin
//BOMつきだったので取り除く
lsl_List[0] := Copy(lsl_List[0], 4, MAXINT);
end;
sWStr := Utf8Decode(lsl_List.Text); //sWStrはWideString
finally
lsl_List.Free;
end;
end;
Unicodeな文字列をUTF-16のままファイルから読みんだりファイルへ書き込んだりしようとするとそこそこ大変なのですが、UTF-8で良ければDelphi
6でもあっけなくできてしまいます。
サロゲートペア
Delphi 6のUtf8Encode関数とUtf8Decode関数はサロゲートペアに対応していません。
サロゲートペアを含んだWideStringもきちんと変換させるにはWindowsのAPIに頼るのが楽で良いと思います。
function gfnsWideToUTF8(sSrc:
WideString): UTF8String;
//WideStringをUTF-8にエンコードして返す。
var
li_Len: Integer;
lp_Buff: PAnsiChar;
begin
li_Len := WideCharToMultiByte(CP_UTF8, 0, PWideChar(sSrc),
-1,
nil, 0,
nil,
nil);
lp_Buff := AllocMem(li_Len + 1);
try
WideCharToMultiByte(CP_UTF8, 0, PWideChar(sSrc), -1, lp_Buff,
li_Len +1,
nil,
nil);
Result := UTF8String(lp_Buff);
finally
FreeMem(lp_Buff);
end;
end;
function gfnsUTF8ToWide(sSrc: UTF8String):
WideString;
//UTF-8でエンコードされている文字列をWideStringにして返す。
var
li_Len: Integer;
lp_Buff: PWideChar;
begin
li_Len := MultiByteToWideChar(CP_UTF8, 0, PAnsiChar(sSrc), -1,
nil, 0);
lp_Buff := AllocMem((li_Len + 1) * 2);
try
MultiByteToWideChar(CP_UTF8, 0, PAnsiChar(sSrc), -1, lp_Buff,
li_Len);
Result :=
WideString(lp_Buff);
finally
FreeMem(lp_Buff);
end;
end;
Utf8Encodeの代わりにgfnsWideToUtf8を、Utf8Decodeの代わりにgfnsUtf8ToWideを使用します。
2010-10-30:UTF-8で保存でBOMをシングルクオートで囲んでいた間違いを修正。
2010-10-03:サロゲートペアの項を追加。
2008-06-12: