"use strict"; import {Check} from "../Utils/Check.ecma.js"; import {Utils} from "../Utils/Utils.ecma.js"; import {RandomFormat} from "./Format/RandomFormat.ecma.js"; import {RangeFormat} from "./Format/RangeFormat.ecma.js"; import {MixFormat} from "./Format/MixFormat.ecma.js"; import {SerieFormat} from "./Format/SerieFormat.ecma.js"; import {SelectFormat} from "./Format/SelectFormat.ecma.js"; import {PlainFormat} from "./Format/PlainFormat.ecma.js"; import {CapitalizeFormat} from "./Format/CapitalizeFormat.ecma.js"; import {BlockFormat} from "./Format/BlockFormat.ecma.js"; import {UniquesFormat} from "./Format/UniquesFormat.ecma.js"; /** * @typedef {import("../OpoTests.ecma.js").OpoTests} OpoTests */ /** * @class FormatModule * @constructor * @param {!OpoTests} ot * @returns {void} * @access public * @static */ export const FormatModule = (function(){ /** * @callback format_recursive_callback * @param {?any} data * @return {?any} */ /** * @callback format_mode_callback * @param {!(string|Array.)} inputs * @param {!Object.} [shared = {}] * @param {!Array.} [fragments = []] * @return {string} */ /** * @callback format_check_callback * @param {!string} string * @param {!(string|Array.)} inputs * @param {!Object.} [shared = {}] * @param {!Array.} [fragments = []] * @return {number} */ /** * @constructs FormatModule * @param {!OpoTests} ot * @returns {void} * @access private * @static */ const FormatModule = function(ot){ /** @type {FormatModule} */ const self = this; /** @type {Object.} */ this.modes = {}; /** @type {Object.} */ this.checks = {}; /** @type {RandomFormat} */ this.random = new RandomFormat(self); /** @type {RangeFormat} */ this.range = new RangeFormat(self); /** @type {MixFormat} */ this.mix = new MixFormat(self); /** @type {SerieFormat} */ this.serie = new SerieFormat(self); /** @type {SelectFormat} */ this.select = new SelectFormat(self); /** @type {PlainFormat} */ this.plain = new PlainFormat(self); /** @type {CapitalizeFormat} */ this.capitalize = new CapitalizeFormat(self); /** @type {BlockFormat} */ this.block = new BlockFormat(self); /** @type {UniquesFormat} */ this.uniques = new UniquesFormat(self); /** * @returns {void} * @access private */ const constructor = () => {}; /** * @param {![number, number]} coordenates * @param {!string} key * @returns {boolean} * @access public */ this.variable_exists = ([i, j], key) => ( key in ot.variables || (ot.group_variables[ot.database[i].group] && key in ot.group_variables[ot.database[i].group]) || (ot.database[i].queries[j].own_variables && key in ot.database[i].queries[j].own_variables) ); /** * @param {![number, number]} coordenates * @param {!string} key * @returns {[string, ...(any|null)]|null} * @access public */ this.get_variable = ([i, j], key) => { // key == "l_40_2015" && console.log([i, j, key, ot.variables]); return ( (ot.database[i].queries[j].own_variables && ot.database[i].queries[j].own_variables[key]) || (ot.group_variables[ot.database[i].group] && ot.group_variables[ot.database[i].group][key]) || ot.variables[key] ); }; /** * @param {[number, number]} coordenates * @param {!string} string * @param {!(string|Array.)} options * @param {!Object.} [shared = {}] * @param {!Array.} [fragments = []] * @param {!boolean} [check_full = true] * @returns {number} * @access public */ this.get_check_length = ([i, j], string, options, shared = {}, fragments = [], check_full = true) => { /** @type {[boolean, number]} */ const [has, length] = self.check([i, j], string, options, shared, fragments, check_full); return has ? length : -1; }; /** * @param {[number, number]} coordenates * @param {!string} string * @param {!(string|Array.)} options * @param {!Object.} [shared = {}] * @param {!Array.} [fragments = []] * @param {!boolean} [check_full = true] * @returns {[boolean, number]} * @access public */ this.check = ([i, j], string, options, shared = {}, fragments = [], check_full = true) => { /** @type {number} */ let length = 0; string = string.toLowerCase(); return [options.some(option => { /** @type {string} */ let clone = "" + string; /** @type {Array.} */ const parts = []; self.build_fragments(option, fragments).replace(/\{[^\{\}]+\}|[^\{]+|\{/g, part => { parts.push( /^\{.+\}$/.test(part) ? part : part.toLowerCase()); return ""; }); length = 0; let done = parts.every((part, k) => { if(clone.indexOf(part) == 0){ clone = clone.slice(part.length); return true; }; /** @type {number} */ let m = -1; /** @type {RegExpMatchArray} */ const matches = part.match(/^\{([^\{\}]+)\}$/); if(matches){ /** @type {string} */ const key = matches[1]; if(/^[a-z0-9_]+$/i.test(key)){ if(self.variable_exists([i, j], key)){ /** @type {[string, ...(any|null)]} */ const [method, ...data] = self.get_variable([i, j], key); method in self.checks && // self.checks[method]([i, j], clone, data, shared, fragments) != -1 && (m = self.checks[method]([i, j], clone, data, shared, fragments)); }else if(parts[k + 1]){ // TEMPORARY SOLUTION. NEEDS BUILD REAL SOLUTION. m = clone.indexOf(parts[k + 1]); }; }else{ /** @type {RegExpMatchArray} */ const matches = key.match(/^([^,:]+)[,:](.*)$/); if(matches){ /** @type {[string, string]} */ const [method, data] = matches.slice(1, 3); method in self.checks && (m = self.checks[method]([i, j], clone, data, shared, fragments)); }; }; }; m == -1 && (m = FormatModule.prepare_result(clone, part, shared)); if(m != -1){ clone = clone.slice(m); length += m; return true; }; return false; }) && (!check_full || !clone.length); return done; }), length]; }; /** * @param {!string} string * @param {!Array.} fragments * @returns {string} * @access public */ this.set_fragments_level = (string, fragments) => { return ( !fragments.length ? string : string.replace(/\${3}([0-9]+)\${3}/g, (_, i) => fragments[Number(i)])); }; /** * @param {!string} string * @param {!Array.} [fragments = []] * @returns {string} * @access public */ this.build_fragments = (string, fragments = []) => ( self.set_fragments_level(FormatModule.recursive(string, string => ( string.replace(/\{[^\{\}]+\}/g, fragment => { fragments.push(fragment); return "$$$" + (fragments.length - 1) + "$$$"; }) )), fragments) ); /** * @param {[number, number]} coordenates * @param {!string} string * @param {!Object.} [shared = {}] * @param {!Array.} [fragments = []] * @returns {string} * @access public */ this.execute = ([i, j], string, shared = {}, fragments = []) => ( FormatModule.recursive(self.build_fragments(string, fragments), substring => { try{ return substring.replace(/\{([^\{\}]+)\}/g, (original, key) => { if(/^[a-z0-9_]+$/i.test(key)){ if(key in shared) return shared[key]; /** @type {[string, ...(any|null)]|null} */ const variable = self.get_variable([i, j], key); if(variable){ /** @type {[string, ...(any|null)]} */ const [method, ...data] = variable; if(method in self.modes) return self.modes[method]([i, j], data, shared, fragments); }; }else{ /** @type {RegExpMatchArray} */ const matches = key.match(/^([^,:]+)[,:](.*)$/); if(matches){ /** @type {[string, string]} */ const [method, data] = matches.slice(1, 3); if(method in self.modes) return self.modes[method]([i, j], data, shared, fragments); }; }; return original; }); }catch(exception){ console.error([exception, fragments, string, substring]); throw new Error("STOPPED"); }; return substring; }) ); /** * @param {[number, number]} coordenates * @param {!string} string * @param {[[number, number], string, Array.]} parameters * @param {!Object.} [shared = {}] * @param {!Array.} [fragments = []] * @returns {number} * @access public */ this.check_select = ([i, j], string, [[minimum, maximum], separator, items], shared = {}, fragments = []) => { /** @type {number} */ let length = 0, /** @type {number} */ k = 0, /** @type {boolean} */ check_last = false; /** @type {Array.} */ const clone_items = [...items], separator_string = " " + separator + " "; for(let done = false; !done && k < maximum; k ++){ /** @type {number} */ const l = clone_items.length; if(!l) break; /** @type {boolean} */ let ok = false; if(check_last || (k && k == l - 1)){ // if(string.startsWith(" " + separator + " ")){ // length += separator.length + 2; // string = string.substring(0, separator.length + 2); // }else if(minimum > 1 || k > 1) // return length || -1; // else // break; if(minimum > 1 || k > 1) break; }; for(let m = 0; m < l; m ++){ /** @type {number} */ const item_length = ( /\{[^\{\}]+\}/.test(self.set_fragments_level(clone_items[m], shared, fragments)) ? self.get_check_length([i, j], string, [items[m]], shared, fragments, false) : FormatModule.prepare_result(string, clone_items[m], shared)); if(item_length != -1){ string = string.slice(item_length); clone_items.splice(m, 1); length += item_length; ok = true; if(!string.indexOf(separator_string)){ length += separator_string.length; string = string.slice(separator_string.length); }else if(!string.indexOf(", ")){ length += 2; string = string.slice(2); }else done = true; break; }; }; if(!ok){ k --; break; }; shared.capitalized && (shared.capitalized = false); // if(ok) // shared.capitalized && (shared.capitalized = false); // else if(check_last || !k) // return length || -1; // else{ // check_last = true; // k --; // }; }; return !length || k < minimum ? -1 : length; }; /** * @param {[number, number]} coordenates * @param {!Array.} items * @returns {Array.} * @access public */ this.get_list = ([i, j], items) => items.reduce((items, item) => { /** @type {RegExpMatchArray} */ const matches = item.match(/^list\:([a-z0-9_]+)$/i); if(matches){ /** @type {string} */ const key = matches[1], /** @type {[string, ...(any|null)]|null} */ variable = self.get_variable([i, j], key); if(variable[0] == "list") return items.concat(self.get_list([i, j], variable[1])); }; return items.concat([item]); }, []); /** * @param {!string} string * @param {!Array.} fragments * @returns {string} * @access public */ this.restore_fragments = (string, fragments) => FormatModule.recursive(string, substring => ( self.set_fragments_level(substring, fragments) )); /** * @param {[number, number]} coordenates * @param {!Array.} items * @param {?number} k * @param {?string} separator * @param {!Array.} fragments * @param {?Object.} shared * @returns {string} * @access public */ this.get_items = ([i, j], items, k, separator, fragments, shared) => { /** @type {number} */ const l = (items = self.get_list([i, j], items)).length, /** @type {Array.} */ selected = [], /** @type {Object.} */ subshared = { ...(shared || (shared = {})), /** @type {boolean} */ capitalized : false, /** @type {boolean} */ uniques : false }; while(Check.is_array(k)) Check.is_string(k = Utils.get_random(...( k.every(Check.is_number) && k.length < 3 ? k : [k] ))) && (k = k.includes("-") ? k.split("-").map(Number) : Number(k)); k || (k = 1); if((shared || (shared = {})).uniques){ shared.uniques_cache = []; for(let m = 0; m < k && items.length; m ++){ /** @type {string} */ const item = Utils.get_random(items); if(/\${3}[0-9]+\${3}/.test(item)){ /** @type {boolean} */ let done = false; for(let t = 0; t < 100; t ++){ /** @type {string} */ const item_string = self.execute([i, j], item, subshared, fragments); if(done = !selected.includes(item_string) && ( !shared.uniques_cache || !shared.uniques_cache.includes(item_string) )){ selected.push(item_string); break; }; }; if(!done){ items.splice(items.indexOf(item), 1); m --; }; }else{ !selected.includes(item) && ( !shared.uniques_cache || !shared.uniques_cache.includes(item_string) ) && selected.push(item); items.splice(items.indexOf(item), 1); }; }; }else{ for(let m = 0; m < k && items.length; m ++){ /** @type {number} */ const x = Utils.get_random(items.length - 1); if(shared.uniques_cache && shared.uniques_cache.includes(items[x])) m --; else selected.push(self.execute([i, j], items[x], subshared, fragments)); items.splice(x, 1); }; // Utils.randomize_array(items); // selected.push(...items.slice(0, k > l ? l : k).map(item => self.execute([i, j], item, subshared, fragments))); }; return FormatModule.process_item((selected.length > 1 ? selected.slice(0, selected.length - 1).join(", ") + ( !separator ? " " : /^[a-z 0-9]+$/i.test(separator.trim()) ? " " + separator.trim() + " " : separator) + selected[selected.length - 1] : selected[0]), shared); }; constructor(); }; /** @type {RegExp} */ FormatModule.PATTERN = new RegExp("9876543210[".split("").reduce((pattern, character) => { /** @type {[string, string]} */ const [start, end] = character != "[" ? ["\\[" + character, character + "\\]"] : ["\\[{2}", "\\]{2}"]; return (pattern ? pattern + "|" : "") + start + "((?:(?!(?:" + start + "|" + end + ")).)+)" + end; }, ""), "g"); /** * @param {?any} data * @param {!format_recursive_callback} callback * @returns {any|null} * @access public * @static */ FormatModule.recursive = (data, callback) => { /** @type {any|null} */ let cache = data; const x = data; do{ cache === undefined && console.warn([x, cache]); cache = callback(data = cache); } while(cache != data); return data; }; /** * @param {!string} string * @returns {string} * @access public * @static */ FormatModule.update = string => FormatModule.recursive(string, string => string.replace(FormatModule.PATTERN, (original, ...data) => { for(let i = 0; i < data.length; i ++) if(data[i]) return "{" + data[i].replace(/:{3}/g, "|") + "}"; return original; })); /** * @param {!string} string * @param {!string} option * @param {!object.} shared * @returns {string} * @access public * @static */ FormatModule.prepare_result = (string, option, shared) => { if(string.toLowerCase().startsWith(option.toLowerCase())){ shared.capitalized && (shared.capitalized = false); return option.length; }; return -1; }; /** * @param {!string} string * @param {!(string|Array.)} inputs * @returns {number} * @access public * @static */ FormatModule.check_range = (string, inputs) => { /** @type {RegExpMatchArray} */ let number_match = string.match(/^[0-9]+/); if(number_match){ /** @type {number} */ const number = Number(number_match[0]); for(const range of ( Check.is_string(inputs) ? Utils.get_random(inputs.split("|")).split("-").map(Number) : Check.is_array(inputs) ? inputs : [])){ if(Check.is_number(range)){ if(number == range) return ("" + number).length; }else if(range.length == 1){ if(number == range[0]) return ("" + number).length; }else{ if(number >= range[0] && number <= range[1]) return ("" + number).length; }; }; }; return -1; }; /** * @param {!string} item * @param {!Object.} shared * @returns {string} * @access public * @static */ FormatModule.process_item = (item, shared) => shared && shared.capitalized ? item.replace(/^./, character => character.toUpperCase()) : item; return FormatModule; })();