2025-09-24 06:24:52 +00:00
import $ from "./dependencyLibs/inputmask.dependencyLib" ;
import MaskToken from "./masktoken" ;
2025-10-03 11:00:05 +00:00
import Inputmask from "./inputmask" ;
import escapeRegex from "./escapeRegex" ;
2025-09-24 06:24:52 +00:00
2025-10-03 11:00:05 +00:00
export { generateMaskSet , analyseMask } ;
2025-09-24 06:24:52 +00:00
function generateMaskSet ( opts , nocache ) {
2025-10-03 11:00:05 +00:00
var ms ;
2025-09-24 06:24:52 +00:00
2025-10-03 11:00:05 +00:00
function preProcessMask ( mask , { repeat , groupmarker , quantifiermarker , keepStatic } ) {
if ( repeat > 0 || repeat === "*" || repeat === "+" ) {
var repeatStart = repeat === "*" ? 0 : ( repeat === "+" ? 1 : repeat ) ;
mask = groupmarker [ 0 ] + mask + groupmarker [ 1 ] + quantifiermarker [ 0 ] + repeatStart + "," + repeat + quantifiermarker [ 1 ] ;
2025-09-24 06:24:52 +00:00
}
2025-10-03 11:00:05 +00:00
if ( keepStatic === true ) {
let optionalRegex = "(.)\\[([^\\]]*)\\]" , // "(?<p1>.)\\[(?<p2>[^\\]]*)\\]", remove named capture group @2428
maskMatches = mask . match ( new RegExp ( optionalRegex , "g" ) ) ;
maskMatches && maskMatches . forEach ( ( m , i ) => {
let [ p1 , p2 ] = m . split ( "[" ) ; p2 = p2 . replace ( "]" , "" ) ;
mask = mask . replace ( new RegExp ( ` ${ escapeRegex ( p1 ) } \\ [ ${ escapeRegex ( p2 ) } \\ ] ` ) ,
p1 . charAt ( 0 ) === p2 . charAt ( 0 ) ?
` ( ${ p1 } | ${ p1 } ${ p2 } ) ` :
` ${ p1 } [ ${ p2 } ] ` ) ;
// console.log(mask);
} ) ;
}
return mask ;
2025-09-24 06:24:52 +00:00
}
2025-10-03 11:00:05 +00:00
function generateMask ( mask , metadata , opts ) {
var regexMask = false ;
if ( mask === null || mask === "" ) {
regexMask = opts . regex !== null ;
if ( regexMask ) {
mask = opts . regex ;
mask = mask . replace ( /^(\^)(.*)(\$)$/ , "$2" ) ;
} else {
regexMask = true ;
mask = ".*" ;
}
}
if ( mask . length === 1 && opts . greedy === false && opts . repeat !== 0 ) {
opts . placeholder = "" ;
} //hide placeholder with single non-greedy mask
mask = preProcessMask ( mask , opts ) ;
2025-09-24 06:24:52 +00:00
2025-10-03 11:00:05 +00:00
// console.log(mask);
var masksetDefinition , maskdefKey ;
maskdefKey = regexMask ? "regex_" + opts . regex : opts . numericInput ? mask . split ( "" ) . reverse ( ) . join ( "" ) : mask ;
if ( opts . keepStatic !== null ) { //keepstatic modifies the output from the testdefinitions ~ so differentiate in the maskcache
maskdefKey = "ks_" + opts . keepStatic + maskdefKey ;
}
2025-09-24 06:24:52 +00:00
2025-10-03 11:00:05 +00:00
if ( Inputmask . prototype . masksCache [ maskdefKey ] === undefined || nocache === true ) {
masksetDefinition = {
"mask" : mask ,
"maskToken" : Inputmask . prototype . analyseMask ( mask , regexMask , opts ) ,
"validPositions" : [ ] ,
"_buffer" : undefined ,
"buffer" : undefined ,
"tests" : { } ,
"excludes" : { } , //excluded alternations
"metadata" : metadata ,
"maskLength" : undefined ,
"jitOffset" : { }
} ;
if ( nocache !== true ) {
Inputmask . prototype . masksCache [ maskdefKey ] = masksetDefinition ;
masksetDefinition = $ . extend ( true , { } , Inputmask . prototype . masksCache [ maskdefKey ] ) ;
}
} else {
masksetDefinition = $ . extend ( true , { } , Inputmask . prototype . masksCache [ maskdefKey ] ) ;
}
2025-09-24 06:24:52 +00:00
2025-10-03 11:00:05 +00:00
return masksetDefinition ;
2025-09-24 06:24:52 +00:00
}
2025-10-03 11:00:05 +00:00
if ( typeof opts . mask === "function" ) { //allow mask to be a preprocessing fn - should return a valid mask
opts . mask = opts . mask ( opts ) ;
}
if ( Array . isArray ( opts . mask ) ) {
if ( opts . mask . length > 1 ) {
if ( opts . keepStatic === null ) { //enable by default when passing multiple masks when the option is not explicitly specified
opts . keepStatic = true ;
}
var altMask = opts . groupmarker [ 0 ] ;
( opts . isRTL ? opts . mask . reverse ( ) : opts . mask ) . forEach ( function ( msk ) {
if ( altMask . length > 1 ) {
altMask += opts . alternatormarker ;
}
if ( msk . mask !== undefined && typeof msk . mask !== "function" ) {
altMask += msk . mask ;
} else {
altMask += msk ;
}
} ) ;
altMask += opts . groupmarker [ 1 ] ;
// console.log(altMask);
return generateMask ( altMask , opts . mask , opts ) ;
2025-09-24 06:24:52 +00:00
} else {
2025-10-03 11:00:05 +00:00
opts . mask = opts . mask . pop ( ) ;
2025-09-24 06:24:52 +00:00
}
2025-10-03 11:00:05 +00:00
}
if ( opts . mask && opts . mask . mask !== undefined && typeof opts . mask . mask !== "function" ) {
ms = generateMask ( opts . mask . mask , opts . mask , opts ) ;
2025-09-24 06:24:52 +00:00
} else {
2025-10-03 11:00:05 +00:00
ms = generateMask ( opts . mask , opts . mask , opts ) ;
2025-09-24 06:24:52 +00:00
}
2025-10-03 11:00:05 +00:00
if ( opts . keepStatic === null ) opts . keepStatic = false ;
return ms ;
2025-09-24 06:24:52 +00:00
}
function analyseMask ( mask , regexMask , opts ) {
2025-10-03 11:00:05 +00:00
const tokenizer = /(?:[?*+]|\{[0-9+*]+(?:,[0-9+*]*)?(?:\|[0-9+*]*)?\})|[^.?*+^${[]()|\\]+|./g ,
//Thx to https://github.com/slevithan/regex-colorizer for the regexTokenizer regex
regexTokenizer = /\[\^?]?(?:[^\\\]]+|\\[\S\s]?)*]?|\\(?:0(?:[0-3][0-7]{0,2}|[4-7][0-7]?)?|[1-9][0-9]*|x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4}|c[A-Za-z]|[\S\s]?)|\((?:\?[:=!]?)?|(?:[?*+]|\{[0-9]+(?:,[0-9]*)?\})\??|[^.?*+^${[()|\\]+|./g ;
var escaped = false ,
currentToken = new MaskToken ( ) ,
match ,
m ,
openenings = [ ] ,
maskTokens = [ ] ,
openingToken ,
currentOpeningToken ,
alternator ,
lastMatch ,
closeRegexGroup = false ;
2025-09-24 06:24:52 +00:00
2025-10-03 11:00:05 +00:00
//test definition => {fn: RegExp/function, static: true/false optionality: bool, newBlockMarker: bool, casing: null/upper/lower, def: definitionSymbol, placeholder: placeholder, mask: real maskDefinition}
function insertTestDefinition ( mtoken , element , position ) {
position = position !== undefined ? position : mtoken . matches . length ;
var prevMatch = mtoken . matches [ position - 1 ] ;
if ( regexMask ) {
if ( element . indexOf ( "[" ) === 0 || ( escaped && /\\d|\\s|\\w|\\p/i . test ( element ) ) || element === "." ) {
let flag = opts . casing ? "i" : "" ;
if ( /^\\p\{.*}$/i . test ( element ) )
flag += "u" ;
mtoken . matches . splice ( position ++ , 0 , {
fn : new RegExp ( element , flag ) ,
static : false ,
optionality : false ,
newBlockMarker : prevMatch === undefined ? "master" : prevMatch . def !== element ,
casing : null ,
def : element ,
placeholder : undefined ,
nativeDef : element
} ) ;
} else {
if ( escaped ) element = element [ element . length - 1 ] ;
element . split ( "" ) . forEach ( function ( lmnt , ndx ) {
prevMatch = mtoken . matches [ position - 1 ] ;
mtoken . matches . splice ( position ++ , 0 , {
fn : /[a-z]/i . test ( ( opts . staticDefinitionSymbol || lmnt ) ) ? new RegExp ( "[" + ( opts . staticDefinitionSymbol || lmnt ) + "]" , opts . casing ? "i" : "" ) : null ,
static : true ,
optionality : false ,
newBlockMarker : prevMatch === undefined ? "master" : ( prevMatch . def !== lmnt && prevMatch . static !== true ) ,
casing : null ,
def : opts . staticDefinitionSymbol || lmnt ,
placeholder : opts . staticDefinitionSymbol !== undefined ? lmnt : undefined ,
nativeDef : ( escaped ? "'" : "" ) + lmnt
} ) ;
} ) ;
}
escaped = false ;
} else {
var maskdef = ( opts . definitions && opts . definitions [ element ] ) || ( opts . usePrototypeDefinitions && Inputmask . prototype . definitions [ element ] ) ;
if ( maskdef && ! escaped ) {
mtoken . matches . splice ( position ++ , 0 , {
fn : maskdef . validator ? typeof maskdef . validator == "string" ? new RegExp ( maskdef . validator , opts . casing ? "i" : "" ) : new function ( ) {
this . test = maskdef . validator ;
} : new RegExp ( "." ) ,
static : maskdef . static || false ,
optionality : maskdef . optional || false ,
defOptionality : maskdef . optional || false , //indicator for an optional from the definition
newBlockMarker : ( prevMatch === undefined || maskdef . optional ) ? "master" : prevMatch . def !== ( maskdef . definitionSymbol || element ) ,
casing : maskdef . casing ,
def : maskdef . definitionSymbol || element ,
placeholder : maskdef . placeholder ,
nativeDef : element ,
generated : maskdef . generated
} ) ;
} else {
mtoken . matches . splice ( position ++ , 0 , {
fn : /[a-z]/i . test ( ( opts . staticDefinitionSymbol || element ) ) ? new RegExp ( "[" + ( opts . staticDefinitionSymbol || element ) + "]" , opts . casing ? "i" : "" ) : null ,
static : true ,
optionality : false ,
newBlockMarker : prevMatch === undefined ? "master" : ( prevMatch . def !== element && prevMatch . static !== true ) ,
casing : null ,
def : opts . staticDefinitionSymbol || element ,
placeholder : opts . staticDefinitionSymbol !== undefined ? element : undefined ,
nativeDef : ( escaped ? "'" : "" ) + element
} ) ;
escaped = false ;
2025-09-24 06:24:52 +00:00
}
}
}
2025-10-03 11:00:05 +00:00
function verifyGroupMarker ( maskToken ) {
if ( maskToken && maskToken . matches ) {
maskToken . matches . forEach ( function ( token , ndx ) {
var nextToken = maskToken . matches [ ndx + 1 ] ;
if ( ( nextToken === undefined || ( nextToken . matches === undefined || nextToken . isQuantifier === false ) ) && token && token . isGroup ) { //this is not a group but a normal mask => convert
token . isGroup = false ;
if ( ! regexMask ) {
insertTestDefinition ( token , opts . groupmarker [ 0 ] , 0 ) ;
if ( token . openGroup !== true ) {
insertTestDefinition ( token , opts . groupmarker [ 1 ] ) ;
}
}
}
verifyGroupMarker ( token ) ;
} ) ;
2025-09-24 06:24:52 +00:00
}
2025-10-03 11:00:05 +00:00
}
function defaultCase ( ) {
2025-09-24 06:24:52 +00:00
if ( openenings . length > 0 ) {
2025-10-03 11:00:05 +00:00
currentOpeningToken = openenings [ openenings . length - 1 ] ;
insertTestDefinition ( currentOpeningToken , m ) ;
if ( currentOpeningToken . isAlternator ) { //handle alternator a | b case
alternator = openenings . pop ( ) ;
for ( var mndx = 0 ; mndx < alternator . matches . length ; mndx ++ ) {
if ( alternator . matches [ mndx ] . isGroup ) alternator . matches [ mndx ] . isGroup = false ; //don't mark alternate groups as group
}
if ( openenings . length > 0 ) {
currentOpeningToken = openenings [ openenings . length - 1 ] ;
currentOpeningToken . matches . push ( alternator ) ;
} else {
currentToken . matches . push ( alternator ) ;
}
}
2025-09-24 06:24:52 +00:00
} else {
2025-10-03 11:00:05 +00:00
insertTestDefinition ( currentToken , m ) ;
2025-09-24 06:24:52 +00:00
}
}
2025-10-03 11:00:05 +00:00
function reverseTokens ( maskToken ) {
function reverseStatic ( st ) {
if ( st === opts . optionalmarker [ 0 ] ) {
st = opts . optionalmarker [ 1 ] ;
} else if ( st === opts . optionalmarker [ 1 ] ) {
st = opts . optionalmarker [ 0 ] ;
} else if ( st === opts . groupmarker [ 0 ] ) {
st = opts . groupmarker [ 1 ] ;
} else if ( st === opts . groupmarker [ 1 ] ) st = opts . groupmarker [ 0 ] ;
2025-09-24 06:24:52 +00:00
2025-10-03 11:00:05 +00:00
return st ;
2025-09-24 06:24:52 +00:00
}
2025-10-03 11:00:05 +00:00
maskToken . matches = maskToken . matches . reverse ( ) ;
for ( var match in maskToken . matches ) {
if ( Object . prototype . hasOwnProperty . call ( maskToken . matches , match ) ) {
var intMatch = parseInt ( match ) ;
if ( maskToken . matches [ match ] . isQuantifier && maskToken . matches [ intMatch + 1 ] && maskToken . matches [ intMatch + 1 ] . isGroup ) { //reposition quantifier
var qt = maskToken . matches [ match ] ;
maskToken . matches . splice ( match , 1 ) ;
maskToken . matches . splice ( intMatch + 1 , 0 , qt ) ;
}
if ( maskToken . matches [ match ] . matches !== undefined ) {
maskToken . matches [ match ] = reverseTokens ( maskToken . matches [ match ] ) ;
} else {
maskToken . matches [ match ] = reverseStatic ( maskToken . matches [ match ] ) ;
}
}
2025-09-24 06:24:52 +00:00
}
2025-10-03 11:00:05 +00:00
return maskToken ;
}
2025-09-24 06:24:52 +00:00
2025-10-03 11:00:05 +00:00
function groupify ( matches ) {
var groupToken = new MaskToken ( true ) ;
groupToken . openGroup = false ;
groupToken . matches = matches ;
return groupToken ;
}
2025-09-24 06:24:52 +00:00
2025-10-03 11:00:05 +00:00
function closeGroup ( ) {
// Group closing
openingToken = openenings . pop ( ) ;
openingToken . openGroup = false ; //mark group as complete
if ( openingToken !== undefined ) {
if ( openenings . length > 0 ) {
currentOpeningToken = openenings [ openenings . length - 1 ] ;
currentOpeningToken . matches . push ( openingToken ) ;
if ( currentOpeningToken . isAlternator ) { //handle alternator (a) | (b) case
alternator = openenings . pop ( ) ;
let altMatchesLength = alternator . matches [ 0 ] . matches ? alternator . matches [ 0 ] . matches . length : 1 ;
for ( var mndx = 0 ; mndx < alternator . matches . length ; mndx ++ ) {
alternator . matches [ mndx ] . isGroup = false ; //don't mark alternate groups as group
alternator . matches [ mndx ] . alternatorGroup = false ;
if ( opts . keepStatic === null && altMatchesLength < ( alternator . matches [ mndx ] . matches ? alternator . matches [ mndx ] . matches . length : 1 ) ) { //enable by default when passing multiple masks when the option is not explicitly specified
opts . keepStatic = true ;
}
altMatchesLength = alternator . matches [ mndx ] . matches ? alternator . matches [ mndx ] . matches . length : 1 ;
}
if ( openenings . length > 0 ) {
currentOpeningToken = openenings [ openenings . length - 1 ] ;
currentOpeningToken . matches . push ( alternator ) ;
} else {
currentToken . matches . push ( alternator ) ;
}
}
} else {
currentToken . matches . push ( openingToken ) ;
}
} else {
defaultCase ( ) ;
2025-09-24 06:24:52 +00:00
}
}
2025-10-03 11:00:05 +00:00
function groupQuantifier ( matches ) {
var lastMatch = matches . pop ( ) ;
if ( lastMatch . isQuantifier ) {
lastMatch = groupify ( [ matches . pop ( ) , lastMatch ] ) ;
}
return lastMatch ;
2025-09-24 06:24:52 +00:00
}
if ( regexMask ) {
2025-10-03 11:00:05 +00:00
opts . optionalmarker [ 0 ] = undefined ;
opts . optionalmarker [ 1 ] = undefined ;
2025-09-24 06:24:52 +00:00
}
2025-10-03 11:00:05 +00:00
while ( ( match = regexMask ? regexTokenizer . exec ( mask ) : tokenizer . exec ( mask ) ) ) {
m = match [ 0 ] ;
2025-09-24 06:24:52 +00:00
2025-10-03 11:00:05 +00:00
if ( regexMask ) {
switch ( m . charAt ( 0 ) ) {
//Quantifier
case "?" :
m = "{0,1}" ;
break ;
case "+" :
case "*" :
m = "{" + m + "}" ;
break ;
case "|" :
//regex mask alternator ex: [01][0-9]|2[0-3] => ([01][0-9]|2[0-3])
if ( openenings . length === 0 ) { //wrap the mask in a group to form a regex alternator ([01][0-9]|2[0-3])
var altRegexGroup = groupify ( currentToken . matches ) ;
altRegexGroup . openGroup = true ;
openenings . push ( altRegexGroup ) ;
currentToken . matches = [ ] ;
closeRegexGroup = true ;
}
break ;
}
switch ( m ) {
case "\\d" :
m = "[0-9]" ;
break ;
case "\\p" : //Unicode Categories
m += regexTokenizer . exec ( mask ) [ 0 ] ; // {
m += regexTokenizer . exec ( mask ) [ 0 ] ; // ?}
break ;
case "(?:" : //non capturing group
case "(?=" : //lookahead
case "(?!" : //negative lookahead
case "(?<=" : //lookbehind
case "(?<!" : //negative lookbehind
// treat as group
break ;
}
2025-09-24 06:24:52 +00:00
}
2025-10-03 11:00:05 +00:00
if ( escaped ) {
defaultCase ( ) ;
continue ;
2025-09-24 06:24:52 +00:00
}
2025-10-03 11:00:05 +00:00
switch ( m . charAt ( 0 ) ) {
case "$" :
case "^" :
//ignore beginswith and endswith as in masking this makes no point
if ( ! regexMask ) {
defaultCase ( ) ;
}
break ;
case opts . escapeChar :
escaped = true ;
if ( regexMask ) defaultCase ( ) ;
break ;
// optional closing
case opts . optionalmarker [ 1 ] :
case opts . groupmarker [ 1 ] :
closeGroup ( ) ;
break ;
case opts . optionalmarker [ 0 ] :
// optional opening
openenings . push ( new MaskToken ( false , true ) ) ;
break ;
case opts . groupmarker [ 0 ] :
// Group opening
openenings . push ( new MaskToken ( true ) ) ;
break ;
case opts . quantifiermarker [ 0 ] :
//Quantifier
var quantifier = new MaskToken ( false , false , true ) ;
2025-09-24 06:24:52 +00:00
2025-10-03 11:00:05 +00:00
m = m . replace ( /[{}?]/g , "" ) ; //? matches lazy quantifiers
var mqj = m . split ( "|" ) ,
mq = mqj [ 0 ] . split ( "," ) ,
mq0 = isNaN ( mq [ 0 ] ) ? mq [ 0 ] : parseInt ( mq [ 0 ] ) ,
mq1 = mq . length === 1 ? mq0 : ( isNaN ( mq [ 1 ] ) ? mq [ 1 ] : parseInt ( mq [ 1 ] ) ) ,
mqJit = isNaN ( mqj [ 1 ] ) ? mqj [ 1 ] : parseInt ( mqj [ 1 ] ) ;
if ( mq0 === "*" || mq0 === "+" ) {
mq0 = mq1 === "*" ? 0 : 1 ;
}
quantifier . quantifier = {
min : mq0 ,
max : mq1 ,
jit : mqJit
} ;
var matches = openenings . length > 0 ? openenings [ openenings . length - 1 ] . matches : currentToken . matches ;
match = matches . pop ( ) ;
// if (match.isAlternator) { //handle quantifier in an alternation [0-9]{2}|[0-9]{3}
// matches.push(match); //push back alternator
// matches = match.matches; //remap target matches
// var groupToken = new MaskToken(true);
// var tmpMatch = matches.pop();
// matches.push(groupToken); //push the group
// matches = groupToken.matches;
// match = tmpMatch;
// }
if ( ! match . isGroup ) {
match = groupify ( [ match ] ) ;
}
matches . push ( match ) ;
matches . push ( quantifier ) ;
break ;
case opts . alternatormarker :
if ( openenings . length > 0 ) {
currentOpeningToken = openenings [ openenings . length - 1 ] ;
var subToken = currentOpeningToken . matches [ currentOpeningToken . matches . length - 1 ] ;
if ( currentOpeningToken . openGroup && //regexp alt syntax
( subToken . matches === undefined || ( subToken . isGroup === false && subToken . isAlternator === false ) ) ) { //alternations within group
lastMatch = openenings . pop ( ) ;
} else {
lastMatch = groupQuantifier ( currentOpeningToken . matches ) ;
}
} else {
lastMatch = groupQuantifier ( currentToken . matches ) ;
}
if ( lastMatch . isAlternator ) {
openenings . push ( lastMatch ) ;
} else {
if ( lastMatch . alternatorGroup ) {
alternator = openenings . pop ( ) ;
lastMatch . alternatorGroup = false ;
} else {
alternator = new MaskToken ( false , false , false , true ) ;
}
alternator . matches . push ( lastMatch ) ;
openenings . push ( alternator ) ;
if ( lastMatch . openGroup ) { //regexp alt syntax
lastMatch . openGroup = false ;
var alternatorGroup = new MaskToken ( true ) ;
alternatorGroup . alternatorGroup = true ;
openenings . push ( alternatorGroup ) ;
}
}
break ;
default :
defaultCase ( ) ;
2025-09-24 06:24:52 +00:00
}
}
2025-10-03 11:00:05 +00:00
if ( closeRegexGroup ) closeGroup ( ) ;
2025-09-24 06:24:52 +00:00
2025-10-03 11:00:05 +00:00
while ( openenings . length > 0 ) {
openingToken = openenings . pop ( ) ;
currentToken . matches . push ( openingToken ) ;
}
if ( currentToken . matches . length > 0 ) {
verifyGroupMarker ( currentToken ) ;
maskTokens . push ( currentToken ) ;
}
2025-09-24 06:24:52 +00:00
2025-10-03 11:00:05 +00:00
if ( opts . numericInput || opts . isRTL ) {
reverseTokens ( maskTokens [ 0 ] ) ;
}
// console.log(JSON.stringify(maskTokens));
return maskTokens ;
2025-09-24 06:24:52 +00:00
}