laravel-dom-pipeline maintained by bayareawebpro
Description
A DOM Pipeline Utility for Modifying HTML
Author
Last update
2026/03/19 07:01
(dev-master)
License
Downloads
1 333
Laravel DOM Pipeline
composer require bayareawebpro/laravel-dom-pipeline
https://packagist.org/packages/bayareawebpro/laravel-dom-pipeline
Laravel DOM Pipeline allows you to pipe HTML content through a series of classes
which can be helpful with sanitization and server-side enhancement / modification of page
elements. The pipeline will not return the <body> tag or any other tags outside the
body scope.
Usage:
use BayAreaWebPro\DomPipeline\DomPipeline;
use My\Pipes\{
LazyLoadImageTags,
LazyLoadVideoTags,
BuildTableOfContents
};
$modified = DomPipeline::make($html, [
LazyLoadImageTags::class,
LazyLoadVideoTags::class,
BuildTableOfContents::class,
]);
Example Dom Pipe Class
LazyLoad Images
<?php declare(strict_types=1);
namespace App\Services\Html\Formatters;
use Closure;
use DOMElement;
use DOMDocument;
class LazyLoadImages
{
public function handle(DOMDocument $dom, Closure $next)
{
foreach ($dom->getElementsByTagName('img') as $node) {
$this->lazyLoad($node);
}
return $next($dom);
}
protected function lazyLoad(DOMElement $node): void
{
if (!$node->hasAttribute('loading')) {
$node->setAttribute('loading', 'lazy');
}
}
}
Element to VueComponent
Convert an Iframe into a Vue Component extracting the video ID from the URL.
<?php declare(strict_types=1);
namespace App\Services\Html\Formatters;
use Closure;
use DOMElement;
use DOMDocument;
class LazyLoadVideos
{
public function handle(DOMDocument $dom, Closure $next)
{
$xpath = new \DOMXPath($dom);
foreach($xpath->query('//iframe[@class="media"]') as $node){
$this->lazyLoad($dom, $node);
}
return $next($dom);
}
protected function lazyLoad(DOMDocument $dom, DOMElement $node): void
{
if(is_null($node->parentNode)) return;
// Match the YouTube Video ID.
// https://stackoverflow.com/questions/2936467/parse-youtube-video-id-using-preg-match
preg_match('%(?:youtube(?:-nocookie)?\.com/(?:[^/]+/.+/|(?:v|e(?:mbed)?)/|.*[?&]v=)|youtu\.be/)([^"&?/ ]{11})%i',
(string) $node->getAttribute('src'), $matches
);
if(isset($matches[1])){
// Create a new HTML fragment.
$fragment = $dom->createDocumentFragment();
$fragment->appendXML(<<<HTML
<v-video id="$matches[1]" label="Click to play..." :show-image="true"></v-video>
HTML);
// Replace Self with Fragment.
$node->parentNode->replaceChild($fragment, $node);
}
}
}
Table of Contents
<?php declare(strict_types=1);
namespace App\Services\Html\Formatters;
use DOMElement;
use Closure;
use StdClass;
use DOMDocument;
use Illuminate\Support\Str;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\View;
class TableOfContents
{
public function handle(DOMDocument $dom, Closure $next)
{
$nodes = Collection::make();
$xpath = new \DOMXPath($dom);
foreach ($xpath->query('//h1|//h2|//h3|//h4|//h5|//h6') as $node) {
$text = strip_tags(html_entity_decode((string) $node->nodeValue));
$nodes->push($this->makeBookmark($node, $text));
}
if($nodes->count() > 5){
View::share('tableOfContents', $nodes->take(25));
}
return $next($dom);
}
protected function makeBookmark(DOMElement $node, string $text): StdClass
{
$bookmark = (object)[
'anchor' => Str::slug($text),
'text' => Str::title(Str::replaceLast('.', '', $text)),
];
$node->setAttribute('id', $bookmark->anchor);
return $bookmark;
}
}