Querying
XPath Query Cookbook
Use these ready-to-run XPath queries for common template analysis tasks. Each recipe is self-contained and can be copied into a project. For the full DOM mapping and XPath syntax reference, see XPath Queries.
#Accessibility Queries
Accessibility queries locate markup that needs an accessible name, alternative text, or equivalent context.
#Images Without Alt Attributes
The //img[not(@alt)] query finds every <img> element that lacks an alt attribute:
<?php
use Forte\Facades\Forte;
$blade = <<<'BLADE'
<img src="/hero.jpg" alt="Hero banner">
<img src="/logo.png">
<img src="/avatar.jpg" alt="">
BLADE;
$doc = Forte::parse($blade);
$doc->xpath('//img[not(@alt)]')->count(); // 1
$doc->xpath('//img[not(@alt)]')->first()->tagNameText(); // "img"
Note that alt="" is valid HTML (it signals a decorative image), so the query correctly skips it.
#Empty Links
Links that contain no visible text are inaccessible to screen readers. The query //a[not(text()[normalize-space()]) and not(@aria-label)] finds links that have neither text content nor an aria-label fallback:
<?php
use Forte\Facades\Forte;
$blade = <<<'BLADE'
<a href="/home">Home</a>
<a href="/icon"><img src="/icon.svg"></a>
<a href="/about" aria-label="About us"><img src="/about.svg"></a>
BLADE;
$doc = Forte::parse($blade);
$missing = $doc->xpath('//a[not(text()[normalize-space()]) and not(@aria-label)]');
$missing->count(); // 1
$missing->first()->tagNameText(); // "a"
#Buttons Without Accessible Text
Buttons need either visible text content or an aria-label attribute. The query //button[not(text()[normalize-space()]) and not(@aria-label)] catches icon-only buttons that screen readers cannot describe:
<?php
use Forte\Facades\Forte;
$blade = <<<'BLADE'
<button>Save</button>
<button aria-label="Close"><svg>...</svg></button>
<button><svg>...</svg></button>
BLADE;
$doc = Forte::parse($blade);
$doc->xpath('//button[not(text()[normalize-space()]) and not(@aria-label)]')->count(); // 1
#Security Queries
Security queries surface output and inline behavior that deserve manual review.
#Raw Echo Statements
The //forte:raw-echo query finds all {!! !!} output statements, which bypass Blade's XSS escaping. Not every raw echo is a vulnerability, but each one warrants review:
<?php
use Forte\Facades\Forte;
$blade = <<<'BLADE'
<p>{{ $name }}</p>
<div>{!! $html !!}</div>
<span>{{ $safe }}</span>
BLADE;
$doc = Forte::parse($blade);
$doc->xpath('//forte:raw-echo')->count(); // 1
$doc->xpath('//forte:echo')->count(); // 2
#Inline Event Handlers
Inline event handlers like onclick and onmouseover are a common source of XSS vulnerabilities and are generally discouraged in favor of JavaScript event listeners. Use or to check multiple handler attributes at once:
<?php
use Forte\Facades\Forte;
$blade = <<<'BLADE'
<button onclick="submit()">Go</button>
<div onmouseover="highlight()">Hover</div>
<a href="/safe">Safe link</a>
BLADE;
$doc = Forte::parse($blade);
$handlers = $doc->xpath('//*[@onclick or @onmouseover or @onsubmit or @onchange]');
$handlers->count(); // 2
#Directive Queries
Directive queries use Forte's XML namespace to find Blade control structures and their branches.
#Finding Directives by Name
Every Blade directive maps to a forte: namespaced element, so you can query for specific directive types directly. The //forte:foreach query finds all @foreach loops, and you can drill into their children with standard axis syntax:
<?php
use Forte\Facades\Forte;
$blade = <<<'BLADE'
@foreach($users as $user)
<li>{{ $user->name }}</li>
@endforeach
@if($show)
<p>Visible</p>
@endif
BLADE;
$doc = Forte::parse($blade);
$doc->xpath('//forte:foreach')->count(); // 1
$doc->xpath('//forte:if')->count(); // 1
$doc->xpath('//forte:foreach//li')->count(); // 1
#Switch Case Branches
@switch blocks contain @case and @default as child elements, so a query can count branches or find the default:
<?php
use Forte\Facades\Forte;
$blade = <<<'BLADE'
@switch($status)
@case('active')
<span>Active</span>
@break
@case('inactive')
<span>Inactive</span>
@break
@default
<span>Unknown</span>
@endswitch
BLADE;
$doc = Forte::parse($blade);
$doc->xpath('//forte:switch//forte:case')->count(); // 2
$doc->xpath('//forte:switch//forte:default')->count(); // 1
#Component Queries
Component queries use Forte's component and slot metadata instead of relying only on tag text.
#Finding Components by Prefix
All Blade components carry a data-forte-component attribute in the DOM. You can combine this with starts-with() on the tag name to find component families:
<?php
use Forte\Facades\Forte;
$blade = <<<'BLADE'
<x-button>Click</x-button>
<x-form-input name="email" />
<x-form-select name="role" />
<div class="wrapper">Content</div>
BLADE;
$doc = Forte::parse($blade);
$doc->xpath('//*[@data-forte-component]')->count(); // 3
$doc->xpath('//*[starts-with(name(), "x-form")]')->count(); // 2
#Slots
Slots are identified by data-forte-slot="true". Named slots also carry data-forte-slot-name:
<?php
use Forte\Facades\Forte;
$blade = <<<'BLADE'
<x-card>
<x-slot:header>Title</x-slot:header>
<x-slot:footer>Footer</x-slot:footer>
Body content
</x-card>
BLADE;
$doc = Forte::parse($blade);
$doc->xpath('//*[@data-forte-slot="true"]')->count(); // 2
$doc->xpath('//*[@data-forte-slot-name="header"]')->count(); // 1
#Structural Queries
Structural queries use XPath axes and position predicates to describe relationships between nodes.
#Position-Based Selection
XPath position predicates select nodes by their ordinal position among siblings. Positions are 1-indexed, and last() returns the total count:
<?php
use Forte\Facades\Forte;
$doc = Forte::parse('<ul><li>A</li><li>B</li><li>C</li><li>D</li></ul>');
$doc->xpath('//ul/li[1]')->first()->innerContent(); // "A"
$doc->xpath('//ul/li[last()]')->first()->innerContent(); // "D"
$doc->xpath('//ul/li[position() > 1]')->count(); // 3
#Ancestor Traversal
The ancestor:: axis lets you navigate upward from a matched node. This is useful when you find a leaf node and need to inspect its parent context:
<?php
use Forte\Facades\Forte;
$blade = <<<'BLADE'
<div class="container">
<ul>
<li>Item</li>
</ul>
</div>
BLADE;
$doc = Forte::parse($blade);
$doc->xpath('//li/ancestor::div[@class="container"]')->count(); // 1
$doc->xpath('//li/ancestor::ul')->count(); // 1
#Combined Patterns
Combined patterns let one query return several independent node types.
#Union Queries
The | operator combines multiple independent queries into a single result set. Use this when you need to find several different construct types at once:
<?php
use Forte\Facades\Forte;
$blade = <<<'BLADE'
{{ $name }}
{!! $html !!}
@include("header")
<p>Text</p>
BLADE;
$doc = Forte::parse($blade);
$all = $doc->xpath('//forte:echo | //forte:raw-echo | //forte:include');
$all->count(); // 3
#See Also
Continue with these related Forte guides:
- XPath Queries: Full DOM mapping reference and XPath syntax primer
- Traversal: Alternative traversal methods without XPath
- Building a Blade Template Linter: Full linter tutorial using XPath queries