symfony/css-selector
Symfony CssSelector converts CSS selectors into XPath expressions, enabling CSS-style element matching in XML/HTML documents. Ported from the Python cssselect library, it’s a lightweight component for selector parsing and XPath generation in PHP.
Install the package via Composer:
composer require symfony/css-selector
Basic usage (convert CSS to XPath):
use Symfony\Component\CssSelector\CssSelectorConverter;
use Symfony\Component\CssSelector\XPathNodeConverter;
$converter = new CssSelectorConverter();
$xpathConverter = new XPathNodeConverter();
$cssSelector = 'div.is-active > p';
$xpath = $converter->toXPath($cssSelector);
// Example output: `//div[contains(@class, 'is-active')]/p`
First use case: Query a DOM document with CSS-like syntax:
$dom = new DOMDocument();
$dom->loadHTML('<div class="is-active"><p>Hello</p></div>');
$xpath = $converter->toXPath('div.is-active > p');
$nodes = $dom->xpath($xpath);
// $nodes now contains the <p> element
CssSelectorConverter class for core conversion logic.XPathNodeConverter for translating node names/attributes to XPath (e.g., div#id → //div[@id='id']).use Symfony\Component\CssSelector\CssSelectorConverter;
use Symfony\Component\DomCrawler\Crawler;
$converter = new CssSelectorConverter();
$crawler = new Crawler('<html><body><div class="user">John</div></body></html>');
// Convert CSS to XPath and query
$xpath = $converter->toXPath('div.user');
$nodes = $crawler->filterXPath($xpath); // Symfony DomCrawler method
// Or use native DOMDocument
$dom = new DOMDocument();
$dom->loadHTML($html);
$nodes = $dom->xpath($xpath);
use Symfony\Component\CssSelector\CssSelectorConverter;
use PHPUnit\Framework\Assert;
$converter = new CssSelectorConverter();
$dom = new DOMDocument();
$dom->loadHTML('<div data-test="user">Test</div>');
$xpath = $converter->toXPath('[data-test="user"]');
$nodes = $dom->xpath($xpath);
Assert::count(1, $nodes);
// app/Providers/AppServiceProvider.php
use Symfony\Component\CssSelector\CssSelectorConverter;
public function register()
{
$this->app->singleton(CssSelectorConverter::class, function ($app) {
return new CssSelectorConverter();
});
}
// Usage in controllers/services
$converter = app(CssSelectorConverter::class);
$xpath = $converter->toXPath('h1.title');
$cache = new \Symfony\Component\Cache\Simple\FilesystemCache();
$converter = new CssSelectorConverter($cache);
spatie/array-to-xml for XML scraping:
use Spatie\ArrayToXml\ArrayToXml;
$xml = ArrayToXml::convert(['user' => ['name' => 'John']]);
$dom = new DOMDocument();
$dom->loadXML($xml);
$xpath = $converter->toXPath('user.name');
use Collective\Html\HtmlFacade;
$html = HtmlFacade::script()->withContent('<script>...</script>');
$dom = new DOMDocument();
$dom->loadHTML($html);
PHP 8.4 Requirement:
Memory Leaks in High-Volume Scraping:
$cache = new \Symfony\Component\Cache\Adapter\FilesystemAdapter();
$converter = new CssSelectorConverter($cache);
Selector Syntax Limitations:
// Works: div.is-active > p
// May fail: div:has(> p) // Requires v7.1.0+ (Symfony 5.4+)
XPath vs. CSS Quirks:
> (child combinator) becomes / in XPath, while CSS (descendant) becomes //.[href^="https"] may not translate 1:1 to XPath. Workaround:
// CSS: a[href^="https"]
// XPath: //a[starts-with(@href, 'https')]
Symfony DomCrawler Dependency:
symfony/dom-crawler requires:
composer require symfony/dom-crawler
php-dom).$xpath = $converter->toXPath('div.is-active');
echo $xpath; // Debug the generated XPath
DOMXPath:
$dom = new DOMDocument();
$dom->loadHTML('<div class="test">Hello</div>');
$xpath = new DOMXPath($dom);
$nodes = $xpath->query($converter->toXPath('div.test'));
try {
$xpath = $converter->toXPath('invalid[selector');
} catch (\Symfony\Component\CssSelector\Exception\ParseErrorException $e) {
// Log or handle gracefully
}
$xpath = $converter->toXPath('div.is-active');
$xpath = str_replace('//div', '/html/body/div', $xpath); // Force absolute path
$this->app->when(CssSelectorConverter::class)
->needs('$cache')
->give(function () {
return new \Symfony\Component\Cache\Adapter\ArrayAdapter();
});
// app/Facades/CssSelector.php
use Illuminate\Support\Facades\Facade;
class CssSelector extends Facade
{
protected static function getFacadeAccessor()
{
return 'css.selector.converter';
}
}
// Usage: CssSelector::toXPath('div.is-active');
DomCrawler, register it after the converter to avoid DI conflicts:
$this->app->singleton(DomCrawler::class, function ($app) {
$converter = $app->make(CssSelectorConverter::class);
return new DomCrawler($html, $converter);
});
Blade::directive('xpath', function ($selector) {
return "<?php echo app('css.selector.converter')->toXPath({$selector}); ?>";
});
Usage: @xpath('div.is-active') in Blade templates.How can I help you explore Laravel packages today?