【问题标题】:Exception with German Umlaut characters in TMemIniFile.CreateTMemIniFile.Create 中的德语变音符号异常
【发布时间】:2016-04-06 05:15:30
【问题描述】:

我有一个 .URL 文件,其中包含以下包含德语变音符号的文本:

[互联网快捷方式]
网址=http://edn.embarcadero.com/article/44358
[我的应用]
注释=特殊测试 geändert
图标=默认
Title=RAD Studio XE8 的错误修复列表

我尝试使用TMemIniFile 加载文本:

uses System.IniFiles;
//
procedure TForm1.Button1Click(Sender: TObject);
var
  BookmarkIni: TMemIniFile;
begin
  // The error occurs here:      
  BookmarkIni := TMemIniFile.Create('F:\Bug fix list for RAD Studio XE8.url',
                                    TEncoding.UTF8);
  try
    // Some code here
  finally
    BookmarkIni.Free;
  end;
end;

这是来自调试器的错误消息文本:

Project MyApp.exe 引发异常类 EEncodingError 并带有消息 '目标多字节中不存在 Unicode 字符的映射 代码页”。

当我从 .URL 文件中删除带有德语变音符号“geändert”的单词时,没有错误。

但这就是我使用TMemIniFile 的原因,因为当.URL 文件中的文本包含Unicode 字符时,TIniFile 在这里不起作用。 (.URL 文件中还可能有其他 Unicode 字符)。

那么为什么我在TMemIniFile.Create 中遇到异常?

编辑:找到罪魁祸首:.URL 文件是ANSI 格式。当 .URL 文件为 UTF-8 格式时,不会发生该错误。但是当文件是ANSI 格式时,我该怎么办?

EDIT2:我创建了一个解决方法,它对ANSIUTF-8 文件都有效:

procedure TForm1.Button1Click(Sender: TObject);
var
  BookmarkIni: TMemIniFile;
  BookmarkIni_: TIniFile;
  ThisFileIsAnsi: Boolean;
begin
  try
    ThisFileIsAnsi := False;
    BookmarkIni := TMemIniFile.Create('F:\Bug fix list for RAD Studio XE8.url',
                                    TEncoding.UTF8);
  except
    BookmarkIni_ := TIniFile.Create('F:\Bug fix list for RAD Studio XE8.url');
    ThisFileIsAnsi := True;
  end;
  try
    // Some code here
  finally
    if ThisFileIsAnsi then
      BookmarkIni_.Free
    else
      BookmarkIni.Free;
  end;
end;

你怎么看?

【问题讨论】:

  • 你的解决方案很糟糕。选择一种编码。
  • @DavidHeffernan 请看我下面的评论,证明我的解决方法在所有 3 种情况下都返回了正确的结果,在没有 BOM 的情况下,没有任何其他方法可以为 UTF-8 文件获得正确的结果!跨度>
  • 看看我对你下面评论的评论...:-) 如果没有 BOM,就没有万无一失的方法。
  • 您无法仅从文件内容中可靠地恢复编码。这是众所周知的。

标签: delphi unicode utf-8 ini delphi-10-seattle


【解决方案1】:

一般来说,从文件内容中自动检测文件的编码是不可能的。

Raymond Chen 的这篇文章清楚地证明了这一点:The Notepad file encoding problem, redux。 Raymond 以包含这两个字节的文件为例:

D0 自动曝光

Raymond 继续证明这是一个格式良好的文件,具有以下四种编码:ANSI 1252、UTF-8、UTF-16BE 和 UTF-16LE。

这里的要点是您必须知道文件的编码。要么按照惯例与编写文件的人达成一致。或者强制存在 BOM。

【讨论】:

    【解决方案2】:

    您需要一劳永逸地决定文件的编码是什么。没有万无一失的自动检测方法,因此您必须从创建这些文件的代码中强制执行它。

    如果此文件的创建超出了您的控制范围,那么您或多或少地不走运。您可以尝试依赖文件开头的 BOM(字节顺序标记)(如果它是 UTF-8 文件,应该在那里)。我无法从 TMemIniFile 的规范中看到没有编码参数的 CREATE 构造函数对文件编码的假设(我的 猜测 是它遵循 BOM,如果没有这样的东西,它假定为 ANSI,即系统代码页)。

    可以做的一件事——如果你决定坚持你当前的方法——就是将你的代码更改为:

    procedure TForm1.Button1Click(Sender: TObject);
    var
      BookmarkIni: TCustomIniFile;
    begin
      // The error occurs here:
      try
        BookmarkIni := TMemIniFile.Create('F:\Bug fix list for RAD Studio XE8.url',
                                        TEncoding.UTF8);
      except
        BookmarkIni := TIniFile.Create('F:\Bug fix list for RAD Studio XE8.url');
      end;
      try
        // Some code here
      finally
        BookmarkIni.Free;
      end;
    end;
    

    您不需要两个单独的变量,因为 TIniFile 和 TMemIniFile(以及 TRegistryIniFile)都有一个共同的祖先:TCustomIniFile。通过将变量声明为这个共同的祖先,您可以将它实例化(创建)为任何从 TCustomIniFile 继承的类类型。实际(运行时)类型取决于您调用创建的构造函数。

    但首先,你应该尝试使用

    BookmarkIni := TMemIniFile.Create('F:\Bug fix list for RAD Studio XE8.url');
    

    即。没有指定任何编码,看看它是否适用于 ANSI 和 UTF-8 文件。

    编辑:这是一个测试程序来验证我在 cmets 中的声明:

    program Project21;
    
    {$APPTYPE CONSOLE}
    
    uses
      IniFiles, System.SysUtils;
    
    const
      FileName = 'F:\Bug fix list for RAD Studio XE8.url';
    
    var
      TXT : TextFile;
    
    procedure Test;
    var
      BookmarkIni: TCustomIniFile;
    begin
      try
        BookmarkIni := TMemIniFile.Create(FileName,TEncoding.UTF8);
      except
        BookmarkIni := TIniFile.Create(FileName);
      end;
      try
        Writeln(BookmarkIni.ReadString('MyApp','Notes','xxx'))
      finally
        BookmarkIni.Free;
      end;
    end;
    
    begin
      try
        AssignFile(TXT,FileName); REWRITE(TXT);
        try
          WRITELN(TXT,'[InternetShortcut]');
          WRITELN(TXT,'URL=http://edn.embarcadero.com/article/44358');
          WRITELN(TXT,'[MyApp]');
          WRITELN(TXT,'Notes=The German a umlaut consists of the following two ANSI characters: '#$C3#$A4);
          WRITELN(TXT,'Icon=default');
          WRITELN(TXT,'Title=Bug fix list for RAD Studio XE8');
        finally
          CloseFile(TXT)
        end;
        Test;
        ReadLn
      except
        on E: Exception do
          Writeln(E.ClassName, ': ', E.Message);
      end;
    end.
    

    【讨论】:

    • TMemIniFile.Create 没有 Encoding 参数似乎适用于 ANSI 和 UTF-8 文件(至少不再抛出异常)。现在我必须进行更多测试,看看在这种情况下,Read 和 Write 方法对于 Unicode 字符是否给出正确的结果。
    • 当不使用TMemIniFile.Create 指定编码时,BookmarkIni.ReadString 会为ANSIUTF-8-BOM 文件提供正确的结果。 UTF-8 没有 BOM 的文件会为 Unicode 字符提供带有“有趣字符”的字符串。但是,如果没有BOM,似乎没有办法检测UTF-8 文件。
    • HEUREKA!!!!当使用我在except 部分中使用TIniFile 的上述解决方法时(大卫称之为“可怕”),在所有3 种情况下,BookmarkIni.ReadString 都会返回正确的结果!!! ANSI、UTF-8-BOM 和没有 BOM 的 UTF-8 !!!所以这似乎是解决方案!
    • 澄清一下:在某些情况下,您的解决方法将无法正常工作,因为没有万无一失的方法来确定在 UTF-8 编码中有效的文本字符串是否应该被解释为UTF-8 或者如果组成 Unicode 字符的两个字节(或更多)被理解为 ANSI“有趣的字符”。使用启发式方法,您可以对它是什么做出合格的猜测,但这并不是万无一失的。您应该坚持使用“无编码指定”的 TMemIniFile 并坚持您正在处理的文件是 ANSI 或具有正确的 UTF-8 BOM。
    • 您能告诉我在哪些情况下我的解决方法不起作用吗?到目前为止,它已在所有 3 种文件编码格式中正常工作。
    【解决方案3】:

    经验法则 - 要正确读取数据(文件、流等),您必须知道编码!最好的解决方案是让用户选择编码或强制一个,例如utf-8.

    此外,ANSI 的信息确实使没有代码页的事情变得更容易。

    必读 - The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)

    其他方法是尝试检测编码(就像浏览器在没有指定编码的情况下对网站所做的那样)。如果BOM 存在,检测UTF 相对容易,但更多时候被忽略。看看Mozilla's universalchardetchsdet

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-09-28
      • 1970-01-01
      • 2016-01-21
      • 2013-02-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多