odatafy-parser
Version:
generate odatafy ast from odata query parameters
155 lines (154 loc) • 6.83 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.reverseExprLogic = exports.resolveExpression = exports.reverseEscaped = exports.escapeExpression = exports.hasOnlyMatchingParentheses = exports.escapeFunctions = exports.escapeStrings = exports.getIdentifier = exports.Prefixes = void 0;
// this is by far the most amazing algorithm ever created in 6 hours
//God intends that one shall never touch any of this code
const find_matching_bracket_1 = __importDefault(require("find-matching-bracket"));
const crypto_1 = __importDefault(require("crypto"));
var Prefixes;
(function (Prefixes) {
Prefixes["Str_Escape"] = "$";
Prefixes["Func_Escape"] = "%";
Prefixes["Expr_Escape"] = "\u00A7";
Prefixes["Multiexpr_Escape"] = "&";
})(Prefixes = exports.Prefixes || (exports.Prefixes = {}));
function getIdentifier(prefix) {
return `${prefix}${crypto_1.default.randomBytes(20).toString('hex')}${prefix}`;
}
exports.getIdentifier = getIdentifier;
function escapeStrings(expr) {
let escaped = expr;
const stringResolve = {};
while (escaped.includes("'")) {
const regex = /'.*?'/;
const identifier = getIdentifier(Prefixes.Str_Escape);
const toEscape = escaped.match(regex);
if (toEscape == null) {
break;
}
stringResolve[identifier] = toEscape[0].toString();
escaped = escaped.replace(regex, identifier);
}
return [escaped, stringResolve];
}
exports.escapeStrings = escapeStrings;
function escapeFunctions(expr) {
let escaped = expr;
const functionResolve = {};
const regex = /(concat|contains|endswith|indexof|matchesPattern|startswith|geo.distance|geo.intersects|hassubset|hassubsequence|length|tolower|toupper|trim|year|month|day|hour|minute|second|fractionalseconds|totalseconds|date|time|totaloffsetminutes|round|floor|ceiling|geo.length|mindatetime|maxdatetime|now)\(/g;
while ([...escaped.matchAll(regex)].length > 0) {
const matches = [...escaped.matchAll(regex)];
const end = (0, find_matching_bracket_1.default)(escaped, matches[0].index + (matches[0][0].length - 1));
const identifier = getIdentifier(Prefixes.Func_Escape);
const toEscape = escaped.substring(matches[0].index, end + 1);
functionResolve[identifier] = toEscape;
escaped =
escaped.substring(0, matches[0].index) +
identifier +
escaped.substring(end + 1);
}
return [escaped, functionResolve];
}
exports.escapeFunctions = escapeFunctions;
function hasOnlyMatchingParentheses(expr) {
return [...expr.matchAll(/\(/g)].length == [...expr.matchAll(/\)/g)].length;
}
exports.hasOnlyMatchingParentheses = hasOnlyMatchingParentheses;
const expressionResolver = {};
const multiExpressionResolve = {};
function escapeExpression(expr, depth = 0) {
let result = '';
const andOrRegex = /( and | or )/;
const expressionsElements = expr.split(andOrRegex);
for (let i = 0; i < expressionsElements.length; i++) {
if (expressionsElements[i].match(andOrRegex)) {
result += expressionsElements[i];
continue;
}
if (!(expressionsElements[i].includes('(') ||
expressionsElements[i].includes(')')) ||
hasOnlyMatchingParentheses(expressionsElements[i])) {
const identifier = getIdentifier(Prefixes.Expr_Escape);
result += identifier;
expressionResolver[identifier] = expressionsElements[i];
continue;
}
let subExpr = expressionsElements[i];
let not = false;
if (subExpr.startsWith('not ')) {
subExpr = subExpr.replace('not ', '');
not = true;
}
//cannot be out of bounds
while (!hasOnlyMatchingParentheses(subExpr)) {
subExpr += expressionsElements[i + 1] + expressionsElements[i + 2];
i += 2;
}
subExpr = subExpr.trim();
subExpr = subExpr.substring(1, subExpr.length - 1);
const identifier = getIdentifier(Prefixes.Multiexpr_Escape);
const toEscape = escapeExpression(subExpr, depth + 1);
result += identifier;
multiExpressionResolve[identifier] = {
toEscape: toEscape,
depth: depth,
not: not
};
}
return result;
}
exports.escapeExpression = escapeExpression;
function reverseEscaped(expr, resolvers) {
const andOrRegex = /( and | or )/;
const expressionElements = expr.split(andOrRegex).reverse();
expr = expressionElements.join('');
const transformedResolvers = Object.keys(resolvers).map((resolverKey) => {
return {
toEscape: resolvers[resolverKey].toEscape,
depth: resolvers[resolverKey].depth,
not: resolvers[resolverKey].not,
key: resolverKey
};
});
const maxDepth = Math.max(...transformedResolvers.map((o) => o.depth));
for (let i = 0; i <= maxDepth; i++) {
const transformedResolversOnDepth = transformedResolvers.filter((transRes) => transRes.depth == i);
transformedResolversOnDepth.forEach((tRoD) => {
const tRoDElements = tRoD.toEscape.split(andOrRegex).reverse();
tRoD.toEscape = tRoDElements.join('');
expr = expr.replace(tRoD.key, `${tRoD.not ? 'not ' : ''}(${tRoD.toEscape})`);
});
}
return expr;
}
exports.reverseEscaped = reverseEscaped;
function resolveExpression(expr, exprResolver, funcResolver, strResolver) {
Object.keys(exprResolver).forEach((key) => {
expr = expr.replace(key, exprResolver[key]);
});
Object.keys(funcResolver).forEach((key) => {
expr = expr.replace(key, funcResolver[key]);
});
Object.keys(strResolver).forEach((key) => {
expr = expr.replace(key, strResolver[key]);
});
return expr;
}
exports.resolveExpression = resolveExpression;
function reverseExprLogic(expr) {
const [strResult, stringResolver] = escapeStrings(expr);
const [funcResult, functionResolver] = escapeFunctions(strResult);
const exprResult = escapeExpression(funcResult);
const reverseResult = reverseEscaped(exprResult, multiExpressionResolve);
const result = resolveExpression(reverseResult, expressionResolver, functionResolver, stringResolver);
return result;
}
exports.reverseExprLogic = reverseExprLogic;
function filterExpressionPreProc(expr) {
return reverseExprLogic(expr);
}
exports.default = filterExpressionPreProc;
//console.log(reverseExprLogic("Alter eq 5 and contains(XYZ, 'B') or NOT ((Name eq 'Fynn') or NOT (Name eq 'Robbyn' and Name lte 3 and Lol gt 5 ) ) or Mem gt 6"));