UNPKG

fcf-framework-core

Version:
1,410 lines (1,283 loc) 288 kB
(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 += "&lt;"; break; case ">": result += "&gt;"; break; case "\"": result += "&quot;"; break; case "\'": result += "&apos;"; break; case "&": result += "&amp;"; 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