Unicode対応LoadFromFile SaveToFile
Unicodeファイル名対応LoadFromFile SaveToFileでUnicodeのファイル名に対応できたので次は中身のテキストもがんばってUnicodeに対応しましょう、というのがこのページの趣旨。
下準備
まずは下請けの関数を一つ。
//ファイルサイズを取得する関数。
function gfniFileSizeGet(
const sWFile:
WideString): Int64;
//sWFileのサイズをByte単位で返す。
//http://delwiki.info/?Tips%2F%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%B5%E3%82%A4%E3%82%BA%E3%81%AF%20Int64%20%E3%81%A7
var
lh_File: Cardinal;
lr_FileInfo: TWin32FindDataW;
begin
Result := 0;
lh_File := FindFirstFileW(PWideChar(sWFile), lr_FileInfo);
try
if (lh_File <> INVALID_HANDLE_VALUE)
then begin
repeat
if not(BOOL(lr_FileInfo.dwFileAttributes
and FILE_ATTRIBUTE_DIRECTORY))
then begin
Result := lr_FileInfo.nFileSizeHigh
* (Int64(MAXDWORD) + 1) + lr_FileInfo.nFileSizeLow;
Break;
end;
until not(FindNextFileW(lh_File, lr_FileInfo));
end;
finally
Windows.FindClose(lh_File);
end;
end;
次に読み書きする時の文字コードを指定するための列挙型を宣言します。
書き込む時はもとより読み込みの時もBOMありのUTF-16やUTF-8であれば自動で文字コードの判定ができますがそうでない場合自動で判定するのはややこしいのでBOMで判定できなかった時の文字コードを引数で指定するようにします。
type
TMyCharCode = (cdAnsi, cdUnicodeLE, cdUnicodeBE, cdUTF_16LE, cdUTF_16BE,
cdUTF_8, cdUTF_8N, cdUTF_7, cdAuto);
cdAnsi |
Shift-JIS
俗にいう普通〜のテキストファイル。
Unicode特有の文字は代替文字に変わるか ? に変わります。 |
cdUnicodeLE |
UTF-16 BOMありのリトルエンディアン |
cdUnicodeBE |
UTF-16 BOMありのビッグエンディアン |
cdUTF_16LE |
UTF-16LE BOMなしのリトルエンディアン |
cdUTF_16BE |
UTF-16BE BOMなしのビッグエンディアン |
cdUTF_8 |
UTF-8 BOMあり |
cdUTF_8N |
UTF-8 BOMなし |
cdUTF_7 |
UTF-7 |
cdAuto |
書き込むテキストにウムラウトのようなUnicode文字があればUTF-8で、なければShift-JISで書き込みます。 |
XP付属のメモ帳(Notepad.exe)の「ファイル」→「名前をつけて保存」で「文字コード」の選択で
- 「ANSI」を選んで保存したものが cdAnsi
- 「Unicode」で保存したものが cdUnicodeLE
- 「Unicode big endian」で保存したものが cdUnicodeBE
- 「UTF-8」で保存したものが cdUTF_8
にそれぞれ対応します。
そして
- cdUTF_16LEは cdUnicodeLE のBOMのないもの
- cdUTF_16BEは cdUnicodeBE のBOMのないもの
- cdUTF_8Nは cdUTF_8 のBOMのないもの
になります。
ちなみにTStringsの文字列はUTF-7を使ってWideStringをAnsiStringに変換しているものとします。
読み込み
//Unicode対応ファイル読み込み関数。
function gfniFileRead(
var pData: PAnsiChar; sWFile:
WideString;
const iByte: DWORD): DWORD;
//ファイルをpDataに読み込んで、読み込んだバイト数を返す関数。
//2バイト余計にメモリ確保するのは呼び出し側でWideString(pData)などとしても後ろにゴミがつかないようにするため。
var
lh_Handle: THandle;
begin
Result := 0;
lh_Handle := CreateFileW(
PWideChar(sWFile),
//ファイル名
GENERIC_READ,
//アクセスモード
FILE_SHARE_READ,
//共有モード
nil,
//セキュリティ
OPEN_EXISTING,
//作成方法
FILE_ATTRIBUTE_NORMAL,
//ファイル属性
0
//テンプレート
);
if (lh_Handle <> 0)
then begin
try
//呼び出し側でpDataのメモリーを開放をする必要あり
pData := AllocMem(iByte + 2);
//2バイト余計に確保することで後の処理が楽になる
ReadFile(lh_Handle, pData^, iByte, Result,
nil);
finally
CloseHandle(lh_Handle);
end;
end;
end;
function gfnsFileReadText(sFile:
WideString;
var cdCode:
TMyCharCode):
WideString;
//Unicode対応。
//cdCodeにはBOMがない時に適用する文字コードを指定する。
var
lp_Buff: PAnsiChar;
ls_Swap: AnsiChar;
li_Size, li_Start, i: DWORD;
begin
li_Size :=
gfniFileRead(lp_Buff, sFile,
gfniFileSizeGet(sFile));
if (li_Size <= 0)
then begin
Result := '';
end else begin
try
li_Start := 0;
if (lp_Buff[0] = #$EF)
and (lp_Buff[1] = #$BB)
and (lp_Buff[2] = #$BF)
then begin
//UTF-8 BOMあり
cdCode := cdUTF_8;
Inc(li_Start, 3);
end else
if (lp_Buff[0] = #$FF)
and (lp_Buff[1] = #$FE)
then begin
//UTF-16 BOMありのリトルエンディアン
cdCode := cdUnicodeLE;
Inc(li_Start, 2);
Dec(li_Size, 2);
end else
if (lp_Buff[0] = #$FE)
and (lp_Buff[1] = #$FF)
then begin
//UTF-16 BOMありのビッグエンディアン
cdCode := cdUnicodeBE;
Inc(li_Start, 2);
Dec(li_Size, 2);
end;
if (cdCode = cdUTF_8)
or (cdCode = cdUTF_8N)
then begin
//UTF-8
//Utf8DecodeはBOMも変換する
Result := Utf8Decode(Utf8String(PAnsiChar(@lp_Buff[li_Start])));
end else
if (cdCode = cdUnicodeLE)
or (cdCode = cdUnicodeBE)
or (cdCode = cdUTF_16LE)
or (cdCode = cdUTF_16BE)
then begin
//UTF-16
if (cdCode = cdUTF_16BE)
or (cdCode = cdUnicodeBE)
then begin
for i := li_Start
to li_Start + li_Size - 1
do begin
if ((i - li_Start)
mod 2 = 1)
then begin
//入れ替え
ls_Swap := lp_Buff[i];
lp_Buff[i] := lp_Buff[i
-1];
lp_Buff[i -1] := ls_Swap;
Application.ProcessMessages;
end;
end;
end;
//gfniFileReadでlp_Buffを2バイト余計にAllocMemでメモリ確保しているのでWideStringでキャストOK
Result :=
WideString(PWideChar(@lp_Buff[li_Start]));
end else begin
//Shift-JISあるいはUTF-7あるいはcdAuto
//gfniFileReadでlp_Buffを2バイト余計にAllocMemでメモリ確保しているのでAnsiStringでキャストOK
Result := AnsiString(lp_Buff);
if (cdCode = cdUTF_7) then begin
//UTF-7
Result :=
gfnsUtf7ToWide(Result);
end;
end;
finally
FreeMem(lp_Buff);
end;
end;
end;
procedure gpcLoadFromFile(slList: TStrings; sFile:
WideString; cdCode: TMyCharCode);
//Unicode対応のLoadFromFile
//cdCodeにはBOMがない時に適用する文字コードを指定する
begin
slList.Clear;
ls_Text := ;
//WideStringをUTF-7に変換してTStringsに持つ
slList.Text :=
gfnsWideToUtf7(gfnsFileReadText(sFile, cdCode));
end;
TStringsにはWideStringをUTF-7に変換してAnsiStringで持つようにします。
ListBoxなどで表示するときはOnDrawItemイベントを使います。
sFileがBOMつきのUTF-16、あるいはBOMつきのUTF-8テキストであれば文字コードが自動で判定されるのでcdCodeは無視されます。
cdCodeはBOMのついていないファイルの場合に使用されるだけで強制的に指定した文字コードで読み込むわけではありません。
またcdAutoは名称からすると自動で文字コードを判定しそうですが、これは書き込みの時に指定するもので、読み込みのときに指定した場合はcdAnsiと同じ動作になります。
書き込み
//Unicode対応のファイル書き込み関数。
function gfnhFileWriteOpen(sFile:
WideString): THandle;
begin
Result := CreateFileW(
PWideChar(sFile),
//ファイル名
GENERIC_WRITE,
//アクセスモード
0,
//共有モード
nil,
//セキュリティ
CREATE_ALWAYS,
//作成方法
FILE_ATTRIBUTE_NORMAL,
//ファイル属性
0
//テンプレート
);
end;
function gfniFileWrite(hHandle: THandle; pData: PAnsiChar; iWrite: DWORD): DWORD;
overload;
begin
if (hHandle <> 0)
then begin
WriteFile(hHandle, pData^, iWrite, Result,
nil);
end;
end;
function gfniFileWrite(sWFile:
WideString; pData: PAnsiChar; iWrite: DWORD): DWORD;
overload;
//pDataの内容をsWFileにiWriteバイト書き込み書き込んだバイト数を返す。
var
lh_Handle: THandle;
begin
Result := 0;
lh_Handle :=
gfnhFileWriteOpen(sWFile);
if (lh_Handle <> 0)
then begin
try
WriteFile(lh_Handle, pData^, iWrite, Result,
nil);
finally
CloseHandle(lh_Handle);
end;
end;
end;
procedure gpcFileWriteText(sWFile, sWText:
WideString; cdCode:
TMyCharCode);
//sWTextを文字コードcdCodeでsWFileに書き込む
//Unicode対応
var
lp_Buff: PAnsiChar;
li_Len: Integer;
i: DWORD;
ls_Str: AnsiString;
lh_Handle: THandle;
begin
if (cdCode = cdUTF_8)
or (cdCode = cdUTF_8N)
or ((cdCode = cdAuto)
and gfnbIsUnicode(sWText))
then begin
//UTF-8
ls_Str := Utf8Encode(sWText);
if (cdCode = cdUTF_8N)
then begin
//BOMなし
gfniFileWrite(sWFile, PAnsiChar(ls_Str), Length(ls_Str) - 1);
end else begin
//BOMあり
lh_Handle :=
gfnhFileWriteOpen(sWFile);
try
gfniFileWrite(lh_Handle, #$EF#$BB#$BF, 3);
//BOM書き込み
gfniFileWrite(lh_Handle, PAnsiChar(ls_Str), Length(ls_Str) - 1);
//本文書き込み
finally
CloseHandle(lh_Handle);
end;
end;
end else if (cdCode = cdUTF_7)
then begin
//UTF-7
ls_Str :=
gfnsWideToUtf7(sWText);
gfniFileWrite(sWFile, PAnsiChar(ls_Str), Length(ls_Str));
end else
if (cdCode = cdUnicodeLE)
or (cdCode = cdUnicodeBE)
or (cdCode = cdUTF_16LE)
or (cdCode = cdUTF_16BE)
then begin
//UTF-16
li_Len := Length(sWText) * 2;
lh_Handle :=
gfnhFileWriteOpen(sWFile);
try
if (cdCode = cdUnicodeBE)
or (cdCode = cdUTF_16BE)
then begin
//ビッグエンディアン
lp_Buff := AllocMem(li_Len);
try
if (cdCode = cdUnicodeBE)
then begin
//UTF-16 BOMありのビッグエンディアン
gfniFileWrite(lh_Handle, #$FE#$FF, 2);
//BOM書き込み
end;
for i := 0
to li_Len - 1
do begin
//入れ替え
if (i
mod 2 = 0)
then begin
lp_Buff[i + 1] := PAnsiChar(PWideChar(sWText))[i];
end else begin
lp_Buff[i - 1] := PAnsiChar(PWideChar(sWText))[i];
Application.ProcessMessages;
end;
end;
gfniFileWrite(lh_Handle, lp_Buff, DWORD(li_Len));
//本文書き込み
finally
FreeMem(lp_Buff);
end;
end else if (cdCode = cdUnicodeLE)
or (cdCode = cdUTF_16LE)
then begin
//リトルエンディアン
if (cdCode = cdUnicodeLE)
then begin
//UTF-16 BOMありのリトルエンディアン
gfniFileWrite(lh_Handle, #$FF#$FE, 2);
//BOM書き込み
end;
gfniFileWrite(lh_Handle, PAnsiChar(PWideChar(sWText)), li_Len);
//本文書き込み
end;
finally
CloseHandle(lh_Handle);
end;
end else begin
//Shift-JIS
//AnsiString(sWText)のようにキャストするだけだとUnicodeの合成文字が正しく変換されない場合あり。
ls_Str :=
gfnsWideToAnsi(sWText);
gfniFileWrite(sWFile, PAnsiChar(ls_Str), Length(ls_Str));
end;
end;
procedure gpcSaveToFile(slList: TStrings; sWFile:
WideString; cdCode:
TMyCharCode);
{
Unicode対応SaveToFile
文字コードcdCodeの指定で
Shift-JIS
UTF-16のBOMありのリトルエンディアン
UTF-16のBOMありのビッグエンディアン
UTF-16LE
UTF-16BE
UTF-8 (BOMあり)
UTF-8N (BOMなし)
UTF-7
の書き込みに対応
}
begin
//WideStringをUTF-7に変換してTStringsに持っていると仮定。
gpcFileWriteText(sWFile,
gfnsUtf7ToWide(slList.Text), cdCode);
end;
gpcLoadFromFileでWideStringをUTF-7にしてTStringsに読み込んだり、追加するときにUTF-7に変換させたりしたものをWideStringに戻してからファイルに書き込みます。
この場合重要なのは、普通のAnsiStringの文字列であってもTStringsに追加するときはUTF-7に変換しなければならないということです。
そうでないとノーマルのテキストであってもgfnsUtf7ToWideで変換してしまうので文字化けしてしまいます。
2009-02-12:
gfnsFileReadTextを若干書き換え。
自作関数にリンクを張って分かりやすく。
2008-09-17:
gpcFileWriteTextの下請け関数gfniFileWriteを載せた。
2008-05-30: