Querying

Source Positions

Forte tracks the exact source location of every parsed node, including byte offsets, line numbers, and column numbers. This makes it possible to build cursor-aware tooling, generate precise error reports, and filter nodes by their position in the template.

#Node Positions

Every parsed node records where it appeared in the original source. The offset methods return absolute byte positions, while the line and column methods return 1-indexed values:

1<?php
2
3use Forte\Facades\Forte;
4
5$doc = Forte::parse('<div>Hello</div>');
6
7$element = $doc->findElementByName('div');
8
9$element->startOffset(); // 0
10$element->endOffset(); // 16
11$element->startLine(); // 1
12$element->endLine(); // 1
13$element->startColumn(); // 1
14$element->endColumn(); // 16

Synthetic nodes (those created by rewriters rather than parsed from source) return -1 for all position methods:

1<?php
2
3$node->isSynthetic(); // true
4$node->startOffset(); // -1
5$node->startLine(); // -1
6$node->startColumn(); // -1

#Cursor-Aware Lookups

When building editor integrations, you often need to find the node under the cursor. The findNodeAtOffset method returns the most deeply nested node containing the given byte offset, and findNodeAtPosition does the same for 1-indexed line and column coordinates:

1<?php
2
3use Forte\Facades\Forte;
4
5$doc = Forte::parse('<div><span>Hello</span></div>');
6
7$node = $doc->findNodeAtOffset(12);
8$node->isText(); // true
9
10$node = $doc->findNodeAtPosition(1, 13);
11$node->isText(); // true

Both methods return null if the offset or position falls outside the document bounds. They are designed for cursor-aware tooling. The returned node is always the deepest match, so you can walk up with getParent() to find the surrounding element or directive.

#Line Utilities

The document provides several methods for working with source lines. These are useful for error reporting, diagnostics formatting, and building context windows around specific locations.

Convert a byte offset to a 1-indexed line number:

1<?php
2
3$doc->getLineForOffset(42); // int (1-indexed line number)

When you need to move between offsets and editor-style coordinates, use the document's conversion helpers:

1<?php
2
3use Forte\Facades\Forte;
4
5$doc = Forte::parse("<div>\n <span>Hello</span>\n</div>");
6
7$doc->getLineForOffset(10); // 2
8$doc->getColumnForOffset(10); // 5
9$doc->getLineAndColumnForOffset(10); // ['line' => 2, 'column' => 5]
10$doc->getOffsetFromPosition(2, 5); // 10

Access individual lines or the full line array:

1<?php
2
3$doc->getLine(1); // string (first line of source)
4$doc->getLines(); // array<string> (all lines)
5$doc->getLineCount(); // int (total number of lines)

The getLineExcerpt method returns a window of lines around a given line number, keyed by their 1-based line numbers. This is particularly useful for displaying context in error messages:

1<?php
2
3use Forte\Facades\Forte;
4
5$template = <<<'BLADE'
6<header>
7 <nav>
8 <ul>
9 <li>Home</li>
10 </ul>
11 </nav>
12</header>
13BLADE;
14
15$doc = Forte::parse($template);
16
17$excerpt = $doc->getLineExcerpt(4, 1);
18// Returns lines 3-5 keyed by line number:
19// [3 => ' <ul>', 4 => ' <li>Home</li>', 5 => ' </ul>']
20
21count($excerpt); // 3

#Source Extraction

You can extract substrings from the original source using byte offsets or node references.

The getText method returns a substring between two byte offsets. The start offset is inclusive and the end offset is exclusive:

1<?php
2
3$doc->getText(0, 5); // bytes at offsets 0-4
4$doc->getText(5, 16); // bytes at offsets 5-15

The getTextBetween method extracts the source text between two nodes:

1<?php
2
3$doc->getTextBetween($nodeA, $nodeB); // text between two nodes

#Collection Position Filters

NodeCollection extends Laravel's Collection with position-based filtering methods. These are useful for editor integrations and range-based operations:

1<?php
2
3use Forte\Facades\Forte;
4
5$template = <<<'BLADE'
6<div>First</div>
7<p>Second</p>
8<span>Third</span>
9BLADE;
10
11$doc = Forte::parse($template);
12
13$roots = $doc->getRootNodes();
14
15$roots->onLine(2)->count(); // nodes that span line 2
16$roots->startingOnLine(2)->count(); // nodes starting on line 2
17$roots->endingOnLine(2)->count(); // nodes ending on line 2
18
19$roots->containingOffset(20)->count(); // nodes containing byte offset 20
20$roots->betweenOffsets(0, 16)->count(); // nodes within an offset range

The onLine method returns nodes whose line range includes the given line. The startingOnLine and endingOnLine variants are stricter, checking only the start or end boundary. The offset-based methods (containingOffset and betweenOffsets) work with byte positions for precise range queries.

#See also

  • Traversal: Navigate and query the document tree
  • Diagnostics: Detect and report template errors
  • Documents: Parse templates and access the document API