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

ast-manipulation.mddocs/

0

# AST Manipulation

1

2

XML Abstract Syntax Tree manipulation utilities for querying, modifying, and traversing SVG document structures with CSS selector support.

3

4

**Note**: The main AST functions (`querySelector`, `querySelectorAll`, `mapNodesToParents`) are exported from the main SVGO entry point. Additional functions like `matches` and `detachNodeFromParent` are available from the internal xast module and are primarily used within custom plugins.

5

6

## Capabilities

7

8

### CSS Selector Queries

9

10

Query SVG elements using CSS selectors.

11

12

```javascript { .api }

13

/**

14

* Query single element using CSS selector

15

* @param node - Parent element to query within

16

* @param selector - CSS selector string

17

* @param parents - Optional parent mapping for context

18

* @returns First matching child element or null

19

*/

20

function querySelector(

21

node: XastParent,

22

selector: string,

23

parents?: Map<XastNode, XastParent>

24

): XastChild | null;

25

26

/**

27

* Query multiple elements using CSS selector

28

* @param node - Parent element to query within

29

* @param selector - CSS selector string

30

* @param parents - Optional parent mapping for context

31

* @returns Array of all matching child elements

32

*/

33

function querySelectorAll(

34

node: XastParent,

35

selector: string,

36

parents?: Map<XastNode, XastParent>

37

): XastChild[];

38

39

/**

40

* Check if element matches CSS selector

41

* @param node - Element to test

42

* @param selector - CSS selector string

43

* @param parents - Optional parent mapping for context

44

* @returns True if element matches selector

45

*/

46

function matches(

47

node: XastElement,

48

selector: string,

49

parents?: Map<XastNode, XastParent>

50

): boolean;

51

```

52

53

**Usage Examples:**

54

55

```javascript

56

import { optimize, querySelector, querySelectorAll, matches } from "svgo";

57

58

// First, get the AST by parsing SVG (typically done inside plugins)

59

// This example shows how you might use these functions in a custom plugin

60

61

const customPlugin = {

62

name: 'examplePlugin',

63

fn: (root) => {

64

return {

65

element: {

66

enter(node, parent) {

67

// Find first rect element

68

const firstRect = querySelector(root, 'rect');

69

if (firstRect) {

70

console.log('Found rect:', firstRect.attributes);

71

}

72

73

// Find all circle elements

74

const circles = querySelectorAll(root, 'circle');

75

console.log('Found circles:', circles.length);

76

77

// Find elements with specific attributes

78

const redElements = querySelectorAll(root, '[fill="red"]');

79

const classElements = querySelectorAll(root, '.my-class');

80

const idElements = querySelectorAll(root, '#my-id');

81

82

// Complex selectors

83

const pathsInGroups = querySelectorAll(root, 'g path');

84

const directChildren = querySelectorAll(root, 'svg > rect');

85

86

// Check if current element matches selector

87

if (matches(node, 'rect[width="100"]')) {

88

console.log('Found 100-width rectangle');

89

}

90

91

// Use parent mapping for context-aware queries

92

const parents = new Map();

93

// ... populate parents map

94

const contextualQuery = querySelector(root, 'use', parents);

95

}

96

}

97

};

98

}

99

};

100

```

101

102

### CSS Selector Support

103

104

Supported CSS selector syntax for SVG element queries.

105

106

**Basic Selectors:**

107

108

```javascript

109

// Element selectors

110

querySelector(root, 'rect') // All <rect> elements

111

querySelector(root, 'g') // All <g> elements

112

querySelector(root, 'path') // All <path> elements

113

114

// ID selectors

115

querySelector(root, '#my-id') // Element with id="my-id"

116

117

// Class selectors

118

querySelector(root, '.my-class') // Elements with class="my-class"

119

120

// Attribute selectors

121

querySelector(root, '[fill]') // Elements with fill attribute

122

querySelector(root, '[fill="red"]') // Elements with fill="red"

123

querySelector(root, '[width="100"]') // Elements with width="100"

124

querySelector(root, '[data-icon]') // Elements with data-icon attribute

125

```

126

127

**Combinators:**

128

129

```javascript

130

// Descendant combinator (space)

131

querySelectorAll(root, 'g rect') // <rect> inside any <g>

132

querySelectorAll(root, 'svg path') // <path> inside any <svg>

133

134

// Child combinator (>)

135

querySelectorAll(root, 'svg > g') // <g> directly inside <svg>

136

querySelectorAll(root, 'g > rect') // <rect> directly inside <g>

137

138

// Multiple selectors (comma)

139

querySelectorAll(root, 'rect, circle') // All <rect> and <circle> elements

140

```

141

142

**Attribute Selector Variants:**

143

144

```javascript

145

// Exact match

146

querySelector(root, '[fill="blue"]') // fill exactly equals "blue"

147

148

// Contains substring

149

querySelector(root, '[class*="icon"]') // class contains "icon"

150

151

// Starts with

152

querySelector(root, '[id^="prefix"]') // id starts with "prefix"

153

154

// Ends with

155

querySelector(root, '[class$="suffix"]') // class ends with "suffix"

156

157

// Space-separated list contains

158

querySelector(root, '[class~="active"]') // class list contains "active"

159

```

160

161

### Node Manipulation

162

163

Utilities for manipulating AST nodes.

164

165

```javascript { .api }

166

/**

167

* Remove node from its parent

168

* @param node - Child node to remove

169

* @param parentNode - Parent node containing the child

170

*/

171

function detachNodeFromParent(node: XastChild, parentNode: XastParent): void;

172

```

173

174

### AST Traversal

175

176

Advanced traversal utilities for walking through AST nodes with visitor patterns.

177

178

```javascript { .api }

179

/**

180

* Traverse AST nodes with visitor pattern

181

* @param node - Starting node for traversal

182

* @param visitor - Visitor object with enter/exit callbacks

183

* @param parentNode - Parent of the starting node

184

*/

185

function visit(node: XastNode, visitor: Visitor, parentNode?: XastParent | null): void;

186

187

/**

188

* Symbol to skip traversing children of current node

189

* Return this from visitor enter callback to skip subtree

190

*/

191

const visitSkip: symbol;

192

193

interface Visitor {

194

doctype?: VisitorNode<XastDoctype>;

195

instruction?: VisitorNode<XastInstruction>;

196

comment?: VisitorNode<XastComment>;

197

cdata?: VisitorNode<XastCdata>;

198

text?: VisitorNode<XastText>;

199

element?: VisitorNode<XastElement>;

200

root?: VisitorRoot;

201

}

202

203

interface VisitorNode<Node> {

204

enter?: (node: Node, parentNode: XastParent) => void | symbol;

205

exit?: (node: Node, parentNode: XastParent) => void;

206

}

207

208

interface VisitorRoot {

209

enter?: (node: XastRoot, parentNode: null) => void;

210

exit?: (node: XastRoot, parentNode: null) => void;

211

}

212

```

213

214

**Usage Examples:**

215

216

```javascript

217

import { detachNodeFromParent } from "svgo";

218

219

// Custom plugin that removes elements

220

const removeElementsPlugin = {

221

name: 'removeElements',

222

fn: (root, params) => {

223

return {

224

element: {

225

enter(node, parent) {

226

// Remove elements matching criteria

227

if (params.selectors && params.selectors.some(sel => matches(node, sel))) {

228

detachNodeFromParent(node, parent);

229

return; // Skip processing children of removed node

230

}

231

232

// Remove elements by tag name

233

if (params.tagNames && params.tagNames.includes(node.name)) {

234

detachNodeFromParent(node, parent);

235

return;

236

}

237

238

// Remove empty text nodes

239

if (node.type === 'text' && !node.value.trim()) {

240

detachNodeFromParent(node, parent);

241

}

242

}

243

}

244

};

245

},

246

params: {

247

selectors: ['.remove-me', '[data-temp]'],

248

tagNames: ['title', 'desc']

249

}

250

};

251

```

252

253

**Traversal Examples:**

254

255

```javascript

256

import { visit, visitSkip } from "svgo";

257

258

// Custom plugin using AST traversal

259

const customTraversalPlugin = {

260

name: 'customTraversal',

261

fn: (root) => {

262

let elementCount = 0;

263

let pathCount = 0;

264

265

// Use visitor pattern to traverse the AST

266

visit(root, {

267

element: {

268

enter(node, parent) {

269

elementCount++;

270

271

if (node.name === 'path') {

272

pathCount++;

273

274

// Example: Skip processing children of path elements

275

if (node.children.length > 0) {

276

console.log('Skipping path children');

277

return visitSkip;

278

}

279

}

280

281

// Example: Remove elements with specific attributes

282

if (node.attributes['data-remove']) {

283

detachNodeFromParent(node, parent);

284

return visitSkip; // Skip children of removed node

285

}

286

},

287

exit(node, parent) {

288

// Called after visiting all children

289

if (node.name === 'g' && node.children.length === 0) {

290

console.log('Found empty group');

291

}

292

}

293

},

294

text: {

295

enter(node, parent) {

296

// Process text nodes

297

if (node.value.trim() === '') {

298

detachNodeFromParent(node, parent);

299

}

300

}

301

},

302

root: {

303

exit() {

304

console.log(`Processed ${elementCount} elements, ${pathCount} paths`);

305

}

306

}

307

});

308

309

// Return null since we handled traversal manually

310

return null;

311

}

312

};

313

314

// Advanced traversal example with conditional processing

315

const conditionalTraversalPlugin = {

316

name: 'conditionalTraversal',

317

fn: (root) => {

318

const processedNodes = new Set();

319

320

visit(root, {

321

element: {

322

enter(node, parent) {

323

// Skip already processed nodes

324

if (processedNodes.has(node)) {

325

return visitSkip;

326

}

327

328

processedNodes.add(node);

329

330

// Example: Only process elements with transforms

331

if (!node.attributes.transform) {

332

return visitSkip;

333

}

334

335

console.log(`Processing transformed ${node.name}:`, node.attributes.transform);

336

337

// Complex conditional logic

338

if (node.name === 'use' && node.attributes.href) {

339

// Skip use elements with external references

340

return visitSkip;

341

}

342

}

343

}

344

});

345

346

return null;

347

}

348

};

349

```

350

351

### Parent Mapping

352

353

Create and use parent mappings for context-aware operations.

354

355

```javascript { .api }

356

/**

357

* Create mapping of nodes to their parent nodes

358

* @param node - Root node to start mapping from

359

* @returns Map of each node to its parent

360

*/

361

function mapNodesToParents(node: XastNode): Map<XastNode, XastParent>;

362

```

363

364

**Usage Examples:**

365

366

```javascript

367

import { mapNodesToParents, querySelector } from "svgo";

368

369

const parentMappingPlugin = {

370

name: 'parentMapping',

371

fn: (root) => {

372

// Create parent mapping for entire tree

373

const parents = mapNodesToParents(root);

374

375

return {

376

element: {

377

enter(node) {

378

// Get parent of current node

379

const parent = parents.get(node);

380

if (parent && parent.type === 'element') {

381

console.log(`${node.name} is inside ${parent.name}`);

382

}

383

384

// Use parent mapping with selectors for better context

385

const contextualQuery = querySelector(node, 'use', parents);

386

387

// Find all ancestors of current node

388

let ancestor = parents.get(node);

389

const ancestorChain = [];

390

while (ancestor && ancestor.type !== 'root') {

391

ancestorChain.push(ancestor);

392

ancestor = parents.get(ancestor);

393

}

394

395

if (ancestorChain.length > 0) {

396

console.log('Ancestor chain:', ancestorChain.map(a => a.name || a.type));

397

}

398

}

399

}

400

};

401

}

402

};

403

```

404

405

### AST Node Types

406

407

Understanding the structure of AST nodes for manipulation.

408

409

```javascript { .api }

410

// Root node (document root)

411

interface XastRoot {

412

type: 'root';

413

children: XastChild[];

414

}

415

416

// Element nodes (SVG tags)

417

interface XastElement {

418

type: 'element';

419

name: string; // Tag name (e.g., 'rect', 'g', 'path')

420

attributes: Record<string, string>; // All attributes as key-value pairs

421

children: XastChild[]; // Child nodes

422

}

423

424

// Text content

425

interface XastText {

426

type: 'text';

427

value: string; // Text content

428

}

429

430

// XML comments

431

interface XastComment {

432

type: 'comment';

433

value: string; // Comment content

434

}

435

436

// CDATA sections

437

interface XastCdata {

438

type: 'cdata';

439

value: string; // CDATA content

440

}

441

442

// Processing instructions

443

interface XastInstruction {

444

type: 'instruction';

445

name: string; // Instruction name

446

value: string; // Instruction value

447

}

448

449

// DOCTYPE declarations

450

interface XastDoctype {

451

type: 'doctype';

452

name: string; // Doctype name

453

data: {

454

doctype: string; // Doctype content

455

};

456

}

457

458

// Union types

459

type XastChild = XastElement | XastText | XastComment | XastCdata | XastInstruction | XastDoctype;

460

type XastParent = XastRoot | XastElement;

461

type XastNode = XastRoot | XastChild;

462

```

463

464

### Advanced AST Manipulation

465

466

Complex node manipulation examples.

467

468

```javascript

469

// Plugin that restructures SVG elements

470

const restructurePlugin = {

471

name: 'restructure',

472

fn: (root) => {

473

const parents = mapNodesToParents(root);

474

475

return {

476

element: {

477

enter(node, parent) {

478

// Move all paths to a dedicated group

479

if (node.name === 'path' && parent.type === 'element' && parent.name !== 'g') {

480

// Create new group if it doesn't exist

481

let pathGroup = querySelector(root, 'g[data-paths="true"]');

482

if (!pathGroup) {

483

pathGroup = {

484

type: 'element',

485

name: 'g',

486

attributes: { 'data-paths': 'true' },

487

children: []

488

};

489

// Add to root SVG

490

const svgElement = querySelector(root, 'svg');

491

if (svgElement) {

492

svgElement.children.push(pathGroup);

493

}

494

}

495

496

// Move path to group

497

detachNodeFromParent(node, parent);

498

pathGroup.children.push(node);

499

}

500

501

// Flatten unnecessary groups

502

if (node.name === 'g' && Object.keys(node.attributes).length === 0) {

503

// Move all children up one level

504

const children = [...node.children];

505

children.forEach(child => {

506

parent.children.push(child);

507

});

508

509

// Remove empty group

510

detachNodeFromParent(node, parent);

511

}

512

}

513

}

514

};

515

}

516

};

517

518

// Plugin that analyzes SVG structure

519

const analyzePlugin = {

520

name: 'analyze',

521

fn: (root) => {

522

const stats = {

523

elements: 0,

524

paths: 0,

525

groups: 0,

526

transforms: 0,

527

ids: new Set(),

528

classes: new Set()

529

};

530

531

return {

532

element: {

533

enter(node) {

534

stats.elements++;

535

536

if (node.name === 'path') stats.paths++;

537

if (node.name === 'g') stats.groups++;

538

if (node.attributes.transform) stats.transforms++;

539

if (node.attributes.id) stats.ids.add(node.attributes.id);

540

if (node.attributes.class) {

541

node.attributes.class.split(/\s+/).forEach(cls => stats.classes.add(cls));

542

}

543

}

544

},

545

root: {

546

exit() {

547

console.log('SVG Analysis:', {

548

...stats,

549

ids: Array.from(stats.ids),

550

classes: Array.from(stats.classes)

551

});

552

}

553

}

554

};

555

}

556

};

557

```