diff --git a/lib/svgo/plugins.js b/lib/svgo/plugins.js index 4e1926cd..ed418134 100644 --- a/lib/svgo/plugins.js +++ b/lib/svgo/plugins.js @@ -1,5 +1,7 @@ 'use strict'; +const { visit } = require('../xast.js'); + /** * Plugins engine. * @@ -34,6 +36,14 @@ module.exports = function (data, info, plugins) { case 'full': data = full(data, info, group); break; + case 'visitor': + for (const plugin of group) { + if (plugin.active) { + const visitor = plugin.fn(data, plugin.params, info); + visit(data, visitor); + } + } + break; } } return data; diff --git a/lib/xast.js b/lib/xast.js index 8a8118bc..9800e2ac 100644 --- a/lib/xast.js +++ b/lib/xast.js @@ -51,3 +51,36 @@ const traverse = (node, fn) => { } }; exports.traverse = traverse; + +const visit = (node, visitor) => { + const callbacks = visitor[node.type]; + if (callbacks && callbacks.enter) { + callbacks.enter(node); + } + // visit root children + if (node.type === 'root') { + // copy children array to not loose cursor when children is spliced + for (const child of node.children) { + visit(child, visitor); + } + } + // visit element children if still attached to parent + if (node.type === 'element') { + if (node.parentNode.children.includes(node)) { + for (const child of node.children) { + visit(child, visitor); + } + } + } + if (callbacks && callbacks.exit) { + callbacks.exit(node); + } +}; +exports.visit = visit; + +const detachNodeFromParent = (node) => { + const parentNode = node.parentNode; + // avoid splice to not break for loops + parentNode.children = parentNode.children.filter((child) => child !== node); +}; +exports.detachNodeFromParent = detachNodeFromParent; diff --git a/lib/xast.test.js b/lib/xast.test.js new file mode 100644 index 00000000..0008b936 --- /dev/null +++ b/lib/xast.test.js @@ -0,0 +1,108 @@ +'use strict'; + +const { expect } = require('chai'); +const { visit, detachNodeFromParent } = require('./xast.js'); + +const getAst = () => { + const ast = { + type: 'root', + children: [ + { + type: 'element', + name: 'g', + attributes: {}, + children: [ + { + type: 'element', + name: 'rect', + attributes: {}, + children: [], + }, + { + type: 'element', + name: 'circle', + attributes: {}, + children: [], + }, + ], + }, + { + type: 'element', + name: 'ellipse', + attributes: {}, + children: [], + }, + ], + }; + ast.children[0].parentNode = ast; + ast.children[0].children[0].parentNode = ast.children[0]; + ast.children[0].children[1].parentNode = ast.children[0]; + ast.children[1].parentNode = ast; + return ast; +}; + +describe('xast', () => { + it('enter into nodes', () => { + const root = getAst(); + const entered = []; + visit(root, { + root: { + enter: (node) => { + entered.push(node.type); + }, + }, + element: { + enter: (node) => { + entered.push(`${node.type}:${node.name}`); + }, + }, + }); + expect(entered).to.deep.equal([ + 'root', + 'element:g', + 'element:rect', + 'element:circle', + 'element:ellipse', + ]); + }); + + it('exit from nodes', () => { + const root = getAst(); + const exited = []; + visit(root, { + root: { + exit: (node) => { + exited.push(node.type); + }, + }, + element: { + exit: (node) => { + exited.push(`${node.type}:${node.name}`); + }, + }, + }); + expect(exited).to.deep.equal([ + 'element:rect', + 'element:circle', + 'element:g', + 'element:ellipse', + 'root', + ]); + }); + + it('skip entering children if node is detached', () => { + const root = getAst(); + const entered = []; + visit(root, { + element: { + enter: (node) => { + entered.push(node.name); + if (node.name === 'g') { + detachNodeFromParent(node); + } + }, + }, + }); + expect(entered).to.deep.equal(['g', 'ellipse']); + }); +}); diff --git a/plugins/mergeStyles.js b/plugins/mergeStyles.js index 48f2ba1d..c57a5029 100644 --- a/plugins/mergeStyles.js +++ b/plugins/mergeStyles.js @@ -1,87 +1,85 @@ 'use strict'; -const { querySelectorAll, closestByName } = require('../lib/xast.js'); -const { getCssStr, setCssStr } = require('../lib/css-tools'); +const { closestByName, detachNodeFromParent } = require('../lib/xast.js'); +const JSAPI = require('../lib/svgo/jsAPI.js'); -exports.type = 'full'; +exports.type = 'visitor'; exports.active = true; exports.description = 'merge multiple style elements into one'; /** * Merge multiple style elements into one. * - * @param {Object} document document element - * * @author strarsis */ -exports.fn = function (document) { - // collect + + + +@@@ + + + + diff --git a/tsconfig.json b/tsconfig.json index 1e39c1a8..254f0254 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -36,6 +36,7 @@ "plugins/convertStyleToAttrs.js", "plugins/convertTransform.js", "plugins/mergePaths.js", + "plugins/mergeStyles.js", "plugins/moveElemsAttrsToGroup.js", "plugins/moveGroupAttrsToElems.js", "plugins/plugins.js",