pinyinUtil.js 14 KB

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