将(某些)Unicode 非空格标记与关联字母相结合以进行统一处理

Ces*_*Gon 3 c# unicode text

我正在用 C# 编写一个文本处理 Windows 应用程序。该应用程序处理许多纯文本文件以计算字符、单词等。为此,该应用程序会迭代每个文件中的字符。我发现某些文本文件表示重音字母,例如\xc3\xa1使用 Unicode 字符 U+00E1(小写字母 A 带尖音符号),而其他文本文件则使用简单的非重音符号a(U+0061,小写字母 A),后跟 U+0301(组合尖锐的口音)。通过记事本或我使用的其他编辑器在屏幕上呈现文本的方式没有视觉差异,但底层字符流明显不同。

\n

我想以同样的方式检测和处理这两种情况。换句话说,我希望我的应用程序将一个字母和一个组合代码点组合成等效的独立字符。例如,我想将序列 U+0061 U+0301 组合成 U+00E1。据我所知,除了一个大型且容易出错的查找表来查找普通字母和组合字符的所有可能组合之外,没有简单的算法可以做到这一点。

\n

我有更简单、更直接的算法来执行这种组合吗?

\n

can*_*on7 9

您指的是 Unicode规范化形式。该页面介绍了一些有趣的细节,但要点是将重音字母表示为单个代码点(例如\xc3\xa1U+00E1)是标准化形式 C 或 NFC,并且表示为单独的代码点(例如\xc3\xa1U+0061 U+0301)是 NFD。

\n

Unicode 规范的第 3.11 节详细介绍了如何实现它,并在此处提供了一些额外的详细信息。

\n

幸运的是,您不需要自己实现这个:string.Normalize()已经存在。

\n
"\\u00E1".Normalize(NormalizationForm.FormD); // \\u0061\\u0301\n"\\u0061\\u0301".Normalize(NormalizationForm.FormC); // \\u00E1\n
Run Code Online (Sandbox Code Playgroud)\n
\n

也就是说,我们只是触及了“角色”的表面。表情符号可以很好地说明这一点,但它也适用于各种脚本:在现代脚本中,普通字符由两个代码点组成,并且没有可用的单个组合代码点。这会出现在泰米尔语和泰语以及一些东欧语言 (IIRC) 中。

\n

我最喜欢的例子是 \xe2\x80\x8d,或“女消防员:中等肤色”。想猜猜它是如何编码的吗?没错,有 4 个不同的代码点:U+1F469 U+1F3FD U+200D U+1F692。

\n
    \n
  • U+1F469 是,女人表情符号。
  • \n
  • U+1F3FD 是“Emoji Modifier Fitzpatrick Type-4”,它将之前的表情符号修改为棕色肤色,呈现为单独出现时的效果。
  • \n
  • U+200D 是一个“零宽度连接符”,用于将代码点粘合在一起形成同一个字符
  • \n
  • U+1F692 是 ,消防车表情符号。
  • \n
\n

所以你拿一个女人,加上棕色肤色,把她粘到消防车上,你就得到了一个棕色皮肤的女消防员。

\n

(只是为了好玩,尝试将 \xe2\x80\x8d 粘贴到各种编辑器中,然后在其上使用退格键。如果渲染正确,一些编辑器会将其转换为然后删除它,而另一些编辑器会跳过各个部分。但是,您将其选择为单个字符。这反映了在某些脚本中编辑复杂字符的工作方式)。

\n

(另一个有趣的块是国旗表情符号。Unicode 定义了“区域指示符号字母 AZ”(U+1F1E6 到 U+1F1FF),并且国旗使用以下方式编码为国家/地区的 ISO 3166-1 alpha-2 字母国家/地区代码这些指示符号。So 后面是 。将 粘贴到 the 之后,就会出现一个标志!)

\n

当然,如果您逐个代码点迭代此代码点,您将单独访问 U+1F469 U+1F3FD U+200D U+1F692,这可能不是您想要的。

\n

如果你逐个字符地迭代这个字符,由于代理对,你会做得更糟:像 U+1F469 这样的代码点太大,无法使用单个 16 位字符来表示,所以我们需要使用其中两个。这意味着,如果您尝试迭代 U+1F469,您实际上会发现有两个字符:0xD83D(高代理项)和 0xDC69(低代理项)。

\n

相反,我们需要引入扩展的字素簇,它代表您传统上认为的单个字符。如果您想自己做这件事,那么又会很复杂,而且有人已经帮您完成了:StringInfo.GetTextElementEnumerator。请注意,这在 .NET 5 之前存在一些问题,并且无法正确处理所有 EGC

\n

然而,在 .NET 5 中:

\n
// Number of chars, as 3 of the codepoints need to use surrogate pairs when\n// encoded with UTF-16\n"\xe2\x80\x8d".Length; // 7\n\n// Number of Unicode codepoints\n"\xe2\x80\x8d".EnumerateRunes().Count(); // 4\n\n// Number of extended grapheme clusters\nGetTextElements("\xe2\x80\x8d").Count(); // 1\n\npublic static IEnumerable<string> GetTextElements(string s)\n{\n    TextElementEnumerator charEnum = StringInfo.GetTextElementEnumerator(s);\n    while (charEnum.MoveNext())\n    {\n        yield return charEnum.GetTextElement();\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

我在这里使用表情符号作为一个可以理解的示例,但这些问题也会出现在现代脚本中,处理文本的人们需要意识到它们。

\n