alancc
alancc

Reputation: 799

How to Prevent TStrings.SaveToStream from writing the BOM?

I am using Delphi XE3.

In my code, I will need to create a file stream, and then write some my own data, as well as the contents of several TStringList into it. The file is in UTF-16LE format.

Therefore, my code:

FileStream := TFileStream.Create('D:\MyFile.dat', fmCreate or fmOpenWrite or fmShareExclusive);

try
  // Write some data to FileStream

  // Write contents of StringList1 into FileStream
  StringList1.SaveToStream(FileStream, TEncoding.Unicode);

  // Write some more data to FileStream

  // Write contents of StringList2 into FileSteram
  StringList2.SaveToStream(FileStream, TEncoding.Unicode);
finally
  FileStream.Free;
end;

After executing the codes, I find a problem, each time I invoke StringList1.SaveToStream(FileStream, TEncoding.Unicode); it will write BOM (0xFFFE) then followed by the actual strings in the string list.

Therefore, I get a Unicode file like this:

0xFFFE(The first one is written by myself)

(some data)

0xFFFE (StringList1 contents)

(some data)

0xFFFE (StringList2 contents)

But this is not I expect since there should be only one 0xFFFE at the beginning of the file. Therefore, I just wonder how to prevent StringList1.SaveToStream to write the 0xFFFE BOM before writing the actual string lists?

Upvotes: 2

Views: 708

Answers (2)

alancc
alancc

Reputation: 799

I find another solution for my question.

TStrings has a WriteBOM property, which will control whether to write out the BOM when using SaveToStream or SaveToFile.

Therefore, using the following codes will disable the BOM:

  StringList1.WriteBOM := False;
  StringList1.SaveToStream(FileStream, TEncoding.Unicode);

Upvotes: 6

Remy Lebeau
Remy Lebeau

Reputation: 596256

You can use SaveToStream() to save to a TMemoryStream first, then set that stream's Position to skip past the BOM in it and save the rest of its data to the TFileStream.

procedure WriteUnicodeStrings(AStream: TStream; AStrings: TStrings);
var
  MS: TMemoryStream;
begin
  MS := TMemoryStream.Create;
  try
    AStrings.SaveToStream(MS, TEncoding.Unicode);
    if MS.Size > 2 then
    begin
      MS.Position := 2;
      AStream.CopyFrom(MS, MS.Size-2);
    end;
  finally
    MS.Free;
  end;
end;
...
FileStream := TFileStream.Create('D:\MyFile.dat', fmCreate or fmOpenWrite or fmShareExclusive);
try
  // Write some data to FileStream
  WriteUnicodeStrings(FileStream, StringList1);
  // Write some more data to FileStream
  WriteUnicodeStrings(FileStream, StringList2);
finally
  FileStream.Free;
end;

Or, you can simply derive a class from SysUtils.TUnicodeEncoding and override its GetPreamble() method to not return any BOM, then use that class instead of using TEncoding.Unicode.

type
  TMyUnicodeEncoding = class(TUnicodeEncoding)
  public
    function GetPreamble: TBytes; override;
  end;

function TMyUnicodeEncoding.GetPreamble: TBytes;
begin
  Result := nil;
end;

procedure WriteUnicodeStrings(AStream: TStream; AStrings: TStrings);
var
  Enc: TMyUnicodeEncoding;
begin
  Enc := TMyUnicodeEncoding.Create;
  try
    AStrings.SaveToStream(AStream, Enc);
  finally
    Enc.Free;
  end;
end;
...
FileStream := TFileStream.Create('D:\MyFile.dat', fmCreate or fmOpenWrite or fmShareExclusive);
try
  // Write some data to FileStream
  WriteUnicodeStrings(FileStream, StringList1);
  // Write some more data to FileStream
  WriteUnicodeStrings(FileStream, StringList2);
finally
  FileStream.Free;
end;

Upvotes: 2

Related Questions