fcf-framework-core
Version:
Basic functions of the fcf framework 2.0
1,375 lines (1,271 loc) • 348 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";
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 += "<"; 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 });
/// @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[";