/** * @name CeL function for 繁簡中文字詞彙轉換。 * * TODO:
* 在量大的時候,此方法速度頗慢。 Using Map()
* words conversion * * @fileoverview 本檔案包含了繁體/簡體中文轉換的 functions。 * @example // 在非 Windows 平台上避免 fatal 錯誤。 CeL.env.ignore_COM_error = true; // load module for CeL.CN_to_TW('简体') CeL.run('extension.zh_conversion', function() { var text = CeL.CN_to_TW('简体中文文字'); CeL.CN_to_TW.file('from.htm', 'to.htm', 'utf-8'); }); * @see https://github.com/BYVoid/OpenCC https://zhconvert.org/ * https://en.wiktionary.org/wiki/Module:zh * @since 2014/6/17 22:39:16 */ 'use strict'; // -------------------------------------------------------------------------------------------- // 不採用 if 陳述式,可以避免 Eclipse JSDoc 與 format 多縮排一層。 typeof CeL === 'function' && CeL.run({ // module name name : 'extension.zh_conversion', require : 'data.|data.Convert_Pairs.|application.OS.Windows.file.', // 設定不匯出的子函式。 no_extend : 'generate_converter', // 為了方便格式化程式碼,因此將 module 函式主體另外抽出。 code : module_code }); function module_code(library_namespace) { // requiring var Convert_Pairs = library_namespace.data.Convert_Pairs; /** * null module constructor * * @class 中文繁簡轉換的 functions */ var _// JSDT:_module_ = function() { // null module constructor }; /** * for JSDT: 有 prototype 才會將之當作 Class */ _// JSDT:_module_ .prototype = {}; // ------------------------------------------------------------------------ // new RegExp(key, REPLACE_FLAG); var REPLACE_FLAG = undefined, // using BYVoid / OpenCC 開放中文轉換 (Open Chinese Convert) table. // https://github.com/BYVoid/OpenCC/tree/master/data/dictionary dictionary_base = library_namespace.get_module_path(this.id, 'OpenCC' + library_namespace.env.path_separator); // console.log('dictionary_base: ' + dictionary_base); function Converter(options) { // console.log(options); // e.g., .files, .file_filter Object.assign(this, options); // console.trace(this.files); } function Converter_initialization(options) { // console.trace(this.files); function item_processor(item) { var matched = item.match(/^([^\t]+)\t([^\t]+)$/); if (matched) { if (!matched[1].trim()) return; if (/ [^\t]+$/.test(matched[2])) { if (matched[2].startsWith(matched[1] + ' ')) { // console.log('詞有疑意: ' + item); // 但像是"小丑"之類的還是必須保留。 // return; } // 必須置換,那就換個最常用的。 return item.replace(/ +[^\t]+$/, ''); } } return item; } function corrections_item_processor(item, options) { var matched = item.match(/^-([^\t\n]{1,30})$/); if (!matched) { return item; } remove_key_hash[matched[1]] = options.path; return ''; } function to_full_file_path(file_path) { return /[\\\/]/.test(file_path) ? file_path : dictionary_base + file_path + '.txt'; } this.conversions = []; this.files.map(function(file_list) { var _options = { file_filter : this.file_filter, // no_the_same_key_value : !Array.isArray(file_list) // || file_list.length < 2, item_processor : item_processor, // 在開始轉換之後就不會再修改辭典檔,因此可移除 .pair_Map。 may_remove_pair_Map : !options || !options.mode }; if (!Array.isArray(file_list)) file_list = [ file_list ]; _options.path = file_list // 載入 resources。 .map(function(file_path) { if (typeof file_path === 'string') return to_full_file_path(file_path); // assert: library_namespace.is_Object(file_path) var __options = file_path; // e.g., for .remove_comments if (!__options.file_path && !__options.path // && __options.file_name) { __options.file_path // = to_full_file_path(__options.file_name); } // assert: !!__options.file_path === true return __options; }); var convert_Pairs = new Convert_Pairs(null, _options); if (convert_Pairs.pair_Map.size > 0) { // console.trace([ convert_Pairs.pair_Map.get('猜拳斗酒') ]); this.conversions.push(convert_Pairs); } }, this); delete this.file_filter; // console.log(this.conversions); // console.trace(this.conversions[0].pair_Map.size); // -------------------------------------- if (this.corrections) { // keys_to_remove var remove_key_hash = Object.create(null); // this.conversions: 手動修正表。提供自行更改的功能。 this.conversions.push(new Convert_Pairs(null, { path : dictionary_base.replace(/[^\\\/]+[\\\/]$/, this.corrections), item_processor : corrections_item_processor, remove_comments : true })); delete this.corrections; if (!library_namespace.is_empty_object(remove_key_hash)) { this.conversions.forEach(function(conversion) { // console.trace(conversion.pair_Map.get('猜拳斗酒')); if (false && conversion.pair_Map.get('猜拳斗酒')) { console.log(conversion); throw conversion.pair_Map.get('猜拳斗酒'); } conversion.remove(remove_key_hash, { remove_matched_path : true }); }); // free remove_key_hash = null; } } // 設定事前轉換表。 if (this.prefix_conversions) { this.conversions.unshift(new Convert_Pairs(this.prefix_conversions, { flags : this.flags || REPLACE_FLAG })); delete this.prefix_conversions; } // 設定事後轉換表。 if (this.postfix_conversions) { this.conversions.push(new Convert_Pairs(this.postfix_conversions, { flags : this.flags || REPLACE_FLAG })); delete this.postfix_conversions; } // console.trace(this('签')); } // convert text function convert_text(text, options) { if (!this.conversions) { this.initialization(options); } // 事前轉換表。 if (options && options.prefix_conversions) { text = (new Convert_Pairs(options.prefix_conversions, { flags : options.flags || REPLACE_FLAG })).convert(text); } var for_each_conversion; if (!options) { } else if (options.mode === 'word') { // 僅轉換完全相符的詞彙 key。 for_each_conversion = function(_text, conversion) { // console.log(conversion.pair) var convert_to = conversion.get_value(_text); return typeof convert_to === 'string' ? convert_to : _text; }; } else if (false && options.mode === 'word_first') { // 輸入單一詞彙時使用,以期加快速度...可惜沒有。 // node.js: 直接開 `conversion.convert(text)` 速度相同,且還包含 // .special_keys_Map 的轉換,較完整。 for_each_conversion = function(_text, conversion) { var convert_to = conversion.get_value(_text); return typeof convert_to === 'string' ? convert_to : conversion .convert(_text, options); }; } text = this.conversions.reduce(for_each_conversion || function(_text, conversion) { return conversion.convert(_text, options); }, text); // console.trace(text); if (!(this.max_convert_word_length >= 0)) { this.max_convert_word_length = this.conversions.reduce(function( length, conversion) { return Math.max(length, conversion.pair_Map_by_length.length); }, 0); // console.trace(this); if (this['interface']) this['interface'].max_convert_word_length = this.max_convert_word_length; } // 事後轉換表。 if (options && options.postfix_conversions) { text = (new Convert_Pairs(options.postfix_conversions, { flags : options.flags || REPLACE_FLAG })).convert(text); } return text; } // convert text file function convert_file(from, to, target_encoding) { var text = library_namespace.get_file(from); text = this(text); library_namespace.write_file(to, text, target_encoding); } Object.assign(Converter.prototype, { initialization : Converter_initialization, convert : convert_text, file : convert_file }); Converter.options = { CN_to_TW : { // 事前事後轉換表須事先設定。 // 不可以 Object.assign(CeL.CN_to_TW.prefix_conversions = {}, {}) // 來新增事前轉換表。 // prefix_conversions : {}, // postfix_conversions : {}, files : [ [ 'STPhrases', 'STCharacters', // 以 generate_additional_table.js 合併新同文堂和 ConvertZZ 的辭典檔。 'additional.to_TW.auto-generated', // 後來的會覆蓋前面的。 { file_name : 'additional.to_TW', remove_comments : true } ], // ------------------------------------------------------ // ** 下面的是上面詞彙與單字轉換後的再轉換。 [ 'TWPhrasesIT', // ↑ TWPhrasesIT.txt 有許多常用詞彙,在 corrections_to_TW.txt 取消。 'TWPhrasesName', 'TWPhrasesOther', // 若要篩選或增減 conversion files,可參考範例: // start_downloading() @ CeL.application.net.work_crawler.task { file_name : 'additional.to_TW.phrases', remove_comments : true } ], // https://github.com/BYVoid/OpenCC/blob/master/data/config/s2twp.json 'TWVariants' ], corrections : 'corrections_to_TW.txt' }, TW_to_CN : { // 事前事後轉換表須事先設定。 // prefix_conversions : {}, // postfix_conversions : {}, // https://github.com/BYVoid/OpenCC/blob/master/data/config/tw2s.json // https://github.com/BYVoid/OpenCC/blob/master/node/dicts.gypi files : [ 'TWVariantsRevPhrases', [ 'TSPhrases', 'TSCharacters', // 以 generate_additional_table.js 合併新同文堂和 ConvertZZ 的辭典檔。 'additional.to_CN.auto-generated', // 後來的會覆蓋前面的。 { file_name : 'additional.to_CN', remove_comments : true } ] ], // ------------------------------------------------------ // ** 下面的是上面詞彙與單字轉換後的再轉換。 corrections : 'corrections_to_CN.txt' } }; // ------------------------------------------------------------------------ function set_as_default(method_name, method) { library_namespace[method_name] = _[method_name] = method; } function generate_converter(type, options) { options = library_namespace.setup_options(options); if (!(type in Converter.options)) { library_namespace.error( // 'generate_converter: Invalid type: ' + type); return; } var converter = new Converter(Object.assign(Object .clone(Converter.options[type]), options)); var converter_interface = converter.convert.bind(converter); converter['interface'] = converter_interface; if (options.set_as_default) { set_as_default(type, converter_interface); } return converter_interface; } var cecc; function using_CeCC(options) { // 前置處理。 options = library_namespace.setup_options(options); if (cecc && !options.force_using_cecc) { library_namespace.debug('CeCC loaded.'); return true; } var CeCC; try { CeCC = require('cecc'); } catch (e) { try { // base_path/CeJS/ce.js // base_path/Chinese_converter/Chinese_converter.js CeCC = require(library_namespace.simplify_path( // library_namespace.get_module_path().replace(/[^\\\/]*$/, '../Chinese_converter/Chinese_converter.js'))); } catch (e) { } } if (!CeCC) { return; } if (!options.try_LTP_server) { cecc = new CeCC(options); return setup_CeCC_methods(options); } return CeCC.has_LTP_server(options).then(function(LTP_URL) { if (!LTP_URL) return; options.LTP_URL = LTP_URL; cecc = new CeCC(options); cecc.is_asynchronous = true; return setup_CeCC_methods(options); }); } function setup_CeCC_methods(options) { library_namespace.info('using_CeCC: Using CeCC (' + (cecc.is_asynchronous ? 'asynchronous' : 'synchronous') + ' version) to convert language.'); var methods = { CN_to_TW : 'to_TW', TW_to_CN : 'to_TW' }; for ( var method_name in methods) { var cecc_name = methods[method_name]; if (!cecc.is_asynchronous) cecc_name += '_sync'; var method = cecc[cecc_name].bind(cecc); method.is_CeCC = true; method.cecc = cecc; if (cecc.is_asynchronous) method.is_asynchronous = true; set_as_default(method_name, method); } return true; } // ------------------------------------------------------------------------ // export _.generate_converter = generate_converter; _.using_CeCC = using_CeCC; try { var OpenCC = require('opencc'); // Load the default Simplified to Traditional config _.CN_to_TW = new OpenCC('s2t.json'); // Sync API _.CN_to_TW = _.CN_to_TW.convertSync.bind(_.CN_to_TW); _.TW_to_CN = new OpenCC('t2s.json'); _.TW_to_CN = _.TW_to_CN.convertSync.bind(_.TW_to_CN); } catch (e) { // CeL.application.net.work_crawler.task will re-generate the functions! generate_converter('CN_to_TW', { set_as_default : true }); generate_converter('TW_to_CN', { set_as_default : true }); } // Warning: require('cecc') will overwrite CeL.CN_to_TW, CeL.TW_to_CN ! return (_// JSDT:_module_ ); }