svgo-v2 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +294 -0
  3. package/bin/svgo +10 -0
  4. package/dist/svgo.browser.js +1 -0
  5. package/lib/css-tools.js +239 -0
  6. package/lib/parser.js +259 -0
  7. package/lib/path.js +347 -0
  8. package/lib/stringifier.js +326 -0
  9. package/lib/style.js +283 -0
  10. package/lib/svgo/coa.js +517 -0
  11. package/lib/svgo/config.js +138 -0
  12. package/lib/svgo/css-class-list.js +72 -0
  13. package/lib/svgo/css-select-adapter.d.ts +2 -0
  14. package/lib/svgo/css-select-adapter.js +120 -0
  15. package/lib/svgo/css-style-declaration.js +232 -0
  16. package/lib/svgo/jsAPI.d.ts +2 -0
  17. package/lib/svgo/jsAPI.js +443 -0
  18. package/lib/svgo/plugins.js +109 -0
  19. package/lib/svgo/tools.js +137 -0
  20. package/lib/svgo-node.js +106 -0
  21. package/lib/svgo.js +83 -0
  22. package/lib/types.ts +172 -0
  23. package/lib/xast.js +102 -0
  24. package/package.json +130 -0
  25. package/plugins/_applyTransforms.js +335 -0
  26. package/plugins/_collections.js +2168 -0
  27. package/plugins/_path.js +816 -0
  28. package/plugins/_transforms.js +379 -0
  29. package/plugins/addAttributesToSVGElement.js +87 -0
  30. package/plugins/addClassesToSVGElement.js +87 -0
  31. package/plugins/cleanupAttrs.js +55 -0
  32. package/plugins/cleanupEnableBackground.js +75 -0
  33. package/plugins/cleanupIDs.js +297 -0
  34. package/plugins/cleanupListOfValues.js +154 -0
  35. package/plugins/cleanupNumericValues.js +113 -0
  36. package/plugins/collapseGroups.js +135 -0
  37. package/plugins/convertColors.js +152 -0
  38. package/plugins/convertEllipseToCircle.js +39 -0
  39. package/plugins/convertPathData.js +1023 -0
  40. package/plugins/convertShapeToPath.js +175 -0
  41. package/plugins/convertStyleToAttrs.js +132 -0
  42. package/plugins/convertTransform.js +432 -0
  43. package/plugins/inlineStyles.js +379 -0
  44. package/plugins/mergePaths.js +104 -0
  45. package/plugins/mergeStyles.js +93 -0
  46. package/plugins/minifyStyles.js +148 -0
  47. package/plugins/moveElemsAttrsToGroup.js +130 -0
  48. package/plugins/moveGroupAttrsToElems.js +62 -0
  49. package/plugins/plugins.js +56 -0
  50. package/plugins/prefixIds.js +241 -0
  51. package/plugins/preset-default.js +80 -0
  52. package/plugins/removeAttributesBySelector.js +99 -0
  53. package/plugins/removeAttrs.js +159 -0
  54. package/plugins/removeComments.js +31 -0
  55. package/plugins/removeDesc.js +41 -0
  56. package/plugins/removeDimensions.js +43 -0
  57. package/plugins/removeDoctype.js +42 -0
  58. package/plugins/removeEditorsNSData.js +68 -0
  59. package/plugins/removeElementsByAttr.js +78 -0
  60. package/plugins/removeEmptyAttrs.js +33 -0
  61. package/plugins/removeEmptyContainers.js +58 -0
  62. package/plugins/removeEmptyText.js +57 -0
  63. package/plugins/removeHiddenElems.js +318 -0
  64. package/plugins/removeMetadata.js +29 -0
  65. package/plugins/removeNonInheritableGroupAttrs.js +38 -0
  66. package/plugins/removeOffCanvasPaths.js +138 -0
  67. package/plugins/removeRasterImages.js +33 -0
  68. package/plugins/removeScriptElement.js +29 -0
  69. package/plugins/removeStyleElement.js +29 -0
  70. package/plugins/removeTitle.js +29 -0
  71. package/plugins/removeUnknownsAndDefaults.js +218 -0
  72. package/plugins/removeUnusedNS.js +61 -0
  73. package/plugins/removeUselessDefs.js +65 -0
  74. package/plugins/removeUselessStrokeAndFill.js +144 -0
  75. package/plugins/removeViewBox.js +51 -0
  76. package/plugins/removeXMLNS.js +30 -0
  77. package/plugins/removeXMLProcInst.js +30 -0
  78. package/plugins/reusePaths.js +113 -0
  79. package/plugins/sortAttrs.js +113 -0
  80. package/plugins/sortDefsChildren.js +60 -0
@@ -0,0 +1,80 @@
1
+ 'use strict';
2
+
3
+ const { createPreset } = require('../lib/svgo/plugins.js');
4
+
5
+ const removeDoctype = require('./removeDoctype.js');
6
+ const removeXMLProcInst = require('./removeXMLProcInst.js');
7
+ const removeComments = require('./removeComments.js');
8
+ const removeMetadata = require('./removeMetadata.js');
9
+ const removeEditorsNSData = require('./removeEditorsNSData.js');
10
+ const cleanupAttrs = require('./cleanupAttrs.js');
11
+ const mergeStyles = require('./mergeStyles.js');
12
+ const inlineStyles = require('./inlineStyles.js');
13
+ const minifyStyles = require('./minifyStyles.js');
14
+ const cleanupIDs = require('./cleanupIDs.js');
15
+ const removeUselessDefs = require('./removeUselessDefs.js');
16
+ const cleanupNumericValues = require('./cleanupNumericValues.js');
17
+ const convertColors = require('./convertColors.js');
18
+ const removeUnknownsAndDefaults = require('./removeUnknownsAndDefaults.js');
19
+ const removeNonInheritableGroupAttrs = require('./removeNonInheritableGroupAttrs.js');
20
+ const removeUselessStrokeAndFill = require('./removeUselessStrokeAndFill.js');
21
+ const removeViewBox = require('./removeViewBox.js');
22
+ const cleanupEnableBackground = require('./cleanupEnableBackground.js');
23
+ const removeHiddenElems = require('./removeHiddenElems.js');
24
+ const removeEmptyText = require('./removeEmptyText.js');
25
+ const convertShapeToPath = require('./convertShapeToPath.js');
26
+ const convertEllipseToCircle = require('./convertEllipseToCircle.js');
27
+ const moveElemsAttrsToGroup = require('./moveElemsAttrsToGroup.js');
28
+ const moveGroupAttrsToElems = require('./moveGroupAttrsToElems.js');
29
+ const collapseGroups = require('./collapseGroups.js');
30
+ const convertPathData = require('./convertPathData.js');
31
+ const convertTransform = require('./convertTransform.js');
32
+ const removeEmptyAttrs = require('./removeEmptyAttrs.js');
33
+ const removeEmptyContainers = require('./removeEmptyContainers.js');
34
+ const mergePaths = require('./mergePaths.js');
35
+ const removeUnusedNS = require('./removeUnusedNS.js');
36
+ const sortDefsChildren = require('./sortDefsChildren.js');
37
+ const removeTitle = require('./removeTitle.js');
38
+ const removeDesc = require('./removeDesc.js');
39
+
40
+ const presetDefault = createPreset({
41
+ name: 'presetDefault',
42
+ plugins: [
43
+ removeDoctype,
44
+ removeXMLProcInst,
45
+ removeComments,
46
+ removeMetadata,
47
+ removeEditorsNSData,
48
+ cleanupAttrs,
49
+ mergeStyles,
50
+ inlineStyles,
51
+ minifyStyles,
52
+ cleanupIDs,
53
+ removeUselessDefs,
54
+ cleanupNumericValues,
55
+ convertColors,
56
+ removeUnknownsAndDefaults,
57
+ removeNonInheritableGroupAttrs,
58
+ removeUselessStrokeAndFill,
59
+ removeViewBox,
60
+ cleanupEnableBackground,
61
+ removeHiddenElems,
62
+ removeEmptyText,
63
+ convertShapeToPath,
64
+ convertEllipseToCircle,
65
+ moveElemsAttrsToGroup,
66
+ moveGroupAttrsToElems,
67
+ collapseGroups,
68
+ convertPathData,
69
+ convertTransform,
70
+ removeEmptyAttrs,
71
+ removeEmptyContainers,
72
+ mergePaths,
73
+ removeUnusedNS,
74
+ sortDefsChildren,
75
+ removeTitle,
76
+ removeDesc,
77
+ ],
78
+ });
79
+
80
+ module.exports = presetDefault;
@@ -0,0 +1,99 @@
1
+ 'use strict';
2
+
3
+ const { querySelectorAll } = require('../lib/xast.js');
4
+
5
+ exports.name = 'removeAttributesBySelector';
6
+ exports.type = 'visitor';
7
+ exports.active = false;
8
+ exports.description =
9
+ 'removes attributes of elements that match a css selector';
10
+
11
+ /**
12
+ * Removes attributes of elements that match a css selector.
13
+ *
14
+ * @example
15
+ * <caption>A selector removing a single attribute</caption>
16
+ * plugins: [
17
+ * {
18
+ * name: "removeAttributesBySelector",
19
+ * params: {
20
+ * selector: "[fill='#00ff00']"
21
+ * attributes: "fill"
22
+ * }
23
+ * }
24
+ * ]
25
+ *
26
+ * <rect x="0" y="0" width="100" height="100" fill="#00ff00" stroke="#00ff00"/>
27
+ * ↓
28
+ * <rect x="0" y="0" width="100" height="100" stroke="#00ff00"/>
29
+ *
30
+ * <caption>A selector removing multiple attributes</caption>
31
+ * plugins: [
32
+ * {
33
+ * name: "removeAttributesBySelector",
34
+ * params: {
35
+ * selector: "[fill='#00ff00']",
36
+ * attributes: [
37
+ * "fill",
38
+ * "stroke"
39
+ * ]
40
+ * }
41
+ * }
42
+ * ]
43
+ *
44
+ * <rect x="0" y="0" width="100" height="100" fill="#00ff00" stroke="#00ff00"/>
45
+ * ↓
46
+ * <rect x="0" y="0" width="100" height="100"/>
47
+ *
48
+ * <caption>Multiple selectors removing attributes</caption>
49
+ * plugins: [
50
+ * {
51
+ * name: "removeAttributesBySelector",
52
+ * params: {
53
+ * selectors: [
54
+ * {
55
+ * selector: "[fill='#00ff00']",
56
+ * attributes: "fill"
57
+ * },
58
+ * {
59
+ * selector: "#remove",
60
+ * attributes: [
61
+ * "stroke",
62
+ * "id"
63
+ * ]
64
+ * }
65
+ * ]
66
+ * }
67
+ * }
68
+ * ]
69
+ *
70
+ * <rect x="0" y="0" width="100" height="100" fill="#00ff00" stroke="#00ff00"/>
71
+ * ↓
72
+ * <rect x="0" y="0" width="100" height="100"/>
73
+ *
74
+ * @link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors|MDN CSS Selectors
75
+ *
76
+ * @author Bradley Mease
77
+ *
78
+ * @type {import('../lib/types').Plugin<any>}
79
+ */
80
+ exports.fn = (root, params) => {
81
+ const selectors = Array.isArray(params.selectors)
82
+ ? params.selectors
83
+ : [params];
84
+ for (const { selector, attributes } of selectors) {
85
+ const nodes = querySelectorAll(root, selector);
86
+ for (const node of nodes) {
87
+ if (node.type === 'element') {
88
+ if (Array.isArray(attributes)) {
89
+ for (const name of attributes) {
90
+ delete node.attributes[name];
91
+ }
92
+ } else {
93
+ delete node.attributes[attributes];
94
+ }
95
+ }
96
+ }
97
+ }
98
+ return {};
99
+ };
@@ -0,0 +1,159 @@
1
+ 'use strict';
2
+
3
+ exports.name = 'removeAttrs';
4
+ exports.type = 'visitor';
5
+ exports.active = false;
6
+ exports.description = 'removes specified attributes';
7
+
8
+ const DEFAULT_SEPARATOR = ':';
9
+ const ENOATTRS = `Warning: The plugin "removeAttrs" requires the "attrs" parameter.
10
+ It should have a pattern to remove, otherwise the plugin is a noop.
11
+ Config example:
12
+
13
+ plugins: [
14
+ {
15
+ name: "removeAttrs",
16
+ params: {
17
+ attrs: "(fill|stroke)"
18
+ }
19
+ }
20
+ ]
21
+ `;
22
+
23
+ /**
24
+ * Remove attributes
25
+ *
26
+ * @example elemSeparator
27
+ * format: string
28
+ *
29
+ * @example preserveCurrentColor
30
+ * format: boolean
31
+ *
32
+ * @example attrs:
33
+ *
34
+ * format: [ element* : attribute* : value* ]
35
+ *
36
+ * element : regexp (wrapped into ^...$), single * or omitted > all elements (must be present when value is used)
37
+ * attribute : regexp (wrapped into ^...$)
38
+ * value : regexp (wrapped into ^...$), single * or omitted > all values
39
+ *
40
+ * examples:
41
+ *
42
+ * > basic: remove fill attribute
43
+ * ---
44
+ * removeAttrs:
45
+ * attrs: 'fill'
46
+ *
47
+ * > remove fill attribute on path element
48
+ * ---
49
+ * attrs: 'path:fill'
50
+ *
51
+ * > remove fill attribute on path element where value is none
52
+ * ---
53
+ * attrs: 'path:fill:none'
54
+ *
55
+ *
56
+ * > remove all fill and stroke attribute
57
+ * ---
58
+ * attrs:
59
+ * - 'fill'
60
+ * - 'stroke'
61
+ *
62
+ * [is same as]
63
+ *
64
+ * attrs: '(fill|stroke)'
65
+ *
66
+ * [is same as]
67
+ *
68
+ * attrs: '*:(fill|stroke)'
69
+ *
70
+ * [is same as]
71
+ *
72
+ * attrs: '.*:(fill|stroke)'
73
+ *
74
+ * [is same as]
75
+ *
76
+ * attrs: '.*:(fill|stroke):.*'
77
+ *
78
+ *
79
+ * > remove all stroke related attributes
80
+ * ----
81
+ * attrs: 'stroke.*'
82
+ *
83
+ *
84
+ * @author Benny Schudel
85
+ *
86
+ * @type {import('../lib/types').Plugin<{
87
+ * elemSeparator?: string,
88
+ * preserveCurrentColor?: boolean,
89
+ * attrs: string | Array<string>
90
+ * }>}
91
+ */
92
+ exports.fn = (root, params) => {
93
+ if (typeof params.attrs == 'undefined') {
94
+ console.warn(ENOATTRS);
95
+ return null;
96
+ }
97
+
98
+ const elemSeparator =
99
+ typeof params.elemSeparator == 'string'
100
+ ? params.elemSeparator
101
+ : DEFAULT_SEPARATOR;
102
+ const preserveCurrentColor =
103
+ typeof params.preserveCurrentColor == 'boolean'
104
+ ? params.preserveCurrentColor
105
+ : false;
106
+ const attrs = Array.isArray(params.attrs) ? params.attrs : [params.attrs];
107
+
108
+ return {
109
+ element: {
110
+ enter: (node) => {
111
+ for (let pattern of attrs) {
112
+ // if no element separators (:), assume it's attribute name, and apply to all elements *regardless of value*
113
+ if (pattern.includes(elemSeparator) === false) {
114
+ pattern = ['.*', elemSeparator, pattern, elemSeparator, '.*'].join(
115
+ ''
116
+ );
117
+ // if only 1 separator, assume it's element and attribute name, and apply regardless of attribute value
118
+ } else if (pattern.split(elemSeparator).length < 3) {
119
+ pattern = [pattern, elemSeparator, '.*'].join('');
120
+ }
121
+
122
+ // create regexps for element, attribute name, and attribute value
123
+ const list = pattern.split(elemSeparator).map((value) => {
124
+ // adjust single * to match anything
125
+ if (value === '*') {
126
+ value = '.*';
127
+ }
128
+ return new RegExp(['^', value, '$'].join(''), 'i');
129
+ });
130
+
131
+ // matches element
132
+ if (list[0].test(node.name)) {
133
+ // loop attributes
134
+ for (const [name, value] of Object.entries(node.attributes)) {
135
+ const isFillCurrentColor =
136
+ preserveCurrentColor &&
137
+ name == 'fill' &&
138
+ value == 'currentColor';
139
+ const isStrokeCurrentColor =
140
+ preserveCurrentColor &&
141
+ name == 'stroke' &&
142
+ value == 'currentColor';
143
+ if (
144
+ !isFillCurrentColor &&
145
+ !isStrokeCurrentColor &&
146
+ // matches attribute name
147
+ list[1].test(name) &&
148
+ // matches attribute value
149
+ list[2].test(value)
150
+ ) {
151
+ delete node.attributes[name];
152
+ }
153
+ }
154
+ }
155
+ }
156
+ },
157
+ },
158
+ };
159
+ };
@@ -0,0 +1,31 @@
1
+ 'use strict';
2
+
3
+ const { detachNodeFromParent } = require('../lib/xast.js');
4
+
5
+ exports.name = 'removeComments';
6
+ exports.type = 'visitor';
7
+ exports.active = true;
8
+ exports.description = 'removes comments';
9
+
10
+ /**
11
+ * Remove comments.
12
+ *
13
+ * @example
14
+ * <!-- Generator: Adobe Illustrator 15.0.0, SVG Export
15
+ * Plug-In . SVG Version: 6.00 Build 0) -->
16
+ *
17
+ * @author Kir Belevich
18
+ *
19
+ * @type {import('../lib/types').Plugin<void>}
20
+ */
21
+ exports.fn = () => {
22
+ return {
23
+ comment: {
24
+ enter: (node, parentNode) => {
25
+ if (node.value.charAt(0) !== '!') {
26
+ detachNodeFromParent(node, parentNode);
27
+ }
28
+ },
29
+ },
30
+ };
31
+ };
@@ -0,0 +1,41 @@
1
+ 'use strict';
2
+
3
+ const { detachNodeFromParent } = require('../lib/xast.js');
4
+
5
+ exports.name = 'removeDesc';
6
+ exports.type = 'visitor';
7
+ exports.active = true;
8
+ exports.description = 'removes <desc>';
9
+
10
+ const standardDescs = /^(Created with|Created using)/;
11
+
12
+ /**
13
+ * Removes <desc>.
14
+ * Removes only standard editors content or empty elements 'cause it can be used for accessibility.
15
+ * Enable parameter 'removeAny' to remove any description.
16
+ *
17
+ * https://developer.mozilla.org/en-US/docs/Web/SVG/Element/desc
18
+ *
19
+ * @author Daniel Wabyick
20
+ *
21
+ * @type {import('../lib/types').Plugin<{ removeAny?: boolean }>}
22
+ */
23
+ exports.fn = (root, params) => {
24
+ const { removeAny = true } = params;
25
+ return {
26
+ element: {
27
+ enter: (node, parentNode) => {
28
+ if (node.name === 'desc') {
29
+ if (
30
+ removeAny ||
31
+ node.children.length === 0 ||
32
+ (node.children[0].type === 'text' &&
33
+ standardDescs.test(node.children[0].value))
34
+ ) {
35
+ detachNodeFromParent(node, parentNode);
36
+ }
37
+ }
38
+ },
39
+ },
40
+ };
41
+ };
@@ -0,0 +1,43 @@
1
+ 'use strict';
2
+
3
+ exports.name = 'removeDimensions';
4
+
5
+ exports.type = 'perItem';
6
+
7
+ exports.active = false;
8
+
9
+ exports.description =
10
+ 'removes width and height in presence of viewBox (opposite to removeViewBox, disable it first)';
11
+
12
+ /**
13
+ * Remove width/height attributes and add the viewBox attribute if it's missing
14
+ *
15
+ * @example
16
+ * <svg width="100" height="50" />
17
+ * ↓
18
+ * <svg viewBox="0 0 100 50" />
19
+ *
20
+ * @param {Object} item current iteration item
21
+ * @return {Boolean} if true, with and height will be filtered out
22
+ *
23
+ * @author Benny Schudel
24
+ */
25
+ exports.fn = function (item) {
26
+ if (item.type === 'element' && item.name === 'svg') {
27
+ if (item.attributes.viewBox != null) {
28
+ delete item.attributes.width;
29
+ delete item.attributes.height;
30
+ } else if (
31
+ item.attributes.width != null &&
32
+ item.attributes.height != null &&
33
+ Number.isNaN(Number(item.attributes.width)) === false &&
34
+ Number.isNaN(Number(item.attributes.height)) === false
35
+ ) {
36
+ const width = Number(item.attributes.width);
37
+ const height = Number(item.attributes.height);
38
+ item.attributes.viewBox = `0 0 ${width} ${height}`;
39
+ delete item.attributes.width;
40
+ delete item.attributes.height;
41
+ }
42
+ }
43
+ };
@@ -0,0 +1,42 @@
1
+ 'use strict';
2
+
3
+ const { detachNodeFromParent } = require('../lib/xast.js');
4
+
5
+ exports.name = 'removeDoctype';
6
+ exports.type = 'visitor';
7
+ exports.active = true;
8
+ exports.description = 'removes doctype declaration';
9
+
10
+ /**
11
+ * Remove DOCTYPE declaration.
12
+ *
13
+ * "Unfortunately the SVG DTDs are a source of so many
14
+ * issues that the SVG WG has decided not to write one
15
+ * for the upcoming SVG 1.2 standard. In fact SVG WG
16
+ * members are even telling people not to use a DOCTYPE
17
+ * declaration in SVG 1.0 and 1.1 documents"
18
+ * https://jwatt.org/svg/authoring/#doctype-declaration
19
+ *
20
+ * @example
21
+ * <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
22
+ * q"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
23
+ *
24
+ * @example
25
+ * <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
26
+ * "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" [
27
+ * <!-- an internal subset can be embedded here -->
28
+ * ]>
29
+ *
30
+ * @author Kir Belevich
31
+ *
32
+ * @type {import('../lib/types').Plugin<void>}
33
+ */
34
+ exports.fn = () => {
35
+ return {
36
+ doctype: {
37
+ enter: (node, parentNode) => {
38
+ detachNodeFromParent(node, parentNode);
39
+ },
40
+ },
41
+ };
42
+ };
@@ -0,0 +1,68 @@
1
+ 'use strict';
2
+
3
+ const { detachNodeFromParent } = require('../lib/xast.js');
4
+ const { editorNamespaces } = require('./_collections.js');
5
+
6
+ exports.type = 'visitor';
7
+ exports.name = 'removeEditorsNSData';
8
+ exports.active = true;
9
+ exports.description = 'removes editors namespaces, elements and attributes';
10
+
11
+ /**
12
+ * Remove editors namespaces, elements and attributes.
13
+ *
14
+ * @example
15
+ * <svg xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd">
16
+ * <sodipodi:namedview/>
17
+ * <path sodipodi:nodetypes="cccc"/>
18
+ *
19
+ * @author Kir Belevich
20
+ *
21
+ * @type {import('../lib/types').Plugin<{
22
+ * additionalNamespaces?: Array<string>
23
+ * }>}
24
+ */
25
+ exports.fn = (_root, params) => {
26
+ let namespaces = editorNamespaces;
27
+ if (Array.isArray(params.additionalNamespaces)) {
28
+ namespaces = [...editorNamespaces, ...params.additionalNamespaces];
29
+ }
30
+ /**
31
+ * @type {Array<string>}
32
+ */
33
+ const prefixes = [];
34
+ return {
35
+ element: {
36
+ enter: (node, parentNode) => {
37
+ // collect namespace aliases from svg element
38
+ if (node.name === 'svg') {
39
+ for (const [name, value] of Object.entries(node.attributes)) {
40
+ if (name.startsWith('xmlns:') && namespaces.includes(value)) {
41
+ prefixes.push(name.slice('xmlns:'.length));
42
+ // <svg xmlns:sodipodi="">
43
+ delete node.attributes[name];
44
+ }
45
+ }
46
+ }
47
+ // remove editor attributes, for example
48
+ // <* sodipodi:*="">
49
+ for (const name of Object.keys(node.attributes)) {
50
+ if (name.includes(':')) {
51
+ const [prefix] = name.split(':');
52
+ if (prefixes.includes(prefix)) {
53
+ delete node.attributes[name];
54
+ }
55
+ }
56
+ }
57
+ // remove editor elements, for example
58
+ // <sodipodi:*>
59
+ if (node.name.includes(':')) {
60
+ const [prefix] = node.name.split(':');
61
+ if (prefixes.includes(prefix)) {
62
+ detachNodeFromParent(node, parentNode);
63
+ }
64
+ }
65
+ },
66
+ },
67
+ };
68
+ };
@@ -0,0 +1,78 @@
1
+ 'use strict';
2
+
3
+ const { detachNodeFromParent } = require('../lib/xast.js');
4
+
5
+ exports.name = 'removeElementsByAttr';
6
+ exports.type = 'visitor';
7
+ exports.active = false;
8
+ exports.description =
9
+ 'removes arbitrary elements by ID or className (disabled by default)';
10
+
11
+ /**
12
+ * Remove arbitrary SVG elements by ID or className.
13
+ *
14
+ * @example id
15
+ * > single: remove element with ID of `elementID`
16
+ * ---
17
+ * removeElementsByAttr:
18
+ * id: 'elementID'
19
+ *
20
+ * > list: remove multiple elements by ID
21
+ * ---
22
+ * removeElementsByAttr:
23
+ * id:
24
+ * - 'elementID'
25
+ * - 'anotherID'
26
+ *
27
+ * @example class
28
+ * > single: remove all elements with class of `elementClass`
29
+ * ---
30
+ * removeElementsByAttr:
31
+ * class: 'elementClass'
32
+ *
33
+ * > list: remove all elements with class of `elementClass` or `anotherClass`
34
+ * ---
35
+ * removeElementsByAttr:
36
+ * class:
37
+ * - 'elementClass'
38
+ * - 'anotherClass'
39
+ *
40
+ * @author Eli Dupuis (@elidupuis)
41
+ *
42
+ * @type {import('../lib/types').Plugin<{
43
+ * id?: string | Array<string>,
44
+ * class?: string | Array<string>
45
+ * }>}
46
+ */
47
+ exports.fn = (root, params) => {
48
+ const ids =
49
+ params.id == null ? [] : Array.isArray(params.id) ? params.id : [params.id];
50
+ const classes =
51
+ params.class == null
52
+ ? []
53
+ : Array.isArray(params.class)
54
+ ? params.class
55
+ : [params.class];
56
+ return {
57
+ element: {
58
+ enter: (node, parentNode) => {
59
+ // remove element if it's `id` matches configured `id` params
60
+ if (node.attributes.id != null && ids.length !== 0) {
61
+ if (ids.includes(node.attributes.id)) {
62
+ detachNodeFromParent(node, parentNode);
63
+ }
64
+ }
65
+ // remove element if it's `class` contains any of the configured `class` params
66
+ if (node.attributes.class && classes.length !== 0) {
67
+ const classList = node.attributes.class.split(' ');
68
+ for (const item of classes) {
69
+ if (classList.includes(item)) {
70
+ detachNodeFromParent(node, parentNode);
71
+ break;
72
+ }
73
+ }
74
+ }
75
+ },
76
+ },
77
+ };
78
+ };
@@ -0,0 +1,33 @@
1
+ 'use strict';
2
+
3
+ const { attrsGroups } = require('./_collections.js');
4
+
5
+ exports.type = 'visitor';
6
+ exports.name = 'removeEmptyAttrs';
7
+ exports.active = true;
8
+ exports.description = 'removes empty attributes';
9
+
10
+ /**
11
+ * Remove attributes with empty values.
12
+ *
13
+ * @author Kir Belevich
14
+ *
15
+ * @type {import('../lib/types').Plugin<void>}
16
+ */
17
+ exports.fn = () => {
18
+ return {
19
+ element: {
20
+ enter: (node) => {
21
+ for (const [name, value] of Object.entries(node.attributes)) {
22
+ if (
23
+ value === '' &&
24
+ // empty conditional processing attributes prevents elements from rendering
25
+ attrsGroups.conditionalProcessing.includes(name) === false
26
+ ) {
27
+ delete node.attributes[name];
28
+ }
29
+ }
30
+ },
31
+ },
32
+ };
33
+ };