la sortie du fichier texte unicode diffère-t-elle entre XE2 et Delphi 2009?

quand j'essaie le code ci-dessous, il semble y avoir une sortie différente dans XE2 par rapport à 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;

compiler avec XE2 sur un PC Windows 8 donne en WordPad

?? C

code txt hex: EF BB BF 3F 3F 0D 0A B0 43 0D 0A

compiler avec D2009 sur un PC Windows XP donne en Wordpad

°C

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

ma question est pourquoi il diffère et comment puis-je sauvegarder des caractères chinois dans un fichier texte en utilisant l'ancien fichier texte e/s?

Merci!

8
demandé sur Thomas 2013-01-09 14:16:16

3 réponses

à partir de XE2, AssignFile() a un paramètre optionnel CodePage qui définit la page de code du fichier de sortie:

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

Write() et Writeln() ont tous deux des surcharges supportant les entrées UnicodeString et WideChar .

ainsi, vous pouvez créer un fichier dont le codepage est défini à CP_UTF8 , et ensuite Write/ln() convertira automatiquement les chaînes Unicode en UTF-8 en les écrivant pour le fichier.

l'inconvénient est que vous ne pourrez plus écrire le BOM UTF-8 en utilisant les valeurs AnsiChar , parce que les octets individuels seront convertis en UTF-8 et ne seront donc pas écrits correctement. Vous pouvez contourner cela en écrivant le BOM comme un caractère Unicode simple (ce qui est ce qu'il est vraiment - U+FEFF ) au lieu de comme des octets individuels.

cela fonctionne en 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;

Avec cela dit, si vous voulez quelque chose qui est plus compatible et fiable entre D2009 et XE2, utilisez TStreamWriter à la place:

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;

ou faire le fichier I/O manuellement:

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;
15
répondu Remy Lebeau 2016-06-12 06:37:58

vous ne devriez vraiment plus utiliser l'ancienne entrée/sortie de texte.

de toute façon, vous pouvez utiliser TEncoding pour obtenir les TBytes UTF-8 comme ceci:

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;

Je ne suis pas sûr qu'il y ait un moyen plus facile d'écrire des TBytes dans un fichier texte, peut-être que quelqu'un d'autre a une meilleure idée.

Edit:

pour un fichier binaire pur ( File au lieu de TextFile type) l'utilisation peut utiliser BlockWrite .

6
répondu Jens Mühlenhoff 2013-01-09 11:11:43

il y a quelques signes révélateurs qui peuvent vous dire ce qui ne va pas avec Unicode. Dans votre cas, vous voyez " ? " dans le fichier de Sortie Résultant: vous obtenez des points d'interrogation lorsque vous essayez de convertir quelque chose D'Unicode à une Page de Code et la Page de Code cible ne peut pas représenter les caractères demandés.

en regardant le dump hexadécimal il est évident (en comptant les terminateurs de ligne) que les points d'interrogation sont le résultat de sauver les deux Chinois des caractères dans le fichier. Les deux caractères suis converti à exactement deux points d'interrogation. Cela vous indique que le Writeln() a décidé de vous donner de l'AIDE et a converti le texte de UTF8 (une représentation unicode) à votre page de code local. L'équipe de Delphi a probablement décidé de le faire puisque les anciennes routines d'E/S ne sont pas supposées être compatibles UNICODE; puisque vous écrivez une chaîne UTF8 en utilisant les anciennes routines d'e/s, ils vous aident en convertissant ceci à votre page de Code. Vous ne pourriez pas les bienvenus que aider la main, mais cela ne veut pas dire que c'était mal de le faire: c'est un territoire sans papiers.

Puisque vous savez maintenant pourquoi cela se passe, vous savez quoi faire pour l'arrêter. Dites à WriteLn() que vous envoyez quelque chose qui n'a pas besoin d'être converti. Vous découvrirez que ce n'est pas particulièrement facile, puisque Delphi XE2 apparemment "vous aide" quoi que vous. Par exemple, ce genre de truc ne change pas seulement le type de chaîne, il convertit en AnsiString, en passant par la conversion de la page de code

AnsiString(UTF8String('Whatever Unicode'));

pour cette raison, et si vous avez besoin de solutions à une seule doublure, vous pouvez essayer une routine de conversion, quelque chose comme ceci:

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

vous pourrez alors faire:

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

et il va faire ce que vous attendez (Je l'ai effectivement essayé avant de poster!)

bien sûr, la seule vraie réponse à cette question Est, puisque vous mis à jour tout le chemin à Delphi XE2:

Cesser d'utiliser obsolète I/O routines, passer à TStream fonction

5
répondu Cosmin Prund 2013-01-09 12:58:13