TODO
將 module_name 改成 arguments
http://threecups.org/?p=129
http://cdnjs.com/
listen language change event
play board
use Singleton pattern,
Module 模式或單例模式(Singleton)為 Douglas Crockford 所推崇,並被大量應用在 Yahoo User Interface Library YUI。
http://wiki.forum.nokia.com/index.php/JavaScript_Performance_Best_Practices
http://ioio.name/core-javascript-pitfalls.html
CommonJS
http://www.heliximitate.cn/studyblog/archives/tag/commonjs
*/
/**
*
// TODO
// 2011/7/31 21:18:01
//module
//typeof CeL_id === 'string' && typeof this[CeL_id] === 'function' &&
typeof CeL === 'function' && CeL.run({
name:[module_name],
require:[function_name,module_name],
code:function(CeL){
var private_value=1;
function module_function_1(arg) {
;
}
module_function_1.required='';
function module_class_1(arg) {
;
}
function get_value(){
return private_value;
}
module_class_1.prototype.print=function(){};
module_class_1.print=function(){};
return {module_function_1,module_class_1};
}
});
*/
// void(
// typeof CeL !== 'function' &&
(
/**
* We can redefine native values only for undefined.
// // 前導作業/前置處理。
// if (!library_namespace.is_Object(options))
// options = Object.create(null);
// →
// options = library_namespace.setup_options(options);
// options = library_namespace.setup_options(options, true);
*
*
* @param {Object}[options]
* 附加參數/設定選擇性/特殊功能與選項。
* @param {Boolean}[new_one]
* 重新造出可被更改的選項。當會更改到options時,再設定此項。
*
* @returns {Object}選項。
*
* @since 2016/3/13 13:58:9
*/
function _setup_options(options, new_one) {
if (options && !new_one) {
return options;
}
// create a new one. copy options.
// or use Object.clone(options)
options = Object.assign(Object.create(null), options);
// 註冊為副本。
options.new_NO = (options.new_NO | 0) + 1;
return options;
}
/**
* setup options. 前置處理 options,正規化並提供可隨意改變的同內容參數,以避免修改或覆蓋附加參數。
// // 前導作業/前置處理。
// if (!library_namespace.is_Object(options))
// options = Object.create(null);
// →
// options = library_namespace.setup_options(options);
*
*
* @param {Object}[options]
* 附加參數/設定選擇性/特殊功能與選項。
*
* @returns {Object}選項。
*
* @since 2016/3/13 13:58:9
*/
function setup_options(options) {
if (typeof options === 'string') {
// e.g., 'bot' → {bot:true}
// e.g., 'bot|minor' → {bot:true,minor:true}
var _options = Object.create(null), i = 0;
for (options = options.split('|'); i < options.length; i++) {
if (options[i]) {
_options[options[i]] = true;
}
}
return _options;
}
// e.g., number: Invalid option?
return (typeof options === 'object' /* || typeof options === 'function' */)
// typeof null === 'object'
&& options || Object.assign(Object.create(null), options);
}
/**
* setup options. 前置處理 / clone options,避免修改或覆蓋附加參數。
// 前導作業/前置處理。
// 重新造一個 options 以避免污染。
if (!library_namespace.is_Object(options))
options = Object.create(null);
// →
options = library_namespace.new_options(options);
// 使用新語法。
options = { ...options };
*
* @param {Object}[options]
* 附加參數/設定選擇性/特殊功能與選項。
*
* @returns {Object}選項。
*
* @since 2016/03/14 16:34:09
*/
function new_options(options) {
// create a new one. copy options.
// or use Object.clone(options)
var length = arguments.length;
if (_.is_Object(options)) {
if ((new_options.new_key in options) && length === 1) {
// converted
return options;
}
options = Object.assign(Object.create(null), options);
} else {
options = Object.create(null);
}
if (length > 1) {
for(var i = 1; i < length; i++)
// if (_.is_Object(arguments[i]))
if (arguments[i])
Object.assign(options, arguments[i]);
}
Object.defineProperty(options, new_options.new_key, {
// let [new_options.new_key] deletable
configurable : true,
// 不允許 enumerable 以避免此屬性被使用。
// enumerable : false
value : true
});
return options;
}
new_options.new_key = 'is new options';
// 不會更動 options 的用此。
_.setup_options = setup_options;
// 會更動 options 的用此。
_.new_options = new_options;
var modify_function_hash = Object.create(null);
_// JSDT:_module_
.
/**
* simple evaluates to get the value of specified variable identifier name.
*
* 不使用 eval() 的方法,一層一層 call name-space。
*
* BUG: 無論是不是相同 name_space,只要 variable_name 相同,即會執行 modify_function。
CeL.is_type(value_to_test, 'Array');
*
* @since 2009/12/14 19:50:14
* @see JavaScript类型检测小结(下) - 岁月如歌
* // 大量驗證時,推薦另外在本身 scope 中造出捷徑:
* _.OtS = Object.prototype.toString;
* var is_Person = CeL.type_tester('Person', 'OtS');
* // test
* if(is_Person(value))
* // it's really a Person object
* ;
*
*
* @param {String}want_type
* object type to compare
* @param {String}[toString_reference]
* a reference name to Object.prototype.toString
*
* @returns {Function} type test function
* @since 2009/12/20 08:38:26
*/
type_tester = function type_tester(want_type, toString_reference) {
var t = '[object ' + want_type + ']';
if (false)
return new Function('v', 'return "' + t + '"==='
+ (toString_reference ||
// 在 Google Chrome 中,
// 'Object.prototype.toString' 可以與其 reference 同速度,
// 但其他的 reference 會快些。
'Object.prototype.toString'
)
+ '.call(v);');
return typeof toString_reference === 'string'
&& toString_reference ?
new Function('v', 'return "' + t
+ '"===' + toString_reference + '.call(v);')
// slow@Chrome
: function (v) { return t === get_object_type(v); };
// faster@Chrome
// : new Function('v', 'return "' + t +
// '"===Object.prototype.toString.call(v);');
};
_// JSDT:_module_
.
/**
* Test if the value is a native Function.
*
* @param v
* value to test
* @returns {Boolean} the value is a native Function.
* @since 2009/12/20 08:38:26
*/
is_Function =
// _.type_tester('Function');
function is_Function(v) {
// typeof 比 Object.prototype.toString 快,
// 不過得注意有些 native object 可能 type 是 'function',但不具有 function 特性。
return get_object_type(v) === '[object Function]';
// 須注意,在 firefox 3 中,
// typeof [object HTMLObjectElement] 之外的 HTMLElement 皆 ===
// 'function',
// 因此光用 typeof() === 'function' 而執行下去會得出
// [XPCWrappedNative_NoHelper] Component is not available
if (false)
return typeof v === 'function'
|| get_object_type(v) === '[object Function]';
};
_// JSDT:_module_
.
/**
* Test if the value is a native ECMAScript Object / plain {Object}. is an
* ordinary object.
* CeL.log('full path: [' + CeL.env.registry_path + CeL.env.main_script + ']');
*
*
* @name CeL.env.main_script
* @type {String}
*/
env.main_script = env.main_script_name + env.script_extension;
/**
* module 中的這 member 定義了哪些 member 不被 extend。
*
* @name CeL.env.not_to_extend_keyword
* @type {String}
*/
env.not_to_extend_keyword = 'no_extend';
/**
* 非 native 的 method (native methods / native objects / built-in
* objects), 可由 [KEY_not_native] ([CeL.env.not_native_keyword]) 來判別是否為
* native method。
/*@cc_on
@if(@_PowerPC||@_mac)
OS='Mac';
@else
@if(@_win32||@_win64||@_win16)
OS='Windows';
@else
OS='UNIX'; // unknown
@end
@end@
*/
/**
* 本次執行所在 OS 平台。
*
* @name CeL.env.OS
* @type {String}
*/
env.OS = OS = OS_type || OS
// @see os.version()
|| platform.nodejs && process.platform
// 假如未設定則由 path 判斷。
|| (_.get_script_full_name().indexOf('\\') !== -1 ? 'Windows' : 'UNIX')
//
|| env.OS;
var is_UNIX = env.OS.toLowerCase() in {
// macOS @ node.js
darwin : true,
linux : true,
freebsd : true,
unix : true
};
/**
* 文件預設 line separator / NewLine / new_line / line delimiter。
* in VB: vbCrLf
*
* @name CeL.env.line_separator
* @type {String}
*/
env.line_separator =
is_UNIX ? '\n' : OS === 'Mac' ? '\r'
// e.g., 'win32'
: '\r\n';
/**
* file system 預設 path separator。
* platform-dependent path separator character, 決定目錄(directory)分隔。
*
* @name CeL.env.path_separator
* @type {String}
*
* @see https://stackoverflow.com/questions/125813/how-to-determine-the-os-path-separator-in-javascript
*/
env.path_separator =
platform.nodejs && require('path') && require('path').sep
|| (is_UNIX ? '/' : '\\');
if (env.home && !/[\\\/]$/.test(env.home)) {
// CeL.append_path_separator(CeL.env.home)
env.home += env.path_separator;
}
/**
* library 之外部檔案 (external source files) 放置地。 純目錄名,不加目錄分隔。
*
* @name CeL.env.external_directory_name
* @type {String}
*/
env.external_directory_name = 'external';
/**
* library 之資源文件 (resource files) 放置地。 純目錄名,不加目錄分隔。 resources/
*
* @name CeL.env.resources_directory_name
* @type {String}
*/
env.resources_directory_name = 'resources';
/**
* 預設 module name separator。
*
* @name CeL.env.module_name_separator
* @type {String}
*/
env.module_name_separator = '.';
/**
* path_separator pattern in 通用(regular)運算式。
*
* @name CeL.env.path_separator_pattern
* @type {String}
*/
env.path_separator_pattern = _.to_RegExp_pattern ?
_.to_RegExp_pattern(env.path_separator)
: (env.path_separator === '\\' ? '\\' : '') + env.path_separator;
/**
* 預設語系。
* 0x404:中文-台灣,
* 0x0411:日文-日本
*
* @name CeL.env.locale
* @see CultureInfo
* 類別
* @type {Number}
*/
env.locale = 0x404;
/**
* script name.
*
* @name CeL.env.script_name
* @type {String}
*/
env.script_name = _.get_script_name();
/**
* base path of script.
*
* TODO:
* 以 reg 代替
*
* @name CeL.env.script_base_path
* @type {String}
*/
env.script_base_path = _.get_script_full_name()
// 去除 filename
.replace(/[^\\\/]+$/, '');
/**
* Legal identifier name in RegExp.
* 這 pattern 會佔去兩個筆紀錄: first letter, and least.
* .replace(/_/ [g],'for first letter')
* .replace(/\\d/,'for least')
* 這邊列出的只是合法 identifier 的*子集*,且未去除 reserved words!
* 請注意實際判別須加入 ^..$
*
* 不用 \d 而用 0-9 是因為 \d 還包括了 MATHEMATICAL BOLD DIGIT。
* 基于正则的URL匹配安全性考虑
*
* @name CeL.env.identifier_RegExp
* @type {RegExp}
* @see ECMA-262 7.6 Identifier Names and Identifiers
*/
env.identifier_RegExp = /([a-zA-Z$_]|\\u[0-9a-fA-F]{4})([a-zA-Z$_0-9]+|\\u[0-9a-fA-F]{4}){0,63}/;
/**
* Legal identifier name in String from env.identifier_RegExp.
*
* @name CeL.env.identifier_String
*/
env.identifier_String = env.identifier_RegExp.source;
// test for-of statement (IterationStatement)
try {
env.has_for_of = new Function('for(var i of [7])return i===7;')();
} catch (e) {
// TODO: handle exception
}
// arrow function
try {
env.has_arrow_function = new Function('a','return((a)=>a+1)(a);')(2) === 3;
} catch (e) {
// TODO: handle exception
}
// RegExp lookbehind assertions
// from ECMA-262, 9th edition, ECMAScript 2018
try {
env.has_RegExp_lookbehind = '$12.34'.match(new RegExp('(?<=\\D)\\d+'))[0] === '12'
// http://2ality.com/2017/05/regexp-lookbehind-assertions.html
&& 'a1ba2ba3b'.match(new RegExp('(?<=b)a.b', 'g')).join(',') === 'a2b,a3b'
&& '0b11b22b33b4'.match(new RegExp('(?
* 提供給函數設定 flag / optional argument 處理用。
*
* @example
var setting = setting_pair({});
*
*
* @param default_setting
* 預設 setting.
*
* @returns {Function}
*/
function setting_pair(default_setting) {
var setting_now = default_setting || Object.create(null),
setting_handle = function (name, value) {
if (_.is_Object(name)) {
// setter
for (var i in name) {
// _.debug('[' + i + ']=[' + name[i] + ']'),
if (typeof name[i] !== 'undefined')
setting_now[i] = name[i];
else if (i in setting_now)
delete setting_now[i];
}
return setting_now;
}
if (Array.isArray(name)) {
// getter
var r = [];
name.forEach(function (n, i) {
if (n in setting_now)
r[i] = setting_now[n];
});
return r;
}
if (false)
if (arguments.length > 1)
_.debug('[' + name + ']=[' + value + ']');
return arguments.length > 1 ? (setting_now[name] = value)
: name ? setting_now[name] : setting_now;
};
setting_handle.reset = function (setting) {
return setting_now = setting || Object.create(null);
};
// additional setting.
for (var i = 1, length = arguments.length, o; i < length; i++)
if (_.is_Object(o = arguments[i]))
setting_handle(o);
return setting_handle;
}
/**
*
setting_pair.prototype.handle = function(name, value) {
var setting_now = this.setting_now;
if (_.is_Object(name)) {
// setter
for ( var i in name) {
//_.debug('[' + i + ']=[' + name[i] + ']'),
if(typeof name[i] !== 'undefined')
setting_now[i] = name[i];
else if(i in setting_now)
delete setting_now[i];
}
return setting_now;
}
if (Array.isArray(name)) {
// getter
var i, r = [], n;
for (i in name) {
n = name[i];
if (n in setting_now)
r[i] = setting_now[n];
}
return r;
}
//if(arguments.length > 1) _.debug('[' + name + ']=[' + value + ']');
return arguments.length > 1 ? (setting_now[name] = value)
: setting_now[name];
};
setting_pair.prototype.reset = function(setting) {
return this.setting_now = setting || Object.create(null);
};
*/
_// JSDT:_module_
.
setting_pair = setting_pair;
// ----------------------------------------------------------------------------------------------------------------------------------------------------------//
// for debug & log.
_// JSDT:_module_
.
/**
* Tell if it's now debugging.
*
* @param {ℕ⁰:Natural+0}[debug_level]
* if it's now in this debug level.
*
* @returns {Boolean} It's now in specified debug level.
* @returns {ℕ⁰:Natural+0} It's now in what debug level (Integer).
*/
is_debug = function (debug_level) {
return typeof debug_level !== 'number' ? debug || 0
: debug >= debug_level;
};
_// JSDT:_module_
.
/**
* Set debugging level
*
* @param {ℕ⁰:Natural+0}[debug_level]
* The debugging level to set.
*
* @type {ℕ⁰:Natural+0}
* @returns {ℕ⁰:Natural+0} debugging level now
*/
set_debug = function (debug_level) {
if (!isNaN(debug_level))
debug = Math.max(0, debug_level);
else if (typeof debug_level === 'undefined' && !debug)
debug = 1;
if (Error.stackTraceLimit > 0) {
// Node.js: default: 10
Error.stackTraceLimit = debug > 2 ? 100 : debug > 0 ? 15 : 10;
}
return debug;
};
_// JSDT:_module_
.
/**
* Get the hash key of text.
*
* @param {String}text
* text to test
*
* @returns {String} hash key
*/
_get_hash_key = function (text) {
// text = String(text);
// text = '' + text;
var l = text.length, take = 30, from = .3;
from = Math.floor(l * from);
if (false)
_.log(from + '~' + l + ': '
+ (l - from < take ? text : text.substr(from, take)));
return l - from < take ? text : text.substr(from, take);
};
/**
*
Chrome/22.0.1229.64
fast->slow:
(1000000*Math.random())>>>0
but int32 only
parseInt(1000000*Math.random())
Math.floor(1000000*Math.random())
*/
// for JScript<=5
try {
// @deprecated /^\s*function[\s\n]+(\w+)[\s\n]*\(/
// ^\\s*: JScript 6-9 native object 需要這個。
// function_name_pattern = new
// RegExp('^\\s*function[\\s\\n]+(\\w+)[\\s\\n]*\\(');
_.PATTERN_function = function_name_pattern =
// [ all, function name, function arguments, function body ]
/^\s*function(?:[\s\n]+([^\s\n]*?)[\s\n]*)?\([\s\n]*([^)]*?)[\s\n]*\)[\s\n]*{[\s\n]*([\s\S]*)[\s\n]*}[\s\n;]*$/;
// TODO: arrow function expression
// [ all, function arguments, function body ]
// e.g., "(n) => {return n>0;}"
/^\s*\([\s\n]*([^)]*?)[\s\n]*\)[\s\n]*=>[\s\n]*{[\s\n]*([\s\S]*)[\s\n]*}[\s\n;]*$/;
} catch (e) {
function_name_pattern = function emulate_function_name(fs) {
fs = String(fs);
var l = 'function ', r, s;
if (fs.indexOf(l) === 0) {
l = l.length;
s = {
' ': 1,
'\n': 1,
'\r': 1,
'\t': 1
};
while (fs.charAt(l) in s)
l++;
r = fs.indexOf('(', l);
while (fs.charAt(--r) in s) { }
return [, fs.slice(l, r + 1)];
}
};
// TODO
if (typeof RegExp !== 'object')
globalThis.RegExp = function () { };
}
/**
* 獲得函數名。
*
* @param {Function}fr
* function reference
* @param {String}ns
* name-space
* @param {Boolean}force_load
* force reload this name-space
*
* @returns
* @see 可能的話請改用 {@link CeL.native.parse_function}(F).funcName
* @since 2010/1/7 22:10:27
*/
function get_function_name(fr, ns, force_load) {
if (!fr)
try {
fr = arguments.caller;
} catch (e) {
if (!fr)
return '';
}
if (fr.name)
return fr.name;
var
// 初始化變數 'm'。
// 不用 insteadof 是怕傳入奇怪的東西,例如 {String} script code.
m = typeof fr,
// function body text (函數的解譯文字)
ft, b, load, k, i;
if (m === 'function') {
// 勿更改傳入之 argument
if(false){
if ('toString' in fr) {
m = fr.toString;
delete fr.toString;
}
ft = String(fr);
if (m)
fr.toString = m;
}
// TODO: cache Function.prototype.toString
ft = Function.prototype.toString.call(fr);
} else if (m === 'string')
// typeof fr === 'string'
ft = fr;
else
return '';
// 以函數的解譯文字獲得函數名
m = _.is_RegExp(function_name_pattern) ?
// 包含引數: + '(' + (f ? m[2] : '') + ')';
((m = ft.match(function_name_pattern)) && m[1] || /^[a-zA-Z_\d.]{1,30}$/.test(ft) && ft || 0)
: function_name_pattern instanceof Function ?
function_name_pattern(ft)
: 0;
if (m) {
// _.debug('matched ' + m, 1, _.Class + '.get_function_name');
return m;
}
// 無法從 function code 本身得到 name 之資訊。
// 匿名函數?
// 查詢是否是已註冊之 function。
b = get_function_name.b;
if (b)
load = get_function_name.ns;
else
get_function_name.b = b = Object.create(null), get_function_name.ns = load = Object.create(null);
if (!ns)
ns = _;
// cache functions
if ((_.is_Function(ns) || _.is_Object(ns)) && ns.Class
&& (force_load || !load[ns.Class])) {
for (i in ns)
if (typeof ns[i] === 'function') {
k = _._get_hash_key(String(ns[i]));
m = ns.Class + _.env.module_name_separator + i;
// _.debug(m + ': ' + k + (', ' + ns[i]).slice(0, 200));
if (!(m in load)) {
load[m] = 1;
if (!b[k])
b[k] = [];
b[k].push([m, ns[i]]);
}
}
load[ns.Class] = 1;
}
// 將函數與 cache 比對以獲得函數名。
// TODO: Array.prototype.indexOf()
m = b[_._get_hash_key(ft)];
if (m)
for (i = 0; i < m.length; i++) {
b = m[i][1];
if (// typeof fr === 'function' &&
fr === b || ft === String(b))
return m[i][0];
}
return '';// '(unknown)';
};
_// JSDT:_module_
.
get_function_name = get_function_name;
// noop
_// JSDT:_module_
.
null_function =
// new Function;
function () { };
_// JSDT:_module_
.
constant_function = function(value) {
value = String(value);
if (!(value in constant_function)
// true/false/Number/null/undefined/global variables only!
// && ((value in globalThis) || !isNaN(value))
) {
constant_function[value] = new Function('return(' + value + ')');
}
return constant_function[value];
};
try {
_.constant_function(false);
} catch (e) {
// Firefox/49.0 WebExtensions 可能 throw:
// Error: call to Function() blocked by CSP
_.constant_function = function(value) {
return function() {
return value;
};
};
}
// ---------------------------------------------------------------------//
// Initialization
// ---------------------------------------------------------------------//
// 處理 styled/stylized messages.
// @see
// https://stackoverflow.com/questions/22155879/how-do-i-create-formatted-javascript-console-log-messages
/**
* 將 messages 去掉 style,轉成 plain text messages。
*
* @param {Array}messages
* 附加格式的訊息。 messages with style.
*
* @returns {String}plain text messages.
*/
function SGR_to_plain(messages) {
return Array.isArray(messages) ? messages.filter(function(message, index) {
return index % 2 === 0;
}).join('')
// '' + messages
: messages;
}
/** {Object}cache for CeL.interact.console.SGR */
var SGR, SGR_debug;
/**
* 在已經存在 SGR 的功能下,以之格式化訊息。
*
* @param {Array}messages
* 附加格式的訊息。 messages with style. 將當作 new SGR() 之 arguments。
*
* @returns {String}formatted messages. 格式化後的訊息。
*
* @see 'interact.console'
*/
function new_SGR(messages) {
// 注意: 在 call stack 中有 SGR 時會造成:
// RangeError: Maximum call stack size exceeded
// 因此不能用於測試 SGR 本身! 故須避免之。
// CeL.is_debug(min_debug): assert: SGR 在這 level 以上才會呼叫 .debug()。
// TODO: 檢測 call stack。
return _.is_debug(SGR_debug)
// 若 SGR.CSI 被改過,則即便顯示亦無法得到預期之結果,不如跳過。
|| SGR.CSI !== SGR.default_CSI ? SGR_to_plain(messages)
// 顯示具格式(如 color 顏色)的 messages。
: new SGR(messages).toString();
}
/**
* 處理 console 之 message。添加主控端報告的顯示格式(如 color 顏色)。
* 若無法執行 new SGR(),則會將 messages 轉成 plain text。實作部分詳見 SGR。
*
* @param {Array}messages
* 附加格式的訊息。 messages with style.
*
* @returns {String}格式化後的訊息。
*
* @see to_SGR() @ 'application.debug.log'
*/
function to_SGR(messages) {
if (_.SGR) {
SGR = _.SGR;
SGR_debug = SGR.min_debug_level;
return (_.to_SGR = new_SGR)(messages);
}
// 將 messages 去掉 style,轉成 plain text messages。
return SGR_to_plain(messages);
}
// 在 WWW 的環境下,則直接 pass style 設定。
_.to_SGR = is_WWW ? SGR_to_plain : to_SGR;
// --------------------------------
var
/** {RegExp}是否具有 caller。能辨識紀錄之 caller。須排除"C:\"之類。 */
PATTERN_log_caller = /^([a-z_\d.]{2,}:\s*)([\s\S]+)$/i,
/** {Boolean}使用 styled 紀錄。 */
using_style = !_.env.no_log_style,
/** {Object}default style of console. */
default_style = {
// trace : '',
// debug 另外設定。
// debug : '',
log : 'green',
// information
info : 'cyan',
// warning
warn : 'yellow',
error : 'red;bg=white'
};
// a simple simulation of CeL.application.locale.gettext
// Please include application.locale if you need a full version.
// cache gettext only inside sync function, or using CeL.gettext instead:
// application.locale 會自動 overwrite .gettext。
// 假如多次使用,不如直接 include application.locale。
function simple_gettext(text_id) {
if (false && _.locale && _.locale.gettext) {
_.gettext = _.locale.gettext;
return _.gettext.apply(null, arguments);
}
// a simplified version
// assert: typeof text_id === 'string'
var arg = arguments;
return text_id.replace(/%(\d+)/g, function(all, NO) {
return NO < arg.length ?
// extract_message_from_nodes(arg[NO])
arg[NO] : all;
});
}
_.gettext = simple_gettext;
/**
* @example
var gettext = CeL.cache_gettext(function(_) { gettext = _; });
var gettext = CeL.cache_gettext(_ => gettext = _);
*/
_.cache_gettext = function(adapter) {
return function _gettext() {
var gettext = _.locale && _.locale.gettext;
if (gettext) {
adapter(gettext);
} else {
gettext = simple_gettext;
}
return gettext.apply ? gettext.apply(null, arguments)
// 這方法沒有準確符合arguments的長度,有缺陷。
: gettext(arguments[0], arguments[1], arguments[2], arguments[3]);
};
};
if (platform.nodejs && process.versions) {
process.versions[library_name.toLowerCase()] = library_version;
if (using_style === undefined) {
// 若為 nodejs,預設使用 styled 紀錄。
// using_style = _.platform.nodejs
using_style = !!process.versions.node;
}
}
function is_DOM_node(node) {
return _.is_Object(node) && ('T' in node
// || 'span' in node
);
}
// 在沒有載入 new_node() @ CeL.DOM 的情況下嘗試解析 DOM object
function extract_message_from_nodes(nodes, style_array) {
if (Array.isArray(nodes)) {
// nodes.forEach()
for (var index = 0; index < nodes.length; index++) {
var node = nodes[index];
nodes[index] = extract_message_from_nodes(node, style_array);
if (_.gettext.append_message_tail_space && node && node.T) {
var inner = nodes[index + 1];
// 只是簡易處理,不完善。
// @see CeL.interact.DOM.new_node()
inner = _.gettext.apply(null, Array.isArray(inner) ? inner : [ inner ]);
nodes[index] = _.gettext.append_message_tail_space(nodes[index], {
no_more_convert : true,
next_sentence : inner
});
}
}
return nodes.join('');
}
if (!_.is_Object(nodes)) {
if (style_array) {
style_array.push(style_array.has_style ? [
style_array.has_style.fg ? '-fg' : '',
style_array.has_style.bg ? '-bg' : ''].join(';') : '', nodes);
if (style_array.has_style)
style_array.has_style = true;
}
return nodes;
}
var tag_name = nodes.$;
if (!tag_name) {
for (tag_name in nodes) {
break;
}
}
var inner = nodes[tag_name];
if (tag_name !== 'T') {
inner = extract_message_from_nodes(inner);
} {
inner = _.gettext.apply(null, Array.isArray(inner) ? inner : [ inner ]);
}
var color_index = _.SGR && _.SGR.color_index,
//
style = color_index && (nodes.style || nodes.S);
// console.log(style);
// parse CSS to SGR color style
if (typeof style === 'string') {
style.replace(/(?:^|[;\s])(background-)?color\s*:\s*([^\s;]+)/g, function(all, bg, color) {
color = color.toLowerCase();
if (!(color in color_index))
return;
if (typeof style === 'string') {
style = Object.create(null);
}
style[bg ? 'bg' : 'fg'] = color;
});
if (typeof style === 'string') {
style = '';
}
} else if (style && ((style.color in color_index)
|| (style.backgroundColor in color_index))) {
style = {
fg : (style.color in color_index) && style.color || '',
bg : (style.backgroundColor in color_index) && style.backgroundColor || ''
};
} else
style = '';
if (style_array) {
style_array.push(style, inner);
if (style)
style_array.has_style = style;
}
// 不再傳入 style_array
return inner;
}
/**
* 預先處理 log messages。
*
* TODO: 判別 console 是否具備 stylify/著色功能。
*
* @param {Array|String}messages
* 欲記錄訊息。
* @param {Boolean}[from_styled_logger]
* caller is styled logger.
*
* @returns {Array}styled messages
*/
function preprocess_log_messages(messages, type, from_styled_logger) {
// console.log(using_style);
// console.trace(messages);
if (!using_style) {
// 不採用 styled log。不自動著色。
return typeof messages === 'string' ? messages : SGR_to_plain(messages);
}
var style_array;
if (Array.isArray(messages)) {
// messages.forEach()
for (var index = 0; index < messages.length; index++) {
if (is_DOM_node(messages[index])) {
style_array = true;
break;
}
}
} else if (is_DOM_node(messages)) {
style_array = true;
}
if (style_array) {
// 從頭到尾沒有特殊格式的話,就轉成純粹的字串。
messages = extract_message_from_nodes(messages, style_array = [ '' ]);
if (style_array.has_style) {
// reset style
if (_.is_Object(style_array.has_style)) {
style_array.push([
style_array.has_style.fg ? '-fg' : '',
style_array.has_style.bg ? '-bg' : ''].join(';'), '');
}
messages = style_array;
}
// console.trace(style_array);
}
var matched;
if (typeof messages === 'string') {
// 自動著色。
matched = messages.match(PATTERN_log_caller);
if (matched) {
// e.g., CeL.log("function_name: messages");
messages = [ matched[1], default_style[type], matched[2], 0 ];
} else {
messages = [ '', default_style[type], messages, 0 ];
}
} else if (from_styled_logger) {
// assert: Array.isArray(messages)
// 自動著色。
// TODO: 效果不佳。
matched = messages[0].match(PATTERN_log_caller);
if (matched) {
// e.g., CeL.log([ 'function_name: messages 0', 'style',
// 'messages 1' ]);
messages.splice(0, 1, '', default_style[type], matched[1], 0, matched[2]);
// 最後設定 reset,避免影響到後頭之顯示。
if (messages.length % 2 === 0)
messages.push('', 0);
else
messages.push(0);
}
}
return _.to_SGR(messages);
}
_.preprocess_log_messages = preprocess_log_messages;
/**
* 不能放在 if (has_console) {} 中 @ node.js v0.10.25:
*
*
SyntaxError: In strict mode code, functions can only be declared at top level or immediately within another function.
*/
function setup_log(type) {
// 將 CeL[type] 轉成 console[_type]。
var _type = type;
if (!console[_type])
// e.g., 不見得在所有平台上都有 console.info() 。
return;
_[type] = function(messages, clear) {
if (clear && console.clear)
console.clear();
// IE8 中,無法使用 console.log.apply()。
// return console[type].apply(console, arguments);
console[_type](preprocess_log_messages(messages, type));
};
/**
* setup frontend of styled messages. 使可輸入 CeL.s*().
*
*
CeL.slog([ 'CeJS: This is a ', 'fg=yellow', 'styled', '-fg', ' message.' ]);
CeL.sinfo('CeJS: There are some informations.');
*/
_['s' + type] = function(messages, clear) {
if (clear && console.clear)
console.clear();
console[_type](preprocess_log_messages(messages, type, true));
};
}
// temporary decoration of debug console,
// in case we call for nothing and raise error
if (has_console) {
_.env.has_console = has_console;
// 利用原生 console 來 debug。
// 不直接指定 console.*: 預防 'Uncaught TypeError: Illegal invocation'.
(function() {
for ( var type in default_style) {
// default style: foreground 前景
default_style[type] = 'fg=' + default_style[type];
setup_log(type);
}
})();
// caller: from what caller
_.debug = function (messages, level, caller) {
if (!_.is_debug(level))
return;
if (caller) {
caller = _.get_function_name(caller) + ': ';
if (typeof messages === 'object') {
if (Array.isArray(messages)) {
// e.g., CeL.debug([{T:'msg'},'msg2'],1,'caller');
messages.unshift(caller);
} else {
// e.g., CeL.debug({T:'msg'},1,'caller');
messages = [ caller, messages ];
}
} else {
// e.g., CeL.debug('msg',1,'caller');
messages = caller + messages;
}
}
// console.trace()
console.log(preprocess_log_messages(messages, 'debug'));
};
// styled logger
_.sdebug = function (messages, level, caller) {
if (!_.is_debug(level))
return;
if (caller) {
if (!Array.isArray(messages))
// assert: (typeof messages === 'string')
messages = [ messages ];
messages.unshift('fg=blue', _.get_function_name(caller) + ': ', 0);
messages = _.to_SGR(messages);
} else {
messages = preprocess_log_messages(messages, 'debug', true);
}
// console.trace()
console.log(messages);
}
} else {
_.error = _.warn = _.log = function (message) {
/**
* 請注意:
* _.log.buffer === this.log.buffer !== log.buffer
*
* 在 WScript 中 需要用 _.log,其他則可用 log。
* 因此應該將所有類似的值指定給雙方,並注意不是[常數]的情況。
*/
var _s = _.log;
// _s.function_to_call.apply(null,arguments);
// _s.function_to_call.apply(globalThis, arguments);
_s.buffer.push(message);
if (!(_s.max_length >= 0))
_s.max_length = 0;
// 沒加 'debug &&' 在 IE 中會跳出大量 alert.
if (debug && _s.buffer.length > _s.max_length) {
_s.function_to_call.call(globalThis, _s.buffer.join('\n\n'));
// reset buffer
_s.buffer = [];
}
};
_.debug = function (message, level, from) {
if (_.is_debug(level))
return _.log((from && (from = _.get_function_name(from)) ? from + ': ' : '[debug] ') + message);
};
/**
* test:
* var k=function l(){alert(l.m);};k.m=1;alert(l.m+','+k.m);k();
*
* JScript 中
* k();
* 為 undefined, 其他會把 "l." 代換成 "k."?
*
* @inner
*/
// _.debug.buffer = _.error.buffer = _.warn.buffer =
_.log.buffer = [];
// _.debug.max_length = _.error.max_length = _.warn.max_length =
_.log.max_length = 0;
// if(!isNaN(CeL.log.max_length)) CeL.log.max_length = 20;
var max_log_length = 1000,
prepare_message = function (message) {
message = String(message);
if (message.length > 2 * max_log_length)
message = message.slice(0, max_log_length) + '\n\n...\n\n' + message.slice(-max_log_length);
return message;
};
// _.debug.function_to_call = _.error.function_to_call =
// _.warn.function_to_call =
_.log.function_to_call =
// console 已在前面特別處理,以作美化。
// typeof JSalert === 'function' ? JSalert :
script_host ?
function (message) { WScript.Echo(prepare_message(message)); } :
// for jslibs
typeof _configuration === 'object' && typeof _configuration.stdout === 'function' ?
function (message) { _configuration.stdout(prepare_message(message) + '\n'); } :
// for JSDB
typeof writeln === 'function' ?
function (message) { writeln(prepare_message(message)); } :
// 預設以訊息框代替。
typeof alert === 'object' || typeof alert === 'function' ?
function (message) { alert(prepare_message(message)); } :
// 無任何可用之反映管道。
_.null_function;
}
// cache
_.debug_console = function debug_console() {};
_.debug_console.log = _.log;
_.debug_console.warn = _.warn;
_.debug_console.error = _.error;
_.debug_console.debug = _.debug;
// CeL.log_temporary(): temporary message
// console_message(), log_status(), interactive_message()
// Will re-set @ set_initializor() @ module.js
_.log_temporary = _.null_function;
// ---------------------------------------------------------------------//
// 補強 (shim, polyfill) 用的 functions。
// setup Object.defineProperty()
/**
* 修改/加入屬性 propertyKey 至物件 object。
* shim for 先前過舊的版本。
*
* @param {Object|Function}object
* 要加入或修改屬性的目標物件。
* @param {String}propertyKey
* 屬性名稱。
* @param {Object}attributes
* 屬性的描述元。
* @returns 目標物件 object。
*
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
*/
function defineProperty(object, propertyKey, attributes) {
if ('value' in attributes) {
object[propertyKey] = attributes.value;
} else if (typeof attributes.get === 'function') {
try {
object[propertyKey] = attributes.get();
if (_.is_debug(2))
_.warn('Object.defineProperty: 將設定成 get() 所得之值 ['
+ object[propertyKey] + ']!');
} catch (error) {
// TODO: handle exception
}
// ignore .set
}
// else: nothing to set.
return object;
}
defineProperty[KEY_not_native] = true;
if (typeof Object.defineProperty !== 'function') {
// 會動到原來的 Object.defineProperty。
Object.defineProperty = defineProperty;
} else {
try {
(function () {
// workaround for Object.defineProperty @ IE8
// http://kangax.github.com/es5-compat-table/
// In Internet Explorer 8 Object.defineProperty only accepts DOM
// objects (MSDN reference).
// http://blogs.msdn.com/b/ie/archive/2010/09/07/transitioning-existing-code-to-the-es5-getter-setter-apis.aspx
// Trying to use Object.defineProperty() on native objects
// throws an error.
var o = {};
if (Object.defineProperty({}, 'p', { value : o }).p !== o)
throw 1;
})();
} catch (e) {
// backup original Object.defineProperty.
var _defineProperty = Object._defineProperty = Object.defineProperty;
// copy from interact.DOM
// for IE5-8
(Object.defineProperty = function IE5_to_8_defineProperty(target, propertyKey, attributes) {
if (Object.prototype.toString.call(target) === '[object Object]'
// e.g., IE 5-8. 這種判別方法有漏洞!
&& typeof target.nodeType === 'number')
try {
return _defineProperty(target, propertyKey, attributes);
} catch (e) {
}
// 不作錯誤偵測: 不能設定,就直接 throw。
return defineProperty(target, propertyKey, attributes);
})[KEY_not_native] = true;
}
}
// 確認 Object.defineProperty() 是否能正確設值。
if (!Object.defineProperty[KEY_not_native]) {
try {
(function() {
var i, value = 7, old_value = value,
//
test_Funciton = function() {
};
Object.defineProperty(test_Funciton, 'size', {
// enumerable : false,
// configurable : false,
get : function() {
return value;
},
set : function(v) {
if (value - 1 === v)
value = v;
}
});
for (i in test_Funciton)
if (i === 'size')
throw 1;
try {
test_Funciton.size = value + 1;
} catch (e) {
}
try {
delete test_Funciton.size;
} catch (e) {
}
if (test_Funciton.size !== value)
throw 1;
test_Funciton.size = value - 1;
if (test_Funciton.size !== value || test_Funciton.size === old_value)
throw 1;
})();
} catch (e) {
// Don't have standard Object.defineProperty()!
Object.defineProperty[KEY_not_native] = true;
}
}
// ---------------------------------------------------------------------------//
// 這裡添加本 library base 會用到的,或重要的,過於基本的 native function
// (標準已規定,但先前版本未具備的內建物件功能)。
// 添加 method, to add method, use set_method() or Object.defineProperties()
// or Object.defineProperty()
// 延展物件, to add property, use Object.assign()
// 因為 set_method() 會用到 is_debug(),因此須先確保 is_debug() 已 loaded。
// ^\s*: JScript 6-9 native object 需要這個。
// console.log() @ node.js: "function () {...}"
// TODO: see ((function_name_pattern)) above
// @see
// https://tc39.github.io/Function-prototype-toString-revision/#prod-NativeFunction
// [ all, IdentifierName ]
// 舊的 JS environment 無法取得 FormalParameters。
var native_pattern = /^\s*function\s+(\w*)\s*\([^()]*\)\s*{\s*\[native code\]\s*}\s*$/;
_.is_native_Function = function(variable) {
return typeof variable === 'function'
// is a builtin function
&& native_pattern.test(Function.prototype.toString.call(variable));
};
/**
* 若 variable 為 Standard Built-in ECMAScript Objects / native object /
* native ECMASCript object, 則回傳其 name / Constructor name。
* 現行實作並未有標準支持!
*
* @param variable
* 欲測試之 variable。
* @returns native object 之 name。
*/
function native_name(variable) {
try {
var value, match;
// TODO: Function.prototype.bind 可能造成非 native Function 卻形如 "[native
// code]" @ Firefox 20。
// 注意: '' + Object.create(null) 會 throw TypeError: Cannot convert
// object to primitive value
if (typeof variable === 'function'
//
&& (match = Function.prototype.toString.call(variable).match(native_pattern)))
return match[1];
match = String(variable.constructor).match(native_pattern);
if (match && (value = _.value_of(match[1])) && variable === value.prototype)
return match[1] + '.prototype';
if (variable === Math)
// '' + Math === "[object Math]" @ Chrome/36
return 'Math';
} catch (e) {
// TODO: handle exception
}
}
_// JSDT:_module_
.
native_name = native_name;
// need_to_check_in_for_in = undefined || { 'valueOf' : {}.valueOf,
// 'toString' : {}.toString };
var need_to_check_in_for_in = (function() {
var key = {}, need_to_check = {
_valueOf : key.valueOf,
_toString : key.toString
};
for (key in {
// IE8 中,以 for ( in ) 迭代,會漏掉 valueOf, toString 這兩個。
valueOf : function() {
},
toString : function() {
}
})
delete need_to_check['_' + key];
for (key in need_to_check)
return need_to_check;
})();
/**
* 設定物件方法:
* extend properties to name_space.
* 將 from_name_space 下的 variable_set 延展/覆蓋到 name_space。
* Object.defineProperties() without overwrite extend properties to
* name_space.
*
* @example
* var o={a:0,b:1,c:'a',d:2,e:'g',f:4};
* CeL.set_method({a:1,b:2,c:3},o,[function(key){return !CeL.is_digits(o[key]);},'b','c','d','e','s']);
* // {a:1,b:1,c:3,d:2}
*
*
* 注意: CeL.set_method() 不覆蓋原有的設定。欲覆蓋原有的設定請用 Object.assign()。
*
* @param {Object|Function}name_space
* target name-space. extend to what name-space.
* @param {Object|Function}properties
* 欲延展那些 properties.
* @param {Undefined|Boolean|String|Array|Object|Function}[filter]
* {Boolean} false: preserve NONE. overwrite even 衝突.
* {Boolean} true: preserve ALL. don't overwrite if 衝突.
*
* {Null} null: the same as false.
* undefined: default: if the target has the same key, preserve
* the same type.
* {String} preserve type, should be this type. 若已存在此 type,或 eval
* 後回傳 true (function),則不 overwrite。
*
* {Object} {key : 'preserve type'}
* {Array} [keys]: copy 所有 type 不同之 keys。
* {Function} filter(key, name_space, properties) return true:
* preserve, false: copy the key.
* @param {Object}[attributes]
* attributes used in Object.defineProperty()
* @returns target name-space
* @see https://www.audero.it/blog/2016/12/05/monkey-patching-javascript/
* @since 2014/5/5
* 2014/5/6 refactoring 重構
*/
function set_method(name_space, properties, filter, attributes) {
if (!attributes)
attributes = Object.create(null);
if (!name_space) {
_.debug('沒有指定擴展的對象,擴展到 set_method.default_target。', 1, 'set_method');
if (!(name_space = set_method.default_target))
if (name_space === null
// && _.is_Object(properties)
)
return name_space;
else
name_space = Object.create(null);
}
if (name_space === properties) {
_.debug('(' + properties + '): 目標與來源相同。', 2, 'set_method');
return;
}
var key;
// assert: 在 Array.isArray() 設定前,不可以使用 filter。
if (filter && Array.isArray(filter)) {
// filter: Array → Object
key = filter;
filter = Object.create(null);
if (typeof key[0] === 'string')
// set default value: overwrite.
key.unshift(false);
key.forEach(function(k, i, o) {
if (i === 0)
key = o[i];
else
filter[o[i]] = key;
});
}
function setter() {
// !_.is_Function()
var value = filter, not_native_keyword = _.env.not_native_keyword || KEY_not_native;
if (_.is_Object(filter))
if (key in properties)
value = filter[key];
else
// 僅考慮 filter 與 properties 皆包含的屬性。
return;
if (typeof value === 'function'
//
&& (value = value(key, name_space, properties)) === true)
// 直接跳過,保留原值。
return;
if (typeof value === 'string') {
// _.is_type()
value = typeof name_space[key] === value;
} else if (value) {
if (value === true)
// 偵測是否已經存在 target name_space。
value = key in name_space;
else
_.warn('set_method.setter: Unknown filter: [' + value + ']');
} else if (value !== false) {
// undefined, null, NaN
value = typeof name_space[key] === typeof properties[key]
// 假如原先有的並非原生函數,應該是有比較好、針對性的實作方法,那麼就用新的覆蓋舊的。
&& name_space[key] && !name_space[key][not_native_keyword];
}
if (value)
return;
attributes.value = value = properties[key];
// 以新的覆蓋舊的。
if (name_space[key] && name_space[key][not_native_keyword]) {
try {
delete name_space[key];
} catch (e) {
// TODO: handle exception
}
}
// Opera/9.80 中,set_method(Number, ..) 會造成:
// Object.defineProperty: first argument not an Object
try {
Object.defineProperty(name_space, key, attributes);
} catch (e) {
name_space[key] = value;
}
// 放這邊,確保 not_native_keyword 一定會被設定。
var name = native_name(name_space);
if (name && typeof value === 'function') {
try {
Object.defineProperty(value,
// 設定非 native 之 flag.
not_native_keyword, {
value : true
});
} catch (e) {
value[not_native_keyword] = true;
}
} else if (typeof value === 'function') {
value[not_native_keyword] = true;
}
// Warning: 由於執行時可能處於 log() stack 中,若 log() 會用到 set_method(),這邊又
// call .debug(),可能會循環呼叫,造成 stack overflow。
if (_.is_debug(name ? 1 : 3)) {
// 若更動 native Object 等,則作個警示。
_.debug((name || '(' + _.is_type(name_space) + ')')
+ '.' + key + ' = (' + (typeof value) + ')'
+ (_.is_debug(4) || typeof value !== 'function'
&& typeof value !== 'object' && typeof value !== 'symbol' ? ' [' + value + ']'
: ''), 1, 'set_method');
}
}
// TODO: 若 {Function}properties 另外處理,依現行實作會出問題?
for (key in (_.is_Object(filter) ? filter : properties))
setter();
if (need_to_check_in_for_in
// Object 的情況,已經在前面處理完了。
&& !_.is_Object(filter)) {
if (!filter)
filter = false;
for (key in need_to_check_in_for_in)
// assert: !== 須由左至右運算。
// assert: i = 0; [ 1, 2 ][i] !== [ 2, 2 ][i = 1];
if (need_to_check_in_for_in[key] !== properties[key = key.slice(1)])
setter();
}
return name_space;
}
_.set_method = set_method;
/**
* Test if the value is a native Array.
*
* @param v
* value to test
* @returns {Boolean} the value is a native Array.
* @since 2009/12/20 08:38:26
*/
set_method(Array, {
isArray: // _.type_tester('Array');
function isArray(v) {
// instanceof 比 Object.prototype.toString 快
return v instanceof Array
|| get_object_type(v) === '[object Array]';
}
});
// Warning: 在 node.js v0.10.48 下,對於以 set/get 來設定 target[key]
// 的情況,可能造成設定完後 process, console 變成未定義之變數。
// node.js v0.12.18 下沒有這個問題。
_.need_avoid_assign_to_setter = platform.nodejs && !platform('node', '0.12');
set_method(Object, {
// Object.defineProperties()
defineProperties : function defineProperties(object, properties) {
var key;
for (key in properties)
Object.defineProperty(object, key, properties[key]);
if (need_to_check_in_for_in)
for (key in need_to_check_in_for_in)
// assert: !== 須由左至右運算。
// assert: i = 0; [ 1, 2 ][i] !== [ 2, 2 ][i = 1];
if (need_to_check_in_for_in[key] !== properties[key = key
.slice(1)])
Object.defineProperty(object, key, properties[key]);
return object;
},
// https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Object/create
// Object.create() 指定其原型物件與屬性,創建一個新物件。
create : function create(proto, propertiesObject) {
if (proto === null && !propertiesObject) {
/**
* 取得裸 Object (naked Object)。
*
* TODO: 快速回應的方法。但不見得在所有環境都適用,還需要再經過測試。
*
* @returns 裸 Object (naked Object)。
*/
return {};
}
if (proto !== null && typeof proto !== 'object' && typeof proto !== 'function') {
throw TypeError('Object prototype may only be an Object or null');
}
var object = new Object();
object.__proto__ = proto;
/**
* Object.create(null) 可取得裸 Object (naked Object)。Object prototype
* may only be an Object or null
* 預防 Object.prototype 有東西,並消除 .toString() 之類。
*
* 注意: '' + Object.create(null) 會 throw TypeError: Cannot convert
* object to primitive value
*
* @see 如何创建一个JavaScript裸对象 - hax的技术部落格 -
* ITeye技术网站
*/
for ( var attribute in object) {
// This will also delete .__proto__
delete object[attribute];
}
if (typeof propertiesObject === 'object')
Object.defineProperties(object, propertiesObject);
return object;
},
// 延展物件
// to add property, use Object.assign()
// application.debug.log use this.
assign : function assign(target, source) {
target = Object(target);
for (var index = 1, length = arguments.length, key; index < length;) {
source = Object(arguments[index++]);
for (key in source) {
// Warning: 可能得注意 `need_avoid_assign_to_setter`
// @see CeL.application.net.URI()
target[key] = source[key];
}
if (need_to_check_in_for_in)
for (key in need_to_check_in_for_in)
// assert: !== 須由左至右運算。
// assert: i = 0; [ 1, 2 ][i] !== [ 2, 2 ][i = 1];
if (need_to_check_in_for_in[key] !== source[key = key
.slice(1)])
target[key] = source[key];
}
return target;
}
});
set_method(Array.prototype, {
// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/forEach
forEach: function forEach(callbackfn, thisArg) {
for (var index = 0, length = this.length,
// 使用 Function.prototype.call。
use_call = thisArg !== undefined && thisArg !== null
&& typeof callbackfn.call === 'function';
index < length; index++)
// 為允許 delete,先作 check。
if (index in this) {
if (use_call) {
callbackfn.call(thisArg, this[index], index, this);
} else {
// 少一道手續。
callbackfn(this[index], index, this);
}
}
}
});
// ---------------------------------------------------------------------//
// @see CeL.data.code.compatibility.is_thenable()
// cf. Promise.isPromise()
function is_thenable(value) {
return value
// https://github.com/then/is-promise/blob/master/index.js
// && (typeof value === 'object' || typeof value === 'function')
&& typeof value.then === 'function';
}
function is_async_function(value) {
// https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/AsyncFunction
// 注意 AsyncFunction 不是一個全域物件。 它可以以下程式碼獲得。
// Object.getPrototypeOf(async function(){}).constructor
return typeof value === 'function'
// to allow async functions:
// https://github.com/tc39/ecmascript-asyncawait/issues/78
&& value.constructor.name === 'AsyncFunction';
}
function run_and_then(first_to_run, and_then, error_catcher) {
if (!error_catcher) {
var result = first_to_run();
if (is_thenable(result))
return result.then(and_then);
return and_then(result);
}
try {
var result = first_to_run();
if (is_thenable(result))
return result.then(and_then, error_catcher);
return and_then(result);
} catch(e) {
return error_catcher(e);
}
}
set_method(_, {
is_thenable : is_thenable,
is_async_function : is_async_function,
run_and_then : run_and_then
});
// ----------------------------------------------------------------------------------------------------------------------------------------------------------//
// 依賴於 set_method() 設定完之後才能使用的方法
// ----------------------------------------------------------------------------------------------------------------------------------------------------------//
// for software verification(驗證) and validation(驗收).
// _.preserve_verify_code = false;
// ----------------------------------------------------------------------------------------------------------------------------------------------------------//
// 最終設定。
if (false) {
var test_obj = _(2, 'test: Initialization');
test_obj.test_print('OK!');
}
if (false) {
if (has_console) {
console.log('globalThis: ' + typeof globalThis);
console.log(library_name + ': ' + typeof globalThis[library_name]);
}
}
/**
* 能執行到最後都沒出錯才設定到 globalThis。
*
* @ignore
*/
globalThis[library_name] = _;
if (typeof module === 'object'
// NG if we have specified module.exports: ((module.exports === exports))
// http://weizhifeng.net/node-js-exports-vs-module-exports.html
// 如果module.exports當前沒有任何屬性的話,exports會把這些屬性賦予module.exports。
&& typeof module.exports === 'object') {
module.exports = _;
}
// test globalThis.
try {
if (_ !== eval(library_name))
throw 1;
// TODO: test delete globalThis object.
} catch (e) {
if (e === 1) {
// 若失敗,表示其他對 globalThis 的操作亦無法成功。可能因為 globalThis 並非真的
// Global,或權限被限制了?
_.warn('無法正確設定 globalThis object!');
} else if (e && e.message && e.message.indexOf('by CSP') !== -1) {
// Firefox/49.0 WebExtensions 可能 throw:
// Error: call to eval() blocked by CSP
_.env.no_eval = true;
// use chrome.tabs.executeScript(null, {code:''});
}
}
}
)(
/**
* Global Scope object 整體
* 於 CeL.eval_code 使用.
*
* TODO:
* Function constructor evaluates in a scope of that function, not in a
* global scope.
* http://perfectionkills.com/global-eval-what-are-the-options/
*
* @ignore
* @see How to get the Global Object in
* JavaScript? - Stack Overflow
*/
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis
typeof globalThis === 'object' && globalThis.Array === Array && globalThis
// In strict mode, this inside globe functions is undefined.
// https://developer.mozilla.org/en/JavaScript/Strict_mode
|| typeof window !== 'undefined' && window
// isWeb() ? window : this;
// https://github.com/tc39/proposal-global
// 由於在HTML Application環境中,self並不等於window,但是應該要用window,所以先跳過這一項。
// 因著HTA的問題,要採用也必須放在window之後。
|| typeof self !== 'undefined' && self
// e.g., node.js
|| typeof global === 'object' && global.Array === Array && global
// http://nodejs.org/api/globals.html
// node.js requires this method to setup REALLY global various:
// require isn't actually a global but rather local to each module.
// However, this causes CSP violations in Chrome apps.
|| Function('return this')()
// (function(){return this;})()
)
// ) // void(
;
/**
*
TODO:
瘦身
等呼叫時才 initialization
http://headjs.com/#theory
Head JS :: The only script in your HEAD
Multiversion Support
http://requirejs.org/docs/api.html
arguments Object:
The arguments object is not available when running in fast mode, the default for JScript. To compile a program from the command line that uses the arguments object, you must turn off the fast option by using /fast-. It is not safe to turn off the fast option in ASP.NET because of threading issues.
*/
if (typeof CeL === 'function') {
(function(_) {
// var _// JSDT:_module_
// = this;
if (false) {
// IE8 中,以 for ( in ) 迭代,會漏掉這兩個。
var need_check_toString = (function() {
var a, OK = 0;
for (a in {
valueOf : function() {
},
toString : function() {
},
p : 1
})
if (a === 'valueOf' || a === 'toString')
OK++;
return OK !== 2;
})();
/**
*
CeL.extend(function f_name(){}, object || string, initial arguments);
CeL.extend({name:function(){},.. }, object || string);
CeL.extend([function1,function12,..], object || string);
set .name
*/
/**
* 延展物件 (learned from jQuery):
* extend variable_set to name_space.
* 將 from_name_space 下的 variable_set 延展/覆蓋到 name_space。
*
* @remark MooTools 1.4.5 會 overwrite 此函數!
*
* @param {Object|Array|String}variable_set
* 欲延展之 variable set.
* @param {Object|Function}name_space
* target name-space. extend to what name-space.
* @param {Object|Function}from_name_space
* When inputing function names, we need a base
* name-space to search these functions.
* @param {true|String|Function}reserve_type
* 若已存在此 type (true|String),或 eval 後回傳 true (function),則不
* overwrite。
* @returns target names-pace
* @see jQuery.extend的用法,
* jQuery源码学习笔记三 - Ruby's Louvre -
* 博客园
* @since 2009/11/25 21:17:44
* @deprecated 2014/5/6 → CeL.set_method()
*/
var extend = function(variable_set, name_space, from_name_space,
reserve_type) {
if (typeof name_space === 'undefined' || name_space === null) {
_
.debug('沒有指定擴展的對象,擴展到 extend.default_target。', 3,
'extend');
if (!(name_space = extend.default_target))
if (name_space === null
&& typeof from_name_space === 'undefined'
// && _.is_Object(variable_set)
)
return variable_set;
else
name_space = {};
}
if (typeof from_name_space === 'undefined'
|| from_name_space === null)
from_name_space = extend.default_target;
else if (variable_set === null
&& _.is_Function(from_name_space))
variable_set = from_name_space;
var variable_name, setter = function(v) {
if (!reserve_type
|| (
// true: any type.
reserve_type === true ? !(variable_name in name_space)
: typeof reserve_type === 'function' ? !reserve_type(
name_space[variable_name], v)
: !_.is_type(
name_space[variable_name],
reserve_type))) {
// Warning: 由於執行時可能處於 log() stack 中,若 log() 會用到
// extend(),這邊又 call .debug(),可能會循環呼叫,造成 stack overflow。
if (_.is_debug()) {
var target_name = _.native_name(name_space);
// 若更動 native Object 等,則作個警示。
_.debug((target_name || '(' + _.is_type(name_space)
+ ')')
+ '.'
+ variable_name
+ ' = ('
+ (typeof v)
+ ')'
+ (_.is_debug(4) || typeof v !== 'function'
&& typeof v !== 'object' ? ' [' + v
+ ']' : ''), target_name ? 1 : 3,
'extend.setter');
}
name_space[variable_name] = v;
}
};
if (_.is_Object(variable_set)
// 若 function 另外處理,依現行實作會出問題!
|| _.is_Function(variable_set)) {
if (need_check_toString) {
if ('valueOf' in variable_set)
variable_set['. added_' + 'valueOf'] = variable_set.valueOf;
if ('toString' in variable_set)
variable_set['. added_' + 'toString'] = variable_set.toString;
}
for (variable_name in variable_set) {
if (need_check_toString)
variable_name = variable_name.replace(/^\. added_/,
'');
if (from_name_space)
if (variable_name in from_name_space) {
setter(from_name_space[variable_name]);
// 這邊的處置可能不甚周延。
} else {
if (false && (variable_set[variable_name] in from_name_space))
setter(from_name_space[variable_set[variable_name]]);
}
else
setter(variable_set[variable_name]);
}
if (need_check_toString) {
if ('valueOf' in variable_set)
delete variable_set['. added_' + 'valueOf'];
if ('toString' in variable_set)
delete variable_set['. added_' + 'toString'];
}
} else if (Array.isArray(variable_set)
&& !Array.isArray(name_space)) {
variable_set
.forEach(function(o) {
if (typeof o === 'object'
|| (o in from_name_space))
extend(o, name_space, from_name_space,
reserve_type);
});
} else if (typeof variable_set === 'string') {
if (!from_name_space) {
_.debug('預設從本 library 自身 extend to target name-space。',
3, 'extend');
from_name_space = _;
}
if (name_space === from_name_space)
_
.debug('(' + variable_set + '): 目標與來源相同。', 2,
'extend');
else if ((variable_name = variable_set) in from_name_space) {
setter(from_name_space[variable_name]);
_.debug('(' + (typeof from_name_space[variable_name])
+ ') ' + variable_name + '\n='
+ from_name_space[variable_name] + '\n\nto:\n'
+ name_space, 2, 'extend');
} else
try {
setter(_.value_of(variable_name));
_.debug('.' + variable_name + ' = '
+ name_space[variable_name], 2, 'extend');
} catch (e) {
_.warn(_.Class + '.extend:\n' + e.message);
}
} else if (typeof variable_set === 'function') {
if (_.parse_function) {
// TODO
throw new Error(1,
'extend: Not Yet Implemented! (for function)');
} else {
_.warn(_.Class + '.extend: Warning: Please include '
+ _.Class + '.parse_function() first!');
}
}
return name_space;
};
// extend.default_target = _;
_// JSDT:_module_
.extend = extend;
}
// .object_hash 之類會用到。
_.set_method(Array.prototype, {
indexOf : function indexOf(element, index) {
index = index > 1 ? Math.floor(index) : 0;
for (var length = this.length; index < length; index++)
if (index in this && this[index] === element)
return index;
return -1;
}
});
// ----------------------------------------------------------------------------------------------------------------------------------------------------------//
/**
* @examples
var some_function = args => some_operators ;
var some_function = (args) => { some_operators };
some_function = CeL.function_placeholder(() => some_function = CeL.some_function || some_function, some_function);
var some_function = function(args) { some_operators; };
some_function = CeL.function_placeholder(function(){
return some_function = CeL.some_function || some_function;
}, some_function);
*/
function function_placeholder(setter, fallback) {
var full_version = setter();
if (full_version && full_version !== fallback
&& _.is_Function(full_version)) {
_.debug('採用完整功能版函數', 1, 'function_placeholder');
} else {
full_version = fallback;
}
return (full_version || fallback).apply(arguments);
}
_.function_placeholder = function_placeholder;
_// JSDT:_module_
.
/**
* 設定 name_space 下的 function_name 待執行時換作 initializor 的 return。
* 換句話說,執行 name_space 下的 function_name (name_space[function_name]) 時把
* name_space[function_name] 換成 new_function (initializor 的 return)。
*
* for Lazy Function Definition Pattern.
* 惰性求值(lazy evaluation or call-by-need),又稱懶惰求值、懶漢求值。
*
* TODO:
* 使用本函數不能完全解決先前已經指定 identifier 的情況。
* 因此對於會使用本函數的函數,盡量別使用 .use_function() 來 include,否則可能會出現問題!
*
* @example
* library_namespace.set_initializor('function_name', function(function_name){return function(){};}, _);
*
*
* @param {String}function_name
* function name to replace: name_space.function_name
* @param {Function}initializor
* will return function identifier to replace with
* @param name_space
* in which name-space
* @returns new_function
* @see http://realazy.org/blog/2007/08/16/lazy-function-definition-pattern/,
* http://peter.michaux.ca/article/3556
*/
set_initializor = function(function_name, initializor, name_space) {
var do_replace;
if (arguments.length < 3 && _.is_Function(function_name)
&& (do_replace = _.get_function_name(function_name))) {
// e.g., library_namespace.set_initializor(get_HTA, _);
name_space = initializor;
initializor = function_name;
function_name = do_replace;
// _.debug('Get function name [' + function_name + '].');
}
if (!name_space)
name_space = _;
if (!initializor)
initializor = name_space[function_name];
do_replace = function() {
if (false) {
_.debug(name_space[function_name] === do_replace);
_.debug(name_space.Class + '[' + function_name + ']='
+ name_space[function_name]);
_.debug('do_replace=' + do_replace);
}
var old_function = name_space[function_name], new_function;
if (old_function === do_replace) {
// 實際執行。
try {
new_function = initializor.call(name_space,
function_name, arguments);
// new_function = initializor.apply(_, arguments);
if (false)
_.debug('new_function = [' + (typeof new_function)
+ ']' + new_function);
} catch (r) {
// 可能因時機未到,或是 initialization arguments 不合適。不作 replace。
return r;
// throw r;
}
if (typeof new_function !== 'function')
// 確定會回傳 function 以供後續執行。
initializor = new_function, new_function = function() {
if (false)
_.debug('new function return [' + initializor
+ '].', 1, 'set_initializor');
return initializor;
};
// searching for other extends
if (_[function_name] === old_function) {
_.debug('Replace base name-space function ['
+ function_name + '].', 1, 'set_initializor');
_[function_name] = new_function;
} else
_.debug('Base name-space function [' + function_name
+ ']: ' + _[function_name] + '.', 1,
'set_initializor');
// 設定 name_space[function_name]。
_.debug('Replace function [' + function_name + '].', 1,
'set_initializor');
name_space[function_name] = new_function;
if (false) {
_.debug(name_space[function_name] === do_replace);
_.debug(name_space.Class + '[' + function_name + ']='
+ name_space[function_name]);
}
} else {
// 已經替換過。
if (_.is_debug(2))
_.warn('set_initializor: The function ['
+ function_name
+ '] had replaced with a new one.');
new_function = old_function;
}
// _.debug('new function: ' + new_function);
// _.debug('return ' + new_function.apply(_, arguments));
return new_function.apply(_, arguments);
};
return name_space[function_name] = do_replace;
};
// ----------------------------------------------------------------------------------------------------------------------------------------------------------//
_.is_HTA = _.is_WWW()
// http://msdn.microsoft.com/en-us/library/ms536496(v=vs.85).aspx
// HTAs do not support the AutoComplete in HTML forms feature, or the
// window.external object.
&& window.external === null && window.ActiveXObject
&& document.getElementsByTagName('APPLICATION').length === 1;
function new_XMLHttpRequest() {
return new XMLHttpRequest();
}
// 'Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'
// 'Msxml2.XMLHTTP.6.0','Msxml2.XMLHTTP.5.0','Msxml2.XMLHTTP.4.0','Msxml2.XMLHTTP.3.0',["MSXML2",
// "Microsoft", "MSXML"].['XMLHTTP','DOMDocument'][".6.0", ".4.0",
// ".3.0", ""]
function new_MS_XMLHTTP() {
return new ActiveXObject('Microsoft.XMLHTTP');
}
/**
* 設定取得 XMLHttpRequest object 的方法。
* The XMLHttpRequest object can't be cached. So we cache the method to
* get the XMLHttpRequest controller.
*
* 在 HTA 中,XMLHttpRequest() 比 ActiveXObject('Microsoft.XMLHTTP')
* 更容易遇到拒絕存取。例如在同一目錄下的 .txt 檔。
* 但在 IE 中,ActiveXObject 可能造成主動式內容之問題。
* jQuery: Microsoft failed to properly implement the XMLHttpRequest in
* IE7, so we use the ActiveXObject when it is available.
*
* @inner
* @ignore
*/
if (_.is_HTA)
try {
_.new_XMLHttp = new_MS_XMLHTTP() && new_MS_XMLHTTP;
} catch (e) {
}
if (!_.new_XMLHttp)
try {
// normal method to get a new XMLHttpRequest controller.
// 相當於 new XMLHttpRequest()
// Ajax 程式應該考慮到 server 沒有回應時之處置
_.new_XMLHttp = new_XMLHttpRequest() && new_XMLHttpRequest;
} catch (e) {
}
// _.is_HTA 的情況,已經測在前面試過了。
if (!_.new_XMLHttp && !_.is_HTA)
try {
_.new_XMLHttp = new_MS_XMLHTTP() && new_MS_XMLHTTP;
} catch (e) {
}
// 皆無:use XMLDocument.
// The document.all().XMLDocument is a Microsoft IE subset of
// JavaScript.
// http://www.bindows.net/
// http://www.java2s.com/Code/JavaScriptReference/Javascript-Properties/XMLDocument.htm
if (_.new_XMLHttp
// https://github.com/electron/electron/issues/2288
// How to detect if running in electron?
// https://github.com/cheton/is-electron/blob/master/index.js
&& (typeof process !== 'object'
|| typeof process.versions !== 'object' || !process.versions.electron)) {
} else if (_.platform.nodejs) {
// for node.js, node_fs
_.new_XMLHttp = require('fs');
_.platform.browser = process.versions.electron ? 'electron'
: 'node';
_.platform.version = process.versions.electron
|| process.versions.node;
// @see os.version()
_.platform.OS = process.platform;
// shortcut for Windows
_.platform.Windows = _.platform.is_Windows();
// argument vector
_.env.argv = process.argv;
// env hash: see CeL.env.arg_hash @ CeL.application.platform.nodejs
if (_.platform.browser === 'node')
_.platform.is_CLI = true;
else if (_.platform.browser === 'electron')
// is GUI
_.platform.is_CLI = false;
// 為 CLI interactive 互動形式。
// @see WScript.Interactive @ CeL.application.OS.Windows.job
_.platform.is_interactive
// isTTY: 為 nodejs: interactive 互動形式。
// 但 isTTY 在 command line 執行程式時也會為 true!
= process.stdout && process.stdout.isTTY
// Windows 7 to Windows 10
|| process.env.SESSIONNAME === 'Console';
if (_.platform.is_interactive) {
_.log_temporary = function log_temporary(message) {
// message + ' ...\r'
process.stdout.write('\r'
+ _.preprocess_log_messages(message) + ' \r');
};
}
// TODO:
// https://github.com/driverdan/node-XMLHttpRequest/blob/master/lib/XMLHttpRequest.js
var node_read_file = _.new_XMLHttp = _.new_XMLHttp.readFileSync;
// The encoding can be 'utf8', 'ascii', or 'base64'.
// http://nodejs.org/api/fs.html#fs_fs_createreadstream_path_options
_.get_file = function get_file(path, encoding) {
// for node.js
if (_.platform.Windows && /^\/[a-z]:\//i.test(path)) {
// 在 electron package 中,script_base_path 可能形如 '/D:/'...。
// node.js 在讀取 "/D:/"... 這一種檔案時會轉換成 "/D:/D:/"...
path = path.slice(1);
}
var data, i, l, tmp;
try {
data = node_read_file(path, encoding);
} catch (e) {
data = node_read_file(path);
}
if (typeof data !== 'string') {
// auto detect encoding
l = data.length;
if (data[0] === 255 && data[1] === 254) {
_.debug(path + ': UTF-16LE', 4);
// 去掉 BOM。
// pass byte order mark (BOM), the first 2 bytes.
i = 2;
tmp = [];
while (i < l)
tmp.push(String.fromCharCode(data[i++] + 256
* data[i++]));
} else if (data[0] === 254 && data[1] === 255) {
_.debug(path + ': UTF-16BE', 4);
// pass byte order mark (BOM), the first 2 bytes.
i = 2;
tmp = [];
while (i < l)
tmp.push(String.fromCharCode(256 * data[i++]
+ data[i++]));
} else if (!encoding && data[0] === 239 && data[1] === 187
&& data[2] === 191) {
// 或許是存成了 UTF-8?
// https://en.wikipedia.org/wiki/Byte_order_mark#Representations_of_byte_order_marks_by_encoding
_.debug('get_file: Treat file as UTF-8 with BOM: ['
+ path + ']', 2);
// tmp = null;
if (false) {
// http://nodejs.org/api/fs.html#fs_fs_readfilesync_filename_options
data = node_read_file(path, 'utf8')
// pass byte order mark (BOM), the first 1 byte:
// \uFEFF.
.slice(1);
} else {
// console.log([ path, data.slice(0, 10) ]);
// assert: data.toString().charCodeAt(0) === 65279
// data.toString().charAt(0) === \uFEFF
// buffer.toString('utf8', 0, length);
data = data.toString(/* Default: 'utf8' */)
// pass byte order mark (BOM), the first 1 byte:
// \uFEFF.
// 採用 data.toString('utf8', 3),奇怪的是有時仍然會得到
// [65279,...] @ node.js 14.7.0 。
// 不如全部 .toString() 之後再 .slice(1)。
.slice(1);
}
} else
try {
i = node_read_file(path, 'utf8');
_.debug('get_file: Treat file as UTF-8: [' + path
+ ']', 2);
// tmp = null;
data = i;
} catch (e) {
// console.warn(e);
if (l > 1)
_
.debug('get_file: Unknown byte order mark (BOM) of ['
+ path
+ ']: '
+ data[0]
+ ','
+ data[1]);
// 當作 ASCII 處理。
i = 0;
tmp = [];
while (i < l)
// data.toString('utf-8', 0, length);
tmp.push(String.fromCharCode(data[i++]));
}
if (tmp)
data = tmp.join('');
}
return data;
};
} else if (typeof _configuration === 'object'
// for jslibs
&& typeof File === 'function') {
_.platform.browser = 'jsio';
LoadModule('jsio');
_.get_file = function(path) {
// _configuration.stderr(path);
var c, i, data = new File(path).Open('r').Read(), l = data.length, tmp = [], next_code = function() {
c = data.charCodeAt(i++);
return c < 0 ? c + 256 : c;
};
_configuration.stderr(path + ': ' + data.charCodeAt(0) + ','
+ data.charCodeAt(1));
if (data.charCodeAt(0) === -1 && data.charCodeAt(1) === -2) {
// _.debug(path + ': UTF-16LE');
for (i = 2; i < l;)
tmp.push(String.fromCharCode(next_code() + 256
* next_code()));
data = tmp.join('');
} else if (data.charCodeAt(0) === -2
&& data.charCodeAt(1) === -1) {
// _.debug(path + ': UTF-16BE');
for (i = 2; i < l;)
tmp.push(String.fromCharCode(next_code() * 256
+ next_code()));
data = tmp.join('');
}
return data;
};
} else if (typeof Stream === 'function') {
// for JSDB
_.platform.browser = 'JSDB';
_.get_file = function(path) {
// _.log('get_file: ' + path);
try {
return new Stream(path
// , 'r'
).readFile();
} catch (e) {
// _.log(e.message);
}
var data = new Stream(path, 'b'), tmp = [],
// The byte order mark (BOM).
BOM = [ data.readUInt8(), data.readUInt8() ];
if (BOM[0] === 255 && BOM[1] === 254) {
// _.debug(path + ': UTF-16LE');
while (!data.eof)
tmp.push(String.fromCharCode(data.readUInt8() + 256
* data.readUInt8()));
} else if (BOM[0] === 254 && BOM[1] === 255) {
// _.debug(path + ': UTF-16BE');
while (!data.eof)
tmp.push(String.fromCharCode(data.readUInt8() * 256
+ data.readUInt8()));
} else {
data.rewind();
while (!data.eof)
tmp.push(data.get());
}
data.close();
return tmp.join('');
};
} else
_.get_file = function() {
// No XMLHttpRequest controller.
var m = 'get_file: This scripting engine does not support XMLHttpRequest.';
_.warn(m);
throw new Error(m);
// firefox: This function must return a result of type any.
// return undefined;
};
_// JSDT:_module_
.
/**
* Ask privilege in mozilla projects: Firefox 2, 3.
* get_file() 遇到需要提高權限時使用。
* enablePrivilege 似乎只能在執行的 function 本身或 caller 呼叫才有效果,跳出函數即無效,不能
* cache,因此提供 callback。
* 就算按下「記住此決定」,重開瀏覽器後需要再重新授權。
*
* @param {String|Error}
* privilege privilege that asked 或因權限不足導致的 Error
* @param {Function|Array}
* callback|[callback,arguments] Run this callback if getting
* the privilege. If it's not a function but a
* number(經過幾層/loop層數), detect if there's a loop or run the
* caller.
* @returns OK / the return of callback
* @throws error
* @since 2010/1/2 00:40:42
*/
require_netscape_privilege = function require_netscape_privilege(
privilege, callback) {
var _s = require_netscape_privilege, f, i,
/**
* raise error. error 有很多種,所以僅以 'object' 判定。
*
* @inner
* @ignore
*/
re = function(m) {
// _.debug('Error: ' + m);
throw privilege && typeof privilege === 'object' ?
// Error object
privilege :
// new Error (message)
new Error(m);
};
if (!_s.enabled)
re('Privilege requiring disabled.');
// test loop
// 得小心使用: 指定錯可能造成 loop!
if (!isNaN(callback) && callback > 0 && callback < 32) {
try {
/**
* @Firefox 4:
* TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them
*
*/
for (f = _s, i = 0; i < callback; i++) {
f = f.caller;
if (f)
// TODO: do not use arguments
f = f.arguments.callee;
}
if (f === _s)
// It's looped
re('Privilege requiring looped.');
callback = 1;
} catch (e) {
// TODO: handle exception
}
}
f = _s.enablePrivilege;
// _.debug('enablePrivilege: ' + f);
// '我們需要一點權限來使用 XMLHttpRequest.open。\n* 請勾選記住這項設定的方格。'
if (!f
&& !(_s.enablePrivilege = f = _
.value_of('netscape.security.PrivilegeManager.enablePrivilege')))
// 更改設定,預防白忙。
_s.enabled = false, re('No enablePrivilege get.');
if (_.is_type(privilege, 'DOMException') && privilege.code === 1012)
// http://jck11.pixnet.net/blog/post/11630232
// Mozilla的安全機制是透過PrivilegeManager來管理,透過PrivilegeManager的enablePrivilege()函式來開啟這項設定。
// 須在open()之前呼叫enablePrivilege()開啟UniversalBrowserRead權限。
// http://code.google.com/p/ubiquity-xforms/wiki/CrossDomainSubmissionDeployment
// Or: In the URL type "about:config", get to
// "signed.applets.codebase_principal_support" and change its
// value to true.
// 由任何網站或視窗讀取私密性資料
privilege = 'UniversalBrowserRead';
else if (!privilege || typeof privilege !== 'string')
re('Unknown privilege.');
// _.debug('privilege: ' + privilege);
try {
if (false)
_.log(_.Class
+ '.require_netscape_privilege: Asking privilege ['
+ privilege + ']..');
f(privilege);
} catch (e) {
if (privilege !== 'UniversalBrowserRead' || !_.is_local())
_
.warn(_.Class
+ '.require_netscape_privilege: User denied privilege ['
+ privilege + '].');
throw e;
}
// _.debug('OK. Get [' + privilege + ']');
if (callback === 1) {
// _.debug('再執行一次 caller..');
try {
callback = _s.caller;
} catch (e) {
// TODO: handle exception
}
return callback.apply(_, callback.arguments);
if (false) {
i = callback.apply(_, callback.arguments);
_.debug(('return ' + i).slice(0, 200));
return i;
}
} else if (_.is_Function(callback))
// 已審查過,為 function
return callback();
else if (Array.isArray(callback))
return callback[0].apply(_, callback[1]);
};
/**
* 當需要要求權限時,是否執行。(這樣可能彈出對話框)
* Firefox 5 之後,就算要求了,對 local 也沒用,甚至會 hang 住掛掉,因此取消了。
*
* @type Boolean
*/
_// JSDT:_module_
.require_netscape_privilege.enabled = false;
// ----------------------------------------------------------------------------------------------------------------------------------------------------------//
var is_Opera = _.is_WWW(true) && navigator.appName === 'Opera';
/**
* 以同時依序(synchronously)的方式,載入最基本之資源取得功能。
* Get resource files by {@link XMLHttpRequest}.
* 依序載入 resources,用於 include JavaScript 檔之類需求時,取得檔案內容之輕量級函數。
* 除 Ajax,本函數亦可用在 CScript 執行中。
* see also: .application.net.Ajax.get_URL()
*
* @example
// get contents of [path/to/file]:
var file_contents = CeL.get_file('path/to/file');
*
* @param {String}
* path URI / full path.
* 不能用相對path!
* @param {String}
* [encoding] file encoding
* @returns {String} data content of path
* @returns {undefined} when error occurred: no Ajax function, ..
* @throws uncaught
* exception @ Firefox: 0x80520012 (NS_ERROR_FILE_NOT_FOUND), NETWORK_ERR
* exception
* @throws 'Access
* to restricted URI denied' 當 access 到上一層目錄時 @ Firefox
* @see Cross
* Site AJAX, Cross-domain Ajax,
* FF3 issue with iFrames and XSLT
* standards, Security.fileuri.strict origin
* policy - MozillaZine Knowledge Base Chrome: NETWORK_ERR:
* XMLHttpRequest Exception 101
*/
function get_file(path, encoding, post_data) {
if (_.is_Object(encoding)) {
post_data = encoding;
encoding = null;
}
if (_.is_Object(path)) {
post_data = path.post || post_data;
encoding = path.encoding || encoding;
}
var method = post_data ? 'POST' : 'GET',
/**
* The XMLHttpRequest object can't be cached.
*
* @inner
* @ignore
*/
object = _.new_XMLHttp();
// 4096: URL 長度限制,與瀏覽器有關。
if (typeof path === 'string' && path.length > 4096
&& (post_data = path.match(/^([^?]{6,200})\?(.+)$/)))
path = post_data[1], post_data = post_data[2], method = 'PUT';
else
post_data = null;
try {
// IE 10 中,local file 光 .open() 就 throw 了。
object.open(method, path, false);
// 有些版本的 Mozilla 瀏覽器在伺服器送回的資料未含 XML mime-type
// 檔頭(header)時會出錯。為了避免這個問題,可以用下列方法覆寫伺服器傳回的檔頭,以免傳回的不是 text/xml。
// http://squio.nl/blog/2006/06/27/xmlhttprequest-and-character-encoding/
// http://www.w3.org/TR/XMLHttpRequest/ search encoding
if (encoding && object.overrideMimeType)
/**
* old:
object.overrideMimeType('text/xml;charset=' + encoding);
* 但這樣會被當作 XML 解析,產生語法錯誤。
*
* TODO:
* try:
object.overrideMimeType('text/plain;charset=' + encoding);
*/
object.overrideMimeType('application/json;charset='
+ encoding);
// http://www.w3.org/TR/2007/WD-XMLHttpRequest-20070227/#dfn-send
// Invoking send() without the data argument must give the same
// result as if it was invoked with null as argument.
// 若檔案不存在,會 throw。
object.send(post_data);
if (65533 === object.responseText.charCodeAt(0)
/**
* e.g., @ Mozilla/5.0 (Windows NT 6.1; rv:29.0) Gecko/20100101 Firefox/29.0
*/
&& navigator.userAgent.indexOf(' Gecko/2') !== -1) {
_.env.same_origin_policy = true;
var error = new Error(
'get_file: Cannot parse UTF-32 encoding of ['
+ path + '] @ Firefox!');
// 於 load_named() 使用,避免顯示 '重新讀取(reload),或是過段時間再嘗試或許可以解決問題。'
error.type = 'encode';
throw error;
}
delete get_file.error;
} catch (e) {
if (e.number === -1072896658
// || e.message.indexOf('c00ce56e') !== -1
) {
// http://support.microsoft.com/kb/304625
throw new Error(
'指定的資源回傳了系統不支援的文字編碼,因此無法解碼資料。請檢查此網頁回傳之 header,確認系統可解碼 Content-Type 之 charset。');
}
/**
* Chome:
* XMLHttpRequest cannot load file:///X:/*.js. Cross origin requests are only supported for HTTP.
*
* Opera 11.50: 不會 throw,但是 .responseText === ''。
*
* Apple Safari 3.0.3 may throw NETWORK_ERR: XMLHttpRequest
* Exception 101
*/
get_file.error = e;
if (_.is_debug(2)) {
_.warn(_.Class + '.get_file: Loading [' + path
+ '] failed!');
_.error(e);
}
/** [XPCWrappedNative_NoHelper] Cannot modify properties of a WrappedNative @ firefox
*/
// e.object = o;
if (
// 5: 系統找不到指定的資源。/存取被拒。
// IE 10 中,5: "存取被拒。"。same origin policy 下,即使是檔案存在,值一樣為
// 5,因此無法以資判別。
// (e.number & 0xFFFF) !== 5 &&
_.is_WWW()
&& (_.is_local() || ((object = path
.match(/:(\/\/)?([^\/]+)/)) && object[2] !== window.location.hostname))) {
// 八九不離十: no Cross-site scripting (XSS).
if (_.is_debug()) {
_
.warn('get_file: '
+ (_.is_local() ? '呼叫了上層 local file'
: '所要求檔案之 domain ['
+ object[2]
+ '] 與所處之 domain ['
+ window.location.hostname
+ '] 不同')
+ '!
\n您可能需要嘗試使用 '
+ _.Class
+ '.run()!\nSet up same origin policy flag.');
}
_.env.same_origin_policy = true;
throw new Error('get_file: Different domain!');
}
object = _.require_netscape_privilege(e,
[ get_file, arguments ]);
if (false)
_.debug('require_netscape_privilege return ['
+ typeof (object) + ('] ' + object).slice(0, 200)
+ ' ' + (e === object ? '=' : '!') + '== '
+ 'error (' + e + ')');
if (e === object)
throw e;
return object;
}
// workaround for Opera: Opera 11.50:
// 不會 throw,但是 .responseText === ''。
if (object.responseText === '' && is_Opera)
throw new Error('get_file: Nothing get @ Opera');
// 當在 local 時,成功的話 status === 0。失敗的話,除 IE 外,status 亦總是 0。
// status was introduced in Windows Internet Explorer 7.
// http://msdn.microsoft.com/en-us/library/ms534650%28VS.85%29.aspx
// 因此,在 local 失敗時,僅 IE 可由 status 探測,其他得由 responseText 判別。
if (false)
_.debug('Get [' + path + '], status: [' + object.status + '] '
+ object.statusText);
// .responseXML
return object.status === 400 ? [ object.status, object.responseText ]
: object.responseText;
}
if (!_.get_file)
_.get_file = get_file;
// ----------------------------------------------------------------------------------------------------------------------------------------------------------//
/**
* 較為安全的執行,可當作 JSON.parse()。
* we only need simple JSON.parse @ .get_script_base_path
*
* @param {String}text
* string to evaluate
* @param {Boolean}cache_error
* 是否 cache error.
* Warning: deprecated. 請自行 cache.
* @param {Function}[filter]
* callback/receiver to filter the value
* Warning: deprecated. Please use Object.filter () instead.
*
* @returns evaluated value
*/
function eval_parse(text, cache_error, filter) {
if (cache_error)
try {
return eval_parse(text, filter);
} catch (e) {
if (_.is_debug(2))
_.error('eval_parse: SyntaxError: [' + text + ']');
// throw e;
return;
}
if (text)
// borrow from Google, jQuery
// TODO: 對 {String}text 只是做簡單處理,勢必得再加強。
text = ((new Function("return({o:" + text + "\n})"))()).o;
return text;
}
// see Array.from of dependency_chain.js
function tag_list_default(tag, context) {
// 必須考量輸入的可能是 document.styleSheets 的情況。
// 須注意: @ IE8, false === CeL.is_NodeList(document.styleSheets);
return tag
&& Array.prototype.slice
.call(typeof tag === 'string' ? (context || document)
.getElementsByTagName(tag)
: tag) || [];
}
function tag_list_compatible(tag, context) {
var list = [], i = 0, nodes = typeof tag === 'string' ? (context || document)
.getElementsByTagName(tag)
: tag, length = nodes && nodes.length || 0;
while (i < length)
list.push(nodes[i++]);
return list;
}
_// JSDT:_module_
.
// 代替 .getElementsByTagName(), get nodes, 並將之轉成不變化的 native Array.
get_tag_list = _.is_WWW(1) ? function(tag, context) {
var list;
try {
// 一般做法。
list = tag_list_default(tag, context);
_.get_tag_list = tag_list_default;
} catch (e) {
// Array.prototype.slice.call(document.getElementsByTagName('a'))
// Array.prototype.slice.call(document.getElementsByTagName('a'),0)
// get error @ IE8 (Script engine: JScript 5.8.18702):
// Error 5014 [TypeError] (facility code 10): 必須要有 JScript 物件
// @ IE8: typeof document.getElementsByTagName('a') === 'object'
list = tag_list_compatible(tag, context);
// 成功才設定。
if ((e.number & 0xFFFF) === 5014) {
_.debug('get_tag_list: 使用舊的實現方法。');
_.get_tag_list = tag_list_compatible;
}
}
return list;
} : function() {
_.warn('get_tag_list: No method availed!');
return [];
};
_// JSDT:_module_
.
/**
* 得知 script file 之相對 base path
*
* @param {String}
* JSFN script file name (NOT path name)
* @returns {String} relative base path
* @example
// 引數為本.js檔名。若是更改.js檔名,亦需要同時更動此值!
var base_path = CeL.get_script_base_path('baseFunc.js');
const main_script_path = CeL.get_script_base_path(/\.js/i, module);
# perl:
use File::Basename;
*/
get_script_base_path = function(JSFN, terminal_module) {
if (terminal_module === undefined && typeof module === 'object')
terminal_module = module;
// alert('JSFN: ' + JSFN);
if (!JSFN) {
if (_.is_WWW()) {
// window.location.pathname
JSFN = window.location.href.replace(/#.*$/, '');
try {
JSFN = typeof decodeURI === 'function' ? decodeURI(JSFN)
: unescape(JSFN);
} catch (e) {
// TODO: handle exception
}
} else if (typeof terminal_module === 'object') {
// for node.js
JSFN = terminal_module.filename;
} else if (_.script_host) {
JSFN = WScript.ScriptFullName;
} else if (false && typeof WshShell === 'object') {
// 用在把檔案拉到此檔上時不方便。
JSFN = WshShell.CurrentDirectory;
}
return typeof JSFN === 'string' ? JSFN.replace(/[^\\\/]+$/, '')
: '';
}
// ----------------------------------
// TODO: using import.meta.url
// console.log([ typeof require, typeof require.main ]);
// console.trace(require.main);
var filename, test_filename = function(module) {
var path = module.filename;
if (false) {
console.log('get_script_base_path: ' + JSFN + ' @ ' + path);
}
if (path
// 在 electron 中可能會是 index.html 之類的。
// && /\.js/i.test(path)
&& (_.is_RegExp(JSFN) ? JSFN.test(path)
//
: path.indexOf(JSFN) !== -1)) {
filename = path;
}
};
if (typeof require === 'function'
// There is no `require.main` @ electron 9.2-
&& typeof require.main === 'object') {
// for node.js 14.7+
var _module = require.main;
filename = null;
while (_module) {
test_filename(_module);
if (_module === terminal_module)
break;
_module = _module.children;
}
if (!filename && terminal_module)
test_filename(terminal_module);
if (filename)
return filename;
}
// There is no `module.parent` @ node.js 14.7+
if (typeof module === 'object') {
// for electron
var _module = module;
filename = null;
while (_module) {
// Warning: 此處不計 `terminal_module`!
test_filename(_module);
_module = _module.parent;
}
if (filename)
return filename;
}
// ----------------------------------
// We don't use is_Object or so.
// 通常會傳入的,都是已經驗證過的值,不會出現需要特殊認證的情況。
// 因此精準繁複的驗證只用在可能輸入奇怪引數的情況。
if (!_.is_WWW())
return '';
// form dojo: d.config.baseUrl = src.substring(0, m.index);
var i = 0, node = document.getElementById(_.env.main_script_name),
// TODO: 若是有 id,則以 id 為主。
o = node ? [ node ] : _.get_tag_list('script'), l = o.length, j, base_path, index;
// console.log('-----------------------------------');
// console.log(o);
for (; i < l; i++)
try {
// o[i].src 多是 full path, o[i].getAttribute('src')
// 僅取得其值,因此可能是相對的。
j = node = o[i];
j = j.getAttribute && j.getAttribute('src') || j.src;
index = j.lastIndexOf(JSFN);
// alert(j + ',' + JSFN + ',' + I);
if (index !== -1) {
// 正規化: URL 使用 '/' 而非 '\'
// TODO: 尚未完善。
if (j.indexOf('/') === -1 && j.indexOf('\\') !== -1)
j = j.replace(/\\/g, '/');
/**
* 處理
*/
if (setup_extension) {
if (JSFN === _.env.main_script)
setup_extension(_.env.script_extension, node);
else if (JSFN === _.env.main_script_name)
setup_extension(j.slice(index + JSFN.length),
node);
}
base_path = j.slice(0, index);
if (j.length === index + JSFN.length) {
// test 是否以 JSFN 作為結尾。
// 注意: 依照現行的實作方法,用loader來載入JSFN時,必須以 JSFN 作為結尾。
break;
}
}
} catch (e) {
}
// _.log()
// base_path || './'
return base_path || '';
};
if (false)
console.log(_.get_tag_list('script').map(function(n) {
return n.getAttribute('src')
}));
/**
* 處理
* TODO: modify the dirty hack.
*/
var setup_extension = function(extension, node) {
if (extension === _.env.script_extension
// || extension === '.js' || extension === '.txt'
) {
// TODO: unload 時 delete .script_node
// _.script_node = node;
var env = _.env, config, matched;
try {
config = node.innerText || (config = node.firstChild)
&& config.nodeValue;
// IE8 沒有 .innerText || .nodeValue
if (!config
&& typeof (config = node.innerHTML) === 'string')
config = (matched = config
.match(/^[\s\n]*[\s\n]*$/)) ? matched[1]
: config.replace(//g, '');
if (config) {
// http://www.whatwg.org/specs/web-apps/current-work/multipage/scripting-1.html#inline-documentation-for-external-scripts
// If there is a src attribute, the element must be
// either empty or contain only script documentation
// that also matches script content restrictions.
if (matched = config.match(/\/\*([\s\S]+?)\*\//))
config = matched[1];
if (config = (typeof JSON === 'object' && JSON.parse || eval_parse)
(config.replace(/[\s\r\n]*\/\//g, '')))
env.script_config = config;
}
} catch (e) {
_.error('setup_extension: Invalid configuration: ['
+ node.outerHTML + ']');
_.error(e);
}
env.main_script = env.main_script.replace(new RegExp('\\'
+ env.script_extension + '$'), extension);
env.script_extension = extension;
// alert(env.main_script + '\n' + env.script_extension);
// done.
setup_extension = null;
}
};
// ----------------------------------------------------------------------------------------------------------------------------------------------------------//
_// JSDT:_module_
.
/**
* test 是否符合 module pattern.
*
* TODO: improve
*
* @param {String}
* test_string string to test
* @returns {Boolean} 是否符合 module pattern
*/
is_module_pattern = function(test_string) {
var env = _.env;
var r = env.module_identifier_RegExp;
if (!r) {
// initial module_identifier_RegExp
r = env.identifier_RegExp.source;
r = env.module_identifier_RegExp = new RegExp('^' + r + '(\\.'
+ r + ')*$');
}
return r.test(test_string);
};
_// JSDT:_module_
.
/**
* test function.request 的項目是否為 module.
* 以 ./ 開頭可以確保必定是 path.
*
* TODO: 現在還有很大問題!
*
* @param {String}resource_String
* resource to test
* @returns {Boolean} resource 是否為 module (true: is module, false: is
* URL?)
*/
match_module_name_pattern = function match_module_name_pattern(
resource_String) {
return typeof resource_String !== 'string'
|| resource_String.charAt(0) === '.'
|| resource_String.charAt(0) === '/'
|| resource_String.indexOf(':') !== -1
// || resource_String.indexOf('%')!==-1
|| /\.(js|css)$/i.test(resource_String) ? false : /\.$/
.test(resource_String)
|| _.is_module_pattern(resource_String);
};
_// JSDT:_module_
.
/**
* reduce path. 減縮 path. 轉化所有 /., /.., // 尚未處理:: * ?
*
* @example
CeL.simplify_path('http://hostname.org/pp/../aaa/bbb/../ccc/../ddd');
*
* @param {String}path
* 欲轉化之 path
* @returns {String} path
* @since 2009/11/23 22:32:52
*/
simplify_path = function simplify_path(path, options) {
_.debug('[' + typeof path + '] [' + path + ']', 8, 'simplify_path');
if (false && typeof path !== 'string')
return path;
// path = '' + path;
path = String(path);
if (!path)
return;
if (/^"([^"]+)"$|^'([^']+)'$/.test(path)) {
// JSON.parse(path);
path = path.slice(1, -1);
}
// Windows environment variables 在真實 path 前,尚未測試!
// Using function ExpandEnvironmentStrings(string) @
// CeL.application.platform.nodejs instead!
if (false && typeof WinEnvironment === 'object'
&& (t = path.match(/%(.+)%/g))) {
for ( var i in t)
if (WinEnvironment[i])
path.replace(new RegExp(i, "ig"), WinEnvironment[i]);
}
// 有 head 表示 is absolute
var head, tail, is_URL;
// 對於 URL 如:
// https://web.archive.org/web/http://site.org
// http://site.org?p=//\\#a/b/c
// 由於有太多不可不可預測因素,因此需特別處理之。
if (/[\w\-]:\/\//.test(path)) {
// [ all, protocol + ://, path ]
is_URL = path.match(/^((?:(?:file:\/|[\w\-]+:)?\/)?\/)(.*?)$/);
if (is_URL) {
// e.g.,
// 'http://example.org/path/to/'
// '//example.org/path/to/'
// '/example.org/path/to/'
head = is_URL[1];
path = is_URL[2];
} else {
// e.g., '/path/to/http://example.org/path/to/'
}
is_URL = true;
if (tail = path.match(/^([^#?]+)([#?].*?)$/) || '') {
path = tail[1];
tail = tail[2];
}
if (/\/$/.test(path)) {
// 保存 path 最後的 '/'。
path = path.slice(0, -1);
tail = '/' + tail;
}
path = path.replace(/:\/\//g, encodeURIComponent(':/') + '/');
} else {
path = path.replace(
/^(?:[a-zA-Z]:\\?|\\\\(?:[^\\\/]+)\\?|\\|\/)/,
function($0) {
head = $0;
return '';
})
// 不應去除前後空白. TODO: use String.prototype.trim()
// .replace(/\s+$|^\s+/g,'')
// .replace(/\/\/+/g,'/')
;
if (tail = path.match(/^(.*?)([\\\/]+)$/))
path = tail[1], tail = tail[2].charAt(0);
}
var separator_matched = path.match(/[\\\/]/) || tail
&& tail.match(/^[\\\/]/);
if (!separator_matched)
return (head || '') + path + (tail || '') || '.';
path = path.split(/[\\\/]+/);
for (var i = 0, length = path.length; i < length; i++) {
if (path[i] === '.') {
// ./ → ''
// /./ → /
path[i] = '';
} else if (path[i] === '..') {
// xx/../ → ''
var j = i;
while (j > 0)
if (path[--j] && path[j] !== '..') {
// 找到第一個非 '', '..' 的以相消。
path[i] = path[j] = '';
break;
}
}
}
// '//path' → '/path', '///path' → '/path'
while (path.length > 0 && !path[0]
// '/../path' → '/path'
|| path[0] === '..' && head)
path.shift();
while (path.length > 0 && !path[path.length - 1]) {
// 因為有 separator 結尾的話,應該都放在 tail 了;因此此處能去掉所有的空結尾。
path.pop();
}
path = path.join(separator_matched[0])
// 對 archive.org 之類的網站,不可以簡化 '://'。
// 若為了預防有些情況下需要保留 '//',此條需要 comment out。
// '//' → '/'
.replace(/([\\\/])[\\\/]+/g, '$1')
// .replace(head ? /^([\\\/]\.\.)+/g : /^(\.[\\\/])+/g, '')
;
// postfix
if (is_URL)
// recover. '%3A%2F': encodeURIComponent(':/')
path = path.replace(/%3A%2F\//g, '://');
if (head)
path = head + path;
else if (!path)
path = '.';
if (tail)
path += tail;
if (false && options && options.directory_only) {
// 去除檔名,只餘目錄。如輸入 http://hostname.org/aaa/bbb/ccc,得到
// http://hostname.org/aaa/bbb/
// 假如輸入sss/ddd,會把ddd除去!需輸入sss/ddd/以標示ddd為目錄.
path = path.replace(/[^\\\/]+$/, '');
}
_.debug('→ [' + path + ']', 8, 'simplify_path');
return path;
};
_// JSDT:_module_
.
/**
* 將輸入的 string 分割成各 module 單元。已去除 library name。
* need environment_adapter()
** 並沒有對 module 做完善的審核!
*
* @param {String}
* module_name module name
* @returns {Array} module unit array
*/
split_module_name = function(module_name) {
if (false)
_.debug('['
+ module_name
+ ']→['
+ module_name.replace(/\.\.+|\\\\+|\/\/+/g, '.').split(
/\.|\\|\/|::/) + ']');
if (typeof module_name === 'string') {
module_name = module_name
// .replace(/\.\.+|\\\\+|\/\/+/g, '.')
// '.': CeL.env.module_name_separator
.replace(/[\\\/]+/g, '.').split(/[.\\\/]|::/);
}
if (Array.isArray(module_name) && module_name.length) {
// 去除 library name。
if (// module_name.length > 1 &&
_.Class === module_name[0])
module_name.shift();
return module_name;
} else
return [ '' ];
};
_// JSDT:_module_
.
/**
* 取得 module 之 name。以 library name 起始。
*
* @returns {String} module name start with library name
*/
to_module_name = function(module, separator) {
if (_.is_Function(module))
module = module.Class;
else if (module === _.env.main_script_name)
module = _.Class;
if (typeof module === 'string')
module = _.split_module_name(module);
var name = '';
if (Array.isArray(module)) {
if (typeof separator !== 'string')
separator = _.env.module_name_separator;
if (module[0] !== _.Class)
name = _.Class + separator;
name += module.join(separator);
}
return name;
};
// ----------------------------------------------------------------------------------------------------------------------------------------------------------//
_// JSDT:_module_
.is_local = function() {
// cache
return (_.is_local = _.constant_function(!_.is_WWW()
|| window.location.protocol === 'file:'))();
};
// ----------------------------------------------------------------------------------------------------------------------------------------------------------//
_.reset_env();
}
// 不用 apply(),因為比較舊的瀏覽器沒有 apply()。
)(CeL);
}
if (typeof CeL === 'function')
(function(library_namespace) {
var
/** {Number}未發現之index。 const: 基本上與程式碼設計合一,僅表示名義,不可更改。(=== -1) */
NOT_FOUND = ''.indexOf('_');
// ---------------------------------------------------------------------//
// 為一些比較舊的版本或不同瀏覽器而做調適。
// @see data.code.compatibility.
// cache.
var Array_slice = Array.prototype.slice;
/**
* Function.prototype.apply();
* apply & call: after ECMAScript 3rd Edition.
* 不直接用 value undefined: for JS5.
*
* 傳回某物件的方法,以另一個物件取代目前的物件。
* apply是將現在正在執行的function其this改成apply的引數。所有函數內部的this指針都會被賦值為oThis,這可實現將函數作為另外一個對象的方法運行的標的.
* xxx.apply(oThis,arrayArgs): 執行xxx,執行時以 oThis 作為 this,arrayArgs作為
* arguments.
*
* @param apply_this_obj
* @param apply_args
* @returns apply 後執行的結果。
* @see http://msdn.microsoft.com/en-us/library/4zc42wh1(VS.85).aspx
* http://www.cnblogs.com/sunwangji/archive/2007/06/26/791428.html
* http://www.cnblogs.com/sunwangji/archive/2006/08/21/482341.html
* http://msdn.microsoft.com/en-us/library/4zc42wh1(VS.85).aspx
* http://www.interq.or.jp/student/exeal/dss/ejs/3/1.html
* http://blog.mvpcn.net/fason/
* http://d.hatena.ne.jp/m-hiyama/20051017/1129510043
* http://noir.s7.xrea.com/archives/000203.html
* http://www.tohoho-web.com/js/object.htm#inheritClass
*
* @since 2011/11/20
*/
function apply(apply_this_obj, apply_args) {
var temp_apply_key, _arg_list = [], r, i = 0, l = apply_args
&& apply_args.length;
if (apply_this_obj !== null
&& typeof apply_this_obj !== 'undefined')
try {
apply_this_obj[temp_apply_key = 'temp_apply'] = this;
} catch (e) {
temp_apply_key = null;
}
if (l) {
for (; i < l; i++)
_arg_list[i] = 'apply_args[' + i + ']';
if (!temp_apply_key)
apply_this_obj = this;
r = eval('apply_this_obj'
+ (temp_apply_key ? '.' + temp_apply_key : '') + '('
+ _arg_list.join(',') + ')');
} else
r = temp_apply_key ? apply_this_obj[temp_apply_key]() : this();
if (temp_apply_key)
delete apply_this_obj[temp_apply_key];
return r;
}
/**
* Function.prototype.call();
* call 方法是用來呼叫代表另一個物件的方法。call 方法可讓您將函式的物件內容從原始內容變成由 thisObj 所指定的新物件。
* 如果未提供 thisObj 的話,將使用 global 物件作為 thisObj。
*
* @see http://msdn.microsoft.com/library/CHT/jscript7/html/jsmthcall.asp
* @since 2011/11/20
*/
function call(this_obj) {
// 因 arguments 非 instanceof Array,
// arguments.slice(sp) → Array.prototype.slice.call(arguments, sp).
return this.apply(this_obj, Array_slice.call(arguments, 1));
}
function copy_properties_keys(from, to) {
Object.keys(from).forEach(function(property) {
to[property] = from[property];
});
return to;
}
var copy_properties = library_namespace.copy_properties = function copy_properties_old(
from, to) {
// TODO: using Object.getOwnPropertyNames() to copy others
if (Object.keys) {
copy_properties = library_namespace.copy_properties = copy_properties_keys;
return copy_properties(from, to);
}
for ( var property in from)
to[property] = from[property];
return to;
};
// 有 Object.keys() 則使用 Object.keys()。
copy_properties(Object.create(null), Object.create(null));
/**
* Function.prototype.bind();
*
* @since 2011/11/20
* @see bind
*/
function bind(this_obj) {
var func = this, args;
if (arguments.length < 2)
return this_obj === null || typeof this_obj === 'undefined' ? func
: copy_properties(func, function() {
if (false)
library_namespace.debug('this_obj: ['
+ this_obj + '],
\nfunction: ('
+ typeof func + ') [' + func + ']', 1,
'bind');
return func.apply(this_obj, arguments);
});
args = Array_slice.call(arguments, 1);
return copy_properties(func, function() {
var counter = arguments.length, arg, i;
if (!counter)
return func.apply(this_obj, args);
// TODO: TEST: 對於少量 arguments,將 arguments 添入於 .concat() 以加快速度。
arg = args.concat();
i = counter + args.length;
while (counter--)
arg[--i] = arguments[counter];
return func.apply(this_obj, arg);
});
}
// public interface.
library_namespace.set_method(Function.prototype, {
apply : apply,
call : call,
bind : bind
});
// ---------------------------------------------------------------------//
// for Iterator
// for the Iterator interface
/**
*
* @param object
* object to iterate
* @param {String|Function}kind
* kind (The possible values are: "key", "value",
* "key+value"), or next function(index, Iterator, arguments)
*/
function create_list_iterator(object, kind, get_Array, use_origin) {
var key, iterator;
if (use_origin && Array.isArray(object))
iterator = object;
else
for (key in (iterator = []))
// delete any properties that can be iterated.
delete iterator[key];
// assert: Array.isArray(iterator)
if (!kind && typeof kind !== 'function')
kind = Array.isArray(object) ? 'value'
// 當作 Object。視 for(in) 而定。
: 'key';
// define iterator
if (typeof object.forEach === 'function')
object.forEach(kind === 'value' ? function(value) {
iterator.push(value);
} : kind === 'key' ? function(value, key) {
iterator.push(key);
} : function(value, key) {
iterator.push([ key, value ]);
});
else
for (key in object)
iterator.push(
//
kind === 'key' ? key
//
: kind === 'value' ? object[key]
// "key+value"
: [ key, object[key] ]);
if (get_Array)
return iterator;
return new Array_Iterator(iterator, true);
}
// ---------------------------------------------------------------------//
/**
* test code for Map, Set, Array.from():
*
* TODO:
* test: Array.from(Iterator, other arrayLike)
*
* @example
// More examples: see /_test suite/test.js
*
*
*/
// Array.from()
function from(items, mapfn, thisArg) {
if (typeof items === 'undefined' || items === null) {
throw new Error('Cannot convert undefined or null to object');
}
var array, i, iterator = items && !Array.isArray(items)
// 測試是否有 iterator。
&& (
// items['@@iterator'] ||
items.constructor === Set ? 'values'
//
: (items.entries ? 'entries' : items.values && 'values'));
if (!iterator && typeof items.next === 'function') {
// items itself is an iterator.
iterator = items;
}
if (iterator) {
array = [];
// need test library_namespace.env.has_for_of
// for(i of items) array.push(i);
if (typeof iterator === 'function')
iterator = iterator.call(items);
else if (iterator && typeof items[iterator] === 'function')
iterator = items[iterator]();
else if (!iterator.next)
throw new Error('Array.from: invalid iterator!');
while (!(i = iterator.next()).done)
array.push(i.value);
return array;
}
if (typeof mapfn !== 'function') {
try {
// for IE, Array.prototype.slice.call('ab').join() !== 'a,b'
return typeof items === 'string' ? items.split('')
: Array_slice.call(items);
} catch (e) {
if ((e.number & 0xFFFF) !== 5014)
throw e;
mapfn = null;
}
}
var length = items && items.length | 0;
array = [];
if (mapfn) {
for (i = 0; i < length; i++) {
array.push(thisArg ? mapfn.call(thisArg, items[i], i)
// 不採用 .call() 以加速執行。
: mapfn(items[i], i));
}
} else {
while (i < length)
array.push(items[i++]);
}
return array;
}
library_namespace.set_method(Array, {
from : from
});
function Array_Iterator_next() {
// this: [ index, array, use value ]
library_namespace.debug(this.join(';'), 6, 'Array_Iterator.next');
var index;
while ((index = this[0]++) < this[1].length)
if (index in this[1])
return {
value : this[2] ? this[1][index]
//
: [ index, this[1][index] ],
done : false
};
// 已經 done 的不能 reuse。
this[0] = NaN;
return {
value : undefined,
done : true
};
}
function Array_Iterator(array, use_value) {
// library_namespace.debug(array);
// reset index to next index.
// define .next() function onto items.
this.next = Array_Iterator_next.bind([ 0, array, use_value ]);
}
Array_Iterator.prototype.toString = function() {
return "[object Array Iterator]";
};
// export.
library_namespace.Array_Iterator = Array_Iterator;
// ---------------------------------------------------------------------//
// 測試是否具有標準的 ES6 Set/Map collections (ECMAScript 6 中的集合類型)。
var is_Set, is_Map, has_native_Set, has_native_Map,
//
KEY_not_native = library_namespace.env.not_native_keyword,
// use Object.defineProperty[library_namespace.env.not_native_keyword]
// to test if the browser don't have native support for
// Object.defineProperty().
has_native_Object_defineProperty = !Object.defineProperty[KEY_not_native];
try {
has_native_Set = !!(new Set());
has_native_Map = !!(new Map());
// TODO: use library_namespace.type_tester()
is_Set = function(value) {
return Object.prototype.toString.call(value) === "[object Set]";
};
is_Map = function(value) {
return Object.prototype.toString.call(value) === "[object Map]";
};
// (new Map()).entries();
(new Map()).forEach();
} catch (e) {
// browser 非標準 ES6 collections。
// 想辦法補強。
// TODO: WeakMap 概念驗證碼:
// var _WeakMap=function(v){return function(){return eval('v');};};
// var a={s:{a:3}},g=_WeakMap(a.s);
// delete a.s;/* .. */alert(g());
// https://code.google.com/p/es-lab/source/browse/trunk/src/ses/WeakMap.js
if (!has_native_Object_defineProperty || !has_native_Set
|| !has_native_Map)
(function() {
library_namespace
.debug('完全使用本 library 提供的 ES6 collections 實作功能。');
// ---------------------------------------
/**
* hash 處理。在盡可能不動到 value/object 的情況下,為其建立 hash。
* 在 ES5 下,盡可能模擬 ES6 collections。
* 在先前過舊的版本下,盡可能達到堪用水準。
*
* @see harmony-collections
*/
var max_hash_length = 80,
// operator
ADD = 1, DELETE = 2,
// id 註記。
Map_id = 'Map id\n' + Math.random(),
// Object.prototype.toString.call()
get_object_type = library_namespace.get_object_type,
// private operator, access/pass keys.
// ** WARNING:
// Should be Array (see forEach).
// 只要是 object,會以 reference 傳遞,可以 "===" 判斷即可。
OP_HASH = [],
//
OP_SIZE = [],
//
OP_KEYS = [], OP_VALUES = [], OP_ENTRIES = [],
// 取得裸 Object (naked Object) 與屬性判別函數。
new_hash_set = function new_hash_set() {
var hash_map = Object.create(null);
// [ hash_map, has_hash() ]
return [ hash_map, function(key) {
return key in hash_map;
} ];
};
// 測試可否用 \0 作為 id。
(function() {
var o = {}, a = [], t = {}, id = '\0' + Map_id;
o[id] = a[id] = t;
if (o[id] === t && a[id] === t)
Map_id = id;
})();
try {
new_hash_set();
} catch (e) {
// 使用較原始的方法。
new_hash_set = function() {
var hash_map = {};
return [ hash_map,
// has_hash()
Object.hasOwn ? function(key) {
return Object.hasOwn(hash_map, key);
} : Object.prototype.hasOwnProperty
//
? function(key) {
return Object.prototype.hasOwnProperty
//
.call(hash_map, key);
} : Object.prototype ? function(key) {
return key in hash_map
//
&& hash_map[key] !== Object.prototype[key];
} : function(key) {
return key in hash_map;
} ];
};
}
/**
* 判別是否為 −0。
*
* @see Signed zero, [译]JavaScript中的两个0 -
* 紫云飞 - 博客园
*/
var is_negative_zero = Object.is && !Object.is(+0, -0)
// Object.is() 採用 SameValue Algorithm。
? function(value) {
return Object.is(value, -0);
}
// 相容方法。
: function(value) {
return value === -0 && 1 / value === -Infinity;
};
library_namespace.is_negative_zero = is_negative_zero;
/**
* 鍵值對。
*
* TODO: comparator
*
* @constructor
*
* @see Map - JavaScript | MDN
*/
function Map(iterable, comparator) {
if (this === null || this === undefined
|| this === library_namespace.env.global) {
// 採用 Map(),而非 new 呼叫。
// called as a function rather than as a
// constructor.
return new Map(iterable, comparator);
}
var size,
// {Object}map hash to key (object) Array.
//
// get hash map of (
// hash → [value/object 1, value/object 2, ..]
// )
hash_map,
// has this hash.
has_hash,
// {Object}value objects 的 id hash map。可用來維持插入順序。
// value_of_id[
// id: {String}hash + "_" + {ℕ⁰:Natural+0}index
// ] = value.
//
// 在 Set 中 value_of_id={ id: key object },
// 因此可以更快的作 forEach()。
value_of_id;
// 快速處理法。
Object.defineProperty(this, 'clear', {
// enumerable : false,
value : function clear() {
// reset.
var set = new_hash_set();
hash_map = set[0];
has_hash = set[1];
value_of_id = Object.create(null);
size = 0;
}
});
// 初始化。
this.clear();
Object.defineProperty(this, 'size', {
// enumerable : false,
// configurable : false,
get : function() {
return size;
},
set : function(v) {
if (Array.isArray(v) && v[1] === OP_SIZE)
size = v[0];
}
});
// 假扮的 interface(仮面):
// 借用標準 method 介面,
// 若是傳入 OP_*,則表示為 private method,作出內部特殊操作。
// 否則作出正常表現。
//
// 使用這方法以盡量減少多餘的 property 出現,
// 並維持 private method 之私密特性。
Object.defineProperty(this, 'values', {
// enumerable : false,
value : function values() {
// arguments[0]: 隱藏版 argument。
if (arguments[0] === OP_ENTRIES)
// 傳入 OP_*,則表示為 private method。
// 回傳 private property 以便操作。
return [ hash_map, value_of_id ];
if (arguments[0] === OP_VALUES)
return create_list_iterator(value_of_id,
'value', true);
// 作出正常表現。
return create_list_iterator(value_of_id,
'value');
}
});
// 為了能初始化 iterable,因此將設定函數放在 constructor 中。
Object.defineProperty(this, 'has', {
// enumerable : false,
value : function has(key) {
// arguments[1]: 隱藏版 argument。
return arguments[1] === OP_HASH ?
// 傳入 OP_HASH,則表示為 private method,回傳 has_hash()。
has_hash(key) :
// 作出正常表現。
!!hash_of_key.call(this, key);
}
});
if (iterable)
// initialization. 為 Map 所作的初始化工作。
try {
if (Array.isArray(iterable)) {
// "key+value"
for (var index = 0; index < iterable.length; index++) {
var entry = iterable[index];
this.set(entry[0], entry[1]);
}
} else if (iterable.forEach) {
var _this = this;
iterable.forEach(function(v, k) {
_this.set(k, v);
});
} else {
throw 1;
for ( var k in iterable)
this.set(k, iterable[k]);
}
} catch (e) {
if (false) {
library_namespace.info('' + this.set);
library_namespace.info(Array
.isArray(iterable) ? 'isArray'
: iterable.forEach ? 'forEach'
: 'throw');
library_namespace.error(e);
}
throw new TypeError(
//
'Map: Input value is not iterable: '
//
+ (library_namespace.is_Object(iterable)
//
? library_namespace.is_type(iterable)
//
: iterable));
}
}
/**
* collections 之核心功能:get hash of specified value/object.
* 所有對 hash_map 之變更皆由此函式負責。
*
* 本函式僅能以下列方式呼叫:
*
* hash_of_key.call(this, ..)
*
*
* TODO: hash collision DoS
*
* @param key
* key object
* @param {Integer}operator
* 操作
* @param value
* value object
*
* @private
*
* @returns [ hash, index ]
*/
function hash_of_key(key, operator, value) {
if (arguments.length === 0)
return;
var hash = this.values(OP_ENTRIES), type = typeof key, map = this,
//
hash_map = hash[0], value_of_id = hash[1],
//
add_size = has_native_Object_defineProperty ?
// set inner 'size' property
function(v) {
map.size = [ map.size + v, OP_SIZE ];
} : function(v) {
map.size += v;
},
//
add_value = function(no_size_change) {
value_of_id[hash + '_' + index] = value;
if (!no_size_change)
add_size(1);
},
//
delete_one = function() {
delete value_of_id[hash + '_' + index];
add_size(-1);
};
// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/typeof
switch (type) {
case 'string':
hash = key;
break;
case 'number':
if (is_negative_zero(key)) {
// 直接避免紛爭。
//
// 實際應使用 SameValue Algorithm。
// 因為本處實作採用 Array.prototype.indexOf(),
// 而 indexOf() 採用嚴格相等運算符(===);
// 實際上應該處理所有 "===" 判斷為相等,
// 但以 SameValue Algorithm 並不相等的值。
hash = '-0';
break;
}
case 'boolean':
case 'undefined':
hash = String(key);
break;
// 對以上純量,無法判別個別 instance。
case 'function':
if (library_namespace.is_Function(key)) {
// 若設定 function.toString,僅能得到 key.toString()。
hash = String(key);
// 盡量增加 hash 能取得的特徵。
hash = hash.length + '|' + hash;
break;
}
case 'object':
try {
if (!(hash = key[Map_id])) {
// 對於 Object/Arrry,在更改內容的情況下,可能無法得到相同的特徵碼,
// 因此還是加個 id 註記比較保險。
hash = String(Math.random());
Object.defineProperty(key, Map_id, {
// configurable : true,
// writable : false,
// enumerable : false,
value : hash
});
if (hash !== key[Map_id])
throw new Error('無法設定 hash id: .['
+ Map_id + ']');
}
break;
} catch (e) {
// TODO: handle exception
}
// 警告:採用不保險的方法。
if (Array.isArray(key)) {
hash = (2 * key.length < max_hash_length ? key
: key.slice(0, max_hash_length / 2))
.toString();
break;
}
if (library_namespace.is_Object(key)) {
hash = '{';
var i;
for (i in key) {
hash += i + ':' + key[i] + ',';
// 不須過長。
if (hash.length > max_hash_length) {
i = null;
break;
}
}
if (i !== null)
// 已完結的時候,加個 ending mark。
hash += '}';
break;
}
// TODO:
// test DOM, COM object.
// case 'xml':
// case 'date':
default:
try {
hash = get_object_type(key) + key;
} catch (e) {
hash = '[' + type + ']' + key;
}
break;
}
// assert: typeof hash === 'string'
// 正規化 hash。
hash = hash.slice(0, max_hash_length).replace(
/_(\d+)$/, '-$1');
if (library_namespace.is_debug(6)
&& library_namespace.is_WWW())
library_namespace.debug('hash: [' + hash + ']', 0,
'hash_of_key');
if (this.has(hash, OP_HASH)) {
var list = hash_map[hash],
// 實際上應該以 SameValue Algorithm, Object.is() 判斷。
// NaN 等於 NaN, -0 不等於 +0.
index = list.indexOf(key);
if (library_namespace.is_debug(6)
&& library_namespace.is_WWW())
library_namespace.debug('index: [' + index
+ ']', 0, 'hash_of_key');
if (index === NOT_FOUND) {
// 測試是否為本身與本身不相等的特殊情形。
// TODO:
// 偵測 ELEMENT_NODE.isSameNode,
// Array 之深度檢測等。
// incase NaN. 可用 Number.isNaN().
// 但不可用 isNaN(key), 因為 isNaN(非數字) === true.
if (key !== key) {
for (var i = 0, length = list.length; i < length; i++) {
// 若具有所有可偵測的相同特徵(特徵碼相同+本身與本身不相等),
// 則判別為相同。
if (list[i] !== list[i]) {
index = i;
break;
}
}
}
}
if (index === NOT_FOUND) {
if (operator === ADD) {
if (library_namespace.is_debug(5)
&& library_namespace.is_WWW())
library_namespace.debug(
'衝突(collision) : ' + type
+ ' @ hash [' + hash
+ '], index ' + index
+ ' / ' + list.length,
0, 'hash_of_key');
index = list.push(key) - 1;
add_value();
} else
hash = undefined;
} else if (operator === DELETE) {
if (library_namespace.is_debug(6)
&& library_namespace.is_WWW())
library_namespace.debug('remove key: ['
+ hash + ']', 0, 'hash_of_key');
if (list.length < 2)
// assert: list.length ===1 && list[0] ===
// key.
delete hash_map[hash];
else
// assert: list[index] === key.
delete list[index];
delete_one();
return true;
} else if (operator === ADD) {
if (library_namespace.is_debug(6)
&& library_namespace.is_WWW())
library_namespace.debug('modify key: ['
+ hash + ']', 0, 'hash_of_key');
add_value(true);
}
} else if (operator === ADD) {
// add new one.
hash_map[hash] = [ key ];
index = 0;
add_value();
} else
hash = undefined;
return operator === DELETE ? false : hash
&& [ hash, index ];
}
function forEach(callbackfn, thisArg) {
var id, match, key = this.values(OP_ENTRIES), value,
//
hash_map = key[0], value_of_id = key[1],
//
use_call = thisArg !== undefined && thisArg !== null
&& typeof callback.call === 'function',
//
list = Array.isArray(callbackfn)
&& (callbackfn === OP_ENTRIES ? function(v, k) {
list.push([ k, v ]);
} : callbackfn === OP_KEYS && function(v, k) {
list.push(k);
});
if (list)
callbackfn = list, list = [];
for (id in value_of_id) {
match = id.match(/^([\s\S]*)_(\d+)$/);
// assert: match succeed.
key = hash_map[match[1]][match[2] | 0];
value = value_of_id[id];
if (use_call)
callbackfn.call(thisArg, value, key, this);
else
callbackfn(value, key, this);
}
if (list) {
// 這裡可以檢測 size。
// assert: size === list.length
return new Array_Iterator(list, true);
}
}
// public interface of Map.
Object.assign(Map.prototype, {
set : function set(key, value) {
hash_of_key.call(this, key, ADD, value);
},
get : function get(key) {
var hash = hash_of_key.call(this, key);
if (hash)
return this.values(OP_ENTRIES)[1][hash
.join('_')];
},
'delete' : function Map_delete(key) {
return hash_of_key.call(this, key, DELETE);
},
keys : function keys() {
return this.forEach(OP_KEYS);
},
entries : function entries() {
return this.forEach(OP_ENTRIES);
},
forEach : forEach,
toString : function() {
// Object.prototype.toString.call(new Map)
// === "[object Map]"
return '[object Map]';
},
// place holder for Map.prototype.values()
// will reset runtime
values : function() {
}
});
// ---------------------------------------
/**
* 一個不包含任何重複值的有序列表。
*
* NOTE:
* 為了維持插入順序,因此將 Set 作為 Map 之下層 (Set inherits
* Map)。副作用為犧牲(加大了)空間使用量。
*
* @constructor
*/
function Set(iterable, comparator) {
if (this === null || this === undefined
|| this === library_namespace.env.global) {
// 採用 Set(),而非 new 呼叫。
// called as a function rather than as a
// constructor.
return new Set(iterable, comparator);
}
var map = new Map(undefined, comparator);
Object.defineProperty(this, 'size', {
// enumerable : false,
// configurable : false,
get : function() {
return map.size;
},
set : function(v) {
if (Array.isArray(v) && v[1] === OP_SIZE)
map.size = v[0];
}
});
this.values = has_native_Object_defineProperty ?
//
function values() {
// arguments[0]: 隱藏版 argument。
return arguments[0] === OP_VALUES ?
//
map[arguments[1]](arguments[2], arguments[3])
// 作出正常表現。
// 用 values 會比 keys 快些。
: map.values();
}
// 先前過舊的版本。
: function values() {
// arguments[0]: 隱藏版 argument。
if (arguments[0] === OP_VALUES) {
var r = map[arguments[1]](arguments[2],
arguments[3]);
this.size = map.size;
return r;
}
// 作出正常表現。
// 用 values 會比 keys 快些。
return map.values();
};
if (iterable)
// initialization. 為 Set 所作的初始化工作。
try {
if (iterable.forEach) {
iterable.forEach(function(v) {
this.add(v);
}, this);
} else {
for ( var i in iterable)
this.add(iterable[i]);
}
} catch (e) {
throw new TypeError(
//
'Set: Input value is not iterable: '
//
+ (library_namespace.is_Object(iterable)
//
? library_namespace.is_type(iterable)
//
: iterable));
}
}
// public interface of Set.
Object.assign(Set.prototype, {
add : function add(value) {
// 在 Set 中 value_of_id={ id: key object },
// 因此將 value 設成與 key 相同,可以更快的作 forEach()。
return this.values(OP_VALUES, 'set', value, value);
},
// 對於 Map 已有的 function name,不能取相同的名稱。
// 相同名稱的 function 在舊版 IE 會出問題:前面的會被後面的取代。
// 因此無法使用 "function clear()",
// 僅能使用 "function Set_clear()"。
// 餘以此類推。
clear : function Set_clear() {
return this.values(OP_VALUES, 'clear');
},
'delete' : function Set_delete(value) {
return this.values(OP_VALUES, 'delete', value);
},
has : function Set_has(value) {
return this.values(OP_VALUES, 'has', value);
},
entries : function Set_entries() {
var entries = [];
this.values(OP_VALUES, 'values', OP_VALUES)
.forEach(function(value) {
entries.push([ value, value ]);
});
return new Array_Iterator(entries, true);
},
// 在 JScript 10.0.16438 中,兩個 "function forEach()" 宣告,會造成
// Map.prototype.forEach 也被設到 Set.prototype.forEach,但
// Map.prototype.forEach !== Set.prototype.forEach。
forEach : function Set_forEach(callbackfn, thisArg) {
this.values(OP_VALUES, 'values', OP_VALUES)
.forEach(callbackfn, thisArg);
},
toString : function() {
// Object.prototype.toString.call(new Set)
// === "[object Set]"
return '[object Set]';
},
// place holder for Set.prototype.values()
// will reset runtime
values : function() {
}
});
// ---------------------------------------
// export.
var global = library_namespace.env.global;
(global.Set = library_namespace.Set = Set)[KEY_not_native] = true;
(global.Map = library_namespace.Map = Map)[KEY_not_native] = true;
if (false && Array.from === Array_from) {
library_namespace
.debug('做個標記,設定 Set.prototype[@@iterator]。');
Set.prototype['@@iterator'] = 'values';
}
is_Set = function(value) {
// value.__proto__ === Set.prototype
return value && value.constructor === Set;
};
is_Map = function(value) {
// value.__proto__ === Map.prototype
return value && value.constructor === Map;
};
})();
// ---------------------------------------------------------------------//
// 現在只有 mozilla firefox 20 會執行到這。
else if (library_namespace.env.has_for_of)
// 現在只有 mozilla firefox 20 會需要這項補強。
(function() {
function collection_clear() {
if (this.size > 0) {
var list = [];
this.forEach(function(v, k) {
list.push(k);
});
list.forEach(function(k) {
this['delete'](k);
}, this);
// last check.
if (this.size > 0)
library_namespace.warn(
//
'collection_clear: 仍有元素存在於 collection 中!');
}
}
try {
// 確定有 Set。
var s = new Set(), a = [], Set_forEach;
if (!s.forEach) {
// shim (backward compatible) for
// Set.prototype.forEach().
// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Set
// use eval() because for(..of..) is not supported
// in current (2013) environment.
eval('Set_forEach=function(callback,thisArg){var i,use_call=thisArg!==undefined&&thisArg!==null&&typeof callback.call==="function";for(i of this)if(use_call)callback.call(thisArg,i,i,this);else callback(i,i,this);}');
s.add('2 ');
s.add(1);
Set_forEach.call(s, function(i) {
a.push(i);
});
if (a.join('|') === '2 |1') {
library_namespace
.debug('採用 Set_forEach() 作為 Set.prototype.forEach()。');
Object.defineProperty(Set.prototype, 'forEach',
{
// enumerable : false,
value : Set_forEach
});
}
}
if (!Set.prototype.clear)
Object.defineProperty(Set.prototype, 'clear', {
// enumerable : false,
value : collection_clear
});
if (typeof Set.prototype.size === 'function') {
var Set_size = Set.prototype.size;
Object.defineProperty(Set.prototype, 'size', {
// enumerable : false,
get : Set_size
});
}
} catch (e) {
}
try {
// 確定有 Map。
var m = new Map(), a = [], Map_forEach;
if (!m.forEach) {
// use eval() because for(..of..) is not supported
// in current (2013) environment.
eval('Map_forEach=function(callback,thisArg){var k,v,use_call=thisArg!==undefined&&thisArg!==null&&typeof callback.call==="function";for([k,v] of this)if(use_call)callback.call(thisArg,v,k,this);else callback(v,k,this);}');
m.set('1 ', 2);
m.set(' 3', 4);
Map_forEach.call(m, function(v, k) {
a.push(k, v);
});
if (a.join('|') === '1 |2| 3|4') {
library_namespace
.debug('採用 Map_forEach() 作為 Map.prototype.forEach()。');
Object.defineProperty(Map.prototype, 'forEach',
{
// enumerable : false,
value : Map_forEach
});
}
}
if (!Map.prototype.clear)
Object.defineProperty(Map.prototype, 'clear', {
// enumerable : false,
value : collection_clear
});
if (typeof Map.prototype.size === 'function') {
var Map_size = Map.prototype.size;
Object.defineProperty(Map.prototype, 'size', {
// enumerable : false,
get : Map_size
});
}
} catch (e) {
}
// TODO: .size
})();
}
// IE11 無法使用 new Set([ , ]),但 firefox 23 可以。
var Set_from_Array = new Set([ 1, 2 ]);
library_namespace.Set_from_Array = Set_from_Array =
//
Set_from_Array.size === 2 ? function(array) {
return new Set(array);
} : function(array) {
var set = new Set;
if (typeof array.forEach === 'function')
array.forEach(function(value) {
set.add(value);
});
else
set.add(array);
return set;
};
// e.g., IE 11 has no Set.prototype.values()
if (typeof Set.prototype.values !== 'function'
//
&& typeof Set.prototype.forEach === 'function')
Set.prototype.values = function Set_prototype_values() {
var values = [];
this.forEach(function(v) {
values.push(v);
});
return new Array_Iterator(values, true);
};
library_namespace.is_Set = is_Set;
library_namespace.is_Map = is_Map;
// ---------------------------------------------------------------------//
var
// 計數用。
CONST_COUNT = 0,
// const: 程序處理方法。
// {Integer} PARALLEL (平行處理), SEQUENTIAL (循序/依序執行, in order).
PARALLEL = 0, SEQUENTIAL = 1,
// const: major status of object.
// UNKNOWN 不可為 undefined,會造成無法判別。
UNKNOWN = 'unknown',
// LOADING, INCLUDING, reloading, reincluding.
// WORKING = ++CONST_COUNT,
// 主要的兩種處理結果。
// IS_OK = ++CONST_COUNT, IS_FAILED = ++CONST_COUNT,
//
PROCESSED = ++CONST_COUNT,
// const: 詳細 status/detailed information of object.
// LOADING = ++CONST_COUNT, LOAD_FAILED = ++CONST_COUNT,
//
INCLUDING = ++CONST_COUNT, INCLUDE_FAILED = ++CONST_COUNT;
// included: URL 已嵌入/掛上/named source code registered/函數已執行。
// INCLUDED = ++CONST_COUNT;
// ---------------------------------------------------------------------//
/**
* 程式碼主檔內建相依性(dependency chain)和關聯性處理 class。
*
* @example
// More examples: see /_test suite/test.js
*
*
*/
function dependency_chain() {
this.relations = new Map;
}
/**
* 取得指定 item 之 relation 結構。
* TODO: 無此 item 時,預設不順便加入此 item。
*
* @param [item]
* 指定 item。未指定 item 時,回傳所有 item 之 Array。
* @param {Boolean}[no_add]
* 無此 item 時,是否不順便加入此 item。
* @returns 指定 item 之 relation 結構。
*/
function dependency_chain_get(item, no_add) {
var relations = this.relations, relation;
if (arguments.length === 0)
// 未指定 item 時,回傳所有 items。
return relations.keys();
if (!(relation = relations.get(item)) && !no_add)
// initialization. 為 item 所作的初始化工作。
relations.set(item, relation = {
previous : new Set,
next : new Set,
// fallback
item : item
});
return relation;
}
/**
* 將 previous → next (independent → dependent) 之相依性添加進 dependency chain。
*
* @param previous
* previous(prior) item.
* @param next
* next item.
* @returns {dependency_chain} dependency chain
*/
function dependency_chain_add(previous, next) {
if (0 < arguments.length
//
&& (previous !== undefined || (previous = next) !== undefined))
if (previous === next || next === undefined) {
// initialization. 為 previous 所作的初始化工作。
this.get(previous);
} else {
// 維護雙向指標。
this.get(previous).next.add(next);
this.get(next).previous.add(previous);
}
return this;
}
/**
* 自 dependency chain 中,刪除此 item。
*
* @param item
* 指定欲刪除之 item。
* @returns {Boolean} item 是否存在,且成功刪除。
*/
function dependency_chain_delete(item) {
var relation, relations;
if (!(relation = (relations = this.relations).get(item)))
// 注意:此處與 ECMAScript [[Delete]] (P) 之預設行為不同!
return false;
if (library_namespace.is_debug() && relation.previous.size > 0)
library_namespace.warn('刪除一個還有 ' + relation.previous.size
+ ' 個 previous 的元素。循環相依?');
// 維護雙向指標。
relation.previous.forEach(function(previous) {
var next_of_previous = relations.get(previous).next;
// 維持/傳遞相依關聯性。
relation.next.forEach(function(next) {
// 維護雙向指標。
// assert: previous, next 存在 relations 中。
// 因此採取下列方法取代 this.add(previous, next);
以加快速度。
next_of_previous.add(next);
relations.get(next).previous.add(previous);
});
// 一一去除 previous 的關聯性。
next_of_previous['delete'](item);
});
// 一一去除 next 的關聯性。
relation.next.forEach(function(next) {
relations.get(next).previous['delete'](item);
});
// delete self.
relations['delete'](item);
return true;
}
/**
* 取得需求鏈中獨立之元素 (get the independent one),
* 或者起碼是循環相依(循環參照, circular dependencies)的一員。
*
* @param [item]
* 指定取得此 item 之上游。
*
* @returns 獨立之元素/節點,或者起碼是循環相依的一員。
*
* @see Loop dependence analysis
*/
function dependency_chain_independent(item) {
var relations = this.relations, no_independent;
if (relations.size > 0)
try {
if (!arguments.length) {
library_namespace.debug('自 ' + relations.size
+ ' 個元素中,隨便取得一個沒 previous 的元素。', 5,
'dependency_chain.independent');
// 用 for .. of 會更好。
relations.forEach(function(declaration, _item) {
library_namespace.debug('item [' + _item + ']', 6,
'dependency_chain.independent');
item = _item;
if (declaration.previous.size === 0)
throw 1;
});
if (library_namespace.is_debug())
library_namespace
.warn('dependency_chain.independent: 沒有獨立之元素!');
no_independent = true;
}
var
// 已經處理過的 item Set。
chain = new Set,
// 當前要處理的 item Set。
current,
// 下一個要處理的 item Set。
next = new Set;
next.add(item);
item = undefined;
while ((current = next).size > 0) {
next = new Set;
// 針對 item 挑一個沒 previous 的元素。
current.forEach(function(_item) {
var declaration = relations.get(_item);
if (declaration.previous.size === 0) {
item = _item;
throw 2;
}
if (!chain.has(_item))
chain.add(_item);
else {
// 否則最起碼挑一個在 dependency chain 中的元素。
item = _item;
if (no_independent)
throw 3;
}
// 把所有未處理過的 previous 排入 next 排程。
// 遍歷 previous,找出獨立之元素。
declaration.previous.forEach(function(previous) {
// assert: previous !== _item
if (!chain.has(previous))
next.add(previous);
else if (no_independent) {
item = previous;
throw 4;
}
});
});
}
} catch (e) {
if (isNaN(e)) {
library_namespace.warn('dependency_chain.independent: '
+ e.message);
library_namespace.error(e);
}
}
return item;
}
// public interface of dependency_chain.
Object.assign(dependency_chain.prototype, {
get : dependency_chain_get,
add : dependency_chain_add,
// quote 'delete' for "必須要有識別項" @ IE8.
'delete' : dependency_chain_delete,
independent : dependency_chain_independent
});
// export.
library_namespace.dependency_chain = dependency_chain;
// ---------------------------------------------------------------------//
// named source code declaration / module controller 之處理。
/**
* named source code declaration.
* named_code = { id : source code declaration }.
* assert: is_controller(named_code 之元素) === true.
*
* cache 已經 include 了哪些 resource/JavaScript 檔(存有其路徑)/class(函式)。
* 預防重複載入。
*
* note:
* named source code/module 定義: 具 id (預設不會重覆載入)、行使特殊指定功能之 source。
* module 特性: 可依名稱自動判別 URL。 預設會搭入 library name-space 中。
*
* @inner
* @ignore
* @type {Object}
*/
var named_code = Object.create(null),
// modules_loaded 獲得的是依相依性先後,不會有 require 的順序。
modules_loaded = new Set;
/**
* @example
// Get all modules loaded
Object.values(CeL.get_named_code()).map(declaration => declaration.id);
*/
function get_named_code(id) {
if (!id) {
// TODO: return a duplicate.
return named_code;
}
return named_code[id];
}
// const modules_loaded = CeL.get_modules_loaded();
function get_modules_loaded() {
return Array.from(modules_loaded);
}
// export.
library_namespace.get_named_code = get_named_code;
library_namespace.get_modules_loaded = get_modules_loaded;
/**
* 在 module 中稍後求值,僅對 function 有效。
* TODO: use get method. TODO: replace 變數.
*/
function load_later() {
var name = String(this);
if (library_namespace.is_debug()) {
library_namespace.debug('load_later: 演算 [' + name + ']。', 5,
'load_later');
if (name !== this)
library_namespace.warn('變數名與 "this" 不同!');
}
var method;
try {
method = library_namespace.value_of(name);
if (!method || (typeof method !== 'function' &&
// JScript 中,有些函式可能為object。
typeof method !== 'object'))
// 非函式,為常量?
return method;
return method.apply(
// 處理 bind。
library_namespace.value_of(name.replace(/\.[^.]+$/, '')),
arguments);
} catch (e) {
library_namespace.error(e);
}
if (!method) {
library_namespace.warn('load_later: 無法演算 [' + name + ']!');
return method;
}
if (library_namespace.is_debug())
library_namespace
.warn('load_later: 可能是特殊 object,因無法 bind 而出錯。嘗試跳過 bind。');
var length = arguments.length;
try {
if (length > 0)
return method.apply(null, arguments);
} catch (e) {
if (library_namespace.is_debug())
library_namespace.error(e);
}
if (library_namespace.is_debug())
library_namespace
.warn('load_later: 可能是特殊 object,因無法 apply 而出錯。嘗試跳過 apply。');
try {
switch (length) {
case 0:
return method();
case 1:
return method(arguments[0]);
case 2:
return method(arguments[0], arguments[1]);
case 3:
return method(arguments[0], arguments[1], arguments[2]);
case 4:
return method(arguments[0], arguments[1], arguments[2],
arguments[3]);
default:
if (length > 5)
library_namespace.warn('load_later: 共指派了 ' + length
+ ' 個 arguments,過長。將僅取前 5 個。');
return method(arguments[0], arguments[1], arguments[2],
arguments[3], arguments[4]);
}
} catch (e) {
library_namespace.error(e);
}
library_namespace.warn('load_later: 無法執行 [' + name
+ ']!跳過執行動作,直接回傳之。');
return method;
}
/**
* Get named source code declaration.
* 注意:亦包括 URL/path!!見 check_and_run_normalize()。
* 對相同 id 會傳回相同之 declaration。
*
* @param {String}name
* source code (module) name/id, URL/path, variable name.
* @param {Object}[setup_declaration]
* source code 之設定選項。
*
* @return {Object} named source code declaration.
*/
function get_named(name, setup_declaration) {
if (typeof name !== 'string' || !name)
return name;
// module declaration/controller.
var declaration, id,
// 看看是否為 named source code。
is_module = library_namespace.match_module_name_pattern(name);
// TODO:
// 就算輸入 module path 亦可自動判別出為 module,而非普通 resource。
// 先嘗試是否為變數/數值名。
id = library_namespace.value_of(name);
if (id !== undefined
// 若存在此值,且並未載入過(載入過的皆應該有資料),才判別為變數/數值名。
&& (!(declaration = library_namespace.to_module_name(name)) || !(declaration in named_code))) {
library_namespace.is_debug('treat [' + name
+ '] as variable name.', 2, 'get_named');
return id;
}
// 再看看是否為 named source code。
if (is_module) {
// 正規化 name。登記 full module name。e.g., 'CeL.data.code'.
id = declaration || library_namespace.to_module_name(name);
} else if (!/^(?:[a-z\-]+:[\/\\]{2}|(?:[.]{2}[\/\\])+)?(?:[^.]+(?:\.[^.]+)*[\/\\])*[^.]+(?:\.[^.]+)*$/i
// 最後看是否為 resource。
.test(id = library_namespace.simplify_path(name))
&& library_namespace.is_debug())
library_namespace.warn('get_named: 輸入可能有誤的 URL/path: [' + id
+ ']');
if (!(declaration = named_code[id])) {
if (!is_module
|| !(declaration = named_code[library_namespace
.get_module_path(id)])) {
/**
* initialization. 為 declaration 所作的初始化工作。
* 因為 URL 可能也具有 named code 功能,因此一視同仁都設定 full function。
*/
declaration = named_code[id] = {
id : id,
callback : new Set,
error_handler : new Set,
load_later : load_later,
base : library_namespace,
r : function require_variable(variable_name) {
// require variable without eval()
if (variable_name in declaration.variable_hash) {
variable_name = declaration.variable_hash[variable_name];
} else {
library_namespace
.warn('require_variable: unregistered variable ['
+ variable_name
+ '] @ module [' + id + '].');
}
return library_namespace.value_of(variable_name);
}
};
/**
* note:
* "use" 是 JScript.NET 的保留字。或可考慮 "requires"。
* use -> using because of 'use' is a keyword of JScript.
*/
// declaration.use = use_function;
if (is_module)
// 判別 URL 並預先登記。但先不處理。
named_code[library_namespace.get_module_path(id)] = declaration;
}
if (is_module) {
library_namespace.debug('treat resource [' + name
+ '] as module.', 5, 'get_named');
// declaration.module = id;
declaration.module_name = name;
// 若是先 call URL,再 call module,這時需要補充登記。
if (!(id in named_code))
named_code[id] = declaration;
} else {
library_namespace.debug('treat resource [' + name
+ '] as URL/path. 登記 [' + id + ']', 5, 'get_named');
declaration.URL = id;
}
}
if (false && declaration.module_name
&& declaration.module_name !== declaration.id) {
id = declaration.id = declaration.module_name;
}
if (library_namespace.is_Object(setup_declaration) &&
// 已載入過則 pass。
(!declaration.included || declaration.force)) {
library_namespace.debug(
'included' in declaration ? 'named source code [' + id
+ '] 已經載入過,卻仍然要求再度設定細項。' : '設定 [' + id
+ '] 之 source code 等 options。', 2, 'get_named');
var setup_callback = function(name) {
var i = setup_declaration[name];
// TODO: 這種判斷法不好。
if (i) {
if (typeof i === 'function'
&& typeof i.forEach !== 'function')
i = [ i ];
try {
if (i && typeof i.forEach === 'function') {
// 初始設定函式本身定義的 callback 應該先執行。
// i = new Set(i);
i = Set_from_Array(i);
if (i.size > 0) {
library_namespace.debug('[' + id
+ '] 初始設定函式本身定義了 ' + i.size + ' 個 '
+ name + '。', 2, 'get_named');
declaration[name]
.forEach(function(callback) {
i.add(callback);
});
declaration[name] = i;
}
}
} catch (e) {
// TODO: handle exception
}
}
};
// 需要特別做處理的設定。
setup_callback('callback');
setup_callback('error_handler');
// .finish 會直接設定,不經特別處理!
if (typeof setup_declaration.extend_to === 'object'
|| typeof setup_declaration.extend_to === 'function')
declaration.extend_to = setup_declaration.extend_to;
// 將 setup_declaration 所有 key of named_code_declaration 之屬性 copy
// / overwrite 到 declaration。
library_namespace.set_method(declaration, setup_declaration,
function(key) {
return !(key in named_code_declaration);
}, {
configurable : true,
writable : true
});
}
return declaration;
}
// {String|Array}name
function is_included_assertion(name, assertion) {
if (assertion)
throw typeof assertion === 'string' ? assertion : new Error(
'Please include module [' + name + '] first!');
return false;
}
/**
* 判斷 module 是否已經成功載入。
*
* TODO
* 以及檢測是否破損。
* prefix.
*
* @param {String|Array}name
* resource/module name || name list
* @param {Boolean|String}[assertion]
* throw the assertion if NOT included.
*
* @returns {Boolean} 所指定 module 是否已經全部成功載入。
* true: 已經成功載入。
* false: 載入失敗。
* @returns undefined 尚未載入。
*/
function is_included(name, assertion) {
if (Array.isArray(name)) {
var i = 0, l = name.length, yet_included = [];
for (; i < l; i++)
if (!is_included(name[i]))
yet_included.push(name[i]);
if (yet_included.length > 0)
return is_included_assertion(yet_included, assertion);
return true;
}
if (is_controller(name) || is_controller(name = get_named(name)))
return name.included;
return is_included_assertion(name, assertion);
}
// export.
library_namespace.is_included = is_included;
/**
* 解析 dependency list,以獲得所需之 URL/path/module/variable name。
*
* note: URL paths 請在 code 中載入。
*
* @param {controller}declaration
*
* @returns {Array|Object} dependency sequence
* @returns {controller}declaration
*/
function parse_require(declaration) {
/** {Array|String}dependency list */
var code_required = typeof declaration.require === 'function'
// WARNING: {Function}declaration.require必須能獨立執行,不能有其他依賴。
// 並且在單次執行中,重複call時必須回傳相同的結果。
// 一般來說,應該是為了依照執行環境includes相同API之不同實作時使用。
// e.g.,
// .write_file()在不同platform有不同實作方法,但對caller應該只需要includes同一library。
? declaration.require(library_namespace) : declaration.require;
if (false) {
// TODO: 自 declaration.code 擷取出 requires。
var matched, pattern = /=\s*this\s*\.\s*r\s*\(\s*["']\s*([^()"']+)\s*["']\s*\)/g;
while (matched = pattern.exec(declaration.code)) {
code_required.push(matched[1]);
}
}
if (code_required) {
library_namespace.debug('解析 [' + declaration.id
//
+ '] 之 dependency list,以獲得所需之 URL/path/module/variable name: ['
+ code_required + ']。', 5, 'parse_require');
if (typeof code_required === 'string')
code_required = code_required.split('|');
if (Array.isArray(code_required)) {
// 挑出所有需要的 resources,
// 把需要的 variable 填入 variable_hash 中,
// 並去除重複。
var require_resources = Object.create(null),
// required variables.
// variable_hash = {
// variable name : variable full name
// }.
variable_hash = declaration.variable_hash = Object
.create(null);
code_required.forEach(function(variable) {
// [ variable full name, module name, variable name ]
var matched = variable.match(/^(.+)\.([^.]*)$/);
if (matched && library_namespace
//
.match_module_name_pattern(matched[1])) {
// module/variable name?
// 類似 'data.split_String_to_Object' 的形式,為 function。
// 類似 'data.' 的形式,為 module。
if (matched[2])
variable_hash[matched[2]]
//
= library_namespace.to_module_name(
//
matched[1], '.') + '.' + matched[2];
require_resources[matched[1]] = null;
} else {
// URL/path?
require_resources[variable] = null;
}
});
// cache. 作個紀錄。
declaration.require_resources = code_required = [];
for ( var i in require_resources)
code_required.push(i);
// 處理完把待處理清單消掉。
delete declaration.require;
} else {
// TODO: 此處實尚未規範,應不可能執行到。
library_namespace.warn('parse_require: 無法解析 ['
+ declaration.id + '] 之 dependency:['
+ declaration.require + ']!');
}
}
if (code_required && code_required.length > 0) {
var require_now = [];
code_required.forEach(function(item) {
var declaration = get_named(item);
// 確定是否還沒載入,必須 load。還沒載入則放在 require_now 中。
if (is_controller(declaration)
&& !('included' in declaration))
require_now.push(item);
});
if (Array.isArray(require_now) && require_now.length > 0) {
library_namespace.debug('檢查並確認 required module/URL,尚須處理 '
+ require_now.length + ' 項: ['
+ require_now.join('|')
+ ']。', 5, 'parse_require');
// 臨時/後續/後來新增
return [
SEQUENTIAL,
require_now.length === 1 ? require_now[0]
: require_now, declaration ];
}
}
return declaration;
}
// ---------------------------------------------------------------------//
// file loading 之處理。
// cache
var document_head, tag_of_type = Object.create(null), URL_of_tag = Object
.create(null), TO_FINISH = Object.create(null),
// 需要修補 load events on linking elements?
no_sheet_onload = library_namespace.is_WWW(true) && navigator.userAgent,
// external resources tester.
external_RegExp = library_namespace.env.module_name_separator,
// Node.js 有比較特殊的 global scope 處理方法。
is_nodejs = library_namespace.platform.nodejs,
// tag_map[tag name]=[URL attribute name, type/extension list];
tag_map = {
script : [ 'src', 'js' ],
link : [ 'href', 'css' ],
img : [ 'src', 'png|jpg|gif' ]
};
external_RegExp = new RegExp('(?:^|\\' + external_RegExp + ')'
+ library_namespace.env.resources_directory_name + '\\'
+ external_RegExp + '|^(?:' + library_namespace.Class + '\\'
+ external_RegExp + ')?'
+ library_namespace.env.external_directory_name + '\\'
+ external_RegExp);
if (no_sheet_onload)
(function() {
// Safari css link.onload problem:
// Gecko and WebKit don't support the onload
// event on link nodes.
// http://www.zachleat.com/web/load-css-dynamically/
// http://www.phpied.com/when-is-a-stylesheet-really-loaded/
// http://stackoverflow.com/questions/2635814/javascript-capturing-load-event-on-link
no_sheet_onload = no_sheet_onload.toLowerCase();
// move from 'interact.DOM'.
var is_Safari = no_sheet_onload.indexOf('safari') !== NOT_FOUND
&& no_sheet_onload.indexOf('chrome') === NOT_FOUND
&& no_sheet_onload.indexOf('chromium') === NOT_FOUND,
//
is_old_Firefox = no_sheet_onload.match(/ Firefox\/(\d+)/i);
if (is_old_Firefox)
is_old_Firefox = (is_old_Firefox[1] | 0) < 9;
no_sheet_onload = is_Safari || is_old_Firefox;
library_namespace.debug(
'看似需要修補 load events on linking elements.', 5);
})();
// TODO: watchdog for link.onload
// function link_watchdog() {}
function all_requires_loaded(declaration) {
var require_resources = declaration.require_resources;
return !Array.isArray(require_resources)
//
|| require_resources.every(function(module_name) {
var item = get_named(module_name);
return item && item.included;
});
}
/**
* 載入 named source code(具名程式碼: module/URL)。
* Include / requires specified module.
*
*
* 會先嘗試使用 .get_file(),以 XMLHttpRequest
* 同時依序(synchronously,會掛住,直至收到回應才回傳)的方式依序取得、載入 module。
*
* 若因為瀏覽器安全策略(browser 安全性設定, e.g., same origin policy)等問題,無法以
* XMLHttpRequest 取得、循序載入時,則會以異序(asynchronously,不同時)的方式並行載入 module。
* 因為 module 尚未載入,在此階段尚無法判別此 module 所需之 dependency list。
*
*
* TODO:
* unload module.
* test: 若兩函數同時 require 相同 path,可能造成其中一個通過,一個未載入?
* for JSONP
*
* @param {String|Object}item
* source code (module/URL/path) name/id.
* @param {Object}[options]
* load options.
* @param {Function}[caller]
* 當以異序(asynchronously,不同時)的方式並行載入 module 時,將排入此 caller
* 作為回調/回撥函式。
*
* @returns {Number} status.
* PROCESSED: done.
* INCLUDE_FAILED: error occurred. fault.
* INCLUDING: loading asynchronously,
* 以異序(asynchronously,不同時)的方式並行載入(in parallel with)。
*/
function load_named(item, options, caller) {
var id = typeof item === 'string' ? item : is_controller(item)
&& item.id,
//
force = is_controller(item) && item.force,
//
declaration = id && named_code[id];
if (!id || !is_controller(declaration)) {
// 內部 bug?
library_namespace.error('load_named: 沒有 [' + id + '] 的資料!');
return PROCESSED;
}
// id 正規化(normalization)處理。
id = declaration.id;
// 預先定義/正規化,避免麻煩。
if (!library_namespace.is_Object(options))
options = Object.create(null);
/**
* need waiting callback / handler: .finish() 回傳此值會使其中的 .run() 執行到了
* waiting 之後才繼續載入其他組件。
*/
function waiting() {
return load_named(item, {
finish_only : TO_FINISH
}, caller);
}
function run_callback(name) {
var callback = declaration[name], args, need_waiting = [];
if (callback) {
// 因為不能保證 callback 之型態,可能在 module 中被竄改過,
// 因此需要預先處理。
if (typeof callback === 'function'
&& typeof callback.forEach !== 'function')
callback = [ callback ];
if (Array.isArray(callback)) {
// callback = new Set(callback);
callback = Set_from_Array(callback);
declaration[name] = new Set;
}
// TODO: assert: callback 為 Set。
if (callback.size > 0
// && typeof callback.forEach === 'function'
) {
// 獲利了結,出清。
library_namespace.debug('繼續完成 ' + callback.size
+ ' 個所有原先 ' + name
+ ' queue 中之執行緒,或是 named source code 所添加之函數。',
5, 'load_named.run_callback');
// 作 cache。
// 需預防 arguments 可被更改的情況!
args = Array.prototype.slice.call(arguments, 1);
callback.forEach(library_namespace.env.no_catch
//
? function(callback) {
if (typeof callback === 'function'
&& callback.apply(declaration, args)
//
=== waiting)
// callback 需要 waiting。
need_waiting.push(callback);
} : function(callback) {
try {
// 已經過鑑別。這邊的除了 named source code
// 所添加之函數外,
// 應該都是 {Function}
// check_and_run.run。
// TODO: using setTimeout?
library_namespace.debug('run ' + name + ' of ['
+ id + ']: [' + callback + ']', 5,
'load_named.run_callback');
if (typeof callback === 'function'
&& callback.apply(declaration, args)
//
=== waiting)
// callback 需要 waiting。
need_waiting.push(callback);
} catch (e) {
library_namespace.error('執行 [' + id + '] 之 '
+ name + ' 時發生錯誤! ' + e.message);
library_namespace.debug(''
+ ('' + callback).replace(/')
+ '
', 1,
'load_named.run_callback');
}
});
callback.clear();
}
}
// Release memory. 釋放被占用的記憶體. 早點 delete 以釋放記憶體空間/資源。
// assert: declaration.error_handler 為 Set。
if (declaration.error_handler) {
// @ work.hta
// 有可能已經載入,因此 `delete declaration.error_handler;` 了。
declaration.error_handler.clear();
}
if (need_waiting.length > 0) {
need_waiting.forEach(function(cb) {
callback.add(cb);
});
return true;
}
}
if ('finish_only' in options)
options.finish_only = options.finish_only === TO_FINISH;
// 存在 .included 表示已經處理過(無論成功失敗)。
// URL 已嵌入/含入/掛上/module registered/函數已執行。
if (force || !('included' in declaration)) {
if (!options.finish_only && declaration.is_waiting_now
// 在網頁環境插入
*
則 .css 後的 .js 可能執行不到,會被跳過。
*/
var queue = library_namespace.env.script_config;
if (library_namespace.is_Object(queue) && (queue = queue.run))
library_initializer.queue.push(queue);
queue = library_initializer.queue;
// 已處理完畢,destroy & set free。
library_initializer = function() {
library_namespace.log('library_initializer: 已處理完畢。');
};
// use CeL={initializer:function(){}}; as callback
var old_namespace = library_namespace.get_old_namespace(), initializer;
if (library_namespace.is_Object(old_namespace)
&& (initializer = old_namespace.initializer)) {
if (Array.isArray(initializer))
Array.prototype.push.call(queue, initializer);
else
queue.push(initializer);
}
// 處理積存工作。
// export .run().
return (library_namespace.run = normal_run)(queue);
};
library_initializer.queue = [];
if (false) {
console.log('is_WWW: ' + library_namespace.is_WWW()
+ ', document.readyState: ' + document.readyState);
console.log(library_namespace.get_tag_list('script').map(
function(n) {
return n.getAttribute('src')
}));
}
// 需要確定還沒有 DOMContentLoaded
// https://stackoverflow.com/questions/9457891/how-to-detect-if-domcontentloaded-was-fired
if (!library_namespace.is_WWW() || document.readyState === "complete"
|| document.readyState === "loaded"
|| document.readyState === "interactive") {
library_initializer();
} else {
// 先檢查插入的