or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

ast-manipulation.mdcli.mdconfiguration.mdcore-optimization.mddata-uri.mdindex.mdplugins.mdutility-functions.md

utility-functions.mddocs/

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

```