选择文本模式的编码

对于写入的文本文件,编码应基于文本特性选择,或适配外部程序对于生成文件的特定要求。如果没有外部要求,你可以遵循这一规则:对于具有数字、英文字母和符号的纯文本,使用 ANSI 编码(包含 128 个国际字符表格详见 字符串比较章节中讨论过)。处理各种语言或特殊字符时,使用 UTF-8 或 Unicode,即分别为:

int u8 = FileOpen("utf8.txt"FILE_WRITE | FILE_TXT | FILE_ANSI0CP_UTF8);
int u0 = FileOpen("unicode.txt"FILE_WRITE | FILE_TXT | FILE_UNICODE);

例如,这些设置很适合用于将金融工具的名称保存到文件,因为它们有时候使用表示货币或交易模式的特殊字符。

读取你自己的文件应没有问题,因为在读取时只需指定与写入时的相同编码设置即可。然而,文本文件的来源可能各不相同。它们的编码可能未知,或者会未经通知而更改。因此,问题来了,如果一些文件以单字节字符串 (ANSI) 提供,一些是双字节字符串 (Unicode),而一些是 UTF-8 编码,该怎么办。

可以通过程序的 输入参数 来选择编码。然而,这仅对一个文件有效,如果你必须打开很多不同文件,它们的编码可能不匹配。因此,最好指示系统即时(根据文件)选择正确的模型。

MQL5 不允许 100% 自动检查和应用正确编码,然而,有一种用于读取各种文本文件的最通用的模式。为此,你需要设置 FileOpen 函数的以下输入参数:

int h = FileOpen(filenameFILE_READ | FILE_TXT | FILE_ANSI0CP_UTF8);

这幕后有几个因素。

首先,UTF-8 编码明确跳过上述任何 ANSI 编码中的 128 个字符(即,这些字符是“一对一”传输)。

其次,它对互联网协议来说是使用最广泛的。

再次,MQL5 具有针对两字节 Unicode 的附加内置分析,可让你在必要时将文件操作模式自动切换为 FILE_UNICODE,不论指定的参数如何。事实是,Unicode 格式的文件通常在开头有一对特殊标识符:0xFFFE 或者 0xFEFF。该序列称为字节顺序标记 (BOM)。这是必需的,因为我们已经知道,不同平台上数字的字节存储顺序可能不同(这在 整数中的字节顺序控制章节中讨论过)。

FILE_UNICODE 格式中,每字符使用 2 个字节整数(代码),因此字节顺序变得重要,不同于其它编码。Windows 字节顺序 BOM 是 0xFFFE。如果 MQL5 核心在一个文本文件的开头即找到该标签,则其读取模式将自动切换为 Unicode 模式。

我们看看不同模式设置如何处理不同编码的文本文件。为此,我们将使用 FileText.mq5 脚本以及具有相同内容但不同编码的若干文本文件(括号中是字节数大小):

  • ansi1252.txt (50):European 编码 1252(在欧洲语言的 Windows 中,其将完整无损显示)
  • unicode1.txt (102):两字节 Unicode,开头是固有的 Windows BOM 0xFFFE
  • unicode2.txt (100):两字节 Unicode,没有 BOM(一般来说,BOM 可选)
  • unicode3.txt (102):两字节 Unicode,开头是 Unix 固有的 BOM,0xFEFF
  • utf8.txt (54):UTF-8 编码

OnStart 函数中,我们将以 FileOpen 的不同设置循环读取这些文件。请注意,使用 FileHandle(在 上一节中回顾过),我们不必担心关闭文件的问题:所有操作会在每次迭代中自动完成。

void OnStart()
{
   Print("=====> UTF-8");
   for(int i = 0i < ArraySize(texts); ++i)
   {
      FileHandle fh(FileOpen(texts[i], FILE_READ | FILE_TXT | FILE_ANSI0CP_UTF8));
      Print(texts[i], " -> "FileReadString(~fh));
   }
   
   Print("=====> Unicode");
   for(int i = 0i < ArraySize(texts); ++i)
   {
      FileHandle fh(FileOpen(texts[i], FILE_READ | FILE_TXT | FILE_UNICODE));
      Print(texts[i], " -> "FileReadString(~fh));
   }
   
   Print("=====> ANSI/1252");
   for(int i = 0i < ArraySize(texts); ++i)
   {
      FileHandle fh(FileOpen(texts[i], FILE_READ | FILE_TXT | FILE_ANSI01252));
      Print(texts[i], " -> "FileReadString(~fh));
   }
}

FileReadString 函数从文件读取字符串。我们将在关于 读写变量的章节中探讨。

这里是个带有脚本执行结果的示例日志:

=====> UTF-8
MQL5Book/ansi1252.txt -> This is a text with special characters: ?? / ? / ?
MQL5Book/unicode1.txt -> This is a text with special characters: ±Σ / £ / ¥
MQL5Book/unicode2.txt -> T
MQL5Book/unicode3.txt -> ??
MQL5Book/utf8.txt -> This is a text with special characters: ±Σ / £ / ¥
=====> Unicode
MQL5Book/ansi1252.txt -> 桔��椠⁳⁡整��眠��⁨���X档牡捡整�@›㾱⼠ꌠ⼠ꔠ
MQL5Book/unicode1.txt -> This is a text with special characters: ±Σ / £ / ¥
MQL5Book/unicode2.txt -> This is a text with special characters: ±Σ / £ / ¥
MQL5Book/unicode3.txt -> �歌�氮Q �氮Q 愀 �H攀砀�H �b�惮H栀 �Q�X攀���点�� ��栀愀�e愀���H攀�e�Q㨀 넀
MQL5Book/utf8.txt -> 桔��椠⁳⁡整��眠��⁨���X档牡捡整�@›뇂ꏎ⼠술₣ ꗂ
=====> ANSI/1252
MQL5Book/ansi1252.txt -> This is a text with special characters: ±? / £ / ¥
MQL5Book/unicode1.txt -> This is a text with special characters: ±Σ / £ / ¥
MQL5Book/unicode2.txt -> T
MQL5Book/unicode3.txt -> þÿ
MQL5Book/utf8.txt -> This is a text with special characters: Â±Î£ / Â£ / Â¥

unicode1.txt 文件始终被正确读取,因为它有 BOM 0xFFFE,系统忽略源代码中的设置。然而,如果标签缺失或为大端模式,则这种自动检测不起作用。同时,若设置了 FILE_UNICODE,我们便无法读取单字节文本和 UTF-8。

因此,前述 FILE_ANSI 和 CP_UTF8 的组合应被视为更能适应格式设置中的变化。仅当明确要求时,才建议选择特定国家代码页。

尽管在文本模式下处理文件时,API 为编程人员提供了极大帮助,但如果必要,我们可以避免 FILE_TXT 或 FILE_CSV 模式,而是以二进制模式 FILE_BINARY 打开一个文本文件。这将把解析文本和确定编码的所有复杂任务将由编程人员负责完成,但能够支持其它非标准格式。但要点在于,可以从以二进制模式打开的文件读取或写入文本。但是,反过来一般不行。以文本模式打开的带有任意数据的二进制文件(即,其不是仅包含字符串)将很大可能被解读为乱码文本。如果想要将二进制数据写入到一个文本文件,则首先使用 CryptEncode 函数和 CRYPT_BASE64 编码。