fcf-framework-core
Version:
Basic functions of the fcf framework 2.0
1,410 lines (1,283 loc) • 288 kB
JavaScript
(function() {
var fcf = typeof global !== 'undefined' && global.fcf ? global.fcf :
typeof window !== 'undefined' && window.fcf ? window.fcf :
{};
fcf.NDetails = fcf.NDetails || {};
fcf.namespaces = fcf.namespaces || {};
if (typeof module !== 'undefined')
module.exports = fcf;
if (typeof global !== 'undefined')
global.fcf = fcf;
if (typeof window !== 'undefined')
window.fcf = fcf;
/// @fn boolean _isServer
/// @brief Determines where the code is executed on the server or on the client
/// @result boolean - Returns true if the code is running on the server side
fcf.isServer = () => {
return _isServer;
}
const _isServer = typeof module === "object" && typeof module.filename !== "undefined";
//////////////////////////////////////////////////////////////////////////////
// SERVER SIDE INCLUDES
//////////////////////////////////////////////////////////////////////////////
let libResolver, libPath, libFS, libUtil, libState, libLogger;
if (_isServer) {
libResolver = require("./NDetails/resolver.js");
libPath = require("path");
libFS = require("fs");
libUtil = require("util")
libInlineInterpreter = require("./NDetails/inlineExecution.js");
libLoad = require("./NDetails/load.js");
libState = require("./NDetails/state.js");
libLogger = require("./NDetails/logger.js");
}
//////////////////////////////////////////////////////////////////////////////
// STRING FUNCTIONS
//////////////////////////////////////////////////////////////////////////////
function _autoParse(a_value) {
if ( typeof a_value == "string" && a_value.length){
let s = 0;
for(; s < a_value.length && a_value.charCodeAt(s) <= 32; ++s);
let l = a_value.length-1;
for(; l >= 0 && a_value.charCodeAt(l) <= 32; --l);
if (
l >= 0 &&
(
!isNaN(a_value) ||
((l - s >= 1) && a_value[s] == "\"" && a_value[l] == "\"") ||
(a_value[s] == "{" && a_value[l] == "}") ||
(a_value[s] == "[" && a_value[l] == "]") ||
((l - s == 3) && a_value[s] == "t" && a_value[s+1] == "r" && a_value[s+2] == "u" && a_value[s+3] == "e" ) ||
((l - s == 4) && a_value[s] == "f" && a_value[s+1] == "a" && a_value[s+2] == "l" && a_value[s+3] == "s" && a_value[s+4] == "e" ) ||
((l - s == 3) && a_value[s] == "n" && a_value[s+1] == "u" && a_value[s+2] == "l" && a_value[s+3] == "l" )
) )
{
try {
let res = JSON.parse(a_value);
return res;
} catch(e) {
return a_value;
}
} else {
return a_value;
}
} else {
return a_value;
}
}
/// @fn string fcf.str(mixed a_data)
/// @brief Converts data to a string
/// @details NaN, undefined and null values are represented as an empty string
/// @param mixed a_data - source data
/// @result string
fcf.str = (a_data, a_fullMode) => {
return typeof a_data == "string" ? a_data :
a_data === undefined ? "" :
a_data === null ? "" :
typeof a_data === "number" && isNaN(a_data) ? "" :
typeof a_data == "object" ? (
a_data instanceof fcf.Exception ? fcf.errorToString(a_data, a_fullMode) :
a_data instanceof Error ? fcf.errorToString(a_data, a_fullMode) :
a_data.sqlMessage && a_data.sqlState && a_data.code ? a_data.sqlMessage :
JSON.stringify(a_data, undefined, 2)
) :
a_data.toString();
}
/// @fn string fcf.escapeQuotes(string a_str, string|[string] a_quote = undefined)
/// @brief Escapes single and double quotes with \
/// @param string a_str - Source string
/// @param string|[string] a_quote = undefined - If the parameter is specified and contains the value
/// of the escaped character or an array of escaped characters,
/// then only the specified character and the \ character are escaped.
/// @result string - String with escaped characters
fcf.escapeQuotes = (a_str, a_quote) => {
let result = "";
if (Array.isArray(a_quote)) {
for (let i = 0; i < a_str.length; ++i) {
let c = a_str[i];
if (c === "\\"){
result += "\\\\";
} else if (a_quote.indexOf(c) != -1){
result += "\\";
result += c;
} else {
result += c;
}
}
} else {
for (let i = 0; i < a_str.length; ++i) {
let c = a_str[i];
if (c === "\\"){
result += "\\\\";
} else if (a_quote && c === a_quote){
result += "\\";
result += a_quote;
} else if (!a_quote && c === "\""){
result += "\\\"";
} else if (!a_quote && c === "'"){
result += "\\'";
} else {
result += c;
}
}
}
return result;
}
/// @fn string|object fcf.unescape(string|object a_data)
/// @brief Performs unescaping of a string or strings in the passed object
/// @param string|object a_data - Source data (an object or a string)
/// result string|object - Unescaped data
fcf.unescape = (a_data) => {
if (typeof a_data == "object" && a_data !== null) {
a_data = fcf.clone(a_data);
if (Array.isArray(a_data)) {
for (let key = 0; key < a_data.length; ++key)
a_data[key] = fcf.unescape(a_data[key]);
} else {
for (let key in a_data)
a_data[key] = fcf.unescape(a_data[key]);
}
return a_data;
} else if (typeof a_data == "string"){
let result = "";
let counter = 0;
for(let i = 0; i < a_data.length; ++i) {
let c = a_data[i];
if (c == "\\") {
++counter;
if (counter%2 == 0)
result += c;
} else {
counter = 0;
result += c;
}
}
return result;
}
}
/// @fn string fcf.replaceAll(string a_str, string a_search, string a_replacement)
/// @brief Performs replacement of all searched substrings in a string
/// @param string a_str - Source string
/// @param string a_search - Search substring
/// @param string a_replacement - replacement
/// @result string - New string
fcf.replaceAll = (a_str, a_search, a_replacement) => {
a_str = fcf.str(a_str);
if (a_str.indexOf(a_search) == -1)
return a_str;
return a_str.split(a_search).join(a_replacement);
}
/// @fn string fcf.decodeHtml(string a_str)
/// @brief Performs decoding of special characters in an HTML string
/// @param string a_str - Source string
/// @result string - Decoding result string
fcf.decodeHtml = (a_str) => {
a_str = fcf.str(a_str);
let zn = "0".charCodeAt(0);
let nn = "9".charCodeAt(0);
a_str = fcf.str(a_str);
let result = "";
for(let i = 0; i < a_str.length; ++i){
let c = a_str[i];
if (c == "&") {
if (a_str[i+1] == "#"){
let code = "";
let p = i+2;
for(; p < a_str.length; ++p) {
let c = a_str[p];
let cn = a_str.charCodeAt(p);
if (cn >= zn && cn <= nn){
code += c;
} else {
if (c != ";"){
--p;
}
break;
}
}
if (code.length){
result += String.fromCharCode(parseInt(code));
i = p;
continue;
}
} else {
let p = i+1;
let inst = "";
for(; p < a_str.length; ++p) {
let c = a_str[p];
if (c == "&"){
--p;
break;
}
inst += c;
if (inst in _decodeHtml_map)
break;
if (inst.length == _decodeHtml_mapMaxLength)
break;
}
i = p;
if (inst in _decodeHtml_map) {
if (a_str[i+1] == ";")
++i;
result += _decodeHtml_map[inst];
} else {
result += "&";
result += inst;
}
continue;
}
result += c;
} else {
result += c;
}
}
return result;
}
const _decodeHtml_map = {
'quot': '"', 'amp': '&', 'apos': '\'', 'lt': '<', 'gt': '>', 'nbsp': '\u00a0', 'iexcl': '¡', 'cent': '¢', 'pound': '£',
'curren': '¤', 'yen': '¥', 'brvbar': '¦', 'sect': '§', 'uml': '¨', 'copy': '©', 'ordf': 'ª', 'laquo': '«', 'not': '¬',
'shy': '\u00ad', 'reg': '®', 'macr': '¯', 'deg': '°', 'plusmn': '±', 'sup2': '²', 'sup3': '³', 'acute': '´', 'micro': 'µ',
'para': '¶', 'middot': '·', 'cedil':'¸', 'sup1':'¹', 'ordm':'º', 'raquo':'»', 'frac14':'¼', 'frac12':'½', 'frac34':'¾',
'iquest':'¿', 'Agrave':'À', 'Aacute':'Á', 'Acirc':'Â', 'Atilde':'Ã', 'Auml':'Ä', 'Aring':'Å', 'AElig':'Æ', 'Ccedil':'Ç',
'Egrave':'È', 'Eacute':'É', 'Ecirc':'Ê', 'Euml':'Ë', 'Igrave':'Ì', 'Iacute':'Í', 'Icirc':'Î', 'Iuml':'Ï', 'ETH':'Ð', 'Ntilde':'Ñ',
'Ograve':'Ò', 'Oacute':'Ó', 'Ocirc':'Ô', 'Otilde':'Õ', 'Ouml':'Ö', 'times':'×', 'Oslash':'Ø', 'Ugrave':'Ù', 'Uacute':'Ú',
'Ucirc':'Û', 'Uuml':'Ü', 'Yacute':'Ý', 'THORN':'Þ', 'szlig':'ß', 'agrave':'à', 'aacute':'á', 'atilde':'ã', 'auml':'ä',
'aring':'å', 'aelig':'æ', 'ccedil':'ç', 'egrave':'è', 'eacute':'é', 'ecirc':'ê', 'euml':'ë', 'igrave':'ì', 'iacute':'í',
'icirc':'î', 'iuml':'ï', 'eth':'ð', 'ntilde':'ñ', 'ograve':'ò', 'oacute':'ó', 'ocirc':'ô', 'otilde':'õ', 'ouml':'ö',
'divide':'÷', 'oslash':'ø', 'ugrave':'ù', 'uacute':'ú', 'ucirc':'û', 'uuml':'ü', 'yacute':'ý', 'thorn':'þ', 'yuml':'ÿ',
'bull':'•', 'infin':'∞', 'permil':'‰', 'sdot':'⋅', 'dagger':'†', 'mdash':'—', 'perp':'⊥', 'par':'∥', 'euro':'€', 'trade':'™',
'alpha':'α', 'beta':'β', 'gamma':'γ', 'delta':'δ', 'epsilon':'ε', 'zeta':'ζ', 'eta':'η', 'theta':'θ', 'iota':'ι', 'kappa':'κ',
'lambda':'λ', 'mu':'μ', 'nu':'ν', 'xi':'ξ', 'omicron':'ο', 'pi':'π', 'rho':'ρ', 'sigma':'σ', 'tau':'τ', 'upsilon':'υ',
'phi':'φ', 'chi':'χ', 'psi':'ψ', 'omega':'ω', 'Alpha':'Α', 'Beta':'Β', 'Gamma':'Γ', 'Delta':'Δ', 'Epsilon':'Ε', 'Zeta':'Ζ',
'Eta':'Η', 'Theta':'Θ', 'Iota':'Ι', 'Kappa':'Κ', 'Lambda':'Λ', 'Mu':'Μ', 'Nu':'Ν', 'Xi':'Ξ', 'Omicron':'Ο', 'Pi':'Π', 'Rho':'Ρ',
'Sigma':'Σ', 'Tau':'Τ', 'Upsilon':'Υ', 'Phi':'Φ', 'Chi':'Χ', 'Psi':'Ψ', 'Omega':'Ω'
};
const _decodeHtml_mapMaxLength = 7;
/// @fn string fcf.encodeHtml(string a_str)
/// @brief Performs encoding of special characters ( " ' > < &) HTML code constructs
/// @param string a_str - Source string
/// @result string - Encoded string
fcf.encodeHtml = (a_str) => {
let result = "";
a_str = fcf.str(a_str);
for(let i = 0; i < a_str.length; ++i) {
let c = a_str[i];
switch(c){
case "<": result += "<"; break;
case ">": result += ">"; break;
case "\"": result += """; break;
case "\'": result += "'"; break;
case "&": result += "&"; break;
default: result += c; break;
}
}
return result;
}
/// @fn string fcf.stripTags(string a_str)
/// @brief Removing HTML tags from a string
/// @param string a_str - Source string
/// @result string - String with tags removed
fcf.stripTags = (a_str) => {
return fcf.str(a_str).replace(_regStripTags, "");
}
const _regStripTags = new RegExp("(<[^>]*>)", "g");
/// @fn string fcf.ltrim(string a_str, string|false|[string|false] a_arr = [false])
/// @brief Removes the given characters from the beginning of a string
/// @param string a_str - Source string
/// @param string|false|[string|false] a_arr = [false]- Array of characters for which deletion will be performed or single string delimiter
/// If the array element is false, characters with code <= 32 are removed
/// @result string - New string
fcf.ltrim = (a_str, a_arr) => {
a_str = fcf.str(a_str);
let pos = _ltrimPos(a_str, a_arr);
return pos != 0 ? a_str.substr(pos) : a_str;
}
/// @fn string fcf.ltrim(string a_str, a_arr = [false])
/// @brief Removes the given characters from the end of a string
/// @param string a_str - Source string
/// @param string|false|[string|false] a_arr = [false]- Array of characters for which deletion will be performed or single string delimiter
/// If the array element is false, characters with code <= 32 are removed
/// @result string - New string
fcf.rtrim = (a_str, a_arr) => {
a_str = fcf.str(a_str);
let pos = _rtrimPos(a_str, a_arr);
return pos != a_str.length ? a_str.substr(0, pos) : a_str;
}
/// @fn string fcf.ltrim(string a_str, a_arr = [false])
/// @brief Removes the given characters from the beginning and end of a string
/// @param string a_str - Source string
/// @param string|false|[string|false] a_arr = [false]- Array of characters for which deletion will be performed or single string delimiter
/// If the array element is false, characters with code <= 32 are removed
/// @result string - New string
fcf.trim = (a_str, a_arr) => {
a_str = fcf.str(a_str);
let posBeg = _ltrimPos(a_str, a_arr);
let posEnd = _rtrimPos(a_str, a_arr);
return posBeg != 0 || posEnd != a_str.length
? a_str.substr(posBeg, posEnd - posBeg)
: a_str;
}
function _rtrimPos(a_str, a_arr) {
if (!a_str.length)
return 0;
if (!Array.isArray(a_arr)) {
a_arr = [a_arr];
}
let pos = a_str.length - 1;
for(; pos >= 0; --pos) {
let found = false;
for(let i = 0; i < a_arr.length; ++i){
if (!a_arr[i]) {
let cn = a_str.charCodeAt(pos);
if (cn >= 0 && cn <= 32){
found = true;
break;
}
} else if (a_str.charAt(pos) == a_arr[i]){
found = true;
break;
}
}
if (!found)
break;
}
return pos+1;
}
function _ltrimPos(a_str, a_arr) {
let pos = 0;
if (!Array.isArray(a_arr)) {
a_arr = [a_arr];
}
for(; pos < a_str.length; ++pos) {
let found = false;
for(let i = 0; i < a_arr.length; ++i){
if (!a_arr[i]) {
let cn = a_str.charCodeAt(pos);
if (cn >= 0 && cn <= 32){
found = true;
break;
}
} else if (a_str.charAt(pos) === a_arr[i]) {
found = true;
break;
}
}
if (!found)
break;
}
return pos;
}
/// @fn string fcf.pad(string a_str, number a_len, string a_fill = " ", string a_align = "left")
/// @brief Pads a string to a given length
/// @param string a_str - Source string
/// @param number a_len - The length to which you want to pad the original string
/// @param string a_fill = " " - The string which will be filled with empty space
/// @param string a_align = "left" - String alignment a_str
/// - "l"|"left" - Alignment is done to the left
/// - "r"|"right" - Alignment is done to the right
/// - "c"|"center" - Alignment is done in the center
/// @result string - Result string
fcf.pad = (a_str, a_len, a_fill, a_align) => {
if (isNaN(a_len))
return a_str;
let fillLen = a_len - a_str.length;
if (fillLen <= 0)
return a_str;
if (!a_fill)
a_fill = " ";
if (typeof a_align !== "string")
a_align = "l";
let leftLen = a_align[0] == "r" ? fillLen :
a_align[0] == "c" ? Math.floor(fillLen / 2) :
0;
let rightLen = a_align[0] == "r" ? 0 :
a_align[0] == "c" ? Math.floor(fillLen / 2) + (fillLen % 2) :
fillLen;
let result = "";
for (let i = 0; i < leftLen; ++i) {
result += a_fill[i%a_fill.length];
}
result += a_str;
for (let i = 0; i < rightLen; ++i) {
result += a_fill[i%a_fill.length];
}
return result;
}
/// @fn string fcf.id(number a_size = 32, boolean a_safeFirstChar = true)
/// @brief Creates a string from random characters in hex format
/// @param number a_size default = 32 - Generated string size
/// @param boolean a_safeFirstChar default = true - If false, then the first character takes
/// values from 0-f. If true, then the first character takes values from a-f.
/// @result string - String with random hex characters
fcf.id = (a_size, a_safeFirstChar) => {
a_size = a_size || 32;
a_safeFirstChar = a_safeFirstChar === undefined ? true : a_safeFirstChar;
let result = "";
for(let i = 0; i < a_size; ++i) {
result += i || !a_safeFirstChar ? (Math.floor(Math.random()*16)).toString(16)
: "abcdef"[(Math.floor(Math.random()*6))];
}
return result;
}
/// @fn string uuid()
/// @brief Creates a UUID string (v4)
/// @result string - UUID string
fcf.uuid = () => {
let res = "";
for(let i = 0; i < 36; ++i) {
if (i == 8 || i == 13 || i == 18 || i == 23) {
res += "-";
} else if (i == 14) {
res += "4";
} else if (i == 19) {
res += ((Math.random() * 16 | 0) & 0x3 | 0x8).toString(16);
} else {
res += (Math.random() * 16 | 0).toString(16);
}
}
return res;
}
/// @fn string fcf.decodeBase64(string a_base64String)
/// @brief Decodes a string from base64 format
/// @param string a_base64String - Source base64 string
/// @result string - Result string
fcf.decodeBase64 = function (a_input) {
function utf8Decode (utftext) {
let string = "";
let i = 0;
let c = 0;
let c1 = 0;
let c2 = 0;
while ( i < utftext.length ) {
c = utftext.charCodeAt(i);
if (c < 128) {
string += String.fromCharCode(c);
i++;
} else if((c > 191) && (c < 224)) {
c2 = utftext.charCodeAt(i+1);
string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
i += 2;
} else {
c2 = utftext.charCodeAt(i+1);
c3 = utftext.charCodeAt(i+2);
string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
i += 3;
}
}
return string;
}
let output = "";
let chr1, chr2, chr3;
let enc1, enc2, enc3, enc4;
let i = 0;
a_input = fcf.str(a_input).replace(/[^A-Za-z0-9\+\/\=]/g, "");
while (i < a_input.length) {
enc1 = _keyBase64.indexOf(a_input.charAt(i++));
enc2 = _keyBase64.indexOf(a_input.charAt(i++));
enc3 = _keyBase64.indexOf(a_input.charAt(i++));
enc4 = _keyBase64.indexOf(a_input.charAt(i++));
chr1 = (enc1 << 2) | (enc2 >> 4);
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
chr3 = ((enc3 & 3) << 6) | enc4;
output = output + String.fromCharCode(chr1);
if (enc3 != 64) {
output = output + String.fromCharCode(chr2);
}
if (enc4 != 64) {
output = output + String.fromCharCode(chr3);
}
}
output = utf8Decode(output);
return output;
}
/// @fn string fcf.encodeBase64(string a_input)
/// @brief Encodes a string in base64 format
/// @param string a_input - Source string
/// @result string - Result base64 string
fcf.encodeBase64 = function (a_input) {
function utf8Encode (a_string) {
a_string = a_string.replace(/\r\n/g,"\n");
let utftext = "";
for (let n = 0; n < a_string.length; n++) {
let c = a_string.charCodeAt(n);
if (c < 128) {
utftext += String.fromCharCode(c);
} else if ((c > 127) && (c < 2048)) {
utftext += String.fromCharCode((c >> 6) | 192);
utftext += String.fromCharCode((c & 63) | 128);
} else {
utftext += String.fromCharCode((c >> 12) | 224);
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
utftext += String.fromCharCode((c & 63) | 128);
}
}
return utftext;
}
let output = "";
let chr1, chr2, chr3, enc1, enc2, enc3, enc4;
let i = 0;
a_input = utf8Encode(fcf.str(a_input));
while (i < a_input.length) {
chr1 = a_input.charCodeAt(i++);
chr2 = a_input.charCodeAt(i++);
chr3 = a_input.charCodeAt(i++);
enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
enc4 = chr3 & 63;
if (isNaN(chr2)) {
enc3 = enc4 = 64;
} else if (isNaN(chr3)) {
enc4 = 64;
}
output = output +
_keyBase64.charAt(enc1) + _keyBase64.charAt(enc2) +
_keyBase64.charAt(enc3) + _keyBase64.charAt(enc4);
}
return output;
}
const _keyBase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
//////////////////////////////////////////////////////////////////////////////
// DATA FUNCTIONS
//////////////////////////////////////////////////////////////////////////////
/// @fn boolean fcf.isObject(mixed a_value)
/// @brief Checks if the argument is an object and not null
/// @param mixed a_value Checked value
/// @result boolean - Returns true if the argument is an object and is not null
fcf.isObject = (a_value) => {
return typeof a_value === "object" && a_value !== null;
}
/// @fn boolean fcf.isIterable(mixed a_value)
/// @brief Checks if an argument is iterable (but not a string)
/// @param mixed a_value Checked value
/// @result boolean - Returns true if the argument iterable
fcf.isIterable = (a_value) => {
return typeof a_value === "object" && a_value !== null
? typeof a_value[Symbol.iterator] === 'function'
: false;
}
/// @fn boolean fcf.isNumbered(mixed a_value)
/// @brief Checks if an argument is numbered (but not a string)
/// @param mixed a_value Checked value
/// @result boolean - Returns true if the argument numbered
fcf.isNumbered = (a_value) => {
if (typeof a_value !== "object" || a_value === null)
return false;
if (typeof a_value[Symbol.iterator] !== 'function' || typeof a_value.length !== "number")
return false;
if (a_value.length > 0) {
return 0 in a_value;
} else {
for(let v of a_value) {
return false;
}
return true;
}
}
/// @var integer fcf.UNDEFINED = 0
/// @brief Nature type of variable. Undefined value
Object.defineProperty(fcf,
"UNDEFINED",
{ value: 0, writable: false });
/// @var integer fcf.NULL = 1
/// @brief Nature type of variable. Null value
Object.defineProperty(fcf,
"NULL",
{ value: 1, writable: false });
/// @var integer fcf.NAN = 2
/// @brief Nature type of variable. NaN value
Object.defineProperty(fcf,
"NAN",
{ value: 2, writable: false });
/// @var integer fcf.BOOLEAN = 3
/// @brief Nature type of variable. Boolean value
Object.defineProperty(fcf,
"BOOLEAN",
{ value: 3, writable: false });
/// @var integer fcf.NUMBER = 4
/// @brief Nature type of variable. Number value
Object.defineProperty(fcf,
"NUMBER",
{ value: 4, writable: false });
/// @var integer fcf.STRING = 5
/// @brief Nature type of variable. String value
Object.defineProperty(fcf,
"STRING",
{ value: 5, writable: false });
/// @var integer fcf.DATE = 6
/// @brief Nature type of variable. Date value
Object.defineProperty(fcf,
"DATE",
{ value: 6, writable: false });
/// @var integer fcf.OBJECT = 7
/// @brief Nature type of variable. Object value (Excluding null and date)
Object.defineProperty(fcf,
"OBJECT",
{ value: 7, writable: false });
/// @var integer fcf.ARRAY = 8
/// @brief Nature type of variable. Array value
Object.defineProperty(fcf,
"ARRAY",
{ value: 8, writable: false });
/// @var integer fcf.ITERABLE = 9
/// @brief Nature type of variable. Iterable object
Object.defineProperty(fcf,
"ITERABLE",
{ value: 9, writable: false });
/// @var integer fcf.NUMBERED = 10
/// @brief Nature type of variable. Numbered object (Excluding string)
Object.defineProperty(fcf,
"NUMBERED",
{ value: 10, writable: false });
/// @fn boolean fcf.isNature(mixed a_value, string|fcf.UNDEFINED..fcf.NUMBERED|[string|fcf.UNDEFINED..fcf.NUMBERED] a_nature, boolean a_softMode = false)
/// @brief Checks if a value matches the nature type
/// @param mixed a_value - Checked value
/// @param string|integer|[string|integer] a_nature - The nature type or an array of nature types.
/// nature can take an integer value or a string value:
/// - fcf.UNDEFINED=0 | "undefined" - Undefined value
/// - fcf.NULL=1 | "null" - Null value
/// - fcf.NAN=2 | "nan" - NaN value
/// - fcf.BOOLEAN=3 | "boolean" - Boolean value
/// - fcf.NUMBER=4 | "number" - Number value
/// - fcf.STRING=5 | "string" - String value
/// - fcf.DATE=6 | "date" - Date value
/// - fcf.OBJECT=7 | "object" - Object value (Excluding null and date)
/// - fcf.ARRAY=8 | "array" - Array value
/// - fcf.ITERABLE=9 | "iterable" - Iterable object (Excluding string)
/// - fcf.NUMBERED=10 | "numbered" - Numbered object (Excluding string)
/// @param boolean a_softMode = false - If it is true when checking a string containing a number or a date
/// for compliance with the fcf.NUMBER or fcf.DATE types, the function will return true
/// @result boolean - Returns true if there is a match with type nature
fcf.isNature = (a_value, a_nature, a_softMode) => {
let l = Array.isArray(a_nature) ? a_nature.length : 1;
for(let i = 0; i < l; ++i) {
let nature = Array.isArray(a_nature) ? a_nature[i] : a_nature;
if (typeof nature == "string") {
nature = nature == "numbered" ? fcf.NUMBERED :
nature == "iterable" ? fcf.ITERABLE :
nature == "array" ? fcf.ARRAY :
nature == "object" ? fcf.OBJECT :
nature == "date" ? fcf.DATE :
nature == "string" ? fcf.STRING :
nature == "number" ? fcf.NUMBER :
nature == "boolean" ? fcf.BOOLEAN :
nature == "nan" ? fcf.NAN :
nature == "null" ? fcf.NULL :
fcf.UNDEFINED;
}
switch(nature) {
case fcf.NUMBERED:
if (fcf.isNumbered(a_value))
return true;
break;
case fcf.ITERABLE:
if (fcf.isIterable(a_value))
return true;
break;
case fcf.ARRAY:
if (Array.isArray(a_value))
return true;
break;
case fcf.OBJECT:
if (typeof a_value == "object" && a_value !== null && !(a_value instanceof Date))
return true;
break;
case fcf.DATE:
if (a_value instanceof Date)
return true;
if (a_softMode && typeof a_value == "string" && !isNaN(new Date(a_value).getTime()))
return true;
break;
case fcf.STRING:
if (typeof a_value === "string")
return true;
break;
case fcf.NUMBER:
if (typeof a_value === "number" && !isNaN(a_value))
return true;
if (a_softMode && !isNaN(a_value) && !isNaN(parseFloat(a_value)) )
return true;
break;
case fcf.BOOLEAN:
if (typeof a_value === "boolean")
return true;
break;
case fcf.NAN:
if (typeof a_value === "number" && isNaN(a_value))
return true;
break;
case fcf.NULL:
if (a_value === null)
return true;
break;
case fcf.UNDEFINED:
if (a_value === undefined)
return true;
break;
}
}
return false;
}
/// @fn boolean fcf.empty(mixed a_object)
/// @brief Checks if the object is empty.
/// The following are considered empty: empty arrays (all empty enumerated objects),
/// fieldless objects, empty strings, and the following values: new Date(NaN), NaN , null, undefined.
/// @param mixed a_object Checked object
/// @result boolean Returns true if the object is empty
fcf.empty = (a_object) => {
if (a_object === undefined || a_object === null) {
return true;
} else if (typeof a_object === "number") {
return isNaN(a_object);
} else if (typeof a_object === "string") {
return a_object === "";
} else if (a_object instanceof Error) {
return false;
} else if (a_object instanceof Date) {
return isNaN(a_object.getTime());
} else if (fcf.isIterable(a_object)) {
for(let v of a_object)
return false;
return true;
} else if (typeof a_object === "object") {
for(var k in a_object)
return false;
return true;
}
return false;
}
/// @fn boolean fcf.has(object a_object, mixed a_key)
/// @brief Checks if an object contains an element with a given key. Also performed for Map and Set objects
/// @param mixed a_object - Checked object
/// @param mixed a_key - Checked key
/// @result boolean - Returns true if the object contains the given key
fcf.has = (a_object, a_key) => {
if (typeof a_object !== "object" || a_object === null) {
return false;
}
if (fcf.isIterable(a_object) && typeof a_object.has == "function") {
return a_object.has(a_key);
}
if (fcf.isNumbered(a_object)) {
a_key = parseInt(a_key);
return a_key >= 0 && a_key < a_object.length;
}
return a_key in a_object;
}
/// @fn mixed fcf.get(object a_object, mixed a_key)
/// @brief Get an element stored in an object by key. Also performed for Map and Set objects
/// @param object a_object - Source object
/// @param mixed a_key - Source key
/// @result mixed - Returns the element stored in the object,
/// if the element is not found in the object, undefined is returned
fcf.get = (a_object, a_key) => {
if (typeof a_object !== "object" || a_object === null) {
return;
}
if (a_object instanceof Set) {
return a_object.has(a_key) ? a_key : undefined;
}
if (a_object instanceof Map) {
return a_object.get(a_key);
}
return a_object[a_key];
}
/// @fn bool fcf.equal(mixed a_left, mixed a_right, boolean a_strict = false)
/// @brief Compares two values for equality
/// The objects being compared can be simple types, arrays, objects, Date, Map and Set
/// When comparing two NaN, the function will return true
/// @param mixed a_left - First comparison value
/// @param mixed a_right - Second comparison value
/// @param boolean a_strict = false - If true, then strict comparison is used for comparison ===
/// @result boolean - Returns true if two values are equal
fcf.equal = (a_left, a_right, a_strict) => {
if (!_equalObject(a_left, a_right, a_strict))
return false;
if (typeof a_left == "object")
return _equalObject(a_right, a_left, a_strict);
return true;
}
function _equalObject(a_left, a_right, a_strict) {
if (Array.isArray(a_left)) {
if (!Array.isArray(a_right))
return false;
if (a_left.length != a_right.length)
return false;
for (let i = 0; i < a_left.length; ++i) {
if (!fcf.equal(a_left[i], a_right[i], a_strict))
return false;
}
} else if (a_left instanceof Date || a_right instanceof Date) {
if (!(a_left instanceof Date) || !(a_right instanceof Date)){
return false;
} else {
if (isNaN(a_left.getTime()) && isNaN(a_right.getTime()))
return true;
return a_left.getTime() == a_right.getTime();
}
} else if (typeof a_left === "object" && a_left !== null ){
if (typeof a_right !== "object" || a_right == null)
return false;
if (Array.isArray(a_right))
return false;
if (a_strict && a_left.constructor != a_right.constructor) {
return false;
}
let fastResult;
fastResult = fcf.each(a_left, (a_key, a_value)=>{
if (a_value !== undefined && !fcf.has(a_right, a_key))
return false;
if (!_equalObject(a_value, fcf.get(a_right, a_key), a_strict))
return false;
}).result();
if (fastResult !== undefined)
return fastResult;
fastResult = fcf.each(a_right, (a_key, a_value)=>{
if (a_value !== undefined && !fcf.has(a_left, a_key)){
return false;
}
}).result();
if (fastResult !== undefined)
return false;
} else {
if (typeof a_left == "number" && isNaN(a_left) && typeof a_right == "number" && isNaN(a_right)){
return true;
}
if (a_strict){
if (a_left !== a_right) {
return false;
}
} else {
if (a_left != a_right) {
return false;
}
}
}
return true;
}
/// @fn integer fcf.compare(mixed a_left, mixed a_right, boolean a_strict = false)
/// @brief Compares two values
/// The objects being compared can be simple types, arrays, objects, Date, Map and Set
/// When comparing two NaN, the function will return 0
/// @param mixed a_left - First comparison value
/// @param mixed a_right - Second comparison value
/// @param boolean a_strict = false - If it is true, then when comparing for equality, strict equality is used ===, if it is false, comparison == is used
/// @result integer - Returns 0 if two values are equal;
/// Returns 1 if a_left > a_right;
/// Returns -1 if a_left < a_right;
fcf.compare = (a_left, a_right, a_strict) => {
let c = _compareObject(a_left, a_right, a_strict);
if (c)
return c;
c = _compareObject(a_right, a_left, a_strict);
return c == 0 ? 0 :
c < 0 ? 1 :
-1;
}
function _compareObject(a_left, a_right, a_strict) {
if (Array.isArray(a_left)) {
if (!Array.isArray(a_right)) {
return 1;
}
if (a_left.length != a_right.length) {
return a_left.length < a_right.length ? -1 : 1;
}
for (let i = 0; i < a_left.length; ++i) {
let c = fcf.compare(a_left[i], a_right[i], a_strict);
if (c) {
return c;
}
}
return 0;
} else if (typeof a_left === "object" && a_left !== null && !(a_left instanceof Date)) {
if (typeof a_right !== "object" || a_right == null) {
return 1;
}
if (Array.isArray(a_right)) {
return -1;
}
if (a_strict){
if (a_left.constructor > a_right.constructor) {
return 1;
} else if (a_left.constructor < a_right.constructor) {
return -1;
}
}
let fastResult;
fastResult = fcf.each(a_left, (a_key, a_value)=>{
if (a_value !== undefined && !fcf.has(a_right, a_key)) {
return 1;
}
let c = fcf.compare(a_value, fcf.get(a_right, a_key), a_strict);
if (c)
return c;
}).result();
if (fastResult)
return fastResult;
fastResult = fcf.each(a_right, (a_key, a_value)=>{
if (a_value !== undefined && !fcf.has(a_left, a_key))
return -1;
}).result();
if (fastResult)
return -1;
return 0;
} else {
if (!a_strict) {
if (a_left === undefined || a_left === null || a_left === 0 || a_left === false) {
if (a_right === undefined || a_right === null || a_right === 0 || a_right === false){
return 0;
} else if (typeof a_right == "string") {
let tr = fcf.trim(a_right);
if (tr == "" || tr == "0")
return 0;
}
}
if (a_right === undefined || a_right === null || a_right === 0 || a_right === false) {
if (a_left === undefined || a_left === null || a_left === 0 || a_left === false){
return 0;
} else if (typeof a_left == "string") {
let tr = fcf.trim(a_left);
if (tr == "" || tr == "0")
return 0;
}
}
}
let t1 = a_left instanceof Date ? "date" : typeof a_left;
let t2 = a_right instanceof Date ? "date" : typeof a_right;
if (!a_strict && (typeof a_left == "number" || typeof a_right == "number")) {
if (typeof a_left != "number") {
a_left = parseFloat(a_left);
t1 = "number";
}
if (isNaN(a_left)) {
t1 = "nan";
};
if (typeof a_right != "number") {
a_right = parseFloat(a_left);
t2 = "number";
}
if (isNaN(a_right)) {
t2 = "nan";
};
}
let tc1 = _compareWeights[t1];
let tc2 = _compareWeights[t2];
if (t1 == "nan" && t2 == "nan") {
return 0;
} else if (!a_strict && a_left == a_right) {
return 0;
} else if (tc1 == tc2){
if (a_left instanceof Date){
a_left = a_left.getTime();
a_right = a_right.getTime();
if (isNaN(a_left) && isNaN(a_right))
return 0;
}
if (a_left == a_right)
return 0;
if (a_left < a_right)
return -1;
return 1;
} else if (tc1 > tc2){
return 1
} else {
return -1;
}
}
}
const _compareWeights = {
"undefined": 0,
"null": 1,
"nan": 2,
"boolean": 3,
"number": 4,
"string": 5,
"date": 6,
"object": 7,
"array": 8,
};
/// @fn number fcf.count(object a_object, function a_cb = undefined)
/// @brief Safely determines the number of elements in an object or a string
/// @param object a_object - Source object
/// @param function a_cb = undefined - If a functor is given, then the child
/// element is taken into account if the function returns true
/// - Function signature: boolean a_cb(mixed a_key, mixed a_value)
/// @result integer - Number of counted elements in an object or a string
fcf.count = (a_object, a_cb) => {
if (!a_cb) {
if (typeof a_object == "object" && a_object !== null) {
if (fcf.isNumbered(a_object)) {
return a_object.length;
} else if (a_object instanceof Map || a_object instanceof Set) {
return a_object.size;
} else {
let count = 0;
for(let k in a_object)
++count;
return count;
}
} else if (typeof a_object == "string") {
return a_object.length;
} else {
return 0;
}
} else {
let res = 0;
fcf.each(a_object, (a_key, a_val)=>{
if (a_cb(a_key, a_val)) {
++res;
}
});
return res;
}
}
/// @fn fcf.Actions->mixed fcf.each(mixed a_obj, function a_cb(mixed a_key, mixed a_value) )
/// @brief Iterates over the elements of the argument a_obj
/// @details The function is used for unified enumeration of
/// objects, enumerated objects (but not strings).
/// @details Iteration is performed until the last element or until the a_cb
/// functor returns a result other than undefined, Promise->!undefined or fcf.Actions->!undefined
/// @param mixed a_obj - Iterable object
/// @param function a_cb(mixed a_key, mixed a_value) - Functor, can be asynchronous.
/// - Example:
/// await fcf.each([1,2,3], async (a_key, a_value)=>{ ... }));
/// @result mixed - Returns a fcf.Actions object with value of a last result.
fcf.each = (a_obj, a_cb) => {
let asyncResult;
let result = undefined;
if (fcf.isNumbered(a_obj) && (a_obj && typeof a_obj == "object" && typeof a_obj.forEach != "function")) {
for(let i = 0; i < a_obj.length; ++i) {
result = a_cb(i, a_obj[i]);
if (result instanceof Promise || result instanceof fcf.Actions) {
let index = i + 1;
let currentResult = result;
result = asyncResult ? asyncResult : fcf.actions();
let actions = fcf.actions();
let act = undefined;
result.then((a_res, a_act) => { act = a_act; });
function doAction(){
actions.then(()=>{
if (index < a_obj.length)
return a_cb(index, a_obj[index]);
})
.then((a_res)=>{
++index;
if (a_res === undefined && index < a_obj.length) {
doAction();
} else {
act.complete(a_res);
}
})
.catch((e)=>{
act.error(e);
});
}
currentResult.then((a_res)=>{
if (a_res === undefined) {
doAction();
} else {
act.complete(a_res);
}
})
.catch((e)=>{
act.error(e);
});
break;
} else if (result !== undefined) {
break;
}
}
} else if (typeof a_obj === "object" && a_obj !== null) {
let asyncEnable = false;
let asyncKeys = [];
let index = 0;
if (typeof a_obj.forEach === "function") {
let breakLoop = false;
a_obj.forEach((a_value, a_key)=>{
if (breakLoop) {
return;
}
if (asyncEnable) {
asyncKeys.push([a_key, a_value]);
} else {
result = a_cb(a_key, a_value);
if (result instanceof Promise || result instanceof fcf.Actions) {
asyncEnable = true;
} else if (result !== undefined) {
breakLoop = true;
return true;
}
}
});
} else {
for(let k in a_obj) {
if (asyncEnable) {
asyncKeys.push([k, a_obj[k]]);
} else {
result = a_cb(k, a_obj[k]);
if (result instanceof Promise || result instanceof fcf.Actions) {
asyncEnable = true;
} else if (result !== undefined) {
break;
}
}
}
}
if (asyncEnable) {
let currentResult = result;
result = asyncResult ? asyncResult : fcf.actions();
let actions = fcf.actions();
let act = undefined;
result.then((a_res, a_act) => { act = a_act; });
function doAction(){
actions.then(() => {
if (index < asyncKeys.length)
return a_cb(asyncKeys[index][0], asyncKeys[index][1]);
})
.then((a_res) => {
++index;
if (a_res === undefined && index < asyncKeys.length) {
doAction();
} else {
act.complete(a_res);
}
})
.catch((e)=>{
act.error(e);
});
}
currentResult.then((a_res) => {
if (a_res === undefined) {
doAction();
} else {
act.complete(a_res);
}
})
.catch((e)=>{
act.error(e);
});
}
}
return (result instanceof fcf.Actions ? result : fcf.actions().result(result)).options({quiet: true}).exception();
}
/// @fn object fcf.append(object|array a_dstObject, object|array a_srcObject1, object|array a_srcObject2, ...)
/// @fn object fcf.append(boolean a_recursionCopy, object a_dstObject, object a_srcObject1, object a_srcObject2, ...)
/// @brief Copies properties from a_srcObjectN objects to the receiving object
/// @param boolean a_recursionCopy - If the parameter is not used or is equal to false,
/// then only the fields are copied into the a_dstObject element from the a_srcObjectN objects.
/// If the parameter is used and equals true, then nested nested elements are copied recursively,
/// i.e. the object is supplemented with new objects.
/// @param object|array a_dstObject - Receiving object
/// @param object|array a_srcObject - Source objects
/// @result object|array - Results a_dstObject
fcf.append = (...args) => {
let startArg = typeof args[0] === "boolean" ? 1 : 0;
let req = typeof args[0] === "boolean" && args[0];
if (Array.isArray(args[startArg])) {
for(let j = startArg + 1; j < args.length; ++j) {
if (req) {
if (Array.isArray(args[j])) {
for(let i = 0; i < args[j].length; ++i) {
let itm = args[j][i] === null ? null :
args[j][i] instanceof Date ? new Date(args[j][i]) :
Array.isArray(args[j][i]) ? fcf.append(true, [], args[j][i]) :
typeof args[j][i] === "object" ? fcf.append(true, new args[j][i].__proto__.constructor(), args[j][i]) :
args[j][i];
args[startArg].push(itm);
}
} else {
fcf.each(args[j], (a_key, a_value)=>{
let itm = a_value === null ? null :
a_value instanceof Date ? new Date(a_value) :
Array.isArray(a_value) ? fcf.append(true, [], a_value) :
typeof a_value === "object" ? fcf.append(true, new a_value.__proto__.constructor(), a_value) :
a_value;
args[startArg].push(itm);
});
}
} else {
if (Array.isArray(args[j])) {
for(let i = 0; i < args[j].length; ++i){
args[startArg].push(args[j][i]);
}
} else {
fcf.each(args[j], (a_key, a_value)=>{
args[startArg].push(a_value);
});
}
}
}
} else if (args[startArg] instanceof Set) {
for(let j = startArg + 1; j < args.length; ++j) {
if (typeof args[j] !== "object" || args[j] === null)
continue;
if (args[j] instanceof Set) {
for(let key of args[j]) {
args[startArg].add(key);
}
} else {
fcf.each(args[j], (a_key, a_value) => {
args[startArg].add(a_value);
});
}
}
} else if (args[startArg] instanceof Map) {
for(let j = startArg + 1; j < args.length; ++j) {
if (typeof args[j] !== "object" || args[j] === null)
continue;
if (req) {
if (args[j] instanceof Map) {
for(let pair of args[j]) {
args[startArg].set(pair[0], cloneValue(pair[1]));
}
} else {
fcf.each(args[j], (a_key, a_value) => {
args[startArg].set(a_key, cloneValue(a_value));
});
}
} else {
if (args[j] instanceof Map) {
for(let pair of args[j]) {
args[startArg].set(pair[0], pair[1]);
}
} else {
fcf.each(args[j], (a_key, a_value) => {
args[startArg].set(a_key, a_value);
});
}
}
}
} else if (typeof args[startArg] === "object" && args[startArg] !== null) {
for(let j = startArg + 1; j < args.length; ++j) {
if (typeof args[j] !== "object" || args[j] === null)
continue;
if (req) {
if (!Array.isArray(args[j]) && !(args[j] instanceof Map) && !(args[j] instanceof Set)) {
for(let key of Object.getOwnPropertyNames(args[j])) {
args[startArg][key] = cloneValue(args[j][key]);
}
} else {
fcf.each(args[j], (a_key, a_value) => {
args[startArg][a_key] = cloneValue(a_value);
});
}
} else {
if (!Array.isArray(args[j]) && !(args[j] instanceof Map) && !(args[j] instanceof Set)) {
for(let key of Object.getOwnPropertyNames(args[j])) {
args[startArg][key] = args[j][key];
}
} else {
fcf.each(args[j], (a_key, a_value)=>{
args[startArg][a_key] = a_value;
});
}
}
}
}
return args[startArg];
}
const cloneValue = (a_source) => {
if (a_source === null) {
return null;
} else if (a_source instanceof Date) {
return new Date(a_sourc