TStreamReader とShiftJISデータ
結構なサイズのテキストデータ(ShiftJIS)の分割をする必要が出たので何か良い方法は無いかとヘルプを見ててTStreamReaderを見つけました。
早速TStreamReaderを使って分割をするプログラムを作ったんですが、出来上がったファイルを確認すると途中にNULL値があるんでスペースに置き変えますって言われてデータが所々化けてしまってます。
確認してみると、TStreamReaderのFillBufferの中でShiftJISのLeadByteがバッファの最後に読み込まれるとそこでデータが化けます。
QC登録した時に判ったんですが、WindowsXPだと問題が発生しますが、Windows7だと問題ありません。
さらに調べてみてわかったんですが、エンコーディングの返還にMultiByteToWideChar、WideCharToMultiByteが使われてるんですがMultiByteToWideCharに対してLeadByteのみで終わるようなデータ、例えば#$82#$A0#$82とかってデータを渡すとWindowsXPだと#$3042#$0000と半端なデータがNULLになるみたいなんですが、Windows7で同じ処理を実行すると#$3042#$30FBと変換されます。
FillBufferの中では、読み込んだバイト数と読み込んだ文字列をUNICODEに変換した後、もう一度変換前のエンコーディングに再変換した場合のバイト数を比較して一致しなければデータを分割してしまってると判断するようになっているようです。MultiByteToWideCharの結果が違うからなのでしょう、このバイト数のチェックがXPだとデータを分割した状態でも一致してしまいチェックをすり抜けてしまいます。データが化けるのはこの辺りが原因ぽいです。
TStreamReaderが悪いと言うよりはWindowsのAPIの問題のような気もしますがデータが化けてしまうのは事実で困ってしまうのでとりあえずの範囲で化けないように修正してみました。
TStreamReaderを丸々コピーしてクラス名を適当に変えてFillBufferを修正します。
procedure TStreamReader.FillBuffer(var Encoding: TEncoding); var : RawString: RawByteString; begin : ByteBufLen := BytesRead - StartIndex; if Not TEncoding.IsStandardEncoding(FEncoding) And Not FEncoding.IsSingleByte then begin // バッファの最後がLeadByteだった場合に追加で1バイト読み込むようにします。 RawString := PAnsiChar(@LBuffer[0]); if StrByteType(PAnsiChar(RawString), ByteBufLen-1) = mbLeadByte then begin if (StartIndex + ByteBufLen) = Length(LBuffer) then SetLength(LBuffer, Length(LBuffer) + BufferPadding); BytesRead := FStream.Read(LBuffer[StartIndex + ByteBufLen], 1); if BytesRead > 0 then begin Inc(ByteBufLen, BytesRead); end; end; end; LString := FEncoding.GetString(LBuffer, StartIndex, ByteBufLen); ByteCount := FEncoding.GetByteCount(LString); : end;
登録したQCは以下です。
Report No: 96375 Status: Open
TStreamReader corrupt Shift-JIS data
http://qc.embarcadero.com/wc/qcmain.aspx?d=96375
QCWIN:Defect_No=96375