11

以下のコードを試してみると、XE2 と D2009 では出力が異なるようです。

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 を使用してコンパイルすると、ワードパッドが生成されます

?? C

txt 16 進コード: EF BB BF 3F 3F 0D 0A B0 43 0D 0A

Windows XP PC で D2009 を使用してコンパイルすると、ワードパッドが表示されます

总結 °C

txt 16 進コード: EF BB BF E6 80 BB E7 BB 93 0D 0A B0 43 0D 0A

私の質問は、なぜ異なるのか、また古いテキスト ファイル I/O を使用して中国語の文字をテキスト ファイルに保存するにはどうすればよいのかということです。

ありがとう!

4

3 に答える 3

19

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 に変換され、正しく書き込まれないため、値を使用して UTF-8 BOM を書き込むことができなくなることです。U+FEFFBOMを個々のバイトとしてではなく、単一の Unicode 文字 (これが実際の文字です) として記述することで、これを回避できます。

これは 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;
于 2013-01-09T18:21:37.510 に答える
6

古いテキストI/Oはもう使用しないでください。

とにかく、TEncodingを使用して、次のようなUTF-8TBytesを取得できます。

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

于 2013-01-09T10:50:07.423 に答える
5

Unicode を扱う際に何が問題なのかを示す兆候がいくつかあります。あなたの場合?、結果の出力ファイルに " " が表示されます。何かを Unicode からコード ページに変換しようとすると、疑問符が表示され、ターゲット コード ページが要求された文字を表すことができません。

16 進ダンプを見ると、疑問符が 2 つの漢字をファイルに保存した結果であることは明らかです (行ターミネータを数えます)。2 つの文字が正確に 2 つの疑問符に変換されました。これはWriteln()、テキストを UTF8 (Unicode 表現) からローカル コード ページに変換することを決定したことを示しています。Delphi チームは、古い I/O ルーチンが UNICODE と互換性があると想定されていないため、おそらくこれを行うことにしました。古い I/O ルーチンを使用して UTF8 文字列を作成しているので、これをコード ページに変換することで役に立ちます。あなたはその手助けを歓迎しないかもしれませんが、そうすることが間違っていたという意味ではありません: それは文書化されていない領域です.

なぜそれが起こっているのかがわかったので、それを止めるために何をすべきかがわかります。WriteLn()変換する必要のないものを送信していることを知らせてください。Delphi 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 ベースに移行

于 2013-01-09T12:58:13.477 に答える