mask-lexer.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602
  1. import $ from "./dependencyLibs/inputmask.dependencyLib";
  2. import escapeRegex from "./escapeRegex";
  3. import Inputmask from "./inputmask";
  4. import MaskToken from "./masktoken";
  5. export { generateMaskSet, analyseMask };
  6. function generateMaskSet(opts, nocache) {
  7. let ms;
  8. function preProcessMask(
  9. mask,
  10. { repeat, groupmarker, quantifiermarker, keepStatic }
  11. ) {
  12. if (repeat > 0 || repeat === "*" || repeat === "+") {
  13. const repeatStart = repeat === "*" ? 0 : repeat === "+" ? 1 : repeat;
  14. if (repeatStart != repeat) {
  15. mask =
  16. groupmarker[0] +
  17. mask +
  18. groupmarker[1] +
  19. quantifiermarker[0] +
  20. repeatStart +
  21. "," +
  22. repeat +
  23. quantifiermarker[1];
  24. } else {
  25. // repeat the mask n times
  26. const msk = mask;
  27. for (let i = 1; i < repeatStart; i++) {
  28. mask += msk;
  29. }
  30. }
  31. }
  32. if (keepStatic === true) {
  33. const optionalRegex = "(.)\\[([^\\]]*)\\]", // "(?<p1>.)\\[(?<p2>[^\\]]*)\\]", remove named capture group @2428
  34. maskMatches = mask.match(new RegExp(optionalRegex, "g"));
  35. maskMatches &&
  36. maskMatches.forEach((m, i) => {
  37. let [p1, p2] = m.split("[");
  38. p2 = p2.replace("]", "");
  39. mask = mask.replace(
  40. new RegExp(`${escapeRegex(p1)}\\[${escapeRegex(p2)}\\]`),
  41. p1.charAt(0) === p2.charAt(0)
  42. ? `(${p1}|${p1}${p2})`
  43. : `${p1}[${p2}]`
  44. );
  45. // console.log(mask);
  46. });
  47. }
  48. return mask;
  49. }
  50. function generateMask(mask, metadata, opts) {
  51. let regexMask = false;
  52. if (mask === null || mask === "") {
  53. regexMask = opts.regex !== null;
  54. if (regexMask) {
  55. mask = opts.regex;
  56. mask = mask.replace(/^(\^)(.*)(\$)$/, "$2");
  57. } else {
  58. regexMask = true;
  59. mask = ".*";
  60. }
  61. }
  62. if (mask.length === 1 && opts.greedy === false && opts.repeat !== 0) {
  63. opts.placeholder = "";
  64. } // hide placeholder with single non-greedy mask
  65. mask = preProcessMask(mask, opts);
  66. // console.log(mask);
  67. let masksetDefinition, maskdefKey;
  68. maskdefKey = regexMask
  69. ? "regex_" + opts.regex
  70. : opts.numericInput
  71. ? mask.split("").reverse().join("")
  72. : mask;
  73. if (opts.keepStatic !== null) {
  74. // keepstatic modifies the output from the testdefinitions ~ so differentiate in the maskcache
  75. maskdefKey = "ks_" + opts.keepStatic + maskdefKey;
  76. }
  77. if (typeof opts.placeholder === "object") {
  78. // placeholder object modifies the output from the testdefinitions ~ so differentiate in the maskcache
  79. maskdefKey = "ph_" + JSON.stringify(opts.placeholder) + maskdefKey;
  80. }
  81. if (
  82. Inputmask.prototype.masksCache[maskdefKey] === undefined ||
  83. nocache === true
  84. ) {
  85. masksetDefinition = {
  86. mask,
  87. maskToken: Inputmask.prototype.analyseMask(mask, regexMask, opts),
  88. validPositions: [],
  89. _buffer: undefined,
  90. buffer: undefined,
  91. tests: {},
  92. excludes: {}, // excluded alternations
  93. metadata,
  94. maskLength: undefined,
  95. jitOffset: {}
  96. };
  97. if (nocache !== true) {
  98. Inputmask.prototype.masksCache[maskdefKey] = masksetDefinition;
  99. masksetDefinition = $.extend(
  100. true,
  101. {},
  102. Inputmask.prototype.masksCache[maskdefKey]
  103. );
  104. }
  105. } else {
  106. masksetDefinition = $.extend(
  107. true,
  108. {},
  109. Inputmask.prototype.masksCache[maskdefKey]
  110. );
  111. }
  112. return masksetDefinition;
  113. }
  114. if (typeof opts.mask === "function") {
  115. // allow mask to be a preprocessing fn - should return a valid mask
  116. opts.mask = opts.mask(opts);
  117. }
  118. if (Array.isArray(opts.mask)) {
  119. if (opts.mask.length > 1) {
  120. if (opts.keepStatic === null) {
  121. // enable by default when passing multiple masks when the option is not explicitly specified
  122. opts.keepStatic = true;
  123. }
  124. let altMask = opts.groupmarker[0];
  125. (opts.isRTL ? opts.mask.reverse() : opts.mask).forEach(function (msk) {
  126. if (altMask.length > 1) {
  127. altMask += opts.alternatormarker;
  128. }
  129. if (msk.mask !== undefined && typeof msk.mask !== "function") {
  130. altMask += msk.mask;
  131. } else {
  132. altMask += msk;
  133. }
  134. });
  135. altMask += opts.groupmarker[1];
  136. // console.log(altMask);
  137. return generateMask(altMask, opts.mask, opts);
  138. } else {
  139. opts.mask = opts.mask.pop();
  140. }
  141. }
  142. if (
  143. opts.mask &&
  144. opts.mask.mask !== undefined &&
  145. typeof opts.mask.mask !== "function"
  146. ) {
  147. ms = generateMask(opts.mask.mask, opts.mask, opts);
  148. } else {
  149. ms = generateMask(opts.mask, opts.mask, opts);
  150. }
  151. if (opts.keepStatic === null) opts.keepStatic = false;
  152. return ms;
  153. }
  154. function analyseMask(mask, regexMask, opts) {
  155. const tokenizer =
  156. /(?:[?*+]|\{[0-9+*]+(?:,[0-9+*]*)?(?:\|[0-9+*]*)?\})|[^.?*+^${[]()|\\]+|./g,
  157. // Thx to https://github.com/slevithan/regex-colorizer for the regexTokenizer regex
  158. regexTokenizer =
  159. /\[\^?]?(?:[^\\\]]+|\\[\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;
  160. let escaped = false,
  161. currentToken = new MaskToken(),
  162. match,
  163. m,
  164. openenings = [],
  165. maskTokens = [],
  166. openingToken,
  167. currentOpeningToken,
  168. alternator,
  169. lastMatch,
  170. closeRegexGroup = false;
  171. // test definition => {fn: RegExp/function, static: true/false optionality: bool, newBlockMarker: bool, casing: null/upper/lower, def: definitionSymbol, placeholder: placeholder, mask: real maskDefinition}
  172. function insertTestDefinition(mtoken, element, position) {
  173. position = position !== undefined ? position : mtoken.matches.length;
  174. // console.log(element, position, currentToken.matches.length);
  175. // if (typeof opts.placeholder === "string")
  176. // console.log(opts.placeholder.charAt(currentToken.matches.length % opts.placeholder.length));
  177. let prevMatch = mtoken.matches[position - 1];
  178. if (regexMask) {
  179. if (
  180. element.indexOf("[") === 0 ||
  181. (escaped && /\\d|\\s|\\w|\\p/i.test(element)) ||
  182. element === "."
  183. ) {
  184. let flag = opts.casing ? "i" : "";
  185. if (/\\p\{.*}/i.test(element)) flag += "u";
  186. mtoken.matches.splice(position++, 0, {
  187. fn: new RegExp(element, flag),
  188. static: false,
  189. optionality: false,
  190. newBlockMarker:
  191. prevMatch === undefined ? "master" : prevMatch.def !== element,
  192. casing: null,
  193. def: element,
  194. placeholder:
  195. typeof opts.placeholder === "object"
  196. ? opts.placeholder[currentToken.matches.length]
  197. : undefined,
  198. nativeDef: element
  199. });
  200. } else {
  201. if (escaped) element = element[element.length - 1];
  202. element.split("").forEach(function (lmnt, ndx) {
  203. prevMatch = mtoken.matches[position - 1];
  204. mtoken.matches.splice(position++, 0, {
  205. fn: /[a-z]/i.test(opts.staticDefinitionSymbol || lmnt)
  206. ? new RegExp(
  207. "[" + (opts.staticDefinitionSymbol || lmnt) + "]",
  208. opts.casing ? "i" : ""
  209. )
  210. : null,
  211. static: true,
  212. optionality: false,
  213. newBlockMarker:
  214. prevMatch === undefined
  215. ? "master"
  216. : prevMatch.def !== lmnt && prevMatch.static !== true,
  217. casing: null,
  218. def: opts.staticDefinitionSymbol || lmnt,
  219. placeholder:
  220. opts.staticDefinitionSymbol !== undefined
  221. ? lmnt
  222. : typeof opts.placeholder === "object"
  223. ? opts.placeholder[currentToken.matches.length]
  224. : undefined,
  225. nativeDef: (escaped ? "'" : "") + lmnt
  226. });
  227. });
  228. }
  229. escaped = false;
  230. } else {
  231. const maskdef =
  232. (opts.definitions && opts.definitions[element]) ||
  233. (opts.usePrototypeDefinitions &&
  234. Inputmask.prototype.definitions[element]);
  235. if (maskdef && !escaped) {
  236. mtoken.matches.splice(position++, 0, {
  237. fn: maskdef.validator
  238. ? typeof maskdef.validator === "string"
  239. ? new RegExp(maskdef.validator, opts.casing ? "i" : "")
  240. : new (function () {
  241. this.test = maskdef.validator;
  242. })()
  243. : /./,
  244. static: maskdef.static || false,
  245. optionality: maskdef.optional || false,
  246. defOptionality: maskdef.optional || false, // indicator for an optional from the definition
  247. newBlockMarker:
  248. prevMatch === undefined || maskdef.optional
  249. ? "master"
  250. : prevMatch.def !== (maskdef.definitionSymbol || element),
  251. casing: maskdef.casing,
  252. def: maskdef.definitionSymbol || element,
  253. placeholder: maskdef.placeholder,
  254. nativeDef: element,
  255. generated: maskdef.generated
  256. });
  257. } else {
  258. mtoken.matches.splice(position++, 0, {
  259. fn: /[a-z]/i.test(opts.staticDefinitionSymbol || element)
  260. ? new RegExp(
  261. "[" + (opts.staticDefinitionSymbol || element) + "]",
  262. opts.casing ? "i" : ""
  263. )
  264. : null,
  265. static: true,
  266. optionality: false,
  267. newBlockMarker:
  268. prevMatch === undefined
  269. ? "master"
  270. : prevMatch.def !== element && prevMatch.static !== true,
  271. casing: null,
  272. def: opts.staticDefinitionSymbol || element,
  273. placeholder:
  274. opts.staticDefinitionSymbol !== undefined ? element : undefined,
  275. nativeDef: (escaped ? "'" : "") + element
  276. });
  277. escaped = false;
  278. }
  279. }
  280. }
  281. function verifyGroupMarker(maskToken) {
  282. if (maskToken && maskToken.matches) {
  283. maskToken.matches.forEach(function (token, ndx) {
  284. const nextToken = maskToken.matches[ndx + 1];
  285. if (
  286. (nextToken === undefined ||
  287. nextToken.matches === undefined ||
  288. nextToken.isQuantifier === false) &&
  289. token &&
  290. token.isGroup
  291. ) {
  292. // this is not a group but a normal mask => convert
  293. token.isGroup = false;
  294. if (!regexMask) {
  295. insertTestDefinition(token, opts.groupmarker[0], 0);
  296. if (token.openGroup !== true) {
  297. insertTestDefinition(token, opts.groupmarker[1]);
  298. }
  299. }
  300. }
  301. verifyGroupMarker(token);
  302. });
  303. }
  304. }
  305. function defaultCase() {
  306. if (openenings.length > 0) {
  307. currentOpeningToken = openenings[openenings.length - 1];
  308. insertTestDefinition(currentOpeningToken, m);
  309. if (currentOpeningToken.isAlternator) {
  310. // handle alternator a | b case
  311. alternator = openenings.pop();
  312. for (let mndx = 0; mndx < alternator.matches.length; mndx++) {
  313. if (alternator.matches[mndx].isGroup)
  314. alternator.matches[mndx].isGroup = false; // don't mark alternate groups as group
  315. }
  316. if (openenings.length > 0) {
  317. currentOpeningToken = openenings[openenings.length - 1];
  318. currentOpeningToken.matches.push(alternator);
  319. } else {
  320. currentToken.matches.push(alternator);
  321. }
  322. }
  323. } else {
  324. insertTestDefinition(currentToken, m);
  325. }
  326. }
  327. function reverseTokens(maskToken) {
  328. function reverseStatic(st) {
  329. if (st === opts.optionalmarker[0]) {
  330. st = opts.optionalmarker[1];
  331. } else if (st === opts.optionalmarker[1]) {
  332. st = opts.optionalmarker[0];
  333. } else if (st === opts.groupmarker[0]) {
  334. st = opts.groupmarker[1];
  335. } else if (st === opts.groupmarker[1]) st = opts.groupmarker[0];
  336. return st;
  337. }
  338. maskToken.matches = maskToken.matches.reverse();
  339. for (const match in maskToken.matches) {
  340. if (Object.prototype.hasOwnProperty.call(maskToken.matches, match)) {
  341. const intMatch = parseInt(match);
  342. if (
  343. maskToken.matches[match].isQuantifier &&
  344. maskToken.matches[intMatch + 1] &&
  345. maskToken.matches[intMatch + 1].isGroup
  346. ) {
  347. // reposition quantifier
  348. const qt = maskToken.matches[match];
  349. maskToken.matches.splice(match, 1);
  350. maskToken.matches.splice(intMatch + 1, 0, qt);
  351. }
  352. if (maskToken.matches[match].matches !== undefined) {
  353. maskToken.matches[match] = reverseTokens(maskToken.matches[match]);
  354. } else {
  355. maskToken.matches[match] = reverseStatic(maskToken.matches[match]);
  356. }
  357. }
  358. }
  359. return maskToken;
  360. }
  361. function groupify(matches) {
  362. const groupToken = new MaskToken(true);
  363. groupToken.openGroup = false;
  364. groupToken.matches = matches;
  365. return groupToken;
  366. }
  367. function closeGroup() {
  368. // Group closing
  369. openingToken = openenings.pop();
  370. openingToken.openGroup = false; // mark group as complete
  371. if (openingToken !== undefined) {
  372. if (openenings.length > 0) {
  373. currentOpeningToken = openenings[openenings.length - 1];
  374. currentOpeningToken.matches.push(openingToken);
  375. if (currentOpeningToken.isAlternator) {
  376. // handle alternator (a) | (b) case
  377. alternator = openenings.pop();
  378. for (let mndx = 0; mndx < alternator.matches.length; mndx++) {
  379. alternator.matches[mndx].isGroup = false; // don't mark alternate groups as group
  380. alternator.matches[mndx].alternatorGroup = false;
  381. }
  382. if (openenings.length > 0) {
  383. currentOpeningToken = openenings[openenings.length - 1];
  384. currentOpeningToken.matches.push(alternator);
  385. } else {
  386. currentToken.matches.push(alternator);
  387. }
  388. }
  389. } else {
  390. currentToken.matches.push(openingToken);
  391. }
  392. } else {
  393. defaultCase();
  394. }
  395. }
  396. function groupQuantifier(matches) {
  397. let lastMatch = matches.pop();
  398. if (lastMatch.isQuantifier) {
  399. lastMatch = groupify([matches.pop(), lastMatch]);
  400. }
  401. return lastMatch;
  402. }
  403. if (regexMask) {
  404. opts.optionalmarker[0] = undefined;
  405. opts.optionalmarker[1] = undefined;
  406. }
  407. // console.log(mask);
  408. while (
  409. (match = regexMask ? regexTokenizer.exec(mask) : tokenizer.exec(mask))
  410. ) {
  411. // console.log(match);
  412. m = match[0];
  413. if (regexMask) {
  414. switch (m.charAt(0)) {
  415. // Quantifier
  416. case "?":
  417. m = "{0,1}";
  418. break;
  419. case "+":
  420. case "*":
  421. m = "{" + m + "}";
  422. break;
  423. case "|":
  424. // regex mask alternator ex: [01][0-9]|2[0-3] => ([01][0-9]|2[0-3])
  425. if (openenings.length === 0) {
  426. // wrap the mask in a group to form a regex alternator ([01][0-9]|2[0-3])
  427. const altRegexGroup = groupify(currentToken.matches);
  428. altRegexGroup.openGroup = true;
  429. openenings.push(altRegexGroup);
  430. currentToken.matches = [];
  431. closeRegexGroup = true;
  432. }
  433. break;
  434. }
  435. switch (m) {
  436. case "\\d":
  437. m = "[0-9]";
  438. break;
  439. case "\\p": // Unicode Categories
  440. m += regexTokenizer.exec(mask)[0]; // {
  441. m += regexTokenizer.exec(mask)[0]; // ?}
  442. break;
  443. case "(?:": // non capturing group
  444. case "(?=": // lookahead
  445. case "(?!": // negative lookahead
  446. case "(?<=": // lookbehind
  447. case "(?<!": // negative lookbehind
  448. // treat as group
  449. break;
  450. }
  451. }
  452. if (escaped) {
  453. defaultCase();
  454. continue;
  455. }
  456. switch (m.charAt(0)) {
  457. case "$":
  458. case "^":
  459. // ignore beginswith and endswith as in masking this makes no point
  460. if (!regexMask) {
  461. defaultCase();
  462. }
  463. break;
  464. case opts.escapeChar:
  465. escaped = true;
  466. if (regexMask) defaultCase();
  467. break;
  468. // optional closing
  469. case opts.optionalmarker[1]:
  470. case opts.groupmarker[1]:
  471. closeGroup();
  472. break;
  473. case opts.optionalmarker[0]:
  474. // optional opening
  475. openenings.push(new MaskToken(false, true));
  476. break;
  477. case opts.groupmarker[0]:
  478. // Group opening
  479. openenings.push(new MaskToken(true));
  480. break;
  481. case opts.quantifiermarker[0]:
  482. // Quantifier
  483. var quantifier = new MaskToken(false, false, true);
  484. m = m.replace(/[{}?]/g, ""); // ? matches lazy quantifiers
  485. var mqj = m.split("|"),
  486. mq = mqj[0].split(","),
  487. mq0 = isNaN(mq[0]) ? mq[0] : parseInt(mq[0]),
  488. mq1 = mq.length === 1 ? mq0 : isNaN(mq[1]) ? mq[1] : parseInt(mq[1]),
  489. mqJit = isNaN(mqj[1]) ? mqj[1] : parseInt(mqj[1]);
  490. if (mq0 === "*" || mq0 === "+") {
  491. mq0 = mq1 === "*" ? 0 : 1;
  492. }
  493. quantifier.quantifier = {
  494. min: mq0,
  495. max: mq1,
  496. jit: mqJit
  497. };
  498. var matches =
  499. openenings.length > 0
  500. ? openenings[openenings.length - 1].matches
  501. : currentToken.matches;
  502. match = matches.pop();
  503. // if (match.isAlternator) { //handle quantifier in an alternation [0-9]{2}|[0-9]{3}
  504. // matches.push(match); //push back alternator
  505. // matches = match.matches; //remap target matches
  506. // var groupToken = new MaskToken(true);
  507. // var tmpMatch = matches.pop();
  508. // matches.push(groupToken); //push the group
  509. // matches = groupToken.matches;
  510. // match = tmpMatch;
  511. // }
  512. if (!match.isGroup) {
  513. match = groupify([match]);
  514. }
  515. matches.push(match);
  516. matches.push(quantifier);
  517. break;
  518. case opts.alternatormarker:
  519. if (openenings.length > 0) {
  520. currentOpeningToken = openenings[openenings.length - 1];
  521. const subToken =
  522. currentOpeningToken.matches[currentOpeningToken.matches.length - 1];
  523. if (
  524. currentOpeningToken.openGroup && // regexp alt syntax
  525. (subToken.matches === undefined ||
  526. (subToken.isGroup === false && subToken.isAlternator === false))
  527. ) {
  528. // alternations within group
  529. lastMatch = openenings.pop();
  530. } else {
  531. lastMatch = groupQuantifier(currentOpeningToken.matches);
  532. }
  533. } else {
  534. lastMatch = groupQuantifier(currentToken.matches);
  535. }
  536. if (lastMatch.isAlternator) {
  537. openenings.push(lastMatch);
  538. } else {
  539. if (lastMatch.alternatorGroup) {
  540. alternator = openenings.pop();
  541. lastMatch.alternatorGroup = false;
  542. } else {
  543. alternator = new MaskToken(false, false, false, true);
  544. }
  545. alternator.matches.push(lastMatch);
  546. openenings.push(alternator);
  547. if (lastMatch.openGroup) {
  548. // regexp alt syntax
  549. lastMatch.openGroup = false;
  550. const alternatorGroup = new MaskToken(true);
  551. alternatorGroup.alternatorGroup = true;
  552. openenings.push(alternatorGroup);
  553. }
  554. }
  555. break;
  556. default:
  557. defaultCase();
  558. }
  559. }
  560. if (closeRegexGroup) closeGroup();
  561. while (openenings.length > 0) {
  562. openingToken = openenings.pop();
  563. currentToken.matches.push(openingToken);
  564. }
  565. if (currentToken.matches.length > 0) {
  566. verifyGroupMarker(currentToken);
  567. maskTokens.push(currentToken);
  568. }
  569. if (opts.numericInput || opts.isRTL) {
  570. reverseTokens(maskTokens[0]);
  571. }
  572. // console.log(JSON.stringify(maskTokens));
  573. return maskTokens;
  574. }