mirror of
https://scm.univ-tours.fr/22107988t/rappaurio-sae501_502.git
synced 2025-08-29 08:45:58 +02:00
5134 lines
156 KiB
JavaScript
5134 lines
156 KiB
JavaScript
/**
|
||
* @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 <code>
|
||
|
||
// More examples: see /_test suite/test.js
|
||
|
||
* </code>
|
||
*
|
||
* @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.<br />
|
||
* .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;
|
||
|
||
/**
|
||
* <code>
|
||
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() 時無法使用。
|
||
</code>
|
||
*/
|
||
|
||
_// 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);
|
||
};
|
||
|
||
/**
|
||
* <code> 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<scalar(@b);$i++){
|
||
$r.=sprintf('%%%X',ord($b[$i]));
|
||
};
|
||
|
||
|
||
</code>
|
||
*/
|
||
// 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.<br />
|
||
* escape RegExp pattern,以利作為 RegExp source 使用。<br />
|
||
* cf. qq// in perl.
|
||
*
|
||
* <code>
|
||
* String.prototype.to_RegExp_pattern = function(f) { return to_RegExp_pattern(this.valueOf(), f); };
|
||
* </code>
|
||
*
|
||
* @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。<br />
|
||
* TODO:<br />
|
||
* and, or, not.<br />
|
||
* (?:(^|\s*\|)\s*(!)?(\/(?:[^\/]+|\\\/)(\/([a-z]*))?|\\(\S+)|\S+))+<br />
|
||
* {Object|Array}preprocessor<br />
|
||
*
|
||
* cf. CeL.to_RegExp_pattern()
|
||
*
|
||
* @param {String}pattern
|
||
* 欲轉換成 RegExp 的 pattern text。
|
||
* @param {Object}[options]
|
||
* 附加參數/設定特殊功能與選項 options = {<br />
|
||
* {String}flags : RegExp 的 flags。<br />
|
||
* {Function|String}error_handler : 當遇到不明 pattern 時的處理程序。<br /> }
|
||
*
|
||
* @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));
|
||
|
||
/**
|
||
* <code>
|
||
|
||
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.
|
||
|
||
</code>
|
||
*/
|
||
|
||
/**
|
||
* 重新設定 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 <code>
|
||
|
||
// 附帶 '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)) {
|
||
// ...
|
||
}
|
||
|
||
</code>
|
||
*
|
||
* @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;
|
||
|
||
// ---------------------------------------------------------------------//
|
||
|
||
/**
|
||
* <code> 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:
|
||
檔案名稱不可包含字元 ** 不包含目錄分隔字元 [\\/]:
|
||
/:*?"<>|/
|
||
|
||
</code>
|
||
*/
|
||
|
||
// 萬用字元 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: 兩者等長但不相同,<br />
|
||
* 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' : '<br />');
|
||
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 = '<br />';
|
||
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(/<w?br([^>]*)>/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 <a
|
||
* href="http://stackoverflow.com/questions/7033639/javascript-split-large-string-in-n-size-chunks"
|
||
* accessdate="2015/3/2 23:27">regex - javascript: Split large string
|
||
* in n-size chunks - Stack Overflow</a>
|
||
*/
|
||
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('')!<br />
|
||
* 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.<br />
|
||
* 取得 text 中,head 與 foot 之間的字串。不包括 head 與 foot。<br />
|
||
* 可以 [3] last index 是否回傳 NOT_FOUND (-1) 檢測到底是有找到,只是回傳空字串,或是沒找到。
|
||
*
|
||
* TODO: {RegExp}head, {RegExp}foot
|
||
*
|
||
* @example <code>
|
||
|
||
// More examples: see /_test suite/test.js
|
||
|
||
* </code>
|
||
*
|
||
* @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.<br />
|
||
* 取得 text 中,head 與 foot 之間的字串。不包括 head 與 foot。<br />
|
||
* 回傳 undefined 表示沒找到。只是回傳空字串表示其間為空字串。
|
||
*
|
||
* TODO: lastIndexOf()
|
||
*
|
||
* @example <code>
|
||
|
||
// More examples: see /_test suite/test.js
|
||
|
||
* </code>
|
||
*
|
||
* @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;
|
||
|
||
/**
|
||
* <code>
|
||
|
||
// 推薦 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') {
|
||
;
|
||
}
|
||
|
||
</code>
|
||
*/
|
||
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<br />
|
||
* all_between()→find_between()
|
||
*
|
||
* TODO:<br />
|
||
* return {Iterator} of all between
|
||
*
|
||
* @see http://jsrocks.org/cn/2015/09/javascript-iterables-and-iterators/
|
||
* http://es6.ruanyifeng.com/#docs/symbol
|
||
*
|
||
* <code>
|
||
|
||
// 推薦用法
|
||
var get_next_between = html.find_between('>', '<'), text;
|
||
|
||
while ((text = get_next_between()) !== undefined) {
|
||
text;
|
||
}
|
||
|
||
</code>
|
||
*/
|
||
|
||
// 採用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.<br />
|
||
* 計算 string 中出現 search 之次數。<br />
|
||
*
|
||
* 用 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 <code>
|
||
* 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));
|
||
* </code>
|
||
*
|
||
* @_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 位,<br />
|
||
* 肇因: JScript即使在做加減運算時,有時還是會出現 3*1.6=4.800000000000001,<br />
|
||
* 2.4/3=0.7999999999999999 等數值。此函數可取至 1.4 與 0.1,避免 <a
|
||
* href="http://en.wikipedia.org/wiki/Round-off_error" accessdate="2012/9/19
|
||
* 22:21" title="Round-off error">round-off error</a>。<br />
|
||
* 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 <code>
|
||
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));
|
||
* </code>
|
||
*
|
||
* @_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;
|
||
}
|
||
|
||
/**
|
||
* <code>
|
||
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()
|
||
|
||
</code>
|
||
*
|
||
* @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}<br />
|
||
* for Object.filter()
|
||
*
|
||
* @param {Object}object
|
||
* object to filter
|
||
* @param {Function}filter
|
||
* callback/receiver to filter the value. <br />
|
||
* 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.<br />
|
||
* 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.<br />
|
||
* 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.<br />
|
||
* 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.<br />
|
||
* 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
|
||
};
|
||
}
|
||
|
||
/**
|
||
* <code>
|
||
|
||
// 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
|
||
|
||
</code>
|
||
*/
|
||
|
||
// 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<code>
|
||
|
||
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');
|
||
|
||
|
||
</code>
|
||
*/
|
||
|
||
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<int>()
|
||
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。<br />
|
||
* binary search an Array.<br /> ** 注意:使用前須先手動將 array 排序!<br />
|
||
* TODO: 依資料分布:趨近等差/等比/對數等,以加速搜尋。
|
||
*
|
||
* cf. Array.prototype.search()
|
||
*
|
||
* @param {Array}array
|
||
* 由小至大已排序的 array。
|
||
* @param value
|
||
* value to search.
|
||
* @param {Object}[options]
|
||
* 附加參數/設定特殊功能與選項 options = {<br />
|
||
* found : found_callback(index, not_found:
|
||
* closed/is_near/未準確相符合,僅為趨近、近似),<br />
|
||
* near : not_found_callback(較小的 index, not_found),<br />
|
||
* start : start index,<br />
|
||
* last : last/end index,<br />
|
||
* length : search length.<br />
|
||
* <em>last 與 length 二選一。</em><br /> }
|
||
*
|
||
* @returns default: 未設定 options 時,未找到為 NOT_FOUND(-1),找到為 index。
|
||
*
|
||
* @since 2013/3/3 19:30:2 create.<br />
|
||
*/
|
||
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.<br />
|
||
* merge 2 array by order, without order change<br />
|
||
* 警告: 此法僅於無重複元素時有效。
|
||
*
|
||
* @param {Array}sequence_list
|
||
* sequence list to merge
|
||
*
|
||
* @returns {Array}merged chain
|
||
*
|
||
* @see find duplicate part of 2 strings<br />
|
||
* 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.<br />
|
||
* 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 <code>
|
||
|
||
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'; });
|
||
|
||
* </code>
|
||
*
|
||
* @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].<br />
|
||
* 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;
|
||
}
|
||
|
||
/**
|
||
* <code>
|
||
|
||
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
|
||
|
||
</code>
|
||
*/
|
||
return all_list;
|
||
}
|
||
|
||
_.LCS = LCS;
|
||
|
||
/**
|
||
* <code>
|
||
|
||
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
|
||
|
||
</code>
|
||
*/
|
||
|
||
// 取得 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() 的方法來作簡易篩選。
|
||
/**
|
||
* <code>
|
||
var data = get_intermediate([ this, head, foot, index ]);
|
||
return data[3] !== NOT_FOUND && data[4] || '';
|
||
</code>
|
||
*/
|
||
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.<br />
|
||
* for this function, you should also include 'data.code.compatibility' for
|
||
* toISOString().
|
||
*
|
||
* @example <code>
|
||
|
||
CeL.log((new Date).toISOString());
|
||
CeL.log('' + CeL.ISO_date((new Date).toISOString()));
|
||
|
||
* </code>
|
||
*
|
||
* @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
|
||
/**
|
||
* <code>
|
||
Promise.race([value, fulfilled]).then(v => { status = v === t ? "pending" : "fulfilled" }, () => { status = "rejected" });
|
||
</code>
|
||
*/
|
||
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_
|
||
);
|
||
}
|