src/services/maskService.js
(function() {
'use strict';
angular.module('ngMask')
.factory('MaskService', ['$q', 'OptionalService', 'UtilService', function($q, OptionalService, UtilService) {
function create() {
var options;
var maskWithoutOptionals;
var maskWithoutOptionalsLength = 0;
var maskWithoutOptionalsAndDivisorsLength = 0;
var optionalIndexes = [];
var optionalDivisors = {};
var optionalDivisorsCombinations = [];
var divisors = [];
var divisorElements = {};
var regex = [];
var patterns = {
'9': /[0-9]/,
'8': /[0-8]/,
'7': /[0-7]/,
'6': /[0-6]/,
'5': /[0-5]/,
'4': /[0-4]/,
'3': /[0-3]/,
'2': /[0-2]/,
'1': /[0-1]/,
'0': /[0]/,
'*': /./,
'w': /\w/,
'W': /\W/,
'd': /\d/,
'D': /\D/,
's': /\s/,
'S': /\S/,
'b': /\b/,
'A': /[A-Z]/,
'a': /[a-z]/,
'Z': /[A-ZÇÀÁÂÃÈÉÊẼÌÍÎĨÒÓÔÕÙÚÛŨ]/,
'z': /[a-zçáàãâéèêẽíìĩîóòôõúùũüû]/,
'@': /[a-zA-Z]/,
'#': /[a-zA-ZçáàãâéèêẽíìĩîóòôõúùũüûÇÀÁÂÃÈÉÊẼÌÍÎĨÒÓÔÕÙÚÛŨ]/,
'%': /[0-9a-zA-ZçáàãâéèêẽíìĩîóòôõúùũüûÇÀÁÂÃÈÉÊẼÌÍÎĨÒÓÔÕÙÚÛŨ]/
};
// REGEX
function generateIntermetiateElementRegex(i, forceOptional) {
var charRegex;
try {
var element = maskWithoutOptionals[i];
var elementRegex = patterns[element];
var hasOptional = isOptional(i);
if (elementRegex) {
charRegex = '(' + elementRegex.source + ')';
} else { // is a divisor
if (!isDivisor(i)) {
divisors.push(i);
divisorElements[i] = element;
}
charRegex = '(' + '\\' + element + ')';
}
} catch (e) {
throw e;
}
if (hasOptional || forceOptional) {
charRegex += '?';
}
return new RegExp(charRegex);
}
function generateIntermetiateRegex(i, forceOptional) {
var elementRegex
var elementOptionalRegex;
try {
var intermetiateElementRegex = generateIntermetiateElementRegex(i, forceOptional);
elementRegex = intermetiateElementRegex;
var hasOptional = isOptional(i);
var currentRegex = intermetiateElementRegex.source;
if (hasOptional && ((i+1) < maskWithoutOptionalsLength)) {
var intermetiateRegex = generateIntermetiateRegex((i+1), true).elementOptionalRegex();
currentRegex += intermetiateRegex.source;
}
elementOptionalRegex = new RegExp(currentRegex);
} catch (e) {
throw e;
}
return {
elementRegex: function() {
return elementRegex;
},
elementOptionalRegex: function() {
// from element regex, gets the flow of regex until first not optional
return elementOptionalRegex;
}
};
}
function generateRegex(opts) {
var deferred = $q.defer();
options = opts;
try {
var mask = opts['mask'];
var repeat = opts['repeat'];
if (!mask)
return;
if (repeat) {
mask = Array((parseInt(repeat)+1)).join(mask);
}
optionalIndexes = OptionalService.getOptionals(mask).fromMaskWithoutOptionals();
options['maskWithoutOptionals'] = maskWithoutOptionals = OptionalService.removeOptionals(mask);
maskWithoutOptionalsLength = maskWithoutOptionals.length;
var cumulativeRegex;
for (var i=0; i<maskWithoutOptionalsLength; i++) {
var charRegex = generateIntermetiateRegex(i);
var elementRegex = charRegex.elementRegex();
var elementOptionalRegex = charRegex.elementOptionalRegex();
var newRegex = cumulativeRegex ? cumulativeRegex.source + elementOptionalRegex.source : elementOptionalRegex.source;
newRegex = new RegExp(newRegex);
cumulativeRegex = cumulativeRegex ? cumulativeRegex.source + elementRegex.source : elementRegex.source;
cumulativeRegex = new RegExp(cumulativeRegex);
regex.push(newRegex);
}
generateOptionalDivisors();
maskWithoutOptionalsAndDivisorsLength = removeDivisors(maskWithoutOptionals).length;
deferred.resolve({
options: options,
divisors: divisors,
divisorElements: divisorElements,
optionalIndexes: optionalIndexes,
optionalDivisors: optionalDivisors,
optionalDivisorsCombinations: optionalDivisorsCombinations
});
} catch (e) {
deferred.reject(e);
throw e;
}
return deferred.promise;
}
function getRegex(index) {
var currentRegex;
try {
currentRegex = regex[index] ? regex[index].source : '';
} catch (e) {
throw e;
}
return (new RegExp('^' + currentRegex + '$'));
}
// DIVISOR
function isOptional(currentPos) {
return UtilService.inArray(currentPos, optionalIndexes);
}
function isDivisor(currentPos) {
return UtilService.inArray(currentPos, divisors);
}
function generateOptionalDivisors() {
function sortNumber(a,b) {
return a - b;
}
var sortedDivisors = divisors.sort(sortNumber);
var sortedOptionals = optionalIndexes.sort(sortNumber);
for (var i = 0; i<sortedDivisors.length; i++) {
var divisor = sortedDivisors[i];
for (var j = 1; j<=sortedOptionals.length; j++) {
var optional = sortedOptionals[(j-1)];
if (optional >= divisor) {
break;
}
if (optionalDivisors[divisor]) {
optionalDivisors[divisor] = optionalDivisors[divisor].concat(divisor-j);
} else {
optionalDivisors[divisor] = [(divisor-j)];
}
// get the original divisor for alternative divisor
divisorElements[(divisor-j)] = divisorElements[divisor];
}
}
}
function removeDivisors(value) {
value = value.toString();
try {
if (divisors.length > 0 && value) {
var keys = Object.keys(divisorElements);
var elments = [];
for (var i = keys.length - 1; i >= 0; i--) {
var divisor = divisorElements[keys[i]];
if (divisor) {
elments.push(divisor);
}
}
elments = UtilService.uniqueArray(elments);
// remove if it is not pattern
var regex = new RegExp(('[' + '\\' + elments.join('\\') + ']'), 'g');
return value.replace(regex, '');
} else {
return value;
}
} catch (e) {
throw e;
}
}
function insertDivisors(array, combination) {
function insert(array, output) {
var out = output;
for (var i=0; i<array.length; i++) {
var divisor = array[i];
if (divisor < out.length) {
out.splice(divisor, 0, divisorElements[divisor]);
}
}
return out;
}
var output = array;
var divs = divisors.filter(function(it) {
var optionalDivisorsKeys = Object.keys(optionalDivisors).map(function(it){
return parseInt(it);
});
return !UtilService.inArray(it, combination) && !UtilService.inArray(it, optionalDivisorsKeys);
});
if (!angular.isArray(array) || !angular.isArray(combination)) {
return output;
}
// insert not optional divisors
output = insert(divs, output);
// insert optional divisors
output = insert(combination, output);
return output;
}
function tryDivisorConfiguration(value) {
var output = value.split('');
var defaultDivisors = true;
// has optional?
if (optionalIndexes.length > 0) {
var lazyArguments = [];
var optionalDivisorsKeys = Object.keys(optionalDivisors);
// get all optional divisors as array of arrays [[], [], []...]
for (var i=0; i<optionalDivisorsKeys.length; i++) {
var val = optionalDivisors[optionalDivisorsKeys[i]];
lazyArguments.push(val);
}
// generate all possible configurations
if (optionalDivisorsCombinations.length === 0) {
UtilService.lazyProduct(lazyArguments, function() {
// convert arguments to array
optionalDivisorsCombinations.push(Array.prototype.slice.call(arguments));
});
}
for (var i = optionalDivisorsCombinations.length - 1; i >= 0; i--) {
var outputClone = angular.copy(output);
outputClone = insertDivisors(outputClone, optionalDivisorsCombinations[i]);
// try validation
var viewValueWithDivisors = outputClone.join('');
var regex = getRegex(maskWithoutOptionals.length - 1);
if (regex.test(viewValueWithDivisors)) {
defaultDivisors = false;
output = outputClone;
break;
}
}
}
if (defaultDivisors) {
output = insertDivisors(output, divisors);
}
return output.join('');
}
// MASK
function getOptions() {
return options;
}
function getViewValue(value) {
try {
var outputWithoutDivisors = removeDivisors(value);
var output = tryDivisorConfiguration(outputWithoutDivisors);
return {
withDivisors: function(capped) {
if (capped) {
return output.substr(0, maskWithoutOptionalsLength);
} else {
return output;
}
},
withoutDivisors: function(capped) {
if (capped) {
return outputWithoutDivisors.substr(0, maskWithoutOptionalsAndDivisorsLength);
} else {
return outputWithoutDivisors;
}
}
};
} catch (e) {
throw e;
}
}
// SELECTOR
function getWrongPositions(viewValueWithDivisors, onlyFirst) {
var pos = [];
if (!viewValueWithDivisors) {
return 0;
}
for (var i=0; i<viewValueWithDivisors.length; i++){
var pattern = getRegex(i);
var value = viewValueWithDivisors.substr(0, (i+1));
if(pattern && !pattern.test(value)){
pos.push(i);
if (onlyFirst) {
break;
}
}
}
return pos;
}
function getFirstWrongPosition(viewValueWithDivisors) {
return getWrongPositions(viewValueWithDivisors, true)[0];
}
function removeWrongPositions(viewValueWithDivisors) {
var wrongPositions = getWrongPositions(viewValueWithDivisors, false);
var newViewValue = viewValueWithDivisors;
for(var i = 0; i < wrongPositions.length; i++){
var wrongPosition = wrongPositions[i];
var viewValueArray = viewValueWithDivisors.split('');
viewValueArray.splice(wrongPosition, 1);
newViewValue = viewValueArray.join('');
}
return getViewValue(newViewValue);
}
return {
getViewValue: getViewValue,
generateRegex: generateRegex,
getRegex: getRegex,
getOptions: getOptions,
removeDivisors: removeDivisors,
getFirstWrongPosition: getFirstWrongPosition,
removeWrongPositions: removeWrongPositions
}
}
return {
create: create
}
}]);
})();