UNPKG

@finnair/path

Version:
150 lines (149 loc) 5.11 kB
import { Path } from './Path.js'; import { PropertyMatcher, IndexMatcher, AnyIndex, AnyProperty, UnionMatcher, isPathExpression } from './matchers.js'; export class PathMatcher { expressions; allowGaps; constructor(expressions) { this.expressions = expressions; this.allowGaps = expressions.some((expression) => expression.allowGaps); Object.freeze(this.expressions); Object.freeze(this); } find(root, collector) { if (this.expressions.length === 0) { collector(Path.ROOT, root); } if (typeof root !== 'object') { return; } const currentPath = []; const handlers = []; for (let i = 0; i < this.expressions.length - 1; i++) { handlers[i] = intermediateHandler(i, this.expressions); } handlers[this.expressions.length - 1] = resultHandler(); this.expressions[0].find(root, handlers[0]); function intermediateHandler(index, expressions) { return (value, component) => { currentPath[index] = component; return expressions[index + 1].find(value, handlers[index + 1]); }; } function resultHandler() { return (value, component) => { return collector(Path.of(...currentPath, component), value); }; } } findAll(root, acceptUndefined) { const results = []; this.find(root, (path, value) => { if (value !== undefined || acceptUndefined) { results.push({ path, value }); return true; } return true; }); return results; } findFirst(root, acceptUndefined) { let result = undefined; this.find(root, (path, value) => { if (value !== undefined || acceptUndefined) { result = { path, value }; return false; } return true; }); return result; } findValues(root, acceptUndefined) { const results = []; this.find(root, (path, value) => { if (value !== undefined || acceptUndefined) { results.push(value); return true; } return true; }); return results; } findFirstValue(root, acceptUndefined) { let result = undefined; this.find(root, (path, value) => { if (value !== undefined || acceptUndefined) { result = value; return false; } return true; }); return result; } /** * Exact match: path length must match the number of expressions and all expressions must match. Only sibling paths match. * * @param path * @returns true if path is an exact match to expressions */ match(path) { if (path.length !== this.expressions.length) { return false; } for (let index = 0; index < this.expressions.length; index++) { if (!this.expressions[index].test(path.componentAt(index))) { return false; } } return true; } /** * Prefix match: path length must be equal or longer than the number of expressions and all expressions must match. All sibling and child paths match. * * @param path * @returns true the start the path matches */ prefixMatch(path) { if (path.length < this.expressions.length) { return false; } for (let index = 0; index < this.expressions.length; index++) { if (!this.expressions[index].test(path.componentAt(index))) { return false; } } return true; } /** * Partial match: path length can be less than or more than the number of expressions, but all corresponding expressions must match. All parent, sibling and child paths match. * * @param path * @returns true if all path components match */ partialMatch(path) { for (let index = 0; index < this.expressions.length && index < path.length; index++) { if (!this.expressions[index].test(path.componentAt(index))) { return false; } } return true; } toJSON() { return this.expressions.reduce((str, expression) => str + expression.toString(), '$'); } static of(...path) { return new PathMatcher(path.map(component => { const type = typeof component; if (type === 'number') { return new IndexMatcher(component); } if (type === 'string') { return new PropertyMatcher(component); } if (isPathExpression(component)) { return component; } throw new Error(`Unrecognized PathComponent: ${component} of type ${type}`); })); } } export { AnyIndex, AnyProperty, PropertyMatcher, IndexMatcher, UnionMatcher };