mirror of
https://github.com/svg/svgo.git
synced 2025-07-28 09:22:00 +03:00
chore: drop parentNode property (#2113)
This commit is contained in:
@ -107,11 +107,6 @@ export const parseSvg = (data, from) => {
|
||||
* @type {(node: XastChild) => void}
|
||||
*/
|
||||
const pushToContent = (node) => {
|
||||
// TODO remove legacy parentNode in v4
|
||||
Object.defineProperty(node, 'parentNode', {
|
||||
writable: true,
|
||||
value: current,
|
||||
});
|
||||
current.children.push(node);
|
||||
};
|
||||
|
||||
|
20
lib/style.js
20
lib/style.js
@ -10,15 +10,16 @@ import {
|
||||
|
||||
/**
|
||||
* @typedef {import('css-tree').Rule} CsstreeRule
|
||||
* @typedef {import('./types.js').ComputedStyles} ComputedStyles
|
||||
* @typedef {import('./types.js').Specificity} Specificity
|
||||
* @typedef {import('./types.js').Stylesheet} Stylesheet
|
||||
* @typedef {import('./types.js').StylesheetRule} StylesheetRule
|
||||
* @typedef {import('./types.js').StylesheetDeclaration} StylesheetDeclaration
|
||||
* @typedef {import('./types.js').ComputedStyles} ComputedStyles
|
||||
* @typedef {import('./types.js').XastRoot} XastRoot
|
||||
* @typedef {import('./types.js').XastElement} XastElement
|
||||
* @typedef {import('./types.js').XastParent} XastParent
|
||||
* @typedef {import('./types.js').StylesheetRule} StylesheetRule
|
||||
* @typedef {import('./types.js').XastChild} XastChild
|
||||
* @typedef {import('./types.js').XastElement} XastElement
|
||||
* @typedef {import('./types.js').XastNode} XastNode
|
||||
* @typedef {import('./types.js').XastParent} XastParent
|
||||
* @typedef {import('./types.js').XastRoot} XastRoot
|
||||
*/
|
||||
|
||||
const csstreeWalkSkip = csstree.walk.skip;
|
||||
@ -130,9 +131,10 @@ const parseStyleDeclarations = (css) => {
|
||||
/**
|
||||
* @param {Stylesheet} stylesheet
|
||||
* @param {XastElement} node
|
||||
* @param {Map<XastNode, XastParent>=} parents
|
||||
* @returns {ComputedStyles}
|
||||
*/
|
||||
const computeOwnStyle = (stylesheet, node) => {
|
||||
const computeOwnStyle = (stylesheet, node, parents = undefined) => {
|
||||
/** @type {ComputedStyles} */
|
||||
const computedStyle = {};
|
||||
const importantStyles = new Map();
|
||||
@ -147,7 +149,7 @@ const computeOwnStyle = (stylesheet, node) => {
|
||||
|
||||
// collect matching rules
|
||||
for (const { selector, declarations, dynamic } of stylesheet.rules) {
|
||||
if (matches(node, selector)) {
|
||||
if (matches(node, selector, parents)) {
|
||||
for (const { name, value, important } of declarations) {
|
||||
const computed = computedStyle[name];
|
||||
if (computed && computed.type === 'dynamic') {
|
||||
@ -259,10 +261,10 @@ export const collectStylesheet = (root) => {
|
||||
*/
|
||||
export const computeStyle = (stylesheet, node) => {
|
||||
const { parents } = stylesheet;
|
||||
const computedStyles = computeOwnStyle(stylesheet, node);
|
||||
const computedStyles = computeOwnStyle(stylesheet, node, parents);
|
||||
let parent = parents.get(node);
|
||||
while (parent != null && parent.type !== 'root') {
|
||||
const inheritedStyles = computeOwnStyle(stylesheet, parent);
|
||||
const inheritedStyles = computeOwnStyle(stylesheet, parent, parents);
|
||||
for (const [name, computed] of Object.entries(inheritedStyles)) {
|
||||
if (
|
||||
computedStyles[name] == null &&
|
||||
|
@ -11,9 +11,7 @@ import { parseSvg } from './parser.js';
|
||||
* @type {(node: XastParent, id: string) => XastElement}
|
||||
*/
|
||||
const getElementById = (node, id) => {
|
||||
/**
|
||||
* @type {?XastElement}
|
||||
*/
|
||||
/** @type {?XastElement} */
|
||||
let matched = null;
|
||||
visit(node, {
|
||||
element: {
|
||||
|
@ -5,6 +5,7 @@ import {
|
||||
VERSION,
|
||||
optimize as optimizeAgnostic,
|
||||
builtinPlugins,
|
||||
mapNodesToParents,
|
||||
querySelector,
|
||||
querySelectorAll,
|
||||
_collections,
|
||||
@ -113,6 +114,7 @@ export default {
|
||||
builtinPlugins,
|
||||
loadConfig,
|
||||
optimize,
|
||||
mapNodesToParents,
|
||||
querySelector,
|
||||
querySelectorAll,
|
||||
_collections,
|
||||
|
2
lib/svgo.d.ts
vendored
2
lib/svgo.d.ts
vendored
@ -162,4 +162,4 @@ export declare const VERSION: string;
|
||||
/** The core of SVGO */
|
||||
export declare function optimize(input: string, config?: Config): Output;
|
||||
|
||||
export { querySelector, querySelectorAll } from './xast.js';
|
||||
export { mapNodesToParents, querySelector, querySelectorAll } from './xast.js';
|
||||
|
@ -4,7 +4,7 @@ import { builtin } from './builtin.js';
|
||||
import { invokePlugins } from './svgo/plugins.js';
|
||||
import { encodeSVGDatauri } from './svgo/tools.js';
|
||||
import { VERSION } from './version.js';
|
||||
import { querySelector, querySelectorAll } from './xast.js';
|
||||
import { mapNodesToParents, querySelector, querySelectorAll } from './xast.js';
|
||||
import _collections from '../plugins/_collections.js';
|
||||
|
||||
/**
|
||||
@ -78,6 +78,7 @@ const resolvePluginConfig = (plugin) => {
|
||||
export {
|
||||
VERSION,
|
||||
builtin as builtinPlugins,
|
||||
mapNodesToParents,
|
||||
querySelector,
|
||||
querySelectorAll,
|
||||
_collections,
|
||||
@ -147,6 +148,7 @@ export default {
|
||||
VERSION,
|
||||
optimize,
|
||||
builtinPlugins: builtin,
|
||||
mapNodesToParents,
|
||||
querySelector,
|
||||
querySelectorAll,
|
||||
_collections,
|
||||
|
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @typedef {Required<import('css-select').Options<XastNode & { children?: any }, XastElement & { parentNode?: any }>>['adapter']} Adapter
|
||||
* @typedef {Required<import('css-select').Options<XastNode & { children?: any }, XastElement>>['adapter']} Adapter
|
||||
* @typedef {import('../types.js').XastChild} XastChild
|
||||
* @typedef {import('../types.js').XastElement} XastElement
|
||||
* @typedef {import('../types.js').XastNode} XastNode
|
||||
@ -33,20 +33,6 @@ const getName = (elemAst) => {
|
||||
return elemAst.name;
|
||||
};
|
||||
|
||||
/** @type {Adapter['getParent']} */
|
||||
const getParent = (node) => {
|
||||
return node.parentNode || null;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {any} elem
|
||||
* @returns {any}
|
||||
*/
|
||||
const getSiblings = (elem) => {
|
||||
const parent = getParent(elem);
|
||||
return parent ? getChildren(parent) : [];
|
||||
};
|
||||
|
||||
/** @type {Adapter['getText']} */
|
||||
const getText = (node) => {
|
||||
if (node.children[0].type === 'text' || node.children[0].type === 'cdata') {
|
||||
@ -60,38 +46,6 @@ const hasAttrib = (elem, name) => {
|
||||
return elem.attributes[name] !== undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {any} nodes
|
||||
* @returns {any}
|
||||
*/
|
||||
const removeSubsets = (nodes) => {
|
||||
let idx = nodes.length;
|
||||
let node;
|
||||
let ancestor;
|
||||
let replace;
|
||||
// Check if each node (or one of its ancestors) is already contained in the
|
||||
// array.
|
||||
while (--idx > -1) {
|
||||
node = ancestor = nodes[idx];
|
||||
// Temporarily remove the node under consideration
|
||||
nodes[idx] = null;
|
||||
replace = true;
|
||||
while (ancestor) {
|
||||
if (nodes.includes(ancestor)) {
|
||||
replace = false;
|
||||
nodes.splice(idx, 1);
|
||||
break;
|
||||
}
|
||||
ancestor = getParent(ancestor);
|
||||
}
|
||||
// If the node has been found to be unique, re-insert it.
|
||||
if (replace) {
|
||||
nodes[idx] = node;
|
||||
}
|
||||
}
|
||||
return nodes;
|
||||
};
|
||||
|
||||
/** @type {Adapter['findAll']} */
|
||||
const findAll = (test, elems) => {
|
||||
const result = [];
|
||||
@ -123,19 +77,68 @@ const findOne = (test, elems) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* @type {Adapter}
|
||||
* @param {Map<XastNode, XastParent>} parents
|
||||
* @returns {Adapter}
|
||||
*/
|
||||
export default {
|
||||
isTag,
|
||||
existsOne,
|
||||
getAttributeValue,
|
||||
getChildren,
|
||||
getName,
|
||||
getParent,
|
||||
getSiblings,
|
||||
getText,
|
||||
hasAttrib,
|
||||
removeSubsets,
|
||||
findAll,
|
||||
findOne,
|
||||
};
|
||||
export function createAdapter(parents) {
|
||||
/** @type {Adapter['getParent']} */
|
||||
const getParent = (node) => {
|
||||
return parents.get(node) || null;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {any} elem
|
||||
* @returns {any}
|
||||
*/
|
||||
const getSiblings = (elem) => {
|
||||
const parent = getParent(elem);
|
||||
return parent ? getChildren(parent) : [];
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {any} nodes
|
||||
* @returns {any}
|
||||
*/
|
||||
const removeSubsets = (nodes) => {
|
||||
let idx = nodes.length;
|
||||
let node;
|
||||
let ancestor;
|
||||
let replace;
|
||||
// Check if each node (or one of its ancestors) is already contained in the
|
||||
// array.
|
||||
while (--idx > -1) {
|
||||
node = ancestor = nodes[idx];
|
||||
// Temporarily remove the node under consideration
|
||||
nodes[idx] = null;
|
||||
replace = true;
|
||||
while (ancestor) {
|
||||
if (nodes.includes(ancestor)) {
|
||||
replace = false;
|
||||
nodes.splice(idx, 1);
|
||||
break;
|
||||
}
|
||||
ancestor = getParent(ancestor);
|
||||
}
|
||||
// If the node has been found to be unique, re-insert it.
|
||||
if (replace) {
|
||||
nodes[idx] = node;
|
||||
}
|
||||
}
|
||||
return nodes;
|
||||
};
|
||||
|
||||
return {
|
||||
isTag,
|
||||
existsOne,
|
||||
getAttributeValue,
|
||||
getChildren,
|
||||
getName,
|
||||
getParent,
|
||||
getSiblings,
|
||||
getText,
|
||||
hasAttrib,
|
||||
removeSubsets,
|
||||
findAll,
|
||||
findOne,
|
||||
};
|
||||
}
|
||||
|
76
lib/xast.js
76
lib/xast.js
@ -1,8 +1,8 @@
|
||||
import { selectAll, selectOne, is } from 'css-select';
|
||||
import adapter from './svgo/css-select-adapter.js';
|
||||
import { createAdapter } from './svgo/css-select-adapter.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('css-select').Options<XastNode & { children?: any }, XastElement & { parentNode?: any }>} Options
|
||||
* @typedef {import('css-select').Options<XastNode & { children?: any }, XastElement>} Options
|
||||
* @typedef {import('./types.js').XastElement} XastElement
|
||||
* @typedef {import('./types.js').XastNode} XastNode
|
||||
* @typedef {import('./types.js').XastChild} XastChild
|
||||
@ -10,37 +10,81 @@ import adapter from './svgo/css-select-adapter.js';
|
||||
* @typedef {import('./types.js').Visitor} Visitor
|
||||
*/
|
||||
|
||||
/** @type {Options} */
|
||||
const cssSelectOptions = {
|
||||
xmlMode: true,
|
||||
adapter,
|
||||
};
|
||||
/**
|
||||
* @param {Map<XastNode, XastParent>} parents
|
||||
* @returns {Options}
|
||||
*/
|
||||
function createCssSelectOptions(parents) {
|
||||
return {
|
||||
xmlMode: true,
|
||||
adapter: createAdapter(parents),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {XastNode} node Element to query the children of.
|
||||
* Maps all nodes to their parent node recursively.
|
||||
*
|
||||
* @param {XastParent} node
|
||||
* @returns {Map<XastNode, XastParent>}
|
||||
*/
|
||||
export function mapNodesToParents(node) {
|
||||
/** @type {Map<XastNode, XastParent>} */
|
||||
const parents = new Map();
|
||||
|
||||
for (const child of node.children) {
|
||||
parents.set(child, node);
|
||||
visit(
|
||||
child,
|
||||
{
|
||||
element: {
|
||||
enter: (child, parent) => {
|
||||
parents.set(child, parent);
|
||||
},
|
||||
},
|
||||
},
|
||||
node,
|
||||
);
|
||||
}
|
||||
|
||||
return parents;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {XastParent} node Element to query the children of.
|
||||
* @param {string} selector CSS selector string.
|
||||
* @param {Map<XastNode, XastParent>=} parents
|
||||
* @returns {XastChild[]} All matching elements.
|
||||
*/
|
||||
export const querySelectorAll = (node, selector) => {
|
||||
return selectAll(selector, node, cssSelectOptions);
|
||||
export const querySelectorAll = (
|
||||
node,
|
||||
selector,
|
||||
parents = mapNodesToParents(node),
|
||||
) => {
|
||||
return selectAll(selector, node, createCssSelectOptions(parents));
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {XastNode} node Element to query the children of.
|
||||
* @param {XastParent} node Element to query the children of.
|
||||
* @param {string} selector CSS selector string.
|
||||
* @param {Map<XastNode, XastParent>=} parents
|
||||
* @returns {?XastChild} First match, or null if there was no match.
|
||||
*/
|
||||
export const querySelector = (node, selector) => {
|
||||
return selectOne(selector, node, cssSelectOptions);
|
||||
export const querySelector = (
|
||||
node,
|
||||
selector,
|
||||
parents = mapNodesToParents(node),
|
||||
) => {
|
||||
return selectOne(selector, node, createCssSelectOptions(parents));
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {XastElement} node
|
||||
* @param {string} selector
|
||||
* @param {Map<XastNode, XastParent>=} parents
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const matches = (node, selector) => {
|
||||
return is(node, selector, cssSelectOptions);
|
||||
export const matches = (node, selector, parents = mapNodesToParents(node)) => {
|
||||
return is(node, selector, createCssSelectOptions(parents));
|
||||
};
|
||||
|
||||
export const visitSkip = Symbol();
|
||||
@ -48,7 +92,7 @@ export const visitSkip = Symbol();
|
||||
/**
|
||||
* @param {XastNode} node
|
||||
* @param {Visitor} visitor
|
||||
* @param {?any} parentNode
|
||||
* @param {any=} parentNode
|
||||
*/
|
||||
export const visit = (node, visitor, parentNode = undefined) => {
|
||||
const callbacks = visitor[node.type];
|
||||
|
@ -129,13 +129,6 @@ export const fn = (root) => {
|
||||
// replace current node with all its children
|
||||
const index = parentNode.children.indexOf(node);
|
||||
parentNode.children.splice(index, 1, ...node.children);
|
||||
// TODO remove legacy parentNode in v4
|
||||
for (const child of node.children) {
|
||||
Object.defineProperty(child, 'parentNode', {
|
||||
writable: true,
|
||||
value: parentNode,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
|
@ -83,11 +83,6 @@ export const fn = () => {
|
||||
* @type {XastChild}
|
||||
*/
|
||||
const child = { type: styleContentType, value: collectedStyles };
|
||||
// TODO remove legacy parentNode in v4
|
||||
Object.defineProperty(child, 'parentNode', {
|
||||
writable: true,
|
||||
value: firstStyleElement,
|
||||
});
|
||||
firstStyleElement.children = [child];
|
||||
}
|
||||
},
|
||||
|
@ -55,14 +55,6 @@ export const fn = () => {
|
||||
(child) => child.type !== 'text',
|
||||
);
|
||||
parentNode.children.splice(index, 1, ...usefulChildren);
|
||||
|
||||
// TODO remove legacy parentNode in v4
|
||||
for (const child of node.children) {
|
||||
Object.defineProperty(child, 'parentNode', {
|
||||
writable: true,
|
||||
value: parentNode,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -32,13 +32,6 @@ export const fn = () => {
|
||||
if (usefulNodes.length === 0) {
|
||||
detachNodeFromParent(node, parentNode);
|
||||
}
|
||||
// TODO remove legacy parentNode in v4
|
||||
for (const usefulNode of usefulNodes) {
|
||||
Object.defineProperty(usefulNode, 'parentNode', {
|
||||
writable: true,
|
||||
value: node,
|
||||
});
|
||||
}
|
||||
node.children = usefulNodes;
|
||||
}
|
||||
},
|
||||
|
@ -92,11 +92,6 @@ export const fn = (root) => {
|
||||
attributes: {},
|
||||
children: [],
|
||||
};
|
||||
// TODO remove legacy parentNode in v4
|
||||
Object.defineProperty(defsTag, 'parentNode', {
|
||||
writable: true,
|
||||
value: node,
|
||||
});
|
||||
}
|
||||
|
||||
let index = 0;
|
||||
@ -129,11 +124,6 @@ export const fn = (root) => {
|
||||
reusablePath.attributes.id = originalId;
|
||||
delete list[0].attributes.id;
|
||||
}
|
||||
// TODO remove legacy parentNode in v4
|
||||
Object.defineProperty(reusablePath, 'parentNode', {
|
||||
writable: true,
|
||||
value: defsTag,
|
||||
});
|
||||
defsTag.children.push(reusablePath);
|
||||
// convert paths to <use>
|
||||
for (const pathNode of list) {
|
||||
|
Reference in New Issue
Block a user