exstack
Version:
A utility library designed to simplify and enhance Express.js applications.
151 lines (149 loc) • 4.65 kB
JavaScript
//#region src/router/reg-exp/node.ts
const LABEL_REG_EXP_STR = "[^/]+";
const ONLY_WILDCARD_REG_EXP_STR = ".*";
const TAIL_WILDCARD_REG_EXP_STR = "(?:|/.*)";
const PATH_ERROR = Symbol();
const regExpMetaChars = /* @__PURE__ */ new Set(".\\+*[^]$()");
/**
* Sort order:
* 1. literal
* 2. special pattern (e.g. :label{[0-9]+})
* 3. common label pattern (e.g. :label)
* 4. wildcard
*/
const compareKey = (a, b) => {
if (a.length === 1) return b.length === 1 ? a < b ? -1 : 1 : -1;
if (b.length === 1) return 1;
if (a === ONLY_WILDCARD_REG_EXP_STR || a === TAIL_WILDCARD_REG_EXP_STR) return 1;
else if (b === ONLY_WILDCARD_REG_EXP_STR || b === TAIL_WILDCARD_REG_EXP_STR) return -1;
if (a === LABEL_REG_EXP_STR) return 1;
else if (b === LABEL_REG_EXP_STR) return -1;
return a.length === b.length ? a < b ? -1 : 1 : b.length - a.length;
};
var Node = class Node {
#index;
#varIndex;
#children = Object.create(null);
insert(tokens, index, paramMap, context, pathErrorCheckOnly) {
if (tokens.length === 0) {
if (this.#index !== void 0) throw PATH_ERROR;
if (pathErrorCheckOnly) return;
this.#index = index;
return;
}
const [token, ...restTokens] = tokens;
const pattern = token === "*" ? restTokens.length === 0 ? [
"",
"",
ONLY_WILDCARD_REG_EXP_STR
] : [
"",
"",
LABEL_REG_EXP_STR
] : token === "/*" ? [
"",
"",
TAIL_WILDCARD_REG_EXP_STR
] : token.match(/^\:([^\{\}]+)(?:\{(.+)\})?$/);
let node;
if (pattern) {
const name = pattern[1];
let regexpStr = pattern[2] || LABEL_REG_EXP_STR;
if (name && pattern[2]) {
if (regexpStr === ".*") throw PATH_ERROR;
regexpStr = regexpStr.replace(/^\((?!\?:)(?=[^)]+\)$)/, "(?:");
if (/\((?!\?:)/.test(regexpStr)) throw PATH_ERROR;
}
node = this.#children[regexpStr];
if (!node) {
if (Object.keys(this.#children).some((k) => k !== ONLY_WILDCARD_REG_EXP_STR && k !== TAIL_WILDCARD_REG_EXP_STR)) throw PATH_ERROR;
if (pathErrorCheckOnly) return;
node = this.#children[regexpStr] = new Node();
if (name !== "") node.#varIndex = context.varIndex++;
}
if (!pathErrorCheckOnly && name !== "") paramMap.push([name, node.#varIndex]);
} else {
node = this.#children[token];
if (!node) {
if (Object.keys(this.#children).some((k) => k.length > 1 && k !== ONLY_WILDCARD_REG_EXP_STR && k !== TAIL_WILDCARD_REG_EXP_STR)) throw PATH_ERROR;
if (pathErrorCheckOnly) return;
node = this.#children[token] = new Node();
}
}
node.insert(restTokens, index, paramMap, context, pathErrorCheckOnly);
}
buildRegExpStr() {
const strList = Object.keys(this.#children).sort(compareKey).map((k) => {
const c = this.#children[k];
return (typeof c.#varIndex === "number" ? `(${k})@${c.#varIndex}` : regExpMetaChars.has(k) ? `\\${k}` : k) + c.buildRegExpStr();
});
if (typeof this.#index === "number") strList.unshift(`#${this.#index}`);
if (strList.length === 0) return "";
if (strList.length === 1) return strList[0];
return "(?:" + strList.join("|") + ")";
}
};
var Trie = class {
#context = { varIndex: 0 };
#root = new Node();
insert(path, index, pathErrorCheckOnly) {
const paramAssoc = [];
const groups = [];
for (let i = 0;;) {
let replaced = false;
path = path.replace(/\{[^}]+\}/g, (m) => {
const mark = `@\\${i}`;
groups[i] = [mark, m];
i++;
replaced = true;
return mark;
});
if (!replaced) break;
}
/**
* - pattern (:label, :label{0-9]+}, ...)
* - /* wildcard
* - character
*/
const tokens = path.match(/(?::[^\/]+)|(?:\/\*$)|./g) || [];
for (let i = groups.length - 1; i >= 0; i--) {
const [mark] = groups[i];
for (let j = tokens.length - 1; j >= 0; j--) if (tokens[j].indexOf(mark) !== -1) {
tokens[j] = tokens[j].replace(mark, groups[i][1]);
break;
}
}
this.#root.insert(tokens, index, paramAssoc, this.#context, pathErrorCheckOnly);
return paramAssoc;
}
buildRegExp() {
let regexp = this.#root.buildRegExpStr();
if (regexp === "") return [
/^$/,
[],
[]
];
let captureIndex = 0;
const indexReplacementMap = [];
const paramReplacementMap = [];
regexp = regexp.replace(/#(\d+)|@(\d+)|\.\*\$/g, (_, handlerIndex, paramIndex) => {
if (handlerIndex !== void 0) {
indexReplacementMap[++captureIndex] = Number(handlerIndex);
return "$()";
}
if (paramIndex !== void 0) {
paramReplacementMap[Number(paramIndex)] = ++captureIndex;
return "";
}
return "";
});
return [
/* @__PURE__ */ new RegExp(`^${regexp}`),
indexReplacementMap,
paramReplacementMap
];
}
};
//#endregion
exports.PATH_ERROR = PATH_ERROR;
exports.Trie = Trie;