cejs
Version:
A JavaScript module framework that is simple to use.
782 lines (684 loc) • 25.4 kB
JavaScript
/**
* @name CeL function for 繁簡中文字詞彙轉換。
*
* TODO:<br />
* 在量大的時候,此方法速度頗慢。 Using Map()<br />
* words conversion
*
* @fileoverview 本檔案包含了繁體/簡體中文轉換的 functions。
* @example <code>
// 在非 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');
});
</code>
* @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);
}
/**
* 正規化 item,轉成純粹 `from "\t" to`。
*
* @param {String}text
* text line
*
* @return {String} `from "\t" to`
*/
function default_item_processor(item) {
var matched = item.match(/^([^\t]+)\t([^\t]+)$/);
if (matched) {
var trimmed_convert_from = matched[1].trim();
if (!trimmed_convert_from) {
library_namespace
.warn('default_item_processor: Skip line without convert from: '
+ item);
return;
}
if (trimmed_convert_from !== matched[1]) {
library_namespace.warn('default_item_processor: 前後有空白: ['
+ matched[1] + '] @ ' + item);
}
// 當from含有空白字元時不當作有不同選項可用。
// e.g., "第三百二十五章 面" → "第三百二十五章 麵"
var splitted = !/\s/.test(trimmed_convert_from)
&& matched[2].split(/\s+/);
// 當to含有空白字元時表示有不同選項可用。
if (splitted && splitted.length > 1
// e.g., "方便面" → "泡麵 速食麵"
// && matched[1].length === splitted[0].length
) {
if (matched[1] === splitted[0]) {
// console.log('詞有疑意: ' + item);
// 但像是"小丑"之類的還是必須保留。
// return;
}
// 必須置換,那就換個最常用的。
item = matched[1] + '\t' + splitted[0];
}
}
return item;
}
/** @inner */
function to_full_file_path(file_path) {
return /[\\\/]/.test(file_path) ? file_path : dictionary_base
+ file_path + '.txt';
}
/**
* @param {Object|Array}conversion_group_options
* conversion group configuration
* @param {Object}[options]
* 附加參數/設定選擇性/特殊功能與選項
*
* @example <code>
CeL.zh_conversion.CN_to_TW[CeL.zh_conversion.KEY_converter].add_conversions(file_path)
</code>
* @alias add_conversion_group
*/
function add_conversions(/* const */conversion_group_options, options) {
/** {Boolean}正在初始化。 */
var is_initializing = this.conversions
// assert: Array.isArray(this.conversions
&& this.conversions.length < this.files.length;
// insert at
var sort_index = conversion_group_options.sort;
// 警告: .add_conversions({sort:}) 必須配合 Converter.options @
// CeL.zh_conversion!
// assert: this.files[] 一一對應 this.conversions[]。
if (sort_index && typeof sort_index === 'string') {
// sort_name → sort_index
this.files.some(function(__conversion_group_options, index) {
if (__conversion_group_options.sort_name === sort_index
// 只取第一個 single_conversion_options,
|| Array.isArray(__conversion_group_options)
//
&& __conversion_group_options[0]
// 當作 conversion_group_options.sort_name。
&& __conversion_group_options[0].sort_name === sort_index) {
library_namespace.debug('.sort_name ' + sort_index + '→'
+ index + ': ' + __conversion_group_options, 1,
'add_conversions');
sort_index = index;
return true;
}
});
}
if (sort_index >= 0) {
if (!is_initializing) {
// 將 conversion_group_options 插入 this.files[] 以確保與
// this.conversions[] 一一對應。
if (!Array.isArray(this.files[sort_index]))
this.files[sort_index] = [ this.files[sort_index] ];
this.files[sort_index].push(conversion_group_options);
} else if (this.files[sort_index] !== conversion_group_options) {
library_namespace
.error('add_conversions: 初始化過程中設定了不相容的 sort_index: '
+ sort_index + ' ('
+ JSON.stringify(conversion_group_options)
+ ')');
throw new Error('add_conversions: 初始化過程中設定了不相容的 sort_index: '
+ sort_index);
}
} else {
if (sort_index !== undefined
&& (sort_index >= 0 || typeof sort_index === 'string')) {
library_namespace.error('add_conversions: sort not found: '
+ sort_index);
}
if (!is_initializing) {
this.files.push(conversion_group_options);
}
}
// console.trace(this);
if (!this.conversions) {
library_namespace.debug('尚未執行 Converter_initialization()。'
+ '將在 Converter_initialization() 一起載入。', 2,
'add_conversions');
// console.trace(conversion_group_options);
return;
}
// --------------------------------------------------------------------
// is_initializing || 為初始化完後的補充。
// console.trace(conversion_group_options);
var file_path_list = conversion_group_options;
if (library_namespace.is_Object(file_path_list)) {
if (!file_path_list.file_path) {
library_namespace
.error('add_conversions: Invalid file_path_list: '
+ JSON.stringify(file_path_list));
return;
}
file_path_list = file_path_list.file_path;
}
if (!Array.isArray(file_path_list))
file_path_list = [ file_path_list ];
// console.trace(file_path_list);
/**
* 用於 new Convert_Pairs(null, convert_pairs_path_options); 或
* use_conversion.add_path(convert_pairs_path_options);
*/
var convert_pairs_path_options = {
// 這邊不能先設定欲載入的 resources。
// path : file_path_list,
file_filter : this.file_filter,
remove_comments : conversion_group_options.remove_comments,
// no_the_same_key_value : !Array.isArray(file_path_list)
// || file_path_list.length < 2,
item_processor : default_item_processor,
// 在 convert_text() 開始轉換之後就不會再修改辭典檔,因此可移除 .pair_Map。
may_remove_pair_Map : !options
// @see convert_text(text, options)
|| !options.mode
};
var new_tailored_key_Set = new Set;
new_tailored_key_Set.main_tailored_key = conversion_group_options.tailored_key
// @see work_data.convert_options @
// CeL.application.net.work_crawler.ebook
|| conversion_group_options.work_title
// || conversion_group_options.original_work_title
;
/**
* Build convert_pairs_path_options
*
* @param {Object|String}single_conversion_options
* single conversion configuration
* @returns
*/
function add_single_conversion(single_conversion_options) {
if (typeof single_conversion_options === 'string') {
// Treat single_conversion_options as file_path.
return to_full_file_path(single_conversion_options);
}
// assert: library_namespace.is_Object(single_conversion_options)
if (single_conversion_options.sort_name) {
if (file_path_list.sort_name) {
library_namespace
.error('add_single_conversion: sort_name: '
+ file_path_list.sort_name + '→'
+ single_conversion_options.sort_name);
}
file_path_list.sort_name = single_conversion_options.sort_name;
}
// e.g., for .remove_comments
if (!single_conversion_options.file_path
&& !single_conversion_options.path
&& single_conversion_options.file_name) {
single_conversion_options = library_namespace
.new_options(single_conversion_options);
single_conversion_options.file_path
//
= to_full_file_path(single_conversion_options.file_name);
}
if (!single_conversion_options.file_path) {
// e.g., {"sort_name":"主要繁簡轉換"}
if (!single_conversion_options.sort_name
|| Object.keys(single_conversion_options).length !== 1) {
library_namespace
.error('add_single_conversion: Invalid single_conversion_options: '
+ JSON.stringify(single_conversion_options));
}
return;
}
// ------------------------------------------
var tailored_key = single_conversion_options.tailored_key
|| single_conversion_options.work_title
// || single_conversion_options.original_work_title
|| new_tailored_key_Set.main_tailored_key;
if (tailored_key) {
var tailored_conversion = this.tailored_conversions[tailored_key];
if (!tailored_conversion) {
// initialization an empty {Convert_Pairs}
tailored_conversion = this.tailored_conversions[tailored_key] = new Convert_Pairs(
null, convert_pairs_path_options);
new_tailored_key_Set.add(tailored_key);
}
var tailored_single_conversion_options = single_conversion_options;
single_conversion_options = library_namespace
.new_options(single_conversion_options);
/**
* 警告: 載入特設辭典後無法卸除! 必須在轉換文字時設定
* conversion_group_options.tailored_key 才能使用完整特設辭典。
*
* 但就算沒設定
* conversion_group_options.tailored_key,依然會採用特設辭典中與一般性辭典(Converter.options)沒衝突的部分。
*
* this.tailored_conversions 放置的只有衝突的部分。
*/
single_conversion_options.filter_existing_key_on_set = function(
key, original_value, value) {
var source = Object.create(null);
source[key] = value;
tailored_conversion.add(source,
tailored_single_conversion_options);
return false;
};
}
// ------------------------------------------
return single_conversion_options;
}
convert_pairs_path_options.path = file_path_list.map(
add_single_conversion, this);
// console.trace(convert_pairs_path_options);
var use_conversion
// assert: this.files[] 一一對應 this.conversions[]。
// this.conversions && this.conversions.length >= this.files
// this.conversions = [ {Convert_Pairs}, {Convert_Pairs}, ... ]
= sort_index >= 0 && this.conversions[sort_index];
if (use_conversion && use_conversion.pair_Map_by_length
&& !use_conversion.pair_Map) {
library_namespace.error('add_conversions: '
+ '先前未保留 .pair_Map,忽略 .sort 設定! 這可能造成執行時錯誤!');
// 問題在: this.files[] 一一對應 this.conversions[]
use_conversion = null;
}
if (use_conversion) {
use_conversion.add_path(convert_pairs_path_options);
} else {
use_conversion = new Convert_Pairs(null, convert_pairs_path_options);
if (file_path_list.sort_name)
use_conversion.sort_name = file_path_list.sort_name;
// console.trace(this);
// console.trace(use_conversion);
// console.trace(use_conversion.pair_Map.size);
// 確認有東西才加入此 conversion。
if (use_conversion.pair_Map.size === 0) {
library_namespace.warn('add_conversions: 空的轉換: '
+ JSON.stringify(conversion_group_options));
}
// console.trace([ use_conversion.pair_Map.get('猜拳斗酒') ]);
// 注意: 沒指定 conversion_group_options.sort
// 時,預設會加在最後,其他轉換都完成後才會處理這些轉換。
this.conversions.push(use_conversion);
}
new_tailored_key_Set.forEach(function(tailored_key) {
var tailored_conversion = this.tailored_conversions[tailored_key];
if (tailored_conversion.pair_Map.size === 0) {
// 特設辭典無內容,或已全部納入一般辭典。
delete this.tailored_conversions[tailored_key];
} else {
library_namespace.info('add_conversions: '
//
+ '特色辭典 [' + tailored_key + '] 與'
//
+ (use_conversion.sort_name ? ' ['
//
+ use_conversion.sort_name + '] ' : '主要轉換') + '辭典衝突的轉換詞:');
tailored_conversion.pair_Map.forEach(function(value, key) {
library_namespace.log(key + '→' + value);
});
}
}, this);
// 加入了新辭典,可能有新詞,需要重新計算 this.max_convert_word_length。
delete this.max_convert_word_length;
}
// --------------------------------
/** @inner */
function Converter_initialization(options) {
// console.trace(this.files);
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 '';
}
// Will reset
this.conversions = [];
// 特設辭典對應集。
this.tailored_conversions = Object.create(null);
// @see add_conversions()
this.files.forEach(function(conversion_group_options) {
this.add_conversions(conversion_group_options, options);
}, this);
// assert: this.files[] 一一對應 this.conversions[]。
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) {
options = library_namespace.setup_options(options);
if (!this.conversions) {
this.initialization(options);
}
// 事前轉換表。
if (options.prefix_conversions) {
text = (new Convert_Pairs(options.prefix_conversions, {
flags : options.flags || REPLACE_FLAG
})).convert(text);
}
var for_each_conversion;
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);
};
}
var use_conversions = this.conversions;
// @see work_data.convert_options @
// CeL.application.net.work_crawler.ebook
// @see function convert_paragraph(paragraph, options) @
// Chinese_converter.js
[ 'tailored_key', 'work_title', 'original_work_title' ].some(function(
key) {
var tailored_key = options[key];
var conversions = tailored_key
&& this.tailored_conversions[tailored_key];
if (conversions) {
// 先處理衝突部分。
use_conversions = [ conversions ].append(use_conversions);
return true;
}
}, this);
text = use_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。
// console.trace(this);
var may_remove_pair_Map = this.may_remove_pair_Map;
if (may_remove_pair_Map) {
library_namespace.debug(
'在 convert_text() 開始轉換之後就不會再修改辭典檔,因此可移除 .pair_Map。', 1,
'Convert_Pairs__convert');
}
this.max_convert_word_length = this.conversions.reduce(function(
length, conversion) {
if (may_remove_pair_Map)
delete conversion.pair_Map;
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.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,
add_conversions : add_conversions
});
Converter.options = {
CN_to_TW : {
// 事前事後轉換表須事先設定。
// 不可以 Object.assign(CeL.CN_to_TW.prefix_conversions = {}, {})
// 來新增事前轉換表。
// prefix_conversions : {},
// postfix_conversions : {},
files : [ [ {
sort_name : '主要繁簡轉換'
}, 'STPhrases', 'STCharacters',
// 以 generate_additional_table.js 合併新同文堂和 ConvertZZ 的辭典檔。
'additional.to_TW.auto-generated',
// 後來的會覆蓋前面的。
{
file_name : 'additional.to_TW',
remove_comments : true
} ],
// ------------------------------------------------------
// ** 下面的是上面詞彙與單字轉換後的再轉換。
[ {
sort_name : '再轉換'
}, '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
[ {
sort_name : '變體字'
}, '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', [ {
sort_name : '主要繁簡轉換'
}, '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;
}
var KEY_converter = typeof Symbol === 'function' ? Symbol('KEY_converter')
: 'KEY converter';
_.KEY_converter = KEY_converter;
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;
// method to get the original converter.
converter_interface[KEY_converter] = converter;
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_
);
}