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:
@ -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,32 +49,32 @@ 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)) {
|
||||
console.error(ENOCLS);
|
||||
return;
|
||||
}
|
||||
|
||||
const attributes = params.attributes || [params.attribute];
|
||||
|
||||
for (const attribute of attributes) {
|
||||
if (typeof attribute === 'string') {
|
||||
if (node.attributes[attribute] == null) {
|
||||
node.attributes[attribute] = undefined;
|
||||
}
|
||||
}
|
||||
if (typeof attribute === 'object') {
|
||||
for (const key of Object.keys(attribute)) {
|
||||
if (node.attributes[key] == null) {
|
||||
node.attributes[key] = attribute[key];
|
||||
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) {
|
||||
node.attributes[attribute] = undefined;
|
||||
}
|
||||
}
|
||||
if (typeof attribute === 'object') {
|
||||
for (const key of Object.keys(attribute)) {
|
||||
if (node.attributes[key] == null) {
|
||||
node.attributes[key] = attribute[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
@ -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:
|
||||
className: "mySvg"
|
||||
plugins: [
|
||||
{
|
||||
name: "addClassesToSVGElement",
|
||||
params: {
|
||||
className: "mySvg"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
plugins:
|
||||
- addClassesToSVGElement:
|
||||
classNames: ["mySvg", "size-big"]
|
||||
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);
|
||||
}
|
||||
|
||||
return data;
|
||||
const classNames = params.classNames || [params.className];
|
||||
return {
|
||||
element: {
|
||||
enter: (node, parentNode) => {
|
||||
if (node.name === 'svg' && parentNode.type === 'root') {
|
||||
node.class.add(...classNames);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
@ -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 {};
|
||||
};
|
||||
|
@ -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];
|
||||
}
|
||||
const elemSeparator =
|
||||
typeof params.elemSeparator == 'string'
|
||||
? params.elemSeparator
|
||||
: DEFAULT_SEPARATOR;
|
||||
const preserveCurrentColor =
|
||||
typeof params.preserveCurrentColor == 'boolean'
|
||||
? params.preserveCurrentColor
|
||||
: false;
|
||||
const attrs = Array.isArray(params.attrs) ? params.attrs : [params.attrs];
|
||||
|
||||
if (item.type === 'element') {
|
||||
var elemSeparator =
|
||||
typeof params.elemSeparator == 'string'
|
||||
? params.elemSeparator
|
||||
: DEFAULT_SEPARATOR;
|
||||
var preserveCurrentColor =
|
||||
typeof params.preserveCurrentColor == 'boolean'
|
||||
? params.preserveCurrentColor
|
||||
: false;
|
||||
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.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('');
|
||||
}
|
||||
|
||||
// prepare patterns
|
||||
var patterns = params.attrs.map(function (pattern) {
|
||||
// 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('');
|
||||
// create regexps for element, attribute name, and attribute value
|
||||
const list = pattern.split(elemSeparator).map((value) => {
|
||||
// adjust single * to match anything
|
||||
if (value === '*') {
|
||||
value = '.*';
|
||||
}
|
||||
return new RegExp(['^', value, '$'].join(''), 'i');
|
||||
});
|
||||
|
||||
// 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) {
|
||||
// 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)) {
|
||||
// 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)) {
|
||||
// matches attribute name
|
||||
if (pattern[1].test(name)) {
|
||||
// matches attribute value
|
||||
if (pattern[2].test(value)) {
|
||||
delete item.attributes[name];
|
||||
// matches element
|
||||
if (list[0].test(node.name)) {
|
||||
// loop attributes
|
||||
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
|
||||
list[1].test(name) &&
|
||||
// matches attribute value
|
||||
list[2].test(value)
|
||||
) {
|
||||
delete node.attributes[name];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
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 (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 (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;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
Reference in New Issue
Block a user