first commit

This commit is contained in:
Myk
2025-07-31 23:47:20 +03:00
commit 2186b278a0
5149 changed files with 537218 additions and 0 deletions

35
node_modules/selderee/CHANGELOG.md generated vendored Normal file
View File

@@ -0,0 +1,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.

21
node_modules/selderee/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021-2022 KillyMXI <killy@mxii.eu.org>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

151
node_modules/selderee/README.md generated vendored Normal file
View File

@@ -0,0 +1,151 @@
# selderee
![lint status badge](https://github.com/mxxii/selderee/workflows/lint/badge.svg)
![test status badge](https://github.com/mxxii/selderee/workflows/test/badge.svg)
[![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/mxxii/selderee/blob/main/LICENSE)
[![npm](https://img.shields.io/npm/dw/selderee?color=informational&logo=npm)](https://www.npmjs.com/package/selderee)
**Sel**ectors **de**cision t**ree** - pick matching selectors, fast.
----
## What is it for
The problem statement: there are multiple CSS selectors with attached handlers, and a HTML DOM to process. For each HTML Element a matching handler has to be found and applied.
The naive approach is to walk through the DOM and test each and every selector against each Element. This means *O(n\*m)* complexity.
It is pretty clear though that if we have selectors that share something in common then we can reduce the number of checks.
The main `selderee` package offers the selectors tree structure. Runnable decision functions for specific DOM implementations are built via plugins.
## Limitations
- 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.
## `selderee` vs `css-select`
[css-select](https://github.com/fb55/css-select) - a CSS selector compiler & engine.
| Feature | `selderee` | `css-select` |
| ------------------------------------- | :--------: | :----------: |
| Support for `htmlparser2` DOM AST | plugin | + |
| "Compiles" into a function | + | + |
| Pick selector(s) for a given Element | + | |
| Query Element(s) for a given selector | | + |
## Packages
| Package | Version | Folder | Changelog |
| --------- | --------- | --------- | --------- |
| [selderee](https://www.npmjs.com/package/selderee) | [![npm](https://img.shields.io/npm/v/selderee?logo=npm)](https://www.npmjs.com/package/selderee) | [/packages/selderee](https://github.com/mxxii/selderee/tree/main/packages/selderee/) | [changelog](https://github.com/mxxii/selderee/blob/main/packages/selderee/CHANGELOG.md) |
| [@selderee/plugin-htmlparser2](https://www.npmjs.com/package/@selderee/plugin-htmlparser2) | [![npm](https://img.shields.io/npm/v/@selderee/plugin-htmlparser2?logo=npm)](https://www.npmjs.com/package/@selderee/plugin-htmlparser2) | [/packages/plugin-htmlparser2](https://github.com/mxxii/selderee/tree/main/packages/plugin-htmlparser2/) | [changelog](https://github.com/mxxii/selderee/blob/main/packages/plugin-htmlparser2/CHANGELOG.md) |
## Install
```shell
> npm i selderee @selderee/plugin-htmlparser2
```
## Documentation
- [API](https://github.com/mxxii/selderee/blob/main/docs/index.md)
## Usage example
```js
const htmlparser2 = require('htmlparser2');
const util = require('util');
const { DecisionTree, Treeify } = require('selderee');
const { hp2Builder } = require('@selderee/plugin-htmlparser2');
const selectorValuePairs = [
['p', 'A'],
['p.foo[bar]', 'B'],
['p[class~=foo]', 'C'],
['div.foo', 'D'],
['div > p.foo', 'E'],
['div > p', 'F'],
['#baz', 'G']
];
// Make a tree structure from all given selectors.
const selectorsDecisionTree = new DecisionTree(selectorValuePairs);
// `treeify` builder produces a string output for testing and debug purposes.
// `treeify` expects string values attached to each selector.
const prettyTree = selectorsDecisionTree.build(Treeify.treeify);
console.log(prettyTree);
const html = /*html*/`<html><body>
<div><p class="foo qux">second</p></div>
</body></html>`;
const dom = htmlparser2.parseDocument(html);
const element = dom.children[0].children[0].children[1].children[0];
// `hp2Builder` produces a picker that can pick values
// from the selectors tree.
const picker = selectorsDecisionTree.build(hp2Builder);
// Get all matches
const allMatches = picker.pickAll(element);
console.log(util.inspect(allMatches, { breakLength: 70, depth: null }));
// or get the value from the most specific match.
const bestMatch = picker.pick1(element);
console.log(`Best matched value: ${bestMatch}`);
```
<details><summary>Example output</summary>
```text
├─◻ Tag name
│ ╟─◇ = p
│ ║ ┠─▣ Attr value: class
│ ║ ┃ ╙─◈ ~= "foo"
│ ║ ┃ ┠─◨ Attr presence: bar
│ ║ ┃ ┃ ┖─◁ #1 [0,2,1] B
│ ║ ┃ ┠─◁ #2 [0,1,1] C
│ ║ ┃ ┖─◉ Push element: >
│ ║ ┃ └─◻ Tag name
│ ║ ┃ ╙─◇ = div
│ ║ ┃ ┖─◁ #4 [0,1,2] E
│ ║ ┠─◁ #0 [0,0,1] A
│ ║ ┖─◉ Push element: >
│ ║ └─◻ Tag name
│ ║ ╙─◇ = div
│ ║ ┖─◁ #5 [0,0,2] F
│ ╙─◇ = div
│ ┖─▣ Attr value: class
│ ╙─◈ ~= "foo"
│ ┖─◁ #3 [0,1,1] D
└─▣ Attr value: id
╙─◈ = "baz"
┖─◁ #6 [1,0,0] G
[ { 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 ] } ]
Best matched value: E
```
*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.)

100
node_modules/selderee/lib/Ast.d.ts generated vendored Normal file
View File

@@ -0,0 +1,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>;

34
node_modules/selderee/lib/DecisionTree.d.ts generated vendored Normal file
View File

@@ -0,0 +1,34 @@
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;
}

49
node_modules/selderee/lib/Picker.d.ts generated vendored Normal file
View File

@@ -0,0 +1,49 @@
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;
}

16
node_modules/selderee/lib/TreeifyBuilder.d.ts generated vendored Normal file
View File

@@ -0,0 +1,16 @@
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>;

20
node_modules/selderee/lib/Types.d.ts generated vendored Normal file
View File

@@ -0,0 +1,20 @@
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>[];

460
node_modules/selderee/lib/selderee.cjs generated vendored Normal file
View File

@@ -0,0 +1,460 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var parseley = require('parseley');
function _interopNamespace(e) {
if (e && e.__esModule) return e;
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n["default"] = e;
return Object.freeze(n);
}
var parseley__namespace = /*#__PURE__*/_interopNamespace(parseley);
var Ast = /*#__PURE__*/Object.freeze({
__proto__: null
});
var Types = /*#__PURE__*/Object.freeze({
__proto__: null
});
const treeify = (nodes) => '▽\n' + treeifyArray(nodes, thinLines);
const thinLines = [['├─', '│ '], ['└─', ' ']];
const heavyLines = [['┠─', '┃ '], ['┖─', ' ']];
const doubleLines = [['╟─', '║ '], ['╙─', ' ']];
function treeifyArray(nodes, tpl = heavyLines) {
return prefixItems(tpl, nodes.map(n => treeifyNode(n)));
}
function treeifyNode(node) {
switch (node.type) {
case 'terminal': {
const vctr = node.valueContainer;
return `◁ #${vctr.index} ${JSON.stringify(vctr.specificity)} ${vctr.value}`;
}
case 'tagName':
return `◻ Tag name\n${treeifyArray(node.variants, doubleLines)}`;
case 'attrValue':
return `▣ Attr value: ${node.name}\n${treeifyArray(node.matchers, doubleLines)}`;
case 'attrPresence':
return `◨ Attr presence: ${node.name}\n${treeifyArray(node.cont)}`;
case 'pushElement':
return `◉ Push element: ${node.combinator}\n${treeifyArray(node.cont, thinLines)}`;
case 'popElement':
return `◌ Pop element\n${treeifyArray(node.cont, thinLines)}`;
case 'variant':
return `◇ = ${node.value}\n${treeifyArray(node.cont)}`;
case 'matcher':
return `${node.matcher} "${node.value}"${node.modifier || ''}\n${treeifyArray(node.cont)}`;
}
}
function prefixItems(tpl, items) {
return items
.map((item, i, { length }) => prefixItem(tpl, item, i === length - 1))
.join('\n');
}
function prefixItem(tpl, item, tail = true) {
const tpl1 = tpl[tail ? 1 : 0];
return tpl1[0] + item.split('\n').join('\n' + tpl1[1]);
}
var TreeifyBuilder = /*#__PURE__*/Object.freeze({
__proto__: null,
treeify: treeify
});
class DecisionTree {
constructor(input) {
this.branches = weave(toAstTerminalPairs(input));
}
build(builder) {
return builder(this.branches);
}
}
function toAstTerminalPairs(array) {
const len = array.length;
const results = new Array(len);
for (let i = 0; i < len; i++) {
const [selectorString, val] = array[i];
const ast = preprocess(parseley__namespace.parse1(selectorString));
results[i] = {
ast: ast,
terminal: {
type: 'terminal',
valueContainer: { index: i, value: val, specificity: ast.specificity }
}
};
}
return results;
}
function preprocess(ast) {
reduceSelectorVariants(ast);
parseley__namespace.normalize(ast);
return ast;
}
function reduceSelectorVariants(ast) {
const newList = [];
ast.list.forEach(sel => {
switch (sel.type) {
case 'class':
newList.push({
matcher: '~=',
modifier: null,
name: 'class',
namespace: null,
specificity: sel.specificity,
type: 'attrValue',
value: sel.name,
});
break;
case 'id':
newList.push({
matcher: '=',
modifier: null,
name: 'id',
namespace: null,
specificity: sel.specificity,
type: 'attrValue',
value: sel.name,
});
break;
case 'combinator':
reduceSelectorVariants(sel.left);
newList.push(sel);
break;
case 'universal':
break;
default:
newList.push(sel);
break;
}
});
ast.list = newList;
}
function weave(items) {
const branches = [];
while (items.length) {
const topKind = findTopKey(items, (sel) => true, getSelectorKind);
const { matches, nonmatches, empty } = breakByKind(items, topKind);
items = nonmatches;
if (matches.length) {
branches.push(branchOfKind(topKind, matches));
}
if (empty.length) {
branches.push(...terminate(empty));
}
}
return branches;
}
function terminate(items) {
const results = [];
for (const item of items) {
const terminal = item.terminal;
if (terminal.type === 'terminal') {
results.push(terminal);
}
else {
const { matches, rest } = partition(terminal.cont, (node) => node.type === 'terminal');
matches.forEach((node) => results.push(node));
if (rest.length) {
terminal.cont = rest;
results.push(terminal);
}
}
}
return results;
}
function breakByKind(items, selectedKind) {
const matches = [];
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);
}
else {
empty.push(item);
}
}
return { matches, nonmatches, empty };
}
function getSelectorKind(sel) {
switch (sel.type) {
case 'attrPresence':
return `attrPresence ${sel.name}`;
case 'attrValue':
return `attrValue ${sel.name}`;
case 'combinator':
return `combinator ${sel.combinator}`;
default:
return sel.type;
}
}
function branchOfKind(kind, items) {
if (kind === 'tag') {
return tagNameBranch(items);
}
if (kind.startsWith('attrValue ')) {
return attrValueBranch(kind.substring(10), items);
}
if (kind.startsWith('attrPresence ')) {
return attrPresenceBranch(kind.substring(13), items);
}
if (kind === 'combinator >') {
return combinatorBranch('>', items);
}
if (kind === 'combinator +') {
return combinatorBranch('+', items);
}
throw new Error(`Unsupported selector kind: ${kind}`);
}
function tagNameBranch(items) {
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)
}));
return {
type: 'tagName',
variants: variants
};
}
function attrPresenceBranch(name, items) {
for (const item of items) {
spliceSimpleSelector(item, (x) => (x.type === 'attrPresence') && (x.name === name));
}
return {
type: 'attrPresence',
name: name,
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 matchers = [];
for (const group of Object.values(groups)) {
const sel = group.oneSimpleSelector;
const predicate = getAttrPredicate(sel);
const continuation = weave(group.items);
matchers.push({
type: 'matcher',
matcher: sel.matcher,
modifier: sel.modifier,
value: sel.value,
predicate: predicate,
cont: continuation
});
}
return {
type: 'attrValue',
name: name,
matchers: matchers
};
}
function getAttrPredicate(sel) {
if (sel.modifier === 'i') {
const expected = sel.value.toLowerCase();
switch (sel.matcher) {
case '=':
return (actual) => expected === actual.toLowerCase();
case '~=':
return (actual) => actual.toLowerCase().split(/[ \t]+/).includes(expected);
case '^=':
return (actual) => actual.toLowerCase().startsWith(expected);
case '$=':
return (actual) => actual.toLowerCase().endsWith(expected);
case '*=':
return (actual) => actual.toLowerCase().includes(expected);
case '|=':
return (actual) => {
const lower = actual.toLowerCase();
return (expected === lower) || (lower.startsWith(expected) && lower[expected.length] === '-');
};
}
}
else {
const expected = sel.value;
switch (sel.matcher) {
case '=':
return (actual) => expected === actual;
case '~=':
return (actual) => actual.split(/[ \t]+/).includes(expected);
case '^=':
return (actual) => actual.startsWith(expected);
case '$=':
return (actual) => actual.endsWith(expected);
case '*=':
return (actual) => actual.includes(expected);
case '|=':
return (actual) => (expected === actual) || (actual.startsWith(expected) && actual[expected.length] === '-');
}
}
}
function combinatorBranch(combinator, items) {
const groups = spliceAndGroup(items, (x) => (x.type === 'combinator') && (x.combinator === combinator), (x) => parseley__namespace.serialize(x.left));
const leftItems = [];
for (const group of Object.values(groups)) {
const rightCont = weave(group.items);
const leftAst = group.oneSimpleSelector.left;
leftItems.push({
ast: leftAst,
terminal: { type: 'popElement', cont: rightCont }
});
}
return {
type: 'pushElement',
combinator: combinator,
cont: weave(leftItems)
};
}
function spliceAndGroup(items, predicate, keyCallback) {
const groups = {};
while (items.length) {
const bestKey = findTopKey(items, predicate, keyCallback);
const bestKeyPredicate = (sel) => predicate(sel) && keyCallback(sel) === bestKey;
const hasBestKeyPredicate = (item) => item.ast.list.some(bestKeyPredicate);
const { matches, rest } = partition1(items, hasBestKeyPredicate);
let oneSimpleSelector = null;
for (const item of matches) {
const splicedNode = spliceSimpleSelector(item, bestKeyPredicate);
if (!oneSimpleSelector) {
oneSimpleSelector = splicedNode;
}
}
if (oneSimpleSelector == null) {
throw new Error('No simple selector is found.');
}
groups[bestKey] = { oneSimpleSelector: oneSimpleSelector, items: matches };
items = rest;
}
return groups;
}
function spliceSimpleSelector(item, predicate) {
const simpsels = item.ast.list;
const matches = new Array(simpsels.length);
let firstIndex = -1;
for (let i = simpsels.length; i-- > 0;) {
if (predicate(simpsels[i])) {
matches[i] = true;
firstIndex = i;
}
}
if (firstIndex == -1) {
throw new Error(`Couldn't find the required simple selector.`);
}
const result = simpsels[firstIndex];
item.ast.list = simpsels.filter((sel, i) => !matches[i]);
return result;
}
function findTopKey(items, predicate, keyCallback) {
const candidates = {};
for (const item of items) {
const candidates1 = {};
for (const node of item.ast.list.filter(predicate)) {
candidates1[keyCallback(node)] = true;
}
for (const key of Object.keys(candidates1)) {
if (candidates[key]) {
candidates[key]++;
}
else {
candidates[key] = 1;
}
}
}
let topKind = '';
let topCounter = 0;
for (const entry of Object.entries(candidates)) {
if (entry[1] > topCounter) {
topKind = entry[0];
topCounter = entry[1];
}
}
return topKind;
}
function partition(src, predicate) {
const matches = [];
const rest = [];
for (const x of src) {
if (predicate(x)) {
matches.push(x);
}
else {
rest.push(x);
}
}
return { matches, rest };
}
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 {
constructor(f) {
this.f = f;
}
pickAll(el) {
return this.f(el);
}
pick1(el, preferFirst = false) {
const results = this.f(el);
const len = results.length;
if (len === 0) {
return null;
}
if (len === 1) {
return results[0].value;
}
const comparator = (preferFirst)
? comparatorPreferFirst
: comparatorPreferLast;
let result = results[0];
for (let i = 1; i < len; i++) {
const next = results[i];
if (comparator(result, next)) {
result = next;
}
}
return result.value;
}
}
function comparatorPreferFirst(acc, next) {
const diff = parseley.compareSpecificity(next.specificity, acc.specificity);
return diff > 0 || (diff === 0 && next.index < acc.index);
}
function comparatorPreferLast(acc, next) {
const diff = parseley.compareSpecificity(next.specificity, acc.specificity);
return diff > 0 || (diff === 0 && next.index > acc.index);
}
exports.Ast = Ast;
exports.DecisionTree = DecisionTree;
exports.Picker = Picker;
exports.Treeify = TreeifyBuilder;
exports.Types = Types;

5
node_modules/selderee/lib/selderee.d.ts generated vendored Normal file
View File

@@ -0,0 +1,5 @@
export * as Ast from './Ast';
export * as Types from './Types';
export * as Treeify from './TreeifyBuilder';
export * from './DecisionTree';
export * from './Picker';

433
node_modules/selderee/lib/selderee.mjs generated vendored Normal file
View File

@@ -0,0 +1,433 @@
import * as parseley from 'parseley';
import { compareSpecificity } from 'parseley';
var Ast = /*#__PURE__*/Object.freeze({
__proto__: null
});
var Types = /*#__PURE__*/Object.freeze({
__proto__: null
});
const treeify = (nodes) => '▽\n' + treeifyArray(nodes, thinLines);
const thinLines = [['├─', '│ '], ['└─', ' ']];
const heavyLines = [['┠─', '┃ '], ['┖─', ' ']];
const doubleLines = [['╟─', '║ '], ['╙─', ' ']];
function treeifyArray(nodes, tpl = heavyLines) {
return prefixItems(tpl, nodes.map(n => treeifyNode(n)));
}
function treeifyNode(node) {
switch (node.type) {
case 'terminal': {
const vctr = node.valueContainer;
return `◁ #${vctr.index} ${JSON.stringify(vctr.specificity)} ${vctr.value}`;
}
case 'tagName':
return `◻ Tag name\n${treeifyArray(node.variants, doubleLines)}`;
case 'attrValue':
return `▣ Attr value: ${node.name}\n${treeifyArray(node.matchers, doubleLines)}`;
case 'attrPresence':
return `◨ Attr presence: ${node.name}\n${treeifyArray(node.cont)}`;
case 'pushElement':
return `◉ Push element: ${node.combinator}\n${treeifyArray(node.cont, thinLines)}`;
case 'popElement':
return `◌ Pop element\n${treeifyArray(node.cont, thinLines)}`;
case 'variant':
return `◇ = ${node.value}\n${treeifyArray(node.cont)}`;
case 'matcher':
return `${node.matcher} "${node.value}"${node.modifier || ''}\n${treeifyArray(node.cont)}`;
}
}
function prefixItems(tpl, items) {
return items
.map((item, i, { length }) => prefixItem(tpl, item, i === length - 1))
.join('\n');
}
function prefixItem(tpl, item, tail = true) {
const tpl1 = tpl[tail ? 1 : 0];
return tpl1[0] + item.split('\n').join('\n' + tpl1[1]);
}
var TreeifyBuilder = /*#__PURE__*/Object.freeze({
__proto__: null,
treeify: treeify
});
class DecisionTree {
constructor(input) {
this.branches = weave(toAstTerminalPairs(input));
}
build(builder) {
return builder(this.branches);
}
}
function toAstTerminalPairs(array) {
const len = array.length;
const results = new Array(len);
for (let i = 0; i < len; i++) {
const [selectorString, val] = array[i];
const ast = preprocess(parseley.parse1(selectorString));
results[i] = {
ast: ast,
terminal: {
type: 'terminal',
valueContainer: { index: i, value: val, specificity: ast.specificity }
}
};
}
return results;
}
function preprocess(ast) {
reduceSelectorVariants(ast);
parseley.normalize(ast);
return ast;
}
function reduceSelectorVariants(ast) {
const newList = [];
ast.list.forEach(sel => {
switch (sel.type) {
case 'class':
newList.push({
matcher: '~=',
modifier: null,
name: 'class',
namespace: null,
specificity: sel.specificity,
type: 'attrValue',
value: sel.name,
});
break;
case 'id':
newList.push({
matcher: '=',
modifier: null,
name: 'id',
namespace: null,
specificity: sel.specificity,
type: 'attrValue',
value: sel.name,
});
break;
case 'combinator':
reduceSelectorVariants(sel.left);
newList.push(sel);
break;
case 'universal':
break;
default:
newList.push(sel);
break;
}
});
ast.list = newList;
}
function weave(items) {
const branches = [];
while (items.length) {
const topKind = findTopKey(items, (sel) => true, getSelectorKind);
const { matches, nonmatches, empty } = breakByKind(items, topKind);
items = nonmatches;
if (matches.length) {
branches.push(branchOfKind(topKind, matches));
}
if (empty.length) {
branches.push(...terminate(empty));
}
}
return branches;
}
function terminate(items) {
const results = [];
for (const item of items) {
const terminal = item.terminal;
if (terminal.type === 'terminal') {
results.push(terminal);
}
else {
const { matches, rest } = partition(terminal.cont, (node) => node.type === 'terminal');
matches.forEach((node) => results.push(node));
if (rest.length) {
terminal.cont = rest;
results.push(terminal);
}
}
}
return results;
}
function breakByKind(items, selectedKind) {
const matches = [];
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);
}
else {
empty.push(item);
}
}
return { matches, nonmatches, empty };
}
function getSelectorKind(sel) {
switch (sel.type) {
case 'attrPresence':
return `attrPresence ${sel.name}`;
case 'attrValue':
return `attrValue ${sel.name}`;
case 'combinator':
return `combinator ${sel.combinator}`;
default:
return sel.type;
}
}
function branchOfKind(kind, items) {
if (kind === 'tag') {
return tagNameBranch(items);
}
if (kind.startsWith('attrValue ')) {
return attrValueBranch(kind.substring(10), items);
}
if (kind.startsWith('attrPresence ')) {
return attrPresenceBranch(kind.substring(13), items);
}
if (kind === 'combinator >') {
return combinatorBranch('>', items);
}
if (kind === 'combinator +') {
return combinatorBranch('+', items);
}
throw new Error(`Unsupported selector kind: ${kind}`);
}
function tagNameBranch(items) {
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)
}));
return {
type: 'tagName',
variants: variants
};
}
function attrPresenceBranch(name, items) {
for (const item of items) {
spliceSimpleSelector(item, (x) => (x.type === 'attrPresence') && (x.name === name));
}
return {
type: 'attrPresence',
name: name,
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 matchers = [];
for (const group of Object.values(groups)) {
const sel = group.oneSimpleSelector;
const predicate = getAttrPredicate(sel);
const continuation = weave(group.items);
matchers.push({
type: 'matcher',
matcher: sel.matcher,
modifier: sel.modifier,
value: sel.value,
predicate: predicate,
cont: continuation
});
}
return {
type: 'attrValue',
name: name,
matchers: matchers
};
}
function getAttrPredicate(sel) {
if (sel.modifier === 'i') {
const expected = sel.value.toLowerCase();
switch (sel.matcher) {
case '=':
return (actual) => expected === actual.toLowerCase();
case '~=':
return (actual) => actual.toLowerCase().split(/[ \t]+/).includes(expected);
case '^=':
return (actual) => actual.toLowerCase().startsWith(expected);
case '$=':
return (actual) => actual.toLowerCase().endsWith(expected);
case '*=':
return (actual) => actual.toLowerCase().includes(expected);
case '|=':
return (actual) => {
const lower = actual.toLowerCase();
return (expected === lower) || (lower.startsWith(expected) && lower[expected.length] === '-');
};
}
}
else {
const expected = sel.value;
switch (sel.matcher) {
case '=':
return (actual) => expected === actual;
case '~=':
return (actual) => actual.split(/[ \t]+/).includes(expected);
case '^=':
return (actual) => actual.startsWith(expected);
case '$=':
return (actual) => actual.endsWith(expected);
case '*=':
return (actual) => actual.includes(expected);
case '|=':
return (actual) => (expected === actual) || (actual.startsWith(expected) && actual[expected.length] === '-');
}
}
}
function combinatorBranch(combinator, items) {
const groups = spliceAndGroup(items, (x) => (x.type === 'combinator') && (x.combinator === combinator), (x) => parseley.serialize(x.left));
const leftItems = [];
for (const group of Object.values(groups)) {
const rightCont = weave(group.items);
const leftAst = group.oneSimpleSelector.left;
leftItems.push({
ast: leftAst,
terminal: { type: 'popElement', cont: rightCont }
});
}
return {
type: 'pushElement',
combinator: combinator,
cont: weave(leftItems)
};
}
function spliceAndGroup(items, predicate, keyCallback) {
const groups = {};
while (items.length) {
const bestKey = findTopKey(items, predicate, keyCallback);
const bestKeyPredicate = (sel) => predicate(sel) && keyCallback(sel) === bestKey;
const hasBestKeyPredicate = (item) => item.ast.list.some(bestKeyPredicate);
const { matches, rest } = partition1(items, hasBestKeyPredicate);
let oneSimpleSelector = null;
for (const item of matches) {
const splicedNode = spliceSimpleSelector(item, bestKeyPredicate);
if (!oneSimpleSelector) {
oneSimpleSelector = splicedNode;
}
}
if (oneSimpleSelector == null) {
throw new Error('No simple selector is found.');
}
groups[bestKey] = { oneSimpleSelector: oneSimpleSelector, items: matches };
items = rest;
}
return groups;
}
function spliceSimpleSelector(item, predicate) {
const simpsels = item.ast.list;
const matches = new Array(simpsels.length);
let firstIndex = -1;
for (let i = simpsels.length; i-- > 0;) {
if (predicate(simpsels[i])) {
matches[i] = true;
firstIndex = i;
}
}
if (firstIndex == -1) {
throw new Error(`Couldn't find the required simple selector.`);
}
const result = simpsels[firstIndex];
item.ast.list = simpsels.filter((sel, i) => !matches[i]);
return result;
}
function findTopKey(items, predicate, keyCallback) {
const candidates = {};
for (const item of items) {
const candidates1 = {};
for (const node of item.ast.list.filter(predicate)) {
candidates1[keyCallback(node)] = true;
}
for (const key of Object.keys(candidates1)) {
if (candidates[key]) {
candidates[key]++;
}
else {
candidates[key] = 1;
}
}
}
let topKind = '';
let topCounter = 0;
for (const entry of Object.entries(candidates)) {
if (entry[1] > topCounter) {
topKind = entry[0];
topCounter = entry[1];
}
}
return topKind;
}
function partition(src, predicate) {
const matches = [];
const rest = [];
for (const x of src) {
if (predicate(x)) {
matches.push(x);
}
else {
rest.push(x);
}
}
return { matches, rest };
}
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 {
constructor(f) {
this.f = f;
}
pickAll(el) {
return this.f(el);
}
pick1(el, preferFirst = false) {
const results = this.f(el);
const len = results.length;
if (len === 0) {
return null;
}
if (len === 1) {
return results[0].value;
}
const comparator = (preferFirst)
? comparatorPreferFirst
: comparatorPreferLast;
let result = results[0];
for (let i = 1; i < len; i++) {
const next = results[i];
if (comparator(result, next)) {
result = next;
}
}
return result.value;
}
}
function comparatorPreferFirst(acc, next) {
const diff = compareSpecificity(next.specificity, acc.specificity);
return diff > 0 || (diff === 0 && next.index < acc.index);
}
function comparatorPreferLast(acc, next) {
const diff = compareSpecificity(next.specificity, acc.specificity);
return diff > 0 || (diff === 0 && next.index > acc.index);
}
export { Ast, DecisionTree, Picker, TreeifyBuilder as Treeify, Types };

44
node_modules/selderee/package.json generated vendored Normal file
View File

@@ -0,0 +1,44 @@
{
"name": "selderee",
"version": "0.11.0",
"description": "Selectors decision tree - choose matching selectors, fast",
"keywords": [
"CSS",
"selectors",
"decision tree",
"match"
],
"repository": {
"type": "git",
"url": "git+https://github.com/mxxii/selderee.git"
},
"bugs": {
"url": "https://github.com/mxxii/selderee/issues"
},
"homepage": "https://github.com/mxxii/selderee",
"author": "KillyMXI",
"funding": "https://ko-fi.com/killymxi",
"license": "MIT",
"exports": {
"import": "./lib/selderee.mjs",
"require": "./lib/selderee.cjs"
},
"type": "module",
"main": "./lib/selderee.cjs",
"module": "./lib/selderee.mjs",
"types": "./lib/selderee.d.ts",
"typedocMain": "./src/selderee.ts",
"files": [
"lib"
],
"sideEffects": false,
"scripts": {
"build:rollup": "rollup -c",
"build:types": "tsc -d --emitDeclarationOnly --declarationDir ./lib",
"build": "npm run clean && npm run build:rollup && npm run build:types",
"clean": "rimraf lib"
},
"dependencies": {
"parseley": "^0.12.0"
}
}