关于《汉字转拼音的浏览器端实现》一文的补充与错漏分析

zsx in 代码分析 / 1 / 5360

《汉字转拼音的浏览器端实现》原文:https://zhuanlan.zhihu.com/p/29813596?utm_source=qq&utm_medium=social

这篇文章里的方法论有问题、实现过程有问题、结论也有问题。


看到这篇文章的时候,让我想起了当年写ASP的日子。现在还可以找到《ASP获取汉字拼音(使用Scripting.Dictionary)》这篇文章:http://blog.csdn.net/wlwqw/article/details/1810601

注意《ASP》一文内的以下两行代码:

dic.add "a",-20319 
dic.add "zuo",-10254

我们知道:

  1. -20319 是 B0A1 的十进制补码表示,-10254 代表 D7F2。

  2. ASP 是运行于 Windows 平台的。

  3. 中文 Windows 下,ANSI 编码使用实际使用 GB18030 / GBK / GB2312 (根据操作系统版本决定,但都兼容 GB23121)编码保存。

  4. GB2312 编码表中,16区从 B0A0 开始,55区的结束位置是 D7FF 2

  5. GB2312 的 16-55 区为一级汉字,按拼音排序,共有 3755 个字。

正是受益于GB2312的设计,才能以简短的代码实现拼音排序。如果我们输入的是 GB2312 的二级汉字,或者是 GB18030 里的其它汉字,或者干脆用 UTF-8 输入一个汉字,这份代码就查不出其拼音了。

回到JavaScript。JavaScript 中的中文字符是以 UTF-16 编码保存的,所以通过比较 UTF-16 存储的位置就可以了吗?答案是当然不可以了。仅以文章中的“网易公司”四个字的“网”字为例,其 UTF-16 编码是 7F51,但 7F50 是“罐”,7F52是“罒”。这一些文字在Unicode的中日韩统一表意文字区域(4E00 – 9FEA),置放于基本多文种平面;它们的排序顺序是依照康熙字典的部首/笔画顺序3。所以,需要文章里说的localeCompare


于是,我们回到了《汉字》原文。

先吐槽一个点:

还有比如“谁”,浏览器把它排在了“shei”区块,但我们一般念“shui”,这个字更加特殊,因为只有这么一个字念“shei”,所以可以把“shei”这个读音从映射表中删除,从理论上减少比对时的查找次数。

谁shéi,又音shuí4

读字读半边,这种例子有很多,比如“碡”,念成了“du”(正确的读音是 zhóu),“聒”念成了“gua”(正确的读音是 guō)
纯粹乱读,Safari 中有很多,比如“鼰”,念成了“nian”(正确的读音是 jú),“罉”,念成了“cang”(正确的读音是 chēng),“伝”,念成了“chuan”(正确的读音是 yún),“枡”,念成了“dou”(正确的读音是 shēng)

引用 @赵炎 的回复:

1、芎:《廣韻》去宮切,推導現代漢語 qiong1。
2、还有所谓“读字读半边”
2.1、碡本身就是多音字,有 du2、zhu2 两种读法,读 zhou2 显然是字典收录了白读音。
2.2、聒半边是耳和舌,请问哪半边读 gua?
3、纯粹乱读
3.1、伝就是傳,听说是纯粹乱读,应该读成 yun2
3.2、罉是方言词,读如 cang1,推导汉语 cheng1,这也算乱读?


开始正题。

在 ECMA-262 标准里的第 21.1.3.10 节5,说这个 String.prototype.localeCompare 需要按照 ECMA‑402 标准来实现。在 ECMA-402 标准里的 10.3.4 节Collator Compare Functions6里说,这个函数应由实现定义(The two Strings are compared in an implementation-defined fashion)。所以不如看代码。


先从不跨平台的浏览器开始。

Edge的Chakra里面调用的是Windows API CompareStringEx (lib/Runtime/Library/InJavascript/Intl.js#L868 -> lib/Runtime/Library/IntlEngineInterfaceExtensionObject.cpp#L1320)。刚才已经说过,Windows使用GB.*编码。

作者举例“有些多音字选择了不常见的读音,比如“沈”,在 Chrome 中是“chen”,而不是常见的“shen”,又比如“呵”,在 Safari 中念“a””,我们就用“沈”来做个测试。

image.png

按照原文说法,这个字就应该念“shen”。如图,因为GBK下按照拼音排序它就在“shen”这一带。

image.png

image.png


看完Edge,我们还是回到Chrome和Safari。作者说:“Chrome 的中文语言包制作者应该是一位在国外长大的中国人,Safari 的中文语言包制作应该是一位主修中文专业的外国人。”

嗯,事实证明,这根本不是什么“中文语言包”的锅,中文语言包根本不管这个……这锅,得推到 Unicode 头上。


V8里面调用icu::compare两个UnicodeStringjs/intl.js#2092 -> runtime/runtime-intl.cc#L638)。按照 ICU 的文档7compare对标的就是CompareString这个Windows API,不过实际上他的效果还是有差距。以下代码即为一个演示,这就是按照作者的逻辑,“沈”在“沉”之前的原因。(不要在意代码缩进)


int main()
{
  UnicodeString s[] = { "沉", "神", "沈", "审" };
  uint32_t listSize = sizeof(s) / sizeof(s[0]);
  UErrorCode status = U_ZERO_ERROR;
  Collator *coll = Collator::createInstance(Locale("zh", "CN"), status);
  uint32_t i, j;
  if (U_SUCCESS(status)) {
    for (i = listSize - 1; i >= 1; i--) {
      for (j = 0; j<i; j++) {
        if (coll->compare(s[j], s[j + 1]) != UCOL_LESS) {
          swap(s[j], s[j + 1]);
        }
      }
    }
    delete coll;
  }
  ofstream file;
  file.open("z:\\3.txt");
  for (i = 0; i < listSize; i++) {
    file << s[i] ;
  }
  file.close();
  //std::cin.get();
    return 0;
}

image.png


其在调用compare时,会转进到CollationCompare::compareUpToQuaternary,最后转进到reorder


image.png


reorder的内容是从reorderTable里面拿数据重新比较,而数据是从Collator::createInstance(Locale("zh", "CN"), status);这一行开始就已经读取了的。最后转进到zh.txt


image.png


打开这个txt就会发现,如图所示。

image.png

这个“沈”在“pinyin”里就是摆在这个位置。作者说的念错音的“碡”“聒”也都可以看到是icu这个表的问题。然后,这个表的默认值是“pinyin”。

我们再看看 Safari,直接看 WebKit 里的 JavaScriptCore。懒得编译 WebKit,直接空口调代码。

有两个地方实现了 ECMA-402 的 10.3.4,一处在Source/JavaScriptCore/runtime/IntlCollatorPrototype.cpp#L106,还有一处在 Source/JavaScriptCore/runtime/IntlCollator.cpp#L415。不管怎么着,这俩调用的还是 ICU。

image.png

那为什么原作者说?“纯粹乱读,Safari 中有很多,比如“鼰”,念成了“nian”(正确的读音是 jú),“罉”,念成了“cang”(正确的读音是 chēng),“伝”,念成了“chuan”(正确的读音是 yún),“枡”,念成了“dou”(正确的读音是 shēng)”


也许是因为 Safari 用的不是 pinyin,而是 zhuyin。

image.png

image.pngimage.png


那为啥呢?排查了一下不是copyDefaultLocale()的问题,没调试器,不想查.jpg


image.png


最后 Firefox。Firefox 的国际化处理也是用的 ICU,所以当然啦,它也一样……


……是不是忘了什么?

在前年,我被“𥊍”字坑过一次:https://blog.zsxsoft.com/post/16。这玩意和上面的字所在的平面都不同,属于中日韩统一表意文字扩充B的字符。在 ICU 的zh.txt里可没有这玩意哦。

image.png

事情的发展就变成这样了。


文末总结:

  1. 根据区域取得拼音不可靠。原因包括:

    1. Edge 和 IE11 下只能取得 GB2312 的一级汉字的拼音。

    2. Safari 使用了注音来排序,而不是拼音。

    3. ICU 库只含有GBK字符,不含GB18030字符。

    4. Chrome / Firefox使用的 ICU 库的拼音排序可能有毛病。

  2. 当然,只考虑Chrome+修复部分拼音的话,还是在一定意义上有用的。



参考资料:

  • [1] GB 18030-2000, 信息技术中文编码字符集[S].

  • [2] GB 2312-80, 信息交换用汉字编码字符集·基本集[S].

  • [3] 李宝安, 李燕, 孟庆昌.中文信息处理技术:原理与应用[M].北京:清华大学出版社,2005:26.

  • [4] 《普通话异读词审音表(修订稿)》征求意见公告[EB/OL].http://www.moe.edu.cn/jyb_xwfb/s248/201606/t20160606_248272.html.2016.

  • [5] ECMA-262, ECMAScript® 2017 Language Specification[S],2017:588.

  • [6] ECMA-402, ECMAScript® 2017 Internationalization API Specification[S],2017:49.

  • [7] ICU Project[EB/OL].http://userguide.icu-project.org/collation/api.


如果本文对你有帮助,你可以用支付宝支持一下:

Alipay QrCode
qwe7002 at 2017/10/14[回复]
所以我用了一个Python的实现,感觉还不错。他这个算法的确是有问题的,可以说思路上都有点问题。。。