此Unicode NFC转换是否正确?

时间:2014-06-20 22:56:32

标签: unicode normalization icu unicode-normalization

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

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

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

U+0020, U+1EA5, U+0328, U+1EC4, U+031C

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

U+0020 U+0061 U+0328 U+0302 U+0301 U+0065 U+031C U+0302 U+0303

特别注意,早期转换为NFD将U + 031C转换为U + 1EC4分解的中间位置,根据相关字符的相对CCC编号进行适当调整。这是我试图测试的一部分。

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

U+0020 U+0105 U+0302 U+0301 U+1ec5 U+031C

虽然我认为它应该是

U+0020 U+0105 U+0302 U+0301 U+0065 U+031C U+0302 U+0303

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

所以,有两个问题:

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

1 个答案:

答案 0 :(得分:6)

ICU是正确的。要了解原因,请查看chapter 3 of the Unicode Standard中定义的规范组合算法:

  

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

R1从字符C中回溯(左)编码字符序列,找到字符序列中C之前的最后一个Starter L.

     

R2如果存在这样的L,并且C没有被L阻挡,并且存在规范等效于序列< L,C>的一次复合P,则在序列中用P替换L并删除C来自序列。

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

  

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

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

U+0065 U+031C U+0302 U+0303

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

U+0065 U+031C U+0302 U+0303
L      C
显然C没有被阻止,但是没有与<L, C>相当的主复合,所以我们继续下一个字符:

U+0065 U+031C U+0302 U+0303
L             C

现在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

同样,C不会被L阻止,主要合成U+1EC5等同于U+00EA U+0303,因此合成的最终结果是:

U+1EC5 U+031C

这与ICU的输出相匹配。