mirror of
https://github.com/svg/svgo.git
synced 2025-07-31 07:44:22 +03:00
Support matches and combinations
This commit is contained in:
170
lib/xast.js
170
lib/xast.js
@ -5,13 +5,12 @@
|
||||
* @typedef {import('./types').XastNode} XastNode
|
||||
* @typedef {import('./types').XastChild} XastChild
|
||||
* @typedef {import('./types').XastParent} XastParent
|
||||
* @typedef {import('./types').XastRoot} XastRoot
|
||||
* @typedef {import('./types').XastElement} XastElement
|
||||
* @typedef {import('./types').Visitor} Visitor
|
||||
*/
|
||||
|
||||
const csstree = require('css-tree');
|
||||
const { is } = require('css-select');
|
||||
const xastAdaptor = require('./svgo/css-select-adapter.js');
|
||||
|
||||
/**
|
||||
* @type {(string: string) => string}
|
||||
@ -65,24 +64,145 @@ const elementMatches = (csstreeNode, xastElement) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {(csstreeNode: CsstreeNode, xastNode: XastNode) => Array<XastElement>}
|
||||
* @type {(
|
||||
* startNode: XastParent,
|
||||
* descendants: Array<XastElement>,
|
||||
* parents: WeakMap<XastElement, XastParent>
|
||||
* ) => void}
|
||||
*/
|
||||
const descendantElement = (csstreeNode, xastNode) => {
|
||||
const result = [];
|
||||
if (xastNode.type === 'root') {
|
||||
for (const xastChild of xastNode.children) {
|
||||
result.push(...descendantElement(csstreeNode, xastChild));
|
||||
const collectDescendantElements = (startNode, descendants, parents) => {
|
||||
for (const childNode of startNode.children) {
|
||||
if (childNode.type === 'element') {
|
||||
parents.set(childNode, startNode);
|
||||
descendants.push(childNode);
|
||||
collectDescendantElements(childNode, descendants, parents);
|
||||
}
|
||||
}
|
||||
if (xastNode.type === 'element') {
|
||||
if (elementMatches(csstreeNode, xastNode)) {
|
||||
result.push(xastNode);
|
||||
}
|
||||
for (const xastChild of xastNode.children) {
|
||||
result.push(...descendantElement(csstreeNode, xastChild));
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {(
|
||||
* startNodes: Array<XastElement>,
|
||||
* children: Array<XastElement>,
|
||||
* parents: WeakMap<XastElement, XastParent>
|
||||
* ) => void}
|
||||
*/
|
||||
const collectChildrenElements = (startNodes, children, parents) => {
|
||||
for (const startNode of startNodes) {
|
||||
for (const child of startNode.children) {
|
||||
if (child.type === 'element') {
|
||||
parents.set(child, startNode);
|
||||
children.push(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {(
|
||||
* startNodes: Array<XastElement>,
|
||||
* siblings: Array<XastElement>,
|
||||
* parents: WeakMap<XastElement, XastParent>
|
||||
* ) => void}
|
||||
*/
|
||||
const collectAdjacentSiblings = (startNodes, siblings, parents) => {
|
||||
for (const startNode of startNodes) {
|
||||
const parentNode = parents.get(startNode);
|
||||
if (parentNode != null) {
|
||||
const allSiblings = parentNode.children;
|
||||
const index = allSiblings.indexOf(startNode);
|
||||
const adjacentSiblings = allSiblings.slice(index + 1, index + 2);
|
||||
for (const adjacentSibling of adjacentSiblings) {
|
||||
if (adjacentSibling.type === 'element') {
|
||||
parents.set(adjacentSibling, parentNode);
|
||||
siblings.push(adjacentSibling);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {(
|
||||
* startNodes: Array<XastElement>,
|
||||
* siblings: Array<XastElement>,
|
||||
* parents: WeakMap<XastElement, XastParent>
|
||||
* ) => void}
|
||||
*/
|
||||
const collectGeneralSiblings = (startNodes, siblings, parents) => {
|
||||
for (const startNode of startNodes) {
|
||||
const parentNode = parents.get(startNode);
|
||||
if (parentNode != null) {
|
||||
const allSiblings = parentNode.children;
|
||||
const index = allSiblings.indexOf(startNode);
|
||||
const generalSiblings = allSiblings.slice(index + 1);
|
||||
for (const generalSibling of generalSiblings) {
|
||||
if (generalSibling.type === 'element') {
|
||||
parents.set(generalSibling, parentNode);
|
||||
siblings.push(generalSibling);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {(csstreeNodes: Array<CsstreeNode>, xastNode:XastParent) => Array<XastElement>}
|
||||
*/
|
||||
const combination = (csstreeNodes, xastNode) => {
|
||||
/**
|
||||
* @type {Array<XastElement>}
|
||||
*/
|
||||
let candidateNodes = [];
|
||||
/**
|
||||
* @type {WeakMap<XastElement, XastParent>}
|
||||
*/
|
||||
let candidateParents = new WeakMap();
|
||||
collectDescendantElements(xastNode, candidateNodes, candidateParents);
|
||||
/**
|
||||
* @type {Array<XastElement>}
|
||||
*/
|
||||
let lastMatchedNodes = [];
|
||||
for (const csstreeChild of csstreeNodes) {
|
||||
if (csstreeChild.type === 'WhiteSpace') {
|
||||
for (const node of lastMatchedNodes) {
|
||||
candidateNodes = [];
|
||||
collectDescendantElements(node, candidateNodes, candidateParents);
|
||||
}
|
||||
} else if (csstreeChild.type === 'Combinator') {
|
||||
candidateNodes = [];
|
||||
if (csstreeChild.name === '>') {
|
||||
collectChildrenElements(
|
||||
lastMatchedNodes,
|
||||
candidateNodes,
|
||||
candidateParents
|
||||
);
|
||||
} else if (csstreeChild.name === '+') {
|
||||
collectAdjacentSiblings(
|
||||
lastMatchedNodes,
|
||||
candidateNodes,
|
||||
candidateParents
|
||||
);
|
||||
} else if (csstreeChild.name === '~') {
|
||||
collectGeneralSiblings(
|
||||
lastMatchedNodes,
|
||||
candidateNodes,
|
||||
candidateParents
|
||||
);
|
||||
} else {
|
||||
throw Error(`Unknown combinator ${csstreeChild.name}`);
|
||||
}
|
||||
} else {
|
||||
// actionn
|
||||
lastMatchedNodes = [];
|
||||
for (const candidateNode of candidateNodes) {
|
||||
if (elementMatches(csstreeChild, candidateNode)) {
|
||||
lastMatchedNodes.push(candidateNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return lastMatchedNodes;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -97,7 +217,7 @@ const any = (csstreeNode, xastNode) => {
|
||||
return result;
|
||||
}
|
||||
if (csstreeNode.type === 'Selector') {
|
||||
return descendantElement(csstreeNode.children[0], xastNode);
|
||||
return combination(csstreeNode.children, xastNode);
|
||||
}
|
||||
throw Error(`Unknown type ${csstreeNode.type}`);
|
||||
};
|
||||
@ -111,8 +231,6 @@ const select = (selector, node) => {
|
||||
);
|
||||
const match = any(parsedSelector, node);
|
||||
return match.length === 0 ? null : match[0];
|
||||
// const match = any(parse(selector), node, { one: true, any });
|
||||
// return match.length === 0 ? null : match[0];
|
||||
};
|
||||
|
||||
/**
|
||||
@ -124,12 +242,8 @@ const selectAll = (selector, node) => {
|
||||
);
|
||||
const match = any(parsedSelector, node);
|
||||
return match;
|
||||
// const match = any(parse(selector), node, { any });
|
||||
// return match;
|
||||
};
|
||||
|
||||
///
|
||||
|
||||
/**
|
||||
* @type {(node: XastParent, selector: string) => Array<XastElement>}
|
||||
*/
|
||||
@ -146,16 +260,12 @@ const querySelector = (node, selector) => {
|
||||
};
|
||||
exports.querySelector = querySelector;
|
||||
|
||||
const cssSelectOptions = {
|
||||
xmlMode: true,
|
||||
adapter: xastAdaptor,
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {(node: XastElement, selector: string) => boolean}
|
||||
* @type {(selector: string, node: XastElement, root: XastRoot) => boolean}
|
||||
*/
|
||||
const matches = (node, selector) => {
|
||||
return is(node, selector, cssSelectOptions);
|
||||
const matches = (selector, node, root) => {
|
||||
const match = selectAll(selector, root);
|
||||
return match.includes(node);
|
||||
};
|
||||
exports.matches = matches;
|
||||
|
||||
|
Reference in New Issue
Block a user