1
0
mirror of https://github.com/svg/svgo.git synced 2025-07-31 07:44:22 +03:00

Refactor adhoc plugins with visitor api (#1526)

- addAttributesToSVGElement
- addClassesToSVGElement
- removeAttributesBySelector
- removeAttrs
- removeElementsByAttr
This commit is contained in:
Bogdan Chadkin
2021-08-14 16:48:39 +03:00
committed by GitHub
parent e6b441aca0
commit 7ec255719c
5 changed files with 217 additions and 202 deletions

View File

@ -1,13 +1,8 @@
'use strict';
const { closestByName } = require('../lib/xast.js');
exports.name = 'addAttributesToSVGElement';
exports.type = 'perItem';
exports.type = 'visitor';
exports.active = false;
exports.description = 'adds attributes to an outer <svg> element';
var ENOCLS = `Error in plugin "addAttributesToSVGElement": absent parameters.
@ -54,19 +49,16 @@ plugins: [
*
* @author April Arcus
*/
exports.fn = (node, params) => {
if (
node.type === 'element' &&
node.name === 'svg' &&
closestByName(node.parentNode, 'svg') == null
) {
if (!params || !(Array.isArray(params.attributes) || params.attribute)) {
exports.fn = (root, params) => {
if (!Array.isArray(params.attributes) && !params.attribute) {
console.error(ENOCLS);
return;
}
const attributes = params.attributes || [params.attribute];
return {
element: {
enter: (node, parentNode) => {
if (node.name === 'svg' && parentNode.type === 'root') {
for (const attribute of attributes) {
if (typeof attribute === 'string') {
if (node.attributes[attribute] == null) {
@ -82,4 +74,7 @@ exports.fn = (node, params) => {
}
}
}
},
},
};
};

View File

@ -1,57 +1,72 @@
'use strict';
exports.name = 'addClassesToSVGElement';
exports.type = 'full';
exports.type = 'visitor';
exports.active = false;
exports.description = 'adds classnames to an outer <svg> element';
var ENOCLS = `Error in plugin "addClassesToSVGElement": absent parameters.
It should have a list of classes in "classNames" or one "className".
Config example:
plugins:
- addClassesToSVGElement:
plugins: [
{
name: "addClassesToSVGElement",
params: {
className: "mySvg"
}
}
]
plugins:
- addClassesToSVGElement:
plugins: [
{
name: "addClassesToSVGElement",
params: {
classNames: ["mySvg", "size-big"]
}
}
]
`;
/**
* Add classnames to an outer <svg> element. Example config:
*
* plugins:
* - addClassesToSVGElement:
* className: 'mySvg'
* plugins: [
* {
* name: "addClassesToSVGElement",
* params: {
* className: "mySvg"
* }
* }
* ]
*
* plugins:
* - addClassesToSVGElement:
* classNames: ['mySvg', 'size-big']
* plugins: [
* {
* name: "addClassesToSVGElement",
* params: {
* classNames: ["mySvg", "size-big"]
* }
* }
* ]
*
* @author April Arcus
*/
exports.fn = function (data, params) {
exports.fn = (root, params) => {
if (
!params ||
!(
(Array.isArray(params.classNames) && params.classNames.some(String)) ||
params.className
)
!(Array.isArray(params.classNames) && params.classNames.some(String)) &&
!params.className
) {
console.error(ENOCLS);
return data;
return;
}
var classNames = params.classNames || [params.className],
svg = data.children[0];
if (svg.isElem('svg')) {
svg.class.add.apply(svg.class, classNames);
const classNames = params.classNames || [params.className];
return {
element: {
enter: (node, parentNode) => {
if (node.name === 'svg' && parentNode.type === 'root') {
node.class.add(...classNames);
}
return data;
},
},
};
};

View File

@ -1,55 +1,71 @@
'use strict';
const { querySelectorAll } = require('../lib/xast.js');
exports.name = 'removeAttributesBySelector';
exports.type = 'perItem';
exports.type = 'visitor';
exports.active = false;
exports.description =
'removes attributes of elements that match a css selector';
/**
* Removes attributes of elements that match a css selector.
*
* @param {Object} item current iteration item
* @param {Object} params plugin params
* @return {Boolean} if false, item will be filtered out
*
* @example
* <caption>A selector removing a single attribute</caption>
* plugins:
* - removeAttributesBySelector:
* plugins: [
* {
* name: "removeAttributesBySelector",
* params: {
* selector: "[fill='#00ff00']"
* attributes: "fill"
* }
* }
* ]
*
* <rect x="0" y="0" width="100" height="100" fill="#00ff00" stroke="#00ff00"/>
* ↓
* <rect x="0" y="0" width="100" height="100" stroke="#00ff00"/>
*
* <caption>A selector removing multiple attributes</caption>
* plugins:
* - removeAttributesBySelector:
* selector: "[fill='#00ff00']"
* attributes:
* - fill
* - stroke
* plugins: [
* {
* name: "removeAttributesBySelector",
* params: {
* selector: "[fill='#00ff00']",
* attributes: [
* "fill",
* "stroke"
* ]
* }
* }
* ]
*
* <rect x="0" y="0" width="100" height="100" fill="#00ff00" stroke="#00ff00"/>
* ↓
* <rect x="0" y="0" width="100" height="100"/>
*
* <caption>Multiple selectors removing attributes</caption>
* plugins:
* - removeAttributesBySelector:
* selectors:
* - selector: "[fill='#00ff00']"
* plugins: [
* {
* name: "removeAttributesBySelector",
* params: {
* selectors: [
* {
* selector: "[fill='#00ff00']",
* attributes: "fill"
*
* - selector: "#remove"
* attributes:
* - stroke
* - id
* },
* {
* selector: "#remove",
* attributes: [
* "stroke",
* "id"
* ]
* }
* ]
* }
* }
* ]
*
* <rect x="0" y="0" width="100" height="100" fill="#00ff00" stroke="#00ff00"/>
* ↓
@ -59,18 +75,21 @@ exports.description =
*
* @author Bradley Mease
*/
exports.fn = function (item, params) {
var selectors = Array.isArray(params.selectors) ? params.selectors : [params];
selectors.map(({ selector, attributes }) => {
if (item.matches(selector)) {
exports.fn = (root, params) => {
const selectors = Array.isArray(params.selectors)
? params.selectors
: [params];
for (const { selector, attributes } of selectors) {
const nodes = querySelectorAll(root, selector);
for (const node of nodes) {
if (Array.isArray(attributes)) {
for (const name of attributes) {
delete item.attributes[name];
delete node.attributes[name];
}
} else {
delete item.attributes[attributes];
delete node.attributes[attributes];
}
}
});
}
return {};
};

View File

@ -1,20 +1,11 @@
'use strict';
var DEFAULT_SEPARATOR = ':';
exports.name = 'removeAttrs';
exports.type = 'perItem';
exports.type = 'visitor';
exports.active = false;
exports.description = 'removes specified attributes';
exports.params = {
elemSeparator: DEFAULT_SEPARATOR,
preserveCurrentColor: false,
attrs: [],
};
const DEFAULT_SEPARATOR = ':';
/**
* Remove attributes
@ -77,72 +68,69 @@ exports.params = {
* attrs: 'stroke.*'
*
*
* @param {Object} item current iteration item
* @param {Object} params plugin params
* @return {Boolean} if false, item will be filtered out
*
* @author Benny Schudel
*/
exports.fn = function (item, params) {
exports.fn = (root, params) => {
// wrap into an array if params is not
if (!Array.isArray(params.attrs)) {
params.attrs = [params.attrs];
}
if (item.type === 'element') {
var elemSeparator =
const elemSeparator =
typeof params.elemSeparator == 'string'
? params.elemSeparator
: DEFAULT_SEPARATOR;
var preserveCurrentColor =
const preserveCurrentColor =
typeof params.preserveCurrentColor == 'boolean'
? params.preserveCurrentColor
: false;
const attrs = Array.isArray(params.attrs) ? params.attrs : [params.attrs];
// prepare patterns
var patterns = params.attrs.map(function (pattern) {
return {
element: {
enter: (node) => {
for (let pattern of attrs) {
// if no element separators (:), assume it's attribute name, and apply to all elements *regardless of value*
if (pattern.indexOf(elemSeparator) === -1) {
pattern = ['.*', elemSeparator, pattern, elemSeparator, '.*'].join('');
if (pattern.includes(elemSeparator) === false) {
pattern = ['.*', elemSeparator, pattern, elemSeparator, '.*'].join(
''
);
// if only 1 separator, assume it's element and attribute name, and apply regardless of attribute value
} else if (pattern.split(elemSeparator).length < 3) {
pattern = [pattern, elemSeparator, '.*'].join('');
}
// create regexps for element, attribute name, and attribute value
return pattern.split(elemSeparator).map(function (value) {
const list = pattern.split(elemSeparator).map((value) => {
// adjust single * to match anything
if (value === '*') {
value = '.*';
}
return new RegExp(['^', value, '$'].join(''), 'i');
});
});
// loop patterns
patterns.forEach(function (pattern) {
// matches element
if (pattern[0].test(item.name)) {
if (list[0].test(node.name)) {
// loop attributes
for (const [name, value] of Object.entries(item.attributes)) {
var isFillCurrentColor =
preserveCurrentColor && name == 'fill' && value == 'currentColor';
var isStrokeCurrentColor =
preserveCurrentColor && name == 'stroke' && value == 'currentColor';
if (!(isFillCurrentColor || isStrokeCurrentColor)) {
for (const [name, value] of Object.entries(node.attributes)) {
const isFillCurrentColor =
preserveCurrentColor &&
name == 'fill' &&
value == 'currentColor';
const isStrokeCurrentColor =
preserveCurrentColor &&
name == 'stroke' &&
value == 'currentColor';
if (
!isFillCurrentColor &&
!isStrokeCurrentColor &&
// matches attribute name
if (pattern[1].test(name)) {
list[1].test(name) &&
// matches attribute value
if (pattern[2].test(value)) {
delete item.attributes[name];
list[2].test(value)
) {
delete node.attributes[name];
}
}
}
}
}
});
}
},
},
};
};

View File

@ -1,19 +1,13 @@
'use strict';
const { detachNodeFromParent } = require('../lib/xast.js');
exports.name = 'removeElementsByAttr';
exports.type = 'perItem';
exports.type = 'visitor';
exports.active = false;
exports.description =
'removes arbitrary elements by ID or className (disabled by default)';
exports.params = {
id: [],
class: [],
};
/**
* Remove arbitrary SVG elements by ID or className.
*
@ -47,33 +41,37 @@ exports.params = {
* - 'elementClass'
* - 'anotherClass'
*
* @param {Object} item current iteration item
* @param {Object} params plugin params
* @return {Boolean} if false, item will be filtered out
*
* @author Eli Dupuis (@elidupuis)
*/
exports.fn = function (item, params) {
// wrap params in an array if not already
['id', 'class'].forEach(function (key) {
if (!Array.isArray(params[key])) {
params[key] = [params[key]];
}
});
// abort if current item is no an element
if (item.type !== 'element') {
return;
}
exports.fn = (root, params) => {
const ids =
params.id == null ? [] : Array.isArray(params.id) ? params.id : [params.id];
const classes =
params.class == null
? []
: Array.isArray(params.class)
? params.class
: [params.class];
return {
element: {
enter: (node, parentNode) => {
// remove element if it's `id` matches configured `id` params
if (item.attributes.id != null && params.id.length !== 0) {
return params.id.includes(item.attributes.id) === false;
if (node.attributes.id != null && ids.length !== 0) {
if (ids.includes(node.attributes.id)) {
detachNodeFromParent(node, parentNode);
}
}
// remove element if it's `class` contains any of the configured `class` params
if (item.attributes.class && params.class.length !== 0) {
const classList = item.attributes.class.split(' ');
return params.class.some((item) => classList.includes(item)) === false;
if (node.attributes.class && classes.length !== 0) {
const classList = node.attributes.class.split(' ');
for (const item of classes) {
if (classList.includes(item)) {
detachNodeFromParent(node, parentNode);
break;
}
}
}
},
},
};
};