| /** | ||
| * Decision tree node definitions. | ||
| * | ||
| * @packageDocumentation | ||
| */ | ||
| /** | ||
| * Specificity as defined by Selectors spec. | ||
| * | ||
| * {@link https://www.w3.org/TR/selectors/#specificity} | ||
| * | ||
| * Three levels: for id, class, tag (type). | ||
| * | ||
| * Extra level(s) used in HTML styling don't fit the scope of this package | ||
| * and no space reserved for them. | ||
| */ | ||
| type Specificity = [number, number, number]; | ||
| /** | ||
| * Container for the associated value, | ||
| * selector specificity and position in the selectors collection. | ||
| * | ||
| * @typeParam V - the type of the associated value. | ||
| */ | ||
| type ValueContainer<V> = { | ||
| index: number; | ||
| specificity: Specificity; | ||
| value: V; | ||
| }; | ||
| /** | ||
| * When reached a terminal node, decision tree adds | ||
| * the value container to the list of successful matches. | ||
| */ | ||
| type TerminalNode<V> = { | ||
| type: 'terminal'; | ||
| valueContainer: ValueContainer<V>; | ||
| }; | ||
| /** | ||
| * Tag name has to be checked. | ||
| * Underlying variants can be assembled | ||
| * into a dictionary key check. | ||
| */ | ||
| type TagNameNode<V> = { | ||
| type: 'tagName'; | ||
| variants: VariantNode<V>[]; | ||
| }; | ||
| /** | ||
| * String value variant. | ||
| */ | ||
| type VariantNode<V> = { | ||
| type: 'variant'; | ||
| value: string; | ||
| cont: DecisionTreeNode<V>[]; | ||
| }; | ||
| /** | ||
| * Have to check the presence of an element attribute | ||
| * with the given name. | ||
| */ | ||
| type AttrPresenceNode<V> = { | ||
| type: 'attrPresence'; | ||
| name: string; | ||
| cont: DecisionTreeNode<V>[]; | ||
| }; | ||
| /** | ||
| * Have to check the value of an element attribute | ||
| * with the given name. | ||
| * It usually requires to run all underlying matchers | ||
| * one after another. | ||
| */ | ||
| type AttrValueNode<V> = { | ||
| type: 'attrValue'; | ||
| name: string; | ||
| matchers: MatcherNode<V>[]; | ||
| }; | ||
| /** | ||
| * String value matcher. | ||
| * Contains the predicate so no need to reimplement it | ||
| * from descriptive parameters. | ||
| */ | ||
| type MatcherNode<V> = { | ||
| type: 'matcher'; | ||
| matcher: '=' | '~=' | '|=' | '^=' | '$=' | '*='; | ||
| modifier: 'i' | 's' | null; | ||
| value: string; | ||
| predicate: (prop: string) => boolean; | ||
| cont: DecisionTreeNode<V>[]; | ||
| }; | ||
| /** | ||
| * Simple pseudo-class condition has to be checked. | ||
| */ | ||
| type PseudoClassNode<V> = { | ||
| type: 'pseudoClass'; | ||
| name: string; | ||
| cont: DecisionTreeNode<V>[]; | ||
| }; | ||
| /** | ||
| * Push next element on the stack, defined by the combinator. | ||
| * Only `>` and `+` are expected to be supported. | ||
| * | ||
| * All checks are performed on the element on top of the stack. | ||
| */ | ||
| type PushElementNode<V> = { | ||
| type: 'pushElement'; | ||
| combinator: '>' | '+'; | ||
| cont: DecisionTreeNode<V>[]; | ||
| }; | ||
| /** | ||
| * Remove the top element from the stack - | ||
| * following checks are performed on the previous element. | ||
| */ | ||
| type PopElementNode<V> = { | ||
| type: 'popElement'; | ||
| cont: DecisionTreeNode<V>[]; | ||
| }; | ||
| type DecisionTreeNode<V> = TerminalNode<V> | TagNameNode<V> | AttrPresenceNode<V> | AttrValueNode<V> | PseudoClassNode<V> | PushElementNode<V> | PopElementNode<V>; | ||
| type Ast_AttrPresenceNode<V> = AttrPresenceNode<V>; | ||
| type Ast_AttrValueNode<V> = AttrValueNode<V>; | ||
| type Ast_DecisionTreeNode<V> = DecisionTreeNode<V>; | ||
| type Ast_MatcherNode<V> = MatcherNode<V>; | ||
| type Ast_PopElementNode<V> = PopElementNode<V>; | ||
| type Ast_PseudoClassNode<V> = PseudoClassNode<V>; | ||
| type Ast_PushElementNode<V> = PushElementNode<V>; | ||
| type Ast_Specificity = Specificity; | ||
| type Ast_TagNameNode<V> = TagNameNode<V>; | ||
| type Ast_TerminalNode<V> = TerminalNode<V>; | ||
| type Ast_ValueContainer<V> = ValueContainer<V>; | ||
| type Ast_VariantNode<V> = VariantNode<V>; | ||
| declare namespace Ast { | ||
| export type { Ast_AttrPresenceNode as AttrPresenceNode, Ast_AttrValueNode as AttrValueNode, Ast_DecisionTreeNode as DecisionTreeNode, Ast_MatcherNode as MatcherNode, Ast_PopElementNode as PopElementNode, Ast_PseudoClassNode as PseudoClassNode, Ast_PushElementNode as PushElementNode, Ast_Specificity as Specificity, Ast_TagNameNode as TagNameNode, Ast_TerminalNode as TerminalNode, Ast_ValueContainer as ValueContainer, Ast_VariantNode as VariantNode }; | ||
| } | ||
| /** | ||
| * Function types for builders and matchers. | ||
| * | ||
| * @packageDocumentation | ||
| */ | ||
| /** | ||
| * A function that turn a decision tree into a callable form. | ||
| * | ||
| * To be implemented by builder plugins. | ||
| * | ||
| * @typeParam V - the type of associated value. | ||
| * | ||
| * @typeParam R - return type for this builder | ||
| * (Consider using {@link Picker}.) | ||
| */ | ||
| type BuilderFunction<V, R> = (nodes: DecisionTreeNode<V>[]) => R; | ||
| /** | ||
| * Recommended matcher function shape to implement in builders. | ||
| * | ||
| * The elements stack is represented as the arguments array. | ||
| * | ||
| * @typeParam L - the type of elements in a particular DOM AST. | ||
| * @typeParam V - the type of associated value. | ||
| */ | ||
| type MatcherFunction<L, V> = (el: L, ...tail: L[]) => ValueContainer<V>[]; | ||
| type Types_BuilderFunction<V, R> = BuilderFunction<V, R>; | ||
| type Types_MatcherFunction<L, V> = MatcherFunction<L, V>; | ||
| declare namespace Types { | ||
| export type { Types_BuilderFunction as BuilderFunction, Types_MatcherFunction as MatcherFunction }; | ||
| } | ||
| /** | ||
| * Basic {@link BuilderFunction} implementation | ||
| * for decision tree visualization. | ||
| * | ||
| * @packageDocumentation | ||
| */ | ||
| /** | ||
| * A {@link BuilderFunction} implementation. | ||
| * | ||
| * Produces a string representation of the tree | ||
| * for testing and debug purposes. | ||
| * | ||
| * Only accepts `string` as the associated value type. | ||
| * Map your input collection before creating a {@link DecisionTree} | ||
| * if you want to use it with a different type - | ||
| * the decision on how to stringify the value is up to you. | ||
| * | ||
| * @param nodes - nodes from the root level of the decision tree. | ||
| * @returns the string representation of the tree. | ||
| */ | ||
| declare function treeify(nodes: DecisionTreeNode<string>[]): string; | ||
| declare const TreeifyBuilder_treeify: typeof treeify; | ||
| declare namespace TreeifyBuilder { | ||
| export { | ||
| TreeifyBuilder_treeify as treeify, | ||
| }; | ||
| } | ||
| /** | ||
| * Options for DecisionTree construction. | ||
| */ | ||
| type DecisionTreeOptions = { | ||
| /** | ||
| * Attribute names whose values should be case-insensitive by default. | ||
| * | ||
| * Only affects attribute value selectors (`[name="value"]`) with no explicit case-sensitivity modifier (`i` or `s`). | ||
| */ | ||
| attributesWithNormalizedValues?: string[]; | ||
| }; | ||
| /** | ||
| * CSS selectors decision tree. | ||
| * Data structure that weaves similar selectors together | ||
| * in order to minimize the number of checks required | ||
| * to find the ones matching a given HTML element. | ||
| * | ||
| * Converted into a functioning implementation via plugins | ||
| * tailored for specific DOM ASTs. | ||
| * | ||
| * @typeParam V - the type of values associated with selectors. | ||
| */ | ||
| declare class DecisionTree<V> { | ||
| private readonly branches; | ||
| /** | ||
| * Create new DecisionTree object. | ||
| * | ||
| * @param input - an array containing all selectors | ||
| * paired with associated values. | ||
| * @param options - optional configuration for the decision tree. | ||
| * | ||
| * @typeParam V - the type of values associated with selectors. | ||
| */ | ||
| constructor(input: [string, V][], options?: DecisionTreeOptions); | ||
| /** | ||
| * Turn this decision tree into a callable form. | ||
| * | ||
| * @typeParam R - return type defined by the builder function. | ||
| * | ||
| * @param builder - the builder function. | ||
| * | ||
| * @returns the decision tree in a form ready for use. | ||
| */ | ||
| build<R>(builder: BuilderFunction<V, R>): R; | ||
| } | ||
| /** | ||
| * Simple wrapper around the matcher function. | ||
| * Recommended return type for builder plugins. | ||
| * | ||
| * @typeParam L - the type of HTML Element in the targeted DOM AST. | ||
| * @typeParam V - the type of associated values. | ||
| */ | ||
| declare class Picker<L, V> { | ||
| private f; | ||
| /** | ||
| * Create new Picker object. | ||
| * | ||
| * @typeParam L - the type of HTML Element in the targeted DOM AST. | ||
| * @typeParam V - the type of associated values. | ||
| * | ||
| * @param f - the function that matches an element | ||
| * and returns all associated values. | ||
| */ | ||
| constructor(f: MatcherFunction<L, V>); | ||
| /** | ||
| * Run the selectors decision tree against one HTML Element | ||
| * and return all matched associated values | ||
| * along with selector specificities. | ||
| * | ||
| * Client code then decides how to further process them | ||
| * (sort, filter, etc). | ||
| * | ||
| * @param el - an HTML Element. | ||
| * | ||
| * @returns all associated values along with | ||
| * selector specificities for all matched selectors. | ||
| */ | ||
| pickAll(el: L): ValueContainer<V>[]; | ||
| /** | ||
| * Run the selectors decision tree against one HTML Element | ||
| * and choose the value from the most specific matched selector. | ||
| * | ||
| * @param el - an HTML Element. | ||
| * | ||
| * @param preferFirst - option to define which value to choose | ||
| * when there are multiple matches with equal specificity. | ||
| * | ||
| * @returns the value from the most specific matched selector | ||
| * or `null` if nothing matched. | ||
| */ | ||
| pick1(el: L, preferFirst?: boolean): V | null; | ||
| } | ||
| export { Ast, DecisionTree, Picker, TreeifyBuilder as Treeify, Types }; | ||
| export type { DecisionTreeOptions }; |
| /** | ||
| * Decision tree node definitions. | ||
| * | ||
| * @packageDocumentation | ||
| */ | ||
| /** | ||
| * Specificity as defined by Selectors spec. | ||
| * | ||
| * {@link https://www.w3.org/TR/selectors/#specificity} | ||
| * | ||
| * Three levels: for id, class, tag (type). | ||
| * | ||
| * Extra level(s) used in HTML styling don't fit the scope of this package | ||
| * and no space reserved for them. | ||
| */ | ||
| type Specificity = [number, number, number]; | ||
| /** | ||
| * Container for the associated value, | ||
| * selector specificity and position in the selectors collection. | ||
| * | ||
| * @typeParam V - the type of the associated value. | ||
| */ | ||
| type ValueContainer<V> = { | ||
| index: number; | ||
| specificity: Specificity; | ||
| value: V; | ||
| }; | ||
| /** | ||
| * When reached a terminal node, decision tree adds | ||
| * the value container to the list of successful matches. | ||
| */ | ||
| type TerminalNode<V> = { | ||
| type: 'terminal'; | ||
| valueContainer: ValueContainer<V>; | ||
| }; | ||
| /** | ||
| * Tag name has to be checked. | ||
| * Underlying variants can be assembled | ||
| * into a dictionary key check. | ||
| */ | ||
| type TagNameNode<V> = { | ||
| type: 'tagName'; | ||
| variants: VariantNode<V>[]; | ||
| }; | ||
| /** | ||
| * String value variant. | ||
| */ | ||
| type VariantNode<V> = { | ||
| type: 'variant'; | ||
| value: string; | ||
| cont: DecisionTreeNode<V>[]; | ||
| }; | ||
| /** | ||
| * Have to check the presence of an element attribute | ||
| * with the given name. | ||
| */ | ||
| type AttrPresenceNode<V> = { | ||
| type: 'attrPresence'; | ||
| name: string; | ||
| cont: DecisionTreeNode<V>[]; | ||
| }; | ||
| /** | ||
| * Have to check the value of an element attribute | ||
| * with the given name. | ||
| * It usually requires to run all underlying matchers | ||
| * one after another. | ||
| */ | ||
| type AttrValueNode<V> = { | ||
| type: 'attrValue'; | ||
| name: string; | ||
| matchers: MatcherNode<V>[]; | ||
| }; | ||
| /** | ||
| * String value matcher. | ||
| * Contains the predicate so no need to reimplement it | ||
| * from descriptive parameters. | ||
| */ | ||
| type MatcherNode<V> = { | ||
| type: 'matcher'; | ||
| matcher: '=' | '~=' | '|=' | '^=' | '$=' | '*='; | ||
| modifier: 'i' | 's' | null; | ||
| value: string; | ||
| predicate: (prop: string) => boolean; | ||
| cont: DecisionTreeNode<V>[]; | ||
| }; | ||
| /** | ||
| * Simple pseudo-class condition has to be checked. | ||
| */ | ||
| type PseudoClassNode<V> = { | ||
| type: 'pseudoClass'; | ||
| name: string; | ||
| cont: DecisionTreeNode<V>[]; | ||
| }; | ||
| /** | ||
| * Push next element on the stack, defined by the combinator. | ||
| * Only `>` and `+` are expected to be supported. | ||
| * | ||
| * All checks are performed on the element on top of the stack. | ||
| */ | ||
| type PushElementNode<V> = { | ||
| type: 'pushElement'; | ||
| combinator: '>' | '+'; | ||
| cont: DecisionTreeNode<V>[]; | ||
| }; | ||
| /** | ||
| * Remove the top element from the stack - | ||
| * following checks are performed on the previous element. | ||
| */ | ||
| type PopElementNode<V> = { | ||
| type: 'popElement'; | ||
| cont: DecisionTreeNode<V>[]; | ||
| }; | ||
| type DecisionTreeNode<V> = TerminalNode<V> | TagNameNode<V> | AttrPresenceNode<V> | AttrValueNode<V> | PseudoClassNode<V> | PushElementNode<V> | PopElementNode<V>; | ||
| type Ast_AttrPresenceNode<V> = AttrPresenceNode<V>; | ||
| type Ast_AttrValueNode<V> = AttrValueNode<V>; | ||
| type Ast_DecisionTreeNode<V> = DecisionTreeNode<V>; | ||
| type Ast_MatcherNode<V> = MatcherNode<V>; | ||
| type Ast_PopElementNode<V> = PopElementNode<V>; | ||
| type Ast_PseudoClassNode<V> = PseudoClassNode<V>; | ||
| type Ast_PushElementNode<V> = PushElementNode<V>; | ||
| type Ast_Specificity = Specificity; | ||
| type Ast_TagNameNode<V> = TagNameNode<V>; | ||
| type Ast_TerminalNode<V> = TerminalNode<V>; | ||
| type Ast_ValueContainer<V> = ValueContainer<V>; | ||
| type Ast_VariantNode<V> = VariantNode<V>; | ||
| declare namespace Ast { | ||
| export type { Ast_AttrPresenceNode as AttrPresenceNode, Ast_AttrValueNode as AttrValueNode, Ast_DecisionTreeNode as DecisionTreeNode, Ast_MatcherNode as MatcherNode, Ast_PopElementNode as PopElementNode, Ast_PseudoClassNode as PseudoClassNode, Ast_PushElementNode as PushElementNode, Ast_Specificity as Specificity, Ast_TagNameNode as TagNameNode, Ast_TerminalNode as TerminalNode, Ast_ValueContainer as ValueContainer, Ast_VariantNode as VariantNode }; | ||
| } | ||
| /** | ||
| * Function types for builders and matchers. | ||
| * | ||
| * @packageDocumentation | ||
| */ | ||
| /** | ||
| * A function that turn a decision tree into a callable form. | ||
| * | ||
| * To be implemented by builder plugins. | ||
| * | ||
| * @typeParam V - the type of associated value. | ||
| * | ||
| * @typeParam R - return type for this builder | ||
| * (Consider using {@link Picker}.) | ||
| */ | ||
| type BuilderFunction<V, R> = (nodes: DecisionTreeNode<V>[]) => R; | ||
| /** | ||
| * Recommended matcher function shape to implement in builders. | ||
| * | ||
| * The elements stack is represented as the arguments array. | ||
| * | ||
| * @typeParam L - the type of elements in a particular DOM AST. | ||
| * @typeParam V - the type of associated value. | ||
| */ | ||
| type MatcherFunction<L, V> = (el: L, ...tail: L[]) => ValueContainer<V>[]; | ||
| type Types_BuilderFunction<V, R> = BuilderFunction<V, R>; | ||
| type Types_MatcherFunction<L, V> = MatcherFunction<L, V>; | ||
| declare namespace Types { | ||
| export type { Types_BuilderFunction as BuilderFunction, Types_MatcherFunction as MatcherFunction }; | ||
| } | ||
| /** | ||
| * Basic {@link BuilderFunction} implementation | ||
| * for decision tree visualization. | ||
| * | ||
| * @packageDocumentation | ||
| */ | ||
| /** | ||
| * A {@link BuilderFunction} implementation. | ||
| * | ||
| * Produces a string representation of the tree | ||
| * for testing and debug purposes. | ||
| * | ||
| * Only accepts `string` as the associated value type. | ||
| * Map your input collection before creating a {@link DecisionTree} | ||
| * if you want to use it with a different type - | ||
| * the decision on how to stringify the value is up to you. | ||
| * | ||
| * @param nodes - nodes from the root level of the decision tree. | ||
| * @returns the string representation of the tree. | ||
| */ | ||
| declare function treeify(nodes: DecisionTreeNode<string>[]): string; | ||
| declare const TreeifyBuilder_treeify: typeof treeify; | ||
| declare namespace TreeifyBuilder { | ||
| export { | ||
| TreeifyBuilder_treeify as treeify, | ||
| }; | ||
| } | ||
| /** | ||
| * Options for DecisionTree construction. | ||
| */ | ||
| type DecisionTreeOptions = { | ||
| /** | ||
| * Attribute names whose values should be case-insensitive by default. | ||
| * | ||
| * Only affects attribute value selectors (`[name="value"]`) with no explicit case-sensitivity modifier (`i` or `s`). | ||
| */ | ||
| attributesWithNormalizedValues?: string[]; | ||
| }; | ||
| /** | ||
| * CSS selectors decision tree. | ||
| * Data structure that weaves similar selectors together | ||
| * in order to minimize the number of checks required | ||
| * to find the ones matching a given HTML element. | ||
| * | ||
| * Converted into a functioning implementation via plugins | ||
| * tailored for specific DOM ASTs. | ||
| * | ||
| * @typeParam V - the type of values associated with selectors. | ||
| */ | ||
| declare class DecisionTree<V> { | ||
| private readonly branches; | ||
| /** | ||
| * Create new DecisionTree object. | ||
| * | ||
| * @param input - an array containing all selectors | ||
| * paired with associated values. | ||
| * @param options - optional configuration for the decision tree. | ||
| * | ||
| * @typeParam V - the type of values associated with selectors. | ||
| */ | ||
| constructor(input: [string, V][], options?: DecisionTreeOptions); | ||
| /** | ||
| * Turn this decision tree into a callable form. | ||
| * | ||
| * @typeParam R - return type defined by the builder function. | ||
| * | ||
| * @param builder - the builder function. | ||
| * | ||
| * @returns the decision tree in a form ready for use. | ||
| */ | ||
| build<R>(builder: BuilderFunction<V, R>): R; | ||
| } | ||
| /** | ||
| * Simple wrapper around the matcher function. | ||
| * Recommended return type for builder plugins. | ||
| * | ||
| * @typeParam L - the type of HTML Element in the targeted DOM AST. | ||
| * @typeParam V - the type of associated values. | ||
| */ | ||
| declare class Picker<L, V> { | ||
| private f; | ||
| /** | ||
| * Create new Picker object. | ||
| * | ||
| * @typeParam L - the type of HTML Element in the targeted DOM AST. | ||
| * @typeParam V - the type of associated values. | ||
| * | ||
| * @param f - the function that matches an element | ||
| * and returns all associated values. | ||
| */ | ||
| constructor(f: MatcherFunction<L, V>); | ||
| /** | ||
| * Run the selectors decision tree against one HTML Element | ||
| * and return all matched associated values | ||
| * along with selector specificities. | ||
| * | ||
| * Client code then decides how to further process them | ||
| * (sort, filter, etc). | ||
| * | ||
| * @param el - an HTML Element. | ||
| * | ||
| * @returns all associated values along with | ||
| * selector specificities for all matched selectors. | ||
| */ | ||
| pickAll(el: L): ValueContainer<V>[]; | ||
| /** | ||
| * Run the selectors decision tree against one HTML Element | ||
| * and choose the value from the most specific matched selector. | ||
| * | ||
| * @param el - an HTML Element. | ||
| * | ||
| * @param preferFirst - option to define which value to choose | ||
| * when there are multiple matches with equal specificity. | ||
| * | ||
| * @returns the value from the most specific matched selector | ||
| * or `null` if nothing matched. | ||
| */ | ||
| pick1(el: L, preferFirst?: boolean): V | null; | ||
| } | ||
| export { Ast, DecisionTree, Picker, TreeifyBuilder as Treeify, Types }; | ||
| export type { DecisionTreeOptions }; |
+90
-79
| 'use strict'; | ||
| Object.defineProperty(exports, '__esModule', { value: true }); | ||
| var parseley = require('parseley'); | ||
| function _interopNamespace(e) { | ||
| if (e && e.__esModule) return e; | ||
| function _interopNamespaceDefault(e) { | ||
| var n = Object.create(null); | ||
@@ -21,17 +18,19 @@ if (e) { | ||
| } | ||
| n["default"] = e; | ||
| n.default = e; | ||
| return Object.freeze(n); | ||
| } | ||
| var parseley__namespace = /*#__PURE__*/_interopNamespace(parseley); | ||
| var parseley__namespace = /*#__PURE__*/_interopNamespaceDefault(parseley); | ||
| var Ast = /*#__PURE__*/Object.freeze({ | ||
| var Ast$1 = /*#__PURE__*/Object.freeze({ | ||
| __proto__: null | ||
| }); | ||
| var Types = /*#__PURE__*/Object.freeze({ | ||
| var Types$1 = /*#__PURE__*/Object.freeze({ | ||
| __proto__: null | ||
| }); | ||
| const treeify = (nodes) => '▽\n' + treeifyArray(nodes, thinLines); | ||
| function treeify(nodes) { | ||
| return '▽\n' + treeifyArray(nodes, thinLines); | ||
| } | ||
| const thinLines = [['├─', '│ '], ['└─', ' ']]; | ||
@@ -55,2 +54,4 @@ const heavyLines = [['┠─', '┃ '], ['┖─', ' ']]; | ||
| return `◨ Attr presence: ${node.name}\n${treeifyArray(node.cont)}`; | ||
| case 'pseudoClass': | ||
| return `◪ Pseudo-class: ${node.name}\n${treeifyArray(node.cont)}`; | ||
| case 'pushElement': | ||
@@ -82,4 +83,5 @@ return `◉ Push element: ${node.combinator}\n${treeifyArray(node.cont, thinLines)}`; | ||
| class DecisionTree { | ||
| constructor(input) { | ||
| this.branches = weave(toAstTerminalPairs(input)); | ||
| branches; | ||
| constructor(input, options = {}) { | ||
| this.branches = weave(toAstTerminalPairs(input, options)); | ||
| } | ||
@@ -90,3 +92,3 @@ build(builder) { | ||
| } | ||
| function toAstTerminalPairs(array) { | ||
| function toAstTerminalPairs(array, options) { | ||
| const len = array.length; | ||
@@ -96,3 +98,9 @@ const results = new Array(len); | ||
| const [selectorString, val] = array[i]; | ||
| const ast = preprocess(parseley__namespace.parse1(selectorString)); | ||
| const ast = parseley__namespace.parse1(selectorString); | ||
| reduceSelectorVariants(ast); | ||
| parseley__namespace.normalize(ast, { | ||
| mode: 'html', | ||
| attributesWithNormalizedValues: options.attributesWithNormalizedValues ?? [], | ||
| allowUnspecifiedCaseSensitivityForAttributes: false, | ||
| }); | ||
| results[i] = { | ||
@@ -102,4 +110,4 @@ ast: ast, | ||
| type: 'terminal', | ||
| valueContainer: { index: i, value: val, specificity: ast.specificity } | ||
| } | ||
| valueContainer: { index: i, value: val, specificity: ast.specificity }, | ||
| }, | ||
| }; | ||
@@ -109,10 +117,5 @@ } | ||
| } | ||
| function preprocess(ast) { | ||
| reduceSelectorVariants(ast); | ||
| parseley__namespace.normalize(ast); | ||
| return ast; | ||
| } | ||
| function reduceSelectorVariants(ast) { | ||
| const newList = []; | ||
| ast.list.forEach(sel => { | ||
| ast.list.forEach((sel) => { | ||
| switch (sel.type) { | ||
@@ -136,3 +139,3 @@ case 'class': | ||
| namespace: null, | ||
| specificity: sel.specificity, | ||
| specificity: [0, 1, 0], | ||
| type: 'attrValue', | ||
@@ -158,5 +161,5 @@ value: sel.name, | ||
| while (items.length) { | ||
| const topKind = findTopKey(items, (sel) => true, getSelectorKind); | ||
| const { matches, nonmatches, empty } = breakByKind(items, topKind); | ||
| items = nonmatches; | ||
| const topKind = findTopKey(items, (_sel) => true, getSelectorKind); | ||
| const { matches, nonMatches, empty } = breakByKind(items, topKind); | ||
| items = nonMatches; | ||
| if (matches.length) { | ||
@@ -180,3 +183,3 @@ branches.push(branchOfKind(topKind, matches)); | ||
| const { matches, rest } = partition(terminal.cont, (node) => node.type === 'terminal'); | ||
| matches.forEach((node) => results.push(node)); | ||
| matches.forEach(node => results.push(node)); | ||
| if (rest.length) { | ||
@@ -192,9 +195,9 @@ terminal.cont = rest; | ||
| const matches = []; | ||
| const nonmatches = []; | ||
| const nonMatches = []; | ||
| const empty = []; | ||
| for (const item of items) { | ||
| const simpsels = item.ast.list; | ||
| if (simpsels.length) { | ||
| const isMatch = simpsels.some(node => getSelectorKind(node) === selectedKind); | ||
| (isMatch ? matches : nonmatches).push(item); | ||
| const simpleSelectors = item.ast.list; | ||
| if (simpleSelectors.length) { | ||
| const isMatch = simpleSelectors.some(node => getSelectorKind(node) === selectedKind); | ||
| (isMatch ? matches : nonMatches).push(item); | ||
| } | ||
@@ -205,3 +208,3 @@ else { | ||
| } | ||
| return { matches, nonmatches, empty }; | ||
| return { matches, nonMatches, empty }; | ||
| } | ||
@@ -214,2 +217,4 @@ function getSelectorKind(sel) { | ||
| return `attrValue ${sel.name}`; | ||
| case 'pc': | ||
| return `pc ${sel.name}`; | ||
| case 'combinator': | ||
@@ -231,2 +236,5 @@ return `combinator ${sel.combinator}`; | ||
| } | ||
| if (kind.startsWith('pc ')) { | ||
| return pseudoClassBranch(kind.substring(3), items); | ||
| } | ||
| if (kind === 'combinator >') { | ||
@@ -241,11 +249,11 @@ return combinatorBranch('>', items); | ||
| function tagNameBranch(items) { | ||
| const groups = spliceAndGroup(items, (x) => x.type === 'tag', (x) => x.name); | ||
| const groups = spliceAndGroup(items, (x) => x.type === 'tag', x => x.name); | ||
| const variants = Object.entries(groups).map(([name, group]) => ({ | ||
| type: 'variant', | ||
| value: name, | ||
| cont: weave(group.items) | ||
| cont: weave(group.items), | ||
| })); | ||
| return { | ||
| type: 'tagName', | ||
| variants: variants | ||
| variants: variants, | ||
| }; | ||
@@ -260,12 +268,10 @@ } | ||
| name: name, | ||
| cont: weave(items) | ||
| cont: weave(items), | ||
| }; | ||
| } | ||
| function attrValueBranch(name, items) { | ||
| const groups = spliceAndGroup(items, (x) => (x.type === 'attrValue') && (x.name === name), (x) => `${x.matcher} ${x.modifier || ''} ${x.value}`); | ||
| const groups = spliceAndGroup(items, (x) => (x.type === 'attrValue') && (x.name === name), x => `${x.matcher} ${x.modifier || ''} ${x.value}`); | ||
| const matchers = []; | ||
| for (const group of Object.values(groups)) { | ||
| const sel = group.oneSimpleSelector; | ||
| const predicate = getAttrPredicate(sel); | ||
| const continuation = weave(group.items); | ||
| matchers.push({ | ||
@@ -276,4 +282,4 @@ type: 'matcher', | ||
| value: sel.value, | ||
| predicate: predicate, | ||
| cont: continuation | ||
| predicate: getAttrValuePredicate(sel), | ||
| cont: weave(group.items), | ||
| }); | ||
@@ -284,6 +290,6 @@ } | ||
| name: name, | ||
| matchers: matchers | ||
| matchers: matchers, | ||
| }; | ||
| } | ||
| function getAttrPredicate(sel) { | ||
| function getAttrValuePredicate(sel) { | ||
| if (sel.modifier === 'i') { | ||
@@ -293,11 +299,11 @@ const expected = sel.value.toLowerCase(); | ||
| case '=': | ||
| return (actual) => expected === actual.toLowerCase(); | ||
| return actual => expected === actual.toLowerCase(); | ||
| case '~=': | ||
| return (actual) => actual.toLowerCase().split(/[ \t]+/).includes(expected); | ||
| return actual => actual.toLowerCase().split(/[ \t]+/).includes(expected); | ||
| case '^=': | ||
| return (actual) => actual.toLowerCase().startsWith(expected); | ||
| return actual => actual.toLowerCase().startsWith(expected); | ||
| case '$=': | ||
| return (actual) => actual.toLowerCase().endsWith(expected); | ||
| return actual => actual.toLowerCase().endsWith(expected); | ||
| case '*=': | ||
| return (actual) => actual.toLowerCase().includes(expected); | ||
| return actual => actual.toLowerCase().includes(expected); | ||
| case '|=': | ||
@@ -314,18 +320,35 @@ return (actual) => { | ||
| case '=': | ||
| return (actual) => expected === actual; | ||
| return actual => expected === actual; | ||
| case '~=': | ||
| return (actual) => actual.split(/[ \t]+/).includes(expected); | ||
| return actual => actual.split(/[ \t]+/).includes(expected); | ||
| case '^=': | ||
| return (actual) => actual.startsWith(expected); | ||
| return actual => actual.startsWith(expected); | ||
| case '$=': | ||
| return (actual) => actual.endsWith(expected); | ||
| return actual => actual.endsWith(expected); | ||
| case '*=': | ||
| return (actual) => actual.includes(expected); | ||
| return actual => actual.includes(expected); | ||
| case '|=': | ||
| return (actual) => (expected === actual) || (actual.startsWith(expected) && actual[expected.length] === '-'); | ||
| return actual => (expected === actual) || (actual.startsWith(expected) && actual[expected.length] === '-'); | ||
| } | ||
| } | ||
| } | ||
| function pseudoClassBranch(name, items) { | ||
| if (name !== 'empty' | ||
| && name !== 'only-child' | ||
| && name !== 'first-child' | ||
| && name !== 'last-child' | ||
| && name !== 'any-link') { | ||
| throw new Error(`Unsupported pseudo-class: :${name}`); | ||
| } | ||
| for (const item of items) { | ||
| spliceSimpleSelector(item, (x) => (x.type === 'pc') && (x.name === name)); | ||
| } | ||
| return { | ||
| type: 'pseudoClass', | ||
| name: name, | ||
| cont: weave(items), | ||
| }; | ||
| } | ||
| function combinatorBranch(combinator, items) { | ||
| const groups = spliceAndGroup(items, (x) => (x.type === 'combinator') && (x.combinator === combinator), (x) => parseley__namespace.serialize(x.left)); | ||
| const groups = spliceAndGroup(items, (x) => (x.type === 'combinator') && (x.combinator === combinator), x => parseley__namespace.serialize(x.left)); | ||
| const leftItems = []; | ||
@@ -337,3 +360,3 @@ for (const group of Object.values(groups)) { | ||
| ast: leftAst, | ||
| terminal: { type: 'popElement', cont: rightCont } | ||
| terminal: { type: 'popElement', cont: rightCont }, | ||
| }); | ||
@@ -344,3 +367,3 @@ } | ||
| combinator: combinator, | ||
| cont: weave(leftItems) | ||
| cont: weave(leftItems), | ||
| }; | ||
@@ -354,3 +377,3 @@ } | ||
| const hasBestKeyPredicate = (item) => item.ast.list.some(bestKeyPredicate); | ||
| const { matches, rest } = partition1(items, hasBestKeyPredicate); | ||
| const { matches, rest } = partition(items, hasBestKeyPredicate); | ||
| let oneSimpleSelector = null; | ||
@@ -372,7 +395,7 @@ for (const item of matches) { | ||
| function spliceSimpleSelector(item, predicate) { | ||
| const simpsels = item.ast.list; | ||
| const matches = new Array(simpsels.length); | ||
| const simpleSelectors = item.ast.list; | ||
| const matches = new Array(simpleSelectors.length); | ||
| let firstIndex = -1; | ||
| for (let i = simpsels.length; i-- > 0;) { | ||
| if (predicate(simpsels[i])) { | ||
| for (let i = simpleSelectors.length; i-- > 0;) { | ||
| if (predicate(simpleSelectors[i])) { | ||
| matches[i] = true; | ||
@@ -385,4 +408,4 @@ firstIndex = i; | ||
| } | ||
| const result = simpsels[firstIndex]; | ||
| item.ast.list = simpsels.filter((sel, i) => !matches[i]); | ||
| const result = simpleSelectors[firstIndex]; | ||
| item.ast.list = simpleSelectors.filter((_, i) => !matches[i]); | ||
| return result; | ||
@@ -429,17 +452,5 @@ } | ||
| } | ||
| function partition1(src, predicate) { | ||
| const matches = []; | ||
| const rest = []; | ||
| for (const x of src) { | ||
| if (predicate(x)) { | ||
| matches.push(x); | ||
| } | ||
| else { | ||
| rest.push(x); | ||
| } | ||
| } | ||
| return { matches, rest }; | ||
| } | ||
| class Picker { | ||
| f; | ||
| constructor(f) { | ||
@@ -482,6 +493,6 @@ this.f = f; | ||
| exports.Ast = Ast; | ||
| exports.Ast = Ast$1; | ||
| exports.DecisionTree = DecisionTree; | ||
| exports.Picker = Picker; | ||
| exports.Treeify = TreeifyBuilder; | ||
| exports.Types = Types; | ||
| exports.Types = Types$1; |
+86
-72
| import * as parseley from 'parseley'; | ||
| import { compareSpecificity } from 'parseley'; | ||
| var Ast = /*#__PURE__*/Object.freeze({ | ||
| var Ast$1 = /*#__PURE__*/Object.freeze({ | ||
| __proto__: null | ||
| }); | ||
| var Types = /*#__PURE__*/Object.freeze({ | ||
| var Types$1 = /*#__PURE__*/Object.freeze({ | ||
| __proto__: null | ||
| }); | ||
| const treeify = (nodes) => '▽\n' + treeifyArray(nodes, thinLines); | ||
| function treeify(nodes) { | ||
| return '▽\n' + treeifyArray(nodes, thinLines); | ||
| } | ||
| const thinLines = [['├─', '│ '], ['└─', ' ']]; | ||
@@ -31,2 +33,4 @@ const heavyLines = [['┠─', '┃ '], ['┖─', ' ']]; | ||
| return `◨ Attr presence: ${node.name}\n${treeifyArray(node.cont)}`; | ||
| case 'pseudoClass': | ||
| return `◪ Pseudo-class: ${node.name}\n${treeifyArray(node.cont)}`; | ||
| case 'pushElement': | ||
@@ -58,4 +62,5 @@ return `◉ Push element: ${node.combinator}\n${treeifyArray(node.cont, thinLines)}`; | ||
| class DecisionTree { | ||
| constructor(input) { | ||
| this.branches = weave(toAstTerminalPairs(input)); | ||
| branches; | ||
| constructor(input, options = {}) { | ||
| this.branches = weave(toAstTerminalPairs(input, options)); | ||
| } | ||
@@ -66,3 +71,3 @@ build(builder) { | ||
| } | ||
| function toAstTerminalPairs(array) { | ||
| function toAstTerminalPairs(array, options) { | ||
| const len = array.length; | ||
@@ -72,3 +77,9 @@ const results = new Array(len); | ||
| const [selectorString, val] = array[i]; | ||
| const ast = preprocess(parseley.parse1(selectorString)); | ||
| const ast = parseley.parse1(selectorString); | ||
| reduceSelectorVariants(ast); | ||
| parseley.normalize(ast, { | ||
| mode: 'html', | ||
| attributesWithNormalizedValues: options.attributesWithNormalizedValues ?? [], | ||
| allowUnspecifiedCaseSensitivityForAttributes: false, | ||
| }); | ||
| results[i] = { | ||
@@ -78,4 +89,4 @@ ast: ast, | ||
| type: 'terminal', | ||
| valueContainer: { index: i, value: val, specificity: ast.specificity } | ||
| } | ||
| valueContainer: { index: i, value: val, specificity: ast.specificity }, | ||
| }, | ||
| }; | ||
@@ -85,10 +96,5 @@ } | ||
| } | ||
| function preprocess(ast) { | ||
| reduceSelectorVariants(ast); | ||
| parseley.normalize(ast); | ||
| return ast; | ||
| } | ||
| function reduceSelectorVariants(ast) { | ||
| const newList = []; | ||
| ast.list.forEach(sel => { | ||
| ast.list.forEach((sel) => { | ||
| switch (sel.type) { | ||
@@ -112,3 +118,3 @@ case 'class': | ||
| namespace: null, | ||
| specificity: sel.specificity, | ||
| specificity: [0, 1, 0], | ||
| type: 'attrValue', | ||
@@ -134,5 +140,5 @@ value: sel.name, | ||
| while (items.length) { | ||
| const topKind = findTopKey(items, (sel) => true, getSelectorKind); | ||
| const { matches, nonmatches, empty } = breakByKind(items, topKind); | ||
| items = nonmatches; | ||
| const topKind = findTopKey(items, (_sel) => true, getSelectorKind); | ||
| const { matches, nonMatches, empty } = breakByKind(items, topKind); | ||
| items = nonMatches; | ||
| if (matches.length) { | ||
@@ -156,3 +162,3 @@ branches.push(branchOfKind(topKind, matches)); | ||
| const { matches, rest } = partition(terminal.cont, (node) => node.type === 'terminal'); | ||
| matches.forEach((node) => results.push(node)); | ||
| matches.forEach(node => results.push(node)); | ||
| if (rest.length) { | ||
@@ -168,9 +174,9 @@ terminal.cont = rest; | ||
| const matches = []; | ||
| const nonmatches = []; | ||
| const nonMatches = []; | ||
| const empty = []; | ||
| for (const item of items) { | ||
| const simpsels = item.ast.list; | ||
| if (simpsels.length) { | ||
| const isMatch = simpsels.some(node => getSelectorKind(node) === selectedKind); | ||
| (isMatch ? matches : nonmatches).push(item); | ||
| const simpleSelectors = item.ast.list; | ||
| if (simpleSelectors.length) { | ||
| const isMatch = simpleSelectors.some(node => getSelectorKind(node) === selectedKind); | ||
| (isMatch ? matches : nonMatches).push(item); | ||
| } | ||
@@ -181,3 +187,3 @@ else { | ||
| } | ||
| return { matches, nonmatches, empty }; | ||
| return { matches, nonMatches, empty }; | ||
| } | ||
@@ -190,2 +196,4 @@ function getSelectorKind(sel) { | ||
| return `attrValue ${sel.name}`; | ||
| case 'pc': | ||
| return `pc ${sel.name}`; | ||
| case 'combinator': | ||
@@ -207,2 +215,5 @@ return `combinator ${sel.combinator}`; | ||
| } | ||
| if (kind.startsWith('pc ')) { | ||
| return pseudoClassBranch(kind.substring(3), items); | ||
| } | ||
| if (kind === 'combinator >') { | ||
@@ -217,11 +228,11 @@ return combinatorBranch('>', items); | ||
| function tagNameBranch(items) { | ||
| const groups = spliceAndGroup(items, (x) => x.type === 'tag', (x) => x.name); | ||
| const groups = spliceAndGroup(items, (x) => x.type === 'tag', x => x.name); | ||
| const variants = Object.entries(groups).map(([name, group]) => ({ | ||
| type: 'variant', | ||
| value: name, | ||
| cont: weave(group.items) | ||
| cont: weave(group.items), | ||
| })); | ||
| return { | ||
| type: 'tagName', | ||
| variants: variants | ||
| variants: variants, | ||
| }; | ||
@@ -236,12 +247,10 @@ } | ||
| name: name, | ||
| cont: weave(items) | ||
| cont: weave(items), | ||
| }; | ||
| } | ||
| function attrValueBranch(name, items) { | ||
| const groups = spliceAndGroup(items, (x) => (x.type === 'attrValue') && (x.name === name), (x) => `${x.matcher} ${x.modifier || ''} ${x.value}`); | ||
| const groups = spliceAndGroup(items, (x) => (x.type === 'attrValue') && (x.name === name), x => `${x.matcher} ${x.modifier || ''} ${x.value}`); | ||
| const matchers = []; | ||
| for (const group of Object.values(groups)) { | ||
| const sel = group.oneSimpleSelector; | ||
| const predicate = getAttrPredicate(sel); | ||
| const continuation = weave(group.items); | ||
| matchers.push({ | ||
@@ -252,4 +261,4 @@ type: 'matcher', | ||
| value: sel.value, | ||
| predicate: predicate, | ||
| cont: continuation | ||
| predicate: getAttrValuePredicate(sel), | ||
| cont: weave(group.items), | ||
| }); | ||
@@ -260,6 +269,6 @@ } | ||
| name: name, | ||
| matchers: matchers | ||
| matchers: matchers, | ||
| }; | ||
| } | ||
| function getAttrPredicate(sel) { | ||
| function getAttrValuePredicate(sel) { | ||
| if (sel.modifier === 'i') { | ||
@@ -269,11 +278,11 @@ const expected = sel.value.toLowerCase(); | ||
| case '=': | ||
| return (actual) => expected === actual.toLowerCase(); | ||
| return actual => expected === actual.toLowerCase(); | ||
| case '~=': | ||
| return (actual) => actual.toLowerCase().split(/[ \t]+/).includes(expected); | ||
| return actual => actual.toLowerCase().split(/[ \t]+/).includes(expected); | ||
| case '^=': | ||
| return (actual) => actual.toLowerCase().startsWith(expected); | ||
| return actual => actual.toLowerCase().startsWith(expected); | ||
| case '$=': | ||
| return (actual) => actual.toLowerCase().endsWith(expected); | ||
| return actual => actual.toLowerCase().endsWith(expected); | ||
| case '*=': | ||
| return (actual) => actual.toLowerCase().includes(expected); | ||
| return actual => actual.toLowerCase().includes(expected); | ||
| case '|=': | ||
@@ -290,18 +299,35 @@ return (actual) => { | ||
| case '=': | ||
| return (actual) => expected === actual; | ||
| return actual => expected === actual; | ||
| case '~=': | ||
| return (actual) => actual.split(/[ \t]+/).includes(expected); | ||
| return actual => actual.split(/[ \t]+/).includes(expected); | ||
| case '^=': | ||
| return (actual) => actual.startsWith(expected); | ||
| return actual => actual.startsWith(expected); | ||
| case '$=': | ||
| return (actual) => actual.endsWith(expected); | ||
| return actual => actual.endsWith(expected); | ||
| case '*=': | ||
| return (actual) => actual.includes(expected); | ||
| return actual => actual.includes(expected); | ||
| case '|=': | ||
| return (actual) => (expected === actual) || (actual.startsWith(expected) && actual[expected.length] === '-'); | ||
| return actual => (expected === actual) || (actual.startsWith(expected) && actual[expected.length] === '-'); | ||
| } | ||
| } | ||
| } | ||
| function pseudoClassBranch(name, items) { | ||
| if (name !== 'empty' | ||
| && name !== 'only-child' | ||
| && name !== 'first-child' | ||
| && name !== 'last-child' | ||
| && name !== 'any-link') { | ||
| throw new Error(`Unsupported pseudo-class: :${name}`); | ||
| } | ||
| for (const item of items) { | ||
| spliceSimpleSelector(item, (x) => (x.type === 'pc') && (x.name === name)); | ||
| } | ||
| return { | ||
| type: 'pseudoClass', | ||
| name: name, | ||
| cont: weave(items), | ||
| }; | ||
| } | ||
| function combinatorBranch(combinator, items) { | ||
| const groups = spliceAndGroup(items, (x) => (x.type === 'combinator') && (x.combinator === combinator), (x) => parseley.serialize(x.left)); | ||
| const groups = spliceAndGroup(items, (x) => (x.type === 'combinator') && (x.combinator === combinator), x => parseley.serialize(x.left)); | ||
| const leftItems = []; | ||
@@ -313,3 +339,3 @@ for (const group of Object.values(groups)) { | ||
| ast: leftAst, | ||
| terminal: { type: 'popElement', cont: rightCont } | ||
| terminal: { type: 'popElement', cont: rightCont }, | ||
| }); | ||
@@ -320,3 +346,3 @@ } | ||
| combinator: combinator, | ||
| cont: weave(leftItems) | ||
| cont: weave(leftItems), | ||
| }; | ||
@@ -330,3 +356,3 @@ } | ||
| const hasBestKeyPredicate = (item) => item.ast.list.some(bestKeyPredicate); | ||
| const { matches, rest } = partition1(items, hasBestKeyPredicate); | ||
| const { matches, rest } = partition(items, hasBestKeyPredicate); | ||
| let oneSimpleSelector = null; | ||
@@ -348,7 +374,7 @@ for (const item of matches) { | ||
| function spliceSimpleSelector(item, predicate) { | ||
| const simpsels = item.ast.list; | ||
| const matches = new Array(simpsels.length); | ||
| const simpleSelectors = item.ast.list; | ||
| const matches = new Array(simpleSelectors.length); | ||
| let firstIndex = -1; | ||
| for (let i = simpsels.length; i-- > 0;) { | ||
| if (predicate(simpsels[i])) { | ||
| for (let i = simpleSelectors.length; i-- > 0;) { | ||
| if (predicate(simpleSelectors[i])) { | ||
| matches[i] = true; | ||
@@ -361,4 +387,4 @@ firstIndex = i; | ||
| } | ||
| const result = simpsels[firstIndex]; | ||
| item.ast.list = simpsels.filter((sel, i) => !matches[i]); | ||
| const result = simpleSelectors[firstIndex]; | ||
| item.ast.list = simpleSelectors.filter((_, i) => !matches[i]); | ||
| return result; | ||
@@ -405,17 +431,5 @@ } | ||
| } | ||
| function partition1(src, predicate) { | ||
| const matches = []; | ||
| const rest = []; | ||
| for (const x of src) { | ||
| if (predicate(x)) { | ||
| matches.push(x); | ||
| } | ||
| else { | ||
| rest.push(x); | ||
| } | ||
| } | ||
| return { matches, rest }; | ||
| } | ||
| class Picker { | ||
| f; | ||
| constructor(f) { | ||
@@ -458,2 +472,2 @@ this.f = f; | ||
| export { Ast, DecisionTree, Picker, TreeifyBuilder as Treeify, Types }; | ||
| export { Ast$1 as Ast, DecisionTree, Picker, TreeifyBuilder as Treeify, Types$1 as Types }; |
+1
-1
| MIT License | ||
| Copyright (c) 2021-2022 KillyMXI <killy@mxii.eu.org> | ||
| Copyright (c) 2021-2026 KillyMXI <killy@mxii.eu.org> | ||
@@ -5,0 +5,0 @@ Permission is hereby granted, free of charge, to any person obtaining a copy |
+15
-7
| { | ||
| "name": "selderee", | ||
| "version": "0.11.0", | ||
| "version": "0.12.0", | ||
| "description": "Selectors decision tree - choose matching selectors, fast", | ||
@@ -20,7 +20,16 @@ "keywords": [ | ||
| "author": "KillyMXI", | ||
| "funding": "https://ko-fi.com/killymxi", | ||
| "funding": "https://github.com/sponsors/KillyMXI", | ||
| "license": "MIT", | ||
| "exports": { | ||
| "import": "./lib/selderee.mjs", | ||
| "require": "./lib/selderee.cjs" | ||
| ".": { | ||
| "import": { | ||
| "types": "./lib/selderee.d.mts", | ||
| "default": "./lib/selderee.mjs" | ||
| }, | ||
| "require": { | ||
| "types": "./lib/selderee.d.cts", | ||
| "default": "./lib/selderee.cjs" | ||
| } | ||
| }, | ||
| "./package.json": "./package.json" | ||
| }, | ||
@@ -30,4 +39,3 @@ "type": "module", | ||
| "module": "./lib/selderee.mjs", | ||
| "types": "./lib/selderee.d.ts", | ||
| "typedocMain": "./src/selderee.ts", | ||
| "types": "./lib/selderee.d.cts", | ||
| "files": [ | ||
@@ -44,4 +52,4 @@ "lib" | ||
| "dependencies": { | ||
| "parseley": "^0.12.0" | ||
| "parseley": "~0.13.1" | ||
| } | ||
| } |
+18
-18
@@ -26,4 +26,8 @@ # selderee | ||
| - Pseudo-classes and pseudo-elements are not supported by the underlying library [parseley](https://github.com/mxxii/parseley) (yet?); | ||
| - General siblings (`~`), descendants (` `) and same column combinators (`||`) are also not supported. | ||
| - Only selected combinators are supported - child (`>`) and adjacent siblings (`+`); | ||
| - General siblings (`~`), descendants (` `) and same column (`||`) combinators are not supported; | ||
| - (0.12.x+) Only selected pseudo-classes are currently supported - `:empty`, `:first-child`, `:last-child`, `:only-child` and `:any-link`; | ||
| - Other pseudo-classes and pseudo-elements are not supported here or in the underlying library [parseley](https://github.com/mxxii/parseley) yet; | ||
| - Case sensitivity model of attribute value selectors is simplified: matching is case-sensitive by default (if no case-sensitivity flag is specified); | ||
| - (0.12.x+) It is left to client code to specify what attributes have to be case-insensitive by default - via `DecisionTreeOptions.attributesWithNormalizedValues`. | ||
@@ -83,3 +87,6 @@ | ||
| // Make a tree structure from all given selectors. | ||
| const selectorsDecisionTree = new DecisionTree(selectorValuePairs); | ||
| const selectorsDecisionTree = new DecisionTree( | ||
| selectorValuePairs, | ||
| { attributesWithNormalizedValues: [] }, // optional, default is `[]` | ||
| ); | ||
@@ -110,3 +117,3 @@ // `treeify` builder produces a string output for testing and debug purposes. | ||
| <details><summary>Example output</summary> | ||
| Output of the example: | ||
@@ -118,3 +125,3 @@ ```text | ||
| │ ║ ┠─▣ Attr value: class | ||
| │ ║ ┃ ╙─◈ ~= "foo" | ||
| │ ║ ┃ ╙─◈ ~= "foo"s | ||
| │ ║ ┃ ┠─◨ Attr presence: bar | ||
@@ -134,11 +141,13 @@ │ ║ ┃ ┃ ┖─◁ #1 [0,2,1] B | ||
| │ ┖─▣ Attr value: class | ||
| │ ╙─◈ ~= "foo" | ||
| │ ╙─◈ ~= "foo"s | ||
| │ ┖─◁ #3 [0,1,1] D | ||
| └─▣ Attr value: id | ||
| ╙─◈ = "baz" | ||
| ╙─◈ = "baz"s | ||
| ┖─◁ #6 [1,0,0] G | ||
| [ { index: 2, value: 'C', specificity: [ 0, 1, 1 ] }, | ||
| [ | ||
| { index: 2, value: 'C', specificity: [ 0, 1, 1 ] }, | ||
| { index: 4, value: 'E', specificity: [ 0, 1, 2 ] }, | ||
| { index: 0, value: 'A', specificity: [ 0, 0, 1 ] }, | ||
| { index: 5, value: 'F', specificity: [ 0, 0, 2 ] } ] | ||
| { index: 5, value: 'F', specificity: [ 0, 0, 2 ] } | ||
| ] | ||
| Best matched value: E | ||
@@ -148,10 +157,1 @@ ``` | ||
| *Some gotcha: you may notice the check for `#baz` has to be performed every time the decision tree is called. If it happens to be `p#baz` or `div#baz` or even `.foo#baz` - it would be much better to write it like this. Deeper, narrower tree means less checks on average. (in case of `.foo#baz` the class check might finally outweigh the tag name check and rebalance the tree.)* | ||
| </details> | ||
| ## Development | ||
| Targeting Node.js version >=14. | ||
| Monorepo uses NPM v7 workspaces (make sure v7 is installed when used with Node.js v14.) |
-35
| # Changelog | ||
| ## Version 0.11.0 | ||
| * Bump `parseley` dependency to version 0.12.0 ([changelog](https://github.com/mxxii/parseley/blob/main/CHANGELOG.md)). Escape sequences in selectors. | ||
| ## Version 0.10.0 | ||
| * Targeting Node.js version 14 and ES2020; | ||
| * Bump dependencies. | ||
| ## Version 0.9.0 | ||
| * Bump dependencies - fix "./core module cannot be found" issue. | ||
| ## Version 0.8.1 | ||
| * Bump `parseley` dependency to version 0.9.1 ([changelog](https://github.com/mxxii/parseley/blob/main/CHANGELOG.md)). Now all dependencies are TypeScript, dual CommonJS/ES module packages; | ||
| * Use `rollup-plugin-cleanup` to condition published files; | ||
| * Package is marked as free of side effects. | ||
| ## Version 0.7.0 | ||
| * Drop Node.js version 10 support. At least 12.22.x is required. | ||
| ## Version 0.6.0 | ||
| * Give priority to more common attribute values (Previously the first matching simple selector was taken instead); | ||
| * Repeated simple selectors should not affect the tree balance and should not produce extra tree nodes (But they affect the specificity). | ||
| ## Version 0.5.0 | ||
| Initial release. | ||
| Aiming at Node.js version 10 and up. |
-100
| /** | ||
| * Specificity as defined by Selectors spec. | ||
| * | ||
| * {@link https://www.w3.org/TR/selectors/#specificity} | ||
| * | ||
| * Three levels: for id, class, tag (type). | ||
| * | ||
| * Extra level(s) used in HTML styling don't fit the scope of this package | ||
| * and no space reserved for them. | ||
| */ | ||
| export declare type Specificity = [number, number, number]; | ||
| /** | ||
| * Container for the associated value, | ||
| * selector specificity and position in the selectors collection. | ||
| * | ||
| * @typeParam V - the type of the associated value. | ||
| */ | ||
| export declare type ValueContainer<V> = { | ||
| index: number; | ||
| specificity: Specificity; | ||
| value: V; | ||
| }; | ||
| /** | ||
| * When reached a terminal node, decision tree adds | ||
| * the value container to the list of successful matches. | ||
| */ | ||
| export declare type TerminalNode<V> = { | ||
| type: 'terminal'; | ||
| valueContainer: ValueContainer<V>; | ||
| }; | ||
| /** | ||
| * Tag name has to be checked. | ||
| * Underlying variants can be assembled | ||
| * into a dictionary key check. | ||
| */ | ||
| export declare type TagNameNode<V> = { | ||
| type: 'tagName'; | ||
| variants: VariantNode<V>[]; | ||
| }; | ||
| /** | ||
| * String value variant. | ||
| */ | ||
| export declare type VariantNode<V> = { | ||
| type: 'variant'; | ||
| value: string; | ||
| cont: DecisionTreeNode<V>[]; | ||
| }; | ||
| /** | ||
| * Have to check the presence of an element attribute | ||
| * with the given name. | ||
| */ | ||
| export declare type AttrPresenceNode<V> = { | ||
| type: 'attrPresence'; | ||
| name: string; | ||
| cont: DecisionTreeNode<V>[]; | ||
| }; | ||
| /** | ||
| * Have to check the value of an element attribute | ||
| * with the given name. | ||
| * It usually requires to run all underlying matchers | ||
| * one after another. | ||
| */ | ||
| export declare type AttrValueNode<V> = { | ||
| type: 'attrValue'; | ||
| name: string; | ||
| matchers: MatcherNode<V>[]; | ||
| }; | ||
| /** | ||
| * String value matcher. | ||
| * Contains the predicate so no need to reimplement it | ||
| * from descriptive parameters. | ||
| */ | ||
| export declare type MatcherNode<V> = { | ||
| type: 'matcher'; | ||
| matcher: '=' | '~=' | '|=' | '^=' | '$=' | '*='; | ||
| modifier: 'i' | 's' | null; | ||
| value: string; | ||
| predicate: (prop: string) => boolean; | ||
| cont: DecisionTreeNode<V>[]; | ||
| }; | ||
| /** | ||
| * Push next element on the stack, defined by the combinator. | ||
| * Only `>` and `+` are expected to be supported. | ||
| * | ||
| * All checks are performed on the element on top of the stack. | ||
| */ | ||
| export declare type PushElementNode<V> = { | ||
| type: 'pushElement'; | ||
| combinator: '>' | '+'; | ||
| cont: DecisionTreeNode<V>[]; | ||
| }; | ||
| /** | ||
| * Remove the top element from the stack - | ||
| * following checks are performed on the previous element. | ||
| */ | ||
| export declare type PopElementNode<V> = { | ||
| type: 'popElement'; | ||
| cont: DecisionTreeNode<V>[]; | ||
| }; | ||
| export declare type DecisionTreeNode<V> = TerminalNode<V> | TagNameNode<V> | AttrPresenceNode<V> | AttrValueNode<V> | PushElementNode<V> | PopElementNode<V>; |
| import { BuilderFunction } from './Types'; | ||
| /** | ||
| * CSS selectors decision tree. | ||
| * Data structure that weaves similar selectors together | ||
| * in order to minimize the number of checks required | ||
| * to find the ones matching a given HTML element. | ||
| * | ||
| * Converted into a functioning implementation via plugins | ||
| * tailored for specific DOM ASTs. | ||
| * | ||
| * @typeParam V - the type of values associated with selectors. | ||
| */ | ||
| export declare class DecisionTree<V> { | ||
| private readonly branches; | ||
| /** | ||
| * Create new DecisionTree object. | ||
| * | ||
| * @param input - an array containing all selectors | ||
| * paired with associated values. | ||
| * | ||
| * @typeParam V - the type of values associated with selectors. | ||
| */ | ||
| constructor(input: [string, V][]); | ||
| /** | ||
| * Turn this decision tree into a usable form. | ||
| * | ||
| * @typeParam R - return type defined by the builder function. | ||
| * | ||
| * @param builder - the builder function. | ||
| * | ||
| * @returns the decision tree in a form ready for use. | ||
| */ | ||
| build<R>(builder: BuilderFunction<V, R>): R; | ||
| } |
| import { ValueContainer } from './Ast'; | ||
| import { MatcherFunction } from './Types'; | ||
| /** | ||
| * Simple wrapper around the matcher function. | ||
| * Recommended return type for builder plugins. | ||
| * | ||
| * @typeParam L - the type of HTML Element in the targeted DOM AST. | ||
| * @typeParam V - the type of associated values. | ||
| */ | ||
| export declare class Picker<L, V> { | ||
| private f; | ||
| /** | ||
| * Create new Picker object. | ||
| * | ||
| * @typeParam L - the type of HTML Element in the targeted DOM AST. | ||
| * @typeParam V - the type of associated values. | ||
| * | ||
| * @param f - the function that matches an element | ||
| * and returns all associated values. | ||
| */ | ||
| constructor(f: MatcherFunction<L, V>); | ||
| /** | ||
| * Run the selectors decision tree against one HTML Element | ||
| * and return all matched associated values | ||
| * along with selector specificities. | ||
| * | ||
| * Client code then decides how to further process them | ||
| * (sort, filter, etc). | ||
| * | ||
| * @param el - an HTML Element. | ||
| * | ||
| * @returns all associated values along with | ||
| * selector specificities for all matched selectors. | ||
| */ | ||
| pickAll(el: L): ValueContainer<V>[]; | ||
| /** | ||
| * Run the selectors decision tree against one HTML Element | ||
| * and choose the value from the most specific matched selector. | ||
| * | ||
| * @param el - an HTML Element. | ||
| * | ||
| * @param preferFirst - option to define which value to choose | ||
| * when there are multiple matches with equal specificity. | ||
| * | ||
| * @returns the value from the most specific matched selector | ||
| * or `null` if nothing matched. | ||
| */ | ||
| pick1(el: L, preferFirst?: boolean): V | null; | ||
| } |
| export * as Ast from './Ast'; | ||
| export * as Types from './Types'; | ||
| export * as Treeify from './TreeifyBuilder'; | ||
| export * from './DecisionTree'; | ||
| export * from './Picker'; |
| import { BuilderFunction } from './Types'; | ||
| /** | ||
| * A {@link BuilderFunction} implementation. | ||
| * | ||
| * Produces a string representation of the tree | ||
| * for testing and debug purposes. | ||
| * | ||
| * Only accepts `string` as the associated value type. | ||
| * Map your input collection before creating a {@link DecisionTree} | ||
| * if you want to use it with a different type - | ||
| * the decision on how to stringify the value is up to you. | ||
| * | ||
| * @param nodes - nodes from the root level of the decision tree. | ||
| * @returns the string representation of the tree. | ||
| */ | ||
| export declare const treeify: BuilderFunction<string, string>; |
| import { DecisionTreeNode, ValueContainer } from './Ast'; | ||
| /** | ||
| * A function that turn a decision tree into a usable form. | ||
| * | ||
| * @typeParam V - the type of associated value. | ||
| * | ||
| * @typeParam R - return type for this builder | ||
| * (Consider using {@link Picker}.) | ||
| */ | ||
| export declare type BuilderFunction<V, R> = (nodes: DecisionTreeNode<V>[]) => R; | ||
| /** | ||
| * Recommended matcher function shape to implement | ||
| * in builders. | ||
| * | ||
| * The elements stack is represented as the arguments array. | ||
| * | ||
| * @typeParam L - the type of elements in a particular DOM AST. | ||
| * @typeParam V - the type of associated value. | ||
| */ | ||
| export declare type MatcherFunction<L, V> = (el: L, ...tail: L[]) => ValueContainer<V>[]; |
55869
25.17%7
-41.67%901
-18.02%+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
Updated