ヤミRoot VoidGate
User / IP
:
216.73.216.84
Host / Server
:
146.88.233.70 / dev.loger.cm
System
:
Linux hybrid1120.fr.ns.planethoster.net 3.10.0-957.21.2.el7.x86_64 #1 SMP Wed Jun 5 14:26:44 UTC 2019 x86_64
Command
|
Upload
|
Create
Mass Deface
|
Jumping
|
Symlink
|
Reverse Shell
Ping
|
Port Scan
|
DNS Lookup
|
Whois
|
Header
|
cURL
:
/
home
/
logercm
/
dev.loger.cm
/
fixtures
/
assert
/
Viewing: css-selector.tar
Exception/ExceptionInterface.php 0000644 00000001123 15120141236 0012762 0 ustar 00 <?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\Exception; /** * Interface for exceptions. * * This component is a port of the Python cssselect library, * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. * * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> */ interface ExceptionInterface extends \Throwable { } Exception/ExpressionErrorException.php 0000644 00000001201 15120141236 0014230 0 ustar 00 <?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\Exception; /** * ParseException is thrown when a CSS selector syntax is not valid. * * This component is a port of the Python cssselect library, * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. * * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> */ class ExpressionErrorException extends ParseException { } Exception/InternalErrorException.php 0000644 00000001177 15120141236 0013661 0 ustar 00 <?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\Exception; /** * ParseException is thrown when a CSS selector syntax is not valid. * * This component is a port of the Python cssselect library, * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. * * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> */ class InternalErrorException extends ParseException { } Exception/ParseException.php 0000644 00000001176 15120141236 0012144 0 ustar 00 <?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\Exception; /** * ParseException is thrown when a CSS selector syntax is not valid. * * This component is a port of the Python cssselect library, * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. * * @author Fabien Potencier <fabien@symfony.com> */ class ParseException extends \Exception implements ExceptionInterface { } Exception/SyntaxErrorException.php 0000644 00000003204 15120141236 0013364 0 ustar 00 <?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\Exception; use Symfony\Component\CssSelector\Parser\Token; /** * ParseException is thrown when a CSS selector syntax is not valid. * * This component is a port of the Python cssselect library, * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. * * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> */ class SyntaxErrorException extends ParseException { /** * @return self */ public static function unexpectedToken(string $expectedValue, Token $foundToken) { return new self(sprintf('Expected %s, but %s found.', $expectedValue, $foundToken)); } /** * @return self */ public static function pseudoElementFound(string $pseudoElement, string $unexpectedLocation) { return new self(sprintf('Unexpected pseudo-element "::%s" found %s.', $pseudoElement, $unexpectedLocation)); } /** * @return self */ public static function unclosedString(int $position) { return new self(sprintf('Unclosed/invalid string at %s.', $position)); } /** * @return self */ public static function nestedNot() { return new self('Got nested ::not().'); } /** * @return self */ public static function stringAsFunctionArgument() { return new self('String not allowed as function argument.'); } } Node/AbstractNode.php 0000644 00000001603 15120141236 0010506 0 ustar 00 <?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\Node; /** * Abstract base node class. * * This component is a port of the Python cssselect library, * 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 { /** * @var string */ private $nodeName; public function getNodeName(): string { if (null === $this->nodeName) { $this->nodeName = preg_replace('~.*\\\\([^\\\\]+)Node$~', '$1', static::class); } return $this->nodeName; } } Node/AttributeNode.php 0000644 00000004114 15120141236 0010706 0 ustar 00 <?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\Node; /** * Represents a "<selector>[<namespace>|<attribute> <operator> <value>]" node. * * This component is a port of the Python cssselect library, * 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 { private $selector; private $namespace; private $attribute; private $operator; private $value; public function __construct(NodeInterface $selector, ?string $namespace, string $attribute, string $operator, ?string $value) { $this->selector = $selector; $this->namespace = $namespace; $this->attribute = $attribute; $this->operator = $operator; $this->value = $value; } public function getSelector(): NodeInterface { return $this->selector; } public function getNamespace(): ?string { return $this->namespace; } public function getAttribute(): string { return $this->attribute; } public function getOperator(): string { return $this->operator; } public function getValue(): ?string { return $this->value; } /** * {@inheritdoc} */ public function getSpecificity(): Specificity { return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0)); } public function __toString(): string { $attribute = $this->namespace ? $this->namespace.'|'.$this->attribute : $this->attribute; return 'exists' === $this->operator ? sprintf('%s[%s[%s]]', $this->getNodeName(), $this->selector, $attribute) : sprintf("%s[%s[%s %s '%s']]", $this->getNodeName(), $this->selector, $attribute, $this->operator, $this->value); } } Node/ClassNode.php 0000644 00000002422 15120141236 0010010 0 ustar 00 <?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\Node; /** * Represents a "<selector>.<name>" node. * * This component is a port of the Python cssselect library, * 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 { private $selector; private $name; public function __construct(NodeInterface $selector, string $name) { $this->selector = $selector; $this->name = $name; } public function getSelector(): NodeInterface { return $this->selector; } public function getName(): string { return $this->name; } /** * {@inheritdoc} */ public function getSpecificity(): Specificity { return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0)); } public function __toString(): string { return sprintf('%s[%s.%s]', $this->getNodeName(), $this->selector, $this->name); } } Node/CombinedSelectorNode.php 0000644 00000003163 15120141236 0012167 0 ustar 00 <?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\Node; /** * Represents a combined node. * * This component is a port of the Python cssselect library, * 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 { private $selector; private $combinator; private $subSelector; public function __construct(NodeInterface $selector, string $combinator, NodeInterface $subSelector) { $this->selector = $selector; $this->combinator = $combinator; $this->subSelector = $subSelector; } public function getSelector(): NodeInterface { return $this->selector; } public function getCombinator(): string { return $this->combinator; } public function getSubSelector(): NodeInterface { return $this->subSelector; } /** * {@inheritdoc} */ public function getSpecificity(): Specificity { return $this->selector->getSpecificity()->plus($this->subSelector->getSpecificity()); } public function __toString(): string { $combinator = ' ' === $this->combinator ? '<followed>' : $this->combinator; return sprintf('%s[%s %s %s]', $this->getNodeName(), $this->selector, $combinator, $this->subSelector); } } Node/ElementNode.php 0000644 00000002545 15120141236 0010342 0 ustar 00 <?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\Node; /** * Represents a "<namespace>|<element>" node. * * This component is a port of the Python cssselect library, * 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 { private $namespace; private $element; public function __construct(string $namespace = null, string $element = null) { $this->namespace = $namespace; $this->element = $element; } public function getNamespace(): ?string { return $this->namespace; } public function getElement(): ?string { return $this->element; } /** * {@inheritdoc} */ public function getSpecificity(): Specificity { return new Specificity(0, 0, $this->element ? 1 : 0); } public function __toString(): string { $element = $this->element ?: '*'; return sprintf('%s[%s]', $this->getNodeName(), $this->namespace ? $this->namespace.'|'.$element : $element); } } Node/FunctionNode.php 0000644 00000003445 15120141236 0010536 0 ustar 00 <?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\Node; use Symfony\Component\CssSelector\Parser\Token; /** * Represents a "<selector>:<name>(<arguments>)" node. * * This component is a port of the Python cssselect library, * 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 { private $selector; private $name; private $arguments; /** * @param Token[] $arguments */ public function __construct(NodeInterface $selector, string $name, array $arguments = []) { $this->selector = $selector; $this->name = strtolower($name); $this->arguments = $arguments; } public function getSelector(): NodeInterface { return $this->selector; } public function getName(): string { return $this->name; } /** * @return Token[] */ public function getArguments(): array { return $this->arguments; } /** * {@inheritdoc} */ public function getSpecificity(): Specificity { return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0)); } public function __toString(): string { $arguments = implode(', ', array_map(function (Token $token) { return "'".$token->getValue()."'"; }, $this->arguments)); return sprintf('%s[%s:%s(%s)]', $this->getNodeName(), $this->selector, $this->name, $arguments ? '['.$arguments.']' : ''); } } Node/HashNode.php 0000644 00000002401 15120141236 0007623 0 ustar 00 <?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\Node; /** * Represents a "<selector>#<id>" node. * * This component is a port of the Python cssselect library, * 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 { private $selector; private $id; public function __construct(NodeInterface $selector, string $id) { $this->selector = $selector; $this->id = $id; } public function getSelector(): NodeInterface { return $this->selector; } public function getId(): string { return $this->id; } /** * {@inheritdoc} */ public function getSpecificity(): Specificity { return $this->selector->getSpecificity()->plus(new Specificity(1, 0, 0)); } public function __toString(): string { return sprintf('%s[%s#%s]', $this->getNodeName(), $this->selector, $this->id); } } Node/NegationNode.php 0000644 00000002560 15120141236 0010512 0 ustar 00 <?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\Node; /** * Represents a "<selector>:not(<identifier>)" node. * * This component is a port of the Python cssselect library, * 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 { private $selector; private $subSelector; public function __construct(NodeInterface $selector, NodeInterface $subSelector) { $this->selector = $selector; $this->subSelector = $subSelector; } public function getSelector(): NodeInterface { return $this->selector; } public function getSubSelector(): NodeInterface { return $this->subSelector; } /** * {@inheritdoc} */ public function getSpecificity(): Specificity { return $this->selector->getSpecificity()->plus($this->subSelector->getSpecificity()); } public function __toString(): string { return sprintf('%s[%s:not(%s)]', $this->getNodeName(), $this->selector, $this->subSelector); } } Node/NodeInterface.php 0000644 00000001313 15120141236 0010641 0 ustar 00 <?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\Node; /** * Interface for nodes. * * This component is a port of the Python cssselect library, * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. * * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> * * @internal */ interface NodeInterface { public function getNodeName(): string; public function getSpecificity(): Specificity; public function __toString(): string; } Node/PseudoNode.php 0000644 00000002517 15120141236 0010207 0 ustar 00 <?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\Node; /** * Represents a "<selector>:<identifier>" node. * * This component is a port of the Python cssselect library, * 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 { private $selector; private $identifier; public function __construct(NodeInterface $selector, string $identifier) { $this->selector = $selector; $this->identifier = strtolower($identifier); } public function getSelector(): NodeInterface { return $this->selector; } public function getIdentifier(): string { return $this->identifier; } /** * {@inheritdoc} */ public function getSpecificity(): Specificity { return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0)); } public function __toString(): string { return sprintf('%s[%s:%s]', $this->getNodeName(), $this->selector, $this->identifier); } } Node/SelectorNode.php 0000644 00000002651 15120141236 0010527 0 ustar 00 <?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\Node; /** * Represents a "<selector>(::|:)<pseudoElement>" node. * * This component is a port of the Python cssselect library, * 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 { private $tree; private $pseudoElement; public function __construct(NodeInterface $tree, string $pseudoElement = null) { $this->tree = $tree; $this->pseudoElement = $pseudoElement ? strtolower($pseudoElement) : null; } public function getTree(): NodeInterface { return $this->tree; } public function getPseudoElement(): ?string { return $this->pseudoElement; } /** * {@inheritdoc} */ public function getSpecificity(): Specificity { return $this->tree->getSpecificity()->plus(new Specificity(0, 0, $this->pseudoElement ? 1 : 0)); } public function __toString(): string { return sprintf('%s[%s%s]', $this->getNodeName(), $this->tree, $this->pseudoElement ? '::'.$this->pseudoElement : ''); } } Node/Specificity.php 0000644 00000003414 15120141236 0010412 0 ustar 00 <?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\Node; /** * Represents a node specificity. * * This component is a port of the Python cssselect library, * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. * * @see http://www.w3.org/TR/selectors/#specificity * * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> * * @internal */ class Specificity { public const A_FACTOR = 100; public const B_FACTOR = 10; public const C_FACTOR = 1; private $a; private $b; private $c; public function __construct(int $a, int $b, int $c) { $this->a = $a; $this->b = $b; $this->c = $c; } public function plus(self $specificity): self { return new self($this->a + $specificity->a, $this->b + $specificity->b, $this->c + $specificity->c); } public function getValue(): int { return $this->a * self::A_FACTOR + $this->b * self::B_FACTOR + $this->c * self::C_FACTOR; } /** * Returns -1 if the object specificity is lower than the argument, * 0 if they are equal, and 1 if the argument is lower. */ public function compareTo(self $specificity): int { if ($this->a !== $specificity->a) { return $this->a > $specificity->a ? 1 : -1; } if ($this->b !== $specificity->b) { return $this->b > $specificity->b ? 1 : -1; } if ($this->c !== $specificity->c) { return $this->c > $specificity->c ? 1 : -1; } return 0; } } Parser/Handler/CommentHandler.php 0000644 00000002162 15120141236 0012762 0 ustar 00 <?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\Parser\Handler; use Symfony\Component\CssSelector\Parser\Reader; use Symfony\Component\CssSelector\Parser\TokenStream; /** * CSS selector comment handler. * * This component is a port of the Python cssselect library, * 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 { /** * {@inheritdoc} */ public function handle(Reader $reader, TokenStream $stream): bool { if ('/*' !== $reader->getSubstring(2)) { return false; } $offset = $reader->getOffset('*/'); if (false === $offset) { $reader->moveToEnd(); } else { $reader->moveForward($offset + 2); } return true; } } Parser/Handler/HandlerInterface.php 0000644 00000001410 15120141236 0013253 0 ustar 00 <?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\Parser\Handler; use Symfony\Component\CssSelector\Parser\Reader; use Symfony\Component\CssSelector\Parser\TokenStream; /** * CSS selector handler interface. * * This component is a port of the Python cssselect library, * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. * * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> * * @internal */ interface HandlerInterface { public function handle(Reader $reader, TokenStream $stream): bool; } Parser/Handler/HashHandler.php 0000644 00000003104 15120141236 0012240 0 ustar 00 <?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\Parser\Handler; use Symfony\Component\CssSelector\Parser\Reader; use Symfony\Component\CssSelector\Parser\Token; use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerEscaping; use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns; use Symfony\Component\CssSelector\Parser\TokenStream; /** * CSS selector comment handler. * * This component is a port of the Python cssselect library, * 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 { private $patterns; private $escaping; public function __construct(TokenizerPatterns $patterns, TokenizerEscaping $escaping) { $this->patterns = $patterns; $this->escaping = $escaping; } /** * {@inheritdoc} */ public function handle(Reader $reader, TokenStream $stream): bool { $match = $reader->findPattern($this->patterns->getHashPattern()); if (!$match) { return false; } $value = $this->escaping->escapeUnicode($match[1]); $stream->push(new Token(Token::TYPE_HASH, $value, $reader->getPosition())); $reader->moveForward(\strlen($match[0])); return true; } } Parser/Handler/IdentifierHandler.php 0000644 00000003126 15120141236 0013443 0 ustar 00 <?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\Parser\Handler; use Symfony\Component\CssSelector\Parser\Reader; use Symfony\Component\CssSelector\Parser\Token; use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerEscaping; use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns; use Symfony\Component\CssSelector\Parser\TokenStream; /** * CSS selector comment handler. * * This component is a port of the Python cssselect library, * 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 { private $patterns; private $escaping; public function __construct(TokenizerPatterns $patterns, TokenizerEscaping $escaping) { $this->patterns = $patterns; $this->escaping = $escaping; } /** * {@inheritdoc} */ public function handle(Reader $reader, TokenStream $stream): bool { $match = $reader->findPattern($this->patterns->getIdentifierPattern()); if (!$match) { return false; } $value = $this->escaping->escapeUnicode($match[0]); $stream->push(new Token(Token::TYPE_IDENTIFIER, $value, $reader->getPosition())); $reader->moveForward(\strlen($match[0])); return true; } } Parser/Handler/NumberHandler.php 0000644 00000002562 15120141236 0012614 0 ustar 00 <?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\Parser\Handler; use Symfony\Component\CssSelector\Parser\Reader; use Symfony\Component\CssSelector\Parser\Token; use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns; use Symfony\Component\CssSelector\Parser\TokenStream; /** * CSS selector comment handler. * * This component is a port of the Python cssselect library, * 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 { private $patterns; public function __construct(TokenizerPatterns $patterns) { $this->patterns = $patterns; } /** * {@inheritdoc} */ public function handle(Reader $reader, TokenStream $stream): bool { $match = $reader->findPattern($this->patterns->getNumberPattern()); if (!$match) { return false; } $stream->push(new Token(Token::TYPE_NUMBER, $match[0], $reader->getPosition())); $reader->moveForward(\strlen($match[0])); return true; } } Parser/Handler/StringHandler.php 0000644 00000004607 15120141236 0012634 0 ustar 00 <?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\Parser\Handler; use Symfony\Component\CssSelector\Exception\InternalErrorException; use Symfony\Component\CssSelector\Exception\SyntaxErrorException; use Symfony\Component\CssSelector\Parser\Reader; use Symfony\Component\CssSelector\Parser\Token; use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerEscaping; use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns; use Symfony\Component\CssSelector\Parser\TokenStream; /** * CSS selector comment handler. * * This component is a port of the Python cssselect library, * 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 { private $patterns; private $escaping; public function __construct(TokenizerPatterns $patterns, TokenizerEscaping $escaping) { $this->patterns = $patterns; $this->escaping = $escaping; } /** * {@inheritdoc} */ public function handle(Reader $reader, TokenStream $stream): bool { $quote = $reader->getSubstring(1); if (!\in_array($quote, ["'", '"'])) { return false; } $reader->moveForward(1); $match = $reader->findPattern($this->patterns->getQuotedStringPattern($quote)); if (!$match) { throw new InternalErrorException(sprintf('Should have found at least an empty match at %d.', $reader->getPosition())); } // check unclosed strings if (\strlen($match[0]) === $reader->getRemainingLength()) { throw SyntaxErrorException::unclosedString($reader->getPosition() - 1); } // check quotes pairs validity if ($quote !== $reader->getSubstring(1, \strlen($match[0]))) { throw SyntaxErrorException::unclosedString($reader->getPosition() - 1); } $string = $this->escaping->escapeUnicodeAndNewLine($match[0]); $stream->push(new Token(Token::TYPE_STRING, $string, $reader->getPosition())); $reader->moveForward(\strlen($match[0]) + 1); return true; } } Parser/Handler/WhitespaceHandler.php 0000644 00000002247 15120141236 0013460 0 ustar 00 <?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\Parser\Handler; use Symfony\Component\CssSelector\Parser\Reader; use Symfony\Component\CssSelector\Parser\Token; use Symfony\Component\CssSelector\Parser\TokenStream; /** * CSS selector whitespace handler. * * This component is a port of the Python cssselect library, * 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 { /** * {@inheritdoc} */ public function handle(Reader $reader, TokenStream $stream): bool { $match = $reader->findPattern('~^[ \t\r\n\f]+~'); if (false === $match) { return false; } $stream->push(new Token(Token::TYPE_WHITESPACE, $match[0], $reader->getPosition())); $reader->moveForward(\strlen($match[0])); return true; } } Parser/Shortcut/ClassParser.php 0000644 00000003074 15120141236 0012545 0 ustar 00 <?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\Parser\Shortcut; use Symfony\Component\CssSelector\Node\ClassNode; use Symfony\Component\CssSelector\Node\ElementNode; use Symfony\Component\CssSelector\Node\SelectorNode; use Symfony\Component\CssSelector\Parser\ParserInterface; /** * CSS selector class parser shortcut. * * This component is a port of the Python cssselect library, * 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 { /** * {@inheritdoc} */ public function parse(string $source): array { // Matches an optional namespace, optional element, and required class // $source = 'test|input.ab6bd_field'; // $matches = array (size=4) // 0 => string 'test|input.ab6bd_field' (length=22) // 1 => string 'test' (length=4) // 2 => string 'input' (length=5) // 3 => string 'ab6bd_field' (length=11) if (preg_match('/^(?:([a-z]++)\|)?+([\w-]++|\*)?+\.([\w-]++)$/i', trim($source), $matches)) { return [ new SelectorNode(new ClassNode(new ElementNode($matches[1] ?: null, $matches[2] ?: null), $matches[3])), ]; } return []; } } Parser/Shortcut/ElementParser.php 0000644 00000002554 15120141236 0013073 0 ustar 00 <?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\Parser\Shortcut; use Symfony\Component\CssSelector\Node\ElementNode; use Symfony\Component\CssSelector\Node\SelectorNode; use Symfony\Component\CssSelector\Parser\ParserInterface; /** * CSS selector element parser shortcut. * * This component is a port of the Python cssselect library, * 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 { /** * {@inheritdoc} */ public function parse(string $source): array { // Matches an optional namespace, required element or `*` // $source = 'testns|testel'; // $matches = array (size=3) // 0 => string 'testns|testel' (length=13) // 1 => string 'testns' (length=6) // 2 => string 'testel' (length=6) if (preg_match('/^(?:([a-z]++)\|)?([\w-]++|\*)$/i', trim($source), $matches)) { return [new SelectorNode(new ElementNode($matches[1] ?: null, $matches[2]))]; } return []; } } Parser/Shortcut/EmptyStringParser.php 0000644 00000002316 15120141236 0013763 0 ustar 00 <?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\Parser\Shortcut; use Symfony\Component\CssSelector\Node\ElementNode; use Symfony\Component\CssSelector\Node\SelectorNode; use Symfony\Component\CssSelector\Parser\ParserInterface; /** * CSS selector class parser shortcut. * * This shortcut ensure compatibility with previous version. * - The parser fails to parse an empty string. * - In the previous version, an empty string matches each tags. * * This component is a port of the Python cssselect library, * 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 { /** * {@inheritdoc} */ public function parse(string $source): array { // Matches an empty string if ('' == $source) { return [new SelectorNode(new ElementNode(null, '*'))]; } return []; } } Parser/Shortcut/HashParser.php 0000644 00000003064 15120141236 0012362 0 ustar 00 <?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\Parser\Shortcut; use Symfony\Component\CssSelector\Node\ElementNode; use Symfony\Component\CssSelector\Node\HashNode; use Symfony\Component\CssSelector\Node\SelectorNode; use Symfony\Component\CssSelector\Parser\ParserInterface; /** * CSS selector hash parser shortcut. * * This component is a port of the Python cssselect library, * 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 { /** * {@inheritdoc} */ public function parse(string $source): array { // Matches an optional namespace, optional element, and required id // $source = 'test|input#ab6bd_field'; // $matches = array (size=4) // 0 => string 'test|input#ab6bd_field' (length=22) // 1 => string 'test' (length=4) // 2 => string 'input' (length=5) // 3 => string 'ab6bd_field' (length=11) if (preg_match('/^(?:([a-z]++)\|)?+([\w-]++|\*)?+#([\w-]++)$/i', trim($source), $matches)) { return [ new SelectorNode(new HashNode(new ElementNode($matches[1] ?: null, $matches[2] ?: null), $matches[3])), ]; } return []; } } Parser/Tokenizer/Tokenizer.php 0000644 00000003762 15120141236 0012440 0 ustar 00 <?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\Parser\Tokenizer; use Symfony\Component\CssSelector\Parser\Handler; use Symfony\Component\CssSelector\Parser\Reader; use Symfony\Component\CssSelector\Parser\Token; use Symfony\Component\CssSelector\Parser\TokenStream; /** * CSS selector tokenizer. * * This component is a port of the Python cssselect library, * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. * * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> * * @internal */ class Tokenizer { /** * @var Handler\HandlerInterface[] */ private $handlers; public function __construct() { $patterns = new TokenizerPatterns(); $escaping = new TokenizerEscaping($patterns); $this->handlers = [ new Handler\WhitespaceHandler(), new Handler\IdentifierHandler($patterns, $escaping), new Handler\HashHandler($patterns, $escaping), new Handler\StringHandler($patterns, $escaping), new Handler\NumberHandler($patterns), new Handler\CommentHandler(), ]; } /** * Tokenize selector source code. */ public function tokenize(Reader $reader): TokenStream { $stream = new TokenStream(); while (!$reader->isEOF()) { foreach ($this->handlers as $handler) { if ($handler->handle($reader, $stream)) { continue 2; } } $stream->push(new Token(Token::TYPE_DELIMITER, $reader->getSubstring(1), $reader->getPosition())); $reader->moveForward(1); } return $stream ->push(new Token(Token::TYPE_FILE_END, null, $reader->getPosition())) ->freeze(); } } Parser/Tokenizer/TokenizerEscaping.php 0000644 00000003360 15120141236 0014104 0 ustar 00 <?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\Parser\Tokenizer; /** * CSS selector tokenizer escaping applier. * * This component is a port of the Python cssselect library, * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. * * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> * * @internal */ class TokenizerEscaping { private $patterns; public function __construct(TokenizerPatterns $patterns) { $this->patterns = $patterns; } public function escapeUnicode(string $value): string { $value = $this->replaceUnicodeSequences($value); return preg_replace($this->patterns->getSimpleEscapePattern(), '$1', $value); } public function escapeUnicodeAndNewLine(string $value): string { $value = preg_replace($this->patterns->getNewLineEscapePattern(), '', $value); return $this->escapeUnicode($value); } private function replaceUnicodeSequences(string $value): string { return preg_replace_callback($this->patterns->getUnicodeEscapePattern(), function ($match) { $c = hexdec($match[1]); if (0x80 > $c %= 0x200000) { return \chr($c); } if (0x800 > $c) { return \chr(0xC0 | $c >> 6).\chr(0x80 | $c & 0x3F); } if (0x10000 > $c) { return \chr(0xE0 | $c >> 12).\chr(0x80 | $c >> 6 & 0x3F).\chr(0x80 | $c & 0x3F); } return ''; }, $value); } } Parser/Tokenizer/TokenizerPatterns.php 0000644 00000005332 15120141236 0014154 0 ustar 00 <?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\Parser\Tokenizer; /** * CSS selector tokenizer patterns builder. * * This component is a port of the Python cssselect library, * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. * * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> * * @internal */ class TokenizerPatterns { private $unicodeEscapePattern; private $simpleEscapePattern; private $newLineEscapePattern; private $escapePattern; private $stringEscapePattern; private $nonAsciiPattern; private $nmCharPattern; private $nmStartPattern; private $identifierPattern; private $hashPattern; private $numberPattern; private $quotedStringPattern; public function __construct() { $this->unicodeEscapePattern = '\\\\([0-9a-f]{1,6})(?:\r\n|[ \n\r\t\f])?'; $this->simpleEscapePattern = '\\\\(.)'; $this->newLineEscapePattern = '\\\\(?:\n|\r\n|\r|\f)'; $this->escapePattern = $this->unicodeEscapePattern.'|\\\\[^\n\r\f0-9a-f]'; $this->stringEscapePattern = $this->newLineEscapePattern.'|'.$this->escapePattern; $this->nonAsciiPattern = '[^\x00-\x7F]'; $this->nmCharPattern = '[_a-z0-9-]|'.$this->escapePattern.'|'.$this->nonAsciiPattern; $this->nmStartPattern = '[_a-z]|'.$this->escapePattern.'|'.$this->nonAsciiPattern; $this->identifierPattern = '-?(?:'.$this->nmStartPattern.')(?:'.$this->nmCharPattern.')*'; $this->hashPattern = '#((?:'.$this->nmCharPattern.')+)'; $this->numberPattern = '[+-]?(?:[0-9]*\.[0-9]+|[0-9]+)'; $this->quotedStringPattern = '([^\n\r\f\\\\%s]|'.$this->stringEscapePattern.')*'; } public function getNewLineEscapePattern(): string { return '~'.$this->newLineEscapePattern.'~'; } public function getSimpleEscapePattern(): string { return '~'.$this->simpleEscapePattern.'~'; } public function getUnicodeEscapePattern(): string { return '~'.$this->unicodeEscapePattern.'~i'; } public function getIdentifierPattern(): string { return '~^'.$this->identifierPattern.'~i'; } public function getHashPattern(): string { return '~^'.$this->hashPattern.'~i'; } public function getNumberPattern(): string { return '~^'.$this->numberPattern.'~'; } public function getQuotedStringPattern(string $quote): string { return '~^'.sprintf($this->quotedStringPattern, $quote).'~i'; } } Parser/Parser.php 0000644 00000026542 15120141236 0007751 0 ustar 00 <?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\Parser; use Symfony\Component\CssSelector\Exception\SyntaxErrorException; use Symfony\Component\CssSelector\Node; use Symfony\Component\CssSelector\Parser\Tokenizer\Tokenizer; /** * CSS selector parser. * * This component is a port of the Python cssselect library, * 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 { private $tokenizer; public function __construct(Tokenizer $tokenizer = null) { $this->tokenizer = $tokenizer ?? new Tokenizer(); } /** * {@inheritdoc} */ public function parse(string $source): array { $reader = new Reader($source); $stream = $this->tokenizer->tokenize($reader); return $this->parseSelectorList($stream); } /** * Parses the arguments for ":nth-child()" and friends. * * @param Token[] $tokens * * @throws SyntaxErrorException */ public static function parseSeries(array $tokens): array { foreach ($tokens as $token) { if ($token->isString()) { throw SyntaxErrorException::stringAsFunctionArgument(); } } $joined = trim(implode('', array_map(function (Token $token) { return $token->getValue(); }, $tokens))); $int = function ($string) { if (!is_numeric($string)) { throw SyntaxErrorException::stringAsFunctionArgument(); } return (int) $string; }; switch (true) { case 'odd' === $joined: return [2, 1]; case 'even' === $joined: return [2, 0]; case 'n' === $joined: return [1, 0]; case !str_contains($joined, 'n'): return [0, $int($joined)]; } $split = explode('n', $joined); $first = $split[0] ?? null; return [ $first ? ('-' === $first || '+' === $first ? $int($first.'1') : $int($first)) : 1, isset($split[1]) && $split[1] ? $int($split[1]) : 0, ]; } private function parseSelectorList(TokenStream $stream): array { $stream->skipWhitespace(); $selectors = []; while (true) { $selectors[] = $this->parserSelectorNode($stream); if ($stream->getPeek()->isDelimiter([','])) { $stream->getNext(); $stream->skipWhitespace(); } else { break; } } return $selectors; } private function parserSelectorNode(TokenStream $stream): Node\SelectorNode { [$result, $pseudoElement] = $this->parseSimpleSelector($stream); while (true) { $stream->skipWhitespace(); $peek = $stream->getPeek(); if ($peek->isFileEnd() || $peek->isDelimiter([','])) { break; } if (null !== $pseudoElement) { throw SyntaxErrorException::pseudoElementFound($pseudoElement, 'not at the end of a selector'); } if ($peek->isDelimiter(['+', '>', '~'])) { $combinator = $stream->getNext()->getValue(); $stream->skipWhitespace(); } else { $combinator = ' '; } [$nextSelector, $pseudoElement] = $this->parseSimpleSelector($stream); $result = new Node\CombinedSelectorNode($result, $combinator, $nextSelector); } return new Node\SelectorNode($result, $pseudoElement); } /** * Parses next simple node (hash, class, pseudo, negation). * * @throws SyntaxErrorException */ private function parseSimpleSelector(TokenStream $stream, bool $insideNegation = false): array { $stream->skipWhitespace(); $selectorStart = \count($stream->getUsed()); $result = $this->parseElementNode($stream); $pseudoElement = null; while (true) { $peek = $stream->getPeek(); if ($peek->isWhitespace() || $peek->isFileEnd() || $peek->isDelimiter([',', '+', '>', '~']) || ($insideNegation && $peek->isDelimiter([')'])) ) { break; } if (null !== $pseudoElement) { throw SyntaxErrorException::pseudoElementFound($pseudoElement, 'not at the end of a selector'); } if ($peek->isHash()) { $result = new Node\HashNode($result, $stream->getNext()->getValue()); } elseif ($peek->isDelimiter(['.'])) { $stream->getNext(); $result = new Node\ClassNode($result, $stream->getNextIdentifier()); } elseif ($peek->isDelimiter(['['])) { $stream->getNext(); $result = $this->parseAttributeNode($result, $stream); } elseif ($peek->isDelimiter([':'])) { $stream->getNext(); if ($stream->getPeek()->isDelimiter([':'])) { $stream->getNext(); $pseudoElement = $stream->getNextIdentifier(); continue; } $identifier = $stream->getNextIdentifier(); if (\in_array(strtolower($identifier), ['first-line', 'first-letter', 'before', 'after'])) { // Special case: CSS 2.1 pseudo-elements can have a single ':'. // Any new pseudo-element must have two. $pseudoElement = $identifier; continue; } if (!$stream->getPeek()->isDelimiter(['('])) { $result = new Node\PseudoNode($result, $identifier); continue; } $stream->getNext(); $stream->skipWhitespace(); if ('not' === strtolower($identifier)) { if ($insideNegation) { throw SyntaxErrorException::nestedNot(); } [$argument, $argumentPseudoElement] = $this->parseSimpleSelector($stream, true); $next = $stream->getNext(); if (null !== $argumentPseudoElement) { throw SyntaxErrorException::pseudoElementFound($argumentPseudoElement, 'inside ::not()'); } if (!$next->isDelimiter([')'])) { throw SyntaxErrorException::unexpectedToken('")"', $next); } $result = new Node\NegationNode($result, $argument); } else { $arguments = []; $next = null; while (true) { $stream->skipWhitespace(); $next = $stream->getNext(); if ($next->isIdentifier() || $next->isString() || $next->isNumber() || $next->isDelimiter(['+', '-']) ) { $arguments[] = $next; } elseif ($next->isDelimiter([')'])) { break; } else { throw SyntaxErrorException::unexpectedToken('an argument', $next); } } if (empty($arguments)) { throw SyntaxErrorException::unexpectedToken('at least one argument', $next); } $result = new Node\FunctionNode($result, $identifier, $arguments); } } else { throw SyntaxErrorException::unexpectedToken('selector', $peek); } } if (\count($stream->getUsed()) === $selectorStart) { throw SyntaxErrorException::unexpectedToken('selector', $stream->getPeek()); } return [$result, $pseudoElement]; } private function parseElementNode(TokenStream $stream): Node\ElementNode { $peek = $stream->getPeek(); if ($peek->isIdentifier() || $peek->isDelimiter(['*'])) { if ($peek->isIdentifier()) { $namespace = $stream->getNext()->getValue(); } else { $stream->getNext(); $namespace = null; } if ($stream->getPeek()->isDelimiter(['|'])) { $stream->getNext(); $element = $stream->getNextIdentifierOrStar(); } else { $element = $namespace; $namespace = null; } } else { $element = $namespace = null; } return new Node\ElementNode($namespace, $element); } private function parseAttributeNode(Node\NodeInterface $selector, TokenStream $stream): Node\AttributeNode { $stream->skipWhitespace(); $attribute = $stream->getNextIdentifierOrStar(); if (null === $attribute && !$stream->getPeek()->isDelimiter(['|'])) { throw SyntaxErrorException::unexpectedToken('"|"', $stream->getPeek()); } if ($stream->getPeek()->isDelimiter(['|'])) { $stream->getNext(); if ($stream->getPeek()->isDelimiter(['='])) { $namespace = null; $stream->getNext(); $operator = '|='; } else { $namespace = $attribute; $attribute = $stream->getNextIdentifier(); $operator = null; } } else { $namespace = $operator = null; } if (null === $operator) { $stream->skipWhitespace(); $next = $stream->getNext(); if ($next->isDelimiter([']'])) { return new Node\AttributeNode($selector, $namespace, $attribute, 'exists', null); } elseif ($next->isDelimiter(['='])) { $operator = '='; } elseif ($next->isDelimiter(['^', '$', '*', '~', '|', '!']) && $stream->getPeek()->isDelimiter(['=']) ) { $operator = $next->getValue().'='; $stream->getNext(); } else { throw SyntaxErrorException::unexpectedToken('operator', $next); } } $stream->skipWhitespace(); $value = $stream->getNext(); if ($value->isNumber()) { // if the value is a number, it's casted into a string $value = new Token(Token::TYPE_STRING, (string) $value->getValue(), $value->getPosition()); } if (!($value->isIdentifier() || $value->isString())) { throw SyntaxErrorException::unexpectedToken('string or identifier', $value); } $stream->skipWhitespace(); $next = $stream->getNext(); if (!$next->isDelimiter([']'])) { throw SyntaxErrorException::unexpectedToken('"]"', $next); } return new Node\AttributeNode($selector, $namespace, $attribute, $operator, $value->getValue()); } } Parser/ParserInterface.php 0000644 00000001451 15120141236 0011562 0 ustar 00 <?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\Parser; use Symfony\Component\CssSelector\Node\SelectorNode; /** * CSS selector parser interface. * * This component is a port of the Python cssselect library, * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. * * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> * * @internal */ interface ParserInterface { /** * Parses given selector source into an array of tokens. * * @return SelectorNode[] */ public function parse(string $source): array; } Parser/Reader.php 0000644 00000003532 15120141236 0007711 0 ustar 00 <?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\Parser; /** * CSS selector reader. * * This component is a port of the Python cssselect library, * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. * * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> * * @internal */ class Reader { private $source; private $length; private $position = 0; public function __construct(string $source) { $this->source = $source; $this->length = \strlen($source); } public function isEOF(): bool { return $this->position >= $this->length; } public function getPosition(): int { return $this->position; } public function getRemainingLength(): int { return $this->length - $this->position; } public function getSubstring(int $length, int $offset = 0): string { return substr($this->source, $this->position + $offset, $length); } public function getOffset(string $string) { $position = strpos($this->source, $string, $this->position); return false === $position ? false : $position - $this->position; } /** * @return array|false */ public function findPattern(string $pattern) { $source = substr($this->source, $this->position); if (preg_match($pattern, $source, $matches)) { return $matches; } return false; } public function moveForward(int $length) { $this->position += $length; } public function moveToEnd() { $this->position = $this->length; } } Parser/Token.php 0000644 00000004714 15120141236 0007572 0 ustar 00 <?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\Parser; /** * CSS selector token. * * This component is a port of the Python cssselect library, * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. * * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> * * @internal */ class Token { public const TYPE_FILE_END = 'eof'; public const TYPE_DELIMITER = 'delimiter'; public const TYPE_WHITESPACE = 'whitespace'; public const TYPE_IDENTIFIER = 'identifier'; public const TYPE_HASH = 'hash'; public const TYPE_NUMBER = 'number'; public const TYPE_STRING = 'string'; private $type; private $value; private $position; public function __construct(?string $type, ?string $value, ?int $position) { $this->type = $type; $this->value = $value; $this->position = $position; } public function getType(): ?int { return $this->type; } public function getValue(): ?string { return $this->value; } public function getPosition(): ?int { return $this->position; } public function isFileEnd(): bool { return self::TYPE_FILE_END === $this->type; } public function isDelimiter(array $values = []): bool { if (self::TYPE_DELIMITER !== $this->type) { return false; } if (empty($values)) { return true; } return \in_array($this->value, $values); } public function isWhitespace(): bool { return self::TYPE_WHITESPACE === $this->type; } public function isIdentifier(): bool { return self::TYPE_IDENTIFIER === $this->type; } public function isHash(): bool { return self::TYPE_HASH === $this->type; } public function isNumber(): bool { return self::TYPE_NUMBER === $this->type; } public function isString(): bool { return self::TYPE_STRING === $this->type; } public function __toString(): string { if ($this->value) { return sprintf('<%s "%s" at %s>', $this->type, $this->value, $this->position); } return sprintf('<%s at %s>', $this->type, $this->position); } } Parser/TokenStream.php 0000644 00000006470 15120141236 0010747 0 ustar 00 <?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\Parser; use Symfony\Component\CssSelector\Exception\InternalErrorException; use Symfony\Component\CssSelector\Exception\SyntaxErrorException; /** * CSS selector token stream. * * This component is a port of the Python cssselect library, * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. * * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> * * @internal */ class TokenStream { /** * @var Token[] */ private $tokens = []; /** * @var Token[] */ private $used = []; /** * @var int */ private $cursor = 0; /** * @var Token|null */ private $peeked; /** * @var bool */ private $peeking = false; /** * Pushes a token. * * @return $this */ public function push(Token $token): self { $this->tokens[] = $token; return $this; } /** * Freezes stream. * * @return $this */ public function freeze(): self { return $this; } /** * Returns next token. * * @throws InternalErrorException If there is no more token */ public function getNext(): Token { if ($this->peeking) { $this->peeking = false; $this->used[] = $this->peeked; return $this->peeked; } if (!isset($this->tokens[$this->cursor])) { throw new InternalErrorException('Unexpected token stream end.'); } return $this->tokens[$this->cursor++]; } /** * Returns peeked token. */ public function getPeek(): Token { if (!$this->peeking) { $this->peeked = $this->getNext(); $this->peeking = true; } return $this->peeked; } /** * Returns used tokens. * * @return Token[] */ public function getUsed(): array { return $this->used; } /** * Returns next identifier token. * * @throws SyntaxErrorException If next token is not an identifier */ public function getNextIdentifier(): string { $next = $this->getNext(); if (!$next->isIdentifier()) { throw SyntaxErrorException::unexpectedToken('identifier', $next); } return $next->getValue(); } /** * Returns next identifier or null if star delimiter token is found. * * @throws SyntaxErrorException If next token is not an identifier or a star delimiter */ public function getNextIdentifierOrStar(): ?string { $next = $this->getNext(); if ($next->isIdentifier()) { return $next->getValue(); } if ($next->isDelimiter(['*'])) { return null; } throw SyntaxErrorException::unexpectedToken('identifier or "*"', $next); } /** * Skips next whitespace if any. */ public function skipWhitespace() { $peek = $this->getPeek(); if ($peek->isWhitespace()) { $this->getNext(); } } } XPath/Extension/AbstractExtension.php 0000644 00000002365 15120141236 0013716 0 ustar 00 <?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\XPath\Extension; /** * XPath expression translator abstract extension. * * This component is a port of the Python cssselect library, * 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 { /** * {@inheritdoc} */ public function getNodeTranslators(): array { return []; } /** * {@inheritdoc} */ public function getCombinationTranslators(): array { return []; } /** * {@inheritdoc} */ public function getFunctionTranslators(): array { return []; } /** * {@inheritdoc} */ public function getPseudoClassTranslators(): array { return []; } /** * {@inheritdoc} */ public function getAttributeMatchingTranslators(): array { return []; } } XPath/Extension/AttributeMatchingExtension.php 0000644 00000007356 15120141236 0015576 0 ustar 00 <?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\XPath\Extension; use Symfony\Component\CssSelector\XPath\Translator; use Symfony\Component\CssSelector\XPath\XPathExpr; /** * XPath expression translator attribute extension. * * This component is a port of the Python cssselect library, * 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 { /** * {@inheritdoc} */ public function getAttributeMatchingTranslators(): array { return [ 'exists' => [$this, 'translateExists'], '=' => [$this, 'translateEquals'], '~=' => [$this, 'translateIncludes'], '|=' => [$this, 'translateDashMatch'], '^=' => [$this, 'translatePrefixMatch'], '$=' => [$this, 'translateSuffixMatch'], '*=' => [$this, 'translateSubstringMatch'], '!=' => [$this, 'translateDifferent'], ]; } public function translateExists(XPathExpr $xpath, string $attribute, ?string $value): XPathExpr { return $xpath->addCondition($attribute); } public function translateEquals(XPathExpr $xpath, string $attribute, ?string $value): XPathExpr { return $xpath->addCondition(sprintf('%s = %s', $attribute, Translator::getXpathLiteral($value))); } public function translateIncludes(XPathExpr $xpath, string $attribute, ?string $value): XPathExpr { return $xpath->addCondition($value ? sprintf( '%1$s and contains(concat(\' \', normalize-space(%1$s), \' \'), %2$s)', $attribute, Translator::getXpathLiteral(' '.$value.' ') ) : '0'); } public function translateDashMatch(XPathExpr $xpath, string $attribute, ?string $value): XPathExpr { return $xpath->addCondition(sprintf( '%1$s and (%1$s = %2$s or starts-with(%1$s, %3$s))', $attribute, Translator::getXpathLiteral($value), Translator::getXpathLiteral($value.'-') )); } public function translatePrefixMatch(XPathExpr $xpath, string $attribute, ?string $value): XPathExpr { return $xpath->addCondition($value ? sprintf( '%1$s and starts-with(%1$s, %2$s)', $attribute, Translator::getXpathLiteral($value) ) : '0'); } public function translateSuffixMatch(XPathExpr $xpath, string $attribute, ?string $value): XPathExpr { return $xpath->addCondition($value ? sprintf( '%1$s and substring(%1$s, string-length(%1$s)-%2$s) = %3$s', $attribute, \strlen($value) - 1, Translator::getXpathLiteral($value) ) : '0'); } public function translateSubstringMatch(XPathExpr $xpath, string $attribute, ?string $value): XPathExpr { return $xpath->addCondition($value ? sprintf( '%1$s and contains(%1$s, %2$s)', $attribute, Translator::getXpathLiteral($value) ) : '0'); } public function translateDifferent(XPathExpr $xpath, string $attribute, ?string $value): XPathExpr { return $xpath->addCondition(sprintf( $value ? 'not(%1$s) or %1$s != %2$s' : '%s != %s', $attribute, Translator::getXpathLiteral($value) )); } /** * {@inheritdoc} */ public function getName(): string { return 'attribute-matching'; } } XPath/Extension/CombinationExtension.php 0000644 00000003625 15120141236 0014415 0 ustar 00 <?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\XPath\Extension; use Symfony\Component\CssSelector\XPath\XPathExpr; /** * XPath expression translator combination extension. * * This component is a port of the Python cssselect library, * 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 { /** * {@inheritdoc} */ public function getCombinationTranslators(): array { return [ ' ' => [$this, 'translateDescendant'], '>' => [$this, 'translateChild'], '+' => [$this, 'translateDirectAdjacent'], '~' => [$this, 'translateIndirectAdjacent'], ]; } public function translateDescendant(XPathExpr $xpath, XPathExpr $combinedXpath): XPathExpr { return $xpath->join('/descendant-or-self::*/', $combinedXpath); } public function translateChild(XPathExpr $xpath, XPathExpr $combinedXpath): XPathExpr { return $xpath->join('/', $combinedXpath); } public function translateDirectAdjacent(XPathExpr $xpath, XPathExpr $combinedXpath): XPathExpr { return $xpath ->join('/following-sibling::', $combinedXpath) ->addNameTest() ->addCondition('position() = 1'); } public function translateIndirectAdjacent(XPathExpr $xpath, XPathExpr $combinedXpath): XPathExpr { return $xpath->join('/following-sibling::', $combinedXpath); } /** * {@inheritdoc} */ public function getName(): string { return 'combination'; } } XPath/Extension/ExtensionInterface.php 0000644 00000003005 15120141236 0014043 0 ustar 00 <?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\XPath\Extension; /** * XPath expression translator extension interface. * * This component is a port of the Python cssselect library, * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. * * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> * * @internal */ interface ExtensionInterface { /** * Returns node translators. * * These callables will receive the node as first argument and the translator as second argument. * * @return callable[] */ public function getNodeTranslators(): array; /** * Returns combination translators. * * @return callable[] */ public function getCombinationTranslators(): array; /** * Returns function translators. * * @return callable[] */ public function getFunctionTranslators(): array; /** * Returns pseudo-class translators. * * @return callable[] */ public function getPseudoClassTranslators(): array; /** * Returns attribute operation translators. * * @return callable[] */ public function getAttributeMatchingTranslators(): array; /** * Returns extension name. */ public function getName(): string; } XPath/Extension/FunctionExtension.php 0000644 00000012154 15120141236 0013735 0 ustar 00 <?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\XPath\Extension; use Symfony\Component\CssSelector\Exception\ExpressionErrorException; use Symfony\Component\CssSelector\Exception\SyntaxErrorException; use Symfony\Component\CssSelector\Node\FunctionNode; use Symfony\Component\CssSelector\Parser\Parser; use Symfony\Component\CssSelector\XPath\Translator; use Symfony\Component\CssSelector\XPath\XPathExpr; /** * XPath expression translator function extension. * * This component is a port of the Python cssselect library, * 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 { /** * {@inheritdoc} */ public function getFunctionTranslators(): array { return [ 'nth-child' => [$this, 'translateNthChild'], 'nth-last-child' => [$this, 'translateNthLastChild'], 'nth-of-type' => [$this, 'translateNthOfType'], 'nth-last-of-type' => [$this, 'translateNthLastOfType'], 'contains' => [$this, 'translateContains'], 'lang' => [$this, 'translateLang'], ]; } /** * @throws ExpressionErrorException */ public function translateNthChild(XPathExpr $xpath, FunctionNode $function, bool $last = false, bool $addNameTest = true): XPathExpr { try { [$a, $b] = Parser::parseSeries($function->getArguments()); } catch (SyntaxErrorException $e) { throw new ExpressionErrorException(sprintf('Invalid series: "%s".', implode('", "', $function->getArguments())), 0, $e); } $xpath->addStarPrefix(); if ($addNameTest) { $xpath->addNameTest(); } if (0 === $a) { return $xpath->addCondition('position() = '.($last ? 'last() - '.($b - 1) : $b)); } if ($a < 0) { if ($b < 1) { return $xpath->addCondition('false()'); } $sign = '<='; } else { $sign = '>='; } $expr = 'position()'; if ($last) { $expr = 'last() - '.$expr; --$b; } if (0 !== $b) { $expr .= ' - '.$b; } $conditions = [sprintf('%s %s 0', $expr, $sign)]; if (1 !== $a && -1 !== $a) { $conditions[] = sprintf('(%s) mod %d = 0', $expr, $a); } return $xpath->addCondition(implode(' and ', $conditions)); // todo: handle an+b, odd, even // an+b means every-a, plus b, e.g., 2n+1 means odd // 0n+b means b // n+0 means a=1, i.e., all elements // an means every a elements, i.e., 2n means even // -n means -1n // -1n+6 means elements 6 and previous } public function translateNthLastChild(XPathExpr $xpath, FunctionNode $function): XPathExpr { return $this->translateNthChild($xpath, $function, true); } public function translateNthOfType(XPathExpr $xpath, FunctionNode $function): XPathExpr { return $this->translateNthChild($xpath, $function, false, false); } /** * @throws ExpressionErrorException */ public function translateNthLastOfType(XPathExpr $xpath, FunctionNode $function): XPathExpr { if ('*' === $xpath->getElement()) { throw new ExpressionErrorException('"*:nth-of-type()" is not implemented.'); } return $this->translateNthChild($xpath, $function, true, false); } /** * @throws ExpressionErrorException */ public function translateContains(XPathExpr $xpath, FunctionNode $function): XPathExpr { $arguments = $function->getArguments(); foreach ($arguments as $token) { if (!($token->isString() || $token->isIdentifier())) { throw new ExpressionErrorException('Expected a single string or identifier for :contains(), got '.implode(', ', $arguments)); } } return $xpath->addCondition(sprintf( 'contains(string(.), %s)', Translator::getXpathLiteral($arguments[0]->getValue()) )); } /** * @throws ExpressionErrorException */ public function translateLang(XPathExpr $xpath, FunctionNode $function): XPathExpr { $arguments = $function->getArguments(); foreach ($arguments as $token) { if (!($token->isString() || $token->isIdentifier())) { throw new ExpressionErrorException('Expected a single string or identifier for :lang(), got '.implode(', ', $arguments)); } } return $xpath->addCondition(sprintf( 'lang(%s)', Translator::getXpathLiteral($arguments[0]->getValue()) )); } /** * {@inheritdoc} */ public function getName(): string { return 'function'; } } XPath/Extension/HtmlExtension.php 0000644 00000013426 15120141236 0013057 0 ustar 00 <?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\XPath\Extension; use Symfony\Component\CssSelector\Exception\ExpressionErrorException; use Symfony\Component\CssSelector\Node\FunctionNode; use Symfony\Component\CssSelector\XPath\Translator; use Symfony\Component\CssSelector\XPath\XPathExpr; /** * XPath expression translator HTML extension. * * This component is a port of the Python cssselect library, * 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 { public function __construct(Translator $translator) { $translator ->getExtension('node') ->setFlag(NodeExtension::ELEMENT_NAME_IN_LOWER_CASE, true) ->setFlag(NodeExtension::ATTRIBUTE_NAME_IN_LOWER_CASE, true); } /** * {@inheritdoc} */ public function getPseudoClassTranslators(): array { return [ 'checked' => [$this, 'translateChecked'], 'link' => [$this, 'translateLink'], 'disabled' => [$this, 'translateDisabled'], 'enabled' => [$this, 'translateEnabled'], 'selected' => [$this, 'translateSelected'], 'invalid' => [$this, 'translateInvalid'], 'hover' => [$this, 'translateHover'], 'visited' => [$this, 'translateVisited'], ]; } /** * {@inheritdoc} */ public function getFunctionTranslators(): array { return [ 'lang' => [$this, 'translateLang'], ]; } public function translateChecked(XPathExpr $xpath): XPathExpr { return $xpath->addCondition( '(@checked ' ."and (name(.) = 'input' or name(.) = 'command')" ."and (@type = 'checkbox' or @type = 'radio'))" ); } public function translateLink(XPathExpr $xpath): XPathExpr { return $xpath->addCondition("@href and (name(.) = 'a' or name(.) = 'link' or name(.) = 'area')"); } public function translateDisabled(XPathExpr $xpath): XPathExpr { return $xpath->addCondition( '(' .'@disabled and' .'(' ."(name(.) = 'input' and @type != 'hidden')" ." or name(.) = 'button'" ." or name(.) = 'select'" ." or name(.) = 'textarea'" ." or name(.) = 'command'" ." or name(.) = 'fieldset'" ." or name(.) = 'optgroup'" ." or name(.) = 'option'" .')' .') or (' ."(name(.) = 'input' and @type != 'hidden')" ." or name(.) = 'button'" ." or name(.) = 'select'" ." or name(.) = 'textarea'" .')' .' and ancestor::fieldset[@disabled]' ); // todo: in the second half, add "and is not a descendant of that fieldset element's first legend element child, if any." } public function translateEnabled(XPathExpr $xpath): XPathExpr { return $xpath->addCondition( '(' .'@href and (' ."name(.) = 'a'" ." or name(.) = 'link'" ." or name(.) = 'area'" .')' .') or (' .'(' ."name(.) = 'command'" ." or name(.) = 'fieldset'" ." or name(.) = 'optgroup'" .')' .' and not(@disabled)' .') or (' .'(' ."(name(.) = 'input' and @type != 'hidden')" ." or name(.) = 'button'" ." or name(.) = 'select'" ." or name(.) = 'textarea'" ." or name(.) = 'keygen'" .')' .' and not (@disabled or ancestor::fieldset[@disabled])' .') or (' ."name(.) = 'option' and not(" .'@disabled or ancestor::optgroup[@disabled]' .')' .')' ); } /** * @throws ExpressionErrorException */ public function translateLang(XPathExpr $xpath, FunctionNode $function): XPathExpr { $arguments = $function->getArguments(); foreach ($arguments as $token) { if (!($token->isString() || $token->isIdentifier())) { throw new ExpressionErrorException('Expected a single string or identifier for :lang(), got '.implode(', ', $arguments)); } } return $xpath->addCondition(sprintf( 'ancestor-or-self::*[@lang][1][starts-with(concat(' ."translate(@%s, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), '-')" .', %s)]', 'lang', Translator::getXpathLiteral(strtolower($arguments[0]->getValue()).'-') )); } public function translateSelected(XPathExpr $xpath): XPathExpr { return $xpath->addCondition("(@selected and name(.) = 'option')"); } public function translateInvalid(XPathExpr $xpath): XPathExpr { return $xpath->addCondition('0'); } public function translateHover(XPathExpr $xpath): XPathExpr { return $xpath->addCondition('0'); } public function translateVisited(XPathExpr $xpath): XPathExpr { return $xpath->addCondition('0'); } /** * {@inheritdoc} */ public function getName(): string { return 'html'; } } XPath/Extension/NodeExtension.php 0000644 00000013373 15120141236 0013041 0 ustar 00 <?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\XPath\Extension; use Symfony\Component\CssSelector\Node; use Symfony\Component\CssSelector\XPath\Translator; use Symfony\Component\CssSelector\XPath\XPathExpr; /** * XPath expression translator node extension. * * This component is a port of the Python cssselect library, * 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 { public const ELEMENT_NAME_IN_LOWER_CASE = 1; public const ATTRIBUTE_NAME_IN_LOWER_CASE = 2; public const ATTRIBUTE_VALUE_IN_LOWER_CASE = 4; private $flags; public function __construct(int $flags = 0) { $this->flags = $flags; } /** * @return $this */ public function setFlag(int $flag, bool $on): self { if ($on && !$this->hasFlag($flag)) { $this->flags += $flag; } if (!$on && $this->hasFlag($flag)) { $this->flags -= $flag; } return $this; } public function hasFlag(int $flag): bool { return (bool) ($this->flags & $flag); } /** * {@inheritdoc} */ public function getNodeTranslators(): array { return [ 'Selector' => [$this, 'translateSelector'], 'CombinedSelector' => [$this, 'translateCombinedSelector'], 'Negation' => [$this, 'translateNegation'], 'Function' => [$this, 'translateFunction'], 'Pseudo' => [$this, 'translatePseudo'], 'Attribute' => [$this, 'translateAttribute'], 'Class' => [$this, 'translateClass'], 'Hash' => [$this, 'translateHash'], 'Element' => [$this, 'translateElement'], ]; } public function translateSelector(Node\SelectorNode $node, Translator $translator): XPathExpr { return $translator->nodeToXPath($node->getTree()); } public function translateCombinedSelector(Node\CombinedSelectorNode $node, Translator $translator): XPathExpr { return $translator->addCombination($node->getCombinator(), $node->getSelector(), $node->getSubSelector()); } public function translateNegation(Node\NegationNode $node, Translator $translator): XPathExpr { $xpath = $translator->nodeToXPath($node->getSelector()); $subXpath = $translator->nodeToXPath($node->getSubSelector()); $subXpath->addNameTest(); if ($subXpath->getCondition()) { return $xpath->addCondition(sprintf('not(%s)', $subXpath->getCondition())); } return $xpath->addCondition('0'); } public function translateFunction(Node\FunctionNode $node, Translator $translator): XPathExpr { $xpath = $translator->nodeToXPath($node->getSelector()); return $translator->addFunction($xpath, $node); } public function translatePseudo(Node\PseudoNode $node, Translator $translator): XPathExpr { $xpath = $translator->nodeToXPath($node->getSelector()); return $translator->addPseudoClass($xpath, $node->getIdentifier()); } public function translateAttribute(Node\AttributeNode $node, Translator $translator): XPathExpr { $name = $node->getAttribute(); $safe = $this->isSafeName($name); if ($this->hasFlag(self::ATTRIBUTE_NAME_IN_LOWER_CASE)) { $name = strtolower($name); } if ($node->getNamespace()) { $name = sprintf('%s:%s', $node->getNamespace(), $name); $safe = $safe && $this->isSafeName($node->getNamespace()); } $attribute = $safe ? '@'.$name : sprintf('attribute::*[name() = %s]', Translator::getXpathLiteral($name)); $value = $node->getValue(); $xpath = $translator->nodeToXPath($node->getSelector()); if ($this->hasFlag(self::ATTRIBUTE_VALUE_IN_LOWER_CASE)) { $value = strtolower($value); } return $translator->addAttributeMatching($xpath, $node->getOperator(), $attribute, $value); } public function translateClass(Node\ClassNode $node, Translator $translator): XPathExpr { $xpath = $translator->nodeToXPath($node->getSelector()); return $translator->addAttributeMatching($xpath, '~=', '@class', $node->getName()); } public function translateHash(Node\HashNode $node, Translator $translator): XPathExpr { $xpath = $translator->nodeToXPath($node->getSelector()); return $translator->addAttributeMatching($xpath, '=', '@id', $node->getId()); } public function translateElement(Node\ElementNode $node): XPathExpr { $element = $node->getElement(); if ($element && $this->hasFlag(self::ELEMENT_NAME_IN_LOWER_CASE)) { $element = strtolower($element); } if ($element) { $safe = $this->isSafeName($element); } else { $element = '*'; $safe = true; } if ($node->getNamespace()) { $element = sprintf('%s:%s', $node->getNamespace(), $element); $safe = $safe && $this->isSafeName($node->getNamespace()); } $xpath = new XPathExpr('', $element); if (!$safe) { $xpath->addNameTest(); } return $xpath; } /** * {@inheritdoc} */ public function getName(): string { return 'node'; } private function isSafeName(string $name): bool { return 0 < preg_match('~^[a-zA-Z_][a-zA-Z0-9_.-]*$~', $name); } } XPath/Extension/PseudoClassExtension.php 0000644 00000006525 15120141236 0014402 0 ustar 00 <?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\XPath\Extension; use Symfony\Component\CssSelector\Exception\ExpressionErrorException; use Symfony\Component\CssSelector\XPath\XPathExpr; /** * XPath expression translator pseudo-class extension. * * This component is a port of the Python cssselect library, * 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 { /** * {@inheritdoc} */ public function getPseudoClassTranslators(): array { return [ 'root' => [$this, 'translateRoot'], 'first-child' => [$this, 'translateFirstChild'], 'last-child' => [$this, 'translateLastChild'], 'first-of-type' => [$this, 'translateFirstOfType'], 'last-of-type' => [$this, 'translateLastOfType'], 'only-child' => [$this, 'translateOnlyChild'], 'only-of-type' => [$this, 'translateOnlyOfType'], 'empty' => [$this, 'translateEmpty'], ]; } public function translateRoot(XPathExpr $xpath): XPathExpr { return $xpath->addCondition('not(parent::*)'); } public function translateFirstChild(XPathExpr $xpath): XPathExpr { return $xpath ->addStarPrefix() ->addNameTest() ->addCondition('position() = 1'); } public function translateLastChild(XPathExpr $xpath): XPathExpr { return $xpath ->addStarPrefix() ->addNameTest() ->addCondition('position() = last()'); } /** * @throws ExpressionErrorException */ public function translateFirstOfType(XPathExpr $xpath): XPathExpr { if ('*' === $xpath->getElement()) { throw new ExpressionErrorException('"*:first-of-type" is not implemented.'); } return $xpath ->addStarPrefix() ->addCondition('position() = 1'); } /** * @throws ExpressionErrorException */ public function translateLastOfType(XPathExpr $xpath): XPathExpr { if ('*' === $xpath->getElement()) { throw new ExpressionErrorException('"*:last-of-type" is not implemented.'); } return $xpath ->addStarPrefix() ->addCondition('position() = last()'); } public function translateOnlyChild(XPathExpr $xpath): XPathExpr { return $xpath ->addStarPrefix() ->addNameTest() ->addCondition('last() = 1'); } public function translateOnlyOfType(XPathExpr $xpath): XPathExpr { $element = $xpath->getElement(); return $xpath->addCondition(sprintf('count(preceding-sibling::%s)=0 and count(following-sibling::%s)=0', $element, $element)); } public function translateEmpty(XPathExpr $xpath): XPathExpr { return $xpath->addCondition('not(*) and not(string-length())'); } /** * {@inheritdoc} */ public function getName(): string { return 'pseudo-class'; } } XPath/Translator.php 0000644 00000016237 15120141236 0010436 0 ustar 00 <?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\XPath; use Symfony\Component\CssSelector\Exception\ExpressionErrorException; use Symfony\Component\CssSelector\Node\FunctionNode; use Symfony\Component\CssSelector\Node\NodeInterface; use Symfony\Component\CssSelector\Node\SelectorNode; use Symfony\Component\CssSelector\Parser\Parser; use Symfony\Component\CssSelector\Parser\ParserInterface; /** * XPath expression translator interface. * * This component is a port of the Python cssselect library, * 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 { private $mainParser; /** * @var ParserInterface[] */ private $shortcutParsers = []; /** * @var Extension\ExtensionInterface[] */ private $extensions = []; private $nodeTranslators = []; private $combinationTranslators = []; private $functionTranslators = []; private $pseudoClassTranslators = []; private $attributeMatchingTranslators = []; public function __construct(ParserInterface $parser = null) { $this->mainParser = $parser ?? new Parser(); $this ->registerExtension(new Extension\NodeExtension()) ->registerExtension(new Extension\CombinationExtension()) ->registerExtension(new Extension\FunctionExtension()) ->registerExtension(new Extension\PseudoClassExtension()) ->registerExtension(new Extension\AttributeMatchingExtension()) ; } public static function getXpathLiteral(string $element): string { if (!str_contains($element, "'")) { return "'".$element."'"; } if (!str_contains($element, '"')) { return '"'.$element.'"'; } $string = $element; $parts = []; while (true) { if (false !== $pos = strpos($string, "'")) { $parts[] = sprintf("'%s'", substr($string, 0, $pos)); $parts[] = "\"'\""; $string = substr($string, $pos + 1); } else { $parts[] = "'$string'"; break; } } return sprintf('concat(%s)', implode(', ', $parts)); } /** * {@inheritdoc} */ public function cssToXPath(string $cssExpr, string $prefix = 'descendant-or-self::'): string { $selectors = $this->parseSelectors($cssExpr); /** @var SelectorNode $selector */ foreach ($selectors as $index => $selector) { if (null !== $selector->getPseudoElement()) { throw new ExpressionErrorException('Pseudo-elements are not supported.'); } $selectors[$index] = $this->selectorToXPath($selector, $prefix); } return implode(' | ', $selectors); } /** * {@inheritdoc} */ public function selectorToXPath(SelectorNode $selector, string $prefix = 'descendant-or-self::'): string { return ($prefix ?: '').$this->nodeToXPath($selector); } /** * @return $this */ public function registerExtension(Extension\ExtensionInterface $extension): self { $this->extensions[$extension->getName()] = $extension; $this->nodeTranslators = array_merge($this->nodeTranslators, $extension->getNodeTranslators()); $this->combinationTranslators = array_merge($this->combinationTranslators, $extension->getCombinationTranslators()); $this->functionTranslators = array_merge($this->functionTranslators, $extension->getFunctionTranslators()); $this->pseudoClassTranslators = array_merge($this->pseudoClassTranslators, $extension->getPseudoClassTranslators()); $this->attributeMatchingTranslators = array_merge($this->attributeMatchingTranslators, $extension->getAttributeMatchingTranslators()); return $this; } /** * @throws ExpressionErrorException */ public function getExtension(string $name): Extension\ExtensionInterface { if (!isset($this->extensions[$name])) { throw new ExpressionErrorException(sprintf('Extension "%s" not registered.', $name)); } return $this->extensions[$name]; } /** * @return $this */ public function registerParserShortcut(ParserInterface $shortcut): self { $this->shortcutParsers[] = $shortcut; return $this; } /** * @throws ExpressionErrorException */ public function nodeToXPath(NodeInterface $node): XPathExpr { if (!isset($this->nodeTranslators[$node->getNodeName()])) { throw new ExpressionErrorException(sprintf('Node "%s" not supported.', $node->getNodeName())); } return $this->nodeTranslators[$node->getNodeName()]($node, $this); } /** * @throws ExpressionErrorException */ public function addCombination(string $combiner, NodeInterface $xpath, NodeInterface $combinedXpath): XPathExpr { if (!isset($this->combinationTranslators[$combiner])) { throw new ExpressionErrorException(sprintf('Combiner "%s" not supported.', $combiner)); } return $this->combinationTranslators[$combiner]($this->nodeToXPath($xpath), $this->nodeToXPath($combinedXpath)); } /** * @throws ExpressionErrorException */ public function addFunction(XPathExpr $xpath, FunctionNode $function): XPathExpr { if (!isset($this->functionTranslators[$function->getName()])) { throw new ExpressionErrorException(sprintf('Function "%s" not supported.', $function->getName())); } return $this->functionTranslators[$function->getName()]($xpath, $function); } /** * @throws ExpressionErrorException */ public function addPseudoClass(XPathExpr $xpath, string $pseudoClass): XPathExpr { if (!isset($this->pseudoClassTranslators[$pseudoClass])) { throw new ExpressionErrorException(sprintf('Pseudo-class "%s" not supported.', $pseudoClass)); } return $this->pseudoClassTranslators[$pseudoClass]($xpath); } /** * @throws ExpressionErrorException */ public function addAttributeMatching(XPathExpr $xpath, string $operator, string $attribute, ?string $value): XPathExpr { if (!isset($this->attributeMatchingTranslators[$operator])) { throw new ExpressionErrorException(sprintf('Attribute matcher operator "%s" not supported.', $operator)); } return $this->attributeMatchingTranslators[$operator]($xpath, $attribute, $value); } /** * @return SelectorNode[] */ private function parseSelectors(string $css): array { foreach ($this->shortcutParsers as $shortcut) { $tokens = $shortcut->parse($css); if (!empty($tokens)) { return $tokens; } } return $this->mainParser->parse($css); } } XPath/TranslatorInterface.php 0000644 00000001773 15120141236 0012256 0 ustar 00 <?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\XPath; use Symfony\Component\CssSelector\Node\SelectorNode; /** * XPath expression translator interface. * * This component is a port of the Python cssselect library, * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. * * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> * * @internal */ interface TranslatorInterface { /** * Translates a CSS selector to an XPath expression. */ public function cssToXPath(string $cssExpr, string $prefix = 'descendant-or-self::'): string; /** * Translates a parsed selector node to an XPath expression. */ public function selectorToXPath(SelectorNode $selector, string $prefix = 'descendant-or-self::'): string; } XPath/XPathExpr.php 0000644 00000004670 15120141236 0010166 0 ustar 00 <?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\XPath; /** * XPath expression translator interface. * * This component is a port of the Python cssselect library, * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. * * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> * * @internal */ class XPathExpr { private $path; private $element; private $condition; public function __construct(string $path = '', string $element = '*', string $condition = '', bool $starPrefix = false) { $this->path = $path; $this->element = $element; $this->condition = $condition; if ($starPrefix) { $this->addStarPrefix(); } } public function getElement(): string { return $this->element; } /** * @return $this */ public function addCondition(string $condition): self { $this->condition = $this->condition ? sprintf('(%s) and (%s)', $this->condition, $condition) : $condition; return $this; } public function getCondition(): string { return $this->condition; } /** * @return $this */ public function addNameTest(): self { if ('*' !== $this->element) { $this->addCondition('name() = '.Translator::getXpathLiteral($this->element)); $this->element = '*'; } return $this; } /** * @return $this */ public function addStarPrefix(): self { $this->path .= '*/'; return $this; } /** * Joins another XPathExpr with a combiner. * * @return $this */ public function join(string $combiner, self $expr): self { $path = $this->__toString().$combiner; if ('*/' !== $expr->path) { $path .= $expr->path; } $this->path = $path; $this->element = $expr->element; $this->condition = $expr->condition; return $this; } public function __toString(): string { $path = $this->path.$this->element; $condition = null === $this->condition || '' === $this->condition ? '' : '['.$this->condition.']'; return $path.$condition; } } CHANGELOG.md 0000644 00000000372 15120141236 0006352 0 ustar 00 CHANGELOG ========= 4.4.0 ----- * Added support for `*:only-of-type` 2.8.0 ----- * Added the `CssSelectorConverter` class as a non-static API for the component. * Deprecated the `CssSelector` static API of the component. 2.1.0 ----- * none CssSelectorConverter.php 0000644 00000004151 15120141236 0011372 0 ustar 00 <?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; /** * CssSelectorConverter is the main entry point of the component and can convert CSS * selectors to XPath expressions. * * @author Christophe Coevoet <stof@notk.org> */ class CssSelectorConverter { private $translator; private $cache; private static $xmlCache = []; private static $htmlCache = []; /** * @param bool $html Whether HTML support should be enabled. Disable it for XML documents */ public function __construct(bool $html = true) { $this->translator = new Translator(); if ($html) { $this->translator->registerExtension(new HtmlExtension($this->translator)); $this->cache = &self::$htmlCache; } else { $this->cache = &self::$xmlCache; } $this->translator ->registerParserShortcut(new EmptyStringParser()) ->registerParserShortcut(new ElementParser()) ->registerParserShortcut(new ClassParser()) ->registerParserShortcut(new HashParser()) ; } /** * Translates a CSS expression to its XPath equivalent. * * Optionally, a prefix can be added to the resulting XPath * expression with the $prefix parameter. * * @return string */ public function toXPath(string $cssExpr, string $prefix = 'descendant-or-self::') { return $this->cache[$prefix][$cssExpr] ?? $this->cache[$prefix][$cssExpr] = $this->translator->cssToXPath($cssExpr, $prefix); } } LICENSE 0000644 00000002054 15120141236 0005545 0 ustar 00 Copyright (c) 2004-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. README.md 0000644 00000001260 15120141236 0006015 0 ustar 00 CssSelector Component ===================== The CssSelector component converts CSS selectors to XPath expressions. Resources --------- * [Documentation](https://symfony.com/doc/current/components/css_selector.html) * [Contributing](https://symfony.com/doc/current/contributing/index.html) * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) in the [main Symfony repository](https://github.com/symfony/symfony) Credits ------- This component is a port of the Python cssselect library [v0.7.1](https://github.com/SimonSapin/cssselect/releases/tag/v0.7.1), which is distributed under the BSD license. composer.json 0000644 00000001530 15120141236 0007260 0 ustar 00 { "name": "symfony/css-selector", "type": "library", "description": "Converts CSS selectors to XPath expressions", "keywords": [], "homepage": "https://symfony.com", "license": "MIT", "authors": [ { "name": "Fabien Potencier", "email": "fabien@symfony.com" }, { "name": "Jean-François Simon", "email": "jeanfrancois.simon@sensiolabs.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], "require": { "php": ">=7.2.5", "symfony/polyfill-php80": "^1.16" }, "autoload": { "psr-4": { "Symfony\\Component\\CssSelector\\": "" }, "exclude-from-classmap": [ "/Tests/" ] }, "minimum-stability": "dev" }
Coded With 💗 by
0x6ick