unicode文本文件输出在XE2和Delphi 2009之间有所不同?

时间:2013-01-09 10:16:16

标签: delphi unicode utf-8

当我尝试下面的代码时,与D2009相比,XE2中的输出似乎不同。

procedure TForm1.Button1Click(Sender: TObject);
var Outfile:textfile;
    myByte: Byte;

begin
  assignfile(Outfile,'test_chinese.txt');
  Rewrite(Outfile);

  for myByte in TEncoding.UTF8.GetPreamble do write(Outfile, AnsiChar(myByte));
  //This is the UTF-8 BOM

  Writeln(Outfile,utf8string('总结'));
  Writeln(Outfile,'°C');
  Closefile(Outfile);
end;

在Windows 8 PC上使用XE2进行编译在WordPad中提供

??  ç

txt十六进制代码:EF BB BF 3F 3F 0D 0A B0 43 0D 0A

在Windows XP PC上使用D2009进行编译,在Wordpad中提供

总结 °C

txt hex code:EF BB BF E6 80 BB E7 BB 93 0D 0A B0 43 0D 0A

我的问题是为什么它有所不同,如何使用旧的文本文件I / O将中文字符保存到文本文件中?

谢谢!

3 个答案:

答案 0 :(得分:16)

在XE2之后,AssignFile()有一个可选的CodePage参数,用于设置输出文件的代码页:

function AssignFile(var F: File; FileName: String; [CodePage: Word]): Integer; overload;

Write()Writeln()都有重载,支持UnicodeStringWideChar输入。

因此,您可以创建一个将其代码页设置为CP_UTF8的文件,然后Write/ln()会在将Unicode字符串写入文件时自动将其转换为UTF-8。

缺点是您将无法再使用AnsiChar值编写UTF-8 BOM,因为单个字节将转换为UTF-8,因此无法正确写入。您可以通过将BOM编写为单个Unicode字符(它实际上是它 - U+FEFF)而不是单独的字节来解决这个问题。

这适用于XE2:

procedure TForm1.Button1Click(Sender: TObject);
var
  Outfile: TextFile;
begin
  AssignFile(Outfile, 'test_chinese.txt', CP_UTF8);
  Rewrite(Outfile);

  //This is the UTF-8 BOM
  Write(Outfile, #$FEFF);

  Writeln(Outfile, '总结');
  Writeln(Outfile, '°C');
  CloseFile(Outfile);
end;

话虽如此,如果你想要在D2009和XE2之间更兼容和可靠的东西,请改用TStreamWriter

procedure TForm1.Button1Click(Sender: TObject);
var
  Outfile: TStreamWriter;
begin
  Outfile := TStreamWriter.Create('test_chinese.txt', False, TEncoding.UTF8);
  try
    Outfile.WriteLine('总结');
    Outfile.WriteLine('°C');
  finally
    Outfile.Free;
  end;
end;

或手动执行文件I / O:

procedure TForm1.Button1Click(Sender: TObject);
var
  Outfile: TFileStream;
  BOM: TBytes;

  procedure WriteBytes(const B: TBytes);
  begin
    if B <> '' then Outfile.WriteBuffer(B[0], Length(B));
  end;

  procedure WriteStr(const S: UTF8String);
  begin
    if S <> '' then Outfile.WriteBuffer(S[1], Length(S));
  end;

  procedure WriteLine(const S: UTF8String);
  begin
    WriteStr(S);
    WriteStr(sLineBreak);
  end;

begin
  Outfile := TFileStream.Create('test_chinese.txt', fmCreate);
  try
    WriteBytes(TEncoding.UTF8.GetPreamble);
    WriteLine('总结');
    WriteLine('°C');
  finally
    Outfile.Free;
  end;
end;

答案 1 :(得分:6)

你真的不应该再使用旧的文本I / O.

无论如何,你可以使用TEncoding来获得这样的UTF-8 TBytes:

procedure TForm1.Button1Click(Sender: TObject);
var Outfile:textfile;
    Bytes: TBytes;
    myByte: Byte;
begin
  assignfile(Outfile,'test_chinese.txt');
  Rewrite(Outfile);

  for myByte in TEncoding.UTF8.GetPreamble do write(Outfile, AnsiChar(myByte));
  //This is the UTF-8 BOM

  Bytes := TEncoding.UTF8.GetBytes('总结');
  for myByte in Bytes do begin
    Write(Outfile, AnsiChar(myByte));
  end;

  Writeln(Outfile,'°C');
  Closefile(Outfile);
end;

我不确定是否有更简单的方法将TBytes写入文本文件,也许其他人有更好的想法。

修改

对于纯二进制文件(File而不是TextFile类型),可以使用BlockWrite

答案 2 :(得分:5)

有几个告诉标志可能会告诉你在处理Unicode时出了什么问题。在你的情况下,你会看到&#34; ?&#34;在生成的输出文件中:当您尝试将某些内容从Unicode转换为代码页时,您会收到问号,并且目标代码页不能代表请求的字符。

查看十六进制转储,很明显(计算行终止符)问号是将两个中文字符保存到文件的结果。两个字符转换为恰好两个问号。这告诉您Writeln()决定为您提供帮助并将文本从UTF8(unicode表示)转换为您的本地代码页。 Delphi团队可能决定这样做,因为旧的I / O例程不应该是UNICODE兼容的;因为您使用旧的I / O例程编写UTF8字符串,所以他们会通过将其转换为您的代码页来帮助您。你可能不欢迎这种帮助,但这并不意味着这样做是错误的:它是无证的领域。

既然你现在知道为什么会发生这种情况,你知道如何阻止它。让WriteLn()知道你发送的内容并不需要转换。您会发现这并不是特别容易,因为德尔福XE2显然可以帮助您解决问题。不管你。例如,像这样的东西并不只是改变字符串类型,它会转换为AnsiString,通过代码页转换例程来获取问号:

AnsiString(UTF8String('Whatever Unicode'));

因此,如果您需要单线解决方案,您可以尝试转换例程,如下所示:

function FakeConvert(const InStr: UTF8String): AnsiString;
var N: Integer;
begin
  N := Length(InStr);
  SetLength(Result, N);
  Move(InStr[1], Result[1], N);
end;

然后你就可以做到:

Writeln(Outfile,FakeConvert('总结'));

它会做你期望的事情(我确实在张贴之前尝试过它!)

当然这个问题的唯一正确答案是,因为你一直升级到Delphi XE2:

停止使用已弃用的I / O例程,移至基于TStream的