@enterprize/string-formatter
Version:
Formats and validates strings against arbitrary patterns
184 lines (183 loc) • 7.18 kB
JavaScript
"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;