UNPKG

fcf-framework-core

Version:
1,375 lines (1,271 loc) 348 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"; if (!fcf.isServer() && !fcf.NDetails.inlineExecution) { fcf.NDetails.inlineExecution = { execEnvironment: { functions: { functions: {}, items: {} }, environment: { }, environmentInfo: { parts: {} }, }, getEnvironment: ()=>{ return fcf.NDetails.inlineExecution.execEnvironment; }, setEnvironment: (a_environment)=> { fcf.NDetails.inlineExecution.execEnvironment.functions = a_environment.functions; fcf.NDetails.inlineExecution.execEnvironment.environment = a_environment.environment; fcf.NDetails.inlineExecution.execEnvironment.environmentInfo = a_environment.environmentInfo; } } } ////////////////////////////////////////////////////////////////////////////// // 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 }); /// @var integer fcf.ENUM = 11 /// @brief Nature type of variable. Enum variants Object.defineProperty(fcf, "ENUM", { value: 11, writable: false }); /// @var integer fcf.SET = 12 /// @brief Nature type of variable. Set variants Object.defineProperty(fcf, "SET", { value: 12, writable: false }); /// @var integer fcf.ANY = -1 /// @brief Nature type of variable. Any type Object.defineProperty(fcf, "ANY", { value: -1, 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; } fcf.nature = (a_value, a_nature, a_softMode) => { if (fcf.isNature(a_value, a_nature, a_softMode)){ return a_value; } } const DEFAULT_TYPE_DESCRIPTION = {}; function _typeToString(a_type){ if (typeof a_type == "number") { switch (a_type){ case fcf.UNDEFINED: return "undefined"; case fcf.NULL: return "null"; case fcf.NAN: return "nan"; case fcf.BOOLEAN: return "boolean"; case fcf.NUMBER: return "number"; case fcf.STRING: return "string"; case fcf.DATE: return "date"; case fcf.OBJECT: return "object"; case fcf.ARRAY: return "array"; case fcf.ITERABLE: return "iterable"; case fcf.NUMBERED: return "numbered"; case fcf.ENUM: return "enum"; default: return a_type.toString(); } } else { return a_type.toString(); } } /// @fn object fcf.type(int|string|array[int|string|object] a_type, object a_description = undefined) /// @brief Creates a type description object for subsequent validation and data building. /// @details The function transforms the input parameter `a_type` (a string, a numeric type identifier, or an array of types) into a structured type object. /// The resulting object includes validation rules (min, max, length, minLength, maxLength), /// conversion rules, default values, and the structure of nested elements for objects, arrays, iterables, and enums. /// @param int|string|array[int|string|object] a_type - Type definition. It can be: /// - A string (e.g., "string", "number", "array", "object", "enum"). /// - A numeric identifier (e.g., fcf.STRING, fcf.NUMBER). /// - An array of types (to implement "one of many" logic). /// @param object a_description - An object containing extended type description rules: /// - require (boolean) - Whether the field is mandatory. /// - convert (boolean) - Whether conversion should be performed. /// - default (mixed) - Default value. /// - min/max (number) - Boundaries for numbers. /// - length/minLength/maxLength (number) - Length parameters for strings. /// - item (object) - Type description for elements in arrays or iterables. /// - fields (object) - Field descriptions for objects (key is the field name, value is the type description). /// - undeclared (boolean|object) - Rules for fields not explicitly defined in `fields`. /// - items (array) - List of allowed values for `enum` or `set`. /// @result object - A type object containing an internal `_valid = 1` flag. /// @throws fcf.Exception - Throws "UNKNOWN_TYPE" if the provided type is not recognized. fcf.type = (a_type, a_description) => { if (a_type._valid) { return a_type; } if (!a_type) { a_type = fcf.ANY; } if (typeof a_type == "object" && !Array.isArray(a_type)) { a_description = a_type; a_type = a_description.type; } a_description = a_description || DEFAULT_TYPE_DESCRIPTION; let type = { require: a_description.require, convert: a_description.convert, }; if ("default" in a_description) { type.default = a_description.default; } if (!Array.isArray(a_type)) { switch(a_type) { case fcf.ANY: case "any": type.type = fcf.ANY; break; case fcf.UNDEFINED: case "undefined": type.type = fcf.UNDEFINED; break; case fcf.NULL: case "null": type.type = fcf.NULL; break; case fcf.NAN: case "nan": type.type = fcf.NAN; break; case fcf.BOOLEAN: case "boolean": type.type = fcf.BOOLEAN; break; case fcf.NUMBER: case "number": type.type = fcf.NUMBER; type.min = a_description.min; type.max = a_description.max; break; case fcf.STRING: case "string": type.type = fcf.STRING; type.length = a_description.length; type.minLength = a_description.minLength; type.maxLength = a_description.maxLength; break; case fcf.DATE: case "date": type.type = fcf.DATE; break; case fcf.ARRAY: case "array": type.type = fcf.ARRAY; type.item = a_description.item ? fcf.type(a_description.item) : fcf.type(fcf.ANY); break; case fcf.NUMBERED: case "numbered": type.type = fcf.NUMBERED; type.item = a_description.item ? fcf.type(a_description.item) : fcf.type(fcf.ANY); break; case fcf.OBJECT: case "object": type.type = fcf.OBJECT; type.fields = {}; if (a_description.fields && typeof a_description.fields == "object"){ for(let key in a_description.fields){ type.fields[key] = fcf.type(a_description.fields[key]); } } type.undeclared = a_description.undeclared === false ? a_description.undeclared : a_description.undeclared === true ? fcf.type(fcf.ANY) : a_description.undeclared || a_description.undeclared === fcf.UNDEFINED ? fcf.type(a_description.undeclared) : undefined; break; case fcf.ITERABLE: case "iterable": type.type = fcf.ITERABLE; type.item = a_description.item ? fcf.type(a_description.item) : fcf.type(fcf.ANY); break; case fcf.ENUM: case "enum": type.type = fcf.ENUM; type.items = {}; if (Array.isArray(a_description.items)) { for(const v of a_description.items) { if (!type.items[v]){ type.items[v] = []; } type.items[v].push(v); } } break; case fcf.SET: case "set": type.type = fcf.SET; type.items = {}; if (Array.isArray(a_description.items)) { for(const v of a_description.items) { if (!type.items[v]){ type.items[v] = []; } type.items[v].push(v); } } break; default: throw new fcf.Exception("UNKNOWN_TYPE", {type: a_type}); break; } } else { type.type = -100; type.types = []; for(let t of a_type) { type.types.push(fcf.type(t)); } } Object.defineProperty(type, "_valid", { value: 1, enumerable: false }); return type; } const _createIterable = (a_value) => { if ( Array.isArray(a_value) || a_value instanceof Map || a_value instanceof Set ) { return new a_value.__proto__.constructor(); } else { return {}; } } const _buildType = (a_type, a_value, a_options, a_resultInfo, a_rootType, a_forceError) => { switch(a_type.type) { case fcf.UNDEFINED: if (a_value === undefined) { a_resultInfo.error = false; break; } a_resultInfo.error = 1; if (a_rootType) { a_resultInfo.types = ["undefined"]; } break; case fcf.NULL: if (a_value === null) { a_resultInfo.error = false; break; } a_resultInfo.error = a_value === undefined ? 2 : 1; if (a_rootType) { a_resultInfo.types = ["null"]; } break; case fcf.NAN: if (typeof a_value === "number" && isNaN(a_value)) { a_resultInfo.error = false; break; } a_resultInfo.error = a_value === undefined ? 2 : 1; if (a_rootType) { a_resultInfo.types = ["nan"]; } break; case fcf.BOOLEAN: let value = a_value; if (typeof a_value !== "boolean" && a_type.convert){ value = !!a_value; } if (typeof value === "boolean") { a_resultInfo.error = false; a_value = value; break; } a_resultInfo.error = a_value === undefined ? 2 : 1; if (a_rootType) { a_resultInfo.types = ["boolean"]; } break; case fcf.NUMBER: { let value = a_value; if (typeof a_value !== "number" && a_type.convert){ value = Number(a_value); } if (typeof value === "number" && !isNaN(value)) { if (a_type.min !== undefined && value < a_type.min) { let path = fcf.normalizeObjectAddress(a_resultInfo.path); throw new fcf.Exception("NUMBER_MIN", { value: a_value, min: a_type.min, path: path }) } else if (a_type.max !== undefined && value > a_type.max) { let path = fcf.normalizeObjectAddress(a_resultInfo.path); throw new fcf.Exception("NUMBER_MAX", { value: a_value, max: a_type.max, path: path }) } else { a_resultInfo.error = false; a_value = value; break; } } a_resultInfo.error = a_value === undefined ? 2 : 1; if (a_rootType) { a_resultInfo.types = ["number"]; } } break; case fcf.STRING: { let value = a_value; if (typeof a_value !== "string" && a_type.convert){ value = fcf.str(a_value); } if (typeof value === "string") { if (a_type.length !== undefined && a_type.length != value.length) { let path = fcf.normalizeObjectAddress(a_resultInfo.path); throw new fcf.Exception("STRING_LENGTH", { currentLength: value.length, length: a_type.length, path: path }) } else if (a_type.minLength !== undefined && value.length < a_type.minLength) { let path = fcf.normalizeObjectAddress(a_resultInfo.path); throw new fcf.Exception("STRING_MIN_LENGTH", { currentLength: value.length, minLength: a_type.minLength, path: path }) } else if (a_type.maxLength !== undefined && value.length > a_type.maxLength) { let path = fcf.normalizeObjectAddress(a_resultInfo.path); throw new fcf.Exception("STRING_MAX_LENGTH", { currentLength: value.length, maxLength: a_type.maxLength, path: path }) } a_resultInfo.error = false; a_value = value; break; } a_resultInfo.error = a_value === undefined ? 2 : 1; if (a_rootType) { a_resultInfo.types = ["string"]; } } break; case fcf.DATE: { let value = a_value; if (!(a_value instanceof Date) && a_type.convert){ value = new Date(a_value); } if (value instanceof Date && !isNaN(value.getTime())) { a_resultInfo.error = false; a_value = value; break; } a_resultInfo.error = a_value === undefined ? 2 : 1; if (a_rootType) { a_resultInfo.types = ["date"]; } } break; case -100: a_resultInfo.error = false; for(let t of a_type.types) { let value = _buildType(t, a_value, a_options, a_resultInfo, false, false); if (!a_resultInfo.error) { a_value = value; break; } } if (a_resultInfo.error == 1) { a_resultInfo.types = a_type.types.map((a_type)=>{ if (a_type.type == fcf.ENUM) { let v = []; for(let k in a_type.items) { for(let itm of a_type.items[k]) { v.push(JSON.stringify(itm)); } } let tstr = "enum["; tstr += v.join(";"); tstr += "]"; return tstr; } else { return _typeToString(a_type.type); } }); } break; case fcf.ARRAY: case fcf.NUMBERED: const check = a_type.type == fcf.ARRAY ? Array.isArray(a_value) : fcf.isNumbered(a_value); if (check) { let value = []; a_resultInfo.error = false; for(let i = 0; i < a_value.length; ++i) { a_resultInfo.path.push(i.toString()); let item = _buildType(a_type.item, a_value[i], a_options, a_resultInfo, true, false); if (!a_resultInfo.error) { value[i] = item; } else { let path = fcf.normalizeObjectAddress(a_resultInfo.path); if (a_resultInfo.error == 1) { throw new fcf.Exception("NOT_MATCH_TYPE", { path: path, types: (a_resultInfo.types ? a_resultInfo.types : [] )}); } else { throw new fcf.Exception("FIELD_NOT_SET", { path: path }); } } a_resultInfo.path.pop(); } a_value = value; } else { a_resultInfo.error = a_value === undefined ? 2 : 1; if (a_rootType) { a_resultInfo.types = [( a_type.type == fcf.ARRAY ? "array" : "numbered" )]; } } break; case fcf.OBJECT: if (a_value && typeof a_value == "object") { let value = {}; a_resultInfo.error = false; for(let name in a_type.fields) { a_resultInfo.path.push(name); let item = _buildType(a_type.fields[name], a_value[name], a_options, a_resultInfo, true, false); if (a_resultInfo.error == 2 && !a_type.fields[name].require) { a_resultInfo.error = 0; a_resultInfo.path.pop(); continue; } else if (!a_resultInfo.error) { value[name] = item; } else { let path = fcf.normalizeObjectAddress(a_resultInfo.path); if (a_resultInfo.error == 1) { throw new fcf.Exception("NOT_MATCH_TYPE", { path: path, types: (a_resultInfo.types ? a_resultInfo.types : [] )}); } else { throw new fcf.Exception("FIELD_NOT_SET", { path: path }); } } a_resultInfo.path.pop(); } if (a_type.undeclared === false) { for(let name in a_value) { if (!(name in a_type.fields)) { let path = fcf.normalizeObjectAddress(a_resultInfo.path); throw new fcf.Exception("UNDECLARED_FIELD", { path: path, field: name }); break; } } } else if (a_type.undeclared && typeof a_type.undeclared == "object") { for(let name in a_value) { if (!(name in a_type.fields)) { a_resultInfo.path.push(name); let item = _buildType(a_type.undeclared, a_value[name], a_options, a_resultInfo, true, false); if (!a_resultInfo.error) { value[name] = item; } else { let path = fcf.normalizeObjectAddress(a_resultInfo.path); if (a_resultInfo.error == 1) { throw new fcf.Exception("NOT_MATCH_TYPE", { path: path, types: (a_resultInfo.types ? a_resultInfo.types : [] )}); } else { throw new fcf.Exception("FIELD_NOT_SET", { path: path }); } } a_resultInfo.path.pop(); } } } a_value = value; } else { a_resultInfo.error = a_value === undefined ? 2 : 1; if (a_rootType) { a_resultInfo.types = ["object"]; } } break; case fcf.ITERABLE: if (fcf.isIterable(a_value)) { a_resultInfo.error = false; let value = _createIterable(a_value); fcf.each(a_value, (key, item)=>{ a_resultInfo.path.push(key.toString()); let newItem = {}; newItem[key] = _buildType(a_type.item, item, a_options, a_resultInfo, true, true); fcf.append(value, newItem); if (a_resultInfo.error) { let path = fcf.normalizeObjectAddress(a_resultInfo.path); if (a_resultInfo.error == 1) { throw new fcf.Exception("NOT_MATCH_TYPE", { path: path, types: (a_resultInfo.types ? a_resultInfo.types : [] )}); } else { throw new fcf.Exception("FIELD_NOT_SET", { path: path }); } } a_resultInfo.path.pop(); }); a_value = value; } else { a_resultInfo.error = a_value === undefined ? 2 : 1; if (a_rootType) { a_resultInfo.types = ["array"]; } } break; case fcf.SET: { let value = a_value; if (typeof a_value == "string") { let emp = true; value = []; for(let c of a_value) { if (emp) { emp = false; value.push(""); } if (c == "|" || c == ";") { emp = true; } else { value[value.length-1] += c; } } } if (Array.isArray(value)) { value = [...value]; } if (Array.isArray(value)) { let valid = true; for(let i of value) { if (!(i in a_type.items)) { valid = false; break; } } if (valid) { a_resultInfo.error = false; a_value = value; break; } } a_resultInfo.error = a_value === undefined ? 2 : 1; if (a_rootType) { let v = []; for(let k in a_type.items) { for(let itm of a_type.items[k]) { v.push(JSON.stringify(itm)); } } let tstr = "set["; tstr += v.join(";"); tstr += "]"; a_resultInfo.types = [tstr]; } } break; case fcf.ENUM: { let found = false; if (a_value in a_type.items) { for(let v of a_type.items[a_value]){ let eq = a_type.convert ? v == a_value : v === a_value; if (eq) { a_resultInfo.error = false; a_value = v; found = true; break; } } } if (found){ break; } a_resultInfo.error = a_value === undefined ? 2 : 1; if (a_rootType) { let v = []; for(let k in a_type.items) { for(let itm of a_type.items[k]) { v.push(JSON.stringify(itm)); } } let tstr = "enum[";