TIBInputDelimitedFileのバグ

Firebird-jp-general MLであったこの話です。

IBSQL.BatchInputで日本語が化けるって話だったので調べて見た所。
TIBInputDelimitedFile.GetColumnの中で

function TIBInputDelimitedFile.GetColumn(var Col: string): Integer;
var
  c: Char;
  BytesRead: Integer;

  procedure ReadInput;
  begin
    if FLookAhead <> NULL_TERMINATOR then
    begin
      :
    end else
      BytesRead := FFile.Read(c, 1);
  end;

こんな感じでchar型の変数に1Byteずつデータを読む処理がありました。Delphi2007以前のDelphiだと問題無いんですが、Delphi2009以降だとcharはWideCharになってますからこれはまずいです。質問であったのは日本語が化けるって話だったんですが、恐らく日本語だけで無く例えば、英数のみのデータでもファイルがUTF-8で保存されてたりするとアウトだと思います。
一応QC登録しておきました。

Report No: 93446            Status: Open
TIBInputDelimitedFile.GetColumn destroys non-ASCII data.
http://qc.embarcadero.com/wc/qcmain.aspx?d=93446
QCWIN:Defect_No=93446

どう直せば良いのか悩んだのですが、なるべく処理をいじりたくなかったので、まずファイルからデータを読んだ後、そのデータをUnicodeStringに変換しそのデータをMemoryStreamに書き込む事にしました。TStrings.LoadFromStringの処理を参考にデータを読んでUnicodeStringに変換したデータをMemoryStreamに書き込み、元の処理ではFileStreamからデータを読んでたのをMemoryStreamから読むように変更しました。
TIBInputDelimitedFileを元に変更を加えてTIBInputDelimitedFile2というクラスにしました。それが下記です。
Shift_JISUTF-8のデータだと上手く行くのは確認しました。

unit IBInputDelimitedFile2;

interface

uses
  Classes, SysUtils, IBSQL, IBUtils;

type
  TIBInputDelimitedFile2 = class(TIBBatchInput)
  private
    FEncoding: TEncoding;
    FStream: TStream;

    procedure SetEncoding(const Value: TEncoding);
    procedure ReadBuffer;
  protected
    FColDelimiter,
    FRowDelimiter: string;
    FEOF: Boolean;
    FFile: TFileStream;
    FLookAhead: Char;
    FReadBlanksAsNull: Boolean;
    FSkipTitles: Boolean;
  public
    constructor Create(AEncoding: TEncoding);
    destructor Destroy; override;
    function GetColumn(var Col: string): Integer;
    function ReadParameters: Boolean; override;
    procedure ReadyFile; override;

    property Encoding: TEncoding read FEncoding write SetEncoding;
    property ColDelimiter: string read FColDelimiter write FColDelimiter;
    property ReadBlanksAsNull: Boolean read FReadBlanksAsNull
                                       write FReadBlanksAsNull;
    property RowDelimiter: string read FRowDelimiter write FRowDelimiter;
    property SkipTitles: Boolean read FSkipTitles write FSkipTitles;
  end;

implementation

const
  CBUFFSIZE: Integer = 1024;

{ TIBInputDelimitedFile2 }

constructor TIBInputDelimitedFile2.Create(AEncoding: TEncoding);
begin
  FEncoding := AEncoding.Clone;
  FStream := TMemoryStream.Create;
end;

destructor TIBInputDelimitedFile2.Destroy;
begin
  FFile.Free;
  FreeAndNil(FStream);
  if (FEncoding <> nil) and not TEncoding.IsStandardEncoding(FEncoding) then
    FreeAndNil(FEncoding);
  inherited Destroy;
end;

function TIBInputDelimitedFile2.GetColumn(var Col: string): Integer;
var
  c: Char;
  BytesRead: Integer;

  procedure ReadInput;
  begin
    if FLookAhead <> NULL_TERMINATOR then
    begin
      c := FLookAhead;
      BytesRead := 1;
      FLookAhead := NULL_TERMINATOR;
    end else begin
      BytesRead := FStream.Read(C, SizeOf(C));
      if BytesRead = 0 then
      begin
        ReadBuffer;
        BytesRead := FStream.Read(C, SizeOf(C));
      end;
    end;
  end;

  procedure CheckCRLF(Delimiter: string);
  begin
    if (c = CR) and (Pos(LF, Delimiter) > 0) then {mbcs ok}
    begin
      BytesRead := FStream.Read(c, SizeOf(C));
      if BytesRead = 0 then
      begin
        ReadBuffer;
        BytesRead := FStream.Read(C, SizeOf(C));
      end;
      if (BytesRead = 1) and (c <> #10) then
        FLookAhead := c;
    end;
  end;

begin
  Col := '';
  result := 0;
  ReadInput;
  while BytesRead <> 0 do begin
    if Pos(c, FColDelimiter) > 0 then {mbcs ok}
    begin
      CheckCRLF(FColDelimiter);
      result := 1;
      break;
    end else if Pos(c, FRowDelimiter) > 0 then {mbcs ok}
    begin
      CheckCRLF(FRowDelimiter);
      result := 2;
      break;
    end else begin
      Col := Col + C;
    end;
    ReadInput;
  end;
end;

procedure TIBInputDelimitedFile2.ReadBuffer;
var
  Buffer: TBytes;
  Enc: TEncoding;
  Str: String;
  BytesRead: Integer;
  Size: Integer;
begin
  Enc := Nil;
  SetLength(Buffer, CBUFFSIZE);
  BytesRead := FFile.Read(Buffer[0], CBUFFSIZE);
  Size := TEncoding.GetBufferEncoding(Buffer, Enc, FEncoding);
  SetEncoding(Enc);
  Str := Enc.GetString(Buffer, Size, BytesRead - Size);
  FStream.Position := 0;
  FStream.Size := Length(Str) * 2;
  FStream.Write(Str[1], Length(Str) * 2);
  FStream.Position := 0;
end;

function TIBInputDelimitedFile2.ReadParameters: Boolean;
var
  i, curcol: Integer;
  Col: string;
begin
  result := False;
  if not FEOF then
  begin
    curcol := 0;
    repeat
      i := GetColumn(Col);
      if (i = 0) then
        FEOF := True;
      if (curcol < Params.Count) then
      begin
        try
          if (Col = '') and
             (ReadBlanksAsNull) then
            Params[curcol].IsNull := True
          else
            Params[curcol].AsString := Col;
          Inc(curcol);
        except
          on E: Exception do
          begin
            if not (FEOF and (curcol = Params.Count)) then
              raise;
          end;
        end;
      end;
    until (FEOF) or (i = 2);
    result := ((FEOF) and (curcol = Params.Count)) or
              (not FEOF);
  end;
end;

procedure TIBInputDelimitedFile2.ReadyFile;
var
  col : String;
  curcol : Integer;
begin
  if FColDelimiter = '' then
    FColDelimiter := TAB;
  if FRowDelimiter = '' then
    FRowDelimiter := CRLF;
  FLookAhead := NULL_TERMINATOR;
  FEOF := False;
  if FFile <> nil then
    FFile.Free;
  FFile := TFileStream.Create(FFilename, fmOpenRead or fmShareDenyWrite);

  ReadBuffer;

  if FSkipTitles then
  begin
    curcol := 0;
    while curcol < Params.Count do
    begin
      GetColumn(Col);
      Inc(CurCol)
    end;
  end;
end;

procedure TIBInputDelimitedFile2.SetEncoding(const Value: TEncoding);
begin
  if not TEncoding.IsStandardEncoding(FEncoding) then
    FEncoding.Free;
  if TEncoding.IsStandardEncoding(Value) then
    FEncoding := Value
  else if Value <> nil then
    FEncoding := Value.Clone
  else
    FEncoding := TEncoding.Default;
end;

end.

使い方は下記みたいな感じです。
TIBInputDelimitedFile2.Create(TEncoding.GetEncoding(932));とCreateに引数でファイルのEncodingを渡します。(この例だとShift_JIS
データを読む処理はTStringsを参考にしましたのでTEncoding.Defaultを渡しておけば上手く行くのかなという気もしますが、あまりちゃんとテストしてないので微妙です。

var
  DelimInput : TIBInputDelimitedFile2;
begin
  IBDatabase1.Open;
  IBSQL1.Transaction.StartTransaction;
  DelimInput := TIBInputDelimitedFile2.Create(TEncoding.GetEncoding(932));
  try
    IBSQL1.SQL.Text := 'INSERT INTO NEW_TABLE VALUES(:IDX, :DATA);';

    DelimInput.Filename := 'Data.txt';
    IBSQL1.BatchInput(DelimInput);
    IBSQL1.Transaction.Commit;
  except
    IBSQL1.Transaction.Rollback;
  end;
  FreeAndNil(DelimInput);
  IBDatabase1.Close;