source: trip-planner-front/node_modules/svgo/plugins/cleanupIDs.js@ 8d391a1

Last change on this file since 8d391a1 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 7.0 KB
RevLine 
[6a3a178]1'use strict';
2
3/**
4 * @typedef {import('../lib/types').XastElement} XastElement
5 */
6
7const { visitSkip } = require('../lib/xast.js');
8const { referencesProps } = require('./_collections.js');
9
10exports.type = 'visitor';
11exports.name = 'cleanupIDs';
12exports.active = true;
13exports.description = 'removes unused IDs and minifies used';
14
15const regReferencesUrl = /\burl\(("|')?#(.+?)\1\)/;
16const regReferencesHref = /^#(.+?)$/;
17const regReferencesBegin = /(\w+)\./;
18const generateIDchars = [
19 'a',
20 'b',
21 'c',
22 'd',
23 'e',
24 'f',
25 'g',
26 'h',
27 'i',
28 'j',
29 'k',
30 'l',
31 'm',
32 'n',
33 'o',
34 'p',
35 'q',
36 'r',
37 's',
38 't',
39 'u',
40 'v',
41 'w',
42 'x',
43 'y',
44 'z',
45 'A',
46 'B',
47 'C',
48 'D',
49 'E',
50 'F',
51 'G',
52 'H',
53 'I',
54 'J',
55 'K',
56 'L',
57 'M',
58 'N',
59 'O',
60 'P',
61 'Q',
62 'R',
63 'S',
64 'T',
65 'U',
66 'V',
67 'W',
68 'X',
69 'Y',
70 'Z',
71];
72const maxIDindex = generateIDchars.length - 1;
73
74/**
75 * Check if an ID starts with any one of a list of strings.
76 *
77 * @type {(string: string, prefixes: Array<string>) => boolean}
78 */
79const hasStringPrefix = (string, prefixes) => {
80 for (const prefix of prefixes) {
81 if (string.startsWith(prefix)) {
82 return true;
83 }
84 }
85 return false;
86};
87
88/**
89 * Generate unique minimal ID.
90 *
91 * @type {(currentID: null | Array<number>) => Array<number>}
92 */
93const generateID = (currentID) => {
94 if (currentID == null) {
95 return [0];
96 }
97 currentID[currentID.length - 1] += 1;
98 for (let i = currentID.length - 1; i > 0; i--) {
99 if (currentID[i] > maxIDindex) {
100 currentID[i] = 0;
101 if (currentID[i - 1] !== undefined) {
102 currentID[i - 1]++;
103 }
104 }
105 }
106 if (currentID[0] > maxIDindex) {
107 currentID[0] = 0;
108 currentID.unshift(0);
109 }
110 return currentID;
111};
112
113/**
114 * Get string from generated ID array.
115 *
116 * @type {(arr: Array<number>, prefix: string) => string}
117 */
118const getIDstring = (arr, prefix) => {
119 return prefix + arr.map((i) => generateIDchars[i]).join('');
120};
121
122/**
123 * Remove unused and minify used IDs
124 * (only if there are no any <style> or <script>).
125 *
126 * @author Kir Belevich
127 *
128 * @type {import('../lib/types').Plugin<{
129 * remove?: boolean,
130 * minify?: boolean,
131 * prefix?: string,
132 * preserve?: Array<string>,
133 * preservePrefixes?: Array<string>,
134 * force?: boolean,
135 * }>}
136 */
137exports.fn = (_root, params) => {
138 const {
139 remove = true,
140 minify = true,
141 prefix = '',
142 preserve = [],
143 preservePrefixes = [],
144 force = false,
145 } = params;
146 const preserveIDs = new Set(
147 Array.isArray(preserve) ? preserve : preserve ? [preserve] : []
148 );
149 const preserveIDPrefixes = Array.isArray(preservePrefixes)
150 ? preservePrefixes
151 : preservePrefixes
152 ? [preservePrefixes]
153 : [];
154 /**
155 * @type {Map<string, XastElement>}
156 */
157 const nodeById = new Map();
158 /**
159 * @type {Map<string, Array<{element: XastElement, name: string, value: string }>>}
160 */
161 const referencesById = new Map();
162 let deoptimized = false;
163
164 return {
165 element: {
166 enter: (node) => {
167 if (force == false) {
168 // deoptimize if style or script elements are present
169 if (
170 (node.name === 'style' || node.name === 'script') &&
171 node.children.length !== 0
172 ) {
173 deoptimized = true;
174 return;
175 }
176
177 // avoid removing IDs if the whole SVG consists only of defs
178 if (node.name === 'svg') {
179 let hasDefsOnly = true;
180 for (const child of node.children) {
181 if (child.type !== 'element' || child.name !== 'defs') {
182 hasDefsOnly = false;
183 break;
184 }
185 }
186 if (hasDefsOnly) {
187 return visitSkip;
188 }
189 }
190 }
191
192 for (const [name, value] of Object.entries(node.attributes)) {
193 if (name === 'id') {
194 // collect all ids
195 const id = value;
196 if (nodeById.has(id)) {
197 delete node.attributes.id; // remove repeated id
198 } else {
199 nodeById.set(id, node);
200 }
201 } else {
202 // collect all references
203 /**
204 * @type {null | string}
205 */
206 let id = null;
207 if (referencesProps.includes(name)) {
208 const match = value.match(regReferencesUrl);
209 if (match != null) {
210 id = match[2]; // url() reference
211 }
212 }
213 if (name === 'href' || name.endsWith(':href')) {
214 const match = value.match(regReferencesHref);
215 if (match != null) {
216 id = match[1]; // href reference
217 }
218 }
219 if (name === 'begin') {
220 const match = value.match(regReferencesBegin);
221 if (match != null) {
222 id = match[1]; // href reference
223 }
224 }
225 if (id != null) {
226 let refs = referencesById.get(id);
227 if (refs == null) {
228 refs = [];
229 referencesById.set(id, refs);
230 }
231 refs.push({ element: node, name, value });
232 }
233 }
234 }
235 },
236 },
237
238 root: {
239 exit: () => {
240 if (deoptimized) {
241 return;
242 }
243 /**
244 * @type {(id: string) => boolean}
245 **/
246 const isIdPreserved = (id) =>
247 preserveIDs.has(id) || hasStringPrefix(id, preserveIDPrefixes);
248 /**
249 * @type {null | Array<number>}
250 */
251 let currentID = null;
252 for (const [id, refs] of referencesById) {
253 const node = nodeById.get(id);
254 if (node != null) {
255 // replace referenced IDs with the minified ones
256 if (minify && isIdPreserved(id) === false) {
257 /**
258 * @type {null | string}
259 */
260 let currentIDString = null;
261 do {
262 currentID = generateID(currentID);
263 currentIDString = getIDstring(currentID, prefix);
264 } while (isIdPreserved(currentIDString));
265 node.attributes.id = currentIDString;
266 for (const { element, name, value } of refs) {
267 if (value.includes('#')) {
268 // replace id in href and url()
269 element.attributes[name] = value.replace(
270 `#${id}`,
271 `#${currentIDString}`
272 );
273 } else {
274 // replace id in begin attribute
275 element.attributes[name] = value.replace(
276 `${id}.`,
277 `${currentIDString}.`
278 );
279 }
280 }
281 }
282 // keep referenced node
283 nodeById.delete(id);
284 }
285 }
286 // remove non-referenced IDs attributes from elements
287 if (remove) {
288 for (const [id, node] of nodeById) {
289 if (isIdPreserved(id) === false) {
290 delete node.attributes.id;
291 }
292 }
293 }
294 },
295 },
296 };
297};
Note: See TracBrowser for help on using the repository browser.