This commit is contained in:
Paolo A
2024-08-13 13:44:16 +00:00
parent 1bbb23088d
commit e796d76612
4001 changed files with 30101 additions and 40075 deletions

View File

@@ -26,9 +26,6 @@ use Symfony\Component\CssSelector\Parser\TokenStream;
*/
class CommentHandler implements HandlerInterface
{
/**
* {@inheritdoc}
*/
public function handle(Reader $reader, TokenStream $stream): bool
{
if ('/*' !== $reader->getSubstring(2)) {

View File

@@ -29,18 +29,12 @@ use Symfony\Component\CssSelector\Parser\TokenStream;
*/
class HashHandler implements HandlerInterface
{
private $patterns;
private $escaping;
public function __construct(TokenizerPatterns $patterns, TokenizerEscaping $escaping)
{
$this->patterns = $patterns;
$this->escaping = $escaping;
public function __construct(
private TokenizerPatterns $patterns,
private TokenizerEscaping $escaping,
) {
}
/**
* {@inheritdoc}
*/
public function handle(Reader $reader, TokenStream $stream): bool
{
$match = $reader->findPattern($this->patterns->getHashPattern());

View File

@@ -29,18 +29,12 @@ use Symfony\Component\CssSelector\Parser\TokenStream;
*/
class IdentifierHandler implements HandlerInterface
{
private $patterns;
private $escaping;
public function __construct(TokenizerPatterns $patterns, TokenizerEscaping $escaping)
{
$this->patterns = $patterns;
$this->escaping = $escaping;
public function __construct(
private TokenizerPatterns $patterns,
private TokenizerEscaping $escaping,
) {
}
/**
* {@inheritdoc}
*/
public function handle(Reader $reader, TokenStream $stream): bool
{
$match = $reader->findPattern($this->patterns->getIdentifierPattern());

View File

@@ -28,16 +28,11 @@ use Symfony\Component\CssSelector\Parser\TokenStream;
*/
class NumberHandler implements HandlerInterface
{
private $patterns;
public function __construct(TokenizerPatterns $patterns)
{
$this->patterns = $patterns;
public function __construct(
private TokenizerPatterns $patterns,
) {
}
/**
* {@inheritdoc}
*/
public function handle(Reader $reader, TokenStream $stream): bool
{
$match = $reader->findPattern($this->patterns->getNumberPattern());

View File

@@ -31,18 +31,12 @@ use Symfony\Component\CssSelector\Parser\TokenStream;
*/
class StringHandler implements HandlerInterface
{
private $patterns;
private $escaping;
public function __construct(TokenizerPatterns $patterns, TokenizerEscaping $escaping)
{
$this->patterns = $patterns;
$this->escaping = $escaping;
public function __construct(
private TokenizerPatterns $patterns,
private TokenizerEscaping $escaping,
) {
}
/**
* {@inheritdoc}
*/
public function handle(Reader $reader, TokenStream $stream): bool
{
$quote = $reader->getSubstring(1);

View File

@@ -27,9 +27,6 @@ use Symfony\Component\CssSelector\Parser\TokenStream;
*/
class WhitespaceHandler implements HandlerInterface
{
/**
* {@inheritdoc}
*/
public function handle(Reader $reader, TokenStream $stream): bool
{
$match = $reader->findPattern('~^[ \t\r\n\f]+~');

View File

@@ -19,7 +19,7 @@ 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.
* which is copyright Ian Bicking, @see https://github.com/scrapy/cssselect.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
@@ -27,16 +27,13 @@ use Symfony\Component\CssSelector\Parser\Tokenizer\Tokenizer;
*/
class Parser implements ParserInterface
{
private $tokenizer;
private Tokenizer $tokenizer;
public function __construct(Tokenizer $tokenizer = null)
public function __construct(?Tokenizer $tokenizer = null)
{
$this->tokenizer = $tokenizer ?? new Tokenizer();
}
/**
* {@inheritdoc}
*/
public function parse(string $source): array
{
$reader = new Reader($source);
@@ -60,9 +57,7 @@ class Parser implements ParserInterface
}
}
$joined = trim(implode('', array_map(function (Token $token) {
return $token->getValue();
}, $tokens)));
$joined = trim(implode('', array_map(fn (Token $token) => $token->getValue(), $tokens)));
$int = function ($string) {
if (!is_numeric($string)) {
@@ -92,13 +87,17 @@ class Parser implements ParserInterface
];
}
private function parseSelectorList(TokenStream $stream): array
private function parseSelectorList(TokenStream $stream, bool $isArgument = false): array
{
$stream->skipWhitespace();
$selectors = [];
while (true) {
$selectors[] = $this->parserSelectorNode($stream);
if ($isArgument && $stream->getPeek()->isDelimiter([')'])) {
break;
}
$selectors[] = $this->parserSelectorNode($stream, $isArgument);
if ($stream->getPeek()->isDelimiter([','])) {
$stream->getNext();
@@ -111,15 +110,19 @@ class Parser implements ParserInterface
return $selectors;
}
private function parserSelectorNode(TokenStream $stream): Node\SelectorNode
private function parserSelectorNode(TokenStream $stream, bool $isArgument = false): Node\SelectorNode
{
[$result, $pseudoElement] = $this->parseSimpleSelector($stream);
[$result, $pseudoElement] = $this->parseSimpleSelector($stream, false, $isArgument);
while (true) {
$stream->skipWhitespace();
$peek = $stream->getPeek();
if ($peek->isFileEnd() || $peek->isDelimiter([','])) {
if (
$peek->isFileEnd()
|| $peek->isDelimiter([','])
|| ($isArgument && $peek->isDelimiter([')']))
) {
break;
}
@@ -134,7 +137,7 @@ class Parser implements ParserInterface
$combinator = ' ';
}
[$nextSelector, $pseudoElement] = $this->parseSimpleSelector($stream);
[$nextSelector, $pseudoElement] = $this->parseSimpleSelector($stream, false, $isArgument);
$result = new Node\CombinedSelectorNode($result, $combinator, $nextSelector);
}
@@ -146,7 +149,7 @@ class Parser implements ParserInterface
*
* @throws SyntaxErrorException
*/
private function parseSimpleSelector(TokenStream $stream, bool $insideNegation = false): array
private function parseSimpleSelector(TokenStream $stream, bool $insideNegation = false, bool $isArgument = false): array
{
$stream->skipWhitespace();
@@ -159,7 +162,7 @@ class Parser implements ParserInterface
if ($peek->isWhitespace()
|| $peek->isFileEnd()
|| $peek->isDelimiter([',', '+', '>', '~'])
|| ($insideNegation && $peek->isDelimiter([')']))
|| ($isArgument && $peek->isDelimiter([')']))
) {
break;
}
@@ -197,7 +200,18 @@ class Parser implements ParserInterface
if (!$stream->getPeek()->isDelimiter(['('])) {
$result = new Node\PseudoNode($result, $identifier);
if ('Pseudo[Element[*]:scope]' === $result->__toString()) {
$used = \count($stream->getUsed());
if (!(2 === $used
|| 3 === $used && $stream->getUsed()[0]->isWhiteSpace()
|| $used >= 3 && $stream->getUsed()[$used - 3]->isDelimiter([','])
|| $used >= 4
&& $stream->getUsed()[$used - 3]->isWhiteSpace()
&& $stream->getUsed()[$used - 4]->isDelimiter([','])
)) {
throw SyntaxErrorException::notAtTheStartOfASelector('scope');
}
}
continue;
}
@@ -209,7 +223,7 @@ class Parser implements ParserInterface
throw SyntaxErrorException::nestedNot();
}
[$argument, $argumentPseudoElement] = $this->parseSimpleSelector($stream, true);
[$argument, $argumentPseudoElement] = $this->parseSimpleSelector($stream, true, true);
$next = $stream->getNext();
if (null !== $argumentPseudoElement) {
@@ -221,6 +235,24 @@ class Parser implements ParserInterface
}
$result = new Node\NegationNode($result, $argument);
} elseif ('is' === strtolower($identifier)) {
$selectors = $this->parseSelectorList($stream, true);
$next = $stream->getNext();
if (!$next->isDelimiter([')'])) {
throw SyntaxErrorException::unexpectedToken('")"', $next);
}
$result = new Node\MatchingNode($result, $selectors);
} elseif ('where' === strtolower($identifier)) {
$selectors = $this->parseSelectorList($stream, true);
$next = $stream->getNext();
if (!$next->isDelimiter([')'])) {
throw SyntaxErrorException::unexpectedToken('")"', $next);
}
$result = new Node\SpecificityAdjustmentNode($result, $selectors);
} else {
$arguments = [];
$next = null;
@@ -242,7 +274,7 @@ class Parser implements ParserInterface
}
}
if (empty($arguments)) {
if (!$arguments) {
throw SyntaxErrorException::unexpectedToken('at least one argument', $next);
}

View File

@@ -23,13 +23,12 @@ namespace Symfony\Component\CssSelector\Parser;
*/
class Reader
{
private string $source;
private int $length;
private int $position = 0;
public function __construct(string $source)
{
$this->source = $source;
public function __construct(
private string $source,
) {
$this->length = \strlen($source);
}
@@ -53,7 +52,7 @@ class Reader
return substr($this->source, $this->position + $offset, $length);
}
public function getOffset(string $string)
public function getOffset(string $string): int|false
{
$position = strpos($this->source, $string, $this->position);
@@ -71,12 +70,12 @@ class Reader
return false;
}
public function moveForward(int $length)
public function moveForward(int $length): void
{
$this->position += $length;
}
public function moveToEnd()
public function moveToEnd(): void
{
$this->position = $this->length;
}

View File

@@ -28,9 +28,6 @@ use Symfony\Component\CssSelector\Parser\ParserInterface;
*/
class ClassParser implements ParserInterface
{
/**
* {@inheritdoc}
*/
public function parse(string $source): array
{
// Matches an optional namespace, optional element, and required class

View File

@@ -27,9 +27,6 @@ use Symfony\Component\CssSelector\Parser\ParserInterface;
*/
class ElementParser implements ParserInterface
{
/**
* {@inheritdoc}
*/
public function parse(string $source): array
{
// Matches an optional namespace, required element or `*`

View File

@@ -31,9 +31,6 @@ use Symfony\Component\CssSelector\Parser\ParserInterface;
*/
class EmptyStringParser implements ParserInterface
{
/**
* {@inheritdoc}
*/
public function parse(string $source): array
{
// Matches an empty string

View File

@@ -28,9 +28,6 @@ use Symfony\Component\CssSelector\Parser\ParserInterface;
*/
class HashParser implements ParserInterface
{
/**
* {@inheritdoc}
*/
public function parse(string $source): array
{
// Matches an optional namespace, optional element, and required id

View File

@@ -31,15 +31,11 @@ class Token
public const TYPE_NUMBER = 'number';
public const TYPE_STRING = 'string';
private ?string $type;
private ?string $value;
private ?int $position;
public function __construct(?string $type, ?string $value, ?int $position)
{
$this->type = $type;
$this->value = $value;
$this->position = $position;
public function __construct(
private ?string $type,
private ?string $value,
private ?int $position,
) {
}
public function getType(): ?int
@@ -68,11 +64,11 @@ class Token
return false;
}
if (empty($values)) {
if (!$values) {
return true;
}
return \in_array($this->value, $values);
return \in_array($this->value, $values, true);
}
public function isWhitespace(): bool

View File

@@ -37,7 +37,7 @@ class TokenStream
private array $used = [];
private int $cursor = 0;
private $peeked;
private ?Token $peeked;
private bool $peeking = false;
/**
@@ -145,7 +145,7 @@ class TokenStream
/**
* Skips next whitespace if any.
*/
public function skipWhitespace()
public function skipWhitespace(): void
{
$peek = $this->getPeek();

View File

@@ -23,11 +23,9 @@ namespace Symfony\Component\CssSelector\Parser\Tokenizer;
*/
class TokenizerEscaping
{
private $patterns;
public function __construct(TokenizerPatterns $patterns)
{
$this->patterns = $patterns;
public function __construct(
private TokenizerPatterns $patterns,
) {
}
public function escapeUnicode(string $value): string