UNPKG

@enterprize/string-formatter

Version:

Formats and validates strings against arbitrary patterns

184 lines (183 loc) 7.18 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); class StringFormatter { constructor(pattern, options) { this.tokens = { "0": { pattern: /\d/, defaultValue: "0" }, "9": { pattern: /\d/, optional: true }, "#": { pattern: /\d/, optional: true, recursive: true }, "A": { pattern: /[a-zA-Z0-9]/ }, "S": { pattern: /[a-zA-Z]/ }, "U": { pattern: /[a-zA-Z]/, transform: (char) => char.toLocaleUpperCase() }, "L": { pattern: /[a-zA-Z]/, transform: (char) => char.toLocaleLowerCase() }, "$": { escape: true } }; this.options = options || {}; this.options = { reverse: this.options.reverse || false, useDefaults: this.options.useDefaults || this.options.reverse }; this.pattern = pattern; } process(value) { if (!Boolean(value)) { return { result: "", valid: false }; } const recursive = []; let pattern2 = this.pattern; let valid = true; let formatted = ""; let valuePos = this.options.reverse ? value.length - 1 : 0; let patternPos = 0; let optionalNumbersToUse = this.calcOptionalNumbersToUse(pattern2, value); let escapeNext = false; let inRecursiveMode = false; const steps = { start: this.options.reverse ? pattern2.length - 1 : 0, end: this.options.reverse ? -1 : pattern2.length, inc: this.options.reverse ? -1 : 1 }; const continueCondition = (options) => { if (!inRecursiveMode && !recursive.length && this.hasMoreTokens(pattern2, patternPos, steps.inc)) { return true; } else if (!inRecursiveMode && recursive.length && this.hasMoreRecursiveTokens(pattern2, patternPos, steps.inc)) { return true; } else if (!inRecursiveMode) { inRecursiveMode = recursive.length > 0; } if (inRecursiveMode) { const pc = recursive.shift(); recursive.push(pc); if (options.reverse && valuePos >= 0) { patternPos++; pattern2 = this.insertChar(pattern2, pc, patternPos); return true; } else if (!options.reverse && valuePos < value.length) { pattern2 = this.insertChar(pattern2, pc, patternPos); return true; } } return patternPos < pattern2.length && patternPos >= 0; }; for (patternPos = steps.start; continueCondition(this.options); patternPos = patternPos + steps.inc) { const vc = value.charAt(valuePos); const pc = pattern2.charAt(patternPos); let token = this.tokens[pc]; if (recursive.length && token && !token.recursive) { token = null; } if (!inRecursiveMode || vc) { if (this.options.reverse && this.isEscaped(pattern2, patternPos)) { formatted = this.concatChar(formatted, pc, this.options, token); patternPos = patternPos + steps.inc; continue; } else if (!this.options.reverse && escapeNext) { formatted = this.concatChar(formatted, pc, this.options, token); escapeNext = false; continue; } else if (!this.options.reverse && token && token.escape) { escapeNext = true; continue; } } if (!inRecursiveMode && token && token.recursive) { recursive.push(pc); } else if (inRecursiveMode && !vc) { formatted = this.concatChar(formatted, pc, this.options, token); continue; } else if (!inRecursiveMode && recursive.length > 0 && !vc) { continue; } if (!token) { formatted = this.concatChar(formatted, pc, this.options, token); if (!inRecursiveMode && recursive.length) { recursive.push(pc); } } else if (token.optional) { if (token.pattern.test(vc) && optionalNumbersToUse) { formatted = this.concatChar(formatted, vc, this.options, token); valuePos = valuePos + steps.inc; optionalNumbersToUse--; } else if (recursive.length > 0 && vc) { valid = false; break; } } else if (token.pattern.test(vc)) { formatted = this.concatChar(formatted, vc, this.options, token); valuePos = valuePos + steps.inc; } else if (!vc && token.defaultValue && this.options.useDefaults) { formatted = this.concatChar(formatted, token.defaultValue, this.options, token); } else { valid = false; break; } } return { result: formatted, valid: valid }; } apply(value) { return this.process(value).result; } validate(value) { return this.process(value).valid; } isEscaped(pattern, pos) { let count = 0; let i = pos - 1; let token = { escape: true }; while (i >= 0 && token && token.escape) { token = this.tokens[pattern.charAt(i)]; count += token && token.escape ? 1 : 0; i--; } return count > 0 && count % 2 === 1; } calcOptionalNumbersToUse(pattern, value) { const numbersInP = pattern.replace(/[^0]/g, "").length; const numbersInV = value.replace(/[^\d]/g, "").length; return numbersInV - numbersInP; } concatChar(text, character, options, token) { if (token != null && token.transform != null) { character = token.transform(character); } if (options != null && options.reverse) { return character + text; } return text + character; } hasMoreTokens(pattern, pos, inc) { const pc = pattern.charAt(pos); if (pc === "") { return false; } const token = this.tokens[pc]; return token && !token.escape ? true : this.hasMoreTokens(pattern, pos + inc, inc); } hasMoreRecursiveTokens(pattern, pos, inc) { const pc = pattern.charAt(pos); if (pc === "") { return false; } const token = this.tokens[pc]; return token && token.recursive ? true : this.hasMoreRecursiveTokens(pattern, pos + inc, inc); } insertChar(text, char, position) { const t = text.split(""); t.splice(position, 0, char); return t.join(""); } } exports.StringFormatter = StringFormatter;