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,148 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * @typedef {import('../lib/types').XastElement} XastElement
5
+ */
6
+
7
+ const csso = require('csso');
8
+
9
+ exports.type = 'visitor';
10
+ exports.name = 'minifyStyles';
11
+ exports.active = true;
12
+ exports.description =
13
+ 'minifies styles and removes unused styles based on usage data';
14
+
15
+ /**
16
+ * Minifies styles (<style> element + style attribute) using CSSO
17
+ *
18
+ * @author strarsis <strarsis@gmail.com>
19
+ *
20
+ * @type {import('../lib/types').Plugin<csso.MinifyOptions & Omit<csso.CompressOptions, 'usage'> & {
21
+ * usage?: boolean | {
22
+ * force?: boolean,
23
+ * ids?: boolean,
24
+ * classes?: boolean,
25
+ * tags?: boolean
26
+ * }
27
+ * }>}
28
+ */
29
+ exports.fn = (_root, { usage, ...params }) => {
30
+ let enableTagsUsage = true;
31
+ let enableIdsUsage = true;
32
+ let enableClassesUsage = true;
33
+ // force to use usage data even if it unsafe (document contains <script> or on* attributes)
34
+ let forceUsageDeoptimized = false;
35
+ if (typeof usage === 'boolean') {
36
+ enableTagsUsage = usage;
37
+ enableIdsUsage = usage;
38
+ enableClassesUsage = usage;
39
+ } else if (usage) {
40
+ enableTagsUsage = usage.tags == null ? true : usage.tags;
41
+ enableIdsUsage = usage.ids == null ? true : usage.ids;
42
+ enableClassesUsage = usage.classes == null ? true : usage.classes;
43
+ forceUsageDeoptimized = usage.force == null ? false : usage.force;
44
+ }
45
+ /**
46
+ * @type {Array<XastElement>}
47
+ */
48
+ const styleElements = [];
49
+ /**
50
+ * @type {Array<XastElement>}
51
+ */
52
+ const elementsWithStyleAttributes = [];
53
+ let deoptimized = false;
54
+ /**
55
+ * @type {Set<string>}
56
+ */
57
+ const tagsUsage = new Set();
58
+ /**
59
+ * @type {Set<string>}
60
+ */
61
+ const idsUsage = new Set();
62
+ /**
63
+ * @type {Set<string>}
64
+ */
65
+ const classesUsage = new Set();
66
+
67
+ return {
68
+ element: {
69
+ enter: (node) => {
70
+ // detect deoptimisations
71
+ if (node.name === 'script') {
72
+ deoptimized = true;
73
+ }
74
+ for (const name of Object.keys(node.attributes)) {
75
+ if (name.startsWith('on')) {
76
+ deoptimized = true;
77
+ }
78
+ }
79
+ // collect tags, ids and classes usage
80
+ tagsUsage.add(node.name);
81
+ if (node.attributes.id != null) {
82
+ idsUsage.add(node.attributes.id);
83
+ }
84
+ if (node.attributes.class != null) {
85
+ for (const className of node.attributes.class.split(/\s+/)) {
86
+ classesUsage.add(className);
87
+ }
88
+ }
89
+ // collect style elements or elements with style attribute
90
+ if (node.name === 'style' && node.children.length !== 0) {
91
+ styleElements.push(node);
92
+ } else if (node.attributes.style != null) {
93
+ elementsWithStyleAttributes.push(node);
94
+ }
95
+ },
96
+ },
97
+
98
+ root: {
99
+ exit: () => {
100
+ /**
101
+ * @type {csso.Usage}
102
+ */
103
+ const cssoUsage = {};
104
+ if (deoptimized === false || forceUsageDeoptimized === true) {
105
+ if (enableTagsUsage && tagsUsage.size !== 0) {
106
+ cssoUsage.tags = Array.from(tagsUsage);
107
+ }
108
+ if (enableIdsUsage && idsUsage.size !== 0) {
109
+ cssoUsage.ids = Array.from(idsUsage);
110
+ }
111
+ if (enableClassesUsage && classesUsage.size !== 0) {
112
+ cssoUsage.classes = Array.from(classesUsage);
113
+ }
114
+ }
115
+ // minify style elements
116
+ for (const node of styleElements) {
117
+ if (
118
+ node.children[0].type === 'text' ||
119
+ node.children[0].type === 'cdata'
120
+ ) {
121
+ const cssText = node.children[0].value;
122
+ const minified = csso.minify(cssText, {
123
+ ...params,
124
+ usage: cssoUsage,
125
+ }).css;
126
+ // preserve cdata if necessary
127
+ // TODO split cdata -> text optimisation into separate plugin
128
+ if (cssText.indexOf('>') >= 0 || cssText.indexOf('<') >= 0) {
129
+ node.children[0].type = 'cdata';
130
+ node.children[0].value = minified;
131
+ } else {
132
+ node.children[0].type = 'text';
133
+ node.children[0].value = minified;
134
+ }
135
+ }
136
+ }
137
+ // minify style attributes
138
+ for (const node of elementsWithStyleAttributes) {
139
+ // style attribute
140
+ const elemStyle = node.attributes.style;
141
+ node.attributes.style = csso.minifyBlock(elemStyle, {
142
+ ...params,
143
+ }).css;
144
+ }
145
+ },
146
+ },
147
+ };
148
+ };
@@ -0,0 +1,130 @@
1
+ 'use strict';
2
+
3
+ const { visit } = require('../lib/xast.js');
4
+ const { inheritableAttrs, pathElems } = require('./_collections.js');
5
+
6
+ exports.type = 'visitor';
7
+ exports.name = 'moveElemsAttrsToGroup';
8
+ exports.active = true;
9
+ exports.description = 'Move common attributes of group children to the group';
10
+
11
+ /**
12
+ * Move common attributes of group children to the group
13
+ *
14
+ * @example
15
+ * <g attr1="val1">
16
+ * <g attr2="val2">
17
+ * text
18
+ * </g>
19
+ * <circle attr2="val2" attr3="val3"/>
20
+ * </g>
21
+ * ⬇
22
+ * <g attr1="val1" attr2="val2">
23
+ * <g>
24
+ * text
25
+ * </g>
26
+ * <circle attr3="val3"/>
27
+ * </g>
28
+ *
29
+ * @author Kir Belevich
30
+ *
31
+ * @type {import('../lib/types').Plugin<void>}
32
+ */
33
+ exports.fn = (root) => {
34
+ // find if any style element is present
35
+ let deoptimizedWithStyles = false;
36
+ visit(root, {
37
+ element: {
38
+ enter: (node) => {
39
+ if (node.name === 'style') {
40
+ deoptimizedWithStyles = true;
41
+ }
42
+ },
43
+ },
44
+ });
45
+
46
+ return {
47
+ element: {
48
+ exit: (node) => {
49
+ // process only groups with more than 1 children
50
+ if (node.name !== 'g' || node.children.length <= 1) {
51
+ return;
52
+ }
53
+
54
+ // deoptimize the plugin when style elements are present
55
+ // selectors may rely on id, classes or tag names
56
+ if (deoptimizedWithStyles) {
57
+ return;
58
+ }
59
+
60
+ /**
61
+ * find common attributes in group children
62
+ * @type {Map<string, string>}
63
+ */
64
+ const commonAttributes = new Map();
65
+ let initial = true;
66
+ let everyChildIsPath = true;
67
+ for (const child of node.children) {
68
+ if (child.type === 'element') {
69
+ if (pathElems.includes(child.name) === false) {
70
+ everyChildIsPath = false;
71
+ }
72
+ if (initial) {
73
+ initial = false;
74
+ // collect all inheritable attributes from first child element
75
+ for (const [name, value] of Object.entries(child.attributes)) {
76
+ // consider only inheritable attributes
77
+ if (inheritableAttrs.includes(name)) {
78
+ commonAttributes.set(name, value);
79
+ }
80
+ }
81
+ } else {
82
+ // exclude uncommon attributes from initial list
83
+ for (const [name, value] of commonAttributes) {
84
+ if (child.attributes[name] !== value) {
85
+ commonAttributes.delete(name);
86
+ }
87
+ }
88
+ }
89
+ }
90
+ }
91
+
92
+ // preserve transform on children when group has clip-path or mask
93
+ if (
94
+ node.attributes['clip-path'] != null ||
95
+ node.attributes.mask != null
96
+ ) {
97
+ commonAttributes.delete('transform');
98
+ }
99
+
100
+ // preserve transform when all children are paths
101
+ // so the transform could be applied to path data by other plugins
102
+ if (everyChildIsPath) {
103
+ commonAttributes.delete('transform');
104
+ }
105
+
106
+ // add common children attributes to group
107
+ for (const [name, value] of commonAttributes) {
108
+ if (name === 'transform') {
109
+ if (node.attributes.transform != null) {
110
+ node.attributes.transform = `${node.attributes.transform} ${value}`;
111
+ } else {
112
+ node.attributes.transform = value;
113
+ }
114
+ } else {
115
+ node.attributes[name] = value;
116
+ }
117
+ }
118
+
119
+ // delete common attributes from children
120
+ for (const child of node.children) {
121
+ if (child.type === 'element') {
122
+ for (const [name] of commonAttributes) {
123
+ delete child.attributes[name];
124
+ }
125
+ }
126
+ }
127
+ },
128
+ },
129
+ };
130
+ };
@@ -0,0 +1,62 @@
1
+ 'use strict';
2
+
3
+ const { pathElems, referencesProps } = require('./_collections.js');
4
+
5
+ exports.name = 'moveGroupAttrsToElems';
6
+
7
+ exports.type = 'perItem';
8
+
9
+ exports.active = true;
10
+
11
+ exports.description = 'moves some group attributes to the content elements';
12
+
13
+ const pathElemsWithGroupsAndText = [...pathElems, 'g', 'text'];
14
+
15
+ /**
16
+ * Move group attrs to the content elements.
17
+ *
18
+ * @example
19
+ * <g transform="scale(2)">
20
+ * <path transform="rotate(45)" d="M0,0 L10,20"/>
21
+ * <path transform="translate(10, 20)" d="M0,10 L20,30"/>
22
+ * </g>
23
+ * ⬇
24
+ * <g>
25
+ * <path transform="scale(2) rotate(45)" d="M0,0 L10,20"/>
26
+ * <path transform="scale(2) translate(10, 20)" d="M0,10 L20,30"/>
27
+ * </g>
28
+ *
29
+ * @param {Object} item current iteration item
30
+ * @return {Boolean} if false, item will be filtered out
31
+ *
32
+ * @author Kir Belevich
33
+ */
34
+ exports.fn = function (item) {
35
+ // move group transform attr to content's pathElems
36
+ if (
37
+ item.type === 'element' &&
38
+ item.name === 'g' &&
39
+ item.children.length !== 0 &&
40
+ item.attributes.transform != null &&
41
+ Object.entries(item.attributes).some(
42
+ ([name, value]) =>
43
+ referencesProps.includes(name) && value.includes('url(')
44
+ ) === false &&
45
+ item.children.every(
46
+ (inner) =>
47
+ pathElemsWithGroupsAndText.includes(inner.name) &&
48
+ inner.attributes.id == null
49
+ )
50
+ ) {
51
+ for (const inner of item.children) {
52
+ const value = item.attributes.transform;
53
+ if (inner.attributes.transform != null) {
54
+ inner.attributes.transform = value + ' ' + inner.attributes.transform;
55
+ } else {
56
+ inner.attributes.transform = value;
57
+ }
58
+ }
59
+
60
+ delete item.attributes.transform;
61
+ }
62
+ };
@@ -0,0 +1,56 @@
1
+ 'use strict';
2
+
3
+ // builtin presets
4
+ exports['preset-default'] = require('./preset-default.js');
5
+
6
+ // builtin plugins
7
+ exports.addAttributesToSVGElement = require('./addAttributesToSVGElement.js');
8
+ exports.addClassesToSVGElement = require('./addClassesToSVGElement.js');
9
+ exports.cleanupAttrs = require('./cleanupAttrs.js');
10
+ exports.cleanupEnableBackground = require('./cleanupEnableBackground.js');
11
+ exports.cleanupIDs = require('./cleanupIDs.js');
12
+ exports.cleanupListOfValues = require('./cleanupListOfValues.js');
13
+ exports.cleanupNumericValues = require('./cleanupNumericValues.js');
14
+ exports.collapseGroups = require('./collapseGroups.js');
15
+ exports.convertColors = require('./convertColors.js');
16
+ exports.convertEllipseToCircle = require('./convertEllipseToCircle.js');
17
+ exports.convertPathData = require('./convertPathData.js');
18
+ exports.convertShapeToPath = require('./convertShapeToPath.js');
19
+ exports.convertStyleToAttrs = require('./convertStyleToAttrs.js');
20
+ exports.convertTransform = require('./convertTransform.js');
21
+ exports.mergeStyles = require('./mergeStyles.js');
22
+ exports.inlineStyles = require('./inlineStyles.js');
23
+ exports.mergePaths = require('./mergePaths.js');
24
+ exports.minifyStyles = require('./minifyStyles.js');
25
+ exports.moveElemsAttrsToGroup = require('./moveElemsAttrsToGroup.js');
26
+ exports.moveGroupAttrsToElems = require('./moveGroupAttrsToElems.js');
27
+ exports.prefixIds = require('./prefixIds.js');
28
+ exports.removeAttributesBySelector = require('./removeAttributesBySelector.js');
29
+ exports.removeAttrs = require('./removeAttrs.js');
30
+ exports.removeComments = require('./removeComments.js');
31
+ exports.removeDesc = require('./removeDesc.js');
32
+ exports.removeDimensions = require('./removeDimensions.js');
33
+ exports.removeDoctype = require('./removeDoctype.js');
34
+ exports.removeEditorsNSData = require('./removeEditorsNSData.js');
35
+ exports.removeElementsByAttr = require('./removeElementsByAttr.js');
36
+ exports.removeEmptyAttrs = require('./removeEmptyAttrs.js');
37
+ exports.removeEmptyContainers = require('./removeEmptyContainers.js');
38
+ exports.removeEmptyText = require('./removeEmptyText.js');
39
+ exports.removeHiddenElems = require('./removeHiddenElems.js');
40
+ exports.removeMetadata = require('./removeMetadata.js');
41
+ exports.removeNonInheritableGroupAttrs = require('./removeNonInheritableGroupAttrs.js');
42
+ exports.removeOffCanvasPaths = require('./removeOffCanvasPaths.js');
43
+ exports.removeRasterImages = require('./removeRasterImages.js');
44
+ exports.removeScriptElement = require('./removeScriptElement.js');
45
+ exports.removeStyleElement = require('./removeStyleElement.js');
46
+ exports.removeTitle = require('./removeTitle.js');
47
+ exports.removeUnknownsAndDefaults = require('./removeUnknownsAndDefaults.js');
48
+ exports.removeUnusedNS = require('./removeUnusedNS.js');
49
+ exports.removeUselessDefs = require('./removeUselessDefs.js');
50
+ exports.removeUselessStrokeAndFill = require('./removeUselessStrokeAndFill.js');
51
+ exports.removeViewBox = require('./removeViewBox.js');
52
+ exports.removeXMLNS = require('./removeXMLNS.js');
53
+ exports.removeXMLProcInst = require('./removeXMLProcInst.js');
54
+ exports.reusePaths = require('./reusePaths.js');
55
+ exports.sortAttrs = require('./sortAttrs.js');
56
+ exports.sortDefsChildren = require('./sortDefsChildren.js');
@@ -0,0 +1,241 @@
1
+ 'use strict';
2
+
3
+ const csstree = require('css-tree-v2');
4
+ const { referencesProps } = require('./_collections.js');
5
+
6
+ /**
7
+ * @typedef {import('../lib/types').XastElement} XastElement
8
+ * @typedef {import('../lib/types').PluginInfo} PluginInfo
9
+ */
10
+
11
+ exports.type = 'visitor';
12
+ exports.name = 'prefixIds';
13
+ exports.active = false;
14
+ exports.description = 'prefix IDs';
15
+
16
+ /**
17
+ * extract basename from path
18
+ * @type {(path: string) => string}
19
+ */
20
+ const getBasename = (path) => {
21
+ // extract everything after latest slash or backslash
22
+ const matched = path.match(/[/\\]?([^/\\]+)$/);
23
+ if (matched) {
24
+ return matched[1];
25
+ }
26
+ return '';
27
+ };
28
+
29
+ /**
30
+ * escapes a string for being used as ID
31
+ * @type {(string: string) => string}
32
+ */
33
+ const escapeIdentifierName = (str) => {
34
+ return str.replace(/[. ]/g, '_');
35
+ };
36
+
37
+ /**
38
+ * @type {(string: string) => string}
39
+ */
40
+ const unquote = (string) => {
41
+ if (
42
+ (string.startsWith('"') && string.endsWith('"')) ||
43
+ (string.startsWith("'") && string.endsWith("'"))
44
+ ) {
45
+ return string.slice(1, -1);
46
+ }
47
+ return string;
48
+ };
49
+
50
+ /**
51
+ * prefix an ID
52
+ * @type {(prefix: string, name: string) => string}
53
+ */
54
+ const prefixId = (prefix, value) => {
55
+ if (value.startsWith(prefix)) {
56
+ return value;
57
+ }
58
+ return prefix + value;
59
+ };
60
+
61
+ /**
62
+ * prefix an #ID
63
+ * @type {(prefix: string, name: string) => string | null}
64
+ */
65
+ const prefixReference = (prefix, value) => {
66
+ if (value.startsWith('#')) {
67
+ return '#' + prefixId(prefix, value.slice(1));
68
+ }
69
+ return null;
70
+ };
71
+
72
+ /**
73
+ * Prefixes identifiers
74
+ *
75
+ * @author strarsis <strarsis@gmail.com>
76
+ *
77
+ * @type {import('../lib/types').Plugin<{
78
+ * prefix?: boolean | string | ((node: XastElement, info: PluginInfo) => string),
79
+ * delim?: string,
80
+ * prefixIds?: boolean,
81
+ * prefixClassNames?: boolean,
82
+ * }>}
83
+ */
84
+ exports.fn = (_root, params, info) => {
85
+ const { delim = '__', prefixIds = true, prefixClassNames = true } = params;
86
+
87
+ return {
88
+ element: {
89
+ enter: (node) => {
90
+ /**
91
+ * prefix, from file name or option
92
+ * @type {string}
93
+ */
94
+ let prefix = 'prefix' + delim;
95
+ if (typeof params.prefix === 'function') {
96
+ prefix = params.prefix(node, info) + delim;
97
+ } else if (typeof params.prefix === 'string') {
98
+ prefix = params.prefix + delim;
99
+ } else if (params.prefix === false) {
100
+ prefix = '';
101
+ } else if (info.path != null && info.path.length > 0) {
102
+ prefix = escapeIdentifierName(getBasename(info.path)) + delim;
103
+ }
104
+
105
+ // prefix id/class selectors and url() references in styles
106
+ if (node.name === 'style') {
107
+ // skip empty <style/> elements
108
+ if (node.children.length === 0) {
109
+ return;
110
+ }
111
+
112
+ // parse styles
113
+ let cssText = '';
114
+ if (
115
+ node.children[0].type === 'text' ||
116
+ node.children[0].type === 'cdata'
117
+ ) {
118
+ cssText = node.children[0].value;
119
+ }
120
+ /**
121
+ * @type {null | csstree.CssNode}
122
+ */
123
+ let cssAst = null;
124
+ try {
125
+ cssAst = csstree.parse(cssText, {
126
+ parseValue: true,
127
+ parseCustomProperty: false,
128
+ });
129
+ } catch {
130
+ return;
131
+ }
132
+
133
+ csstree.walk(cssAst, (node) => {
134
+ // #ID, .class selectors
135
+ if (
136
+ (prefixIds && node.type === 'IdSelector') ||
137
+ (prefixClassNames && node.type === 'ClassSelector')
138
+ ) {
139
+ node.name = prefixId(prefix, node.name);
140
+ return;
141
+ }
142
+ // url(...) references
143
+ if (
144
+ node.type === 'Url' &&
145
+ node.value.value &&
146
+ node.value.value.length > 0
147
+ ) {
148
+ const prefixed = prefixReference(
149
+ prefix,
150
+ unquote(node.value.value)
151
+ );
152
+ if (prefixed != null) {
153
+ node.value.value = prefixed;
154
+ }
155
+ }
156
+ });
157
+
158
+ // update styles
159
+ if (
160
+ node.children[0].type === 'text' ||
161
+ node.children[0].type === 'cdata'
162
+ ) {
163
+ node.children[0].value = csstree.generate(cssAst);
164
+ }
165
+ return;
166
+ }
167
+
168
+ // prefix an ID attribute value
169
+ if (
170
+ prefixIds &&
171
+ node.attributes.id != null &&
172
+ node.attributes.id.length !== 0
173
+ ) {
174
+ node.attributes.id = prefixId(prefix, node.attributes.id);
175
+ }
176
+
177
+ // prefix a class attribute value
178
+ if (
179
+ prefixClassNames &&
180
+ node.attributes.class != null &&
181
+ node.attributes.class.length !== 0
182
+ ) {
183
+ node.attributes.class = node.attributes.class
184
+ .split(/\s+/)
185
+ .map((name) => prefixId(prefix, name))
186
+ .join(' ');
187
+ }
188
+
189
+ // prefix a href attribute value
190
+ // xlink:href is deprecated, must be still supported
191
+ for (const name of ['href', 'xlink:href']) {
192
+ if (
193
+ node.attributes[name] != null &&
194
+ node.attributes[name].length !== 0
195
+ ) {
196
+ const prefixed = prefixReference(prefix, node.attributes[name]);
197
+ if (prefixed != null) {
198
+ node.attributes[name] = prefixed;
199
+ }
200
+ }
201
+ }
202
+
203
+ // prefix an URL attribute value
204
+ for (const name of referencesProps) {
205
+ if (
206
+ node.attributes[name] != null &&
207
+ node.attributes[name].length !== 0
208
+ ) {
209
+ node.attributes[name] = node.attributes[name].replace(
210
+ /url\((.*?)\)/gi,
211
+ (match, url) => {
212
+ const prefixed = prefixReference(prefix, url);
213
+ if (prefixed == null) {
214
+ return match;
215
+ }
216
+ return `url(${prefixed})`;
217
+ }
218
+ );
219
+ }
220
+ }
221
+
222
+ // prefix begin/end attribute value
223
+ for (const name of ['begin', 'end']) {
224
+ if (
225
+ node.attributes[name] != null &&
226
+ node.attributes[name].length !== 0
227
+ ) {
228
+ const parts = node.attributes[name].split(/\s*;\s+/).map((val) => {
229
+ if (val.endsWith('.end') || val.endsWith('.start')) {
230
+ const [id, postfix] = val.split('.');
231
+ return `${prefixId(prefix, id)}.${postfix}`;
232
+ }
233
+ return val;
234
+ });
235
+ node.attributes[name] = parts.join('; ');
236
+ }
237
+ }
238
+ },
239
+ },
240
+ };
241
+ };