此Unicode NFC转换是否正确?

Joh*_*ger 3 unicode normalization icu unicode-normalization

我正在开发一个C项目,需要生成"不区分大小写"的规范化形式的Unicode文本.我选择将规范化形式定义为首先转换为NFD规范化,然后应用Unicode案例折叠算法,最后将结果转换为NFC规范化形式NFC.

我依靠ICU的C API来实现其Unicode表示和实用功能,使用ICU unorm_normalize()u_strFoldCase()函数实现我的方案相当简单.但是我的一个测试失败了,我不明白为什么.ICU似乎正在产生一种与我预期不同的NFC形式.

输入序列由以下BMP代码点组成:

U+0020, U+1EA5, U+0328, U+1EC4, U+031C
Run Code Online (Sandbox Code Playgroud)

通过调试器,我确定ICU和我同意案例折叠后的中间结果:

U+0020 U+0061 U+0328 U+0302 U+0301 U+0065 U+031C U+0302 U+0303
Run Code Online (Sandbox Code Playgroud)

特别要注意的是,早期转换为形成NFD将字符U + 031C移动到U + 1EC4分解的中间,根据所涉及字符的相对CCC编号.这是我试图测试的一部分.

现在好的部分:根据ICU,折叠字符序列的NFC标准化是

U+0020 U+0105 U+0302 U+0301 U+1ec5 U+031C
Run Code Online (Sandbox Code Playgroud)

而我认为它应该是

U+0020 U+0105 U+0302 U+0301 U+0065 U+031C U+0302 U+0303
Run Code Online (Sandbox Code Playgroud)

因为三个尾随组合字符已经按规范顺序排列,并且没有U + 0065和U + 031C的规范组合.

那么,有两个问题:

  1. 哪种NFC形式正确?
  2. 如果ICU是正确的那么为什么?

nwe*_*hof 6

ICU是对的.要了解原因,请查看Unicode标准第3章中定义的规范组合算法:

D117 规范组合算法:从编码字符序列(规范分解或兼容性分解)中的第二个字符开始,然后按顺序进行到最终字符,执行以下步骤:

R1在字符C的编码字符序列中向后(左)查找字符序列中C之前的最后一个Starter L.

R2如果存在这样的L,并且C没有被L阻挡,并且存在规范等效于序列<L,C>的初级复合P,则在序列中将L替换为P并从序列中删除C .

您还必须了解前面的定义,尤其是:

D115被阻止:令A和C为编码字符序列<A,... C>中的两个字符.当且仅当ccc(A)= 0并且在编码字符序列中A和C之间存在一些字符B,即<A,... B,... C>和ccc时,C被阻止. (B)= 0或ccc(B)> = ccc(C).

现在考虑输入序列的以下子字符串:

U+0065 U+031C U+0302 U+0303
Run Code Online (Sandbox Code Playgroud)

我们从角色开始,U+031C然后回到最后一个启动器U+0065:

U+0065 U+031C U+0302 U+0303
L      C
Run Code Online (Sandbox Code Playgroud)

C显然没有被阻挡,但是没有主要的复合等价物,<L, C>所以我们继续下一个字符:

U+0065 U+031C U+0302 U+0303
L             C
Run Code Online (Sandbox Code Playgroud)

现在C仍然没有被阻止L(这是你可能误解的)因为ccc(U+031C) = 220 < 230 = ccc(U+0302)并且存在一个U+00EA等同于的主复合U+0065 U+0302.所以我们替换L并删除C:

U+00EA U+031C U+0303
L             C
Run Code Online (Sandbox Code Playgroud)

同样,C不会被L阻挡,并且主要复合材料U+1EC5相当于U+00EA U+0303所以组合物的最终结果是:

U+1EC5 U+031C
Run Code Online (Sandbox Code Playgroud)

这与ICU的输出相匹配.