validation-tests.js 36 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085
  1. import Inputmask from "./inputmask";
  2. import { getLastValidPosition, seekNext } from "./positioning";
  3. export {
  4. determineTestTemplate,
  5. getDecisionTaker,
  6. getMaskTemplate,
  7. getPlaceholder,
  8. getTest,
  9. getTests,
  10. getTestTemplate,
  11. isSubsetOf
  12. };
  13. function getLocator(tst, align) {
  14. // need to align the locators to be correct
  15. let locator = (
  16. tst.alternation != undefined
  17. ? tst.mloc[`${getDecisionTaker(tst)}:${tst.alternation}`] || tst.locator
  18. : tst.locator
  19. ).join("");
  20. if (locator !== "") {
  21. locator = locator.split(":")[0]; // strip off alternation marker
  22. while (locator.length < align) locator += "0";
  23. }
  24. return locator;
  25. }
  26. function getDecisionTaker(tst) {
  27. let decisionTaker = tst.locator[tst.alternation];
  28. if (typeof decisionTaker === "string" && decisionTaker.length > 0) {
  29. // no decision taken ~ take smallest as decider
  30. decisionTaker = decisionTaker.split(",").sort((a, b) => a - b)[0];
  31. }
  32. return decisionTaker !== undefined ? decisionTaker.toString() : "";
  33. }
  34. // tobe put on prototype?
  35. function getPlaceholder(pos, test, returnPL) {
  36. const inputmask = this,
  37. opts = this.opts,
  38. maskset = this.maskset;
  39. test = test || getTest.call(inputmask, pos).match;
  40. if (test.placeholder !== undefined || returnPL === true) {
  41. if (
  42. test.placeholder !== "" &&
  43. test.static === true &&
  44. test.generated !== true
  45. ) {
  46. // static and not dynamically generated ~ does not occur in regex mask ~ numeric alias def is not a valid entry
  47. const lvp = getLastValidPosition.call(inputmask, pos),
  48. nextPos = seekNext.call(inputmask, lvp);
  49. return (returnPL ? pos <= nextPos : pos < nextPos)
  50. ? opts.staticDefinitionSymbol && test.static
  51. ? test.nativeDef
  52. : test.def
  53. : typeof test.placeholder === "function"
  54. ? test.placeholder(opts)
  55. : test.placeholder;
  56. } else {
  57. return typeof test.placeholder === "function"
  58. ? test.placeholder(opts)
  59. : test.placeholder;
  60. }
  61. } else if (test.static === true) {
  62. if (pos > -1 && maskset.validPositions[pos] === undefined) {
  63. let tests = getTests.call(inputmask, pos),
  64. staticAlternations = [],
  65. prevTest;
  66. if (
  67. typeof opts.placeholder === "string" &&
  68. tests.length > 1 + (tests[tests.length - 1].match.def === "" ? 1 : 0)
  69. ) {
  70. for (let i = 0; i < tests.length; i++) {
  71. if (
  72. tests[i].match.def !== "" &&
  73. tests[i].match.optionality !== true &&
  74. tests[i].match.optionalQuantifier !== true &&
  75. (tests[i].match.static === true ||
  76. prevTest === undefined ||
  77. tests[i].match.fn.test(
  78. prevTest.match.def,
  79. maskset,
  80. pos,
  81. true,
  82. opts
  83. ) !== false)
  84. ) {
  85. staticAlternations.push(tests[i]);
  86. if (tests[i].match.static === true) prevTest = tests[i];
  87. if (staticAlternations.length > 1) {
  88. if (/[0-9a-bA-Z]/.test(staticAlternations[0].match.def)) {
  89. return opts.placeholder.charAt(pos % opts.placeholder.length);
  90. }
  91. }
  92. }
  93. }
  94. }
  95. }
  96. return test.def;
  97. }
  98. return typeof opts.placeholder === "object"
  99. ? test.def
  100. : opts.placeholder.charAt(pos % opts.placeholder.length);
  101. }
  102. // tobe put on prototype?
  103. function getMaskTemplate(
  104. baseOnInput,
  105. minimalPos,
  106. includeMode,
  107. noJit,
  108. clearOptionalTail
  109. ) {
  110. // includeMode true => input, undefined => placeholder, false => mask
  111. const inputmask = this,
  112. opts = this.opts,
  113. maskset = this.maskset,
  114. greedy = opts.greedy;
  115. if (clearOptionalTail && opts.greedy) {
  116. opts.greedy = false;
  117. inputmask.maskset.tests = {};
  118. }
  119. minimalPos = minimalPos || 0;
  120. let maskTemplate = [],
  121. ndxIntlzr,
  122. pos = 0,
  123. test,
  124. testPos,
  125. jitRenderStatic;
  126. do {
  127. if (baseOnInput === true && maskset.validPositions[pos]) {
  128. testPos =
  129. clearOptionalTail &&
  130. maskset.validPositions[pos].match.optionality &&
  131. maskset.validPositions[pos + 1] === undefined &&
  132. (maskset.validPositions[pos].generatedInput === true ||
  133. (maskset.validPositions[pos].input ==
  134. opts.skipOptionalPartCharacter &&
  135. pos > 0))
  136. ? determineTestTemplate.call(
  137. inputmask,
  138. pos,
  139. getTests.call(inputmask, pos, ndxIntlzr, pos - 1)
  140. )
  141. : maskset.validPositions[pos];
  142. test = testPos.match;
  143. ndxIntlzr = testPos.locator.slice();
  144. maskTemplate.push(
  145. includeMode === true
  146. ? testPos.input
  147. : includeMode === false
  148. ? test.nativeDef
  149. : getPlaceholder.call(inputmask, pos, test)
  150. );
  151. } else {
  152. testPos = getTestTemplate.call(inputmask, pos, ndxIntlzr, pos - 1);
  153. test = testPos.match;
  154. ndxIntlzr = testPos.locator.slice();
  155. const jitMasking =
  156. noJit === true
  157. ? false
  158. : opts.jitMasking !== false
  159. ? opts.jitMasking
  160. : test.jit;
  161. // check for groupSeparator is a hack for the numerics as we don't want the render of the groupSeparator beforehand
  162. jitRenderStatic =
  163. (jitRenderStatic ||
  164. maskset.validPositions[
  165. pos - 1
  166. ]) /* && getTest.call(inputmask, pos + 1).match.def == "" */ &&
  167. test.static &&
  168. test.def !== opts.groupSeparator &&
  169. test.fn === null;
  170. if (
  171. jitRenderStatic ||
  172. jitMasking === false ||
  173. jitMasking === undefined /* || pos < lvp */ ||
  174. (typeof jitMasking === "number" &&
  175. isFinite(jitMasking) &&
  176. jitMasking > pos)
  177. ) {
  178. maskTemplate.push(
  179. includeMode === false
  180. ? test.nativeDef
  181. : getPlaceholder.call(inputmask, maskTemplate.length, test)
  182. );
  183. } else {
  184. jitRenderStatic = false;
  185. }
  186. }
  187. pos++;
  188. } while (test.static !== true || test.def !== "" || minimalPos > pos);
  189. if (maskTemplate[maskTemplate.length - 1] === "") {
  190. maskTemplate.pop(); // drop the last one which is empty
  191. }
  192. if (
  193. includeMode !== false || // do not alter the masklength when just retrieving the maskdefinition
  194. maskset.maskLength === undefined
  195. ) {
  196. // just make sure the maskLength gets initialized in all cases (needed for isValid)
  197. maskset.maskLength = pos - 1;
  198. }
  199. opts.greedy = greedy;
  200. return maskTemplate;
  201. }
  202. // tobe put on prototype?
  203. function getTestTemplate(pos, ndxIntlzr, tstPs) {
  204. const inputmask = this,
  205. maskset = this.maskset;
  206. return (
  207. maskset.validPositions[pos] ||
  208. determineTestTemplate.call(
  209. inputmask,
  210. pos,
  211. getTests.call(
  212. inputmask,
  213. pos,
  214. ndxIntlzr ? ndxIntlzr.slice() : ndxIntlzr,
  215. tstPs
  216. )
  217. )
  218. );
  219. }
  220. // tobe put on prototype?
  221. function determineTestTemplate(pos, tests) {
  222. const inputmask = this,
  223. opts = inputmask.opts,
  224. optionalityLevel = determineOptionalityLevel(pos, tests);
  225. pos = pos > 0 ? pos - 1 : 0;
  226. const longestLocator = Math.max(
  227. ...tests.map((tst) =>
  228. tst.locator === undefined ? 0 : tst.locator.length
  229. )
  230. ),
  231. prevTest = getTest.call(inputmask, pos),
  232. prevLocator = getLocator(prevTest, longestLocator);
  233. let lenghtOffset = 0,
  234. tstLocator,
  235. closest,
  236. bestMatch;
  237. if (
  238. opts.greedy &&
  239. tests.length > 1 &&
  240. tests[tests.length - 1].match.def === ""
  241. )
  242. lenghtOffset = 1;
  243. // console.log(" optionality = " + optionalityLevel);
  244. // console.log(" - " + JSON.stringify(tests));
  245. for (let ndx = 0; ndx < tests.length - lenghtOffset; ndx++) {
  246. // find best matching
  247. const tst = tests[ndx];
  248. tstLocator = getLocator(tst, longestLocator);
  249. const distance = Number(tstLocator) - Number(prevLocator); // find the closest match to the previous one
  250. // console.log("distance", distance, tstLocator, prevLocator);
  251. if (
  252. tst.unMatchedAlternationStopped !== true ||
  253. tests.filter((tst) => tst.unMatchedAlternationStopped !== true).length <=
  254. 1
  255. ) {
  256. // only skip when there are choices outside the alternation
  257. if (
  258. closest === undefined ||
  259. (tstLocator !== "" && distance < closest) ||
  260. (bestMatch &&
  261. !opts.greedy &&
  262. bestMatch.match.optionality &&
  263. bestMatch.match.optionality - optionalityLevel > 0 &&
  264. bestMatch.match.newBlockMarker === "master" &&
  265. (!tst.match.optionality ||
  266. tst.match.optionality - optionalityLevel < 1 ||
  267. !tst.match.newBlockMarker)) ||
  268. (bestMatch &&
  269. !opts.greedy &&
  270. bestMatch.match.optionalQuantifier &&
  271. !tst.match.optionalQuantifier)
  272. ) {
  273. closest = distance;
  274. bestMatch = tst;
  275. }
  276. }
  277. }
  278. return bestMatch;
  279. }
  280. function determineOptionalityLevel(pos, tests) {
  281. let optionalityLevel = 0,
  282. differentOptionalLevels = false;
  283. tests.forEach((test) => {
  284. if (test.match.optionality) {
  285. if (optionalityLevel !== 0 && optionalityLevel !== test.match.optionality)
  286. differentOptionalLevels = true;
  287. if (optionalityLevel === 0 || optionalityLevel > test.match.optionality) {
  288. optionalityLevel = test.match.optionality;
  289. }
  290. }
  291. });
  292. if (optionalityLevel) {
  293. if (pos == 0) optionalityLevel = 0;
  294. else if (tests.length == 1) optionalityLevel = 0;
  295. else if (!differentOptionalLevels) optionalityLevel = 0;
  296. }
  297. return optionalityLevel;
  298. }
  299. // tobe put on prototype?
  300. function getTest(pos, tests) {
  301. const inputmask = this,
  302. maskset = this.maskset;
  303. if (maskset.validPositions[pos]) {
  304. return maskset.validPositions[pos];
  305. }
  306. return (tests || getTests.call(inputmask, pos))[0];
  307. }
  308. function isSubsetOf(source, target, opts) {
  309. function expand(pattern) {
  310. let expanded = [],
  311. start = -1,
  312. end;
  313. for (let i = 0, l = pattern.length; i < l; i++) {
  314. if (pattern.charAt(i) === "-") {
  315. end = pattern.charCodeAt(i + 1);
  316. while (++start < end) expanded.push(String.fromCharCode(start));
  317. } else {
  318. start = pattern.charCodeAt(i);
  319. expanded.push(pattern.charAt(i));
  320. }
  321. }
  322. return expanded.join("");
  323. }
  324. if (source.match.def === target.match.nativeDef) return true;
  325. if (
  326. (opts.regex ||
  327. (source.match.fn instanceof RegExp &&
  328. target.match.fn instanceof RegExp)) &&
  329. source.match.static !== true &&
  330. target.match.static !== true
  331. ) {
  332. // is regex a subset
  333. if (target.match.fn.source === ".") return true;
  334. return (
  335. expand(target.match.fn.source.replace(/[[\]/]/g, "")).indexOf(
  336. expand(source.match.fn.source.replace(/[[\]/]/g, ""))
  337. ) !== -1
  338. );
  339. }
  340. return false;
  341. }
  342. // tobe put on prototype?
  343. function getTests(pos, ndxIntlzr, tstPs) {
  344. let inputmask = this,
  345. $ = this.dependencyLib,
  346. maskset = this.maskset,
  347. opts = this.opts,
  348. el = this.el,
  349. maskTokens = maskset.maskToken,
  350. testPos = ndxIntlzr ? tstPs : 0,
  351. ndxInitializer = ndxIntlzr ? ndxIntlzr.slice() : [0],
  352. matches = [],
  353. insertStop = false,
  354. latestMatch,
  355. cacheDependency = ndxIntlzr ? ndxIntlzr.join("") : "",
  356. unMatchedAlternation = false;
  357. function resolveTestFromToken(
  358. maskToken,
  359. ndxInitializer,
  360. loopNdx,
  361. quantifierRecurse
  362. ) {
  363. // ndxInitializer contains a set of indexes to speedup searches in the mtokens
  364. function handleMatch(match, loopNdx, quantifierRecurse) {
  365. function isFirstMatch(latestMatch, tokenGroup) {
  366. let firstMatch = tokenGroup.matches.indexOf(latestMatch) === 0;
  367. if (!firstMatch) {
  368. tokenGroup.matches.every(function (match, ndx) {
  369. if (match.isQuantifier === true) {
  370. firstMatch = isFirstMatch(
  371. latestMatch,
  372. tokenGroup.matches[ndx - 1]
  373. );
  374. } else if (Object.prototype.hasOwnProperty.call(match, "matches"))
  375. firstMatch = isFirstMatch(latestMatch, match);
  376. if (firstMatch) return false;
  377. return true;
  378. });
  379. }
  380. return firstMatch;
  381. }
  382. function resolveNdxInitializer(pos, alternateNdx, targetAlternation) {
  383. let bestMatch,
  384. distance,
  385. locator,
  386. newAlternateMloc,
  387. alternateMloc = `${alternateNdx}:${targetAlternation}`;
  388. if (maskset.tests[pos] || maskset.validPositions[pos]) {
  389. (maskset.validPositions[pos]
  390. ? [maskset.validPositions[pos]]
  391. : maskset.tests[pos]
  392. ).every(function (lmnt, ndx) {
  393. if (lmnt.mloc[alternateMloc]) {
  394. bestMatch = lmnt;
  395. return false; // break
  396. }
  397. // check if an entry in mloc match the alternateNdx on targetAlternation
  398. const mlocMatches = Object.values(lmnt.mloc).filter(
  399. // eslint-disable-next-line eqeqeq
  400. (m) => m[targetAlternation] == alternateNdx
  401. );
  402. // for each mlocMatch check the calculated distance
  403. mlocMatches.every((mlocMatch) => {
  404. let mlocMatchL = mlocMatch.join("").split(":")[0]; // strip off alternation marker
  405. locator = locator || mlocMatchL;
  406. while (mlocMatchL.length < locator.length) mlocMatchL += "0";
  407. const mlocDistance = Number(mlocMatchL);
  408. // console.log("mlocDistance", mlocDistance);
  409. if (bestMatch === undefined || mlocDistance < distance) {
  410. distance = mlocDistance;
  411. bestMatch = lmnt;
  412. // key from mlocMatch
  413. newAlternateMloc = Object.entries(lmnt.mloc).find(
  414. (entry) => entry[1].toString() === mlocMatch.toString()
  415. )[0];
  416. }
  417. return true; // continue
  418. });
  419. return true;
  420. });
  421. }
  422. if (bestMatch) {
  423. if (targetAlternation === undefined) {
  424. alternateMloc = `${alternateNdx}:${bestMatch.alternation}`;
  425. }
  426. const bestMatchAltIndex = `${
  427. bestMatch.locator[bestMatch.alternation]
  428. }:${bestMatch.alternation}`,
  429. slocator =
  430. bestMatch.mloc[newAlternateMloc || alternateMloc] ||
  431. bestMatch.mloc[bestMatchAltIndex] ||
  432. bestMatch.locator;
  433. if (slocator[slocator.length - 1].toString().indexOf(":") !== -1) {
  434. // eslint-disable-next-line no-unused-vars
  435. const alternation = slocator.pop();
  436. // targetAlternation = parseInt(alternation.substring(1));
  437. }
  438. const sliceStart =
  439. parseInt(
  440. // newAlternateMloc
  441. // ? newAlternateMloc.split(":")[1]
  442. // : targetAlternation ||
  443. bestMatch.alternation
  444. ) + 1;
  445. // console.log(
  446. // "resolveNdxInitializer",
  447. // pos,
  448. // alternateNdx,
  449. // targetAlternation,
  450. // slocator,
  451. // sliceStart,
  452. // bestMatch
  453. // );
  454. return slocator.slice(sliceStart);
  455. } else {
  456. return targetAlternation !== undefined
  457. ? resolveNdxInitializer(pos, alternateNdx)
  458. : undefined;
  459. }
  460. }
  461. function staticCanMatchDefinition(source, target) {
  462. return source.match.static === true && target.match.static !== true
  463. ? target.match.fn.test(
  464. source.match.def,
  465. maskset,
  466. pos,
  467. false,
  468. opts,
  469. false
  470. )
  471. : false;
  472. }
  473. // mergelocators for retrieving the correct locator match when merging
  474. function setMergeLocators(targetMatch, altMatch) {
  475. function mergeLoc(altNdx) {
  476. targetMatch.mloc = targetMatch.mloc || {};
  477. let locNdx = targetMatch.locator[altNdx];
  478. if (locNdx === undefined) {
  479. targetMatch.alternation = undefined;
  480. } else {
  481. if (altMatch === undefined) {
  482. if (typeof locNdx === "string") locNdx = locNdx.split(",")[0];
  483. locNdx = `${locNdx}:${altNdx}`;
  484. if (targetMatch.mloc[locNdx] === undefined) {
  485. targetMatch.mloc[locNdx] = targetMatch.locator.slice();
  486. targetMatch.mloc[locNdx].push(`:${altNdx}`); // add alternation index
  487. }
  488. } else {
  489. let offset = 0;
  490. for (const ndx in altMatch.mloc) {
  491. // if (typeof ndx === "string") ndx = parseInt(ndx.split(",")[0]);
  492. if (targetMatch.mloc[ndx] === undefined) {
  493. targetMatch.mloc[ndx] = altMatch.mloc[ndx];
  494. } else {
  495. do {
  496. if (targetMatch.mloc[ndx + offset] === undefined) {
  497. targetMatch.mloc[ndx + offset] = altMatch.mloc[ndx];
  498. break;
  499. }
  500. } while (targetMatch.mloc[ndx + offset++] !== undefined);
  501. }
  502. }
  503. targetMatch.locator = mergeLocators(testPos, [
  504. targetMatch,
  505. altMatch
  506. ]);
  507. }
  508. if (targetMatch.alternation > altNdx) {
  509. // if the alternation index is higher than the current one resolve it to the alternation
  510. targetMatch.alternation = altNdx;
  511. }
  512. return true;
  513. }
  514. return false;
  515. }
  516. let alternationNdx = targetMatch.alternation,
  517. shouldMerge =
  518. altMatch === undefined ||
  519. (alternationNdx <= altMatch.alternation &&
  520. targetMatch.locator[alternationNdx]
  521. .toString()
  522. .indexOf(altMatch.locator[alternationNdx]) === -1);
  523. if (!shouldMerge && alternationNdx > altMatch.alternation) {
  524. for (let i = 0; i < alternationNdx; i++) {
  525. if (targetMatch.locator[i] !== altMatch.locator[i]) {
  526. alternationNdx = i;
  527. shouldMerge = true;
  528. break;
  529. }
  530. }
  531. }
  532. if (shouldMerge) {
  533. return mergeLoc(alternationNdx);
  534. }
  535. return false;
  536. }
  537. function handleGroup() {
  538. match = handleMatch(
  539. maskToken.matches[maskToken.matches.indexOf(match) + 1],
  540. loopNdx,
  541. quantifierRecurse
  542. );
  543. if (match) return true;
  544. }
  545. function handleOptional() {
  546. const optionalToken = match,
  547. mtchsNdx = matches.length;
  548. match = resolveTestFromToken(
  549. match,
  550. ndxInitializer,
  551. loopNdx,
  552. quantifierRecurse
  553. );
  554. if (matches.length > 0) {
  555. // check on matches.length instead of match to handle quantifier in a recursive call
  556. // mark optionality in matches
  557. matches.forEach(function (mtch, ndx) {
  558. if (ndx >= mtchsNdx) {
  559. mtch.match.optionality = mtch.match.optionality
  560. ? mtch.match.optionality + 1
  561. : 1;
  562. }
  563. });
  564. latestMatch = matches[matches.length - 1].match;
  565. if (
  566. quantifierRecurse === undefined &&
  567. isFirstMatch(latestMatch, optionalToken)
  568. ) {
  569. // prevent loop see #698
  570. insertStop = true; // insert a stop
  571. testPos = pos; // match the position after the group
  572. } else {
  573. return match; // make the loop continue when it is deliberately by a quantifier
  574. }
  575. }
  576. }
  577. function handleAlternator() {
  578. function calculateMatchesLength(matches) {
  579. let matchesLength = 0;
  580. for (let ndx = 0; ndx < matches.length; ndx++) {
  581. const match = matches[ndx];
  582. if (match.isQuantifier && !isNaN(match.quantifier.max)) {
  583. matchesLength += match.quantifier.max;
  584. } else {
  585. matchesLength++;
  586. }
  587. }
  588. return matchesLength;
  589. }
  590. function isUnmatchedAlternation(alternateToken) {
  591. const matchesLength = alternateToken.matches[0].matches
  592. ? calculateMatchesLength(alternateToken.matches[0].matches)
  593. : 1;
  594. let matchesNewLength;
  595. for (let alndx = 0; alndx < alternateToken.matches.length; alndx++) {
  596. matchesNewLength = alternateToken.matches[alndx].matches
  597. ? calculateMatchesLength(alternateToken.matches[alndx].matches)
  598. : 1;
  599. if (matchesLength !== matchesNewLength) {
  600. break;
  601. }
  602. }
  603. return matchesLength !== matchesNewLength;
  604. }
  605. inputmask.hasAlternator = true;
  606. const alternateToken = match,
  607. malternateMatches = [],
  608. currentMatches = matches.slice(),
  609. loopNdxCnt = loopNdx.length,
  610. altIndex = ndxInitializer.length > 0 ? ndxInitializer.shift() : -1;
  611. let maltMatches;
  612. if (altIndex === -1 || typeof altIndex === "string") {
  613. const currentPos = testPos,
  614. ndxInitializerClone = ndxInitializer.slice();
  615. let altIndexArr = [],
  616. amndx;
  617. if (typeof altIndex === "string") {
  618. altIndexArr = altIndex.split(",");
  619. } else {
  620. for (amndx = 0; amndx < alternateToken.matches.length; amndx++) {
  621. altIndexArr.push(amndx.toString());
  622. }
  623. }
  624. if (maskset.excludes[pos] !== undefined) {
  625. const altIndexArrClone = altIndexArr.slice();
  626. for (let i = 0, exl = maskset.excludes[pos].length; i < exl; i++) {
  627. const excludeSet = maskset.excludes[pos][i].toString().split(":");
  628. if (loopNdx.length == excludeSet[1]) {
  629. altIndexArr.splice(altIndexArr.indexOf(excludeSet[0]), 1);
  630. }
  631. }
  632. if (altIndexArr.length === 0) {
  633. // fully alternated => reset
  634. delete maskset.excludes[pos];
  635. altIndexArr = altIndexArrClone;
  636. }
  637. }
  638. if (
  639. opts.keepStatic === true ||
  640. (isFinite(parseInt(opts.keepStatic)) &&
  641. currentPos >= opts.keepStatic)
  642. )
  643. altIndexArr = altIndexArr.slice(0, 1);
  644. for (let ndx = 0; ndx < altIndexArr.length; ndx++) {
  645. amndx = parseInt(altIndexArr[ndx]);
  646. matches = [];
  647. // set the correct ndxInitializer
  648. ndxInitializer =
  649. typeof altIndex === "string"
  650. ? resolveNdxInitializer(testPos, amndx, loopNdxCnt) ||
  651. ndxInitializerClone.slice()
  652. : ndxInitializerClone.slice();
  653. // console.log("ndxInit", ndxInitializer);
  654. const tokenMatch = alternateToken.matches[amndx];
  655. if (
  656. tokenMatch &&
  657. handleMatch(
  658. tokenMatch,
  659. [amndx].concat(loopNdx),
  660. quantifierRecurse
  661. )
  662. ) {
  663. match = true;
  664. } else {
  665. // if (currentPos !== 0) {
  666. // only check with the first alternate
  667. unMatchedAlternation = isUnmatchedAlternation(alternateToken);
  668. // }
  669. if (
  670. tokenMatch &&
  671. tokenMatch.matches &&
  672. tokenMatch.matches.length >
  673. alternateToken.matches[0].matches.length
  674. ) {
  675. break;
  676. }
  677. }
  678. maltMatches = matches.slice();
  679. testPos = currentPos;
  680. matches = [];
  681. // fuzzy merge matches
  682. for (let ndx1 = 0; ndx1 < maltMatches.length; ndx1++) {
  683. let altMatch = maltMatches[ndx1],
  684. dropMatch = false;
  685. altMatch.alternation = altMatch.alternation || loopNdxCnt;
  686. setMergeLocators(altMatch);
  687. for (let ndx2 = 0; ndx2 < malternateMatches.length; ndx2++) {
  688. const altMatch2 = malternateMatches[ndx2];
  689. if (
  690. typeof altIndex !== "string" ||
  691. (altMatch.alternation !== undefined &&
  692. altIndex.indexOf(
  693. altMatch.locator[altMatch.alternation].toString()
  694. ) !== -1)
  695. ) {
  696. if (altMatch.match.nativeDef === altMatch2.match.nativeDef) {
  697. dropMatch = true;
  698. setMergeLocators(altMatch2, altMatch);
  699. break;
  700. } else if (isSubsetOf(altMatch, altMatch2, opts)) {
  701. if (setMergeLocators(altMatch, altMatch2)) {
  702. dropMatch = true;
  703. malternateMatches.splice(
  704. malternateMatches.indexOf(altMatch2),
  705. 0,
  706. altMatch
  707. );
  708. }
  709. break;
  710. } else if (isSubsetOf(altMatch2, altMatch, opts)) {
  711. setMergeLocators(altMatch2, altMatch);
  712. break;
  713. } else if (staticCanMatchDefinition(altMatch, altMatch2)) {
  714. if (setMergeLocators(altMatch, altMatch2)) {
  715. // insert match above general match
  716. dropMatch = true;
  717. malternateMatches.splice(
  718. malternateMatches.indexOf(altMatch2),
  719. 0,
  720. altMatch
  721. );
  722. }
  723. break;
  724. } else if (staticCanMatchDefinition(altMatch2, altMatch)) {
  725. setMergeLocators(altMatch2, altMatch);
  726. // hackery to solve a mask like ([0]9)|(2a) ~ note the static 0 is optional ~ see unittest ivaninDarpatov
  727. // this needs a better solution, but his will do for now
  728. if (
  729. altMatch2.match.optionality &&
  730. el.inputmask.userOptions.keepStatic === undefined
  731. ) {
  732. opts.keepStatic = currentPos;
  733. }
  734. break;
  735. }
  736. }
  737. }
  738. if (!dropMatch) {
  739. malternateMatches.push(altMatch);
  740. }
  741. }
  742. }
  743. matches = currentMatches.concat(malternateMatches);
  744. testPos = pos;
  745. insertStop = matches.length > 0 && unMatchedAlternation; // insert a stopelemnt when there is an alternate - needed for non-greedy option
  746. match = malternateMatches.length > 0 && !unMatchedAlternation; // set correct match state
  747. if (unMatchedAlternation && insertStop && !match) {
  748. // mark matches with unMatchedAlternationStopped
  749. matches.forEach(function (mtch, ndx) {
  750. mtch.unMatchedAlternationStopped = true;
  751. });
  752. }
  753. // cloneback
  754. ndxInitializer = ndxInitializerClone.slice();
  755. } else {
  756. match = handleMatch(
  757. alternateToken.matches[altIndex] || maskToken.matches[altIndex],
  758. [altIndex].concat(loopNdx),
  759. quantifierRecurse
  760. );
  761. }
  762. if (match) {
  763. return true;
  764. }
  765. }
  766. function handleQuantifier() {
  767. const qt = match;
  768. let breakloop = false;
  769. for (
  770. let qndx = ndxInitializer.length > 0 ? ndxInitializer.shift() : 0;
  771. qndx < (isNaN(qt.quantifier.max) ? qndx + 1 : qt.quantifier.max) &&
  772. testPos <= pos;
  773. qndx++
  774. ) {
  775. const tokenGroup =
  776. maskToken.matches[maskToken.matches.indexOf(qt) - 1];
  777. match = handleMatch(tokenGroup, [qndx].concat(loopNdx), tokenGroup); // set the tokenGroup as quantifierRecurse marker
  778. if (match) {
  779. matches.forEach(function (mtch, ndx) {
  780. if (IsMatchOf(tokenGroup, mtch.match)) latestMatch = mtch.match;
  781. else latestMatch = matches[matches.length - 1].match;
  782. // mark optionality
  783. // TODO FIX RECURSIVE QUANTIFIERS
  784. latestMatch.optionalQuantifier = qndx >= qt.quantifier.min;
  785. // console.log(pos + " " + qt.quantifier.min + " " + latestMatch.optionalQuantifier);
  786. // qndx + 1 as the index starts from 0
  787. latestMatch.jit =
  788. (qndx + 1) * (tokenGroup.matches.indexOf(latestMatch) + 1) >
  789. qt.quantifier.jit;
  790. if (
  791. latestMatch.optionalQuantifier &&
  792. isFirstMatch(latestMatch, tokenGroup)
  793. ) {
  794. insertStop = true;
  795. testPos = pos; // match the position after the group
  796. if (
  797. opts.greedy &&
  798. maskset.validPositions[pos - 1] == undefined &&
  799. qndx > qt.quantifier.min &&
  800. ["*", "+"].indexOf(qt.quantifier.max) != -1
  801. ) {
  802. matches.pop();
  803. cacheDependency = undefined;
  804. }
  805. breakloop = true; // stop quantifierloop && search for next possible match
  806. match = false; // mark match to false to make sure the loop in optionals continues
  807. }
  808. if (
  809. !breakloop &&
  810. latestMatch.jit /* && !latestMatch.optionalQuantifier */
  811. ) {
  812. // always set jitOffset, isvalid checks when to apply
  813. maskset.jitOffset[pos] =
  814. tokenGroup.matches.length -
  815. tokenGroup.matches.indexOf(latestMatch);
  816. }
  817. });
  818. if (breakloop) break; // search for next possible match
  819. return true;
  820. }
  821. }
  822. }
  823. if (testPos > pos + opts._maxTestPos) {
  824. throw new Error(
  825. `Inputmask: There is probably an error in your mask definition or in the code. Create an issue on github with an example of the mask you are using. ${maskset.mask}`
  826. );
  827. }
  828. if (testPos === pos && match.matches === undefined) {
  829. matches.push({
  830. match,
  831. locator: loopNdx.reverse(),
  832. cd: cacheDependency,
  833. mloc: {}
  834. });
  835. if (
  836. match.optionality &&
  837. quantifierRecurse === undefined &&
  838. ((opts.definitions &&
  839. opts.definitions[match.nativeDef] &&
  840. opts.definitions[match.nativeDef].optional) ||
  841. (Inputmask.prototype.definitions[match.nativeDef] &&
  842. Inputmask.prototype.definitions[match.nativeDef].optional))
  843. ) {
  844. // prevent loop see #698
  845. insertStop = true; // insert a stop
  846. testPos = pos; // match the position after the group
  847. } else {
  848. return true;
  849. }
  850. } else if (match.matches !== undefined) {
  851. if (match.isGroup && quantifierRecurse !== match) {
  852. // when a group pass along to the quantifier
  853. return handleGroup();
  854. } else if (match.isOptional) {
  855. return handleOptional();
  856. } else if (match.isAlternator) {
  857. return handleAlternator();
  858. } else if (
  859. match.isQuantifier &&
  860. quantifierRecurse !==
  861. maskToken.matches[maskToken.matches.indexOf(match) - 1]
  862. ) {
  863. return handleQuantifier();
  864. } else {
  865. match = resolveTestFromToken(
  866. match,
  867. ndxInitializer,
  868. loopNdx,
  869. quantifierRecurse
  870. );
  871. if (match) return true;
  872. }
  873. } else {
  874. testPos++;
  875. }
  876. }
  877. // the offset is set in the quantifierloop when git masking is used
  878. for (
  879. let tndx = ndxInitializer.length > 0 ? ndxInitializer.shift() : 0;
  880. tndx < maskToken.matches.length;
  881. tndx++
  882. ) {
  883. if (maskToken.matches[tndx].isQuantifier !== true) {
  884. const match = handleMatch(
  885. maskToken.matches[tndx],
  886. [tndx].concat(loopNdx),
  887. quantifierRecurse
  888. );
  889. if (match && testPos === pos) {
  890. return match;
  891. } else if (testPos > pos) {
  892. break;
  893. }
  894. }
  895. }
  896. }
  897. function IsMatchOf(tokenGroup, match) {
  898. let isMatch = tokenGroup.matches.indexOf(match) != -1;
  899. if (!isMatch) {
  900. tokenGroup.matches.forEach((mtch, ndx) => {
  901. if (mtch.matches !== undefined && !isMatch) {
  902. isMatch = IsMatchOf(mtch, match);
  903. }
  904. });
  905. }
  906. return isMatch;
  907. }
  908. function mergeLocators(pos, tests) {
  909. let locator = [];
  910. if (!Array.isArray(tests)) tests = [tests];
  911. if (tests.length > 0) {
  912. if (
  913. tests[0].alternation === undefined ||
  914. opts.keepStatic === true ||
  915. (isFinite(parseInt(opts.keepStatic)) && pos >= opts.keepStatic)
  916. ) {
  917. locator = determineTestTemplate
  918. .call(inputmask, pos, tests.slice())
  919. .locator.slice();
  920. if (locator.length === 0) locator = tests[0].locator.slice();
  921. } else {
  922. // alternation = tests[0].locator.length - 1;
  923. tests.forEach((mtch) => {
  924. Object.values(mtch.mloc).forEach((mloc) => {
  925. mloc.forEach((loc, locNdx) => {
  926. // if (locNdx > alternation) return;
  927. const mergedPos = locator[locNdx];
  928. if (
  929. loc.toString().includes(":") ||
  930. (mergedPos && mergedPos.toString().includes(":"))
  931. )
  932. return;
  933. if (mergedPos === undefined) {
  934. locator[locNdx] = loc;
  935. } else if (!mergedPos.toString().includes(loc)) {
  936. locator[locNdx] = locator[locNdx] + "," + loc;
  937. }
  938. });
  939. });
  940. });
  941. }
  942. }
  943. // console.log("mergeLocators", pos, tests, locator);
  944. return locator;
  945. }
  946. if (pos > -1) {
  947. if (ndxIntlzr === undefined) {
  948. // determine index initializer
  949. let previousPos = pos - 1,
  950. test;
  951. while (
  952. (test =
  953. maskset.validPositions[previousPos] || maskset.tests[previousPos]) ===
  954. undefined &&
  955. previousPos > -1
  956. ) {
  957. previousPos--;
  958. }
  959. if (test !== undefined && previousPos > -1) {
  960. ndxInitializer = mergeLocators(previousPos, test);
  961. cacheDependency = ndxInitializer.join("");
  962. testPos = previousPos;
  963. }
  964. }
  965. if (maskset.tests[pos] && maskset.tests[pos][0].cd === cacheDependency) {
  966. // cacheDependency is set on all tests, just check on the first
  967. return maskset.tests[pos];
  968. }
  969. for (
  970. let mtndx = ndxInitializer.shift();
  971. mtndx < maskTokens.length;
  972. mtndx++
  973. ) {
  974. const match = resolveTestFromToken(maskTokens[mtndx], ndxInitializer, [
  975. mtndx
  976. ]);
  977. if ((match && testPos === pos) || testPos > pos) {
  978. break;
  979. }
  980. }
  981. }
  982. if (matches.length === 0 || insertStop) {
  983. matches.push({
  984. match: {
  985. fn: null,
  986. static: true,
  987. optionality: false,
  988. casing: null,
  989. def: "",
  990. placeholder: ""
  991. },
  992. // mark when there are unmatched alternations ex: mask: "(a|aa)"
  993. // this will result in the least distance to select the correct test result in determineTestTemplate
  994. locator:
  995. unMatchedAlternation &&
  996. matches.filter((tst) => tst.unMatchedAlternationStopped !== true)
  997. .length === 0
  998. ? [0]
  999. : [],
  1000. mloc: {},
  1001. cd: cacheDependency
  1002. });
  1003. }
  1004. let result;
  1005. if (ndxIntlzr !== undefined && maskset.tests[pos]) {
  1006. // prioritize full tests for caching
  1007. result = $.extend(true, [], matches);
  1008. } else {
  1009. // console.log("stored " + pos + " - " + JSON.stringify(matches));
  1010. maskset.tests[pos] = $.extend(true, [], matches); // set a clone to prevent overwriting some props
  1011. result = maskset.tests[pos];
  1012. }
  1013. // console.log(pos, JSON.stringify(matches));
  1014. // cleanup optionality marking
  1015. matches.forEach((t) => {
  1016. t.match.optionality = t.match.defOptionality || false;
  1017. });
  1018. return result;
  1019. }