/**
* @name CeL function for native (built-in) objects.
* @fileoverview 本檔案包含了 native objects 的擴充功能。
*
* http://www.hunlock.com/blogs/Ten_Javascript_Tools_Everyone_Should_Have
*
* @see https://github.com/andrewplummer/Sugar
* @since
*/
'use strict';
// 'use asm';
// --------------------------------------------------------------------------------------------
typeof CeL === 'function' && CeL.run({
// module name
name : 'data.native',
// require : '',
// 設定不匯出的子函式。
// no_extend : '*',
// 為了方便格式化程式碼,因此將 module 函式主體另外抽出。
code : module_code
});
function module_code(library_namespace) {
var
/** {Number}未發現之index。 const: 基本上與程式碼設計合一,僅表示名義,不可更改。(=== -1) */
NOT_FOUND = ''.indexOf('_');
/**
* null module constructor
*
* @class native objects 的 functions
*/
var _// JSDT:_module_
= function() {
// null module constructor
};
/**
* for JSDT: 有 prototype 才會將之當作 Class
*/
_// JSDT:_module_
.prototype = {};
// ----------------------------------------------------------------------------------------------------------------------------------------------------------//
// cache
var set_method = library_namespace.set_method,
// https://en.wikipedia.org/wiki/Unicode_subscripts_and_superscripts
SUPERSCRIPT_NUMBER = superscript_integer.map = (superscript_integer.digits = '⁰¹²³⁴⁵⁶⁷⁸⁹')
.split(''),
//
SUBSCRIPT_NUMBER = subscript_integer.map = (subscript_integer.digits = '₀₁₂₃₄₅₆₇₈₉')
.split('');
SUPERSCRIPT_NUMBER['+'] = '⁺';
SUPERSCRIPT_NUMBER['-'] = '⁻';
SUBSCRIPT_NUMBER['+'] = '₊';
SUBSCRIPT_NUMBER['-'] = '₋';
function superscript_integer() {
var v = [];
this.digits().forEach(function(i) {
v.push(SUPERSCRIPT_NUMBER[i]);
});
return v.join('');
}
function subscript_integer() {
var v = [];
this.digits().forEach(function(i) {
v.push(SUBSCRIPT_NUMBER[i]);
});
return v.join('');
}
/**
* padding / fill. 將 string 以 character 補滿至長 length。
*
* @see Number.prototype.toLocaleString()
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString
*
* TODO: 效能測試:與 "return n > 9 ? n : '0' + n;" 相較。
*
* @example
// More examples: see /_test suite/test.js
*
*
* @param {String}string
* 基底 string。
* @param {Integer}length
* 補滿至長 length (maxLength)。
* @param {String}fillString
* 以 fillString 補滿。
* @param {Boolean}from_start
* 補滿方向。基本為 5 → ' 5',設定 from_start 時,為 5 → '5 '。
*
* @since 2012/3/25 19:46:42
*
* @returns {String} padding 過後之 string
*/
function pad(string, length, fillString, from_start) {
// 為負數作特殊處理。
// e.g., pad(-9, 3) === '-09'
if (typeof string === 'number' && string < 0
//
&& !from_start && (!fillString || fillString === 0))
return '-' + pad(-string, length - 1, '0');
string = String(string);
// 差距。
var gap = length - string.length;
if (gap > 0) {
// library_namespace.debug(gap + ' [' + fillString + ']');
if (!fillString || typeof fillString !== 'string') {
fillString = typeof fillString === 'number' ? String(fillString)
: string === '' || isNaN(string) ? ' ' : '0';
}
// assert: {String}fillString
var l = fillString.length,
/**
* TODO: binary extend.
* .join() is too slow.
*/
fill = new Array(l > 1 ? Math.ceil(gap / l) : gap);
// library_namespace.debug('fill.length = ' + fill.length);
if (from_start) {
fill[0] = string;
fill.length++;
string = fill.join(fillString);
if (string.length > length)
string = string.slice(0, length);
} else if (l > 1) {
fill.length++;
string = fill.join(fillString).slice(0, gap) + string;
} else {
fill.push(string);
string = fill.join(fillString);
}
}
return string;
}
_.pad = pad;
/**
*
function經ScriptEngine會轉成/取用'function'開始到'}'為止的字串
用[var thisFuncName=parse_function().funcName]可得本身之函數名
if(_detect_)alert('double run '+parse_function().funcName+'() by '+parse_function(arguments.callee.caller).funcName+'()!');
You may use this.constructor
TODO:
to call: parse_function(this,arguments)
e.g., parent_func.child_func=function(){var name=parse_function(this,arguments);}
bug:
函數定義 .toString() 時無法使用。
*/
_// JSDT:_module_
.
/**
* 函數的文字解譯/取得函數的語法
*
* @param {Function|String}
* function_name function name or function structure
* @param flags
* =1: reduce
* @return
* @example parsed_data = new parse_function(function_name);
* @see http://www.interq.or.jp/student/exeal/dss/ref/jscript/object/function.html,
* Syntax error:
* http://msdn.microsoft.com/library/en-us/script56/html/js56jserrsyntaxerror.asp
* @_memberOf _module_
* @since 2010/5/16 23:04:54
*/
parse_function = function parse_function(function_name, flags) {
if (!function_name)
try {
function_name = parse_function.caller;
if (typeof function_name !== 'function')
return;
} catch (e) {
return;
}
if (typeof function_name === 'string'
&& !(function_name = library_namespace
.get_various(function_name)))
return;
var fs = String(function_name), m = fs
.match(library_namespace.PATTERN_function);
library_namespace.debug(typeof function_name + '\n' + fs + '\n' + m, 6);
// detect error, 包含引數
// 原先:functionRegExp=/^\s*function\s+(\w+) ..
// 因為有function(~){~}這種的,所以改變。
if (!m) {
// JScript5 不能用 throw!
// http://www.oldversion.com/Internet-Explorer.html
// Syntax error!
// gettext_config:{"id":"syntax-error"}
throw new Error(1002, '語法錯誤!');
}
if (function_name != m[1]) {
// Function name unmatched.
library_namespace.warn({
// gettext_config:{"id":"function-name-unmatched"}
T : '函數名稱不相符,可能是用了 reference?'
});
}
library_namespace.debug('function ' + m[1] + '(' + m[2] + '){\n' + m[3]
+ '\n}', 9);
return {
string : fs,
name : m[1],
// 去除前後空白
arguments : m[2].replace(/[\s\n]+/g, '').split(','),
code : m[3]
};
};
// 補強 String.fromCharCode()
function fromCharCode(c) {
if (!isNaN(c))
return String.fromCharCode(c);
try {
// 直接最快
return eval('String.fromCharCode(' + c + ');');
} catch (e) {
}
// comments
if (typeof c == 'string') {
// c=c.split(','); 後者可以通過審查
return eval('String.fromCharCode(' + n + ')');
}
if (typeof c == 'object') {
var t = '', d, i, a, n = [];
if (c.length)
a = c;
else {
a = [];
for (i in c)
a.push(c[i]);
}
for (i = 0; i < a.length; i++)
if (!isNaN(c = a[i]) || !isNaN(c = ('' + a[i]).charCodeAt(0)))
// 跳過無法判讀的值
n.push(c);
// n.join(',') 這樣較快
return eval('String.fromCharCode(' + n + ')');
}
}
_// JSDT:_module_
.
/**
* ASCII_code_at, 對付有時 charCodeAt 會傳回 >256 的數值。 若確定編碼是 ASCII (char code 是
* 0~255) 即可使用此函數替代 charCodeAt。
*
* @param text
* string
* @param position
* at what position
* @return
* @since 2008/8/2 10:10:49
* @see http://www.alanwood.net/demos/charsetdiffs.html
* @_memberOf _module_
*/
toASCIIcode = function(text, position) {
var _f = arguments.callee, c;
if (!_f.t) {
// initialize
var i = 129, t = _f.t = [], l = {
8364 : 128,
8218 : 130,
402 : 131,
8222 : 132,
8230 : 133,
8224 : 134,
8225 : 135,
710 : 136,
8240 : 137,
352 : 138,
8249 : 139,
338 : 140,
381 : 142,
8216 : 145,
8217 : 146,
8220 : 147,
8221 : 148,
8226 : 149,
8211 : 150,
8212 : 151,
732 : 152,
8482 : 153,
353 : 154,
8250 : 155,
339 : 156,
382 : 158,
376 : 159
};
for (; i < 256; i += 2)
t[i] = i;
for (i in l) {
library_namespace.debug(i + ' = ' + l[i], 6);
t[i | 0] = l[i];
}
}
if (position < 0 && !isNaN(text))
c = text;
else
c = text.charCodeAt(position || 0);
return c < 128 ? c : (_f.t[c] || c);
};
/**
* 2008/8/2 9:9:16
encodeURI, encodeURIComponent 僅能編成 utf-8,對於其他 local 編碼可使用本函數。
e.g.,
f.src='http://www.map.com.tw/search_engine/searchBar.asp?search_class=address&SearchWord='+encodeUC(q[0],'big5')
perl
#use Encode qw(from_to);
use Encode;
my $tEnc='utf-8';
$t="金";
$t=Encode::decode($t,'big5');
Encode::from_to($t,$lEnc,$outEnc);
Encode::from_to
@b=split(//,$a);
for($i=0;$i
*/
// encodeUC[generateCode.dLK]='toASCIIcode';
function encodeUC(u, enc) {
if (!enc || enc == 'utf8')
return encodeURI(u);
var i = 0, c = new ActiveXObject("ADODB.Stream"), r = [];
// adTypeText;
c.Type = 2;
c.Charset = enc;
c.Open();
c.WriteText(u);
c.Position = 0;
c.Charset = 'iso-8859-1';
u = c.ReadText();
c.Close();
for (; i < u.length; i++)
r.push((c = u.charCodeAt(i)) < 0x80 ? u.charAt(i) : '%'
+ toASCIIcode(c, -1).toString(0x10).toUpperCase());
return r.join('').replace(/ /g, '+');
}
/**
* String pattern (e.g., "/a+/g") to RegExp pattern.
* escape RegExp pattern,以利作為 RegExp source 使用。
* cf. qq// in perl.
*
*
* String.prototype.to_RegExp_pattern = function(f) { return to_RegExp_pattern(this.valueOf(), f); };
*
*
* @param {String}pattern
* pattern text.
* @param {RegExp}[escape_pattern]
* char pattern need to escape.
* @param {Boolean|String}[RegExp_flags]
* flags when need to return RegExp object.
*
* @return {String|RegExp} escaped RegExp pattern or RegExp object.
*/
function to_RegExp_pattern(pattern, escape_pattern, RegExp_flags) {
pattern = pattern
// 不能用 $0。
.replace(escape_pattern || /([.*?+^$|()\[\]\\{}])/g, '\\$1')
// 這種方法不完全,例如對 /^\s+|\s+$/g
.replace(/^([\^])/, '\\^').replace(/(\$)$/, '\\$');
return RegExp_flags === undefined ? pattern : new RegExp(pattern,
_.PATTERN_RegExp_flags.test(RegExp_flags) ? RegExp_flags : '');
}
_// JSDT:_module_
.to_RegExp_pattern = to_RegExp_pattern;
// CeL.ignore_first_char_case('abc') === '[Aa]bc'
function ignore_first_char_case(pattern) {
// pattern 無特殊字元!否則應該出警告。
var lower_case = pattern.charAt(0),
//
upper_case = lower_case.toUpperCase();
if (upper_case !== lower_case
//
|| upper_case !== (lower_case = upper_case.toLowerCase()))
pattern = '[' + upper_case + lower_case + ']' + pattern.slice(1);
return pattern;
}
_// JSDT:_module_
.ignore_first_char_case = ignore_first_char_case;
// CeL.ignore_case_pattern('abc') === '[Aa][Bb][Cc]'
function ignore_case_pattern(pattern, only_first_char) {
pattern = pattern.split('');
// pattern 無特殊字元!否則應該出警告。
pattern.forEach(function(lower_case, index) {
var upper_case = lower_case.toUpperCase();
if (upper_case !== lower_case
//
|| upper_case !== (lower_case = upper_case.toLowerCase()))
pattern[index] = '[' + upper_case + lower_case + ']';
})
return pattern.join('');
}
_// JSDT:_module_
.ignore_case_pattern = ignore_case_pattern;
// pattern.replace(string)
// 警告: 必須自行檢查 string! 否則會出現 pattern.replace(undefined) === 'undefined'
function pattern_replace(string) {
// assert: {RegExp}this pattern
// assert: pattern has .replace_to
return String(string).replace(this, this.replace_to);
}
if (false) {
pattern = '/move from/g'.to_RegExp();
pattern = '/move from/replace to/g'.to_RegExp({
allow_replacement : true
});
pattern.replace('*move from*') === '*replace to*';
}
/**
* 將 String pattern (e.g., "/a+/g") 轉成 RegExp。
* TODO:
* and, or, not.
* (?:(^|\s*\|)\s*(!)?(\/(?:[^\/]+|\\\/)(\/([a-z]*))?|\\(\S+)|\S+))+
* {Object|Array}preprocessor
*
* cf. CeL.to_RegExp_pattern()
*
* @param {String}pattern
* 欲轉換成 RegExp 的 pattern text。
* @param {Object}[options]
* 附加參數/設定特殊功能與選項 options = {
* {String}flags : RegExp 的 flags。
* {Function|String}error_handler : 當遇到不明 pattern 時的處理程序。
}
*
* @returns {RegExp} RegExp object。
*
* @since 2012/10/13 10:22:20
*/
function String_to_RegExp(pattern, options) {
// 前置作業。
if (typeof options === 'string' && _.PATTERN_RegExp_flags.test(options)) {
options = {
flags : options
};
} else if (typeof options === 'function') {
options = {
error_handler : options
};
} else {
options = library_namespace.setup_options(options);
}
if (typeof pattern === 'string') {
if (typeof String_to_RegExp.preprocessor === 'function')
pattern = String_to_RegExp.preprocessor(pattern);
if (typeof pattern === 'string' && pattern.length > 1)
// pattern.trim()
if (pattern.charAt(0) === '/') {
library_namespace.debug({
// gettext_config:{"id":"treat-$1-as-regexp"}
T : [ 'Treat [%1] as RegExp.', pattern ]
}, 3, 'String_to_RegExp');
var matched = pattern.match(_.PATTERN_RegExp), replace_to;
if (!matched && options.allow_replacement
//
&& (matched = pattern.match(_.PATTERN_RegExp_replacement))) {
replace_to = matched[2];
// matched[2] = matched[3];
matched.splice(2, 1);
}
// 設定 flags。
var flags = _.PATTERN_RegExp_flags.test(options.flags)
&& options.flags
|| (matched ? matched[2]
: String_to_RegExp.default_flags);
try {
try {
pattern = new RegExp(matched ? matched[1] : pattern
.slice(1), flags);
} catch (e) {
try {
if (matched) {
// 設定絕對可接受的 flags,或完全不設定。
pattern = new RegExp(matched[1]);
library_namespace.warn([
//
'String_to_RegExp: ', {
// gettext_config:{"id":"invalid-flags-$1"}
T : [ 'Invalid flags: [%1]', flags ]
} ]);
} else
throw true;
} catch (e) {
library_namespace.warn([
// Illegal pattern: /%1/
'String_to_RegExp: ', {
// gettext_config:{"id":"illegal-pattern-$1"}
T : [ 'Illegal pattern: [%1]', matched[1] ]
} ]);
}
}
} catch (e) {
library_namespace.debug({
// gettext_config:{"id":"conversion-mode-$1-error-invalid-regexp?-$2"}
T : [ '轉換模式 [%1] 出錯:並非 RegExp? %2', pattern,
e.message ]
}, 2, 'String_to_RegExp');
}
if (replace_to) {
pattern.replace_to = replace_to;
if (false) {
pattern.replace = function replace(string) {
return string.replace(pattern, replace_to);
};
}
pattern.replace = pattern_replace;
}
} else if (pattern.charAt(0) === '\\'
&& typeof library_namespace.wildcard_to_RegExp === 'function') {
library_namespace.debug({
T : [
// gettext_config:{"id":"treat-pattern-$1-as-windows-wildcard-search-string"}
'Treat pattern [%1] as Windows wildcard search string.'
//
, pattern ]
}, 3, 'String_to_RegExp');
pattern = new RegExp(library_namespace
.wildcard_to_RegExp(pattern));
}
if (typeof pattern === 'string')
try {
pattern = typeof options.error_handler === 'function'
// default unknown handler.
? options.error_handler(pattern) : new RegExp(pattern
// .replace(/,/g, '|')
);
} catch (e) {
library_namespace.debug({
// gettext_config:{"id":"unable-to-convert-mode-$1"}
T : [ '無法轉換模式 [%1]!', pattern ]
}, 3, 'String_to_RegExp');
}
}
return pattern;
}
String_to_RegExp.default_flags = 'i';
// 前置處理。
String_to_RegExp.preprocessor = function(pattern) {
var m;
if (pattern.length < 800
&& (m = pattern
.match(/^/((?:\/|[^\\\/|?*":<>/\0-\x1f]+)+)/([a-z]*)(?:\.[^.]+)?$/)))
try {
/**
* @see application.net.to_file_name()
*/
library_namespace.debug('因為 pattern [' + pattern
+ '] 以 "/" 起首,可能是以 directory name / file name'
+ ' 充當 pattern,嘗試將之還原為 regular pattern。', 2,
'String_to_RegExp.preprocessor');
pattern = new RegExp(m[1]
// functional characters
.replace(/\/g, '\\').replace(///g, '/').replace(/|/g, '|')
//
.replace(/?/g, '?').replace(/*/g, '*')
//
.replace(/((?:^|[^\\])(?:\\\\)*)\\([\\\/|?*])/g,
//
function($0, $1, $2) {
return $1 + '[\\$2' + {
'\\' : '\',
'/' : '/',
'|' : '|',
'?' : '?',
'*' : '*'
}[$2] + ']';
})
// normal characters
.replace(/"/g, '[""]').replace(/:/g, '[::]').replace(/</g,
'[<<]').replace(/>/g, '[>>]')
// control characters
.replace(/_/g, '[_\\r\\n\\t\\f\\v]'), m[2]);
} catch (e) {
}
return pattern;
};
/**
* 將 string 轉成 search pattern,並回傳是否 matched。
*
* @param {String}pattern
* 欲轉換成 RegExp 的 pattern。
* @param {String}[text]
* 欲測試的 text。
* @param {Function}[unknown_handler]
* 當遇到不明 pattern 時的處理程序。
* @returns 是否 matched。
*
* @since 2012/10/13 10:22:20
*
* @see CeL.data.fit_filter()
*/
function is_matched(pattern, text, unknown_handler) {
pattern = String_to_RegExp(pattern, unknown_handler);
if (typeof text !== 'string')
if (typeof text === 'undefined' || text === null)
return pattern;
else
text = String(text);
return library_namespace.is_RegExp(pattern) ? text.match(pattern)
: text.indexOf(String(pattern)) !== NOT_FOUND;
}
_.is_matched = is_matched;
var RegExp_flags = /./g.flags === 'g'
// get RegExp.prototype.flags
? function(regexp) {
return regexp.flags;
} : function(regexp) {
// regexp = RegExp.prototype.toString.call(regexp);
// return ('' + regexp).match(/[^\/]*$/)[0];
regexp = '' + regexp;
return regexp.slice(regexp.lastIndexOf('/') + 1);
var flags = [];
for ( var flag in RegExp_flags.flags)
if (regexp[flag])
flags.push(RegExp_flags.flags[flag]);
return flags.join('');
};
library_namespace.RegExp_flags = RegExp_flags;
// RegExp.prototype.flags
// 注意: 本 shim 實際上應放置於 data.code.compatibility。惟其可能會被省略執行,因此放置於此。
if (!('flags' in RegExp.prototype)
//
&& !Object.defineProperty[library_namespace.env.not_native_keyword])
Object.defineProperty(RegExp.prototype, 'flags', {
get : function() {
return RegExp_flags(this);
}
});
// https://tc39.es/ecma262/#sec-get-regexp.prototype.flags
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/flags
RegExp_flags.flags = {
// Proposed for ES6
// extended : 'x',
global : 'g',
ignoreCase : 'i',
multiline : 'm',
dotAll : 's',
unicode : 'u',
sticky : 'y',
// https://github.com/tc39/proposal-regexp-match-indices
indices : 'd'
};
if (Object.values) {
_.PATTERN_RegExp_flags = Object.values(RegExp_flags.flags);
} else {
// e.g., @ WSH (Windows Script Host)
_.PATTERN_RegExp_flags = [];
(function() {
for ( var flag in RegExp_flags.flags)
_.PATTERN_RegExp_flags.push(RegExp_flags.flags[flag]);
})();
}
// TODO: flags 只能出現一次!
_.PATTERN_RegExp_flags = _.PATTERN_RegExp_flags.join('');
// CeL.PATTERN_RegExp
// [ all, pattern source, flags ]
_.PATTERN_RegExp = new RegExp(
// /^\/(.+)\/([iugms]*)$/
// /^\/((?:\\[\s\S]|[^\/])+)\/([gimsuy]*)$/
/^\/((?:\\[\s\S]|[^\/])+)\/([flags]*)$/.source.replace('flags',
_.PATTERN_RegExp_flags));
// CeL.PATTERN_RegExp_replacement
// e.g., '/只/隻/i'
_.PATTERN_RegExp_replacement = new RegExp(
// [ all, pattern source, replace to, flags ]
/^\/((?:\\[\s\S]|[^\/])+)\/((?:\\[\s\S]|[^\/])*)\/([flags]*)$/.source
.replace('flags', _.PATTERN_RegExp_flags));
// CeL.PATTERN_RegExp_flags
_.PATTERN_RegExp_flags = new RegExp(/^[flags]+$/.source.replace('flags',
_.PATTERN_RegExp_flags));
/**
*
use (new RegExp(regexp.source, flags)) instead.
or even (new RegExp(regexp, flags)):
RexExp constructor no longer throws when the first argument is a RegExp and the second argument is present. Instead it creates a new RegExp using the same patterns as the first arguments and the flags supplied by the second argument.
*/
/**
* 重新設定 RegExp object 之 flags. change the flags of a RegExp instances.
*
* @param {RegExp}regexp
* RegExp object to set
* @param {String}flags
* flags of RegExp
* @return {RegExp}
* @example
// 附帶 'g' flag 的 RegExp 對相同字串作 .test() 時,第二次並不會重設。
// 因此像下面的 expression 兩次並不會得到相同結果。
var r = /,/g, t = 'a,b';
WScript.Echo(r.test(t) + ',' + r.test(t));
// 改成這樣就可以了:
var r = /,/g, t = 'a,b', s = renew_RegExp_flags(r, '-g');
WScript.Echo(s.test(t) + ',' + s.test(t));
// 這倒沒問題:
r = /,/g, a = 'a,b';
if (r.test(a))
library_namespace.debug(a.replace(r, '_'));
// delete r.lastIndex; 無效,得用 r.lastIndex = 0; 因此下面的亦可:
if (r.global)
r.lastIndex = 0;
if (r.test(a)) {
// ...
}
*
* @see http://msdn.microsoft.com/zh-tw/library/x9h97e00(VS.80).aspx,
* 如果規則運算式已經設定了全域旗標,test 將會從 lastIndex 值表示的位置開始搜尋字串。如果未設定全域旗標,則 test
* 會略過 lastIndex 值,並從字串之首開始搜尋。
* http://www.aptana.com/reference/html/api/RegExp.html
* @_memberOf _module_
*/
function renew_RegExp_flags(regexp, flags) {
// 未指定 flags: get flags
if (!flags) {
flags = '';
for ( var i in RegExp_flags.flags)
if (regexp[i])
flags += RegExp_flags.flags[i];
return flags;
}
var a = flags.charAt(0), F = '', m;
a = a === '+' ? 1 : a === '-' ? 0 : (F = 1);
if (F) {
// 無 [+-]
F = flags;
} else {
// f: [+-]~ 的情況,parse flags
for ( var i in RegExp_flags.flags)
if ((m = flags.indexOf(RegExp_flags.flags[i], 1) !== NOT_FOUND)
&& a || !m && regexp[i])
F += RegExp_flags.flags[i];
}
// for JScript<=5
try {
return new RegExp(regexp.source, F);
} catch (e) {
// TODO: handle exception
}
}
_// JSDT:_module_
.renew_RegExp_flags = renew_RegExp_flags;
// ---------------------------------------------------------------------//
// Unicode category
// 使用例之說明:
// @see CeL.data.native for Unicode category (e.g., \p{Cf})
if (false) {
var
/**
* 振り仮名 / 読み仮名 の正規表現。
*
* @type {RegExp}
* @see [[d:Property:P1814|假名]]
*/
PATTERN_読み仮名 = CeL.RegExp(/^[\p{Hiragana}\p{Katakana}ー・ ]+$/, 'u');
}
// https://github.com/slevithan/xregexp/blob/master/tools/output/categories.js
// http://stackoverflow.com/questions/11598786/how-to-replace-non-printable-unicode-characters-javascript
var Unicode_category = {
// Control
Cc : '\0-\x1F\x7F-\x9F',
// Format
Cf : '\xAD\u0600-\u0605\u061C\u06DD\u070F\u180E\u200B-\u200F\u202A-\u202E\u2060-\u2064\u2066-\u206F\uFEFF\uFFF9-\uFFFB',
// Unassigned
Cn : '\u0378\u0379\u0380-\u0383\u038B\u038D\u03A2\u0530\u0557\u0558\u0560\u0588\u058B\u058C\u0590\u05C8-\u05CF\u05EB-\u05EF\u05F5-\u05FF\u061D\u070E\u074B\u074C\u07B2-\u07BF\u07FB-\u07FF\u082E\u082F\u083F\u085C\u085D\u085F-\u089F\u08B5-\u08E2\u0984\u098D\u098E\u0991\u0992\u09A9\u09B1\u09B3-\u09B5\u09BA\u09BB\u09C5\u09C6\u09C9\u09CA\u09CF-\u09D6\u09D8-\u09DB\u09DE\u09E4\u09E5\u09FC-\u0A00\u0A04\u0A0B-\u0A0E\u0A11\u0A12\u0A29\u0A31\u0A34\u0A37\u0A3A\u0A3B\u0A3D\u0A43-\u0A46\u0A49\u0A4A\u0A4E-\u0A50\u0A52-\u0A58\u0A5D\u0A5F-\u0A65\u0A76-\u0A80\u0A84\u0A8E\u0A92\u0AA9\u0AB1\u0AB4\u0ABA\u0ABB\u0AC6\u0ACA\u0ACE\u0ACF\u0AD1-\u0ADF\u0AE4\u0AE5\u0AF2-\u0AF8\u0AFA-\u0B00\u0B04\u0B0D\u0B0E\u0B11\u0B12\u0B29\u0B31\u0B34\u0B3A\u0B3B\u0B45\u0B46\u0B49\u0B4A\u0B4E-\u0B55\u0B58-\u0B5B\u0B5E\u0B64\u0B65\u0B78-\u0B81\u0B84\u0B8B-\u0B8D\u0B91\u0B96-\u0B98\u0B9B\u0B9D\u0BA0-\u0BA2\u0BA5-\u0BA7\u0BAB-\u0BAD\u0BBA-\u0BBD\u0BC3-\u0BC5\u0BC9\u0BCE\u0BCF\u0BD1-\u0BD6\u0BD8-\u0BE5\u0BFB-\u0BFF\u0C04\u0C0D\u0C11\u0C29\u0C3A-\u0C3C\u0C45\u0C49\u0C4E-\u0C54\u0C57\u0C5B-\u0C5F\u0C64\u0C65\u0C70-\u0C77\u0C80\u0C84\u0C8D\u0C91\u0CA9\u0CB4\u0CBA\u0CBB\u0CC5\u0CC9\u0CCE-\u0CD4\u0CD7-\u0CDD\u0CDF\u0CE4\u0CE5\u0CF0\u0CF3-\u0D00\u0D04\u0D0D\u0D11\u0D3B\u0D3C\u0D45\u0D49\u0D4F-\u0D56\u0D58-\u0D5E\u0D64\u0D65\u0D76-\u0D78\u0D80\u0D81\u0D84\u0D97-\u0D99\u0DB2\u0DBC\u0DBE\u0DBF\u0DC7-\u0DC9\u0DCB-\u0DCE\u0DD5\u0DD7\u0DE0-\u0DE5\u0DF0\u0DF1\u0DF5-\u0E00\u0E3B-\u0E3E\u0E5C-\u0E80\u0E83\u0E85\u0E86\u0E89\u0E8B\u0E8C\u0E8E-\u0E93\u0E98\u0EA0\u0EA4\u0EA6\u0EA8\u0EA9\u0EAC\u0EBA\u0EBE\u0EBF\u0EC5\u0EC7\u0ECE\u0ECF\u0EDA\u0EDB\u0EE0-\u0EFF\u0F48\u0F6D-\u0F70\u0F98\u0FBD\u0FCD\u0FDB-\u0FFF\u10C6\u10C8-\u10CC\u10CE\u10CF\u1249\u124E\u124F\u1257\u1259\u125E\u125F\u1289\u128E\u128F\u12B1\u12B6\u12B7\u12BF\u12C1\u12C6\u12C7\u12D7\u1311\u1316\u1317\u135B\u135C\u137D-\u137F\u139A-\u139F\u13F6\u13F7\u13FE\u13FF\u169D-\u169F\u16F9-\u16FF\u170D\u1715-\u171F\u1737-\u173F\u1754-\u175F\u176D\u1771\u1774-\u177F\u17DE\u17DF\u17EA-\u17EF\u17FA-\u17FF\u180F\u181A-\u181F\u1878-\u187F\u18AB-\u18AF\u18F6-\u18FF\u191F\u192C-\u192F\u193C-\u193F\u1941-\u1943\u196E\u196F\u1975-\u197F\u19AC-\u19AF\u19CA-\u19CF\u19DB-\u19DD\u1A1C\u1A1D\u1A5F\u1A7D\u1A7E\u1A8A-\u1A8F\u1A9A-\u1A9F\u1AAE\u1AAF\u1ABF-\u1AFF\u1B4C-\u1B4F\u1B7D-\u1B7F\u1BF4-\u1BFB\u1C38-\u1C3A\u1C4A-\u1C4C\u1C80-\u1CBF\u1CC8-\u1CCF\u1CF7\u1CFA-\u1CFF\u1DF6-\u1DFB\u1F16\u1F17\u1F1E\u1F1F\u1F46\u1F47\u1F4E\u1F4F\u1F58\u1F5A\u1F5C\u1F5E\u1F7E\u1F7F\u1FB5\u1FC5\u1FD4\u1FD5\u1FDC\u1FF0\u1FF1\u1FF5\u1FFF\u2065\u2072\u2073\u208F\u209D-\u209F\u20BF-\u20CF\u20F1-\u20FF\u218C-\u218F\u23FB-\u23FF\u2427-\u243F\u244B-\u245F\u2B74\u2B75\u2B96\u2B97\u2BBA-\u2BBC\u2BC9\u2BD2-\u2BEB\u2BF0-\u2BFF\u2C2F\u2C5F\u2CF4-\u2CF8\u2D26\u2D28-\u2D2C\u2D2E\u2D2F\u2D68-\u2D6E\u2D71-\u2D7E\u2D97-\u2D9F\u2DA7\u2DAF\u2DB7\u2DBF\u2DC7\u2DCF\u2DD7\u2DDF\u2E43-\u2E7F\u2E9A\u2EF4-\u2EFF\u2FD6-\u2FEF\u2FFC-\u2FFF\u3040\u3097\u3098\u3100-\u3104\u312E-\u3130\u318F\u31BB-\u31BF\u31E4-\u31EF\u321F\u32FF\u4DB6-\u4DBF\u9FD6-\u9FFF\uA48D-\uA48F\uA4C7-\uA4CF\uA62C-\uA63F\uA6F8-\uA6FF\uA7AE\uA7AF\uA7B8-\uA7F6\uA82C-\uA82F\uA83A-\uA83F\uA878-\uA87F\uA8C5-\uA8CD\uA8DA-\uA8DF\uA8FE\uA8FF\uA954-\uA95E\uA97D-\uA97F\uA9CE\uA9DA-\uA9DD\uA9FF\uAA37-\uAA3F\uAA4E\uAA4F\uAA5A\uAA5B\uAAC3-\uAADA\uAAF7-\uAB00\uAB07\uAB08\uAB0F\uAB10\uAB17-\uAB1F\uAB27\uAB2F\uAB66-\uAB6F\uABEE\uABEF\uABFA-\uABFF\uD7A4-\uD7AF\uD7C7-\uD7CA\uD7FC-\uD7FF\uFA6E\uFA6F\uFADA-\uFAFF\uFB07-\uFB12\uFB18-\uFB1C\uFB37\uFB3D\uFB3F\uFB42\uFB45\uFBC2-\uFBD2\uFD40-\uFD4F\uFD90\uFD91\uFDC8-\uFDEF\uFDFE\uFDFF\uFE1A-\uFE1F\uFE53\uFE67\uFE6C-\uFE6F\uFE75\uFEFD\uFEFE\uFF00\uFFBF-\uFFC1\uFFC8\uFFC9\uFFD0\uFFD1\uFFD8\uFFD9\uFFDD-\uFFDF\uFFE7\uFFEF-\uFFF8\uFFFE\uFFFF',
// Private_Use
Co : '\uE000-\uF8FF',
// Surrogate
Cs : '\uD800-\uDFFF',
// Other
C : '\0-\x1F\x7F-\x9F\xAD\u0378\u0379\u0380-\u0383\u038B\u038D\u03A2\u0530\u0557\u0558\u0560\u0588\u058B\u058C\u0590\u05C8-\u05CF\u05EB-\u05EF\u05F5-\u0605\u061C\u061D\u06DD\u070E\u070F\u074B\u074C\u07B2-\u07BF\u07FB-\u07FF\u082E\u082F\u083F\u085C\u085D\u085F-\u089F\u08B5-\u08E2\u0984\u098D\u098E\u0991\u0992\u09A9\u09B1\u09B3-\u09B5\u09BA\u09BB\u09C5\u09C6\u09C9\u09CA\u09CF-\u09D6\u09D8-\u09DB\u09DE\u09E4\u09E5\u09FC-\u0A00\u0A04\u0A0B-\u0A0E\u0A11\u0A12\u0A29\u0A31\u0A34\u0A37\u0A3A\u0A3B\u0A3D\u0A43-\u0A46\u0A49\u0A4A\u0A4E-\u0A50\u0A52-\u0A58\u0A5D\u0A5F-\u0A65\u0A76-\u0A80\u0A84\u0A8E\u0A92\u0AA9\u0AB1\u0AB4\u0ABA\u0ABB\u0AC6\u0ACA\u0ACE\u0ACF\u0AD1-\u0ADF\u0AE4\u0AE5\u0AF2-\u0AF8\u0AFA-\u0B00\u0B04\u0B0D\u0B0E\u0B11\u0B12\u0B29\u0B31\u0B34\u0B3A\u0B3B\u0B45\u0B46\u0B49\u0B4A\u0B4E-\u0B55\u0B58-\u0B5B\u0B5E\u0B64\u0B65\u0B78-\u0B81\u0B84\u0B8B-\u0B8D\u0B91\u0B96-\u0B98\u0B9B\u0B9D\u0BA0-\u0BA2\u0BA5-\u0BA7\u0BAB-\u0BAD\u0BBA-\u0BBD\u0BC3-\u0BC5\u0BC9\u0BCE\u0BCF\u0BD1-\u0BD6\u0BD8-\u0BE5\u0BFB-\u0BFF\u0C04\u0C0D\u0C11\u0C29\u0C3A-\u0C3C\u0C45\u0C49\u0C4E-\u0C54\u0C57\u0C5B-\u0C5F\u0C64\u0C65\u0C70-\u0C77\u0C80\u0C84\u0C8D\u0C91\u0CA9\u0CB4\u0CBA\u0CBB\u0CC5\u0CC9\u0CCE-\u0CD4\u0CD7-\u0CDD\u0CDF\u0CE4\u0CE5\u0CF0\u0CF3-\u0D00\u0D04\u0D0D\u0D11\u0D3B\u0D3C\u0D45\u0D49\u0D4F-\u0D56\u0D58-\u0D5E\u0D64\u0D65\u0D76-\u0D78\u0D80\u0D81\u0D84\u0D97-\u0D99\u0DB2\u0DBC\u0DBE\u0DBF\u0DC7-\u0DC9\u0DCB-\u0DCE\u0DD5\u0DD7\u0DE0-\u0DE5\u0DF0\u0DF1\u0DF5-\u0E00\u0E3B-\u0E3E\u0E5C-\u0E80\u0E83\u0E85\u0E86\u0E89\u0E8B\u0E8C\u0E8E-\u0E93\u0E98\u0EA0\u0EA4\u0EA6\u0EA8\u0EA9\u0EAC\u0EBA\u0EBE\u0EBF\u0EC5\u0EC7\u0ECE\u0ECF\u0EDA\u0EDB\u0EE0-\u0EFF\u0F48\u0F6D-\u0F70\u0F98\u0FBD\u0FCD\u0FDB-\u0FFF\u10C6\u10C8-\u10CC\u10CE\u10CF\u1249\u124E\u124F\u1257\u1259\u125E\u125F\u1289\u128E\u128F\u12B1\u12B6\u12B7\u12BF\u12C1\u12C6\u12C7\u12D7\u1311\u1316\u1317\u135B\u135C\u137D-\u137F\u139A-\u139F\u13F6\u13F7\u13FE\u13FF\u169D-\u169F\u16F9-\u16FF\u170D\u1715-\u171F\u1737-\u173F\u1754-\u175F\u176D\u1771\u1774-\u177F\u17DE\u17DF\u17EA-\u17EF\u17FA-\u17FF\u180E\u180F\u181A-\u181F\u1878-\u187F\u18AB-\u18AF\u18F6-\u18FF\u191F\u192C-\u192F\u193C-\u193F\u1941-\u1943\u196E\u196F\u1975-\u197F\u19AC-\u19AF\u19CA-\u19CF\u19DB-\u19DD\u1A1C\u1A1D\u1A5F\u1A7D\u1A7E\u1A8A-\u1A8F\u1A9A-\u1A9F\u1AAE\u1AAF\u1ABF-\u1AFF\u1B4C-\u1B4F\u1B7D-\u1B7F\u1BF4-\u1BFB\u1C38-\u1C3A\u1C4A-\u1C4C\u1C80-\u1CBF\u1CC8-\u1CCF\u1CF7\u1CFA-\u1CFF\u1DF6-\u1DFB\u1F16\u1F17\u1F1E\u1F1F\u1F46\u1F47\u1F4E\u1F4F\u1F58\u1F5A\u1F5C\u1F5E\u1F7E\u1F7F\u1FB5\u1FC5\u1FD4\u1FD5\u1FDC\u1FF0\u1FF1\u1FF5\u1FFF\u200B-\u200F\u202A-\u202E\u2060-\u206F\u2072\u2073\u208F\u209D-\u209F\u20BF-\u20CF\u20F1-\u20FF\u218C-\u218F\u23FB-\u23FF\u2427-\u243F\u244B-\u245F\u2B74\u2B75\u2B96\u2B97\u2BBA-\u2BBC\u2BC9\u2BD2-\u2BEB\u2BF0-\u2BFF\u2C2F\u2C5F\u2CF4-\u2CF8\u2D26\u2D28-\u2D2C\u2D2E\u2D2F\u2D68-\u2D6E\u2D71-\u2D7E\u2D97-\u2D9F\u2DA7\u2DAF\u2DB7\u2DBF\u2DC7\u2DCF\u2DD7\u2DDF\u2E43-\u2E7F\u2E9A\u2EF4-\u2EFF\u2FD6-\u2FEF\u2FFC-\u2FFF\u3040\u3097\u3098\u3100-\u3104\u312E-\u3130\u318F\u31BB-\u31BF\u31E4-\u31EF\u321F\u32FF\u4DB6-\u4DBF\u9FD6-\u9FFF\uA48D-\uA48F\uA4C7-\uA4CF\uA62C-\uA63F\uA6F8-\uA6FF\uA7AE\uA7AF\uA7B8-\uA7F6\uA82C-\uA82F\uA83A-\uA83F\uA878-\uA87F\uA8C5-\uA8CD\uA8DA-\uA8DF\uA8FE\uA8FF\uA954-\uA95E\uA97D-\uA97F\uA9CE\uA9DA-\uA9DD\uA9FF\uAA37-\uAA3F\uAA4E\uAA4F\uAA5A\uAA5B\uAAC3-\uAADA\uAAF7-\uAB00\uAB07\uAB08\uAB0F\uAB10\uAB17-\uAB1F\uAB27\uAB2F\uAB66-\uAB6F\uABEE\uABEF\uABFA-\uABFF\uD7A4-\uD7AF\uD7C7-\uD7CA\uD7FC-\uF8FF\uFA6E\uFA6F\uFADA-\uFAFF\uFB07-\uFB12\uFB18-\uFB1C\uFB37\uFB3D\uFB3F\uFB42\uFB45\uFBC2-\uFBD2\uFD40-\uFD4F\uFD90\uFD91\uFDC8-\uFDEF\uFDFE\uFDFF\uFE1A-\uFE1F\uFE53\uFE67\uFE6C-\uFE6F\uFE75\uFEFD-\uFF00\uFFBF-\uFFC1\uFFC8\uFFC9\uFFD0\uFFD1\uFFD8\uFFD9\uFFDD-\uFFDF\uFFE7\uFFEF-\uFFFB\uFFFE\uFFFF',
// 振り仮名 / 読み仮名
// http://www.unicode.org/charts/PDF/U3040.pdf
Hiragana : '\u3041-\u3096\u309D-\u309F',
Katakana : '\u30A1-\u30FA\u30FD-\u30FF\u31F0-\u31FF\u32D0-\u32FE\u3300-\u3357\uFF66-\uFF6F\uFF71-\uFF9D'
};
if (!('unicode' in RegExp.prototype)) {
Unicode_category.C = Unicode_category.C.replace('\uD7FC',
// exclude surrogate pair control characters
// (surrogate code point, \uD800-\uDFFF)
'\uD7FC-\uD7FF\uE000');
}
// invalid characters @ wikitext, XML.
Unicode_category.invalid = Unicode_category.C.replace('\0',
// 去除 \t\n\r
'\0-\x08\x0B\x0C\x0E');
/**
* 可以使用 /\p{C}/u 之類的 RegExp。
*
* @param {String|RegExp}source
* source of RegExp instance.
* @param {String}[flags]
* flags of RegExp instance.
*
* @returns {RegExp}RegExp instance.
*/
function new_RegExp(source, flags) {
if (has_Unicode_flag) {
try {
return new RegExp(source, flags);
} catch (e) {
// e.g., 自行設定了 Unicode_category
}
}
if (library_namespace.is_RegExp(source)) {
if (flags === undefined)
flags = source.flags;
source = source.source;
}
if (typeof flags === 'string' && flags.includes('u')) {
if (!has_Unicode_flag)
flags = flags.replace(/u/g, '');
// 後處理 Unicode category。
source = source.replace(/\\p{([A-Z][A-Za-z_]*)}/g, function(all,
category) {
return Unicode_category[category] || all;
});
}
return new RegExp(source, flags);
}
new_RegExp.category = Unicode_category;
var has_Unicode_flag;
try {
if (has_Unicode_flag = ('unicode' in RegExp.prototype)
&& new RegExp(/\p{C}/, 'u')) {
has_Unicode_flag = has_Unicode_flag.test('\u200E')
&& has_Unicode_flag.unicode
&& has_Unicode_flag.flags === 'u';
}
} catch (e) {
has_Unicode_flag = false;
}
_.RegExp = new_RegExp;
// ---------------------------------------------------------------------//
/**
* 2004/5/27 16:08
將 MS-DOS 萬用字元(wildcard characters)轉成 RegExp, 回傳 pattern
for search
usage:
p=new RegExp(wildcard_to_RegExp('*.*'))
flags&1 有變化的時候才 return RegExp
flags&2 add ^$
萬用字元經常用在檔名的置換。
* 代表任意檔案名稱
如:ls * 表示列出所有檔案名稱。
? 則代表一個字元
如: ls index.??? 表示列出所有 index.三個字元 的檔案名稱
[ ] 代表選擇其中一個字元
[Ab] 則代表 A 或 b 二者之中的一個字元
如: ls [Ab]same 為 Asame 或 bsame
[! ] 代表除外的一個字元
[!Ab] 則代表 不是 A 且 不是 b 的一個字元
如: [!0-9] 表不是數字字元
如: *[!E] 表末尾不是 E 的檔名
memo:
檔案名稱不可包含字元 ** 不包含目錄分隔字元 [\\/]:
/:*?"<>|/
*/
// 萬用字元 RegExp source, ReadOnly
// wildcard_to_RegExp.w_chars = '*?\\[\\]';
wildcard_to_RegExp.w_chars = '*?';
function wildcard_to_RegExp(pattern, flags) {
if (library_namespace.is_RegExp(pattern))
return pattern;
if (!pattern || typeof pattern !== 'string')
return;
var ic = wildcard_to_RegExp.w_chars, r;
if ((flags & 1) && !new RegExp('[' + ic + ']').test(pattern))
return pattern;
ic = '[^' + ic + ']';
if (false) {
// old: 考慮 \
r = pattern.replace(/(\\*)(\*+|\?+|\.)/g, function($0, $1, $2) {
var c = $2.charAt(0);
return $1.length % 2 ? $0 : $1
+ (c === '*' ? ic + '*' : c === '?' ? ic + '{'
+ $2.length + '}' : '\\' + $2);
})
}
r = pattern
// 處理目錄分隔字元:多轉一,'/' → '\\' 或相反
.replace(/[\\\/]+/g, library_namespace.env.path_separator)
// 在 RegExp 中有作用,但非萬用字元,在檔名中無特殊作用的
.replace(/([().^$\-])/g, '\\$1')
// * 代表任意檔案字元
.replace(/\*+/g, '\0*')
// ? 代表一個檔案字元
.replace(/\?+/g, function($0) {
return '\0{' + $0.length + '}';
})
// [ ] 代表選擇其中一個字元
// pass
.replace(/([\[\]])/g, '\\$1')
// [! ] 代表除外的一個字元
// pass
// .replace(/\[!([^\]]*)\]/g, '[^$1]')
// translate wildcard characters
.replace(/\0+/g, ic)
;
// console.trace(r);
// 有變化的時候才 return RegExp
if (!(flags & 1) || pattern !== r) {
try {
pattern = new RegExp(flags & 2 ? '^' + r + '$' : r, 'i');
} catch (e) {
// 輸入了不正確的 RegExp:未預期的次數符號等
}
}
return pattern;
}
_.wildcard_to_RegExp = wildcard_to_RegExp;
function remove_Object_value(object, value) {
for ( var i in object)
if (object[i] === value)
delete object[i];
}
_// JSDT:_module_
.remove_Object_value = remove_Object_value;
// string & Number 處理 -----------------------------------------------
var PATTERN_SPACES = /[ _]+/g,
// Punctuation marks 無實際意義的標點符號
PUNCTUATION_MARKS = /[ _,.;:?'"`~!@#$%^&*()\/\-\[\]<>]+/g;
// String.covers(string_1, string_2, options)
// string_1.covers(string_2, options)
// @see Knuth–Morris–Pratt algorithm
/**
* @return true: 兩者相同, false: 兩者等長但不相同,
* 1: str2為str1之擴展 (str2涵蓋str1), -1: str1為str2之擴展, 2: 兩者等價, 0: 皆非
*/
function String_covers(string_1, string_2, options) {
// 前置作業。
options = library_namespace.setup_options(options);
// 預先處理函數. e.g., 是否忽略大小寫
if (options && typeof options.preprocessor === 'function') {
string_1 = options.preprocessor(string_1);
string_2 = options.preprocessor(string_2);
}
// ignore punctuation marks
if (options.ignore_marks) {
string_1 = string_1.replace(PATTERN_PUNCTUATION_MARKS, '');
string_2 = string_2.replace(PATTERN_PUNCTUATION_MARKS, '');
} else if (options.ignore_spaces) {
// assert: PATTERN_PUNCTUATION_MARKS including PATTERN_SPACES
string_1 = string_1.replace(PATTERN_SPACES, '');
string_2 = string_2.replace(PATTERN_SPACES, '');
}
if (string_1.length === string_2.length) {
if (string_1 === string_2)
return true;
if (!options || !options.force
|| typeof options.equals !== 'function')
// 就算兩者等長但不相同,還是有可能等價。
return false;
}
var result = 1;
// swap: string_2 轉成長的。 (短,長)
if (string_1.length > string_2.length) {
result = string_2, string_2 = string_1, string_1 = result;
result = -1;
}
// string_1 = string_1.replace(/\s+/g, ' ');
string_1 = string_1.chars(true);
string_2 = string_2.chars(true);
var string_1_index = 0, string_2_index = 0, character_1 = string_1[0],
// comparer
equals = options && typeof options.equals === 'function' ? options.equals
: String_covers.equals;
string_2.some(function(character_2, index) {
if (equals(character_1, character_2))
if (++string_1_index === string_1.length) {
string_2_index = index;
return true;
} else
character_1 = string_1[string_1_index];
});
return string_1_index === string_1.length ? result : 0;
}
String_covers.equals = function(a, b) {
return a === b;
};
// compare file name. 比較檔名是否相同。str2 為 str1 添加字元後的擴展?表示兩檔名等價
String_covers.file_name_equals = function(a, b) {
return a === b || /^[ ・.]+$/.test(a + b) || /^[-~]+$/.test(a + b)
|| /^[[\[]+$/.test(a + b) || /^[]\]]+$/.test(a + b);
};
set_method(String, {
covers : String_covers,
similarity : similarity_coefficient
});
function split_String_by_length_(s, l, m) {
var
// less than
lt, lt2,
// great than
gt,
// index
i = 0,
// left count index(left length now)
c = l,
// text now
t = '',
// text index
I = 0;
while (I < s.length) {
// 將lt,gt定在下一label之首尾,i為下一次搜尋起點.label定義:/<.+?>/
if (i !== NOT_FOUND)
if ((lt = s.indexOf('<', i)) !== NOT_FOUND) {
if ((gt = s.indexOf('>', lt + 1)) === NOT_FOUND)
i = lt = NOT_FOUND;
else {
i = gt + 1;
while (lt !== NOT_FOUND
&& (lt2 = s.indexOf('<', lt + 1)) !== NOT_FOUND
&& lt2 < gt)
lt = lt2;
}
} else
i = lt = NOT_FOUND;
if (false && s.indexOf('') !== NOT_FOUND)
alert(i + ',' + lt + ',' + gt + ';' + l + ',' + c + '\n' + t);
if (lt === NOT_FOUND)
gt = lt = s.length;
// 未來:考慮中英文大小,不分隔英文字。前提:'A'<'z'..或許不用
while (I + c <= lt) {
t += s.substr(I, c) + (m ? '\n' : '
');
I += c;
c = l;
}
t += s.slice(I, gt + 1);
c -= lt - I;
I = gt + 1;
}
return t;
}
/*
* 將字串以長l分隔, split String by fixed length. m==0: html用, 1:text.
*/
// split_String_by_length[generateCode.dLK]='split_String_by_length_';
function split_String_by_length(l, m) {
var s = this.valueOf(), t = [], sp = '
';
if (!s || !l || l < 1
// ||!String.charCodeAt: for v5.5
|| !String.fromCharCode)
return m ? s.gText() : s;
// (m):這樣就不用再費心思了.不過既然都作好了,就留著吧..不,還是需要
s = s.turnU(m);
if (s.length <= l)
return s;
if (!m)
s = s.replace(/]*)>/gi, sp);
// deal with line
s = s.split(sp = m ? '\n' : sp);
try {
// 預防JS5不能push
for (var i = 0; i < s.length; i++)
t.push(split_String_by_length_(s[i], l, m));
} catch (e) {
return this.valueOf();
}
return t.join(sp);
}
/**
* 將字串以長 size 切割。
*
* @param {Integer}size
* 切割大小。可以 ((.length / count) | 0 ) 取得。
* @returns {Array} chunks
*
* @see regex - javascript: Split large string
* in n-size chunks - Stack Overflow
*/
function chunk(size) {
if ((size |= 0) < 1)
return [ this ];
var index = 0, length = this.length, result = [];
for (; index < length; index += size)
result.push(this.substr(index, size));
return result;
}
// ---------------------------------------------------------------------//
var no_string_index;
// for IE 6. Or use .chars(), .split(''), .charAt()
try {
no_string_index = '01';
no_string_index = !(no_string_index[1] === '1');
} catch (e) {
// e.g., IE 6
no_string_index = true;
}
// To test if RegExp.prototype has unicode flag:
// if ('unicode' in RegExp.prototype) {}
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp
var has_spread_syntax;
try {
has_spread_syntax = eval("[...'ab'].join(',')==='a,b'");
} catch (e) {
}
var PATTERN_char, PATTERN_char_with_combined, split_by_code_point;
try {
// using [\s\S] or [^] or /./s
// @see https://github.com/tc39/proposal-regexp-dotall-flag
// tested @ Edge/12.10240
PATTERN_char = new RegExp(/[\s\S]/.source, 'ug');
// 注意:因為/./u會切分[[en:Combining character#Unicode ranges]],
// 因此對組合字符,得要另外處理。
PATTERN_char_with_combined = new RegExp(
/[\s\S][\u0300-\u036F\uFE20-\uFE2F\u20D0-\u20FF\u1DC0-\u1DFF\u1AB0-\u1AFF]*/.source,
'ug');
/**
* 對於可能出現 surrogate pairs 的字串,應當以此來取代 .split('')!
* handling of surrogate pairs / code points
*
* TODO: 利用.split('')增進效率。
*
* @see https://en.wikipedia.org/wiki/UTF-16#Code_points_U.2B10000_to_U.2B10FFFF
* http://teppeis.hatenablog.com/entry/2014/01/surrogate-pair-in-javascript
*/
split_by_code_point = function(with_combined) {
// if (has_spread_syntax && !with_combined) return [...this];
return this.match(with_combined ? PATTERN_char_with_combined
: PATTERN_char)
|| [];
// show HEX:
// .map(function(char){return
// char.codePointAt(0).toString(0x10).toUpperCase();});
};
} catch (e) {
// 舊版。
var PATTERN_surrogate = /[\uD800-\uDBFF][\uDC00-\uDFFF]/;
PATTERN_char = /[\uD800-\uDBFF][\uDC00-\uDFFF]|[\s\S]/g;
PATTERN_char_with_combined = /(?:[\uD800-\uDBFF][\uDC00-\uDFFF]|[\s\S])[\u0300-\u036F\uFE20-\uFE2F\u20D0-\u20FF\u1DC0-\u1DFF\u1AB0-\u1AFF]*/g;
split_by_code_point = function(with_combined) {
return with_combined // || !PATTERN_surrogate
|| PATTERN_surrogate.test(this) ? this
.match(with_combined ? PATTERN_char_with_combined
: PATTERN_char)
|| [] : this.split('');
};
}
// String.prototype.codePoints()
// http://docs.oracle.com/javase/8/docs/api/java/lang/CharSequence.html#codePoints--
function codePoints() {
return split_by_code_point.call(this)
// need data.code.compatibility!
.map(function(char) {
return char.codePointAt(0);
});
}
/**
* get string between head and foot.
* 取得 text 中,head 與 foot 之間的字串。不包括 head 與 foot。
* 可以 [3] last index 是否回傳 NOT_FOUND (-1) 檢測到底是有找到,只是回傳空字串,或是沒找到。
*
* TODO: {RegExp}head, {RegExp}foot
*
* @example
// More examples: see /_test suite/test.js
*
*
* @param {Array}data [
* text 欲篩選字串, head 首字串, foot 尾字串, start index ]
*
* @returns [ 0: text 欲篩選字串, 1: head 首字串, 2: foot 尾字串, 3: last index, 4:
* head 與 foot 之間的字串 ]
*
* @since 2014/8/4 21:34:31
*/
function get_intermediate_Array(data) {
if (data && data[0]
// = String(data[0])
) {
// start index of intermediate.
var index;
if (!data[1])
index = 0;
else if ((index = data[0].indexOf(data[1], data[3])) !== NOT_FOUND)
index += data[1].length;
// library_namespace.debug('head index: ' + index, 4);
if ((data[3] = index) !== NOT_FOUND
&& (!data[2] || (data[3] = data[0].indexOf(data[2], index)) !== NOT_FOUND))
data[4] = data[2] ? data[0].slice(index, data[3])
//
: index ? data[0].slice(index)
//
: data[0];
}
return data;
}
/**
* get string between head and foot.
* 取得 text 中,head 與 foot 之間的字串。不包括 head 與 foot。
* 回傳 undefined 表示沒找到。只是回傳空字串表示其間為空字串。
*
* TODO: lastIndexOf()
*
* @example
// More examples: see /_test suite/test.js
*
*
* @param {String}text
* 欲篩選字串。
* @param {String}[head]
* 首字串。 TODO: RegExp
* @param {String}[foot]
* 尾字串。 TODO: RegExp
*
* @returns head 與 foot 之間的字串。undefined 表示沒找到。
*
* @since 2014/7/26 11:28:18
*/
function get_intermediate(text, head, foot, index, return_data) {
if (text
// = String(text)
) {
// start index of intermediate.
if (!head) {
index = 0;
} else if ((index = text.indexOf(head, index | 0)) !== NOT_FOUND) {
index += head.length;
}
library_namespace.debug('head index: ' + index, 4);
if (index !== NOT_FOUND && (!foot || (foot = foot.tail
// 可以用 {tail:'foot'} 來從結尾搜尋。from tail
? text.lastIndexOf(foot.tail)
// 正常:從頭搜尋。
: text.indexOf(foot, index)) !== NOT_FOUND)) {
head = foot ? text.slice(index, foot) : index > 0 ? text
.slice(index) : text;
return return_data ?
// [ last index, head 與 foot 之間的字串 ]
[ foot || text.length, head ] : head;
}
}
if (return_data) {
return [ NOT_FOUND, ];
}
}
_.get_intermediate = get_intermediate;
/**
*
// 推薦 use .find_between(), 見下一段例
var data = html.find_between('>', '<'), text;
text = data.search().search().search().toString();
while (data.next()) {
text = data.toString();
}
// method 1:
while (term = terms.next()) {
term.toString();
}
// method 2:
while (typeof (text = data.search()) === 'string') {
;
}
*/
function next_intermediate(index) {
if (this[3] !== NOT_FOUND) {
var data = get_intermediate(this[0], this[1], this[2], this[3],
true);
library_namespace.debug('Get [' + data + ']', 4);
if ((this[3] = data[0]) !== NOT_FOUND) {
this[4] = data[1];
return this;
}
}
}
// find out next
// 為避免 overwrite Array.prototype.find(),因此改名為 search。
function search_intermediate(index) {
this.next();
return this;
}
function intermediate_result() {
return this[3] !== NOT_FOUND && this[4] || '';
}
function intermediate_between() {
return String.prototype.between.apply(this.toString(), arguments);
}
// 2017/1/3 13:48:21 API change:
// set_intermediate()→find_between()
// WARNING: 請盡可能採用find_between(),勿使用deprecated_find_between()。
// 要用此函數,不如直接採用RegExp.prototype.exec()比較快。
function deprecated_find_between(head, foot, index) {
var data = [ this, head, foot, index | 0 ];
data.next = next_intermediate;
data.search = search_intermediate;
data.toString = intermediate_result;
// data.between = intermediate_between;
return data;
}
/**
* 2017/2/15 16:5:0 API change: rename to .find_between
* all_between()→find_between()
*
* TODO:
* return {Iterator} of all between
*
* @see http://jsrocks.org/cn/2015/09/javascript-iterables-and-iterators/
* http://es6.ruanyifeng.com/#docs/symbol
*
*
// 推薦用法
var get_next_between = html.find_between('>', '<'), text;
while ((text = get_next_between()) !== undefined) {
text;
}
*/
// 採用String.prototype.indexOf()以增進速度,超越RegExp.prototype.exec()。
// @see /_test suite/test.js
function find_between(head, foot, index) {
// start index
index |= 0;
// assert: !!head && !!foot
// && typeof head === 'string' && typeof foot === 'string'
var text = this, head_length = head.length, foot_length = foot.length;
function get_next_between() {
if (index !== NOT_FOUND
&& (index = text.indexOf(head, index)) !== NOT_FOUND) {
var foot_index = text.indexOf(foot, index += head_length);
if (foot_index !== NOT_FOUND) {
var token = text.slice(index, foot_index);
// +foot_length: search next starts from end of foot
index = foot_index + foot_length;
return token;
}
// 接下來皆無foot,則即使再存有head亦無效。
index = NOT_FOUND;
}
// return undefined;
}
return get_next_between;
}
// return {Array}all matched
function all_between(head, foot, index) {
// start index
index |= 0;
// assert: !!head && !!foot
// && typeof head === 'string' && typeof foot === 'string'
var matched = [], head_length = head.length, foot_length = foot.length;
while (index !== NOT_FOUND
&& (index = this.indexOf(head, index)) !== NOT_FOUND) {
var foot_index = this.indexOf(foot, index += head_length);
if (foot_index === NOT_FOUND) {
// 接下來皆無foot,則即使再存有head亦無效。
break;
}
matched.push(this.slice(index, foot_index));
// +foot_length: search next starts from end of foot
index = foot_index + foot_length;
}
return matched;
}
// callback(token, index, foot_index);
// 沒有輸入foot的話,則會把head拿來當作foot。
// TODO: {RegExp}head, foot
function each_between(head, foot, callback, thisArg, index) {
if (Array.isArray(head) && typeof foot === 'function') {
// shift arguments.
index = thisArg;
thisArg = callback;
callback = foot;
// for head: [head, foot]
if (Array.isArray(head) && head.length === 2) {
foot = head[1];
head = head[0];
} else {
if (typeof head === 'string') {
// 每個head切一段?
library_namespace
.error('If you needs cut string into small pieces, please using string.split().slice(1).forEach() !');
}
throw new TypeError('Invalid head type');
}
}
// this.all_between(head, foot, index).forEach(callback, thisArg);
// start index
index |= 0;
// assert: !!head && !!foot
// && typeof head === 'string' && typeof foot === 'string'
var head_length = head ? head.length : 0, foot_length = foot ? foot.length
: 0, foot_index;
if (!thisArg) {
thisArg = this;
}
while (foot || !(foot_index > 0) ? index !== NOT_FOUND
// allow null header
&& (!head || (index = this.indexOf(head, index)) !== NOT_FOUND)
: (index = foot_index) < this.length) {
foot_index = this.indexOf(foot || head, index += head_length);
if (foot_index === NOT_FOUND) {
// 接下來皆無(foot||head),則即使再存有head亦無效。
if (foot) {
break;
}
foot_index = this.length;
}
callback.call(thisArg, this.slice(index, foot_index), index,
foot_index);
// +foot_length: search next starts from end of foot
index = foot_index + foot_length;
}
}
// =====================================================================================================================
function set_bind(handler, need_meny_arg) {
if (typeof need_meny_arg !== 'boolean')
need_meny_arg = handler.length > 1;
return need_meny_arg ? function(args) {
if (arguments.length < 2)
return handler(this, args);
// Array.from()
args = Array.prototype.slice.call(arguments);
args.unshift(this);
return handler.apply(handler, args);
} : function(args) {
return handler(this, args);
};
}
function set_bind_valueOf(handler, need_meny_arg) {
var ReturnIfAbrupt = function(v) {
// 尚有未竟之處。
switch (library_namespace.is_type(v)) {
case 'Boolean':
case 'Number':
case 'String':
v = v.valueOf();
}
return v;
};
return need_meny_arg ? function(args) {
if (arguments.length < 2)
return handler(ReturnIfAbrupt(this), args);
// Array.from()
args = Array.prototype.slice.call(arguments);
args.unshift(ReturnIfAbrupt(this));
return handler.apply(handler, args);
} : function(args) {
return handler(ReturnIfAbrupt(this), args);
};
}
// ReturnIfAbrupt
var need_valueOf = false;
String.prototype.test_valueOf = (function() {
return function() {
if (typeof this !== 'string')
if (this && typeof this.valueOf() === 'string')
need_valueOf = true;
else
library_namespace.error('set_bind: 無法判別是否該使用 .valueOf()!');
};
})();
'.'.test_valueOf();
try {
delete String.prototype.test_valueOf;
} catch (e) {
String.prototype.test_valueOf = undefined;
}
_.set_bind = need_valueOf ? set_bind_valueOf : set_bind;
/**
*
* @param {Array}array
* @param {Function}[comparator]
* @returns
*/
function unique_and_sort_Array(array, comparator) {
if (comparator) {
array.sort(comparator);
} else {
array.sort();
}
var i = 1, j = -1;
for (; i < array.length; i++)
if (array[i] === array[i - 1]) {
if (j < 0)
j = i;
} else if (j >= 0)
array.splice(j, i - j), i = j, j = -1;
if (j >= 0)
array.splice(j, i - j);
return array;
}
// -------------------------------------------
var type_index = {
string : 0,
number : 1,
boolean : 2,
'undefined' : 3
};
function deprecated_unique_Array(sorted) {
var array = [];
if (sorted) {
var last;
this.forEach(function(element) {
if (last !== element)
array.push(element);
last = element;
});
} else {
// 以 hash 純量 index 加速判別是否重複。
var hash = Object.create(null);
this.forEach(function(element) {
var type = typeof element;
// 能確保順序不變。
if (type in type_index) {
// TODO: -0
if (!(element in hash)
|| !(type_index[type] in hash[element])) {
array.push(element);
(hash[element] = [])[type_index[type]] = null;
}
} else if (array.indexOf(element) === NOT_FOUND)
array.push(element);
});
}
return array;
}
// -------------------------------------------
// @see cardinal_1()
function unique_sorted_Array(get_key) {
var latest_key, configured;
var unique_array = this.filter(function(element) {
var key = get_key ? get_key(element) : element;
var is_different = configured ? !Object.is(latest_key, key)
: (configured = true);
latest_key = key;
return is_different;
});
return unique_array;
}
/**
* 取交集 array_1 ∩ array_2
*
* @param {Array}array_1
* array 1
* @param {Array}array_2
* array 2
* @param {Function}[key_of_item]
* Function to get key of item.
* @param {Boolean}[sorted]
* true: array_1, array_2 are sorted.
*
* @returns {Array}intersection of array_1 and array_2
*/
function Array_intersection(array_1, array_2, key_of_item, sorted) {
if (key_of_item === true) {
sorted = true;
key_of_item = null;
}
if (!sorted) {
var sort_function = typeof key_of_item === 'function' ? function(
_1, _2) {
return general_ascending(key_of_item(_1), key_of_item(_2));
} : general_ascending;
array_1 = array_1.clone().sort(sort_function);
array_2 = array_2.clone().sort(sort_function);
}
// console.log([array_1, array_2]);
var index_of_array_1 = 0, index_of_array_2 = 0,
// Object.create(array_1)
result = [];
for (; index_of_array_1 < array_1.length
&& index_of_array_2 < array_2.length; index_of_array_1++) {
var item = array_1[index_of_array_1];
if (key_of_item)
item = key_of_item(item);
do {
var item_2 = array_2[index_of_array_2];
if (key_of_item)
item_2 = key_of_item(item_2);
if (item_2 < item) {
index_of_array_2++;
continue;
}
if (item_2 === item) {
// 相同元素最多取 array_1, array_2 之最小個數。
index_of_array_2++;
result.push(item);
}
break;
} while (index_of_array_2 < array_2.length);
}
return result;
}
var has_native_Map = !Map[library_namespace.env.not_native_keyword];
// 警告: 相同的 key 只會留下一個 item!
function Array_intersection_Map(array_1, array_2, key_of_item, sorted) {
if (key_of_item === true) {
sorted = true;
key_of_item = null;
}
if (sorted)
return Array_intersection(array_1, array_2, key_of_item, sorted);
// @see function unique_Array()
var map = new Map;
function set_item(item) {
var key = key_of_item ? key_of_item(item) : item;
if (!map['has'](key))
map['set'](key, /* item */null);
}
array_1.forEach(set_item);
var result = array_2.filter(function(item) {
var key = key_of_item ? key_of_item(item) : item;
return map['has'](key);
});
return result;
}
/**
* Count occurrence of $search in string.
* 計算 string 中出現 search 之次數。
*
* 用 s/// 亦可 @ perl
*
* @param {String}string
* 在 string 中搜尋。
* @param {String|RegExp}search
* 搜尋對象。
* @param {Integer}[position]
* 開始搜尋的位置(start index)。
*
* @returns {Integer} string 中出現 search 之次數。
*
* @see http://jsperf.com/count-string-occurrence-in-string,
* http://jsperf.com/count-the-number-of-occurances-in-string
* http://stackoverflow.com/questions/881085/count-the-number-of-occurences-of-a-character-in-a-string-in-javascript
*
* @since 2013/2/13 11:12:38 重構
* @since 2014/8/11 12:54:34 重構
*/
function count_occurrence(string, search, position) {
// 正規化 position 成 index (0, 1, ..)。
// 注意:過大的 position 在 |0 時會變成負數!
if (isNaN(position) || (position |= 0) < 0)
position = 0;
if (position > 0)
string = string.slice(position);
return string.split(search).length - 1;
// 以下放棄。
if (library_namespace.is_RegExp(search))
return (string = (position > 0 ? string.slice(position) : string)
.match(search)) ? string.length : 0;
// 正規化 search。
if (!search || !(search = String(search)))
return 0;
// 使用 String.prototype.indexOf (searchString, position)
var count = 0, length = search.length;
while ((position = string.indexOf(search, position)) !== NOT_FOUND)
count++, position += length;
return count;
}
function determine_line_separator(text) {
var matched, PATTERN = /\r?\n|\r/g, rn = 0, r = 0, n = 0;
while (matched = PATTERN.exec(text)) {
if (matched[0] === '\r\n')
rn++;
else if (matched[0] === '\n')
n++;
else
r++;
}
if (rn > n && rn > r) {
return '\r\n';
}
if (n > r && n > rn) {
return '\n';
}
if (r > n && r > rn) {
return '\r';
}
return library_namespace.env.line_separator;
}
_.determine_line_separator = determine_line_separator;
/**
* 取至小數 digits 位, 肇因: JScript即使在做加減運算時,有時還是會出現 3*1.6=4.800000000000001,
* 2.4/3=0.7999999999999999 等數值。此函數可取至 1.4 與 0.1。 c.f., round()
*
* @see Number.prototype.toLocaleString()
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString
*
* @param {Number}[decimals]
* 1,2,...: number of decimal places shown
* @param {Number}[max]
* maximum decimals. max===0:round() else floor()
*
* @return {Number}取至小數 digits 位後之數字。
*
* @see https://bugzilla.mozilla.org/show_bug.cgi?id=5856
* IEEE754の丸め演算は最も報告されるES3「バグ」である。 http://www.jibbering.com/faq/#FAQ4_6
* http://en.wikipedia.org/wiki/Rounding
*
* @example
* var d=new Date,v=0.09999998,i=0,a;
* for(;i<100000;i++)a=v.to_fixed(2);
* alert(v+'\n→'+a+'\ntime:'+format_date(new Date-d));
*
*
* @_memberOf _module_
*/
function slow_to_fixed(decimals, max) {
var value = this.valueOf(), i, negative;
if (isNaN(value))
return value;
if (isNaN(decimals) || (decimals = Math.floor(decimals)) < 0) {
// TODO: using Number.EPSILON
// 內定:10位
decimals = 10;
} else if (decimals > 20)
// 16: Math.ceil(Math.abs(Math.log10(Number.EPSILON)))
decimals = 16;
if (!max && Number.prototype.toFixed) {
// 去掉末尾的0。必須預防 `(49.5).to_fixed(0)`。
return parseFloat(value.toFixed(decimals).replace(/\.0+$/, ''));
}
if (value < 0)
// 負數
negative = true, value = -value;
value = value.toString(10);
i = value.indexOf('e');
if (i !== NOT_FOUND) {
// e-\d: 數字太小.
return value.charAt(i + 1) === '-' ? 0 : value;
}
library_namespace.debug(value, 2);
// TODO: using +.5 的方法
// http://clip.artchiu.org/2009/06/26/%E4%BB%A5%E6%95%B8%E5%AD%B8%E7%9A%84%E5%8E%9F%E7%90%86%E8%99%95%E7%90%86%E3%80%8C%E5%9B%9B%E6%8D%A8%E4%BA%94%E5%85%A5%E3%80%8D/
i = value.indexOf('.');
if (i !== NOT_FOUND && i + 1 + decimals < value.length) {
if (max) {
value = '00000000000000000000' + Math.round(
//
value.slice(0, i++) + value.substr(i, decimals)
//
+ '.' + value.charAt(i + decimals)).toString(10);
if (value != 0)
library_namespace.debug(value + ',' + value.length + ','
+ decimals + ','
+ value.substr(0, value.length - decimals) + ','
+ value.substr(max), 2);
max = value.length - decimals;
value = value.slice(0, max) + '.' + value.substr(max);
} else
value = value.slice(0, i + 1 + decimals);
}
return value ? parseFloat((negative ? '-' : '') + value) : 0;
}
// (15*1.33).to_fixed()===19.95
// old:very slow
function deprecated_to_fixed(d, m) {
var v = this.valueOf(), i;
if (isNaN(v))
return v;
if (isNaN(d) || d < 0)
d = 8; // 內定:8位
if (!m) {
v = Math.round(Math.pow(10, d) * v);
v = v < 0 ? '-' + '0'.repeat(d) + (-v) : '0'.repeat(d) + v;
v = v.slice(0, i = v.length - d) + '.' + v.substr(i);
} else if (i = (v = '' + v).indexOf('.') + 1)
v = v.slice(0, i + (d ? d : d - 1));
return parseFloat(v || 0);
}
if (false) {
var addDenominationSet = {
a : ',,,,'.split(',')
},
// 增添單位
addDenomination = function addDenomination(a, b) {
TODO;
};
}
/**
* 取至小數 digits 位,
* 肇因: JScript即使在做加減運算時,有時還是會出現 3*1.6=4.800000000000001,
* 2.4/3=0.7999999999999999 等數值。此函數可取至 1.4 與 0.1,避免 round-off error。
* c.f., Math.round()
*
* @param {Number}[decimals]
* 1,2,..: number of decimal places shown.
* @return 取至小數 digits 位後之數字。
*
* @see https://bugzilla.mozilla.org/show_bug.cgi?id=5856
* IEEE754の丸め演算は最も報告されるES3「バグ」である。 http://www.jibbering.com/faq/#FAQ4_6
* http://en.wikipedia.org/wiki/Rounding
*
* @example
var d = new Date, v = 0.09999998, i = 0, a;
for (; i < 100000; i++)
a = v.to_fixed(2);
alert(v + '\n→' + a + '\ntime:' + format_date(new Date - d));
*
*
* @_memberOf _module_
*/
function native_to_fixed(decimals) {
// (21.1*2006).toFixed(11) === "42326.60000000001"
// (21.1*200006).toFixed(9)==="4220126.600000001"
// 256738996346789.1.toFixed(10)==="256738996346789.0937500000"
return decimals <= 0 ? Math.round(this) : parseFloat(this
.toFixed(decimals || 12));
}
var to_fixed = slow_to_fixed;
_.to_fixed = slow_to_fixed;
/**
* non-negative modulo, positive modulo. 保證 modulo 結果 >=0。 轉成最接近 0 之正 index。
*
* @param {Number}dividend
* 被除數。
* @param {Number}divisor
* 除數。
*
* @returns {Number}remainder 餘數
*/
function non_negative_modulo(dividend, divisor) {
if (false)
return ((dividend % divisor) + divisor) % divisor;
if ((dividend %= divisor) < 0)
dividend += divisor;
return dividend;
}
/**
*
var sourceF=WScript.ScriptName,targetF='test.js';simpleWrite('tmp.js',alert+'\n'+simpleRead+'\n'+simpleWrite+'\nvar t="",ForReading=1,ForWriting=2,ForAppending=8\n,TristateUseDefault=-2,TristateTrue=-1,TristateFalse=0\n,WshShell=WScript.CreateObject("WScript.Shell"),fso=WScript.CreateObject("Scripting.FileSystemObject");\nt='+'data.native'(simpleRead(sourceF),80)+';\nsimpleWrite("'+targetF+'",t);//eval(t);\nalert(simpleRead("'+sourceF+'")==simpleRead("'+targetF+'")?"The same (test dQuote OK!)":"Different!");');//WshShell.Run('"'+getFolder(WScript.ScriptFullName)+targetF+'"');
// determine quotation mark:輸入字串,傳回已加'或"之字串。
dQuote.qc=function(c,C){
return c<32?'\\'+c:C;
};
TODO:
use JSON.stringify()
*
* @see JSON.stringify()
*/
// string,分割長度(會採用'~'+"~"的方式),separator(去除末尾用)
function dQuote(s, len, sp) {
var q;
s = String(s);
if (sp)
// 去除末尾之sp
s = s.replace(new RegExp('[' + sp + ']+$'), '');
if (isNaN(len) || len < 0)
len = 0;
if (len) {
var t = '';
for (; s;)
t += '+' + dQuote(s.slice(0, len))
// '\n':line_separator
+ '\n', s = s.substr(len);
return t.substr(1);
}
// test用
if (false && len) {
var t = '';
for (; s;)
t += 't+=' + dQuote(s.slice(0, len)) + '\n', s = s.substr(len);
return t.substr(3);
}
s = s.replace(/\\/g, '\\\\').replace(/\r/g, '\\r')
//
.replace(/\n/g, '\\n')
// \b,\t,\f
// 轉換控制字符
.replace(/([\0-\37\x7f\xff])/g, function($0, $1) {
var c = $1.charCodeAt(0);
return c < 8 * 8 ? '\\' + c.toString(8) : '\\x'
//
+ (c < 0x10 ? '0' : '') + c.toString(0x10);
})
// .replace(/([\u00000100-\uffffffff])/g, function($0, $1) {})
;
if (false) {
q = s.length;
while (s.charAt(--q) == sp)
;
s = s.slice(0, q + 1);
}
if (s.indexOf(q = "'") !== NOT_FOUND)
q = '"';
if (s.indexOf(q) !== NOT_FOUND) {
library_namespace.debug(
//
"Can't determine quotation mark, the resource may cause error.\n"
+ s);
s = s.replace(new RegExp(q = "'", 'g'), "\\'");
}
return q + s + q;
}
_.dQuote = dQuote;
// ----------------------------------------------------
// 可以處理 circular 的 JSON.stringify(),以及可以復原的 JSON.parse()。
// 盡量找不會用到,又不包含特殊字元的字串作為識別碼。
var default_KEY_reference = 'REF|';
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify
// https://github.com/WebReflection/circular-json/blob/master/src/circular-json.js
// https://stackoverflow.com/questions/11616630/json-stringify-avoid-typeerror-converting-circular-structure-to-json
function create_reference_map(object, recover_value, KEY_reference) {
if (KEY_reference === undefined) {
KEY_reference = default_KEY_reference;
}
// e.g., reference_map[{k:0}] = 'REF|0'
// reference_list = [ {k:0}, ... ]
var reference_map = new Map, reference_hash = Object.create(null), index = 0;
return JSON.stringify(object, function(key, value) {
// console.log([ this, key, value ]);
// only objects may circular
if (typeof value === 'object') {
// return a reference to the value if it had beed processed
if (reference_map.has(value))
return reference_map.get(value);
// record the value
// find a key that is not in reference_hash or we will be
// confused if there are duplicate keys
while ((key = KEY_reference + index++) in reference_hash) {
if (recover_value) {
throw new Error('create_reference_map: Invalid index '
+ key + '. It should not happen.');
}
}
// assert: typeof key!=='object', or will be traversed by
// JSON.stringify()
reference_hash[key] = value;
reference_map.set(value, key);
} else if (typeof value !== 'object' && recover_value
&& (value in reference_hash)) {
// recover value
this[key] = reference_hash[value];
} else if (typeof value === 'string' && !recover_value
&& value.startsWith(KEY_reference)) {
throw new Error('create_reference_map: '
//
+ 'You should specify another KEY_reference instead of '
+ JSON.stringify(KEY_reference) + '. Confused value: '
+ value);
}
return value;
});
}
// KEY_reference: Any value of object wont starts with KEY_reference
// 若是有任何一個value包含了 default_KEY_reference + 數字的格式,則需要另外指定 KEY_reference。
function stringify_circular(object, KEY_reference) {
return create_reference_map(object, false, KEY_reference);
}
function parse_circular(json_string, KEY_reference) {
var parsed = JSON.parse(json_string);
// stringify again, using the same algorithm as JSON.stringify() in
// JSON.stringify_circular().
create_reference_map(parsed, true, KEY_reference);
return parsed;
}
// old JScript engine do not have JSON
if (typeof JSON === 'object' && typeof JSON.parse === 'function') {
set_method(JSON, {
stringify_circular : stringify_circular,
parse_circular : parse_circular
});
}
// ----------------------------------------------------
_// JSDT:_module_
.
/**
* check input string send to SQL server
*
* @param {String}
* string input string
* @return {String} 轉換過的 string
* @since 2006/10/27 16:36
* @see from lib/perl/BaseF.pm (or program/database/BaseF.pm)
* @_memberOf _module_
*/
checkSQLInput = function(string) {
if (!string)
return '';
// 限制長度 maximum input length
if (maxInput && string.length > maxInput)
string = string.slice(0, maxInput);
return string
// for \uxxxx
.replace(/\\u([\da-f]{4})/g, function($0, $1) {
return String.fromCharCode($1);
}).replace(/\\/g, '\\\\')
// .replace(/[\x00-\x31]/g,'')
.replace(/\x00/g, '\\0')
// .replace(/\x09/g,'\\t')
// .replace(/\x1a/g,'\\Z')
// .replace(/\r\n/g,' ')
.replace(/\r/g, '\\r').replace(/\n/g, '\\n')
// .replace(/"/g,'\\"')
.replace(/'/g, "''");
};
_// JSDT:_module_
.
/**
* 轉換字串成數值,包括分數等。分數亦將轉為分數。
*
* @param {String}
* number 欲轉換之值。
* @return
* @_memberOf _module_
*/
parse_number = function(number) {
var m = typeof number;
if (m === 'number')
return number;
if (!number || m !== 'string')
return NaN;
number = number.replace(/(\d),(\d)/g, '$1$2');
if (m = number.match(/(-?[\d.]+)\s+([\d.]+)\/([\d.]+)/)) {
var p = parseFloat(m[1]), q = parseFloat(m[2]) / parseFloat(m[3]);
return p + (m[1].charAt(0) === '-' ? -q : q);
}
if (m = number.match(/(-?[\d.]+)\/([\d.]+)/))
// new quotient(m[1],m[2])
return parseFloat(m[1]) / parseFloat(m[2]);
if (false) {
try {
return isNaN(m = parseFloat(number)) ?
// TODO: security hole
eval(number) : m;
} catch (e) {
return m;
}
}
};
/**
* filter object. .map() of {Object}
* for Object.filter()
*
* @param {Object}object
* object to filter
* @param {Function}filter
* callback/receiver to filter the value.
* filter(value, key, object) is true: will be preserved.
*
* @returns filtered object
*/
function Object_filter(object, filter) {
if (typeof filter !== 'function' || typeof object !== 'object')
return object;
var key, delete_keys = [];
for (key in object) {
if (!filter(object[key], key, object))
// 在這邊 delete object[key] 怕會因執行環境之不同實作方法影響到 text 的結構。
delete_keys.push(key);
}
if (delete_keys.length > 0)
delete_keys.forEach(function(key) {
delete object[key];
});
}
var has_spread_operator, clone_Object;
try {
has_spread_operator = eval('clone_Object=function(object){return {...object};};');
} catch (e) {
// TODO: handle exception
}
/**
* clone object.
* for Object.clone()
*
* @param {Object}object
* object to clone
* @param {Boolean}deep
* deep clone / with trivial
*
* @returns {Object}cloned object
*
* @see clone() @ CeL.data
* @see https://www.bram.us/2018/01/10/javascript-removing-a-property-from-an-object-immutably-by-destructuring-it/
* @see `return {...object}` :
* https://juejin.im/post/5b2a186cf265da596d04a648
*/
var Object_clone = function clone(object, deep, copy_to) {
if (!object || typeof object !== 'object') {
// assert: object is 純量。
return object;
}
// for read-only??
// return Object.create(object);
if (deep) {
// @see
// http://stackoverflow.com/questions/122102/what-is-the-most-efficient-way-to-clone-an-object
return JSON.parse(JSON.stringify(object));
}
if (!copy_to) {
if (Array.isArray(object)) {
// for {Array}
return object.clone();
}
if (clone_Object) {
return clone_Object(object);
}
}
// shallow clone Object.
return Object.assign(copy_to || Object.create(
// copy prototype
Object.getPrototypeOf(object)), object);
};
/**
* Test if no property in the object.
* for Object.is_empty(), Object.isEmpty()
*
* for ArrayLike, use .length instead. This method includes non-numeric
* property.
*
* @param {Object}object
* object to test
*
* @returns {Boolean}the object is empty.
*
* @see CeL.is_empty_object()
* @see http://stackoverflow.com/questions/3426979/javascript-checking-if-an-object-has-no-properties-or-if-a-map-associative-arra
*/
function Object_is_empty(object) {
if (object !== null)
for ( var key in object) {
if (!Object.hasOwn || Object.hasOwn(object, key)) {
return false;
}
}
return true;
}
// Object.clean() 從 object 中移除所有項目。
// 通常用於釋放記憶體。 Usually used to release memory.
// cf. array.truncate()
// {Boolean|Natural}deepth=1: 清理到 object 底下底幾層。
function Object_clean(object, deepth) {
if (!object || typeof object !== 'object')
return;
if (deepth > 0 && deepth !== true)
deepth--;
Object.keys(object).forEach(function(key) {
// 當有引用重要物件時,不應該採用 deep。
if (deepth)
Object_clean(object[key], deepth);
delete object[key];
});
}
// Object.reverse_key_value({a:b}) → {b:a}
function Object_reverse_key_value(object) {
var new_object = Object.create(null);
if (!object)
return new_object;
for ( var key in object) {
if (!Object.hasOwn || Object.hasOwn(object, key)) {
new_object[object[key]] = key;
}
}
return new_object;
}
/**
* Count properties of the object.
* for Object.size()
*
* for ArrayLike, use .length instead. This method will count non-numeric
* properties.
*
* @param {Object}object
* object to count properties
*
* @returns {Boolean}properties count
*
* @see http://stackoverflow.com/questions/5223/length-of-a-javascript-object-that-is-associative-array
*/
function Object_size(object) {
if (object === null)
return 0;
var count = 0;
for ( var key in object) {
if (!Object.hasOwn || Object.hasOwn(object, key)) {
count++;
}
}
return count;
}
function the_same_content(value_1, value_2) {
if (value_1 === value_2)
return true;
// @see Object.is()
// check NaN. May use Number.isNaN() as well.
if (value_1 !== value_1 && value_2 !== value_2)
return true;
if (typeof value_1 !== 'object' || typeof value_2 !== 'object')
return false;
// TODO: adapt for Map, Set, ...
var keys_1 = Object.keys(value_1), keys_2 = Object.keys(value_2);
if (keys_1.length !== keys_2.length)
return false;
for (var index = 0; index < keys_1.length; index++) {
var key = keys_1[index];
if (!(key in value_2)
|| !the_same_content(value_1[key], value_2[key]))
return false;
}
return true;
}
set_method(Object, {
filter : Object_filter,
clone : Object_clone,
is_empty : Object_is_empty,
the_same_content : the_same_content,
clean : Object_clean,
reverse_key_value : Object_reverse_key_value,
size : Object_size
});
// 非 deep, 淺層/表面 clone/copy: using Array.from().
var Array_clone = Array.prototype.slice;
Array_clone = Array_clone.call.bind(Array_clone);
(function() {
var a = [ 2, 3 ], b = Array_clone(a);
if (b.join(',') !== '2,3')
Array_clone = function clone() {
// Array.prototype.slice.call(array);
// library_namespace.get_tag_list():
// Array.prototype.slice.call(document.getElementsByTagName(tagName));
return this.slice(0);
};
})();
// or use Array.from(): https://kknews.cc/zh-tw/code/x625ppg.html
// Array.prototype.clone = function() { return Array.from(this); };
// 將 element_toPush 加入 array_pushTo 並篩選重複的(本來已經加入的並不會變更)
// array_reverse[value of element_toPush]=index of element_toPush
function pushUnique(array_pushTo, element_toPush, array_reverse) {
if (!array_pushTo || !element_toPush)
return array_pushTo;
var i;
if (!array_reverse)
for (array_reverse = new Array, i = 0; i < array_pushTo; i++)
array_reverse[array_pushTo[i]] = i;
if (typeof element_toPush != 'object')
i = element_toPush, element_toPush = new Array, element_toPush
.push(i);
var l;
for (i in element_toPush)
if (!array_reverse[element_toPush])
// array_pushTo.push(element_toPush),array_reverse[element_toPush]=array_pushTo.length;
array_reverse[array_pushTo[l = array_pushTo.length] = element_toPush[i]] = l;
return array_pushTo;
}
/**
* append/merge to original Array.
* Array.prototype.concat does not change the existing arrays, it only
* returns a copy of the joined arrays.
*
* @param {Array}array
* 添加至此 Array list.
* @param {Array}list
* 欲添加的 Array list. TODO: 若非Array,則會當作單一元素 .push()。
* @param {Integer}index
* 從 list[index] 開始 append。
*
* @returns this
*/
function append_to_Array(list, index) {
if (Array.isArray(list) && (index ? 0 < (index = parseInt(index))
// TODO: for index < 0
&& index < list.length : list.length > 0)) {
if (index > 0) {
list = Array.prototype.slice.call(list, index);
}
// 警告: 當 list 太大時可能產生 RangeError: Maximum call stack size exceeded
try {
Array.prototype.push.apply(this, list);
} catch (e) {
while (index < list.length)
Array.prototype.push.call(this, list[index++]);
}
// return this.concat(list);
// return Array.prototype.concat.apply(this, arguments);
}
return this;
}
// count elements that has something
if (false) {
Array.prototype.count_things = function() {
return this.reduce(function(count, e) {
return e ? count + 1 : count;
}, 0);
};
}
// Array.prototype.frequency()
// values count, 發生率
function array_frequency(select_max, target) {
var count = 0;
if (target !== undefined) {
this.forEach(library_namespace.is_RegExp(pattern) ? function(item) {
if (target.test(item))
count++;
} : function(item) {
if (item === target)
count++;
});
return count;
}
// new Map()
var hash = Object.create(null);
if (!select_max) {
this.forEach(function(item) {
if (item in hash) {
hash[item]++;
} else
hash[item] = 1;
});
return hash;
}
var max_count = 0, max_index;
this.forEach(function(item, index) {
var count;
if (item in hash) {
count = ++hash[item];
} else
count = hash[item] = 1;
if (select_max === true) {
if (max_count < count) {
max_count = count;
max_index = index;
}
} else if (max_count <= count) {
if (max_count < count
// select_max = 1: maximum case 也選擇較大的 item
// select_max = -1: min case 選擇較小的item
|| !(select_max < 0
//
? this[max_index] < (isNaN(item) ? item : +item)
//
: this[max_index] > (isNaN(item) ? item : +item)))
max_index = index;
max_count = count;
}
}, this);
// hash[this[max_index]] === max_count
return {
hash : hash,
value : this[max_index],
count : max_count,
index : max_index
};
}
/**
*
// to inherit from native object:
function Child() {
// Parent: native object
var instance = new Parent;
// do something need to apply arguments
;
// The same as `instance.__proto__ = Child.prototype;`
Object.setPrototypeOf(instance, Child.prototype);
// ↑ ** if there is no prototype chain, we should copy the properties manually.
// do something need to initialize
;
return instance;
}
// setup inheritance: only works for prototype chain.
// The same as `Child.prototype = new Parent;`
Object.setPrototypeOf(Child.prototype, Parent.prototype);
// setup Child.prototype.attributes
Child.prototype.property = property;
Child.prototype.method = function () { };
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create
*/
// subclass and inherit from Array
// 注意: 此處的繼承重視原生 Array 之功能,因此 instance instanceof SubArray 於 IE8 等舊版中不成立。
function new_Array_instance(array_type, no_proto, items) {
var instance;
// 除 Array 外,其他 TypedArray,如 Uint32Array 有不同行為。
// 此處僅處理 constructor。
if (array_type === Array)
if (items && 1 < items.length)
// 將 items copy 至 this instance。
Array.prototype.push.apply(instance = new Array, items);
else
instance = new Array(items && items.length ? items[0] : 0);
else
instance = items ? new array_type(items[0]) : new array_type();
if (no_proto)
// if there is no prototype chain, we should copy the properties
// manually.
// TODO: 此方法極無效率!此外,由於並未使用 .prototype,因此即使採用 delete
// instance[property],也無法得到預設值,且不能探測是否為 instance 特有 property。
set_method(instance, this.prototype, null);
else {
Object.setPrototypeOf(instance, this.prototype);
// TODO: NG:
// instance.prototype = Object.create(this.prototype);
}
return instance;
}
function Array_derive(sub_class, array_type) {
if (!array_type)
array_type = Array;
Object.setPrototypeOf(sub_class.prototype, array_type.prototype);
// TODO: NG:
// sub_class.prototype = Object.create(array_type.prototype);
return new_Array_instance.bind(sub_class, array_type, false);
}
function Array_derive_no_proto(sub_class, array_type) {
// e.g., IE8
return new_Array_instance.bind(sub_class, array_type || Array, true);
}
/**
*
* @example
function SubArray() {
var instance = SubArray.new_instance(arguments);
// do something need to initialize
;
return instance;
}
// setup inheritance
SubArray.new_instance = Array.derive(SubArray);
// setup SubArray.prototype
SubArray.prototype.property = property;
SubArray.prototype.method = function () { };
// manually test code
// setup
// see data.code.compatibility.
if (!Object.setPrototypeOf && typeof {}.__proto__ === 'object')
Object.setPrototypeOf = function (object, proto) { object.__proto__ = proto; return object; };
Array.derive = Object.setPrototypeOf ? Array_derive : Array_derive_no_proto;
// main test code
function SubArray() {
var instance = new_instance(arguments);
// do something need to initialize
;
return instance;
}
// setup inheritance
var new_instance = Array.derive(SubArray);
// setup SubArray.prototype
SubArray.prototype.last = function () { return this.at(-1); };
var a = new SubArray(2, 7, 4), b = [4]; a[6] = 3; a.push(8);
if (Object.setPrototypeOf && !(a instanceof SubArray) || !(a instanceof Array) || a[1] !== 7 || a.length !== 8 || a.last() !== 8 || b.last) console.error('failed');
function SubUint32Array() {
var instance = SubUint32Array.new_instance(arguments);
// do something need to initialize
;
return instance;
}
// setup inheritance
SubUint32Array.new_instance = Array.derive(SubUint32Array, Uint32Array);
// setup SubUint32Array.prototype
SubUint32Array.prototype.last = function () { return this.at(-1); };
var a = new SubUint32Array(8, 7, 4), b = new Uint32Array(4); a[6] = 3; a[7] = 5; a[8] = 4;
if (Object.setPrototypeOf && !(a instanceof SubUint32Array) || !(a instanceof Uint32Array) || a[8] || a[6] !== 3 || a.length !== 8 || a.last() !== 5 || b.last) console.error('failed');
*/
set_method(Array, {
// for data.clone()
clone : Array_clone,
derive : Object.setPrototypeOf ? Array_derive : Array_derive_no_proto,
intersection : has_native_Map ? Array_intersection_Map
: Array_intersection
});
// ------------------------------------
// comparator, compare_function, sort_function for array.sort()
// 用於由小至大升序序列排序, ascending, smallest to largest, A to Z。
// 注意:sort 方法會在原地排序 Array 物件。
// @see std::less()
function general_ascending(a, b) {
// '12/34', '56/78' 可以比大小,但不能相減。
// 但這對數字有問題: '1212'<'987'
// 若對一般物件,採用 .sort() 即可。
return a < b ? -1 : a > b ? 1 : 0;
}
function general_descending(a, b) {
return a < b ? 1 : a > b ? -1 : 0;
}
function Number_ascending(a, b) {
// 升序序列排序: 小→大
return a - b;
// '12/34', '56/78' 可以比大小,但不能相減。
// 但這對數字有問題: '1212'<'987'
// 若對一般物件,採用 .sort() 即可。
return a < b ? -1 : a > b ? 1 : 0;
return _1 - _2;
return Math.sign(a - b);
}
function Number_descending(a, b) {
// 降序序列排序: 大→小
return b - a;
return a < b ? 1 : a > b ? -1 : 0;
}
_.general_ascending = general_ascending;
_.general_descending = general_descending;
_.ascending = Number_ascending;
_.descending = Number_descending;
/**
* 以二分搜尋法(binary search)搜尋已排序的 array。
* binary search an Array.
** 注意:使用前須先手動將 array 排序!
* TODO: 依資料分布:趨近等差/等比/對數等,以加速搜尋。
*
* cf. Array.prototype.search()
*
* @param {Array}array
* 由小至大已排序的 array。
* @param value
* value to search.
* @param {Object}[options]
* 附加參數/設定特殊功能與選項 options = {
* found : found_callback(index, not_found:
* closed/is_near/未準確相符合,僅為趨近、近似),
* near : not_found_callback(較小的 index, not_found),
* start : start index,
* last : last/end index,
* length : search length.
* last 與 length 二選一。
}
*
* @returns default: 未設定 options 時,未找到為 NOT_FOUND(-1),找到為 index。
*
* @since 2013/3/3 19:30:2 create.
*/
function search_sorted_Array(array, value, options) {
if (library_namespace.is_RegExp(value)
&& (!options || !options.comparator)) {
// 處理搜尋 {RegExp} 的情況: 此時回傳最後一個匹配的 index。欲找首次出現,請用 first_matched()。
if (value.global) {
library_namespace
.error('search_sorted_Array: 當匹配時,不應採用 .global! '
+ value);
}
if (!options) {
options = Object.create(null);
}
options.comparator = function(v) {
return value.test(v) ? -1 : 1;
};
if (!('near' in options)) {
options.near = true;
}
} else if (!options
//
&& (typeof value === 'object' || typeof value === 'function')) {
options = value;
value = undefined;
}
if (typeof options === 'function') {
options = {
comparator : options
};
} else if (typeof options === 'boolean' || Array.isArray(options)) {
options = {
found : options
};
} else if (library_namespace.is_digits(options)) {
options = {
start : options
};
} else if (!library_namespace.is_Object(options)) {
options = Object.create(null);
}
var callback, comparison, not_found = true,
//
comparator = options.comparator
|| (typeof array[0] === 'number' ? Number_ascending
: search_sorted_Array.default_comparator),
//
start = (options.start | 0) || 0, small = start, index = start,
//
big = (options.last | 0)
|| (options.length ? options.length | 0 + start - 1
: array.length - 1);
// main comparare loop
// http://codereview.stackexchange.com/questions/1480/better-more-efficient-way-of-writing-this-javascript-binary-search
while (true) {
if (small > big) {
if (comparison > 0 && index > start)
// 修正成較小的 index。
// 除非是 [2,3].search_sorted(1.5,{found:1}),
// 否則 assert: big + 1 === start === index
index--;
break;
} else {
// 首引數應該採用最多資訊者,因此array[]擺在value前。
comparison = comparator(array[index = (small + big) >> 1],
value
// , index
);
// 若下一 loop 跳出,則此時之狀態為
// start = index = big; value 介於 array[index±1]。
// 或
// start = index, big; value 介於兩者間。
if (comparison > 0) {
// 往前找
big = index - 1;
// 若下一 loop 跳出,則此時之狀態為
// big, start = index
// value 介於兩者間。
} else if (comparison < 0) {
// 往後找
small = index + 1;
// 若下一 loop 跳出,則此時之狀態為
// index = big, small
// value 介於兩者間。
} else {
not_found = false;
break;
}
}
}
// 挑一個可用的。
callback = not_found && options.near || options.found;
// console.log([ not_found, callback, index ]);
return Array.isArray(callback)
// treat options.found as mapper
? callback[index]
//
: typeof callback === 'function' ? callback.call(array, index,
not_found)
//
: not_found && (!callback
// 當 library_namespace.is_RegExp(value) 時,callback 僅表示匹不匹配。
|| library_namespace.is_RegExp(value)
// assert: 此時 index === 0 or array.length-1
// 這樣會判別並回傳首個匹配的。
&& (index === 0 && comparator(array[index]) > 0)) ? NOT_FOUND
// default: return index of value
: index;
}
search_sorted_Array.default_comparator = general_ascending;
_.search_sorted_Array = search_sorted_Array;
// return first matched index.
// assert: array 嚴格依照 mismatched→matched,有個首次出現的切分點。
function first_matched(array, pattern, get_last_matched) {
if (!array || !pattern) {
return NOT_FOUND;
}
var first_matched_index = array.length;
if (first_matched_index === 0) {
return NOT_FOUND;
}
var is_RegExp = library_namespace.is_RegExp(pattern), is_Function = !is_RegExp
&& library_namespace.is_Function(pattern),
//
last_mismatched_index = 0;
if (is_RegExp && pattern.global) {
library_namespace.error('first_matched: 當匹配時,不應採用 .global! '
+ pattern);
}
var matched;
while (last_mismatched_index < first_matched_index) {
// binary search
var index = (last_mismatched_index + first_matched_index) / 2 | 0;
matched = is_RegExp ? pattern.test(array[index])
: is_Function ? pattern(array[index]) : array[index]
.includes(pattern);
if (false && matched && is_RegExp) {
library_namespace.log(last_mismatched_index + '-[' + index
+ ']-' + first_matched_index + '/' + array.length
+ ': ' + matched + ' ' + pattern);
console.log(array[index].match(pattern));
}
if (get_last_matched ? !matched : matched) {
first_matched_index = index;
} else if (last_mismatched_index === index) {
break;
} else {
last_mismatched_index = index;
}
}
if (get_last_matched) {
if (last_mismatched_index === 0 && !matched) {
library_namespace.debug('Not found.', 3, 'first_matched');
return NOT_FOUND;
}
library_namespace.debug('return ' + last_mismatched_index, 3,
'first_matched');
return last_mismatched_index;
}
return first_matched_index === array.length ? NOT_FOUND
: first_matched_index;
}
_.first_matched = first_matched;
/**
* merge / combine string with duplicated characters.
* merge 2 array by order, without order change
* 警告: 此法僅於無重複元素時有效。
*
* @param {Array}sequence_list
* sequence list to merge
*
* @returns {Array}merged chain
*
* @see find duplicate part of 2 strings
* https://en.wikipedia.org/wiki/String_metric
* https://en.wikipedia.org/wiki/Shortest_common_supersequence_problem
* http://codegolf.stackexchange.com/questions/17127/array-merge-without-duplicates
* https://en.wikipedia.org/wiki/Topological_sorting
*/
function merge_unduplicated_sequence(sequence_list) {
var map = Object.create(null);
function add_node(element, index) {
var chain = map[element];
if (!chain)
chain = map[element]
// [ 0: possible backward, 1: possible foreword ]
= [ Object.create(null), Object.create(null) ];
if (index > 0)
// 登記前面的。
chain[0][this[index - 1]] = true;
if (index + 1 < this.length)
// 登記前面的。
chain[1][this[index + 1]] = true;
return;
// 不必記太多,反而稱加操作複雜度。上面的相當於把 'abc' 拆成 'ab', 'bc'
var i = 0;
for (; i < index; i++)
// 登記前面的。
chain[0][this[i]] = true;
// i++: skip self
for (i++; i < this.length; i++)
// 登記後面的。
chain[1][this[i]] = true;
}
sequence_list.forEach(function(sequence) {
if (typeof sequence === 'string')
sequence = sequence.split('');
if (typeof sequence.forEach === 'function'
// && Array.isArray(sequence)
) {
sequence.forEach(add_node, sequence);
} else {
library_namespace.warn(
//
'merge_unduplicated_sequence: Invalid sequence: [' + sequence
+ ']');
}
});
// 此法僅於無重複時有效。
/**
* result chain / sequence.
* result = [ start of chain, ends of chain ]
*
* @type {Array}
*/
var result = [ [], [] ];
while (true) {
/** {Array}temporary queue */
var queue = [ [], [] ],
/** {Array}elements added */
added = [];
for ( var element in map) {
if (element in added)
continue;
// 先考慮添入起首,再考慮結尾。
if (Object.is_empty(map[element][0])) {
queue[0].push(element);
// 登記。
added.push(element);
continue;
}
if (Object.is_empty(map[element][1])) {
queue[1].push(element);
// 登記。
added.push(element);
continue;
}
}
if (added.length === 0)
// nothing can do.
// e.g., a ring / cycle, 有重複。
break;
if (queue[0].length === 1)
result[0].push(queue[0][0]);
else if (queue[0].length > 0) {
// 有多重起頭。
throw new Error('Invalid starts: ' + queue[0]);
}
if (queue[1].length === 1)
result[1].unshift(queue[1][0]);
else if (queue[1].length > 0) {
// 有多重結尾。
throw new Error('Invalid ends: ' + queue[1]);
}
// remove node.
added.forEach(function(element) {
var data = map[element];
for ( var node in data[0])
delete map[node][1][element];
for ( var node in data[1])
delete map[node][0][element];
delete map[element];
});
}
result = result[0].concat(result[1]);
return result;
}
_.merge_sequence = merge_unduplicated_sequence;
// ------------------------------------
// TODO: add `Promise` version
/**
* 依照順序從 index 至 last 執行 for_each。
*
* @examples
function for_item(run_next, index, index, list) {
// do something
run_next();
}
CeL.run_serial(for_item, last_NO, first_NO, function() { 'done'; });
function for_item(run_next, item, index, list) {
// do something
run_next();
}
CeL.run_serial(for_item, list, function() { 'done'; });
*
*
* @param {Function}for_each
* run for_each(run_next, item, index, list[, get_status]) for
* every elements. Must handle exception yourself.
* {Object}function get_status(): 狀態探測函數。
* @param {Integer|Array}last
* last index or {Array}list
* @param {Integer|Array}[index]
* start index or [start index, last index].
* default: starts from 0.
* @param {Function}[callback]
* Will run after all elements executed
* @param {Object}[_this]
* passed to for_each
* @param {Boolean}[parallelly]
* run parallelly
*
* @see CeL.data.code.thread
*/
function run_serial_asynchronous(for_each, last, index, callback, _this,
parallelly) {
var list;
// initialization
if (Array.isArray(last)) {
list = last;
last = list.length;
}
if (typeof index === 'function') {
// shift arguments.
parallelly = _this;
_this = callback;
callback = index;
index = 0;
} else if (Array.isArray(index)) {
last = index[1];
index = index[0];
} else {
// default: starts from 0.
index |= 0;
}
// console.log([ for_each, last, index, callback, _this, parallelly ]);
// ----------------------------------------------------------
// main loop for serial
function run_next() {
// 預留可變動 list 的空間。
if (list ? index === list.length : index > last) {
// done.
typeof callback === 'function' && callback.call(_this);
return;
}
library_namespace.debug(index + '/' + last, 3,
'run_serial_asynchronous');
// 先增加 index,預防 callback 直接 call run_next()。
var _index = index++;
var _arguments = [ run_next, list ? list[_index] : _index, _index,
list ];
if (_this && _this.run_interval >= 0) {
library_namespace.log_temporary(index + '/' + last
+ ' Waiting ' + _this.run_interval + ' ms to run');
setTimeout(function() {
for_each.apply(_this, _arguments);
}, _this.run_interval);
} else {
for_each.apply(_this, _arguments);
}
}
if (!parallelly
// parallelly 在這情況下不會執行 callback。
|| !(index <= last)) {
run_next();
return;
}
// ----------------------------------------------------------
// parallelly
function get_status() {
return {
left : left
};
}
var check_left = function(exit_loop) {
if (--left === 0 || exit_loop === 'quit') {
// run once only
check_left = library_namespace.null_function;
typeof callback === 'function' && callback.call(_this);
return;
}
library_namespace.debug(left + ' left...', 3,
'run_parallel_asynchronous');
};
if (_this) {
for_each = for_each.bind(_this);
}
var left = 0;
if (list) {
if (list.length === 0) {
setImmediate(check_left, 'quit');
} else {
list.forEach(function(item, index) {
left++;
setImmediate(for_each, check_left, item, index, list,
get_status);
});
}
return;
}
for (; index <= last; index++, left++) {
setImmediate(for_each, check_left, index, index, list, get_status);
}
}
_.run_serial = run_serial_asynchronous;
// 警告: 採用非同步的方法,獲得網址的順序不一定。因此不能採用 .push(),而應採用 [index] 的方法來記錄。
function run_parallel_asynchronous(for_each, last, index, callback, _this) {
run_serial_asynchronous(for_each, last, index, callback, _this, true);
}
_.run_parallel = run_parallel_asynchronous;
// ---------------------------------------------------------------------//
if (false) {
CeL.edit_distance('from A', 'from B');
}
// Levenshtein distance (edit distance)
// @see LCS()
// https://en.wikipedia.org/wiki/Levenshtein_distance#Iterative_with_two_matrix_rows
// http://www.codeproject.com/Articles/13525/Fast-memory-efficient-Levenshtein-algorithm
// http://locutus.io/php/strings/levenshtein/
// https://github.com/component/levenshtein/blob/master/index.js
// https://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Levenshtein_distance
// http://jsperf.com/levenshtein-distance-2
function Levenshtein_distance(string_1, string_2) {
string_1 = (string_1 || '').chars(true);
string_2 = (string_2 || '').chars(true);
var length_1 = string_1 && string_1.length || 0, length_2 = string_2
&& string_2.length || 0;
// degenerate cases
if (length_1 === 0) {
return length_2;
}
if (length_2 === 0) {
return length_1;
}
if (length_1 === length_2 && string_1 === string_2) {
return 0;
}
// create two work vectors of integer distances
var vector_1 = new Array(length_2 + 1), i = 0;
// initialize vector_1 (the previous row of distances)
// this row is A[0][i]: edit distance for an empty string_1
// the distance is just the number of characters to delete from string_2
for (; i <= length_2; i++) {
vector_1[i] = i;
}
for (i = 0; i < length_1; i++) {
// calculate vector_2 (current row distances) from the previous row
// vector_1
// use formula to fill in the rest of the row
for (var j = 0,
// first element of vector_2 is A[i+1][0]
// edit distance is delete (i+1) chars from string_1 to match empty
// string_2
last_vector_2 = i + 1, vector_2 = [ last_vector_2 ]; j < length_2; j++) {
last_vector_2 = Math.min(
// The cell immediately above + 1
last_vector_2 + 1,
// The cell immediately to the left + 1
vector_1[j + 1] + 1,
// The cell diagonally above and to the left plus the cost
vector_1[j] + (/* cost */string_1[i] === string_2[j] ? 0 : 1));
vector_2.push(last_vector_2);
}
// copy vector_2 (current row) to vector_1 (previous row) for next
// iteration
vector_1 = vector_2;
}
return vector_2[length_2];
}
_.edit_distance = Levenshtein_distance;
// ------------------------------------
/**
* Get LCS length / trace array
*
* @param {Array}from
* @param {Array}to
*
* @returns {Array}
*
* @since 2017/4/5 10:0:36
*
* @see https://en.wikipedia.org/wiki/Longest_common_subsequence_problem
* https://github.com/GerHobbelt/google-diff-match-patch
*/
function LCS_length(from, to, get_trace_array) {
if (typeof from === 'string')
from = from.chars(true);
if (typeof to === 'string')
to = to.chars(true);
// assert: Array.isArray(from) && Array.isArray(from)
// console.trace([ from.length, to.length ]);
var get_length_only = !get_trace_array,
//
from_length = from.length, to_length = to.length,
// TODO: 當文件有過多行時的處置方式。
trace_Array = from_length * (get_length_only ? 2 : to_length);
trace_Array = typeof Uint16Array === 'function' ? new Uint16Array(
trace_Array) : new Array(trace_Array).fill(0);
// loop of ↓
for (var to_index = 0, trace_index = 0, last_trace_index = trace_index
- from_length; to_index < to_length; to_index++) {
if (get_length_only) {
if (to_index % 2 === 0) {
last_trace_index = 0;
// assert: 已經 = from_length
// trace_index = from_length;
} else {
// assert: 已經 = from_length
// last_trace_index = from_length;
trace_index = 0;
}
}
// loop of →
for (var to_element = to[to_index], from_index = 0; from_index < from_length; from_index++, trace_index++, last_trace_index++) {
// @see LCS function
if (to_element === from[from_index]) {
// to[to_index] === from[from_index]
trace_Array[trace_index] =
// 這條件也保證了 last_trace_index > 0
from_index > 0 && to_index > 0 ? trace_Array[last_trace_index - 1] + 1
: 1;
} else {
// to[to_index] !== from[from_index]
trace_Array[trace_index] = from_index > 0 ? trace_Array[trace_index - 1]
: 0;
if (last_trace_index >= 0
&& trace_Array[trace_index] < trace_Array[last_trace_index]) {
trace_Array[trace_index] = trace_Array[last_trace_index];
}
}
}
}
if (get_length_only) {
return trace_Array[trace_index - 1];
}
if (library_namespace.is_debug(3)) {
library_namespace.debug('to\\f\t' + from.join('\t') + '\n'
+ '-'.repeat(8 * (from.length + 2)));
for (var to_index = 0; to_index < to_length; to_index++) {
library_namespace.debug(to[to_index]
+ '\t'
+ trace_Array.slice(to_index * from_length,
(to_index + 1) * from_length).join('\t'));
}
library_namespace.debug('-'.repeat(8 * (from.length + 3)));
}
// trace_Array.LCS_length = trace_Array[trace_index - 1];
return trace_Array;
}
_.LCS_length = LCS_length;
if (false) {
diff_list = CeL.LCS(old_text, new_text, {
// line : true,
diff : true
});
diff_list.forEach(function(diff_pair) {
// added_text === inserted_text
// const [removed_text, added_text] = diff;
var removed_text = diff_pair[0], added_text = diff_pair[1];
});
}
if (false) {
diff_list = CeL.LCS('from A', 'from B', {
diff : true
});
diff_list = CeL.LCS('from A', 'from B', {
with_diff : true
});
}
/**
* Get LCS / diff
*
* Longest Common Subsequence 最長公共子序列
*
* 警告:在 line_mode,"A \n"→"A\n" 的情況下,"A" 會同時出現在增加與刪除的項目中,此時必須自行檢測排除。
*
* @param {Array|String}from
* from text
* @param {Array|String}to
* to text
* @param {Object|String}options
* 附加參數/設定選擇性/特殊功能與選項
*
* @returns {Array|String}
*/
function LCS(from, to, options) {
// 前置作業。
options = library_namespace.setup_options(options);
var treat_as_String = 'treat_as_String' in options ? options.treat_as_String
: typeof from === 'string' && typeof to === 'string',
// options.line : 強制採用行模式,連輸入{String|Array}都會以'\n'結合。
line_mode = 'line' in options ? options.line : treat_as_String
&& (from.includes('\n') || to.includes('\n')),
//
separator = options.separator || (line_mode ? '\n' : '');
if (false) {
library_namespace.debug('separator: ' + JSON.stringify(separator)
+ (line_mode ? ',採用行模式。' : ''), 3, 'LCS');
}
if (typeof from === 'string') {
from = separator ? from.split(separator) : from.chars(true);
} else if (!from) {
from = [];
}
if (typeof to === 'string') {
to = separator ? to.split(separator) : to.chars(true);
} else if (!to) {
to = [];
}
// assert: Array.isArray(from) && Array.isArray(from)
var from_length = from.length, from_index = from_length - 1, to_index = to.length - 1,
// Completed LCS Table
trace_Array = options.trace_Array || LCS_length(from, to, true),
// 獨特/獨有的 exclusive 元素列表。
diff_list = [], from_unique, to_unique,
// flags
get_all = !!options.all,
// get index instead of contents
get_index = options.index || get_all,
// get diff as well
get_diff = !!(options.diff || options.with_diff),
// get diff ONLY
diff_only = get_diff && !get_all && !options.with_diff,
//
try_to_merge_diff = !!options.try_to_merge_diff;
// ---------------------------------------
// 工具函數。
function unique(list) {
return list.map(function(result_Array) {
// assert: .join() 與 .split() 採用的是 result_Array 不包含的字串。
return result_Array.join('\0');
}).unique().map(function(result_Array) {
return result_Array.split('\0');
});
}
function normalize_unique(_unique, _this) {
if (!Array.isArray(_unique)) {
// return '';
// return;
return treat_as_String ? '' : _unique;
// return [];
}
// _unique = [ from index, to index ]
if (options.diff_line) {
return _unique;
}
var index = _unique[0];
if (index === _unique[1]) {
if (_unique === from_unique) {
from_unique = [ index, index ];
} else {
to_unique = [ index, index ];
}
// return _this[index];
}
_unique = _this.slice(index, _unique[1] + 1);
return treat_as_String ? _unique.join(separator) : _unique;
}
function generate_diff_pair(from_index, to_index) {
var diff_pair = [ normalize_unique(from_unique, from),
normalize_unique(to_unique, to) ];
if (false && to_unique) {
diff_pair.push(normalize_unique(to_unique, to));
}
// Array.isArray(to_unique) && from_unique===undefined 代表本段落為新增文字。
// diff_pair.index: 本次差異開始的 index。
// diff_pair.index = [ from_index, to_index ];
// _index = [ start_index, end_index ]
// .indexes
diff_pair.index = [ from_unique, to_unique ];
// diff_pair.last_index: 上一個相同元素的 index。
// 警告: diff_pair.last_index 在 _unique 最初的一個 index 可能不準確!
diff_pair.last_index = [ from_index, to_index ];
return diff_pair;
}
function add_to_diff_list(from_index, to_index) {
if (!get_diff || (!from_unique && !to_unique)) {
// assert: 連續相同元素時
return;
}
if (false) {
library_namespace.debug(JSON.stringify([ from_index, to_index,
from_unique, to_unique ]), 3, 'add_to_diff_list');
}
var diff_pair = generate_diff_pair(from_index, to_index);
if (try_to_merge_diff && diff_list.length > 0) {
var previous_diff_of_start = (to_index === NOT_FOUND ? 0
// 本次不同內容開始的 index 差異。
: to_index) - (from_index === NOT_FOUND ? 0 : from_index);
for (var index = 0; index < diff_list.length
// && 1: 只檢測文字交換。 e.g., "品質" → "質量",避免太多差異被合併在一起。
&& 1; index++) {
var _diff_pair = diff_list[index];
// 到 _diff_pair 為止,from 之不同內容最後的 index 差異。
var last_index_0 = _diff_pair.index[0] ? _diff_pair.index[0][1]
: _diff_pair.last_index[0];
// 到 _diff_pair 為止,to 之不同內容最後的 index 差異。
var last_index_1 = _diff_pair.index[1] ? _diff_pair.index[1][1]
: _diff_pair.last_index[1];
// 到 _diff_pair 為止,不同內容最後的 index 差異。
var following_diff_of_start = last_index_1 - last_index_0;
if (following_diff_of_start !== previous_diff_of_start) {
continue;
}
diff_list.splice(0, index + 1);
if (!from_unique) {
from_unique = [ to_unique[0] ];
if (from_index === NOT_FOUND)
from_index = 0;
}
from_unique[1] = last_index_0;
if (!to_unique) {
to_unique = [ from_unique[0] ];
if (to_index === NOT_FOUND)
to_index = 0;
}
to_unique[1] = last_index_1;
diff_pair = generate_diff_pair(from_index, to_index);
break;
}
}
diff_list.unshift(diff_pair);
// reset indexes.
from_unique = to_unique = undefined;
}
// backtrack subroutine
function backtrack(from_index, to_index, all_list) {
// 因為函數 backtrack() 中呼叫自己,可能出現:
// RangeError: Maximum call stack size exceeded
// 因此必須採用非遞迴呼叫(recursive call)版本。
while (true) {
if (false) {
library_namespace.debug(String([ from_index, to_index ]),
3, 'LCS.backtrack');
library_namespace.debug(String([ from_unique, to_unique,
diff_list ]), 6, 'LCS.backtrack');
}
if (from_index < 0 || to_index < 0) {
if (false) {
library_namespace.debug('→ '
+ JSON.stringify([ from_index, to_index,
from_unique, to_unique ]), 3);
}
if (from_index === -1 && to_index === -1) {
// assert: from_index === -1 && to_index === -1
if (false) {
library_namespace.debug(
//
'LCS starts from the first element of each list, '
+ JSON.stringify([ from_index, to_index,
from[0], to[0] ]), 3,
'LCS.backtrack');
}
} else if (to_index === -1) {
if (false) {
library_namespace.debug('因為 ' + from_index + ',0→('
+ from_index + '|' + (from_index - 1)
+ '),-1 時不會處理到 from_unique,只好補處理。', 3,
'LCS.backtrack');
}
if (Array.isArray(from_unique)) {
// e.g., CeL.LCS('abc123', 'def123', 'diff')
from_unique[0] = 0;
} else {
// e.g., CeL.LCS('a0', 'b0', 'diff')
// e.g., CeL.LCS('a0', '0b', 'diff')
from_unique = [ 0, from_index ];
}
} else if (from_index === -1) {
if (false) {
library_namespace.debug('因為 0,' + to_index
+ ',0→-1,(' + to_index + '|'
+ (to_index - 1)
+ ') 時不會處理到 to_unique,只好補處理。', 3,
'LCS.backtrack');
}
if (Array.isArray(to_unique)) {
to_unique[0] = 0;
} else {
// e.g., CeL.LCS('a1b2', '1a2b', 'diff')
to_unique = [ 0, to_index ];
}
} else {
library_namespace
.warn('LCS.backtrack: Invalid situation!');
}
add_to_diff_list(from_index, to_index);
return;
}
if (from[from_index] === to[to_index]) {
// 此元素為 LCS 之一部分。
if (false) {
library_namespace.debug('相同元素 @ '
+ [ from_index, to_index ] + ': '
+ from[from_index], 3, 'LCS.backtrack');
}
if (!diff_only) {
// get_index = 1: from_index, 2: to_index
var common = get_index ? get_index === 2 ? to_index
: from_index : from[from_index];
if (get_all) {
all_list.forEach(function(result_Array) {
result_Array.unshift(common);
});
} else {
all_list[0].unshift(common);
}
}
add_to_diff_list(from_index, to_index);
// 常常出現 Maximum call stack size exceeded 錯誤的地方...
// backtrack(from_index - 1, to_index - 1, all_list);
// return;
// 採用 iteration
from_index--;
to_index--;
continue;
}
var trace_index;
if (from_index > 0
// ↑ 預防 trace_Array[trace_index - 1] 取到範圍外的值。
&& (trace_index = to_index * from_length + from_index) > 0
// trace_Array[trace_index]
// = max( trace_Array[trace_index - 1], [上面一階])
&& trace_Array[trace_index] === trace_Array[trace_index - 1]) {
if (false) {
library_namespace.debug('trace_index: ' + trace_index,
3, 'LCS.backtrack');
library_namespace.debug('trace: '
+ trace_Array[trace_index - 1] + ' → '
+ trace_Array[trace_index], 3);
}
var _all_list;
if (get_all
// 如此亦保證 to_index > 0
&& trace_index >= from_length && trace_Array[trace_index]
//
=== trace_Array[trace_index - from_length]) {
if (false) {
library_namespace.debug(trace_Array[trace_index]
+ ': ' + all_list, 3, 'LCS.backtrack');
}
_all_list = all_list.map(function(result_Array) {
return result_Array.clone();
});
backtrack(from_index, to_index - 1, _all_list);
}
if (false) {
library_namespace.debug('檢測前一個。 '
+ [ from_index, to_index ], 3, 'LCS.backtrack');
}
if (get_diff) {
if (Array.isArray(from_unique)) {
from_unique[0] = from_index;
} else {
from_unique = [ from_index, from_index ];
}
if (false) {
library_namespace.debug(
//
'from_index: ' + [ from_index,
//
JSON.stringify(from_unique) ], 3, 'LCS.backtrack');
}
}
// TODO: 對於比較長的結構,這裡常出現
// RangeError: Maximum call stack size exceeded
// workaround: 現在只能以重跑程式、跳過這篇文章繞過。
backtrack(from_index - 1, to_index, all_list);
if (get_all) {
if (false) {
library_namespace.debug('merge: '
+ [ all_list, _all_list ], 3);
}
all_list = unique(all_list.append(_all_list));
}
return;
} else {
if (false) {
library_namespace.debug('檢測上一排。 '
+ [ from_index, to_index ], 3, 'LCS.backtrack');
}
if (get_diff) {
if (to_unique) {
to_unique[0] = to_index;
} else {
to_unique = [ to_index, to_index ];
}
if (false) {
library_namespace.debug('to_index: '
+ [ to_index, JSON.stringify(to_unique) ],
3, 'LCS.backtrack');
}
}
// backtrack(from_index, to_index - 1, all_list);
// 採用 iteration
to_index--;
}
}
}
// ---------------------------------------
// 取得所有 LCS,而不僅是其中之一。
var all_list = [ [] ];
// 主要作業。
if (isNaN(from_index) || isNaN(to_index)) {
library_namespace.error('LCS: isNaN(from:' + from_index
+ ') || isNaN(' + to_index + ')');
throw new Error('LCS: isNaN(from_index) || isNaN(to_index)');
}
try {
backtrack(from_index, to_index, all_list);
} catch (e) {
if (options.no_throw_when_stack_size_is_exceeded) {
all_list.error = e;
return all_list;
}
throw new RangeError(
'Maximum call stack size exceeded @ backtrack()');
}
// 以下為後續處理。
if (get_all) {
// 去掉重複的 LCS。
all_list = unique(all_list);
}
if (treat_as_String || !get_index) {
all_list = all_list
// index → 元素
.map(function(result_Array) {
if (get_index) {
result_Array = result_Array.map(function(index) {
return get_index === 2 ? to[index] : from[index];
});
}
return treat_as_String && !options.index
// 特別指定 options.index 時,即使輸入{String}亦保持index,不轉換為{String}。
? result_Array.join(separator) : result_Array;
});
}
if (get_diff && !diff_only) {
// 應為 treat_as_String
if (treat_as_String) {
// 為了能設定 .diff。
// assert: diff_list 設定在 all_list[0] 上,
// 且不因前面 unique(all_list) 而改變。
all_list[0] = new String(all_list[0]);
}
all_list[0].diff = diff_list;
}
if (!get_all) {
all_list = diff_only ? diff_list : all_list[0];
}
if (options.with_list) {
if (typeof all_list !== 'object') {
all_list = new String(all_list);
}
all_list.from = from;
all_list.to = to;
}
/**
*
all_list = diff_only ? diff_list : all_list[0];
{Array}diff_list
= [ diff_pair 1, diff_pair 2, ...,
// need options.with_list
from: [ 'diff from source' ], to: [ 'diff to source' ]
]
{Array}diff_pair =
// 若僅有 from_diff_unique 或 to_diff_unique 則另一方會是 undefined。
// 兩者皆有則表示為變更/modify。
[ {Array}from_diff_unique, {Array}to_diff_unique,
$**deprecated** [ {Array|String|Undefined}from_diff_unique, {Array|String|Undefined}to_diff_unique,
// diff_pair.index = [ from_index, to_index ];
// _index = [ start_index, end_index ]
index:
[ {Array}from_unique_indexes, {Array}to_unique_indexes ],
// diff_pair.last_index: 上一個相同元素的 index。
// 警告: diff_pair.last_index 在 _unique 最初的一個 index 可能不準確!
last_index: [ {Integer}index of from, {Integer}index of to ]
]
{Array|Undefined}from_unique_indexes, to_unique_indexes
= [ {Integer}start_index, {Integer}end_index ]
or = undefined (insert / delete only)
$**deprecated** or = {Integer}start index
{Array}from_diff_unique, to_diff_unique
= [ {String}diff_unique_line, {String}diff_unique_line, ... ]
$**deprecated** or = {String}diff_unique_line
$**deprecated** or = undefined
*/
return all_list;
}
_.LCS = LCS;
/**
*
from * * * *
index 012345678 9 10 11 12 13 14
text _0123_456 7 8 9 a
text 0123 456_7 8 9
index 0123 45678 9 10 11 12
to
*/
// 取得 to 之與 from 相對應的索引。
// from[index_of_from] 相對應於
// to[CeL.LCS.corresponding_index(diff_list, index_of_from_text)]
function get_corresponding_index(diff_list, index_of_from_text,
is_index_of_to) {
if (diff_list.length === 0)
return index_of_from_text;
var FROM_INDEX = is_index_of_to ? 1 : 0, TO_INDEX = 1 - FROM_INDEX;
var diff_list_index = 0, latest_offset_delta = 0;
while (true) {
var diff_pair = diff_list[diff_list_index++];
var from_indexes = diff_pair.index[FROM_INDEX];
if (!from_indexes) {
from_indexes = diff_pair.last_index[FROM_INDEX] + 1;
from_indexes = [ from_indexes, from_indexes ];
}
if (index_of_from_text > from_indexes[1]) {
if (diff_list_index < diff_list.length) {
continue;
}
from_indexes = diff_pair.index[FROM_INDEX];
var to_indexes = diff_pair.index[TO_INDEX];
latest_offset_delta = (to_indexes ? to_indexes[1]
: diff_pair.last_index[TO_INDEX])
- (from_indexes ? from_indexes[1]
: diff_pair.last_index[FROM_INDEX]);
break;
}
if (index_of_from_text < from_indexes[0]) {
latest_offset_delta = diff_pair.last_index[TO_INDEX]
- diff_pair.last_index[FROM_INDEX];
break;
}
var to_indexes = diff_pair.index[TO_INDEX];
if (!to_indexes) {
to_indexes = diff_pair.last_index[TO_INDEX] + 1;
to_indexes = [ to_indexes, to_indexes ];
}
if (from_indexes[1] === from_indexes[0]
|| to_indexes[1] === to_indexes[0]) {
return diff_pair.index[FROM_INDEX] ? to_indexes[0]
: to_indexes[1] + 1;
}
return to_indexes[0] + (index_of_from_text - from_indexes[0])
* (to_indexes[1] - to_indexes[0])
/ (from_indexes[1] - from_indexes[0]);
}
return index_of_from_text + latest_offset_delta;
}
LCS.corresponding_index = get_corresponding_index;
// unfinished
function diff_with_Array(to, options) {
function append(array, item, item_index) {
if (item) {
if (Array.isArray(item_index)) {
item_index = item_index[0];
}
if (Array.isArray(item)) {
array.append(item.filter(function(i, index) {
if (i) {
array.index.push(item_index + index);
return true;
}
}));
} else {
array.index.push(item_index);
array.push(item);
}
}
}
var diff = LCS(this || [], to || [], Object.assign({
diff : true
}, options)), from_added = [], to_added = [],
// 避免經過重排後,已經無法回溯至原先資料。
from_added_index = from_added.index = [], move_to = Object.create(null);
to_added.index = [];
// TODO: diff其中有undefined
diff.forEach(function(pair) {
append(from_added, pair[0], pair.index[0]);
append(to_added, pair[1], pair.index[1]);
});
function scan_list(from_item, from_index) {
// assert: {String}item
var index = to_added.indexOf(from_item);
// 去掉完全相同的行。
if (index !== NOT_FOUND) {
move_to[from_added.index[from_index]] = to_added.index[index];
to_added.index.splice(index, 1);
to_added.splice(index, 1);
from_added.index.splice(from_index, 1);
from_added[from_index] = false;
return;
}
if (typeof from_item === 'string') {
from_item = from_item.chars(true);
}
// use LCS() again
var max_LCS_length = 0,
// ↑ = Math.max(20, from_item.length / 2 | 0)
max_LCS_data;
to_added.forEach(function(to_item, to_index) {
// assert: from_item, to_item 皆無 "\n"
// console.log(to_item);
if (typeof to_item === 'string') {
to_item = to_item.chars(true);
}
var trace_Array = LCS_length(from_item, to_item, true),
// const
this_LCS_length = trace_Array.at(-1);
if (max_LCS_length < this_LCS_length) {
max_LCS_length = this_LCS_length;
max_LCS_data = [ to_index, trace_Array, to_item ];
}
});
if (!max_LCS_data) {
return;
}
var diff = from_item.diff_with(max_LCS_data[2], {
trace_Array : max_LCS_data[1]
});
if (diff[1]) {
to_added[max_LCS_data[0]] = diff[1];
} else {
to_added.splice(max_LCS_data[0], 1);
}
from_added[from_index] = from_item = diff[0];
if (from_item) {
// 經過變更之後,可能需要再掃描一次。
scan_list(from_item, from_index);
}
}
// 檢查是否有被移到前方的,確保回傳的真正是 unique 的。在只有少量增加時較有效率。
from_added.forEach(scan_list);
from_added = from_added.filter(function(item) {
return !!item;
});
if (from_added.length === 0) {
from_added = [];
} else {
// 將item轉為{String}
from_added = [ from_added.map(function(line) {
return Array.isArray(line) ? line.join('') : line;
}) ];
}
if (to_added.length > 0) {
// 將item轉為{String}
to_added = to_added.map(function(line) {
return Array.isArray(line) ? line.join('') : line;
});
from_added[1] = to_added;
}
from_added.index = from_added_index;
from_added.moved = move_to;
return from_added;
}
function diff_with_String(to, options) {
return diff_with_Array.call((this || '').split('\n'),
Array.isArray(to) ? to : (to || '').split('\n'), options);
}
// ---------------------------------------------------------------------//
if (false) {
styled_list = CeL.coloring_diff('a b c d', 'a a c c', {
headers : [ 'header 1: ', 'header 2: ' ],
print : true
});
}
// 為 diff 著色。
function coloring_diff(from, to, options) {
options = library_namespace.new_options(options);
var diff_list = library_namespace.LCS(from, to, {
diff : true
});
from = [ from /* , { reset : true } */];
to = [ to /* , { reset : true } */];
var diff, normal_style = options.normal_style || {
// bold : false,
fg : 'white',
bg : 'black'
}, diff_style = options.diff_style || {
// bold : true,
fg : 'red',
bg : 'white'
},
// 用在多出來的文字的格式。
insertion_style = options.insertion_style || {
// bold : true,
fg : 'white',
bg : 'green'
};
function add_diff(styled_list, diff_index, is_insertion) {
if (!diff_index) {
// e.g., 只有義方有多東西。
return;
}
var next_index = diff_index[1] + 1, this_slice = styled_list
.shift();
styled_list.unshift(this_slice.slice(0, diff_index[0]),
is_insertion ? insertion_style : diff_style, this_slice
.slice(diff_index[0], next_index), normal_style,
this_slice.slice(next_index));
}
while (diff = diff_list.pop()) {
var from_index = diff.index[0], to_index = diff.index[1];
// console.trace([ from, from_index, to, to_index ]);
add_diff(from, from_index, !to_index);
add_diff(to, to_index, !from_index);
}
var headers = Array.isArray(options.headers) ? options.headers : [];
from.unshift(headers[0] || '', normal_style);
to.unshift(headers[1] || '', normal_style);
if (options.header_style) {
from.unshift('', options.header_style);
to.unshift('', options.header_style);
}
if (options.print) {
library_namespace.slog(from);
library_namespace.slog(to);
}
// 注意: from.length 不一定等於 to.length
return [ from, to ];
}
_.coloring_diff = coloring_diff;
// ---------------------------------------------------------------------//
/**
* Find the longest common starting substring in a set of strings
*
* TODO: 可以嘗試先分割words整個字比對
*
* @param {Array}string_list
* array of strings
* @returns {ℕ⁰:Natural+0} longest common starting substring length
*
* @see https://stackoverflow.com/questions/1916218/find-the-longest-common-starting-substring-in-a-set-of-strings/1917041#1917041
*/
function longest_common_starting_length(string_list) {
string_list = string_list.filter(function(string) {
return typeof string === 'string';
});
if (string_list.length <= 1) {
return string_list[0] ? string_list[0].length : 0;
}
var char_index = 0;
for (var last = string_list[0].length; char_index < last; char_index++) {
var char_now = string_list[0].charAt(char_index);
for (var index = 1; index < string_list.length; index++) {
if (char_now !== string_list[index].charAt(char_index)) {
return char_index;
}
}
}
return char_index;
}
_.longest_common_starting_length = longest_common_starting_length;
// ---------------------------------------------------------------------//
// https://en.wikipedia.org/wiki/Letter_case#Headings_and_publication_titles
// http://adminsecret.monster.com/training/articles/358-what-to-capitalize-in-a-title
// http://stackoverflow.com/questions/196972/convert-string-to-title-case-with-javascript
// to_title_case()
// Capitalize the first letter of string
// also use CSS: text-transform:capitalize;
// @see Camel_to_underscore() @ data.code
/**
* 轉成標題用之格式。基本上即將每個字的第一個字母轉成大寫。
*
* @param {Boolean}to_lower_first
* 將要轉換的文字先全部轉換成小寫。
*
* @returns {String}標題用之格式文字。
*
* @see function upper_case_initial(words) @ CeL.application.net.wiki
*/
function toTitleCase(to_lower_first) {
// 要轉換的文字。
var title = this.trim();
if (to_lower_first)
title = title.toLowerCase();
return title.replace(/(^|\s)((\w)(\w*))/g,
//
function($0, $1, $2, $3, $4) {
// console.log($0);
return $2 in toTitleCase.lower ? $0
//
: $2 in toTitleCase.upper ? $0.toUpperCase()
//
: ($1 ? ' ' : '') + $3.toUpperCase() + $4;
})
// capitalize the first and last word of the title itself.
// in case title === ''
.replace(/^\w/, function($0) {
return $0.toUpperCase();
}).replace(/\s(\w)([\w\-]*)$/, function($0, $1, $2) {
return ' ' + $1.toUpperCase() + $2;
});
}
/**
* add exception words
*
* @param {String|Array}words
* exception words
*/
toTitleCase.add_exception = function(words, upper) {
// initialize
if (!toTitleCase.lower)
toTitleCase.lower = Object.create(null);
if (!toTitleCase.upper)
toTitleCase.upper = Object.create(null);
var target = upper ? toTitleCase.upper : toTitleCase.lower;
if (typeof words === 'string')
words = words.split(',');
if (Array.isArray(words))
words.forEach(function(word) {
target[word] = true;
});
else
Object.assign(target, words);
};
toTitleCase
.add_exception('at,by,down,for,from,in,into,like,near,of,off,on,onto,over,past,to,upon,with,and,but,or,yet,for,nor,so,as,if,once,than,that,till,when,to,a,an,the');
toTitleCase.add_exception(
'id,tv,i,ii,iii,iv,v,vi,vii,viii,ix,x,xi,xii,xiii', true);
if (false) {
// 警告:這將使 node.js 6.2.2 卡住。
/\[\[(?:[^\[\]]+|\[[^\[]|\][^\]])*\]\]/
.test("[[ [[ ]] [[ ]] [[ ]] ]]");
}
if (false) {
// 若要消除 "'''" 與 "''",應將長的置於前面。
wikitext = wikitext.remove_head_tail("'''", 0, ' ').remove_head_tail(
"''", 0, ' ');
}
/**
* 去除首尾。這或許該置於 CeL.data.native...
*
* @param {String}text
* 指定的輸入字串。
* @param {String}head
* 欲移除之首字串。
* @param {String}[tail]
* 欲移除之尾字串。
* @param {String}[insert_string]
* 將首尾以((insert_string))取代。 有設定 insert_string 時,會保留內容物。未設定
* insert_string 時,會將內容物連同首尾一同移除。
*
* @returns {String}replaced text. 變更/取代後的結果。
*/
function remove_head_tail(text, head, tail, insert_string) {
var head_eq_tail, index_start, index_end;
if (!tail) {
tail = head;
head_eq_tail = true;
} else {
head_eq_tail = head === tail;
}
var head_length = head.length, tail_length = tail.length;
while (true) {
if (head_eq_tail) {
// 改採自前面搜尋模式。
index_start = text.indexOf(head);
if (index_start === NOT_FOUND) {
// 無首
return text;
}
index_end = text.indexOf(tail, index_start + head_length);
if (index_end === NOT_FOUND) {
// 有首無尾
return text;
}
} else {
index_end = text.indexOf(tail);
if (index_end === NOT_FOUND) {
// 無尾
return text;
}
// 須預防中間包含 head / tail 之字元。
index_start = text.lastIndexOf(head, index_end - head_length);
if (index_start === NOT_FOUND) {
// 有尾無首
return text;
}
}
text = text.slice(0, index_start)
// 未設定 insert_string 時,會將內容物連同首尾一同移除。
+ (insert_string === undefined ? '' : insert_string
// 有設定 insert_string 時,會保留內容物。
+ text.slice(index_start + head_length, index_end) + insert_string)
+ text.slice(index_end + tail_length);
}
}
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace
var has_replacement_offset;
'ab'.replace(/b/g, function(all, offset, string) {
has_replacement_offset = offset === 1 && string === 'ab';
});
/**
* 持續執行 .replace(),直到處理至穩定平衡無變動為止。
*
* @param {String}text
* 指定的輸入字串。
* @param {RegExp}pattern
* 要搜索的正規表示式/規則運算式模式。
* @param {String|Function}replace_to
* 用於替換的字串。
*
* @returns {String}replaced text. 變更/取代後的結果。
*/
function replace_till_stable(text, pattern, replace_to) {
if (false)
library_namespace.debug('pattern: ' + pattern, 6,
'replace_till_stable');
for (var original; original !== text;) {
original = text;
if (has_replacement_offset) {
// new browsers
text = original.replace(pattern, replace_to);
} else {
// old browsers
text = original.replace(pattern, function(all) {
var args = Array.from(arguments);
args.push(original.indexOf(all, pattern.lastIndex || 0),
// original_string 原字串。
original,
// 缺乏 group 的代表非常舊的 JavaScript 引擎版本,只能模擬一個代替。
Object.create(null));
replace_to.apply(undefined, args);
});
}
if (false) {
library_namespace.debug('[' + original + '] '
+ (original === text ? 'done.' : '→ [' + text + ']'),
6, 'replace_till_stable');
}
}
return text;
}
/**
* 當欲變更/取代文字前後的文字符合要求時,才執行取代。
*
* @param {String}text
* 指定的輸入字串。
* @param {RegExp}pattern
* 要搜索的正規表示式/規則運算式模式。
* @param {String|Function}replace_to
* 用於替換的字串。
* @param {Function|Undefined}[match_previous]
* filter match_previous(previous token) return true if it's OK
* to replace, false if it's NOT OK to replace.
* @param {Function|Undefined}[match_following]
* filter match_following(next token) return true if it's OK to
* replace, false if it's NOT OK to replace.
*
* @returns {String}replaced text. 變更/取代後的結果。
*/
function replace_check_near(text, pattern, replace_to, match_previous,
match_following) {
var matched, results = [], last_index = 0;
if (!pattern.global) {
library_namespace.debug("The pattern doesn't has 'global' flag!",
2, 'replace_check_near');
}
while (matched = pattern.exec(text)) {
library_namespace.debug(pattern + ': ' + matched, 5,
'replace_check_near');
var previous_text = text.slice(last_index, matched.index),
//
_last_index = matched.index + matched[0].length;
if ((!match_previous || match_previous(previous_text))
// context 上下文 前後文
// 前面的 foregoing paragraphs, see above, previously stated, precedent
// 後面的 next; behind rearwards;back;posteriority;atergo;rearward
&& (!match_following || match_following(text.slice(_last_index)))) {
last_index = pattern.lastIndex;
library_namespace.debug(previous_text + ',' + matched[0] + ','
+ matched[0].replace(pattern, replace_to), 5,
'replace_check_near');
results.push(
//
previous_text, matched[0].replace(pattern, replace_to));
// restore lastIndex.
pattern.lastIndex = last_index;
last_index = _last_index;
}
if (!pattern.global) {
// 僅執行此一次。
break;
}
}
// 收尾。理想的 pattern 應該用 /([\s\S]*?)(delimiter|$)/g 之類,如此則無須收尾。
if (last_index < text.length) {
if (last_index === 0)
// 完全沒相符的。
return text;
results.push(text.slice(last_index));
}
return results.join('');
}
var PATTERN_bigrams = /.{2}/g;
/**
* Get Sørensen index, or Dice's coefficient.
*
* String.similarity()
*
* @param {String}string_1
* sequence 1
* @param {String}string_2
* sequence 2
*
* @returns {Number}index (or named coefficient)
*
* @see https://en.wikipedia.org/wiki/S%C3%B8rensen%E2%80%93Dice_coefficient
*/
function similarity_coefficient(string_1, string_2) {
var count = 0,
//
bigrams_1 = string_1.match(PATTERN_bigrams).concat(
string_1.slice(1).match(PATTERN_bigrams)),
//
bigrams_2 = string_2.match(PATTERN_bigrams).concat(
string_2.slice(1).match(PATTERN_bigrams));
bigrams_1.forEach(function(bigram) {
if (bigrams_2.includes(bigram))
count++;
});
// 0–1
return 2 * count / (bigrams_1.length + bigrams_2.length);
}
function Array_truncate(length) {
length = Math.max(0, length | 0);
// This is faster than ((this.length = 0))
while (this.length > length) {
this.pop();
}
return this;
}
// ------------------------------------
// 傳回字串於等寬字型monospaced font的寬度。螢幕對齊用。
// 對fullwidth全形字元, width應算2
// http://hyperrate.com/topic-view-thread.php?tid=3322
// TODO: 警告:本函數尚未完善
// @see http://unicode.org/Public/UNIDATA/EastAsianWidth.txt
function String_display_width(string, font) {
// 須注意:不同的字型對不同字元的規範可能不同!如'→'可能為2或1
// @see [[en:Arrow (symbol)]]
string = String(string).replace(/[\u2190-\u21FF]/g, ' ');
// @see http://www.allenkuo.com/genericArticle/view1299.aspx
// @see [[zh:全形和半形]]
return string.replace(/[\u3000-\uFF5E]/g, ' ').length;
}
var DEFAULT_DISPLAY_WIDTH = 80;
// 螢幕寬度多少字元。
function screen_display_width() {
return library_namespace.platform.nodejs && process.stdout.columns
// process.stdout.columns 可能被設定為0。 e.g., at Travis CI
|| DEFAULT_DISPLAY_WIDTH;
}
if (false) {
library_namespace.debug('screen_display_width: '
+ screen_display_width());
}
// CLI 螢幕顯示對齊用。e.g., 對比兩者。
// left justification, to line up in correct
function display_align(lines, options) {
// 前置作業。
options = library_namespace.setup_options(options);
if (library_namespace.is_Object(lines)) {
// pairs/lines={key:value,key:value,...}
lines = Object.entries(lines);
}
var use_display_width = options.display_width || screen_display_width();
library_namespace.debug('display width: ' + use_display_width, 3);
var key_display_width = [], line_separator = String('line_separator' in options ? options.line_separator
// '\n'
: determine_line_separator()), force_using_new_line = line_separator === '\r\n' ? '\n'
: line_separator;
var some_has_new_line = lines.some(function(line) {
var key = String(line[0]), value = String(line[1]);
// assert: key.includes(line_separator) === false
if ((value.length > use_display_width)
|| value.includes(force_using_new_line)) {
return true;
}
key_display_width.push(String_display_width(key));
});
var key_style = options.key_style,
// e.g., value_style : { color : 'green' }
value_style = options.value_style,
// 採用醒目多色彩的顯示方式。
using_style = !!('using_style' in options ? options.using_style
: key_style || value_style),
//
display_lines = [], max_key_display_width = !some_has_new_line
&& Math.max.apply(null, key_display_width);
lines.forEach(function(line) {
var key = String(line[0]), value = line[1];
if (some_has_new_line) {
key = key.trim();
value = String(value);
if (using_style) {
value = [ key_style ? {
T : key,
S : key_style
} : key, line_separator, value_style ? {
T : value,
S : value_style
} : value ];
} else {
value = key + line_separator + value;
}
} else {
// 可能沒有 key.padStart()!
key = key.pad(key.length + max_key_display_width
// assert: String_display_width(' ') === 1
- key_display_width.shift(), options.to_fill || ' ',
options.from_start);
if (using_style) {
value = [ key_style ? {
T : key,
S : key_style
} : key, value_style ? {
T : value,
S : value_style
} : value ];
} else {
value = key + value;
}
}
if (using_style) {
if (display_lines.length > 0) {
// 前面有東西就先跳一行。
display_lines.push(line_separator);
}
display_lines.append(value);
} else {
display_lines.push(value);
}
});
return using_style ? display_lines : display_lines.join(line_separator);
}
_.display_align = display_align;
// ------------------------------------
var has_native_Math_imul = Math.imul
&& !Math.imul[library_namespace.env.not_native_keyword];
// ------------------------------------
set_method(String.prototype, {
covers : function(string) {
return this.length >= string.length
//
&& !!String_covers(string, this);
},
count_of : set_bind(count_occurrence, true),
// gText : getText,
// HTML_to_Unicode : HTML_to_Unicode,
// split_by : split_String_by_length,
chunk : chunk,
// 對於可能出現 surrogate pairs 的字串,應當以此來取代 .split('')!
chars : split_by_code_point,
codePoints : codePoints,
remove_head_tail : set_bind(remove_head_tail, true),
// repeatedly replace till stable
replace_till_stable : set_bind(replace_till_stable, true),
replace_check_near : set_bind(replace_check_near, true),
pad : set_bind(pad, true),
// 2021/5/4: ''.toRegExp() remane to → ''.to_RegExp()
to_RegExp : set_bind(String_to_RegExp, true),
toTitleCase : toTitleCase,
between : function(head, foot, index, return_data) {
// 確保可用 string.between().between() 的方法來作簡易篩選。
/**
*
var data = get_intermediate([ this, head, foot, index ]);
return data[3] !== NOT_FOUND && data[4] || '';
*/
return get_intermediate(this, head, foot, index,
//
return_data) || '';
},
find_between : find_between,
all_between : all_between,
each_between : each_between,
edit_distance : set_bind(Levenshtein_distance),
diff_with : diff_with_String,
display_width : set_bind(String_display_width),
// https://docs.oracle.com/javase/10/docs/api/java/lang/String.html#hashCode()
hashCode : has_native_Math_imul
// https://gist.github.com/hyamamoto/fd435505d29ebfa3d9716fd2be8d42f0
? function hashCode_imul() {
var hash = 0;
for (var index = 0, length = this.length; index < length; index++) {
hash = (Math.imul(31, hash) + this.charCodeAt(index))
// Convert to 32bit integer
| 0;
}
return hash;
}
// https://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/
// https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript
: function hashCode_left_shift() {
var hash = 0;
for (var index = 0, length = this.length; index < length; index++) {
hash = ((hash << 5) - hash + this.charCodeAt(index))
// Convert to 32bit integer
| 0;
}
return hash;
}
});
set_method(Number.prototype, {
// 'super' 於 IE 為保留字。
to_super : superscript_integer,
to_sub : subscript_integer,
to_fixed : to_fixed,
mod : set_bind(non_negative_modulo),
pad : set_bind(pad, true)
});
set_method(RegExp.prototype, {
clone : function() {
// TODO: using Object.getOwnPropertyNames() to copy others
return new RegExp(this.source, this.flags);
},
reflags : set_bind(renew_RegExp_flags)
});
set_method(library_namespace.env.global, {
// 在 old IE 中 typeof alert==='object'
// alert : JSalert,
// https://developer.mozilla.org/en-US/docs/Web/API/Window/setImmediate
// https://github.com/YuzuJS/setImmediate/blob/master/setImmediate.js
// TODO: window.postMessage can be used to trigger an immediate but
// yielding callback.
setImmediate : function setImmediate(callback) {
var args = arguments;
// 因為 setTimeout(callback, 0) 可能使 callback 傳入未規範的 arguments,因此不在外面處理
// callback。
return setTimeout(function() {
if (args.length === 0) {
callback();
return;
}
args = Array.from(args);
args.shift();
callback.apply(null, args);
}, 0);
},
clearImmediate : function clearImmediate(id) {
return clearTimeout(id);
}
});
// 建議不用,因為在for(in Array)時會...
set_method(Array.prototype, {
// Array.prototype.clone
clone : function() {
// TODO: using Object.getOwnPropertyNames() to copy others
return this.slice();
},
remove_once : function(value) {
var index = this.indexOf(value);
if (index !== NOT_FOUND)
return this.splice(index, 1);
},
// remove all. https://esdiscuss.org/topic/array-prototype-remove-item
// value 很多的話,應該用 Set, 或 delete + 去除 blank。
remove : function(value) {
var index = 0;
while ((index = this.indexOf(value, index)) !== NOT_FOUND)
this.splice(index, 1);
},
// TODO: should use Array.prototype.reduce()
// e.g., array.reduce((p,e)=>p+e)
// Array.prototype.sum()
sum : function(using_index) {
// total summation
// ABSORBING_ELEMENT
var sum = 0;
this.forEach(using_index ? function(e, i) {
sum += i;
} : function(e) {
sum += +e;
});
return sum;
},
// Array.prototype.product()
product : function(using_index) {
// MULTIPLICATIVE_IDENTITY
var product = 1;
this.every(using_index ? function(e, i) {
return product *= i;
} : function(e) {
return product *= +e;
});
return product;
},
// TODO: Object.revert_key_value(array, get_key, hash)
//
// Array.prototype.to_hash()
// ['1e3',5,66]→{'1e3':0,'5':1,'66':2}
// {Function}[get_key]
to_hash : function(get_key, hash) {
if (false) {
return Object.assign(hash || Object.create(null),
//
Object.fromEntries(this.map(function(value, index) {
if (get_key)
value = get_key(value);
if (typeof value === 'object')
value = JSON.stringify(value);
return [ value, index ];
})));
}
if (!hash) {
hash = Object.create(null);
}
// TODO: 衝突時處理。
this.forEach(get_key ? function(value, index) {
value = get_key(value);
hash[typeof value === 'object' ? JSON.stringify(value)
//
: value] = index;
} : function(value, index) {
hash[typeof value === 'object' ? JSON.stringify(value)
//
: value] = index;
});
return hash;
},
// Array.prototype.frequency()
frequency : array_frequency,
// clone: Array.prototype.slice,
append : append_to_Array,
unique_sorted : unique_sorted_Array,
/**
* 去掉已排序,或最起碼將相同元素集在一起之 Array 中重複的 items。
*
* 應能確保順序不變。
*/
unique : function unique_Array(sorted, get_key) {
if (typeof sorted === 'function') {
// shift arguments.
get_key = sorted;
sorted = false;
} else if (sorted) {
return this.unique_sorted(get_key);
} else if (typeof get_key !== 'function') {
return Array.from(new Set(this));
}
// @see function Array_intersection_Map()
var map = new Map;
function set_item(item) {
if (!map['has'](item))
map['set'](get_key(item), item);
}
this.forEach(set_item);
return Array.from(map.values());
},
// Check if there is only one unique/single value in the array.
// 集合中包含不重複的元素的個數=1
cardinal_1 : function cardinal_1() {
var configured, value;
value = this.every(function(element) {
if (configured) {
return Object.is(element, value);
}
value = element;
return configured = true;
});
return !!(configured && value);
},
// Array.prototype.search_sorted
search_sorted : set_bind(search_sorted_Array, true),
// Array.prototype.first_matched
first_matched : set_bind(first_matched, true),
diff_with : diff_with_Array,
// warpper
run_serial :
// [].run_serial(for_each(run_next, item, index, list), callback, _this)
function Array_run_serial_asynchronous(for_each, callback, _this) {
run_serial_asynchronous(for_each, this, callback, _this);
},
// [].run_parallel(for_each(run_next, item, index, list, get_status),
// callback, _this)
run_parallel : function Array_run_parallel_asynchronous(for_each,
callback, _this) {
run_serial_asynchronous(for_each, this, callback, _this, true);
},
truncate : Array_truncate,
// empty the array. 清空 array. truncate
// Array.prototype.clear()
clear : Array_truncate
});
// ---------------------------------------------------------------------//
/**
* patch: parse ISO date String for IE.
* for this function, you should also include 'data.code.compatibility' for
* toISOString().
*
* @example
CeL.log((new Date).toISOString());
CeL.log('' + CeL.ISO_date((new Date).toISOString()));
*
*
* @param {String}ISO_date_String
*
* @returns {Date} date
*
* @since 2014/7/26 11:56:1
*/
function IE_ISO_date(ISO_date_String) {
return new Date(IE_ISO_date.parse(ISO_date_String));
}
// 應測試是否可正確 parse。
if (isNaN(Date.parse('0000-01-01T00:00:00.000Z'))) {
// IE8?
IE_ISO_date.offset = (new Date).getTimezoneOffset();
IE_ISO_date.parse = function(ISO_date_String) {
if (false) {
library_namespace.debug(ISO_date_String
.replace(/\.\d{3}Z$/, '').replace(/-/, '/'));
library_namespace.debug(Date.parse(ISO_date_String.replace(
/\.\d{3}Z$/, '').replace(/-/, '/')));
}
return Date.parse(ISO_date_String.replace(/\.\d{3}Z$/, '').replace(
/-/, '/'))
+ IE_ISO_date.offset;
};
} else {
// normal.
IE_ISO_date.parse = Date.parse;
}
_.ISO_date = IE_ISO_date;
set_method(Date.prototype, {
clone : function() {
// TODO: using Object.getOwnPropertyNames() to copy others
return new Date(this.getTime());
}
});
// ---------------------------------------------------------------------//
var fulfilled = Object.create(null);
// assert: needs Promise.race()!
//
// status_handler(value is fulfilled && ('not thenable' || 'resolved' ||
// 'rejected'), value: this_thenable)
//
// return promise to wait for the result
function status_of_thenable(value, status_handler) {
// Promise.isPromise()
if (!library_namespace.is_thenable(value)) {
status_handler('not thenable', value);
return;
}
// https://stackoverflow.com/questions/30564053/how-can-i-synchronously-determine-a-javascript-promises-state
// https://github.com/kudla/promise-status-async/blob/master/lib/promiseState.js
/**
*
Promise.race([value, fulfilled]).then(v => { status = v === t ? "pending" : "fulfilled" }, () => { status = "rejected" });
*/
return Promise.race([ value, fulfilled ])
//
.then(function(first_fulfilled) {
var result = first_fulfilled !== fulfilled && 'resolved';
status_handler(result, value);
return result;
}, function(error_reason) {
// assert: first_fulfilled !== fulfilled
// 因為 fulfilled 不會 throw
var result = 'rejected';
status_handler(result, value);
return result;
});
}
_.status_of_thenable = status_of_thenable;
// ---------------------------------------------------------------------//
// reverse key and value, 改成 value → key
function Map__reverse_key_value(options) {
// 前置處理。
options = library_namespace.setup_options(options);
var preserve_older = options.preserve_older,
// original key is number
key_is_number = options.key_is_number,
//
ignore_null_value = options.ignore_null_value;
// if (library_namespace.env.has_for_of)
var reversed_Map = new Map;
this.forEach(preserve_older === false ? function(key, value) {
if (ignore_null_value && !key)
return;
if (key_is_number)
value = +value;
// newer will overwrite older
reversed_Map.set(key, value);
} : function(key, value) {
if (ignore_null_value && !key)
return;
if (key_is_number)
value = +value;
if (reversed_Map.has(key)) {
if (!preserve_older) {
// ignore exists
return;
}
library_namespace
.warn('Map__reverse_key_value: duplicated key [' + key
+ ']: ' + reversed_Map.get(key) + '→' + value);
}
reversed_Map.set(key, value);
});
return reversed_Map;
}
set_method(Map.prototype, {
reverse_key_value : Map__reverse_key_value
});
// ------------------------------------
return (_// JSDT:_module_
);
}