月別アーカイブ: 2014年5月

Free Pascal で日本語処理(4)

Free Pascal で日本語処理(4)

今回は、文字列変数とテキストファイル出力について投稿します。

以下の KnjTest09.pp は、ANSI(SJIS)のテキストファイル、UTF-8のテキストファイル、Unicode(UTF-16 LE)のテキストファイルを作成するプログラム例です。
以下の4つの手続きを定義して順に呼び出します。

① CreateAnsiFile1
② CreateUtf8File
③ CreateAnsiFile2
④ CreateUnicodeFile

※説明中のエンコーディングについて
ASCIIは、アスキーコード(#$00-#$7F)のシングルバイト文字セット。
ANSIは、日本語Windowsの場合、Shift-JIS(Codepage 932)のマルチバイト文字セット。
UTF-8は、そのままUTF-8のマルチバイト文字セット。
Unicodeは、UTF-16のLE(リトルエンディアン)のワイド文字セット。

ソースコードのエンコーディングは UTF-8 で、ソースコード中に{$CodePage UTF8}ディレクティブを指定します。

program KnjTest09;

{$Mode ObjFpc}{$H+}
{$IOChecks On}
{$CodePage UTF8}    // ソースコードは UTF-8。

uses
  Classes, SysUtils;

procedure CreateAnsiFile1;
var
  F  : Text;           { テキストファイル }
  FN : AnsiString;     { ファイル名       }
  T1 : AnsiString;     { テキストデータ-1 }
  T2 : AnsiString;     { テキストデータ-2 }
begin
  T1 := 'TEXT DATA - 1.';
  T2 := '文字列データ2行目。';
  FN := 'ANSIテキストファイル1.txt';
  Assign(F, FN);
  Rewrite(F);
  try
    WriteLn(F, T1);
    WriteLn(F, T2);
  finally
    Close(F);
  end;
end;

procedure CreateUtf8File;
var
  F   : Text;          { テキストファイル }
  FN  : AnsiString;    { ファイル名       }
  T1  : UTF8String;    { テキストデータ-1 }
  T2  : UTF8String;    { テキストデータ-2 }
begin
  T1  := 'TEXT DATA - 1.';
  T2  := AnsiToUtf8('文字列データ2行目。');
  FN  := 'UTF8テキストファイル.txt';
  Assign(F, FN);
  Rewrite(F);
  try
    WriteLn(F, T1);
    WriteLn(F, T2);
  finally
    Close(F);
  end;
end;

procedure CreateAnsiFile2;
var
  F  : Text;           { テキストファイル }
  FN : AnsiString;     { ファイル名       }
  T1 : UnicodeString;  { テキストデータ-1 }
  T2 : UnicodeString;  { テキストデータ-2 }
begin
  T1 := 'TEXT DATA - 1.';
  T2 := '文字列データ2行目。';
  FN := 'ANSIテキストファイル2.txt';
  Assign(F, FN);
  Rewrite(F);
  try
    WriteLn(F, T1);
    WriteLn(F, T2);
  finally
    Close(F);
  end;
end;

procedure CreateUnicodeFile;
var
  S   : TFileStream;   { ファイルストリーム  }
  W   : SizeInt;       { 1文字のバイト数    }
  FN  : AnsiString;    { ファイル名          }
  BOM : UnicodeChar;   { UTF-16 LE のBOM     }
  T1  : UnicodeString; { テキストデータ-1    }
  T2  : UnicodeString; { テキストデータ-2    }
  EOL : UnicodeString; { Unicodeの改行コード }
begin
  W   := SizeOf(UnicodeChar);
  BOM := #$FEFF;
  EOL := LineEnding;
  T1  := 'TEXT DATA - 1.';
  T2  := '文字列データ2行目。';
  FN  := 'UNICODEテキストファイル.txt';
  S := TFileStream.Create(FN, fmCreate);
  try
    S.WriteBuffer(BOM, W);
    S.WriteBuffer(T1 [1], Length(T1)  * W);
    S.WriteBuffer(EOL[1], Length(EOL) * W);
    S.WriteBuffer(T2 [1], Length(T2)  * W);
    S.WriteBuffer(EOL[1], Length(EOL) * W);
  finally
    S.Free;
  end;
end;

begin
  try
    CreateAnsiFile1;
    CreateUtf8File;
    CreateAnsiFile2;
    CreateUnicodeFile;
  except
    on Ex: Exception do
    begin
      WriteLn(StdErr, Ex.Message);
    end;
  end;
end.


① CreateAnsiFile1

ANSIのテキストファイルを作成します。ファイル名、テキストデータともAnsiString型の変数として宣言しています。
{$CodePage UTF8}が指定されているので、ASCIIの文字列リテラルはAnsiString型の文字列となり、ANSI以外の文字が混じるとUnicodeString型の文字列となります。
17行目はそのままAnsiString型の代入となります。
18行目と19行目は、UnicodeString型をAnsiString型に代入しているので、UnicodeからANSIに変換されて代入されます。
23行目と24行目で、そのままANSIテキスト2行がファイルに書き出されます。WriteLn手続きでAnsiString型の出力の際に変換は発生しません。

② CreateUtf8File

UTF-8のテキストファイルを作成します。ファイル名はAnsiString型、テキストデータはUTF8String型の変数として宣言しています。
UTF8String型はAnsiString型と同じです(型としては区別されますが内容は変りません)。どちらも1バイト単位の任意の文字コードを格納し、エンコーディング情報は持ちません。
UTF8Stringの部分をAnsiStringに変えても結果は同じになります。UTF8Stringにすると見た目に意味合いを持たせる効果はありますが、自動変換等の期待はできません。将来的にはランタイムシスでのエンコーディング自動変換も期待したいところです。
37行目は、ASCIIの文字列リテラルなので、そのままUTF8String型として代入されます。ASCII文字の範囲は、ANSIでもありUTF-8でもあります。
38行目は、UnicodeString型をUTF8String型にUTF-8文字列として代入したいのですが、単に代入すると①同様UnicodeからANSIに変換されて代入されてしまいます。そのため、AnsiToUtf8()関数でANSIからUTF-8の文字列に変換して代入します。AnsiToUtf8()がパラメータで文字列を受け取るときにUnicodeからANSIに変換されます。
39行目はファイル名なので、①同様ANSIに変換されるようにします。
43行目と44行目で、そのままUTF-8テキスト2行がファイルに書き出されます。UTF8Stringt型の出力の際にもAnsiString型と同じなので変換は発生しません。

③ CreateAnsiFile2

再びANSIのテキストファイルを作成します。ファイル名はAnsiString型、テキストデータはUnicodeString型の変数として宣言しています。
57行目でANSIからUnicodeへ変換して代入、58行目でUnicodeを直接代入でテキストデータはUnicodeの文字列となりますが、テキストファイルのWriteLnでは、UnicodeはANSIに変換して出力されるので、結果は①と同じASNIのテキストファイルとなります。

④ CreateUnicodeFile

Unicodeのテキストファイルを作成します。③同様、ファイル名はAnsiString型、テキストデータはUnicodeString型の変数として宣言しています。
テキストファイルのWriteLnではANSIへ変換されてしまうので、TFileStreamを使用して、データをバイナリのまま直接書き出します。
81行目では、UTF-16 LE を表すBOMデータ(16bit)をUnicodeCharとして設定しています。
82行目では、改行コード(#$0D,#$0A)をUnicodeString型の変数に代入して、ANSIからUnicodeの#$000D, #$000Aに変換しています。
これらをTFileStreamのWriteBufferでファイルに書き込むとUnicodeのテキストファイル(BOM付)になります。

4つの出力ファイルをメモ帳(notepad.exe)で表示すると、どれも以下の様に表示します。

TEXT DATA - 1.
文字列データ2行目。

今回の例は、ファイル出力ですが、通信やコンポーネントの利用でも、どのエンコーディングデータを渡す必要があるか、また、返される値はどういうエンコーディングデータであるかを考慮し、プログラム内で正しく変換し、一貫性を持って保持するようにすると文字化けによるトラブルを回避することができると思います。文字列リテラルの扱いにも注意して下さい。

それらをわきまえていれば、KnjTest09.pp から、{$Codepage UTF8}を外した場合でも以下の様にエンコーディングの状態を考慮して正しく変換すれば同じ動作をするKnjTest10.pp を作成することができます。

program KnjTest10;

{$Mode ObjFpc}{$H+}
{$IOChecks On}
// {$CodePage UTF8}はコメントアウト。ソースコードは UTF-8。

uses
  Classes, SysUtils;

procedure CreateAnsiFile1;
var
  F  : Text;           { テキストファイル }
  FN : AnsiString;     { ファイル名       }
  T1 : AnsiString;     { テキストデータ-1 }
  T2 : AnsiString;     { テキストデータ-2 }
begin
  T1 := Utf8ToAnsi('TEXT DATA - 1.');
  T2 := Utf8ToAnsi('文字列データ2行目。');
  FN := Utf8ToAnsi('ANSIテキストファイル1.txt');
  Assign(F, FN);
  Rewrite(F);
  try
    WriteLn(F, T1);
    WriteLn(F, T2);
  finally
    Close(F);
  end;
end;

procedure CreateUtf8File;
var
  F   : Text;          { テキストファイル }
  FN  : AnsiString;    { ファイル名       }
  T1  : UTF8String;    { テキストデータ-1 }
  T2  : UTF8String;    { テキストデータ-2 }
begin
  T1  := 'TEXT DATA - 1.';
  T2  := '文字列データ2行目。';
  FN  := Utf8ToAnsi('UTF8テキストファイル.txt');
  Assign(F, FN);
  Rewrite(F);
  try
    WriteLn(F, T1);
    WriteLn(F, T2);
  finally
    Close(F);
  end;
end;

procedure CreateAnsiFile2;
var
  F  : Text;           { テキストファイル }
  FN : AnsiString;     { ファイル名       }
  T1 : UnicodeString;  { テキストデータ-1 }
  T2 : UnicodeString;  { テキストデータ-2 }
begin
  T1 := Utf8ToAnsi('TEXT DATA - 1.');
  T2 := Utf8ToAnsi('文字列データ2行目。');
  FN := Utf8ToAnsi('ANSIテキストファイル2.txt');
  Assign(F, FN);
  Rewrite(F);
  try
    WriteLn(F, T1);
    WriteLn(F, T2);
  finally
    Close(F);
  end;
end;

procedure CreateUnicodeFile;
var
  S   : TFileStream;   { ファイルストリーム  }
  W   : SizeInt;       { 1文字のバイト数    }
  FN  : AnsiString;    { ファイル名          }
  BOM : UnicodeChar;   { UTF-16 LE のBOM     }
  T1  : UnicodeString; { テキストデータ-1    }
  T2  : UnicodeString; { テキストデータ-2    }
  EOL : UnicodeString; { Unicodeの改行コード }
begin
  W   := SizeOf(UnicodeChar);
  BOM := #$FEFF;
  EOL := LineEnding;
  T1  := Utf8ToAnsi('TEXT DATA - 1.');
  T2  := Utf8ToAnsi('文字列データ2行目。');
  FN  := Utf8ToAnsi('UNICODEテキストファイル.txt');
  S := TFileStream.Create(FN, fmCreate);
  try
    S.WriteBuffer(BOM, W);
    S.WriteBuffer(T1 [1], Length(T1)  * W);
    S.WriteBuffer(EOL[1], Length(EOL) * W);
    S.WriteBuffer(T2 [1], Length(T2)  * W);
    S.WriteBuffer(EOL[1], Length(EOL) * W);
  finally
    S.Free;
  end;
end;

begin
  try
    CreateAnsiFile1;
    CreateUtf8File;
    CreateAnsiFile2;
    CreateUnicodeFile;
  except
    on Ex: Exception do
    begin
      WriteLn(StdErr, Ex.Message);
    end;
  end;
end.