Rewriting

The Selection API

The Selection API provides a simple interface for declarative template mutations. Instead of implementing a full visitor, you describe what to find and what to change.

#Getting a Selection

Selections are created inside the $doc->rewrite() callback, which provides a RewriteBuilder. The builder offers several methods for selecting nodes:

1<?php
2
3use Forte\Rewriting\RewriteBuilder;
4
5$newDoc = $doc->rewrite(function (RewriteBuilder $builder) {
6 $builder->findAll('div')->addClass('container');
7});

The RewriteBuilder provides these selection methods:

1<?php
2
3$builder->find('div'); // Selection (first matching element)
4$builder->findAll('div'); // Selection (all matching elements)
5$builder->xpath('//div[@class]'); // Selection (first XPath match)
6$builder->xpathAll('//div[@class]'); // Selection (all XPath matches)
7$builder->select($node); // Selection (wrap a known node)
8$builder->selectAll([$nodeA, $nodeB]); // Selection (wrap multiple nodes)

#Mutation Methods

All mutation methods return self for chaining. Attribute and class methods operate on element nodes within the selection:

1<?php
2
3use Forte\Facades\Forte;
4use Forte\Rewriting\RewriteBuilder;
5
6$doc = Forte::parse('<div>one</div><div>two</div>');
7
8$newDoc = $doc->rewrite(function (RewriteBuilder $builder) {
9 $builder->findAll('div')->addClass('container');
10});
11
12// '<div class="container">one</div><div class="container">two</div>'
13$newDoc->render();

Setting and removing attributes:

1<?php
2
3use Forte\Facades\Forte;
4use Forte\Rewriting\RewriteBuilder;
5
6$doc = Forte::parse('<img src="photo.jpg">');
7
8$newDoc = $doc->rewrite(function (RewriteBuilder $builder) {
9 $builder->find('img')->setAttribute('loading', 'lazy');
10});
11
12// '<img src="photo.jpg" loading="lazy">'
13$newDoc->render();

Removing selected nodes from the document:

1<?php
2
3use Forte\Facades\Forte;
4use Forte\Rewriting\RewriteBuilder;
5
6$doc = Forte::parse('<div>keep</div><span>remove</span>');
7
8$newDoc = $doc->rewrite(function (RewriteBuilder $builder) {
9 $builder->findAll('span')->remove();
10});
11
12// '<div>keep</div>'
13$newDoc->render();

Replacing nodes with new content:

1<?php
2
3use Forte\Facades\Forte;
4use Forte\Rewriting\RewriteBuilder;
5
6$doc = Forte::parse('<div class="old">legacy</div>');
7
8$newDoc = $doc->rewrite(function (RewriteBuilder $builder) {
9 $builder->find('div')->replaceWith('<section class="new">modern</section>');
10});
11
12// '<section class="new">modern</section>'
13$newDoc->render();

Wrapping nodes in a parent element. The wrapWith method accepts a tag name with an optional dot-separated class shorthand (e.g., 'div.wrapper'), plus an optional attributes array:

1<?php
2
3use Forte\Facades\Forte;
4use Forte\Rewriting\RewriteBuilder;
5
6$doc = Forte::parse('<img src="a.jpg"><img src="b.jpg">');
7
8$newDoc = $doc->rewrite(function (RewriteBuilder $builder) {
9 $builder->findAll('img')->wrapWith('figure.media');
10});
11
12// '<figure class="media"><img src="a.jpg"></figure><figure class="media"><img src="b.jpg"></figure>'
13$newDoc->render();

Injecting content before or after selected nodes:

1<?php
2
3$builder->find('footer')->insertBefore('<!-- start footer -->');
4$builder->find('footer')->insertAfter('<!-- end footer -->');

The full set of mutation methods on Selection:

1<?php
2
3$selection->addClass('name'); // add a CSS class
4$selection->removeClass('name'); // remove a CSS class
5$selection->setAttribute('key', 'value'); // set an attribute
6$selection->removeAttribute('key'); // remove an attribute
7$selection->remove(); // delete selected nodes
8$selection->replaceWith('<new/>'); // replace with HTML/Blade content
9$selection->wrapWith('div.classname'); // wrap in a parent element
10$selection->insertBefore('<!-- ... -->'); // inject content before
11$selection->insertAfter('<!-- ... -->'); // inject content after

#Inspection Methods

Selections provide methods for examining their contents without mutating anything:

1<?php
2
3$selection->first(); // ?Node (first selected node)
4$selection->last(); // ?Node (last selected node)
5$selection->count(); // int (number of selected nodes)
6$selection->isEmpty(); // bool (true if nothing selected)
7$selection->isNotEmpty(); // bool (true if at least one node)
8$selection->all(); // array<Node> (all selected nodes)

#Filtering and Iteration

Use filter to narrow a selection with a predicate, and each to iterate with side effects. Both return a Selection for continued chaining:

1<?php
2
3$builder->findAll('div')
4 ->filter(fn ($node) => $node->hasAttribute('data-legacy'))
5 ->removeClass('modern')
6 ->addClass('legacy');
7
8$builder->findAll('img')
9 ->each(function ($node) {
10 // inspect each node
11 });

#Chaining

Multiple operations can be chained on the same selection. Each mutation method returns self, so you can compose transforms fluently:

1<?php
2
3use Forte\Facades\Forte;
4use Forte\Rewriting\RewriteBuilder;
5
6$doc = Forte::parse('<img src="hero.jpg">');
7
8$newDoc = $doc->rewrite(function (RewriteBuilder $builder) {
9 $builder->find('img')
10 ->setAttribute('loading', 'lazy')
11 ->addClass('responsive');
12});
13
14// '<img src="hero.jpg" class="responsive" loading="lazy">'
15$newDoc->render();

You can also apply different mutations to different selections within the same rewrite callback:

1<?php
2
3$newDoc = $doc->rewrite(function (RewriteBuilder $builder) {
4 $builder->findAll('div')->addClass('container');
5 $builder->findAll('img')->setAttribute('loading', 'lazy');
6 $builder->findAll('span')
7 ->filter(fn ($node) => $node->hasClass('deprecated'))
8 ->remove();
9});

#When to Use Selection vs. Visitor

The Selection API is best for straightforward, declarative transforms such as adding classes, setting attributes, wrapping or removing elements. If your transformation requires complex conditional logic, access to the parent path, or traversal control (skipping children, stopping early), the visitor pattern via Rewriters is a better fit.

A simple rule of thumb is that if you can describe the transform as "find X, do Y," use a Selection. If you need "when entering X, check Z, then conditionally do Y," use a Visitor.

#See also