Migaro. 技術Tips

                       

ミガロ. 製品の技術情報
IBMiの活用に役立つ情報を掲載!


【Delphi】テキストやCSVを指定のエンコードで入出力

IBM i とDelphi/400を利用したシステムでは、現在でもテキストやCSV形式のファイルを介して、
他システムや外部サービスとデータ連携を行っているケースが少なくありません。
シンプルで汎用性が高いことから、長年にわたり採用され続けている方式です。

一方で、Windows環境の変化や外部システムの更新、
さらにはDelphi/400のバージョンアップへの対応などをきっかけに、
既存プログラムの改修が必要になる場面も少なくありません。

従来は問題なく動作していたテキスト/CSV入出力処理が、
新しい環境では文字コードや改行コード、ファイル操作方法の違いによっては
想定通りに動作しないこともあります。

今回はこのような場面で役立つ、Delphi/400における
テキスト/CSVの入出力処理に関するテクニックをいくつか紹介します。
既存資産を活かしながら、より柔軟で保守しやすい実装を行うための参考になれば幸いです。

 


エンコードを指定して読み込む/保存する

TStrings(TStringListなど)を
LoadFromFileで読み込む場合およびSaveToFileで保存する際は、
Shift-JIS(ANSI)またはUTF-8のどちらで読み書きする必要がある、
といった局面が多くなっているかと思います。

LoadFromFileでテキストやCSVを読み込む場合、
エンコードを指定しなければテキストの属性から自動判定されます。
エンコードを指定して読み込む場合は、以下例のように第2引数を付けて記述します。
(※今回の手順は、Delphi/400 XE3以上で対応しています。)
(※注:指定したエンコードが合致しないと文字化けの原因になります。)

  //(例)それぞれのエンコードでテキストを読み込む(sFileはファイルパス)
  StringList1.LoadFromFile(sFile);                 // 未指定(第2引数を省略すると自動判定)
  StringList1.LoadFromFile(sFile, TEncoding.ANSI); // Shift-JISで読み込む
  StringList1.LoadFromFile(sFile, TEncoding.UTF8); // UTF-8(BOMあり)で読み込む

 

テキストファイルをTStrings.LoadFromFileで読み込んだ場合、
TStrings内で文字コードの情報をEncodingプロパティとして保持します。
サブプロパティEncodingNameで確認することも可能です。

  // 読み込んだ後のエンコード名を表示する例
  if (StringList1.Encoding <> nil) then
  begin
    ShowMessage(StringList1.Encoding.EncodingName);
  end
  else
  begin // LoadFromFileしていない場合はnilになるので注意
    ShowMessage('nil');
  end;

 

SaveToFileでテキストやCSVを保存する場合、
先ほどのLoadFromFileでエンコード情報(Encoding)を内部保持している場合は
通常のSaveToFileを行うことで同じエンコードで保存できます。

対して、Createしたあと文字列をAddし続けただけの場合やTextを代入した場合など、
LoadFromFileしていない場合はエンコード情報(Encoding)がnilのため、
そのままSaveToFileするとShift-JISとして保存されます。

連携先の都合などでUTF8として保存する必要がある場合や、
逆に強制的にShift-JISで保存する必要がある場合は、
以下例のように第2引数を付けて記述します。

※今回の手順は、Delphi/400 XE3以上で対応しています。
※LoadFromFileでUTF-8(BOMなし)のテキストを読み込んでいた場合も、
 この方法で出力するとUTF-8(BOMあり)になります。

  //(例)それぞれのエンコードでテキストを出力(sFileはファイルパス)
  StringList1.SaveToFile(sFile);                 // 未指定(第2引数を省略すると自動判定)
  StringList1.SaveToFile(sFile, TEncoding.ANSI); // Shift-JISで出力
  StringList1.SaveToFile(sFile, TEncoding.UTF8); // UTF-8(BOMあり)で出力

UTF-7やUTF-16など、他のエンコードで
LoadFromFileSaveToFileする場合は、以下のページに記載の値を指定してみて下さい。
System.SysUtils.TEncoding のプロパティ – RAD Studio API Documentation

 

 

【以下は12 Athens以降でのみ対応】
UTF-8(BOMなし)で出力したい場合は、
引数のエンコード設定を別の変数で定義することで実装可能です。

※11以前のバージョンでは未対応のため、
 次項「出力時に改行コードを変更する」の方法をご利用ください。

procedure TForm1.Button4Click(Sender: TObject);
var
  Enc: TUTF8Encoding;
  sFile: String;
begin
  // エンコードを指定して保存(sFileはファイルパス)
  Enc := TUTF8Encoding.Create(False);  // False=BOMなし
  try
    StringList1.SaveToFile(sFile, Enc);
  finally
    Enc.Free;
  end;
end;

 


出力時に改行コードを変更する

TStrings(TStringListなど)で複数行のデータを扱う際は、
改行コードが内部で自動的に「#13#10」として扱われます。(#$D#$A・CRLFも同義)

そのため、改行コードを「#13」もしくは「#10」としたい場合には
TStringsのSaveToFileでテキストやCSVを保存することができません。

この場合はそれぞれ、以下のようなロジックで保存します。

※以下の例では「Shift-JIS」「UTF-8(BOMなし)」「UTF-8(BOMあり)」の
 3形式それぞれで保存するようになっています。
 必要な箇所のみを抜粋してください。

procedure TForm1.Button2Click(Sender: TObject);
var
  S: String;
  sFile: String;
  Bytes: TBytes;
  Preamble: TBytes;
  FS: TFileStream;
begin
  // 改行コードを目的の文字コードに変更する
  // (例では#13にしていますが、実際の値に読み替え)
  // (UTF-8(BOMなし)で#13#10のまま保存する場合、この行は不要)
  S := StringReplace(StringList1.Text, #13#10, #13, [rfReplaceAll]);

  // Shift-JISで保存する場合
  begin
    // 保存ファイル名
    sFile := FormatDateTime('"出力"YYYYMMDD_HHNNSS', Now) + '_ansi.txt';

    // Shift-JISへ変換
    Bytes := TEncoding.GetEncoding(932).GetBytes(S);

    // TFileStreamを生成して出力
    FS := TFileStream.Create(sFile, fmCreate);
    try
      FS.WriteBuffer(Bytes[0], Length(Bytes));
    finally
      FS.Free;
    end;
  end;

  // UTF8 BOMなしで保存する場合
  begin
    // 保存ファイル名
    sFile := FormatDateTime('"出力"YYYYMMDD_HHNNSS', Now) + '_utf8.txt';

    // UTF-8(BOMなし)
    Bytes := TEncoding.UTF8.GetBytes(S);

    // TFileStreamを生成して出力
    FS := TFileStream.Create(sFile, fmCreate);
    try
      FS.WriteBuffer(Bytes[0], Length(Bytes));
    finally
      FS.Free;
    end;
  end;

  // UTF8 BOMありで保存する場合
  begin
    // 保存ファイル名
    sFile := FormatDateTime('"出力"YYYYMMDD_HHNNSS', Now) + '_utf8bom.txt';

    // UTF-8(BOMあり)
    Bytes := TEncoding.UTF8.GetBytes(S);
    Preamble := TEncoding.UTF8.GetPreamble;

    // TFileStreamを生成して出力
    FS := TFileStream.Create(sFile, fmCreate);
    try
      if Length(Preamble) > 0 then
        FS.WriteBuffer(Preamble[0], Length(Preamble));

      FS.WriteBuffer(Bytes[0], Length(Bytes));
    finally
      FS.Free;
    end;
  end;
end;