1
0
mirror of https://github.com/svg/svgo.git synced 2025-07-29 20:21:14 +03:00

Pass parent node to visitor (#1517)

Wrapping each node with JSAPI class and passing parent to it is not
reliable. Parent may be changed but the reference will stay.

Here I wasn't able to detach comment from parent node for some reason.
Explicit parent node inferred while ast traverse is easier to debug and
work with. Eventually we will not need to wrap each node with JSAPI class.
This commit is contained in:
Bogdan Chadkin
2021-08-12 13:05:09 +03:00
committed by GitHub
parent 2c0c361074
commit 35b7356ff0
6 changed files with 43 additions and 41 deletions

View File

@ -52,34 +52,33 @@ const traverse = (node, fn) => {
}; };
exports.traverse = traverse; exports.traverse = traverse;
const visit = (node, visitor) => { const visit = (node, visitor, parentNode = null) => {
const callbacks = visitor[node.type]; const callbacks = visitor[node.type];
if (callbacks && callbacks.enter) { if (callbacks && callbacks.enter) {
callbacks.enter(node); callbacks.enter(node, parentNode);
} }
// visit root children // visit root children
if (node.type === 'root') { if (node.type === 'root') {
// copy children array to not loose cursor when children is spliced // copy children array to not loose cursor when children is spliced
for (const child of node.children) { for (const child of node.children) {
visit(child, visitor); visit(child, visitor, node);
} }
} }
// visit element children if still attached to parent // visit element children if still attached to parent
if (node.type === 'element') { if (node.type === 'element') {
if (node.parentNode.children.includes(node)) { if (parentNode.children.includes(node)) {
for (const child of node.children) { for (const child of node.children) {
visit(child, visitor); visit(child, visitor, node);
} }
} }
} }
if (callbacks && callbacks.exit) { if (callbacks && callbacks.exit) {
callbacks.exit(node); callbacks.exit(node, parentNode);
} }
}; };
exports.visit = visit; exports.visit = visit;
const detachNodeFromParent = (node) => { const detachNodeFromParent = (node, parentNode) => {
const parentNode = node.parentNode;
// avoid splice to not break for loops // avoid splice to not break for loops
parentNode.children = parentNode.children.filter((child) => child !== node); parentNode.children = parentNode.children.filter((child) => child !== node);
}; };

View File

@ -95,10 +95,10 @@ describe('xast', () => {
const entered = []; const entered = [];
visit(root, { visit(root, {
element: { element: {
enter: (node) => { enter: (node, parentNode) => {
entered.push(node.name); entered.push(node.name);
if (node.name === 'g') { if (node.name === 'g') {
detachNodeFromParent(node); detachNodeFromParent(node, parentNode);
} }
}, },
}, },

View File

@ -88,7 +88,7 @@ exports.fn = (root, params) => {
floatPrecision, floatPrecision,
noSpaceAfterFlags, noSpaceAfterFlags,
}); });
detachNodeFromParent(child); detachNodeFromParent(child, node);
continue; continue;
} }

View File

@ -17,7 +17,7 @@ exports.fn = () => {
let collectedStyles = ''; let collectedStyles = '';
let styleContentType = 'text'; let styleContentType = 'text';
const enterElement = (node) => { const enterElement = (node, parentNode) => {
// collect style elements // collect style elements
if (node.name !== 'style') { if (node.name !== 'style') {
return; return;
@ -51,7 +51,7 @@ exports.fn = () => {
// remove empty style elements // remove empty style elements
if (css.trim().length === 0) { if (css.trim().length === 0) {
detachNodeFromParent(node); detachNodeFromParent(node, parentNode);
return; return;
} }
@ -67,7 +67,7 @@ exports.fn = () => {
if (firstStyleElement == null) { if (firstStyleElement == null) {
firstStyleElement = node; firstStyleElement = node;
} else { } else {
detachNodeFromParent(node); detachNodeFromParent(node, parentNode);
firstStyleElement.children = [ firstStyleElement.children = [
new JSAPI( new JSAPI(
{ type: styleContentType, value: collectedStyles }, { type: styleContentType, value: collectedStyles },

View File

@ -1,9 +1,9 @@
'use strict'; 'use strict';
exports.type = 'perItem'; const { detachNodeFromParent } = require('../lib/xast.js');
exports.type = 'visitor';
exports.active = true; exports.active = true;
exports.description = 'removes comments'; exports.description = 'removes comments';
/** /**
@ -13,13 +13,16 @@ exports.description = 'removes comments';
* <!-- Generator: Adobe Illustrator 15.0.0, SVG Export * <!-- Generator: Adobe Illustrator 15.0.0, SVG Export
* Plug-In . SVG Version: 6.00 Build 0) --> * Plug-In . SVG Version: 6.00 Build 0) -->
* *
* @param {Object} item current iteration item
* @return {Boolean} if false, item will be filtered out
*
* @author Kir Belevich * @author Kir Belevich
*/ */
exports.fn = function (item) { exports.fn = () => {
if (item.type === 'comment' && item.value.charAt(0) !== '!') { return {
return false; comment: {
} enter: (node, parentNode) => {
if (node.value.charAt(0) !== '!') {
detachNodeFromParent(node, parentNode);
}
},
},
};
}; };

View File

@ -53,7 +53,7 @@ exports.fn = (root, params) => {
return { return {
element: { element: {
enter: (node) => { enter: (node, parentNode) => {
// Removes hidden elements // Removes hidden elements
// https://www.w3schools.com/cssref/pr_class_visibility.asp // https://www.w3schools.com/cssref/pr_class_visibility.asp
const computedStyle = computeStyle(stylesheet, node); const computedStyle = computeStyle(stylesheet, node);
@ -65,7 +65,7 @@ exports.fn = (root, params) => {
// keep if any descendant enables visibility // keep if any descendant enables visibility
querySelector(node, '[visibility=visible]') == null querySelector(node, '[visibility=visible]') == null
) { ) {
detachNodeFromParent(node); detachNodeFromParent(node, parentNode);
return; return;
} }
@ -82,7 +82,7 @@ exports.fn = (root, params) => {
// markers with display: none still rendered // markers with display: none still rendered
node.name !== 'marker' node.name !== 'marker'
) { ) {
detachNodeFromParent(node); detachNodeFromParent(node, parentNode);
return; return;
} }
@ -97,7 +97,7 @@ exports.fn = (root, params) => {
// transparent element inside clipPath still affect clipped elements // transparent element inside clipPath still affect clipped elements
closestByName(node, 'clipPath') == null closestByName(node, 'clipPath') == null
) { ) {
detachNodeFromParent(node); detachNodeFromParent(node, parentNode);
return; return;
} }
@ -113,7 +113,7 @@ exports.fn = (root, params) => {
node.children.length === 0 && node.children.length === 0 &&
node.attributes.r === '0' node.attributes.r === '0'
) { ) {
detachNodeFromParent(node); detachNodeFromParent(node, parentNode);
return; return;
} }
@ -129,7 +129,7 @@ exports.fn = (root, params) => {
node.children.length === 0 && node.children.length === 0 &&
node.attributes.rx === '0' node.attributes.rx === '0'
) { ) {
detachNodeFromParent(node); detachNodeFromParent(node, parentNode);
return; return;
} }
@ -145,7 +145,7 @@ exports.fn = (root, params) => {
node.children.length === 0 && node.children.length === 0 &&
node.attributes.ry === '0' node.attributes.ry === '0'
) { ) {
detachNodeFromParent(node); detachNodeFromParent(node, parentNode);
return; return;
} }
@ -161,7 +161,7 @@ exports.fn = (root, params) => {
node.children.length === 0 && node.children.length === 0 &&
node.attributes.width === '0' node.attributes.width === '0'
) { ) {
detachNodeFromParent(node); detachNodeFromParent(node, parentNode);
return; return;
} }
@ -178,7 +178,7 @@ exports.fn = (root, params) => {
node.children.length === 0 && node.children.length === 0 &&
node.attributes.height === '0' node.attributes.height === '0'
) { ) {
detachNodeFromParent(node); detachNodeFromParent(node, parentNode);
return; return;
} }
@ -193,7 +193,7 @@ exports.fn = (root, params) => {
node.name === 'pattern' && node.name === 'pattern' &&
node.attributes.width === '0' node.attributes.width === '0'
) { ) {
detachNodeFromParent(node); detachNodeFromParent(node, parentNode);
return; return;
} }
@ -208,7 +208,7 @@ exports.fn = (root, params) => {
node.name === 'pattern' && node.name === 'pattern' &&
node.attributes.height === '0' node.attributes.height === '0'
) { ) {
detachNodeFromParent(node); detachNodeFromParent(node, parentNode);
return; return;
} }
@ -223,7 +223,7 @@ exports.fn = (root, params) => {
node.name === 'image' && node.name === 'image' &&
node.attributes.width === '0' node.attributes.width === '0'
) { ) {
detachNodeFromParent(node); detachNodeFromParent(node, parentNode);
return; return;
} }
@ -238,7 +238,7 @@ exports.fn = (root, params) => {
node.name === 'image' && node.name === 'image' &&
node.attributes.height === '0' node.attributes.height === '0'
) { ) {
detachNodeFromParent(node); detachNodeFromParent(node, parentNode);
return; return;
} }
@ -249,12 +249,12 @@ exports.fn = (root, params) => {
// <path d=""/> // <path d=""/>
if (pathEmptyD && node.name === 'path') { if (pathEmptyD && node.name === 'path') {
if (node.attributes.d == null) { if (node.attributes.d == null) {
detachNodeFromParent(node); detachNodeFromParent(node, parentNode);
return; return;
} }
const pathData = parsePathData(node.attributes.d); const pathData = parsePathData(node.attributes.d);
if (pathData.length === 0) { if (pathData.length === 0) {
detachNodeFromParent(node); detachNodeFromParent(node, parentNode);
return; return;
} }
// keep single point paths for markers // keep single point paths for markers
@ -263,7 +263,7 @@ exports.fn = (root, params) => {
computedStyle['marker-start'] == null && computedStyle['marker-start'] == null &&
computedStyle['marker-end'] == null computedStyle['marker-end'] == null
) { ) {
detachNodeFromParent(node); detachNodeFromParent(node, parentNode);
return; return;
} }
return; return;
@ -279,7 +279,7 @@ exports.fn = (root, params) => {
node.name === 'polyline' && node.name === 'polyline' &&
node.attributes.points == null node.attributes.points == null
) { ) {
detachNodeFromParent(node); detachNodeFromParent(node, parentNode);
return; return;
} }
@ -293,7 +293,7 @@ exports.fn = (root, params) => {
node.name === 'polygon' && node.name === 'polygon' &&
node.attributes.points == null node.attributes.points == null
) { ) {
detachNodeFromParent(node); detachNodeFromParent(node, parentNode);
return; return;
} }
}, },