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';
|
'use strict';
|
||||||
|
|
||||||
const { closestByName } = require('../lib/xast.js');
|
|
||||||
|
|
||||||
exports.name = 'addAttributesToSVGElement';
|
exports.name = 'addAttributesToSVGElement';
|
||||||
|
exports.type = 'visitor';
|
||||||
exports.type = 'perItem';
|
|
||||||
|
|
||||||
exports.active = false;
|
exports.active = false;
|
||||||
|
|
||||||
exports.description = 'adds attributes to an outer <svg> element';
|
exports.description = 'adds attributes to an outer <svg> element';
|
||||||
|
|
||||||
var ENOCLS = `Error in plugin "addAttributesToSVGElement": absent parameters.
|
var ENOCLS = `Error in plugin "addAttributesToSVGElement": absent parameters.
|
||||||
@ -54,32 +49,32 @@ plugins: [
|
|||||||
*
|
*
|
||||||
* @author April Arcus
|
* @author April Arcus
|
||||||
*/
|
*/
|
||||||
exports.fn = (node, params) => {
|
exports.fn = (root, params) => {
|
||||||
if (
|
if (!Array.isArray(params.attributes) && !params.attribute) {
|
||||||
node.type === 'element' &&
|
console.error(ENOCLS);
|
||||||
node.name === 'svg' &&
|
return;
|
||||||
closestByName(node.parentNode, 'svg') == null
|
}
|
||||||
) {
|
const attributes = params.attributes || [params.attribute];
|
||||||
if (!params || !(Array.isArray(params.attributes) || params.attribute)) {
|
return {
|
||||||
console.error(ENOCLS);
|
element: {
|
||||||
return;
|
enter: (node, parentNode) => {
|
||||||
}
|
if (node.name === 'svg' && parentNode.type === 'root') {
|
||||||
|
for (const attribute of attributes) {
|
||||||
const attributes = params.attributes || [params.attribute];
|
if (typeof attribute === 'string') {
|
||||||
|
if (node.attributes[attribute] == null) {
|
||||||
for (const attribute of attributes) {
|
node.attributes[attribute] = undefined;
|
||||||
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) {
|
||||||
if (typeof attribute === 'object') {
|
node.attributes[key] = attribute[key];
|
||||||
for (const key of Object.keys(attribute)) {
|
}
|
||||||
if (node.attributes[key] == null) {
|
}
|
||||||
node.attributes[key] = attribute[key];
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,57 +1,72 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
exports.name = 'addClassesToSVGElement';
|
exports.name = 'addClassesToSVGElement';
|
||||||
|
exports.type = 'visitor';
|
||||||
exports.type = 'full';
|
|
||||||
|
|
||||||
exports.active = false;
|
exports.active = false;
|
||||||
|
|
||||||
exports.description = 'adds classnames to an outer <svg> element';
|
exports.description = 'adds classnames to an outer <svg> element';
|
||||||
|
|
||||||
var ENOCLS = `Error in plugin "addClassesToSVGElement": absent parameters.
|
var ENOCLS = `Error in plugin "addClassesToSVGElement": absent parameters.
|
||||||
It should have a list of classes in "classNames" or one "className".
|
It should have a list of classes in "classNames" or one "className".
|
||||||
Config example:
|
Config example:
|
||||||
|
|
||||||
plugins:
|
plugins: [
|
||||||
- addClassesToSVGElement:
|
{
|
||||||
className: "mySvg"
|
name: "addClassesToSVGElement",
|
||||||
|
params: {
|
||||||
|
className: "mySvg"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
plugins:
|
plugins: [
|
||||||
- addClassesToSVGElement:
|
{
|
||||||
classNames: ["mySvg", "size-big"]
|
name: "addClassesToSVGElement",
|
||||||
|
params: {
|
||||||
|
classNames: ["mySvg", "size-big"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add classnames to an outer <svg> element. Example config:
|
* Add classnames to an outer <svg> element. Example config:
|
||||||
*
|
*
|
||||||
* plugins:
|
* plugins: [
|
||||||
* - addClassesToSVGElement:
|
* {
|
||||||
* className: 'mySvg'
|
* name: "addClassesToSVGElement",
|
||||||
|
* params: {
|
||||||
|
* className: "mySvg"
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ]
|
||||||
*
|
*
|
||||||
* plugins:
|
* plugins: [
|
||||||
* - addClassesToSVGElement:
|
* {
|
||||||
* classNames: ['mySvg', 'size-big']
|
* name: "addClassesToSVGElement",
|
||||||
|
* params: {
|
||||||
|
* classNames: ["mySvg", "size-big"]
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ]
|
||||||
*
|
*
|
||||||
* @author April Arcus
|
* @author April Arcus
|
||||||
*/
|
*/
|
||||||
exports.fn = function (data, params) {
|
exports.fn = (root, params) => {
|
||||||
if (
|
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);
|
console.error(ENOCLS);
|
||||||
return data;
|
return;
|
||||||
}
|
}
|
||||||
|
const classNames = params.classNames || [params.className];
|
||||||
var classNames = params.classNames || [params.className],
|
return {
|
||||||
svg = data.children[0];
|
element: {
|
||||||
|
enter: (node, parentNode) => {
|
||||||
if (svg.isElem('svg')) {
|
if (node.name === 'svg' && parentNode.type === 'root') {
|
||||||
svg.class.add.apply(svg.class, classNames);
|
node.class.add(...classNames);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
return data;
|
},
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,55 +1,71 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const { querySelectorAll } = require('../lib/xast.js');
|
||||||
|
|
||||||
exports.name = 'removeAttributesBySelector';
|
exports.name = 'removeAttributesBySelector';
|
||||||
|
exports.type = 'visitor';
|
||||||
exports.type = 'perItem';
|
|
||||||
|
|
||||||
exports.active = false;
|
exports.active = false;
|
||||||
|
|
||||||
exports.description =
|
exports.description =
|
||||||
'removes attributes of elements that match a css selector';
|
'removes attributes of elements that match a css selector';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
* @example
|
||||||
* <caption>A selector removing a single attribute</caption>
|
* <caption>A selector removing a single attribute</caption>
|
||||||
* plugins:
|
* plugins: [
|
||||||
* - removeAttributesBySelector:
|
* {
|
||||||
|
* name: "removeAttributesBySelector",
|
||||||
|
* params: {
|
||||||
* selector: "[fill='#00ff00']"
|
* selector: "[fill='#00ff00']"
|
||||||
* attributes: "fill"
|
* attributes: "fill"
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ]
|
||||||
*
|
*
|
||||||
* <rect x="0" y="0" width="100" height="100" fill="#00ff00" stroke="#00ff00"/>
|
* <rect x="0" y="0" width="100" height="100" fill="#00ff00" stroke="#00ff00"/>
|
||||||
* ↓
|
* ↓
|
||||||
* <rect x="0" y="0" width="100" height="100" stroke="#00ff00"/>
|
* <rect x="0" y="0" width="100" height="100" stroke="#00ff00"/>
|
||||||
*
|
*
|
||||||
* <caption>A selector removing multiple attributes</caption>
|
* <caption>A selector removing multiple attributes</caption>
|
||||||
* plugins:
|
* plugins: [
|
||||||
* - removeAttributesBySelector:
|
* {
|
||||||
* selector: "[fill='#00ff00']"
|
* name: "removeAttributesBySelector",
|
||||||
* attributes:
|
* params: {
|
||||||
* - fill
|
* selector: "[fill='#00ff00']",
|
||||||
* - stroke
|
* 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" fill="#00ff00" stroke="#00ff00"/>
|
||||||
* ↓
|
* ↓
|
||||||
* <rect x="0" y="0" width="100" height="100"/>
|
* <rect x="0" y="0" width="100" height="100"/>
|
||||||
*
|
*
|
||||||
* <caption>Multiple selectors removing attributes</caption>
|
* <caption>Multiple selectors removing attributes</caption>
|
||||||
* plugins:
|
* plugins: [
|
||||||
* - removeAttributesBySelector:
|
* {
|
||||||
* selectors:
|
* name: "removeAttributesBySelector",
|
||||||
* - selector: "[fill='#00ff00']"
|
* params: {
|
||||||
|
* selectors: [
|
||||||
|
* {
|
||||||
|
* selector: "[fill='#00ff00']",
|
||||||
* attributes: "fill"
|
* attributes: "fill"
|
||||||
*
|
* },
|
||||||
* - selector: "#remove"
|
* {
|
||||||
* attributes:
|
* selector: "#remove",
|
||||||
* - stroke
|
* attributes: [
|
||||||
* - id
|
* "stroke",
|
||||||
|
* "id"
|
||||||
|
* ]
|
||||||
|
* }
|
||||||
|
* ]
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ]
|
||||||
*
|
*
|
||||||
* <rect x="0" y="0" width="100" height="100" fill="#00ff00" stroke="#00ff00"/>
|
* <rect x="0" y="0" width="100" height="100" fill="#00ff00" stroke="#00ff00"/>
|
||||||
* ↓
|
* ↓
|
||||||
@ -59,18 +75,21 @@ exports.description =
|
|||||||
*
|
*
|
||||||
* @author Bradley Mease
|
* @author Bradley Mease
|
||||||
*/
|
*/
|
||||||
exports.fn = function (item, params) {
|
exports.fn = (root, params) => {
|
||||||
var selectors = Array.isArray(params.selectors) ? params.selectors : [params];
|
const selectors = Array.isArray(params.selectors)
|
||||||
|
? params.selectors
|
||||||
selectors.map(({ selector, attributes }) => {
|
: [params];
|
||||||
if (item.matches(selector)) {
|
for (const { selector, attributes } of selectors) {
|
||||||
|
const nodes = querySelectorAll(root, selector);
|
||||||
|
for (const node of nodes) {
|
||||||
if (Array.isArray(attributes)) {
|
if (Array.isArray(attributes)) {
|
||||||
for (const name of attributes) {
|
for (const name of attributes) {
|
||||||
delete item.attributes[name];
|
delete node.attributes[name];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
delete item.attributes[attributes];
|
delete node.attributes[attributes];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
return {};
|
||||||
};
|
};
|
||||||
|
@ -1,20 +1,11 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var DEFAULT_SEPARATOR = ':';
|
|
||||||
|
|
||||||
exports.name = 'removeAttrs';
|
exports.name = 'removeAttrs';
|
||||||
|
exports.type = 'visitor';
|
||||||
exports.type = 'perItem';
|
|
||||||
|
|
||||||
exports.active = false;
|
exports.active = false;
|
||||||
|
|
||||||
exports.description = 'removes specified attributes';
|
exports.description = 'removes specified attributes';
|
||||||
|
|
||||||
exports.params = {
|
const DEFAULT_SEPARATOR = ':';
|
||||||
elemSeparator: DEFAULT_SEPARATOR,
|
|
||||||
preserveCurrentColor: false,
|
|
||||||
attrs: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove attributes
|
* Remove attributes
|
||||||
@ -77,72 +68,69 @@ exports.params = {
|
|||||||
* attrs: 'stroke.*'
|
* 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
|
* @author Benny Schudel
|
||||||
*/
|
*/
|
||||||
exports.fn = function (item, params) {
|
exports.fn = (root, params) => {
|
||||||
// wrap into an array if params is not
|
// wrap into an array if params is not
|
||||||
if (!Array.isArray(params.attrs)) {
|
const elemSeparator =
|
||||||
params.attrs = [params.attrs];
|
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') {
|
return {
|
||||||
var elemSeparator =
|
element: {
|
||||||
typeof params.elemSeparator == 'string'
|
enter: (node) => {
|
||||||
? params.elemSeparator
|
for (let pattern of attrs) {
|
||||||
: DEFAULT_SEPARATOR;
|
// if no element separators (:), assume it's attribute name, and apply to all elements *regardless of value*
|
||||||
var preserveCurrentColor =
|
if (pattern.includes(elemSeparator) === false) {
|
||||||
typeof params.preserveCurrentColor == 'boolean'
|
pattern = ['.*', elemSeparator, pattern, elemSeparator, '.*'].join(
|
||||||
? params.preserveCurrentColor
|
''
|
||||||
: false;
|
);
|
||||||
|
// 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
|
// create regexps for element, attribute name, and attribute value
|
||||||
var patterns = params.attrs.map(function (pattern) {
|
const list = pattern.split(elemSeparator).map((value) => {
|
||||||
// if no element separators (:), assume it's attribute name, and apply to all elements *regardless of value*
|
// adjust single * to match anything
|
||||||
if (pattern.indexOf(elemSeparator) === -1) {
|
if (value === '*') {
|
||||||
pattern = ['.*', elemSeparator, pattern, elemSeparator, '.*'].join('');
|
value = '.*';
|
||||||
|
}
|
||||||
|
return new RegExp(['^', value, '$'].join(''), 'i');
|
||||||
|
});
|
||||||
|
|
||||||
// if only 1 separator, assume it's element and attribute name, and apply regardless of attribute value
|
// matches element
|
||||||
} else if (pattern.split(elemSeparator).length < 3) {
|
if (list[0].test(node.name)) {
|
||||||
pattern = [pattern, elemSeparator, '.*'].join('');
|
// loop attributes
|
||||||
}
|
for (const [name, value] of Object.entries(node.attributes)) {
|
||||||
|
const isFillCurrentColor =
|
||||||
// create regexps for element, attribute name, and attribute value
|
preserveCurrentColor &&
|
||||||
return pattern.split(elemSeparator).map(function (value) {
|
name == 'fill' &&
|
||||||
// adjust single * to match anything
|
value == 'currentColor';
|
||||||
if (value === '*') {
|
const isStrokeCurrentColor =
|
||||||
value = '.*';
|
preserveCurrentColor &&
|
||||||
}
|
name == 'stroke' &&
|
||||||
|
value == 'currentColor';
|
||||||
return new RegExp(['^', value, '$'].join(''), 'i');
|
if (
|
||||||
});
|
!isFillCurrentColor &&
|
||||||
});
|
!isStrokeCurrentColor &&
|
||||||
|
// matches attribute name
|
||||||
// loop patterns
|
list[1].test(name) &&
|
||||||
patterns.forEach(function (pattern) {
|
// matches attribute value
|
||||||
// matches element
|
list[2].test(value)
|
||||||
if (pattern[0].test(item.name)) {
|
) {
|
||||||
// loop attributes
|
delete node.attributes[name];
|
||||||
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];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
},
|
||||||
}
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,19 +1,13 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const { detachNodeFromParent } = require('../lib/xast.js');
|
||||||
|
|
||||||
exports.name = 'removeElementsByAttr';
|
exports.name = 'removeElementsByAttr';
|
||||||
|
exports.type = 'visitor';
|
||||||
exports.type = 'perItem';
|
|
||||||
|
|
||||||
exports.active = false;
|
exports.active = false;
|
||||||
|
|
||||||
exports.description =
|
exports.description =
|
||||||
'removes arbitrary elements by ID or className (disabled by default)';
|
'removes arbitrary elements by ID or className (disabled by default)';
|
||||||
|
|
||||||
exports.params = {
|
|
||||||
id: [],
|
|
||||||
class: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove arbitrary SVG elements by ID or className.
|
* Remove arbitrary SVG elements by ID or className.
|
||||||
*
|
*
|
||||||
@ -47,33 +41,37 @@ exports.params = {
|
|||||||
* - 'elementClass'
|
* - 'elementClass'
|
||||||
* - 'anotherClass'
|
* - '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)
|
* @author Eli Dupuis (@elidupuis)
|
||||||
*/
|
*/
|
||||||
exports.fn = function (item, params) {
|
exports.fn = (root, params) => {
|
||||||
// wrap params in an array if not already
|
const ids =
|
||||||
['id', 'class'].forEach(function (key) {
|
params.id == null ? [] : Array.isArray(params.id) ? params.id : [params.id];
|
||||||
if (!Array.isArray(params[key])) {
|
const classes =
|
||||||
params[key] = [params[key]];
|
params.class == null
|
||||||
}
|
? []
|
||||||
});
|
: Array.isArray(params.class)
|
||||||
|
? params.class
|
||||||
// abort if current item is no an element
|
: [params.class];
|
||||||
if (item.type !== 'element') {
|
return {
|
||||||
return;
|
element: {
|
||||||
}
|
enter: (node, parentNode) => {
|
||||||
|
// remove element if it's `id` matches configured `id` params
|
||||||
// remove element if it's `id` matches configured `id` params
|
if (node.attributes.id != null && ids.length !== 0) {
|
||||||
if (item.attributes.id != null && params.id.length !== 0) {
|
if (ids.includes(node.attributes.id)) {
|
||||||
return params.id.includes(item.attributes.id) === false;
|
detachNodeFromParent(node, parentNode);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// remove element if it's `class` contains any of the configured `class` params
|
// remove element if it's `class` contains any of the configured `class` params
|
||||||
if (item.attributes.class && params.class.length !== 0) {
|
if (node.attributes.class && classes.length !== 0) {
|
||||||
const classList = item.attributes.class.split(' ');
|
const classList = node.attributes.class.split(' ');
|
||||||
return params.class.some((item) => classList.includes(item)) === false;
|
for (const item of classes) {
|
||||||
}
|
if (classList.includes(item)) {
|
||||||
|
detachNodeFromParent(node, parentNode);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user