Unicode中检查组合字符的算法

10
我打算将文本标准化为C形式,然后再分成“显示单元”,基本上是一个字形加上所有后续的组合字符。目前,我只是想处理基于拉丁字母的脚本。
要确定一个代码点是否为组合字符,只需要检查它是否在以下范围内吗?
- Combining Diacritical Marks (0300–036F) - Combining Diacritical Marks Supplement (1DC0–1DFF) - Combining Diacritical Marks for Symbols (20D0–20FF) - Combining Half Marks (FE20–FE2F)
阿拉伯语、希伯来语以及各种印度脚本尚未处理...

1
“UTF-8”与此有什么关系?您应该将问题分解为可理解的单元:1)将UTF-8解码为Unicode,2)规范化。 - Kerrek SB
你确定要手动完成这个任务而不是使用ICU或其他库吗?如果我没记错的话,自从Java 6以来,即使使用纯Java库(在Java 5中必须使用sun.*包中的未记录类),也可以执行Unicode规范化和分解。 - Michał Kosmulski
1
如果您打算将字符拆分为基本字符和组合字符,为什么要使用NFC而不是NFD(规范分解)? - Jukka K. Korpela
3个回答

3
以下是所有Unicode范围,其名称包含单词“combining”的范围(例如301 COMBINING ACUTE ACCENT):
300-36F 483-489 7EB-7F3 135F-135F 1A7F-1A7F 1B6B-1B73 1DC0-1DE6 1DFD-1DFF 20D0-20F0 2CEF-2CF1 2DE0-2DFF 3099-309A A66F-A672 A67C-A67D A6F0-A6F1 A8E0-A8F1 FE20-FE26 101FD-101FD 1D165-1D169 1D16D-1D172 1D17B-1D182 1D185-1D18B 1D1AA-1D1AD 1D242-1D244
我使用Python脚本编译了此列表,并利用了unicodedata模块。我不知道这具体是哪个版本的Unicode,但我认为它相当新。 然而,我不知道您是否完成了在严格意义上“组合”的字符,因为Unicode中还有“修饰符字母”等。

6
要确定一个字符是否是一个组合标记,您应该测试它的通用类别(gc)属性,而不是Unicode名称,后者仅是字母标识符。 - Jukka K. Korpela
当然,还有很多,特别是在“Mark, nonspacing”类别中。 - lenz
因此,答案基本上是“不”,尽量依赖现有的库。 - Yimin Rong
以下是使用Java测试“gc”属性的示例:http://stackoverflow.com/a/29111105/32453 - rogerdpack

2

我最近也做了类似的东西。祝您使用愉快!

  public static List<String> stringToCharacterWithCombiningChars(String fullText) {
    Pattern splitWithCombiningChars = Pattern.compile("(\\p{M}+|\\P{M}\\p{M}*)"); // {M} is any kind of 'mark' http://stackoverflow.com/questions/29110887/detect-any-combining-character-in-java/29111105
    Matcher matcher = splitWithCombiningChars.matcher(fullText);
    ArrayList<String> outGoing = new ArrayList<>();
    while(matcher.find()) {
      outGoing.add(matcher.group());
    }
    return outGoing;
  }

如果对读者有价值,可以附上相应的单元测试代码:https://gist.github.com/rdp/0014de502f37abd64ffd


1

@lenz的回答涵盖了大部分代码点,但有些缺失。下面是通过处理名称列表文件发现的一些范围列表。一些代码点在名称中带有COMBINING,但并不是组合字符,例如Combining Grapheme Joiner (CGJ, 0x34f) [wiki]。正如维基百科文章所引用的:

它的名称是错误的,并且不能描述其功能;该字符不连接字形。它的目的是分隔不应被视为二元组的字符

在处理列表时,找到了以下范围(和字符)。请注意,与lenz的列表略有不同的那些用感叹号(!)标注。通常,范围略微偏离,例如因为其中一个字符不在范围内,因此范围被“分成两个”:

  0x300 -   0x34e  !
  0x350 -   0x36f  !
  0x483 -   0x487  !
  0x591 -   0x5bd  !
  0x5bf            !
  0x5c1 -   0x5c2  !
  0x5c4 -   0x5c5  !
  0x5c7            !
  0x610 -   0x61a  !
  0x64b -   0x65f  !
  0x670            !
  0x6d6 -   0x6dc  !
  0x6df -   0x6e4  !
  0x6e7 -   0x6e8  !
  0x6ea -   0x6ed  !
  0x711            !
  0x730 -   0x74a  !
  0x7eb -   0x7f3
  0x816 -   0x819  !
  0x81b -   0x823  !
  0x825 -   0x827  !
  0x829 -   0x82d  !
  0x859 -   0x85b  !
  0x8d4 -   0x8e1  !
  0x8e3 -   0x8ff  !
  0x93c            !
  0x94d            !
  0x951 -   0x954  !
  0x9bc            !
  0x9cd            !
  0xa3c            !
  0xa4d            !
  0xabc            !
  0xacd            !
  0xb3c            !
  0xb4d            !
  0xbcd            !
  0xc4d            !
  0xc55 -   0xc56  !
  0xcbc            !
  0xccd            !
  0xd4d            !
  0xdca            !
  0xe38 -   0xe3a  !
  0xe48 -   0xe4b  !
  0xeb8 -   0xeb9  !
  0xec8 -   0xecb  !
  0xf18 -   0xf19  !
  0xf35            !
  0xf37            !
  0xf39            !
  0xf71 -   0xf72  !
  0xf74            !
  0xf7a -   0xf7d  !
  0xf80            !
  0xf82 -   0xf84  !
  0xf86 -   0xf87  !
  0xfc6            !
 0x1037            !
 0x1039 -  0x103a  !
 0x108d            !
 0x135d -  0x135f  !
 0x1714            !
 0x1734            !
 0x17d2            !
 0x17dd            !
 0x18a9            !
 0x1939 -  0x193b  !
 0x1a17 -  0x1a18  !
 0x1a60            !
 0x1a75 -  0x1a7c  !
 0x1a7f
 0x1ab0 -  0x1abd  !
 0x1b34            !
 0x1b44            !
 0x1b6b -  0x1b73
 0x1baa -  0x1bab  !
 0x1be6            !
 0x1bf2 -  0x1bf3  !
 0x1c37            !
 0x1cd0 -  0x1cd2  !
 0x1cd4 -  0x1ce0  !
 0x1ce2 -  0x1ce8  !
 0x1ced            !
 0x1cf4            !
 0x1cf8 -  0x1cf9  !
 0x1dc0 -  0x1df5  !
 0x1dfb -  0x1dff  !
 0x20d0 -  0x20dc  !
 0x20e1            !
 0x20e5 -  0x20f0  !
 0x2cef -  0x2cf1
 0x2d7f            !
 0x2de0 -  0x2dff
 0x302a -  0x302f  !
 0x3099 -  0x309a
 0xa66f            !
 0xa674 -  0xa67d  !
 0xa69e -  0xa69f  !
 0xa6f0 -  0xa6f1
 0xa806            !
 0xa8c4            !
 0xa8e0 -  0xa8f1
 0xa92b -  0xa92d  !
 0xa953            !
 0xa9b3            !
 0xa9c0            !
 0xaab0            !
 0xaab2 -  0xaab4  !
 0xaab7 -  0xaab8  !
 0xaabe -  0xaabf  !
 0xaac1            !
 0xaaf6            !
 0xabed            !
 0xfb1e            !
 0xfe20 -  0xfe2f  !
0x101fd
0x102e0            !
0x10376 - 0x1037a  !
0x10a0d            !
0x10a0f            !
0x10a38 - 0x10a3a  !
0x10a3f            !
0x10ae5 - 0x10ae6  !
0x11046            !
0x1107f            !
0x110b9 - 0x110ba  !
0x11100 - 0x11102  !
0x11133 - 0x11134  !
0x11173            !
0x111c0            !
0x111ca            !
0x11235 - 0x11236  !
0x112e9 - 0x112ea  !
0x1133c            !
0x1134d            !
0x11366 - 0x1136c  !
0x11370 - 0x11374  !
0x11442            !
0x11446            !
0x114c2 - 0x114c3  !
0x115bf - 0x115c0  !
0x1163f            !
0x116b6 - 0x116b7  !
0x1172b            !
0x11c3f            !
0x16af0 - 0x16af4  !
0x16b30 - 0x16b36  !
0x1bc9e            !
0x1d165 - 0x1d169
0x1d16d - 0x1d172
0x1d17b - 0x1d182
0x1d185 - 0x1d18b
0x1d1aa - 0x1d1ad
0x1d242 - 0x1d244
0x1e000 - 0x1e006  !
0x1e008 - 0x1e018  !
0x1e01b - 0x1e021  !
0x1e023 - 0x1e024  !
0x1e026 - 0x1e02a  !
0x1e8d0 - 0x1e8d6  !
0x1e944 - 0x1e94a  !

这将导致总共814个代码点。


使用Java的\p{M}正则表达式针对Unicode库中的每个字符,我已经得到了2957个标记。这包括了像天城体元音字符等各种字符。 - SethWhite
@SethWhite:但是合并的定义不等于M(甚至不等于Mn):https://wiki.squeak.org/squeak/6257 例如,THAANA ABAFILI 的合并类为0。 - Willem Van Onsem
名字列表的信息有限。例如,1E944是“ADLAM ALIF LENGTHENER”,没有线索表明它是一个组合字符还是其他类型的字符。我想你可能已经参考了其他Unicode文件? - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接