"use strict";
/**
* QueryValidator parses given restrictions on user input and manages user input comparison and validation.
*/
class QueryValidator {
/**
* Create QueryValidator.
* @param {(string|boolean|RegExp|string[]|boolean[]|RegExp[])} validationData - Defines basis for user input comparison.
*/
constructor(validationData) {
this.regExps = [];
this.strings = [];
this.booleans = [];
this.mixture = [];
this.parseValidationData(validationData);
}
/**
* Chceck whether such rule has been registered.
* @param {(string|boolean|RegExp)} rule - a rule to be searched for in the QueryValidator.
* @return {boolean} - If a rule was found or not.
*/
hasRule(rule) {
for(let i = 0; i < this.mixture.length; i++) {
if(rule instanceof RegExp) {
if(String(this.mixture[i]) === String(rule)) {
return true;
}
}
else {
if(this.mixture[i] === rule) {
return true;
}
}
}
return false;
}
/**
* Adds a regular expression to the list of regular expressions as well as to the mixture array.
* @param {RegExp} regExp - A regular espression to be registered for later comparison.
* @return {QueryValidator} - This instance.
*/
appendRegExp(regExp) {
this.regExps.push(regExp);
this.mixture.push(regExp);
return this;
}
/**
* Adds a string to the list of strings as well as to the mixture array.
* @param {string} string - A string to be registered for later comparison.
* @return {QueryValidator} - This instance.
*/
appendString(string) {
this.strings.push(string);
this.mixture.push(string);
return this;
}
/**
* Adds a boolean to the list of booleans as well as to the mixture array.
* @param {boolean} bool - A boolean value to be registered for later comparison.
* @return {QueryValidator} - This instance.
*/
appendBoolean(bool) {
this.booleans.push(bool);
this.mixture.push(bool);
return this;
}
/**
* Takes a single expression or an array of expressions designed for a specific QueryItem
* and pushes them to be registered for later validation comparison.
* @param {(string|boolean|RegExp|string[]|boolean[]|RegExp[])} validationData - Defines basis for user input comparison.
* @return {QueryValidator} - This instance.
*/
parseValidationData(validationData) {
if(typeof validationData !== typeof undefined && validationData !== null) {
if(validationData.constructor === Array) {
for(var i = 0; i < validationData.length; i++) {
this.parseSingleValidationItem(validationData[i]);
}
}
else {
this.parseSingleValidationItem(validationData);
}
}
else {
this.off = true;
}
return this;
}
/**
* Sets match type for this QueryValidator to take passed arrays as AND or OR. AND: Each input entry must have a match, OR: At least on given entry must have a match.
* @param {string} [type] - Defines given array match type to AND or OR.
* @return {string} - Set validation match option.
*/
setMatch(type) {
type = type || "and";
switch(type) {
case "and":
this.and = true;
this.or = false;
return "and";
case "or":
this.or = true;
this.and = false;
return "or";
default:
this.and = true;
this.or = false;
return "default";
}
}
/**
* Convert some known words into booleans
* @param {(string|boolean)} value - A word to be potentionally converted into boolean.
* @return {(boolean|null)} - A converted word, passed boolean or null if conversion was not possible/successful.
*/
booleanStringConversion(value) {
if(value === "true" || value === "false") {
return value === "true";
}
else if(value === "yes" || value === "no") {
return value === "yes";
}
else if(value === true) {
return true;
}
else if(value === false) {
return false;
}
return null;
}
/**
* Parses a signle validation item designed for a specific QueryItem and assigns it to the apropriete category (String, RegExp, Bool).
* In addition allows for complex string values separed by commas - dividing them by commas and parsing each item separately.
* @param {(string|boolean|RegExp)} itemData - Takes one element for later user input comparison.
* @param {number} [depth=1] - Tells the method how deep in the recursion it is. Default is 1, max depth allowed is 2.
* @return {boolean} - State of the parse operation, false on error, otherwise true.
*/
parseSingleValidationItem(itemData, depth) {
depth = depth || 1;
let maxDepth = 2;
if(itemData instanceof RegExp) {
this.appendRegExp(itemData);
}
else if(typeof itemData === "boolean") {
this.appendBoolean(itemData);
}
else {
var parsedData = this.parseRegExpRule(itemData);
if(!parsedData) {
console.error("Unknow check rule expression", parsedData);
return false;
}
else if(parsedData instanceof RegExp) {
this.appendRegExp(parsedData);
}
else {
// Convert some known words into booleans
let stringConversion = this.booleanStringConversion(parsedData);
if(stringConversion !== null) {
this.appendBoolean(stringConversion);
}
else {
// Parse string ... subdivide a string if values in it are separated by comma.
let separateValues = parsedData.split(", ");
if(separateValues.length > 1 && depth < maxDepth) {
for(let i = 0; i < separateValues.length; i++) {
if(!this.parseSingleValidationItem(separateValues[i], depth++)) {
return false;
}
}
}
else {
this.appendString(parsedData);
}
}
}
}
return true;
}
/**
* Parses a given string into RegExp.
* @param {string} rule - Takes a string which is in the form of a regular expression like this: "/^[a-zA-Z ]+$/".
* @return {(RegExp|string|boolean)} - If it could parse the given string returns the final RegExp,
* otherwise a given string is returned back. In case string was not passed to the method false is returned.
*/
parseRegExpRule(rule) {
if(typeof rule === "string" || rule instanceof String) {
try {
// Source: http://stackoverflow.com/a/874742
var flags = rule.replace(/.*\/([gimyu]*)$/, "$1");
var pattern = rule.replace(new RegExp("^/(.*?)/" + flags + "$"), "$1");
return new RegExp(pattern, flags);
}
catch(e) {
return rule;
}
}
return false;
}
/**
* Compares two given values, where the first given value is universal for String and RegExp - won't work with Booleans.
* If a RegExp is passed the comparison is done in the form of a RegExp test on the second value.
* @param {(string|RegExp)} first - A first value for comparison.
* @param {string} second - A second value for comparison.
* @return {boolean} - Returns whether the two given values equal or not.
*/
compareValues(first, second) {
if(typeof first !== typeof undefined && second !== typeof undefined) {
if(typeof second === "string" || second instanceof String) {
if(first instanceof RegExp) {
return first.test(second);
}
if(typeof first === "string" || first instanceof String) {
return first === second;
}
}
}
return false;
}
/**
* Compares given value only to the registered booleans.
* @param {boolean} value - A boolean value for comparison.
* @param {number} [index] - If index is given the value is compared only with an item on the specified index.
* @return {boolean} - Returns whether the given values mathes any of the registered boolean rules or not.
*/
testValueToAllBooleans(value, index) {
if(typeof index !== undefined && !isNaN(index) && index >= 0) {
return this.booleans.length > index && value === this.booleans[index];
}
var atLeastOneCorrect = false;
if(typeof value !== typeof undefined) {
for(let i = 0; i < this.booleans.length; i++) {
if(value === this.booleans[i]) {
atLeastOneCorrect = true;
}
}
}
return atLeastOneCorrect;
}
/**
* Compares given value only to the registered strings.
* @param {string} value - A string value for comparison.
* @param {number} [index] - If index is given the value is compared only with an item on the specified index.
* @return {boolean} - Returns whether the given values mathes any of the registered string rules or not.
*/
testValueToAllStrings(value, index) {
if(typeof index !== undefined && !isNaN(index) && index >= 0) {
return this.strings.length > index && value === this.strings[index];
}
var atLeastOneCorrect = false;
if(typeof value !== typeof undefined) {
for(let i = 0; i < this.strings.length; i++) {
if(this.strings[i] === value) {
atLeastOneCorrect = true;
}
}
}
return atLeastOneCorrect;
}
/**
* Compares given value only to the registered RegExps.
* @param {RegExp} value - A RegExp value for comparison.
* @param {number} [index] - If index is given the value is compared only with an item on the specified index.
* @return {boolean} - Returns whether the given values mathes any of the registered RegExp rules or not.
*/
testValueToAllRegExps(value, index) {
if(typeof index !== undefined && !isNaN(index) && index >= 0) {
return this.regExps.length > index && this.regExps[index].test(value);
}
var atLeastOneCorrect = false;
if(typeof value !== typeof undefined) {
for(let i = 0; i < this.regExps.length; i++) {
if(this.regExps[i].test(value)) {
atLeastOneCorrect = true;
}
}
}
return atLeastOneCorrect;
}
/**
* Get the itemToValidate value.
* @return {InputItem} - The itemToValidate value.
*/
get itemToValidate() {
return this.item || null;
}
/**
* Set the itemToValidate value.
* @param {InputItem} item - The itemToValidate value.
*/
set itemToValidate(item) {
this.item = item || null;
}
/**
* Validates any mixture content (array of strings or booleans and any mixture of those) to all inner values.
* @param {(boolean[]|string[]|mixed)} values - The itemToValidate value.
* @return {boolean} - Return true if all values match (AND) or at least one matches (OR).
*/
validateAnyMixture(values) {
values = values || [];
let valid = false;
// If each must have a match and the number of present rules is not the same as the number of input values than this is invalid right away.
if(this.and && values.length !== this.mixture.length) {
valid = false;
}
else {
for(let i = 0; i < values.length; i++) {
let stringConversion = this.booleanStringConversion(values[i]);
if(stringConversion !== null && typeof stringConversion === "boolean") {
// Test to all booleans
valid = this.testValueToAllBooleans(stringConversion);
}
else {
if(this.strings.length > 0) {
// Test to all strings
valid = this.testValueToAllStrings(values[i]);
}
if(!valid && this.regExps.length > 0) {
// Test to all RegExps
valid = this.testValueToAllRegExps(values[i]);
}
}
if(this.and && valid === false) {
break;
}
}
}
return valid;
}
/**
* Validates any mixture content (array of strings or booleans and any mixture of those) to all inner values in rule order.
* @param {(boolean[]|string[]|mixed)} values - The itemToValidate value.
* @return {boolean} - Return true if all values match (AND) or at least one matches (OR).
*/
validateAnyMixtureInRuleOrder(values) {
values = values || [];
let valid = false;
if(values.length !== this.mixture.length) {
valid = false;
}
else {
for(let i = 0; i < values.length; i++) {
let stringConversion = this.booleanStringConversion(values[i]);
if(stringConversion !== null && typeof stringConversion === "boolean") {
// Test on booleans
valid = this.mixture[i] === stringConversion;
}
else {
// Test on strings and RegExps
valid = this.compareValues(this.mixture[i], values[i]);
}
if(!valid) {
break;
}
}
}
return valid;
}
/**
* Validates a preset itemToValidate (InputItem) using the registered rules.
* @param {(boolean[]|string[]|mixed)} [values] - An optional array of booleans, strings or mixed parameter overwiting a value given by InputItem.
* @return {boolean} - Returns whether the assigned InputItem matches the preset rules.
*/
validate(values) {
if(this.off) {
// If Validation is not requested for this item always return true;
return true;
}
// True if the default input item value should be overwritten.
let overwrite = typeof values === typeof undefined ? false : true;
// If there is no overwrite value to validate set and input item to be validated present.
var valid = true;
// Validate specifix input items - IGNORES CUSTOM VALUES, THOUGH THEY ARE TAKEN CARE IN A DIFFERENT SECTION.
if(this.itemToValidate) {
// Validate input type checkbox and radio
// In case more booleans registered here at least one has to match - Works as OR because AND would not make sense for boolean values.
if(this.itemToValidate.queryData.type === "radio" || this.itemToValidate.queryData.type === "checkbox") {
valid = this.testValueToAllBooleans(this.itemToValidate.dom.checked);
}
// Validate input type order: Will keep order for items validation.
// The only way to make this a bit variable is to use RegExp on relevant option match.
else if(this.itemToValidate.queryData.type === "order") {
let items = this.itemToValidate.value;
valid = this.validateAnyMixtureInRuleOrder(items);
}
else {
valid = this.validateAnyMixture(this.itemToValidate.value);
}
}
else {
// Validation any value
valid = this.validateAnyMixture(overwrite ? values : this.itemToValidate.value);
}
this.lastValidationResult = valid;
return valid;
}
}