UNPKG

diffusion

Version:

Diffusion JavaScript client

240 lines (239 loc) 8.58 kB
"use strict"; /** * @module Topics */ Object.defineProperty(exports, "__esModule", { value: true }); exports.parseSelector = void 0; // topic selector API // internal topic classes var errors_1 = require("./../../errors/errors"); var abstract_topic_selector_1 = require("./../topics/abstract-topic-selector"); var full_path_selector_1 = require("./../topics/full-path-selector"); var path_selector_1 = require("./../topics/path-selector"); var selector_set_1 = require("./../topics/selector-set"); var split_path_selector_1 = require("./../topics/split-path-selector"); var utils = require("./../topics/topic-path-utils"); var memoize_1 = require("./../util/memoize"); var string_1 = require("./../util/string"); var topic_selector_1 = require("../../selectors/topic-selector"); var DQ = utils.DescendantQualifier; /** * Parse a selector expression into a specific TopicSelector implementation. * @param expression the selector's string expression * @return a new topic selector * @throws an {@link IllegalArgumentError} if the expression is empty or invalid */ function parseSelector(expression) { var args = []; for (var _i = 1; _i < arguments.length; _i++) { args[_i - 1] = arguments[_i]; } if (!expression) { throw new errors_1.IllegalArgumentError('Empty topic selector expression'); } if (args.length > 0) { expression = [expression].concat(args); } if (expression instanceof Array) { var actual = expression.map(function (s) { return (s instanceof abstract_topic_selector_1.AbstractTopicSelector ? s : parseSelector(s)); }); return new selector_set_1.SelectorSet(actual); } if (typeof expression === 'string') { var components = getComponents(expression); if (isTopicPath(components)) { return new path_selector_1.PathSelector(components); } switch (components.type) { case topic_selector_1.Type.SPLIT_PATH_PATTERN: return new split_path_selector_1.SplitPathSelector(components); case topic_selector_1.Type.FULL_PATH_PATTERN: return new full_path_selector_1.FullPathSelector(components); case topic_selector_1.Type.SELECTOR_SET: var parts = string_1.split(components.remainder, selector_set_1.DELIMITER); var selectors = parts.map(function (e) { return parseSelector(e); }); return new selector_set_1.SelectorSet(selectors); } } if (expression instanceof abstract_topic_selector_1.AbstractTopicSelector) { return expression; } throw new errors_1.IllegalArgumentError('Topic selector expression must be a string or array'); } /** * Extract a component group from a single expression * @param expression the selector expression * @returns an object containing parsed properties */ function getComponents(expression) { var type = getType(expression); // treat expressions as Path selectors by default if (type === null) { expression = topic_selector_1.Prefix.PATH + expression; type = topic_selector_1.Type.PATH; } var remainder = expression.substring(1); var qualifier; var base; if (type === topic_selector_1.Type.PATH) { base = remainder; qualifier = DQ.MATCH; } else if (remainder[remainder.length - 1] === '/') { if (remainder[remainder.length - 2] === '/') { qualifier = DQ.MATCH_AND_DESCENDANTS; base = utils.canonicalise(remainder.substring(0, remainder.length - 2)); } else { qualifier = DQ.DESCENDANTS_OF_MATCH; base = utils.canonicalise(remainder.substring(0, remainder.length - 1)); } } else { base = utils.canonicalise(remainder); qualifier = DQ.MATCH; } var prefix = (type === topic_selector_1.Type.PATH) ? utils.canonicalise(remainder) : extractPrefixFromRegex(base); return { type: type, base: base, prefix: prefix, remainder: remainder, qualifier: qualifier, expression: type + remainder }; } /** * Determine if a component group can be treated as a topic path. * * @param c the component group * @returns if the components represent a topic path selector */ function isTopicPath(c) { if (c.type === topic_selector_1.Type.PATH) { return true; } if (c.type === topic_selector_1.Type.SELECTOR_SET || c.prefix === '') { return false; } return c.prefix === c.base; } /** * Determine the Topic Selector type of a given expression. * * @param expression the selector expression * @returns the topic selector type, or null * @throws an {@link IllegalArgumentError} if the expression is invalid */ function getType(expression) { switch (expression[0]) { case topic_selector_1.Prefix.PATH: return topic_selector_1.Type.PATH; case topic_selector_1.Prefix.SPLIT_PATH_PATTERN: return topic_selector_1.Type.SPLIT_PATH_PATTERN; case topic_selector_1.Prefix.FULL_PATH_PATTERN: return topic_selector_1.Type.FULL_PATH_PATTERN; case topic_selector_1.Prefix.SELECTOR_SET: return topic_selector_1.Type.SELECTOR_SET; case '$': case '%': case '&': case '<': // reserved character throw new errors_1.IllegalArgumentError('Invalid expression type: ' + expression); default: // default is to treat null as a path return null; } } /** * Invalid characters for a topic path to contain */ var metaChars = ['*', '.', '+', '?', '^', '$', '{', '}', '(', ')', '[', ']', '\\', '|']; /** * Validate characters in a normal parsing mode * * @param c the character to check * @return `true` if the character is a normal path character */ function normal(c) { return metaChars.indexOf(c) === -1; } /** * Validated characters within a quoted block (\Q \E) * * @return `true` */ function quoted() { return true; } /** * Extract the largest possible topic path prefix from a string that may * contain a regular expression. * * @param {String} remainder - The remainder (i.e expression minus prefix) * @returns {String} The maximum topic path that could be extracted */ // eslint-disable-next-line complexity function extractPrefixFromRegex(remainder) { var buffer = []; var composite = []; var validator = normal; for (var i = 0, l = remainder.length; i < l; ++i) { var char = remainder[i]; // identify a metachar and transition to an appropriate parse mode. // we can then safely skip over the '\' char & following metachar. if (char === '\\' && i < l - 1) { var next = remainder[i + 1]; if (validator === normal) { i++; if (next === 'Q') { validator = quoted; } else if ((next < 'a' || next > 'z') && (next < 'A' || next > 'Z')) { // an escaped alphabetic character can be assumed to be a meta construct buffer.push(next); } else { // an escaped metachar indicates the end of the run buffer.length = 0; break; } } else if (next === 'E') { i++; validator = normal; } else { buffer.push(char); } } else if (char === utils.PATH_SEPARATOR) { composite.push(buffer.join('')); buffer.length = 0; buffer.push(char); } else if (validator(char)) { buffer.push(char); } else { buffer.length = 0; break; } } composite.push(buffer.join('')); return utils.canonicalise(composite.join('')); } /** * A matcher function to memoize arguments for the {@link parseSelector} function * * @param args the arguments * @return a JSON string of the arguments */ function memoizeMatcher(args) { return JSON.stringify(args); } var disableTopicCache = process && process.env && (process.env.DISABLE_TOPIC_CACHE === 'true'); /** * Wrap the parse function to cache results */ var cachedParse = disableTopicCache ? parseSelector : memoize_1.memoize(parseSelector, {}, memoizeMatcher); exports.parseSelector = cachedParse;