hexo/node_modules/selderee/lib/selderee.mjs

434 lines
14 KiB
JavaScript

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 };