在C++中处理 Unicode 字符

Processing Unicode characters in C++

本文关键字:Unicode 字符 处理 C++      更新时间:2023-10-16

我有一个文件,其中包含未声明编码的 Unicode 文本。我想扫描此文件,查找 U+0600 到 U+06FF 范围内的任何阿拉伯语码位,并将每个适用的 Unicode 码位映射到 ASCII 的一个字节,以便新生成的文件将由纯 ASCII 字符组成,所有码位都在 128 以下。

我该怎么做? 我试图以与您阅读 ASCII 相同的方式读取它们,但我的终端显示??因为它是一个多字节字符。

注意:该文件由 Unicode 字符集的子集组成,子集大小小于 ASCII 字符的大小。因此,我能够从这个特定的 Unicode 子集到 ASCII 进行 1:1 映射。

这要么是不可能的,要么是微不足道的。以下是一些简单的方法:

  • 如果没有码位超过 127,则只需用 ASCII 将其写出即可。 做。

  • 如果某些码位超过 127,则必须选择如何用 ASCII 表示它们。一种常见的策略是使用 XML 语法,如 U+03B1 的α。 对于转录的每个跨 ASCII Unicode 代码点,这将最多占用 8 个 ASCII 字符。

不可能的那些我留给原始海报作为练习。 我什至不会提到愚蠢但可能(阅读:愚蠢(的方法,因为这些方法很多。数据销毁是数据处理中的死罪,应按此处理。

请注意,我假设"Unicode 字符"实际上是指"Unicode 代码点";即程序员可见的字符。 对于用户可见的字符,您需要"Unicode 字形(集群("。

此外,除非你先规范化你的文本,否则你会讨厌这个世界。我建议NFD。


编辑

经过原始海报的进一步澄清,似乎他想做的事情很容易使用现有工具完成,而无需编写新程序。 例如,这会将一组特定的阿拉伯字符从 UTF-8 输入文件转换为 ASCII 输出文件:

$ perl -CSAD -Mutf8 -pe 'tr[ابتثجحخد][abttjhhd]' < input.utf8 > output.ascii

这仅处理以下代码点:

U+0627 ‭ ا  ARABIC LETTER ALEF
U+0628 ‭ ب  ARABIC LETTER BEH
U+0629 ‭ ة  ARABIC LETTER TEH MARBUTA
U+062A ‭ ت  ARABIC LETTER TEH
U+062B ‭ ث  ARABIC LETTER THEH
U+062C ‭ ج  ARABIC LETTER JEEM
U+062D ‭ ح  ARABIC LETTER HAH
U+062E ‭ خ  ARABIC LETTER KHAH
U+062F ‭ د  ARABIC LETTER DAL

因此,您必须将其扩展到所需的任何映射。

如果你想在脚本而不是命令行工具中使用它,它也很容易,另外,你可以通过设置映射来按名称讨论字符,例如:

 "N{ARABIC LETTER ALEF}"   =>  "a",
 "N{ARABIC LETTER BEH}"    =>  "b",
 "N{ARABIC LETTER TEH}"    =>  "t",
 "N{ARABIC LETTER THEH}"   =>  "t",
 "N{ARABIC LETTER JEEM}"   =>  "j",
 "N{ARABIC LETTER HAH}"    =>  "h",
 "N{ARABIC LETTER KHAH}"   =>  "h",
 "N{ARABIC LETTER DAL}"    =>  "d",

如果这应该是更大C++程序中的一个组件,那么也许你会希望用C++来实现它,可能但不是必需的,使用 ICU4C 库,其中包括音译支持。

但是,如果您只需要一个简单的转换,我不明白为什么要编写专用的C++程序。 似乎工作量太大了。

除非您知道格式,否则无法读取数据。 使用Microsoft Word打开文件,然后转到"另存为","其他格式","纯文本(.txt(",保存。 在转换框中,选择"其他编码","Unicode"(UTF16LE(和"确定"。 该文件现在另存为UTF16LE。

std:ifstream infile("myfile.txt", std::ios::binary); //open stream
infile.seekg (0, ios::end); //get it's size
int length = infile.tellg();
infile.seekg (0, ios::beg);
std::wstring filetext(length/2); //allocate space
ifstream.read((char*)&filetext[0], length); //read entire file
std::string final(length/2);
for(int i=0; i<length/2; ++i) { //"shift" the variables to "valid" range
    if (filetext[length/2] >= 0x600 && filetext[length/2] <= 0xFF)
        final[length/2] = filetext[length/2]-0x600;
    else
        throw std::exception("INVALID CHARACTER");
}
//done

到处都是警告:我非常怀疑这会产生你想要的结果,但这是可以管理的最好的,因为你还没有告诉我们需要做的翻译,或者文件的格式。 另外,我假设您的计算机和编译器与我的相同。 如果没有,部分或全部可能是错误的,但这是我能做的最好的事情,你没有告诉我们这些缺失的信息。

为了解析出 Unicode 代码点,您必须首先将文件解码为其未编码的 Unicode 表示形式(等效于 UTF-32(。 为此,您首先需要知道文件是如何编码的,以便可以对其进行解码。 例如,Unicode 代码点U+0600U+06FF以 UTF-8 编码为0xD8 0x800xDB 0xBF,在 UTF-16LE 中编码为0x00 0x060xFF 0x06,以 UTF-16BE 编码为0x06 0x000x06 0xFF,等等。

如果文件以 BOM 开头,则您知道使用的确切编码,并且可以相应地解释文件的其余部分。 例如,UTF-8 BOM 是0xEF 0xBB 0xBF的,UTF-16LE 是0xFF 0xFE的,UTF-16BE 是0xFE 0xFF的,等等。

如果文件不是以 BOM 开头,则必须分析数据并对其执行启发式以检测编码,但这不是 100% 可靠的。 尽管检测 UTF 编码相当容易,但几乎不可能以任何可靠性来检测 Ansi 编码。 即使检测没有 BOM 的 UTF 编码有时也会导致错误的结果(阅读这个、这个和这个(。

永远不要猜测,您将面临数据丢失的风险。 如果您不知道使用的确切编码,请向用户询问。