使用 Unicode 规范化来表示字符串

应用程序可以使用 Unicode 来表示多个形式的字符串。 随着 Unicode 的接受程度(特别是通过 Internet)的接受度增加,人们开始需要消除 Unicode 字符串中的非基本差异。 字符组合的多个表示形式会使软件复杂化,例如,当 Web 服务器响应页面请求或链接器在库中查找特定标识符时。

注意

不同的 Unicode 字符串在视觉上可能看起来是相同的,这引起了安全问题。 有关详细信息,请参阅 安全注意事项:国际功能

 

为了响应此要求,Unicode 联盟定义了一个名为“规范化”的过程,它为字符的任何等效二进制表示形式生成一个二进制表示形式。 规范化后,当且仅当它们具有相同的二进制表示形式时,两个字符串才等效。 规范化消除了一些差异,但保留了大小写。

若要使用 Unicode 规范化,应用程序可以调用 NormalizeStringIsNormalizedString 函数,以重新排列加入 Unicode 4.0 TR#15 的字符串。 规范化可以通过减少具有相同语言含义的备用字符串表示形式来帮助提高安全性。 但请记住,规范化不能完全消除备用表示形式。

有关规范化的 Unicode 标准的详细说明,请参阅 Unicode 标准附件 #15:Unicode 规范化表单 (UAX #15) 。

注意

由于规范化可以更改字符串的形式,因此通常应在规范化后实现安全机制或字符验证算法。 有关详细信息,请参阅 安全注意事项:国际功能

 

提供同一字符串的多个表示形式

在许多情况下,Unicode 允许在语言上对同一字符串的多个表示形式。 例如:

  • 使用 dieresis (umlaut) 的大写 A 可以表示为单个 Unicode 码位“Ä” (U+00C4) 或大写 A 与 dieresis 字符的组合 (“A”+ “ー”,即 U+0041 U+0308) 。 类似的注意事项适用于具有音调符号的许多其他字符。
  • 大写字母 A 本身可以按通常的方式表示 (拉丁文大写字母 A、U+0041) 或全形拉丁文大写字母 A (U+FF21) 。 类似的注意事项适用于其他简单拉丁字母 (大写和小写) 以及用于编写日语的片假名字符。
  • 字符串“fi”可以由字符“f”和“i” (U+0066 U+0069) 或由连字“fi” (U+FB01) 表示。 类似的注意事项适用于 Unicode 为其定义连字的许多其他字符组合。

使用四种定义的规范化形式

应用程序可以使用多种遵循不同规则的算法(称为“规范化形式”)执行 Unicode 规范化。 Unicode 联盟定义了四种规范化形式:NFC (形式 C) 、NFD (形式 D) 、NFKC (形式 KC) 和 NFKD (形式 KD) 。 每种形式都消除了一些差异,但保留了大小写。 Win32 和 .NET Framework支持所有四种规范化形式。

NLS 枚举类型 NORM_FORM 支持四种标准的 Unicode 规范化形式。 表单 C 和 D 为字符串提供规范形式。 非规范形式 KC 和 KD 提供进一步的兼容性,并且可以揭示某些在形式 C 和 D 中不明显的语义等效性。但是,它们这样做会以某种信息损失为代价,通常不应用作存储字符串的规范方式。

在两种规范形式中,窗体 C 是“组合”窗体,窗体 D 是“分解”窗体。 例如,表单 C 使用单个 Unicode 码位“Ä” (U+00C4) ,而表单 D 使用 (“A” + “ー”,即 U+0041 U+0308) 。 这些呈现方式相同,因为“ー” (U+0308) 是组合字符。 表单 D 可以使用任意数量的代码点来表示表单 C 使用的单个代码点。

如果两个字符串在形式 C 或形式 D 中相同,则它们在另一种形式中是相同的。 此外,正确呈现后,它们彼此和原始非规范化字符串无法区分。

规范化后,字符串无法一致地返回到其原始表示形式。 例如,如果组合字符表示形式和分解字符表示形式混合的字符串转换为规范化形式,则无法将其规范化为原始混合字符串。 因此,如果应用程序需要字符串的原始表示形式,则必须显式存储该表示形式。 但是,这两种规范形式之间的转换是可逆的。 表单 C 中的字符串可以转换为表单 D,然后重新转换为表单 C,结果与原始形式 C 字符串相同。

表单 KC 和 KD 分别类似于形式 C 和 D,但这些“兼容性形式”具有兼容的字符到每个字符的基本形式的附加映射。 此类映射可能会导致次要字符变体丢失。 它们组合了某些在视觉上不同的字符。 例如,它们将全角和半角字符与相同语义含义或相同阿拉伯字母的不同形式组合在一起,或者将连字“fi” (U+FB01) 和字符对“fi” (U+0066 U+0069) 。 它们还组合了一些有时可能具有不同语义含义的字符,例如将数字写成上标、下标或括在圆圈中。 由于这种信息丢失,表单 KC 和 KD 通常不应用作规范形式的字符串,但它们对某些应用程序很有用。

窗体 KC 是组合窗体,表单 KD 是分解的窗体。 应用程序可以在形式 KC 和 KD 之间来回切换,但无法以一致的方式从形式 KC 或 KD 返回到原始字符串,即使原始字符串采用形式 C 或 D。

Windows、Microsoft 应用程序和.NET Framework通常使用普通输入方法以 C 格式生成字符。 对于 Windows 上的大多数用途,表单 C 是首选窗体。 例如,C 格式中的字符由 Windows 键盘输入生成。 但是,从 Web 和其他平台导入的字符可能会将其他规范化形式引入数据流。

以下示例取自 UAX #15,说明了四种规范化形式之间的差异。

原始 表单 D 表单 C 注释
“Äffin” “A\u0308ffin” “Äffin” ffi_ligature (U+FB03) 不会分解,因为它具有兼容性映射,而不是规范映射。${REMOVE}$
“Ä\uFB03n” “A\u0308\uFB03n” “Ä\uFB03n”
“亨利四世” “亨利四世” “亨利四世” 罗马数字 IV (U+2163) 未分解。${REMOVE}$
“Henry \u2163” “Henry \u2163” “Henry \u2163”
ga ka +ten ga 单个日语字符的不同兼容性等效项不会导致格式为 C.${REMOVE}$ 的相同字符串
ka +ten ka +ten ga
hw_ka +hw_ten hw_ka +hw_ten hw_ka +hw_ten
ka +hw_ten ka +hw_ten ka +hw_ten
hw_ka +十 hw_ka +十 hw_ka +十
kaks k i + a m + ks f kaks 朝鲜文音节在规范化下保持。

 

原始 表单 KD 表单 KC 注释
“Äffin” “A\u0308ffin” “Äffin” ffi_ligature (U+FB03) 以 KC 形式分解,但不以 C.${REMOVE}$ 的形式分解
“Ä\uFB03n” “A\u0308ffin” “Äffin”
“亨利四世” “亨利四世” “亨利四世” 此处生成的字符串在形式 KC.${REMOVE}$ 中相同
“Henry \u2163” “亨利四世” “亨利四世”
ga ka +ten ga 单个日语字符的不同兼容性等效项会导致格式为 KC.${REMOVE}$ 的同一字符串
ka +ten ka +ten ga
hw_ka +hw_ten ka +ten ga
ka +hw_ten ka +ten ga
hw_ka +十 ka +ten ga
kaks k i + a m + ks f kaks 朝鲜文音节在规范化下保持。 在早期 Unicode 版本中,ks f 等 jamo 字符具有与 k f + s f 的兼容性映射。 在 Unicode 2.1.9 中删除了这些映射,以确保保留朝鲜文音节。

 

注意

上述两个表的版权©为 1998-2006 Unicode, Inc.保留所有权利。

 

将组合窗体用于单个字形

许多对应于单个字形的字符序列没有组合形式。 即使通过形式 C 规范化,单个视觉标志符号或逻辑文本元素也可以由多个 Unicode 码位组成。 例如,在编写立陶宛语时使用的几个字符具有双音调符号,因为它们只有分解的形式。 例如,带有宏和波形符的小写 U (“е̃”,U+016b U+0303,其中第一个码位是带有宏n 的小写 U,第二个代码点是结合锐重音) 。

示例

可以在 NLS:Unicode 规范化示例中找到相关示例。

使用国家语言支持

安全注意事项:国际功能

IsNormalizedString

NormalizeString