feature #15934 Add a non-static API for the CssSelector component (stof)

This PR was merged into the 2.8 branch.

Discussion
----------

Add a non-static API for the CssSelector component

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | yes
| Tests pass?   | yes
| Fixed tickets | #15850, #8404
| License       | MIT
| Doc PR        | todo

This implements a non-static API for the CssSelector component.

I decided to keep the static API too, as it is convenient when you just need a one-shot conversion (if you need lots of conversions, keeping a reference to the Converter and all its internal object graph may be faster than releasing it all the time and rebuilding it).
I deprecated the global state to choose between HTML and XML conversion. The static API would always enable the HTML extension in 3.0. Dealing with XML would be done by using the Converter class.

A second commit also tags all internal classes of the component as ``@internal``, as there is really no reason for a user to deal with them (btw, we already considered them fully internal in the past, as we broke BC on them in a patch release to fix memory performance of the component in the past).

TODOs:

- [x] Validate whether we keep the static facade to the component
- [ ] send a PR on the documentation to document this new API.
- [x]  handle usage of the deprecated API in the DomCrawler testsuite

The DomCrawler component does not use the new API yet. I will do it in a separate PR, as distinguishing between HTML and XML modes for a crawler will be easier once I deprecate the possibility to load multiple documents (which I will do tomorrow).

Commits
-------

9e51279 [CssSelector] Tag all internal classes as internal ones
f4563c3 Add a non-static API for the CssSelector component
This commit is contained in:
Fabien Potencier 2015-09-28 22:26:21 +02:00
commit 47d001e821
51 changed files with 309 additions and 42 deletions

View File

@ -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
-----

View 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);
}
}

View 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::');
}

View File

@ -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);
}
/**

View File

@ -18,6 +18,8 @@ namespace Symfony\Component\CssSelector\Node;
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
abstract class AbstractNode implements NodeInterface
{

View File

@ -18,6 +18,8 @@ namespace Symfony\Component\CssSelector\Node;
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
class AttributeNode extends AbstractNode
{

View File

@ -18,6 +18,8 @@ namespace Symfony\Component\CssSelector\Node;
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
class ClassNode extends AbstractNode
{

View File

@ -18,6 +18,8 @@ namespace Symfony\Component\CssSelector\Node;
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
class CombinedSelectorNode extends AbstractNode
{

View File

@ -18,6 +18,8 @@ namespace Symfony\Component\CssSelector\Node;
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
class ElementNode extends AbstractNode
{

View File

@ -20,6 +20,8 @@ use Symfony\Component\CssSelector\Parser\Token;
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
class FunctionNode extends AbstractNode
{

View File

@ -18,6 +18,8 @@ namespace Symfony\Component\CssSelector\Node;
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
class HashNode extends AbstractNode
{

View File

@ -18,6 +18,8 @@ namespace Symfony\Component\CssSelector\Node;
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
class NegationNode extends AbstractNode
{

View File

@ -18,6 +18,8 @@ namespace Symfony\Component\CssSelector\Node;
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
interface NodeInterface
{

View File

@ -18,6 +18,8 @@ namespace Symfony\Component\CssSelector\Node;
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
class PseudoNode extends AbstractNode
{

View File

@ -18,6 +18,8 @@ namespace Symfony\Component\CssSelector\Node;
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
class SelectorNode extends AbstractNode
{

View File

@ -20,6 +20,8 @@ namespace Symfony\Component\CssSelector\Node;
* @see http://www.w3.org/TR/selectors/#specificity
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
class Specificity
{

View File

@ -21,6 +21,8 @@ use Symfony\Component\CssSelector\Parser\TokenStream;
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
class CommentHandler implements HandlerInterface
{

View File

@ -21,6 +21,8 @@ use Symfony\Component\CssSelector\Parser\TokenStream;
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
interface HandlerInterface
{

View File

@ -24,6 +24,8 @@ use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns;
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
class HashHandler implements HandlerInterface
{

View File

@ -24,6 +24,8 @@ use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns;
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
class IdentifierHandler implements HandlerInterface
{

View File

@ -23,6 +23,8 @@ use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns;
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
class NumberHandler implements HandlerInterface
{

View File

@ -26,6 +26,8 @@ use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns;
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
class StringHandler implements HandlerInterface
{

View File

@ -22,6 +22,8 @@ use Symfony\Component\CssSelector\Parser\TokenStream;
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
class WhitespaceHandler implements HandlerInterface
{

View File

@ -22,6 +22,8 @@ use Symfony\Component\CssSelector\Parser\Tokenizer\Tokenizer;
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
class Parser implements ParserInterface
{

View File

@ -20,6 +20,8 @@ use Symfony\Component\CssSelector\Node\SelectorNode;
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
interface ParserInterface
{

View File

@ -18,6 +18,8 @@ namespace Symfony\Component\CssSelector\Parser;
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
class Reader
{

View File

@ -23,6 +23,8 @@ use Symfony\Component\CssSelector\Parser\ParserInterface;
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
class ClassParser implements ParserInterface
{

View File

@ -22,6 +22,8 @@ use Symfony\Component\CssSelector\Parser\ParserInterface;
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
class ElementParser implements ParserInterface
{

View File

@ -26,6 +26,8 @@ use Symfony\Component\CssSelector\Parser\ParserInterface;
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
class EmptyStringParser implements ParserInterface
{

View File

@ -23,6 +23,8 @@ use Symfony\Component\CssSelector\Parser\ParserInterface;
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
class HashParser implements ParserInterface
{

View File

@ -18,6 +18,8 @@ namespace Symfony\Component\CssSelector\Parser;
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
class Token
{

View File

@ -21,6 +21,8 @@ use Symfony\Component\CssSelector\Exception\SyntaxErrorException;
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
class TokenStream
{

View File

@ -23,6 +23,8 @@ use Symfony\Component\CssSelector\Parser\TokenStream;
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
class Tokenizer
{

View File

@ -18,6 +18,8 @@ namespace Symfony\Component\CssSelector\Parser\Tokenizer;
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
class TokenizerEscaping
{

View File

@ -18,6 +18,8 @@ namespace Symfony\Component\CssSelector\Parser\Tokenizer;
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
class TokenizerPatterns
{

View 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'));
}
}

View File

@ -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()

View File

@ -18,6 +18,8 @@ namespace Symfony\Component\CssSelector\XPath\Extension;
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
abstract class AbstractExtension implements ExtensionInterface
{

View File

@ -21,6 +21,8 @@ use Symfony\Component\CssSelector\XPath\XPathExpr;
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
class AttributeMatchingExtension extends AbstractExtension
{

View File

@ -20,6 +20,8 @@ use Symfony\Component\CssSelector\XPath\XPathExpr;
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
class CombinationExtension extends AbstractExtension
{

View File

@ -18,6 +18,8 @@ namespace Symfony\Component\CssSelector\XPath\Extension;
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
interface ExtensionInterface
{

View File

@ -25,6 +25,8 @@ use Symfony\Component\CssSelector\XPath\XPathExpr;
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
class FunctionExtension extends AbstractExtension
{

View File

@ -23,6 +23,8 @@ use Symfony\Component\CssSelector\XPath\XPathExpr;
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
class HtmlExtension extends AbstractExtension
{

View File

@ -22,6 +22,8 @@ use Symfony\Component\CssSelector\XPath\XPathExpr;
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
class NodeExtension extends AbstractExtension
{

View File

@ -21,6 +21,8 @@ use Symfony\Component\CssSelector\XPath\XPathExpr;
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
class PseudoClassExtension extends AbstractExtension
{

View File

@ -25,6 +25,8 @@ use Symfony\Component\CssSelector\Parser\ParserInterface;
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
class Translator implements TranslatorInterface
{

View File

@ -20,6 +20,8 @@ use Symfony\Component\CssSelector\Node\SelectorNode;
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
interface TranslatorInterface
{

View File

@ -18,6 +18,8 @@ namespace Symfony\Component\CssSelector\XPath;
* which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
class XPathExpr
{

View File

@ -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;
}
}

View File

@ -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());

View File

@ -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": ""