0
# Utility Functions
1
2
Advanced utility functions for security checking, reference detection, and numeric precision handling. These functions are commonly used in custom plugins and advanced SVG processing workflows.
3
4
## Capabilities
5
6
### Security Utilities
7
8
Functions for detecting potentially unsafe SVG content.
9
10
```javascript { .api }
11
/**
12
* Check if element contains scripts or event handlers
13
* @param node - Element to check for script content
14
* @returns True if element contains scripts or event handlers
15
*/
16
function hasScripts(node: XastElement): boolean;
17
```
18
19
**Usage Examples:**
20
21
```javascript
22
import { hasScripts } from "svgo";
23
24
// Custom plugin that avoids processing elements with scripts
25
const safeOptimizationPlugin = {
26
name: 'safeOptimization',
27
fn: (root) => {
28
return {
29
element: {
30
enter(node, parent) {
31
// Skip optimization for elements with scripts or event handlers
32
if (hasScripts(node)) {
33
console.log(`Skipping ${node.name} - contains scripts`);
34
return visitSkip;
35
}
36
37
// Example: Safe to optimize this element
38
if (node.name === 'g' && Object.keys(node.attributes).length === 0) {
39
// Can safely flatten empty groups without scripts
40
node.children.forEach(child => {
41
parent.children.push(child);
42
});
43
detachNodeFromParent(node, parent);
44
}
45
}
46
}
47
};
48
}
49
};
50
51
// Example usage in element analysis
52
const analyzeScripts = (node) => {
53
if (hasScripts(node)) {
54
console.log('Found unsafe element:', {
55
name: node.name,
56
hasScriptTag: node.name === 'script',
57
hasJavascriptLinks: node.name === 'a' &&
58
Object.entries(node.attributes).some(([key, val]) =>
59
key.includes('href') && val?.startsWith('javascript:')),
60
hasEventHandlers: Object.keys(node.attributes).some(attr =>
61
attr.startsWith('on') || attr.includes('event'))
62
});
63
}
64
};
65
```
66
67
### Reference Detection
68
69
Functions for detecting and extracting SVG element references.
70
71
```javascript { .api }
72
/**
73
* Check if string contains URL references like url(#id)
74
* @param body - String content to check
75
* @returns True if string contains URL references
76
*/
77
function includesUrlReference(body: string): boolean;
78
79
/**
80
* Extract reference IDs from attribute values
81
* @param attribute - Attribute name to check
82
* @param value - Attribute value to search
83
* @returns Array of found reference IDs
84
*/
85
function findReferences(attribute: string, value: string): string[];
86
```
87
88
**Usage Examples:**
89
90
```javascript
91
import { includesUrlReference, findReferences } from "svgo";
92
93
// Custom plugin that tracks SVG references
94
const referenceTrackerPlugin = {
95
name: 'referenceTracker',
96
fn: (root) => {
97
const references = new Set();
98
const definitions = new Set();
99
100
return {
101
element: {
102
enter(node) {
103
// Track defined IDs
104
if (node.attributes.id) {
105
definitions.add(node.attributes.id);
106
}
107
108
// Find references in attributes
109
Object.entries(node.attributes).forEach(([attr, value]) => {
110
// Quick check for URL references
111
if (includesUrlReference(value)) {
112
console.log(`Found URL reference in ${attr}:`, value);
113
}
114
115
// Extract specific reference IDs
116
const refs = findReferences(attr, value);
117
refs.forEach(ref => references.add(ref));
118
});
119
}
120
},
121
root: {
122
exit() {
123
const unusedDefs = [...definitions].filter(id => !references.has(id));
124
const brokenRefs = [...references].filter(id => !definitions.has(id));
125
126
console.log('Reference Analysis:', {
127
definitions: definitions.size,
128
references: references.size,
129
unusedDefinitions: unusedDefs,
130
brokenReferences: brokenRefs
131
});
132
}
133
}
134
};
135
}
136
};
137
138
// Example: Check various attribute types
139
const checkElementReferences = (node) => {
140
Object.entries(node.attributes).forEach(([attr, value]) => {
141
const refs = findReferences(attr, value);
142
143
if (refs.length > 0) {
144
console.log(`${attr}="${value}" references:`, refs);
145
}
146
147
// Examples of what gets detected:
148
// fill="url(#gradient1)" → ['gradient1']
149
// href="#symbol1" → ['symbol1']
150
// begin="rect1.click" → ['rect1']
151
// style="fill: url('#pattern1')" → ['pattern1']
152
});
153
};
154
155
// Example: Style processing with reference awareness
156
const processStyles = (styleValue) => {
157
if (includesUrlReference(styleValue)) {
158
console.log('Style contains references, preserving:', styleValue);
159
return styleValue; // Don't modify styles with references
160
}
161
162
// Safe to process style without references
163
return optimizeStyleValue(styleValue);
164
};
165
```
166
167
### Numeric Precision
168
169
Utility for consistent numeric precision handling.
170
171
```javascript { .api }
172
/**
173
* Round number to specified precision without string conversion
174
* @param num - Number to round
175
* @param precision - Number of decimal places
176
* @returns Rounded number (not string like Number.prototype.toFixed)
177
*/
178
function toFixed(num: number, precision: number): number;
179
```
180
181
**Usage Examples:**
182
183
```javascript
184
import { toFixed } from "svgo";
185
186
// Custom plugin that normalizes numeric values
187
const numericNormalizationPlugin = {
188
name: 'numericNormalization',
189
fn: (root, params) => {
190
const precision = params.precision || 3;
191
192
return {
193
element: {
194
enter(node) {
195
// Normalize coordinate attributes
196
['x', 'y', 'width', 'height', 'cx', 'cy', 'r', 'rx', 'ry'].forEach(attr => {
197
if (node.attributes[attr]) {
198
const num = parseFloat(node.attributes[attr]);
199
if (!isNaN(num)) {
200
node.attributes[attr] = toFixed(num, precision).toString();
201
}
202
}
203
});
204
205
// Normalize transform values
206
if (node.attributes.transform) {
207
node.attributes.transform = normalizeTransform(
208
node.attributes.transform,
209
precision
210
);
211
}
212
213
// Normalize path data
214
if (node.name === 'path' && node.attributes.d) {
215
node.attributes.d = normalizePathData(
216
node.attributes.d,
217
precision
218
);
219
}
220
}
221
}
222
};
223
}
224
};
225
226
// Example: Transform matrix normalization
227
const normalizeTransform = (transform, precision) => {
228
return transform.replace(/[\d.]+/g, (match) => {
229
const num = parseFloat(match);
230
return toFixed(num, precision).toString();
231
});
232
};
233
234
// Example: Path data normalization
235
const normalizePathData = (pathData, precision) => {
236
return pathData.replace(/[\d.-]+/g, (match) => {
237
const num = parseFloat(match);
238
return toFixed(num, precision).toString();
239
});
240
};
241
242
// Example: Consistent numeric operations
243
const calculateBounds = (elements, precision = 2) => {
244
let minX = Infinity, minY = Infinity;
245
let maxX = -Infinity, maxY = -Infinity;
246
247
elements.forEach(el => {
248
const x = parseFloat(el.attributes.x || 0);
249
const y = parseFloat(el.attributes.y || 0);
250
const width = parseFloat(el.attributes.width || 0);
251
const height = parseFloat(el.attributes.height || 0);
252
253
minX = Math.min(minX, x);
254
minY = Math.min(minY, y);
255
maxX = Math.max(maxX, x + width);
256
maxY = Math.max(maxY, y + height);
257
});
258
259
return {
260
x: toFixed(minX, precision),
261
y: toFixed(minY, precision),
262
width: toFixed(maxX - minX, precision),
263
height: toFixed(maxY - minY, precision)
264
};
265
};
266
267
// Example: Comparing with Number.prototype.toFixed
268
const compareToFixed = (num, precision) => {
269
const svgoResult = toFixed(num, precision); // Returns number
270
const nativeResult = Number(num.toFixed(precision)); // String → number
271
272
console.log('Input:', num);
273
console.log('SVGO toFixed:', svgoResult, typeof svgoResult);
274
console.log('Native toFixed:', nativeResult, typeof nativeResult);
275
console.log('Equal:', svgoResult === nativeResult);
276
};
277
278
// Usage in plugin parameter processing
279
const processPluginParams = (params, precision) => {
280
const processed = {};
281
282
Object.entries(params).forEach(([key, value]) => {
283
if (typeof value === 'number') {
284
processed[key] = toFixed(value, precision);
285
} else {
286
processed[key] = value;
287
}
288
});
289
290
return processed;
291
};
292
```
293
294
### Advanced Utility Combinations
295
296
Examples combining multiple utility functions for complex processing.
297
298
```javascript
299
// Complex plugin using all utility functions
300
const comprehensivePlugin = {
301
name: 'comprehensive',
302
fn: (root, params) => {
303
const precision = params.precision || 2;
304
const preserveReferences = params.preserveReferences !== false;
305
const skipScripts = params.skipScripts !== false;
306
307
const referenceIds = new Set();
308
const definedIds = new Set();
309
310
return {
311
element: {
312
enter(node, parent) {
313
// Security check
314
if (skipScripts && hasScripts(node)) {
315
console.log(`Skipping ${node.name} - contains scripts`);
316
return visitSkip;
317
}
318
319
// Track ID definitions
320
if (node.attributes.id) {
321
definedIds.add(node.attributes.id);
322
}
323
324
// Process attributes
325
Object.entries(node.attributes).forEach(([attr, value]) => {
326
// Track references if preservation is enabled
327
if (preserveReferences) {
328
const refs = findReferences(attr, value);
329
refs.forEach(ref => referenceIds.add(ref));
330
331
if (includesUrlReference(value)) {
332
return; // Skip processing attributes with references
333
}
334
}
335
336
// Normalize numeric attributes
337
if (['x', 'y', 'width', 'height', 'r', 'cx', 'cy'].includes(attr)) {
338
const num = parseFloat(value);
339
if (!isNaN(num)) {
340
node.attributes[attr] = toFixed(num, precision).toString();
341
}
342
}
343
});
344
}
345
},
346
root: {
347
exit() {
348
if (preserveReferences) {
349
const unusedIds = [...definedIds].filter(id => !referenceIds.has(id));
350
if (unusedIds.length > 0) {
351
console.log('Unused IDs found:', unusedIds);
352
}
353
}
354
}
355
}
356
};
357
}
358
};
359
```