-
Notifications
You must be signed in to change notification settings - Fork 0
/
sortAttrs.js
113 lines (108 loc) · 2.66 KB
/
sortAttrs.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
'use strict';
exports.type = 'visitor';
exports.name = 'sortAttrs';
exports.active = false;
exports.description = 'Sort element attributes for better compression';
/**
* Sort element attributes for better compression
*
* @author Nikolay Frantsev
*
* @type {import('../lib/types').Plugin<{
* order?: Array<string>
* xmlnsOrder?: 'front' | 'alphabetical'
* }>}
*/
exports.fn = (_root, params) => {
const {
order = [
'id',
'width',
'height',
'x',
'x1',
'x2',
'y',
'y1',
'y2',
'cx',
'cy',
'r',
'fill',
'stroke',
'marker',
'd',
'points',
],
xmlnsOrder = 'front',
} = params;
/**
* @type {(name: string) => number}
*/
const getNsPriority = (name) => {
if (xmlnsOrder === 'front') {
// put xmlns first
if (name === 'xmlns') {
return 3;
}
// xmlns:* attributes second
if (name.startsWith('xmlns:')) {
return 2;
}
}
// other namespaces after and sort them alphabetically
if (name.includes(':')) {
return 1;
}
// other attributes
return 0;
};
/**
* @type {(a: [string, string], b: [string, string]) => number}
*/
const compareAttrs = ([aName], [bName]) => {
// sort namespaces
const aPriority = getNsPriority(aName);
const bPriority = getNsPriority(bName);
const priorityNs = bPriority - aPriority;
if (priorityNs !== 0) {
return priorityNs;
}
// extract the first part from attributes
// for example "fill" from "fill" and "fill-opacity"
const [aPart] = aName.split('-');
const [bPart] = bName.split('-');
// rely on alphabetical sort when the first part is the same
if (aPart !== bPart) {
const aInOrderFlag = order.includes(aPart) ? 1 : 0;
const bInOrderFlag = order.includes(bPart) ? 1 : 0;
// sort by position in order param
if (aInOrderFlag === 1 && bInOrderFlag === 1) {
return order.indexOf(aPart) - order.indexOf(bPart);
}
// put attributes from order param before others
const priorityOrder = bInOrderFlag - aInOrderFlag;
if (priorityOrder !== 0) {
return priorityOrder;
}
}
// sort alphabetically
return aName < bName ? -1 : 1;
};
return {
element: {
enter: (node) => {
const attrs = Object.entries(node.attributes);
attrs.sort(compareAttrs);
/**
* @type {Record<string, string>}
*/
const sortedAttributes = {};
for (const [name, value] of attrs) {
sortedAttributes[name] = value;
}
node.attributes = sortedAttributes;
},
},
};
};