pinyinUtil.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. 
  2. /**
  3. * 汉字与拼音互转工具,根据导入的字典文件的不同支持不同
  4. * 对于多音字目前只是将所有可能的组合输出,准确识别多音字需要完善的词库,而词库文件往往比字库还要大,所以不太适合web环境。
  5. * @start 2016-09-26
  6. * @last 2016-09-29
  7. */
  8. ;(function(global, factory) {
  9. if (typeof module === "object" && typeof module.exports === "object") {
  10. module.exports = factory(global);
  11. } else {
  12. factory(global);
  13. }
  14. })(typeof window !== "undefined" ? window : this, function(window) {
  15. var toneMap =
  16. {
  17. "ā": "a1",
  18. "á": "a2",
  19. "ǎ": "a3",
  20. "à": "a4",
  21. "ō": "o1",
  22. "ó": "o2",
  23. "ǒ": "o3",
  24. "ò": "o4",
  25. "ē": "e1",
  26. "é": "e2",
  27. "ě": "e3",
  28. "è": "e4",
  29. "ī": "i1",
  30. "í": "i2",
  31. "ǐ": "i3",
  32. "ì": "i4",
  33. "ū": "u1",
  34. "ú": "u2",
  35. "ǔ": "u3",
  36. "ù": "u4",
  37. "ü": "v0",
  38. "ǖ": "v1",
  39. "ǘ": "v2",
  40. "ǚ": "v3",
  41. "ǜ": "v4",
  42. "ń": "n2",
  43. "ň": "n3",
  44. "": "m2"
  45. };
  46. var dict = {}; // 存储所有字典数据
  47. var pinyinUtil =
  48. {
  49. /**
  50. * 解析各种字典文件,所需的字典文件必须在本JS之前导入
  51. */
  52. parseDict: function()
  53. {
  54. // 如果导入了 pinyin_dict_firstletter.js
  55. if(window.pinyin_dict_firstletter)
  56. {
  57. dict.firstletter = pinyin_dict_firstletter;
  58. }
  59. // 如果导入了 pinyin_dict_notone.js
  60. if(window.pinyin_dict_notone)
  61. {
  62. dict.notone = {};
  63. dict.py2hz = pinyin_dict_notone; // 拼音转汉字
  64. for(var i in pinyin_dict_notone)
  65. {
  66. var temp = pinyin_dict_notone[i];
  67. for(var j=0, len=temp.length; j<len; j++)
  68. {
  69. if(!dict.notone[temp[j]]) dict.notone[temp[j]] = i; // 不考虑多音字
  70. }
  71. }
  72. }
  73. // 如果导入了 pinyin_dict_withtone.js
  74. if(window.pinyin_dict_withtone)
  75. {
  76. dict.withtone = {}; // 汉字与拼音映射,多音字用空格分开,类似这种结构:{'大': 'da tai'}
  77. var temp = pinyin_dict_withtone.split(',');
  78. for(var i=0, len = temp.length; i<len; i++)
  79. {
  80. // 这段代码耗时28毫秒左右,对性能影响不大,所以一次性处理完毕
  81. dict.withtone[String.fromCharCode(i + 19968)] = temp[i]; // 这里先不进行split(' '),因为一次性循环2万次split比较消耗性能
  82. }
  83. // 拼音 -> 汉字
  84. if(window.pinyin_dict_notone)
  85. {
  86. // 对于拼音转汉字,我们优先使用pinyin_dict_notone字典文件
  87. // 因为这个字典文件不包含生僻字,且已按照汉字使用频率排序
  88. dict.py2hz = pinyin_dict_notone; // 拼音转汉字
  89. }
  90. else
  91. {
  92. // 将字典文件解析成拼音->汉字的结构
  93. // 与先分割后逐个去掉声调相比,先一次性全部去掉声调然后再分割速度至少快了3倍,前者大约需要120毫秒,后者大约只需要30毫秒(Chrome下)
  94. var notone = pinyinUtil.removeTone(pinyin_dict_withtone).split(',');
  95. var py2hz = {}, py, hz;
  96. for(var i=0, len = notone.length; i<len; i++)
  97. {
  98. hz = String.fromCharCode(i + 19968); // 汉字
  99. py = notone[i].split(' '); // 去掉了声调的拼音数组
  100. for(var j=0; j<py.length; j++)
  101. {
  102. py2hz[py[j]] = (py2hz[py[j]] || '') + hz;
  103. }
  104. }
  105. dict.py2hz = py2hz;
  106. }
  107. }
  108. },
  109. /**
  110. * 根据汉字获取拼音,如果不是汉字直接返回原字符
  111. * @param chinese 要转换的汉字
  112. * @param splitter 分隔字符,默认用空格分隔
  113. * @param withtone 返回结果是否包含声调,默认是
  114. * @param polyphone 是否支持多音字,默认否
  115. */
  116. getPinyin: function(chinese, splitter, withtone, polyphone)
  117. {
  118. if(!chinese || /^ +$/g.test(chinese)) return '';
  119. splitter = splitter == undefined ? ' ' : splitter;
  120. withtone = withtone == undefined ? true : withtone;
  121. polyphone = polyphone == undefined ? false : polyphone;
  122. var result = [];
  123. if(dict.withtone) // 优先使用带声调的字典文件
  124. {
  125. var noChinese = '';
  126. for (var i=0, len = chinese.length; i < len; i++)
  127. {
  128. var pinyin = dict.withtone[chinese[i]];
  129. if(pinyin)
  130. {
  131. // 如果不需要多音字,默认返回第一个拼音,后面的直接忽略
  132. // 所以这对数据字典有一定要求,常见字的拼音必须放在最前面
  133. if(!polyphone) pinyin = pinyin.replace(/ .*$/g, '');
  134. if(!withtone) pinyin = this.removeTone(pinyin); // 如果不需要声调
  135. //空格,把noChinese作为一个词插入
  136. noChinese && ( result.push( noChinese), noChinese = '' );
  137. result.push( pinyin );
  138. }
  139. else if ( !chinese[i] || /^ +$/g.test(chinese[i]) ){
  140. //空格,把noChinese作为一个词插入
  141. noChinese && ( result.push( noChinese), noChinese = '' );
  142. }
  143. else{
  144. noChinese += chinese[i];
  145. }
  146. }
  147. if ( noChinese ){
  148. result.push( noChinese);
  149. noChinese = '';
  150. }
  151. }
  152. else if(dict.notone) // 使用没有声调的字典文件
  153. {
  154. if(withtone) console.warn('pinyin_dict_notone 字典文件不支持声调!');
  155. if(polyphone) console.warn('pinyin_dict_notone 字典文件不支持多音字!');
  156. var noChinese = '';
  157. for (var i=0, len = chinese.length; i < len; i++)
  158. {
  159. var temp = chinese.charAt(i),
  160. pinyin = dict.notone[temp];
  161. if ( pinyin ){ //插入拼音
  162. //空格,把noChinese作为一个词插入
  163. noChinese && ( result.push( noChinese), noChinese = '' );
  164. result.push( pinyin );
  165. }
  166. else if ( !temp || /^ +$/g.test(temp) ){
  167. //空格,插入之前的非中文字符
  168. noChinese && ( result.push( noChinese), noChinese = '' );
  169. }
  170. else {
  171. //非空格,关联到noChinese中
  172. noChinese += temp;
  173. }
  174. }
  175. if ( noChinese ){
  176. result.push( noChinese );
  177. noChinese = '';
  178. }
  179. }
  180. else
  181. {
  182. throw '抱歉,未找到合适的拼音字典文件!';
  183. }
  184. if(!polyphone) return result.join(splitter);
  185. else
  186. {
  187. if(window.pinyin_dict_polyphone) return parsePolyphone(chinese, result, splitter, withtone);
  188. else return handlePolyphone(result, ' ', splitter);
  189. }
  190. },
  191. /**
  192. * 获取汉字的拼音首字母
  193. * @param str 汉字字符串,如果遇到非汉字则原样返回
  194. * @param polyphone 是否支持多音字,默认false,如果为true,会返回所有可能的组合数组
  195. */
  196. getFirstLetter: function(str, polyphone)
  197. {
  198. polyphone = polyphone == undefined ? false : polyphone;
  199. if(!str || /^ +$/g.test(str)) return '';
  200. if(dict.firstletter) // 使用首字母字典文件
  201. {
  202. var result = [];
  203. for(var i=0; i<str.length; i++)
  204. {
  205. var unicode = str.charCodeAt(i);
  206. var ch = str.charAt(i);
  207. if(unicode >= 19968 && unicode <= 40869)
  208. {
  209. ch = dict.firstletter.all.charAt(unicode-19968);
  210. if(polyphone) ch = dict.firstletter.polyphone[unicode] || ch;
  211. }
  212. result.push(ch);
  213. }
  214. if(!polyphone) return result.join(''); // 如果不用管多音字,直接将数组拼接成字符串
  215. else return handlePolyphone(result, '', ''); // 处理多音字,此时的result类似于:['D', 'ZC', 'F']
  216. }
  217. else
  218. {
  219. var py = this.getPinyin(str, ' ', false, polyphone);
  220. py = py instanceof Array ? py : [py];
  221. var result = [];
  222. for(var i=0; i<py.length; i++)
  223. {
  224. result.push(py[i].replace(/(^| )(\w)\w*/g, function(m,$1,$2){return $2.toUpperCase();}));
  225. }
  226. if(!polyphone) return result[0];
  227. else return simpleUnique(result);
  228. }
  229. },
  230. /**
  231. * 拼音转汉字,只支持单个汉字,返回所有匹配的汉字组合
  232. * @param pinyin 单个汉字的拼音,可以包含声调
  233. */
  234. getHanzi: function(pinyin)
  235. {
  236. if(!dict.py2hz)
  237. {
  238. throw '抱歉,未找到合适的拼音字典文件!';
  239. }
  240. return dict.py2hz[this.removeTone(pinyin)] || '';
  241. },
  242. /**
  243. * 获取某个汉字的同音字,本方法暂时有问题,待完善
  244. * @param hz 单个汉字
  245. * @param sameTone 是否获取同音同声调的汉字,必须传进来的拼音带声调才支持,默认false
  246. */
  247. getSameVoiceWord: function(hz, sameTone)
  248. {
  249. sameTone = sameTone || false
  250. return this.getHanzi(this.getPinyin(hz, ' ', false))
  251. },
  252. /**
  253. * 去除拼音中的声调,比如将 xiǎo míng tóng xué 转换成 xiao ming tong xue
  254. * @param pinyin 需要转换的拼音
  255. */
  256. removeTone: function(pinyin)
  257. {
  258. return pinyin.replace(/[āáǎàōóǒòēéěèīíǐìūúǔùüǖǘǚǜńň]/g, function(m){ return toneMap[m][0]; });
  259. },
  260. /**
  261. * 将数组拼音转换成真正的带标点的拼音
  262. * @param pinyinWithoutTone 类似 xu2e这样的带数字的拼音
  263. */
  264. getTone: function(pinyinWithoutTone)
  265. {
  266. var newToneMap = {};
  267. for(var i in toneMap) newToneMap[toneMap[i]] = i;
  268. return (pinyinWithoutTone || '').replace(/[a-z]\d/g, function(m) {
  269. return newToneMap[m] || m;
  270. });
  271. }
  272. };
  273. /**
  274. * 处理多音字,将类似['D', 'ZC', 'F']转换成['DZF', 'DCF']
  275. * 或者将 ['chang zhang', 'cheng'] 转换成 ['chang cheng', 'zhang cheng']
  276. */
  277. function handlePolyphone(array, splitter, joinChar)
  278. {
  279. splitter = splitter || '';
  280. var result = [''], temp = [];
  281. for(var i=0; i<array.length; i++)
  282. {
  283. temp = [];
  284. var t = array[i].split(splitter);
  285. for(var j=0; j<t.length; j++)
  286. {
  287. for(var k=0; k<result.length; k++)
  288. temp.push(result[k] + (result[k]?joinChar:'') + t[j]);
  289. }
  290. result = temp;
  291. }
  292. return simpleUnique(result);
  293. }
  294. /**
  295. * 根据词库找出多音字正确的读音
  296. * 这里只是非常简单的实现,效率和效果都有一些问题
  297. * 推荐使用第三方分词工具先对句子进行分词,然后再匹配多音字
  298. * @param chinese 需要转换的汉字
  299. * @param result 初步匹配出来的包含多个发音的拼音结果
  300. * @param splitter 返回结果拼接字符
  301. */
  302. function parsePolyphone(chinese, result, splitter, withtone)
  303. {
  304. var poly = window.pinyin_dict_polyphone;
  305. var max = 7; // 最多只考虑7个汉字的多音字词,虽然词库里面有10个字的,但是数量非常少,为了整体效率暂时忽略之
  306. var temp = poly[chinese];
  307. if(temp) // 如果直接找到了结果
  308. {
  309. temp = temp.split(' ');
  310. for(var i=0; i<temp.length; i++)
  311. {
  312. result[i] = temp[i] || result[i];
  313. if(!withtone) result[i] = pinyinUtil.removeTone(result[i]);
  314. }
  315. return result.join(splitter);
  316. }
  317. for(var i=0; i<chinese.length; i++)
  318. {
  319. temp = '';
  320. for(var j=0; j<max && (i+j)<chinese.length; j++)
  321. {
  322. if(!/^[\u2E80-\u9FFF]+$/.test(chinese[i+j])) break; // 如果碰到非汉字直接停止本次查找
  323. temp += chinese[i+j];
  324. var res = poly[temp];
  325. if(res) // 如果找到了多音字词语
  326. {
  327. res = res.split(' ');
  328. for(var k=0; k<=j; k++)
  329. {
  330. if(res[k]) result[i+k] = withtone ? res[k] : pinyinUtil.removeTone(res[k]);
  331. }
  332. break;
  333. }
  334. }
  335. }
  336. // 最后这一步是为了防止出现词库里面也没有包含的多音字词语
  337. for(var i=0; i<result.length; i++)
  338. {
  339. result[i] = result[i].replace(/ .*$/g, '');
  340. }
  341. return result.join(splitter);
  342. }
  343. // 简单数组去重
  344. function simpleUnique(array)
  345. {
  346. var result = [];
  347. var hash = {};
  348. for(var i=0; i<array.length; i++)
  349. {
  350. var key = (typeof array[i]) + array[i];
  351. if(!hash[key])
  352. {
  353. result.push(array[i]);
  354. hash[key] = true;
  355. }
  356. }
  357. return result;
  358. }
  359. pinyinUtil.parseDict();
  360. pinyinUtil.dict = dict;
  361. window.pinyinUtil = pinyinUtil;
  362. });