-
Notifications
You must be signed in to change notification settings - Fork 0
/
moveElemsAttrsToGroup.js
130 lines (120 loc) · 3.65 KB
/
moveElemsAttrsToGroup.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
'use strict';
const { visit } = require('../lib/xast.js');
const { inheritableAttrs, pathElems } = require('./_collections.js');
exports.type = 'visitor';
exports.name = 'moveElemsAttrsToGroup';
exports.active = true;
exports.description = 'Move common attributes of group children to the group';
/**
* Move common attributes of group children to the group
*
* @example
* <g attr1="val1">
* <g attr2="val2">
* text
* </g>
* <circle attr2="val2" attr3="val3"/>
* </g>
* ⬇
* <g attr1="val1" attr2="val2">
* <g>
* text
* </g>
* <circle attr3="val3"/>
* </g>
*
* @author Kir Belevich
*
* @type {import('../lib/types').Plugin<void>}
*/
exports.fn = (root) => {
// find if any style element is present
let deoptimizedWithStyles = false;
visit(root, {
element: {
enter: (node) => {
if (node.name === 'style') {
deoptimizedWithStyles = true;
}
},
},
});
return {
element: {
exit: (node) => {
// process only groups with more than 1 children
if (node.name !== 'g' || node.children.length <= 1) {
return;
}
// deoptimize the plugin when style elements are present
// selectors may rely on id, classes or tag names
if (deoptimizedWithStyles) {
return;
}
/**
* find common attributes in group children
* @type {Map<string, string>}
*/
const commonAttributes = new Map();
let initial = true;
let everyChildIsPath = true;
for (const child of node.children) {
if (child.type === 'element') {
if (pathElems.includes(child.name) === false) {
everyChildIsPath = false;
}
if (initial) {
initial = false;
// collect all inheritable attributes from first child element
for (const [name, value] of Object.entries(child.attributes)) {
// consider only inheritable attributes
if (inheritableAttrs.includes(name)) {
commonAttributes.set(name, value);
}
}
} else {
// exclude uncommon attributes from initial list
for (const [name, value] of commonAttributes) {
if (child.attributes[name] !== value) {
commonAttributes.delete(name);
}
}
}
}
}
// preserve transform on children when group has clip-path or mask
if (
node.attributes['clip-path'] != null ||
node.attributes.mask != null
) {
commonAttributes.delete('transform');
}
// preserve transform when all children are paths
// so the transform could be applied to path data by other plugins
if (everyChildIsPath) {
commonAttributes.delete('transform');
}
// add common children attributes to group
for (const [name, value] of commonAttributes) {
if (name === 'transform') {
if (node.attributes.transform != null) {
node.attributes.transform = `${node.attributes.transform} ${value}`;
} else {
node.attributes.transform = value;
}
} else {
node.attributes[name] = value;
}
}
// delete common attributes from children
for (const child of node.children) {
if (child.type === 'element') {
for (const [name] of commonAttributes) {
delete child.attributes[name];
}
}
}
},
},
};
};