botbook/node_modules/@sagold/json-query/dist/module/lib/interpreter/nodes.js
Rodrigo Rodriguez 6ae15fe3e5 Updated.
2024-09-04 13:13:15 -03:00

162 lines
4.8 KiB
JavaScript

const join = (a, b) => `${a}/${b}`;
import { VALUE_INDEX, POINTER_INDEX } from "./keys";
const toString = Object.prototype.toString;
const rContainer = /Object|Array/;
const isContainer = (v) => rContainer.test(toString.call(v));
const getTypeOf = (v) => toString
.call(v)
.match(/\s([^\]]+)\]/)
.pop()
.toLowerCase();
function nodeAsRegex(node) {
return new RegExp(node.text.replace(/(^{|}$)/g, ""));
}
/**
* Iterates over object or array, passing each key, value and parentObject to the callback
* @param value - to iterate
* @param callback - receiving key on given input value
*/
function forEach(parent, callback) {
if (Array.isArray(parent)) {
parent.forEach(callback);
}
else if (Object.prototype.toString.call(parent) === "[object Object]") {
Object.keys(parent).forEach(function (key) {
callback(parent[key], key, parent);
});
}
}
/**
* Returns all keys of the given input data
* @param value
* @return {Array} containing keys of given value
*/
function getKeys(value) {
if (Array.isArray(value)) {
return value.map(function (value, index) {
return `${index}`;
});
}
if (Object.prototype.toString.call(value) === "[object Object]") {
return Object.keys(value);
}
return [];
}
const cache = {
mem: [],
get(entry, prop) {
const v = entry[VALUE_INDEX][prop];
if (cache.mem.includes(v)) {
return undefined;
}
if (isContainer(v)) {
cache.mem.push(v);
}
return [v, prop, entry[VALUE_INDEX], join(entry[POINTER_INDEX], prop)];
},
reset() {
cache.mem.length = 0;
},
};
const expand = {
any(node, entry) {
const value = entry[VALUE_INDEX];
return (getKeys(value)
// .map(prop => cache.get(entry, prop));
.map((prop) => [
value[prop],
prop,
value,
join(entry[POINTER_INDEX], prop),
]));
},
all(node, entry) {
const result = [entry];
forEach(entry[VALUE_INDEX], (value, prop) => {
const childEntry = cache.get(entry, prop);
// const childEntry = [value, prop, entry[VALUE_INDEX], join(entry[POINTER_INDEX], prop)];
childEntry && result.push(...expand.all(node, childEntry));
});
return result;
},
regex(node, entry) {
const regex = nodeAsRegex(node);
const value = entry[VALUE_INDEX];
return getKeys(value)
.filter((prop) => regex.test(prop))
.map((prop) => [
value[prop],
prop,
value,
join(entry[POINTER_INDEX], prop),
]);
},
};
const select = {
// alias to property (but escaped)
escaped: (node, entry) => select.property(node, entry),
property: (node, entry) => {
const prop = node.text;
if (entry[VALUE_INDEX] && entry[VALUE_INDEX][prop] !== undefined) {
return [
entry[VALUE_INDEX][prop],
prop,
entry[VALUE_INDEX],
join(entry[POINTER_INDEX], prop),
];
}
},
typecheck: (node, entry) => {
const checkedTyped = node.text.replace(/^\?:/, "");
if (checkedTyped === "value") {
return isContainer(entry[VALUE_INDEX]) ? undefined : entry;
}
const type = getTypeOf(entry[VALUE_INDEX]);
if (type === checkedTyped) {
return entry;
}
},
lookahead: (node, entry) => {
let valid = true;
let or = false;
node.children.forEach((expr) => {
if (expr.type === "expression") {
const isValid = select.expression(expr, entry) !== undefined;
valid = or === true ? valid || isValid : valid && isValid;
}
else {
or = expr.type === "orExpr";
}
});
return valid ? entry : undefined;
},
expression: (node, entry) => {
const prop = node.children[0].text;
const cmp = node.children[1];
const test = node.children[2];
const value = entry[VALUE_INDEX];
if (isContainer(value) === false) {
return undefined;
}
return expressionMatches(value[prop], cmp, test) ? entry : undefined;
},
};
function expressionMatches(value, cmp, test) {
if (cmp === undefined) {
return value !== undefined;
}
let valid;
const valueString = `${value}`;
if (test.type === "regex") {
const regex = nodeAsRegex(test);
valid = regex.test(valueString);
}
else {
valid = valueString === test.text;
}
if (cmp.type === "isnot") {
valid = valid === false && value !== undefined;
}
return valid;
}
export { expand, select, cache };