Add a non-static API for the CssSelector component
This commit is contained in:
parent
078f953935
commit
f4563c39ce
@ -1,6 +1,12 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
2.8.0
|
||||
-----
|
||||
|
||||
* Added the ConverterInterface and the Converter implementation as a non-static API for the component.
|
||||
* Deprecated the `CssSelector` static API of the component.
|
||||
|
||||
2.1.0
|
||||
-----
|
||||
|
||||
|
56
src/Symfony/Component/CssSelector/Converter.php
Normal file
56
src/Symfony/Component/CssSelector/Converter.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\CssSelector;
|
||||
|
||||
use Symfony\Component\CssSelector\Parser\Shortcut\ClassParser;
|
||||
use Symfony\Component\CssSelector\Parser\Shortcut\ElementParser;
|
||||
use Symfony\Component\CssSelector\Parser\Shortcut\EmptyStringParser;
|
||||
use Symfony\Component\CssSelector\Parser\Shortcut\HashParser;
|
||||
use Symfony\Component\CssSelector\XPath\Extension\HtmlExtension;
|
||||
use Symfony\Component\CssSelector\XPath\Translator;
|
||||
|
||||
/**
|
||||
* @author Christophe Coevoet <stof@notk.org>
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class Converter implements ConverterInterface
|
||||
{
|
||||
private $translator;
|
||||
|
||||
/**
|
||||
* @param bool $html Whether HTML support should be enabled. Disable it for XML documents.
|
||||
*/
|
||||
public function __construct($html = true)
|
||||
{
|
||||
$this->translator = new Translator();
|
||||
|
||||
if ($html) {
|
||||
$this->translator->registerExtension(new HtmlExtension($this->translator));
|
||||
}
|
||||
|
||||
$this->translator
|
||||
->registerParserShortcut(new EmptyStringParser())
|
||||
->registerParserShortcut(new ElementParser())
|
||||
->registerParserShortcut(new ClassParser())
|
||||
->registerParserShortcut(new HashParser())
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function toXPath($cssExpr, $prefix = 'descendant-or-self::')
|
||||
{
|
||||
return $this->translator->cssToXPath($cssExpr, $prefix);
|
||||
}
|
||||
}
|
75
src/Symfony/Component/CssSelector/ConverterInterface.php
Normal file
75
src/Symfony/Component/CssSelector/ConverterInterface.php
Normal file
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\CssSelector;
|
||||
|
||||
/**
|
||||
* ConverterInterface is the main entry point of the component and can convert CSS
|
||||
* selectors to XPath expressions.
|
||||
*
|
||||
* This component is a port of the Python cssselect library,
|
||||
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
|
||||
*
|
||||
* Copyright (c) 2007-2012 Ian Bicking and contributors. See AUTHORS
|
||||
* for more details.
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in
|
||||
* the documentation and/or other materials provided with the
|
||||
* distribution.
|
||||
*
|
||||
* 3. Neither the name of Ian Bicking nor the names of its contributors may
|
||||
* be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL IAN BICKING OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* @author Christophe Coevoet <stof@notk.org>
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
interface ConverterInterface
|
||||
{
|
||||
/**
|
||||
* Translates a CSS expression to its XPath equivalent.
|
||||
*
|
||||
* Optionally, a prefix can be added to the resulting XPath
|
||||
* expression with the $prefix parameter.
|
||||
*
|
||||
* @param string $cssExpr The CSS expression.
|
||||
* @param string $prefix An optional prefix for the XPath expression.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function toXPath($cssExpr, $prefix = 'descendant-or-self::');
|
||||
|
||||
}
|
@ -11,12 +11,7 @@
|
||||
|
||||
namespace Symfony\Component\CssSelector;
|
||||
|
||||
use Symfony\Component\CssSelector\Parser\Shortcut\ClassParser;
|
||||
use Symfony\Component\CssSelector\Parser\Shortcut\ElementParser;
|
||||
use Symfony\Component\CssSelector\Parser\Shortcut\EmptyStringParser;
|
||||
use Symfony\Component\CssSelector\Parser\Shortcut\HashParser;
|
||||
use Symfony\Component\CssSelector\XPath\Extension\HtmlExtension;
|
||||
use Symfony\Component\CssSelector\XPath\Translator;
|
||||
@trigger_error('The '.__NAMESPACE__.'\CssSelector class is deprecated since version 2.8 and will be removed in 3.0. Use directly the \Symfony\Component\CssSelector\Converter class instead.', E_USER_DEPRECATED);
|
||||
|
||||
/**
|
||||
* CssSelector is the main entry point of the component and can convert CSS
|
||||
@ -62,6 +57,8 @@ use Symfony\Component\CssSelector\XPath\Translator;
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* @deprecated as of 2.8, will be removed in 3.0. Use the \Symfony\Component\CssSelector\Converter class instead.
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class CssSelector
|
||||
@ -82,20 +79,9 @@ class CssSelector
|
||||
*/
|
||||
public static function toXPath($cssExpr, $prefix = 'descendant-or-self::')
|
||||
{
|
||||
$translator = new Translator();
|
||||
$converter = new Converter(self::$html);
|
||||
|
||||
if (self::$html) {
|
||||
$translator->registerExtension(new HtmlExtension($translator));
|
||||
}
|
||||
|
||||
$translator
|
||||
->registerParserShortcut(new EmptyStringParser())
|
||||
->registerParserShortcut(new ElementParser())
|
||||
->registerParserShortcut(new ClassParser())
|
||||
->registerParserShortcut(new HashParser())
|
||||
;
|
||||
|
||||
return $translator->cssToXPath($cssExpr, $prefix);
|
||||
return $converter->toXPath($cssExpr, $prefix);
|
||||
}
|
||||
|
||||
/**
|
||||
|
36
src/Symfony/Component/CssSelector/Tests/ConverterTest.php
Normal file
36
src/Symfony/Component/CssSelector/Tests/ConverterTest.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\CssSelector\Tests;
|
||||
|
||||
use Symfony\Component\CssSelector\Converter;
|
||||
|
||||
class ConverterTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testCssToXPath()
|
||||
{
|
||||
$converter = new Converter();
|
||||
|
||||
$this->assertEquals('descendant-or-self::*', $converter->toXPath(''));
|
||||
$this->assertEquals('descendant-or-self::h1', $converter->toXPath('h1'));
|
||||
$this->assertEquals("descendant-or-self::h1[@id = 'foo']", $converter->toXPath('h1#foo'));
|
||||
$this->assertEquals("descendant-or-self::h1[@class and contains(concat(' ', normalize-space(@class), ' '), ' foo ')]", $converter->toXPath('h1.foo'));
|
||||
$this->assertEquals('descendant-or-self::foo:h1', $converter->toXPath('foo|h1'));
|
||||
$this->assertEquals('descendant-or-self::h1', $converter->toXPath('H1'));
|
||||
}
|
||||
|
||||
public function testCssToXPathXml()
|
||||
{
|
||||
$converter = new Converter(false);
|
||||
|
||||
$this->assertEquals('descendant-or-self::H1', $converter->toXPath('H1'));
|
||||
}
|
||||
}
|
@ -13,6 +13,9 @@ namespace Symfony\Component\CssSelector\Tests;
|
||||
|
||||
use Symfony\Component\CssSelector\CssSelector;
|
||||
|
||||
/**
|
||||
* @group legacy
|
||||
*/
|
||||
class CssSelectorTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testCssToXPath()
|
||||
|
@ -11,7 +11,7 @@
|
||||
|
||||
namespace Symfony\Component\DomCrawler;
|
||||
|
||||
use Symfony\Component\CssSelector\CssSelector;
|
||||
use Symfony\Component\CssSelector\Converter;
|
||||
|
||||
/**
|
||||
* Crawler eases navigation of a list of \DOMElement objects.
|
||||
@ -42,6 +42,13 @@ class Crawler extends \SplObjectStorage
|
||||
*/
|
||||
private $baseHref;
|
||||
|
||||
/**
|
||||
* Whether the Crawler contains HTML or XML content (used when converting CSS to XPath)
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $isHtml = true;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
@ -263,6 +270,8 @@ class Crawler extends \SplObjectStorage
|
||||
libxml_disable_entity_loader($disableEntities);
|
||||
|
||||
$this->addDocument($dom);
|
||||
|
||||
$this->isHtml = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -349,11 +358,11 @@ class Crawler extends \SplObjectStorage
|
||||
{
|
||||
foreach ($this as $i => $node) {
|
||||
if ($i == $position) {
|
||||
return new static($node, $this->uri, $this->baseHref);
|
||||
return $this->createSubCrawler($node);
|
||||
}
|
||||
}
|
||||
|
||||
return new static(null, $this->uri, $this->baseHref);
|
||||
return $this->createSubCrawler(null);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -378,7 +387,7 @@ class Crawler extends \SplObjectStorage
|
||||
{
|
||||
$data = array();
|
||||
foreach ($this as $i => $node) {
|
||||
$data[] = $closure(new static($node, $this->uri, $this->baseHref), $i);
|
||||
$data[] = $closure($this->createSubCrawler($node), $i);
|
||||
}
|
||||
|
||||
return $data;
|
||||
@ -394,7 +403,7 @@ class Crawler extends \SplObjectStorage
|
||||
*/
|
||||
public function slice($offset = 0, $length = -1)
|
||||
{
|
||||
return new static(iterator_to_array(new \LimitIterator($this, $offset, $length)), $this->uri);
|
||||
return $this->createSubCrawler(iterator_to_array(new \LimitIterator($this, $offset, $length)));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -412,12 +421,12 @@ class Crawler extends \SplObjectStorage
|
||||
{
|
||||
$nodes = array();
|
||||
foreach ($this as $i => $node) {
|
||||
if (false !== $closure(new static($node, $this->uri, $this->baseHref), $i)) {
|
||||
if (false !== $closure($this->createSubCrawler($node), $i)) {
|
||||
$nodes[] = $node;
|
||||
}
|
||||
}
|
||||
|
||||
return new static($nodes, $this->uri, $this->baseHref);
|
||||
return $this->createSubCrawler($nodes);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -459,7 +468,7 @@ class Crawler extends \SplObjectStorage
|
||||
throw new \InvalidArgumentException('The current node list is empty.');
|
||||
}
|
||||
|
||||
return new static($this->sibling($this->getNode(0)->parentNode->firstChild), $this->uri, $this->baseHref);
|
||||
return $this->createSubCrawler($this->sibling($this->getNode(0)->parentNode->firstChild));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -477,7 +486,7 @@ class Crawler extends \SplObjectStorage
|
||||
throw new \InvalidArgumentException('The current node list is empty.');
|
||||
}
|
||||
|
||||
return new static($this->sibling($this->getNode(0)), $this->uri, $this->baseHref);
|
||||
return $this->createSubCrawler($this->sibling($this->getNode(0)));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -495,7 +504,7 @@ class Crawler extends \SplObjectStorage
|
||||
throw new \InvalidArgumentException('The current node list is empty.');
|
||||
}
|
||||
|
||||
return new static($this->sibling($this->getNode(0), 'previousSibling'), $this->uri, $this->baseHref);
|
||||
return $this->createSubCrawler($this->sibling($this->getNode(0), 'previousSibling'));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -522,7 +531,7 @@ class Crawler extends \SplObjectStorage
|
||||
}
|
||||
}
|
||||
|
||||
return new static($nodes, $this->uri, $this->baseHref);
|
||||
return $this->createSubCrawler($nodes);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -542,7 +551,7 @@ class Crawler extends \SplObjectStorage
|
||||
|
||||
$node = $this->getNode(0)->firstChild;
|
||||
|
||||
return new static($node ? $this->sibling($node) : array(), $this->uri, $this->baseHref);
|
||||
return $this->createSubCrawler($node ? $this->sibling($node) : array());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -679,7 +688,7 @@ class Crawler extends \SplObjectStorage
|
||||
|
||||
// If we dropped all expressions in the XPath while preparing it, there would be no match
|
||||
if ('' === $xpath) {
|
||||
return new static(null, $this->uri, $this->baseHref);
|
||||
return $this->createSubCrawler(null);
|
||||
}
|
||||
|
||||
return $this->filterRelativeXPath($xpath);
|
||||
@ -700,12 +709,14 @@ class Crawler extends \SplObjectStorage
|
||||
*/
|
||||
public function filter($selector)
|
||||
{
|
||||
if (!class_exists('Symfony\\Component\\CssSelector\\CssSelector')) {
|
||||
throw new \RuntimeException('Unable to filter with a CSS selector as the Symfony CssSelector is not installed (you can use filterXPath instead).');
|
||||
if (!class_exists('Symfony\\Component\\CssSelector\\Converter')) {
|
||||
throw new \RuntimeException('Unable to filter with a CSS selector as the Symfony CssSelector 2.8+ is not installed (you can use filterXPath instead).');
|
||||
}
|
||||
|
||||
$converter = new Converter($this->isHtml);
|
||||
|
||||
// The CssSelector already prefixes the selector with descendant-or-self::
|
||||
return $this->filterRelativeXPath(CssSelector::toXPath($selector));
|
||||
return $this->filterRelativeXPath($converter->toXPath($selector));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1019,7 +1030,7 @@ class Crawler extends \SplObjectStorage
|
||||
{
|
||||
$prefixes = $this->findNamespacePrefixes($xpath);
|
||||
|
||||
$crawler = new static(null, $this->uri, $this->baseHref);
|
||||
$crawler = $this->createSubCrawler(null);
|
||||
|
||||
foreach ($this as $node) {
|
||||
$domxpath = $this->createDOMXPath($node->ownerDocument, $prefixes);
|
||||
@ -1189,4 +1200,19 @@ class Crawler extends \SplObjectStorage
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a crawler for some subnodes
|
||||
*
|
||||
* @param \DOMElement|\DOMElement[]|\DOMNodeList|null $nodes
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
private function createSubCrawler($nodes)
|
||||
{
|
||||
$crawler = new static($nodes, $this->uri, $this->baseHref);
|
||||
$crawler->isHtml = $this->isHtml;
|
||||
|
||||
return $crawler;
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,6 @@
|
||||
|
||||
namespace Symfony\Component\DomCrawler\Tests;
|
||||
|
||||
use Symfony\Component\CssSelector\CssSelector;
|
||||
use Symfony\Component\DomCrawler\Crawler;
|
||||
|
||||
class CrawlerTest extends \PHPUnit_Framework_TestCase
|
||||
@ -618,16 +617,12 @@ EOF
|
||||
|
||||
public function testFilterWithNamespace()
|
||||
{
|
||||
CssSelector::disableHtmlExtension();
|
||||
|
||||
$crawler = $this->createTestXmlCrawler()->filter('yt|accessControl');
|
||||
$this->assertCount(2, $crawler, '->filter() automatically registers namespaces');
|
||||
}
|
||||
|
||||
public function testFilterWithMultipleNamespaces()
|
||||
{
|
||||
CssSelector::disableHtmlExtension();
|
||||
|
||||
$crawler = $this->createTestXmlCrawler()->filter('media|group yt|aspectRatio');
|
||||
$this->assertCount(1, $crawler, '->filter() automatically registers namespaces');
|
||||
$this->assertSame('widescreen', $crawler->text());
|
||||
|
@ -20,7 +20,7 @@
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/phpunit-bridge": "~2.7|~3.0.0",
|
||||
"symfony/css-selector": "~2.3|~3.0.0"
|
||||
"symfony/css-selector": "~2.8|~3.0.0"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/css-selector": ""
|
||||
|
Reference in New Issue
Block a user