[
  {
    "path": ".gitattributes",
    "content": "/Tests export-ignore\n/phpunit.xml.dist export-ignore\n/.git* export-ignore\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "Please do not submit any Pull Requests here. They will be closed.\n---\n\nPlease submit your PR here instead:\nhttps://github.com/symfony/symfony\n\nThis repository is what we call a \"subtree split\": a read-only subset of that main repository.\nWe're looking forward to your PR there!\n"
  },
  {
    "path": ".github/workflows/close-pull-request.yml",
    "content": "name: Close Pull Request\n\non:\n  pull_request_target:\n    types: [opened]\n\njobs:\n  run:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: superbrothers/close-pull-request@v3\n      with:\n        comment: |\n          Thanks for your Pull Request! We love contributions.\n\n          However, you should instead open your PR on the main repository:\n          https://github.com/symfony/symfony\n\n          This repository is what we call a \"subtree split\": a read-only subset of that main repository.\n          We're looking forward to your PR there!\n"
  },
  {
    "path": ".gitignore",
    "content": "vendor/\ncomposer.lock\nphpunit.xml\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "CHANGELOG\n=========\n\n8.1\n---\n\n * Add support for `:has()`\n\n7.1\n---\n\n * Add support for `:is()`\n * Add support for `:where()`\n\n6.3\n---\n\n * Add support for `:scope`\n\n4.4.0\n-----\n\n * Added support for `*:only-of-type`\n\n2.8.0\n-----\n\n * Added the `CssSelectorConverter` class as a non-static API for the component.\n * Deprecated the `CssSelector` static API of the component.\n\n2.1.0\n-----\n\n * none\n"
  },
  {
    "path": "CssSelectorConverter.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector;\n\nuse Symfony\\Component\\CssSelector\\Parser\\Shortcut\\ClassParser;\nuse Symfony\\Component\\CssSelector\\Parser\\Shortcut\\ElementParser;\nuse Symfony\\Component\\CssSelector\\Parser\\Shortcut\\EmptyStringParser;\nuse Symfony\\Component\\CssSelector\\Parser\\Shortcut\\HashParser;\nuse Symfony\\Component\\CssSelector\\XPath\\Extension\\HtmlExtension;\nuse Symfony\\Component\\CssSelector\\XPath\\Translator;\n\n/**\n * CssSelectorConverter is the main entry point of the component and can convert CSS\n * selectors to XPath expressions.\n *\n * @author Christophe Coevoet <stof@notk.org>\n */\nclass CssSelectorConverter\n{\n    public static int $maxCachedItems = 1024;\n\n    private Translator $translator;\n    private array $cache;\n\n    private static array $xmlCache = [];\n    private static array $htmlCache = [];\n\n    /**\n     * @param bool $html Whether HTML support should be enabled. Disable it for XML documents\n     */\n    public function __construct(bool $html = true)\n    {\n        $this->translator = new Translator();\n\n        if ($html) {\n            $this->translator->registerExtension(new HtmlExtension($this->translator));\n            $this->cache = &self::$htmlCache;\n        } else {\n            $this->cache = &self::$xmlCache;\n        }\n\n        $this->translator\n            ->registerParserShortcut(new EmptyStringParser())\n            ->registerParserShortcut(new ElementParser())\n            ->registerParserShortcut(new ClassParser())\n            ->registerParserShortcut(new HashParser())\n        ;\n    }\n\n    /**\n     * Translates a CSS expression to its XPath equivalent.\n     *\n     * Optionally, a prefix can be added to the resulting XPath\n     * expression with the $prefix parameter.\n     */\n    public function toXPath(string $cssExpr, string $prefix = 'descendant-or-self::'): string\n    {\n        $cacheKey = $prefix.\"\\0\".$cssExpr;\n\n        if (isset($this->cache[$cacheKey])) {\n            // Move the item last in cache (LRU)\n            $value = $this->cache[$cacheKey];\n            unset($this->cache[$cacheKey]);\n\n            return $this->cache[$cacheKey] = $value;\n        }\n\n        if (\\count($this->cache) >= self::$maxCachedItems) {\n            // Evict the oldest entry\n            unset($this->cache[array_key_first($this->cache)]);\n        }\n\n        return $this->cache[$cacheKey] = $this->translator->cssToXPath($cssExpr, $prefix);\n    }\n}\n"
  },
  {
    "path": "Exception/ExceptionInterface.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Exception;\n\n/**\n * Interface for exceptions.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n */\ninterface ExceptionInterface extends \\Throwable\n{\n}\n"
  },
  {
    "path": "Exception/ExpressionErrorException.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Exception;\n\n/**\n * ParseException is thrown when a CSS selector syntax is not valid.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n */\nclass ExpressionErrorException extends ParseException\n{\n}\n"
  },
  {
    "path": "Exception/InternalErrorException.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Exception;\n\n/**\n * ParseException is thrown when a CSS selector syntax is not valid.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n */\nclass InternalErrorException extends ParseException\n{\n}\n"
  },
  {
    "path": "Exception/ParseException.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Exception;\n\n/**\n * ParseException is thrown when a CSS selector syntax is not valid.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @author Fabien Potencier <fabien@symfony.com>\n */\nclass ParseException extends \\Exception implements ExceptionInterface\n{\n}\n"
  },
  {
    "path": "Exception/SyntaxErrorException.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Exception;\n\nuse Symfony\\Component\\CssSelector\\Parser\\Token;\n\n/**\n * ParseException is thrown when a CSS selector syntax is not valid.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/scrapy/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n */\nclass SyntaxErrorException extends ParseException\n{\n    public static function unexpectedToken(string $expectedValue, Token $foundToken): self\n    {\n        return new self(\\sprintf('Expected %s, but %s found.', $expectedValue, $foundToken));\n    }\n\n    public static function pseudoElementFound(string $pseudoElement, string $unexpectedLocation): self\n    {\n        return new self(\\sprintf('Unexpected pseudo-element \"::%s\" found %s.', $pseudoElement, $unexpectedLocation));\n    }\n\n    public static function unclosedString(int $position): self\n    {\n        return new self(\\sprintf('Unclosed/invalid string at %s.', $position));\n    }\n\n    public static function nestedNot(): self\n    {\n        return new self('Got nested ::not().');\n    }\n\n    public static function notAtTheStartOfASelector(string $pseudoElement): self\n    {\n        return new self(\\sprintf('Got immediate child pseudo-element \":%s\" not at the start of a selector', $pseudoElement));\n    }\n\n    public static function stringAsFunctionArgument(): self\n    {\n        return new self('String not allowed as function argument.');\n    }\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2004-present Fabien Potencier\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is furnished\nto do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "Node/AbstractNode.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Node;\n\n/**\n * Abstract base node class.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n *\n * @internal\n */\nabstract class AbstractNode implements NodeInterface\n{\n    private string $nodeName;\n\n    public function getNodeName(): string\n    {\n        return $this->nodeName ??= preg_replace('~.*\\\\\\\\([^\\\\\\\\]+)Node$~', '$1', static::class);\n    }\n}\n"
  },
  {
    "path": "Node/AttributeNode.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Node;\n\n/**\n * Represents a \"<selector>[<namespace>|<attribute> <operator> <value>]\" node.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n *\n * @internal\n */\nclass AttributeNode extends AbstractNode\n{\n    public function __construct(\n        private NodeInterface $selector,\n        private ?string $namespace,\n        private string $attribute,\n        private string $operator,\n        private ?string $value,\n    ) {\n    }\n\n    public function getSelector(): NodeInterface\n    {\n        return $this->selector;\n    }\n\n    public function getNamespace(): ?string\n    {\n        return $this->namespace;\n    }\n\n    public function getAttribute(): string\n    {\n        return $this->attribute;\n    }\n\n    public function getOperator(): string\n    {\n        return $this->operator;\n    }\n\n    public function getValue(): ?string\n    {\n        return $this->value;\n    }\n\n    public function getSpecificity(): Specificity\n    {\n        return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0));\n    }\n\n    public function __toString(): string\n    {\n        $attribute = $this->namespace ? $this->namespace.'|'.$this->attribute : $this->attribute;\n\n        return 'exists' === $this->operator\n            ? \\sprintf('%s[%s[%s]]', $this->getNodeName(), $this->selector, $attribute)\n            : \\sprintf(\"%s[%s[%s %s '%s']]\", $this->getNodeName(), $this->selector, $attribute, $this->operator, $this->value);\n    }\n}\n"
  },
  {
    "path": "Node/ClassNode.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Node;\n\n/**\n * Represents a \"<selector>.<name>\" node.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n *\n * @internal\n */\nclass ClassNode extends AbstractNode\n{\n    public function __construct(\n        private NodeInterface $selector,\n        private string $name,\n    ) {\n    }\n\n    public function getSelector(): NodeInterface\n    {\n        return $this->selector;\n    }\n\n    public function getName(): string\n    {\n        return $this->name;\n    }\n\n    public function getSpecificity(): Specificity\n    {\n        return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0));\n    }\n\n    public function __toString(): string\n    {\n        return \\sprintf('%s[%s.%s]', $this->getNodeName(), $this->selector, $this->name);\n    }\n}\n"
  },
  {
    "path": "Node/CombinedSelectorNode.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Node;\n\n/**\n * Represents a combined node.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n *\n * @internal\n */\nclass CombinedSelectorNode extends AbstractNode\n{\n    public function __construct(\n        private NodeInterface $selector,\n        private string $combinator,\n        private NodeInterface $subSelector,\n    ) {\n    }\n\n    public function getSelector(): NodeInterface\n    {\n        return $this->selector;\n    }\n\n    public function getCombinator(): string\n    {\n        return $this->combinator;\n    }\n\n    public function getSubSelector(): NodeInterface\n    {\n        return $this->subSelector;\n    }\n\n    public function getSpecificity(): Specificity\n    {\n        return $this->selector->getSpecificity()->plus($this->subSelector->getSpecificity());\n    }\n\n    public function __toString(): string\n    {\n        $combinator = ' ' === $this->combinator ? '<followed>' : $this->combinator;\n\n        return \\sprintf('%s[%s %s %s]', $this->getNodeName(), $this->selector, $combinator, $this->subSelector);\n    }\n}\n"
  },
  {
    "path": "Node/ElementNode.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Node;\n\n/**\n * Represents a \"<namespace>|<element>\" node.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n *\n * @internal\n */\nclass ElementNode extends AbstractNode\n{\n    public function __construct(\n        private ?string $namespace = null,\n        private ?string $element = null,\n    ) {\n    }\n\n    public function getNamespace(): ?string\n    {\n        return $this->namespace;\n    }\n\n    public function getElement(): ?string\n    {\n        return $this->element;\n    }\n\n    public function getSpecificity(): Specificity\n    {\n        return new Specificity(0, 0, $this->element ? 1 : 0);\n    }\n\n    public function __toString(): string\n    {\n        $element = $this->element ?: '*';\n\n        return \\sprintf('%s[%s]', $this->getNodeName(), $this->namespace ? $this->namespace.'|'.$element : $element);\n    }\n}\n"
  },
  {
    "path": "Node/FunctionNode.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Node;\n\nuse Symfony\\Component\\CssSelector\\Parser\\Token;\n\n/**\n * Represents a \"<selector>:<name>(<arguments>)\" node.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n *\n * @internal\n */\nclass FunctionNode extends AbstractNode\n{\n    private string $name;\n\n    /**\n     * @param Token[] $arguments\n     */\n    public function __construct(\n        private NodeInterface $selector,\n        string $name,\n        private array $arguments = [],\n    ) {\n        $this->name = strtolower($name);\n    }\n\n    public function getSelector(): NodeInterface\n    {\n        return $this->selector;\n    }\n\n    public function getName(): string\n    {\n        return $this->name;\n    }\n\n    /**\n     * @return Token[]\n     */\n    public function getArguments(): array\n    {\n        return $this->arguments;\n    }\n\n    public function getSpecificity(): Specificity\n    {\n        return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0));\n    }\n\n    public function __toString(): string\n    {\n        $arguments = implode(', ', array_map(static fn (Token $token) => \"'\".$token->getValue().\"'\", $this->arguments));\n\n        return \\sprintf('%s[%s:%s(%s)]', $this->getNodeName(), $this->selector, $this->name, $arguments ? '['.$arguments.']' : '');\n    }\n}\n"
  },
  {
    "path": "Node/HashNode.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Node;\n\n/**\n * Represents a \"<selector>#<id>\" node.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n *\n * @internal\n */\nclass HashNode extends AbstractNode\n{\n    public function __construct(\n        private NodeInterface $selector,\n        private string $id,\n    ) {\n    }\n\n    public function getSelector(): NodeInterface\n    {\n        return $this->selector;\n    }\n\n    public function getId(): string\n    {\n        return $this->id;\n    }\n\n    public function getSpecificity(): Specificity\n    {\n        return $this->selector->getSpecificity()->plus(new Specificity(1, 0, 0));\n    }\n\n    public function __toString(): string\n    {\n        return \\sprintf('%s[%s#%s]', $this->getNodeName(), $this->selector, $this->id);\n    }\n}\n"
  },
  {
    "path": "Node/MatchingNode.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Node;\n\n/**\n * Represents a \"<selector>:is(<subSelectorList>)\" node.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @author Hubert Lenoir <lenoir.hubert@gmail.com>\n *\n * @internal\n */\nclass MatchingNode extends AbstractNode\n{\n    /**\n     * @param array<NodeInterface> $arguments\n     */\n    public function __construct(\n        public readonly NodeInterface $selector,\n        public readonly array $arguments = [],\n    ) {\n    }\n\n    public function getSpecificity(): Specificity\n    {\n        $argumentsSpecificity = array_reduce(\n            $this->arguments,\n            static fn ($c, $n) => 1 === $n->getSpecificity()->compareTo($c) ? $n->getSpecificity() : $c,\n            new Specificity(0, 0, 0),\n        );\n\n        return $this->selector->getSpecificity()->plus($argumentsSpecificity);\n    }\n\n    public function __toString(): string\n    {\n        $selectorArguments = array_map(\n            static fn ($n): string => ltrim((string) $n, '*'),\n            $this->arguments,\n        );\n\n        return \\sprintf('%s[%s:is(%s)]', $this->getNodeName(), $this->selector, implode(', ', $selectorArguments));\n    }\n}\n"
  },
  {
    "path": "Node/NegationNode.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Node;\n\n/**\n * Represents a \"<selector>:not(<identifier>)\" node.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n *\n * @internal\n */\nclass NegationNode extends AbstractNode\n{\n    public function __construct(\n        private NodeInterface $selector,\n        private NodeInterface $subSelector,\n    ) {\n    }\n\n    public function getSelector(): NodeInterface\n    {\n        return $this->selector;\n    }\n\n    public function getSubSelector(): NodeInterface\n    {\n        return $this->subSelector;\n    }\n\n    public function getSpecificity(): Specificity\n    {\n        return $this->selector->getSpecificity()->plus($this->subSelector->getSpecificity());\n    }\n\n    public function __toString(): string\n    {\n        return \\sprintf('%s[%s:not(%s)]', $this->getNodeName(), $this->selector, $this->subSelector);\n    }\n}\n"
  },
  {
    "path": "Node/NodeInterface.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Node;\n\n/**\n * Interface for nodes.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n *\n * @internal\n */\ninterface NodeInterface extends \\Stringable\n{\n    public function getNodeName(): string;\n\n    public function getSpecificity(): Specificity;\n}\n"
  },
  {
    "path": "Node/PseudoNode.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Node;\n\n/**\n * Represents a \"<selector>:<identifier>\" node.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n *\n * @internal\n */\nclass PseudoNode extends AbstractNode\n{\n    private string $identifier;\n\n    public function __construct(\n        private NodeInterface $selector,\n        string $identifier,\n    ) {\n        $this->identifier = strtolower($identifier);\n    }\n\n    public function getSelector(): NodeInterface\n    {\n        return $this->selector;\n    }\n\n    public function getIdentifier(): string\n    {\n        return $this->identifier;\n    }\n\n    public function getSpecificity(): Specificity\n    {\n        return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0));\n    }\n\n    public function __toString(): string\n    {\n        return \\sprintf('%s[%s:%s]', $this->getNodeName(), $this->selector, $this->identifier);\n    }\n}\n"
  },
  {
    "path": "Node/RelationNode.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Node;\n\n/**\n * Represents a \"<selector>:has(<subselector>)\" node.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/scrapy/cssselect.\n *\n * @author Franck Ranaivo-Harisoa <franckranaivo@gmail.com>\n *\n * @internal\n */\nclass RelationNode extends AbstractNode\n{\n    public function __construct(\n        private NodeInterface $selector,\n        private string $combinator,\n        private NodeInterface $subSelector,\n    ) {\n    }\n\n    public function getSelector(): NodeInterface\n    {\n        return $this->selector;\n    }\n\n    public function getCombinator(): string\n    {\n        return $this->combinator;\n    }\n\n    public function getSubSelector(): NodeInterface\n    {\n        return $this->subSelector;\n    }\n\n    public function getSpecificity(): Specificity\n    {\n        return $this->selector->getSpecificity()->plus($this->subSelector->getSpecificity());\n    }\n\n    public function __toString(): string\n    {\n        return \\sprintf('%s[%s:has(%s)]', $this->getNodeName(), $this->selector, $this->subSelector);\n    }\n}\n"
  },
  {
    "path": "Node/SelectorNode.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Node;\n\n/**\n * Represents a \"<selector>(::|:)<pseudoElement>\" node.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n *\n * @internal\n */\nclass SelectorNode extends AbstractNode\n{\n    private ?string $pseudoElement;\n\n    public function __construct(\n        private NodeInterface $tree,\n        ?string $pseudoElement = null,\n    ) {\n        $this->pseudoElement = $pseudoElement ? strtolower($pseudoElement) : null;\n    }\n\n    public function getTree(): NodeInterface\n    {\n        return $this->tree;\n    }\n\n    public function getPseudoElement(): ?string\n    {\n        return $this->pseudoElement;\n    }\n\n    public function getSpecificity(): Specificity\n    {\n        return $this->tree->getSpecificity()->plus(new Specificity(0, 0, $this->pseudoElement ? 1 : 0));\n    }\n\n    public function __toString(): string\n    {\n        return \\sprintf('%s[%s%s]', $this->getNodeName(), $this->tree, $this->pseudoElement ? '::'.$this->pseudoElement : '');\n    }\n}\n"
  },
  {
    "path": "Node/Specificity.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Node;\n\n/**\n * Represents a node specificity.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @see http://www.w3.org/TR/selectors/#specificity\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n *\n * @internal\n */\nclass Specificity\n{\n    public const A_FACTOR = 100;\n    public const B_FACTOR = 10;\n    public const C_FACTOR = 1;\n\n    public function __construct(\n        private int $a,\n        private int $b,\n        private int $c,\n    ) {\n    }\n\n    public function plus(self $specificity): self\n    {\n        return new self($this->a + $specificity->a, $this->b + $specificity->b, $this->c + $specificity->c);\n    }\n\n    public function getValue(): int\n    {\n        return $this->a * self::A_FACTOR + $this->b * self::B_FACTOR + $this->c * self::C_FACTOR;\n    }\n\n    /**\n     * Returns -1 if the object specificity is lower than the argument,\n     * 0 if they are equal, and 1 if the argument is lower.\n     */\n    public function compareTo(self $specificity): int\n    {\n        if ($this->a !== $specificity->a) {\n            return $this->a > $specificity->a ? 1 : -1;\n        }\n\n        if ($this->b !== $specificity->b) {\n            return $this->b > $specificity->b ? 1 : -1;\n        }\n\n        if ($this->c !== $specificity->c) {\n            return $this->c > $specificity->c ? 1 : -1;\n        }\n\n        return 0;\n    }\n}\n"
  },
  {
    "path": "Node/SpecificityAdjustmentNode.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Node;\n\n/**\n * Represents a \"<selector>:where(<subSelectorList>)\" node.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @author Hubert Lenoir <lenoir.hubert@gmail.com>\n *\n * @internal\n */\nclass SpecificityAdjustmentNode extends AbstractNode\n{\n    /**\n     * @param array<NodeInterface> $arguments\n     */\n    public function __construct(\n        public readonly NodeInterface $selector,\n        public readonly array $arguments = [],\n    ) {\n    }\n\n    public function getSpecificity(): Specificity\n    {\n        return $this->selector->getSpecificity();\n    }\n\n    public function __toString(): string\n    {\n        $selectorArguments = array_map(\n            static fn ($n) => ltrim((string) $n, '*'),\n            $this->arguments,\n        );\n\n        return \\sprintf('%s[%s:where(%s)]', $this->getNodeName(), $this->selector, implode(', ', $selectorArguments));\n    }\n}\n"
  },
  {
    "path": "Parser/Handler/CommentHandler.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Parser\\Handler;\n\nuse Symfony\\Component\\CssSelector\\Parser\\Reader;\nuse Symfony\\Component\\CssSelector\\Parser\\TokenStream;\n\n/**\n * CSS selector comment handler.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n *\n * @internal\n */\nclass CommentHandler implements HandlerInterface\n{\n    public function handle(Reader $reader, TokenStream $stream): bool\n    {\n        if ('/*' !== $reader->getSubstring(2)) {\n            return false;\n        }\n\n        $offset = $reader->getOffset('*/');\n\n        if (false === $offset) {\n            $reader->moveToEnd();\n        } else {\n            $reader->moveForward($offset + 2);\n        }\n\n        return true;\n    }\n}\n"
  },
  {
    "path": "Parser/Handler/HandlerInterface.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Parser\\Handler;\n\nuse Symfony\\Component\\CssSelector\\Parser\\Reader;\nuse Symfony\\Component\\CssSelector\\Parser\\TokenStream;\n\n/**\n * CSS selector handler interface.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n *\n * @internal\n */\ninterface HandlerInterface\n{\n    public function handle(Reader $reader, TokenStream $stream): bool;\n}\n"
  },
  {
    "path": "Parser/Handler/HashHandler.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Parser\\Handler;\n\nuse Symfony\\Component\\CssSelector\\Parser\\Reader;\nuse Symfony\\Component\\CssSelector\\Parser\\Token;\nuse Symfony\\Component\\CssSelector\\Parser\\Tokenizer\\TokenizerEscaping;\nuse Symfony\\Component\\CssSelector\\Parser\\Tokenizer\\TokenizerPatterns;\nuse Symfony\\Component\\CssSelector\\Parser\\TokenStream;\n\n/**\n * CSS selector comment handler.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n *\n * @internal\n */\nclass HashHandler implements HandlerInterface\n{\n    public function __construct(\n        private TokenizerPatterns $patterns,\n        private TokenizerEscaping $escaping,\n    ) {\n    }\n\n    public function handle(Reader $reader, TokenStream $stream): bool\n    {\n        $match = $reader->findPattern($this->patterns->getHashPattern());\n\n        if (!$match) {\n            return false;\n        }\n\n        $value = $this->escaping->escapeUnicode($match[1]);\n        $stream->push(new Token(Token::TYPE_HASH, $value, $reader->getPosition()));\n        $reader->moveForward(\\strlen($match[0]));\n\n        return true;\n    }\n}\n"
  },
  {
    "path": "Parser/Handler/IdentifierHandler.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Parser\\Handler;\n\nuse Symfony\\Component\\CssSelector\\Parser\\Reader;\nuse Symfony\\Component\\CssSelector\\Parser\\Token;\nuse Symfony\\Component\\CssSelector\\Parser\\Tokenizer\\TokenizerEscaping;\nuse Symfony\\Component\\CssSelector\\Parser\\Tokenizer\\TokenizerPatterns;\nuse Symfony\\Component\\CssSelector\\Parser\\TokenStream;\n\n/**\n * CSS selector comment handler.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n *\n * @internal\n */\nclass IdentifierHandler implements HandlerInterface\n{\n    public function __construct(\n        private TokenizerPatterns $patterns,\n        private TokenizerEscaping $escaping,\n    ) {\n    }\n\n    public function handle(Reader $reader, TokenStream $stream): bool\n    {\n        $match = $reader->findPattern($this->patterns->getIdentifierPattern());\n\n        if (!$match) {\n            return false;\n        }\n\n        $value = $this->escaping->escapeUnicode($match[0]);\n        $stream->push(new Token(Token::TYPE_IDENTIFIER, $value, $reader->getPosition()));\n        $reader->moveForward(\\strlen($match[0]));\n\n        return true;\n    }\n}\n"
  },
  {
    "path": "Parser/Handler/NumberHandler.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Parser\\Handler;\n\nuse Symfony\\Component\\CssSelector\\Parser\\Reader;\nuse Symfony\\Component\\CssSelector\\Parser\\Token;\nuse Symfony\\Component\\CssSelector\\Parser\\Tokenizer\\TokenizerPatterns;\nuse Symfony\\Component\\CssSelector\\Parser\\TokenStream;\n\n/**\n * CSS selector comment handler.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n *\n * @internal\n */\nclass NumberHandler implements HandlerInterface\n{\n    public function __construct(\n        private TokenizerPatterns $patterns,\n    ) {\n    }\n\n    public function handle(Reader $reader, TokenStream $stream): bool\n    {\n        $match = $reader->findPattern($this->patterns->getNumberPattern());\n\n        if (!$match) {\n            return false;\n        }\n\n        $stream->push(new Token(Token::TYPE_NUMBER, $match[0], $reader->getPosition()));\n        $reader->moveForward(\\strlen($match[0]));\n\n        return true;\n    }\n}\n"
  },
  {
    "path": "Parser/Handler/StringHandler.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Parser\\Handler;\n\nuse Symfony\\Component\\CssSelector\\Exception\\InternalErrorException;\nuse Symfony\\Component\\CssSelector\\Exception\\SyntaxErrorException;\nuse Symfony\\Component\\CssSelector\\Parser\\Reader;\nuse Symfony\\Component\\CssSelector\\Parser\\Token;\nuse Symfony\\Component\\CssSelector\\Parser\\Tokenizer\\TokenizerEscaping;\nuse Symfony\\Component\\CssSelector\\Parser\\Tokenizer\\TokenizerPatterns;\nuse Symfony\\Component\\CssSelector\\Parser\\TokenStream;\n\n/**\n * CSS selector comment handler.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n *\n * @internal\n */\nclass StringHandler implements HandlerInterface\n{\n    public function __construct(\n        private TokenizerPatterns $patterns,\n        private TokenizerEscaping $escaping,\n    ) {\n    }\n\n    public function handle(Reader $reader, TokenStream $stream): bool\n    {\n        $quote = $reader->getSubstring(1);\n\n        if (!\\in_array($quote, [\"'\", '\"'], true)) {\n            return false;\n        }\n\n        $reader->moveForward(1);\n        $match = $reader->findPattern($this->patterns->getQuotedStringPattern($quote));\n\n        if (!$match) {\n            throw new InternalErrorException(\\sprintf('Should have found at least an empty match at %d.', $reader->getPosition()));\n        }\n\n        // check unclosed strings\n        if (\\strlen($match[0]) === $reader->getRemainingLength()) {\n            throw SyntaxErrorException::unclosedString($reader->getPosition() - 1);\n        }\n\n        // check quotes pairs validity\n        if ($quote !== $reader->getSubstring(1, \\strlen($match[0]))) {\n            throw SyntaxErrorException::unclosedString($reader->getPosition() - 1);\n        }\n\n        $string = $this->escaping->escapeUnicodeAndNewLine($match[0]);\n        $stream->push(new Token(Token::TYPE_STRING, $string, $reader->getPosition()));\n        $reader->moveForward(\\strlen($match[0]) + 1);\n\n        return true;\n    }\n}\n"
  },
  {
    "path": "Parser/Handler/WhitespaceHandler.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Parser\\Handler;\n\nuse Symfony\\Component\\CssSelector\\Parser\\Reader;\nuse Symfony\\Component\\CssSelector\\Parser\\Token;\nuse Symfony\\Component\\CssSelector\\Parser\\TokenStream;\n\n/**\n * CSS selector whitespace handler.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n *\n * @internal\n */\nclass WhitespaceHandler implements HandlerInterface\n{\n    public function handle(Reader $reader, TokenStream $stream): bool\n    {\n        $match = $reader->findPattern('~^[ \\t\\r\\n\\f]+~');\n\n        if (false === $match) {\n            return false;\n        }\n\n        $stream->push(new Token(Token::TYPE_WHITESPACE, $match[0], $reader->getPosition()));\n        $reader->moveForward(\\strlen($match[0]));\n\n        return true;\n    }\n}\n"
  },
  {
    "path": "Parser/Parser.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Parser;\n\nuse Symfony\\Component\\CssSelector\\Exception\\InternalErrorException;\nuse Symfony\\Component\\CssSelector\\Exception\\SyntaxErrorException;\nuse Symfony\\Component\\CssSelector\\Node;\nuse Symfony\\Component\\CssSelector\\Parser\\Tokenizer\\Tokenizer;\n\n/**\n * CSS selector parser.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/scrapy/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n *\n * @internal\n */\nclass Parser implements ParserInterface\n{\n    private Tokenizer $tokenizer;\n\n    public function __construct(?Tokenizer $tokenizer = null)\n    {\n        $this->tokenizer = $tokenizer ?? new Tokenizer();\n    }\n\n    public function parse(string $source): array\n    {\n        $reader = new Reader($source);\n        $stream = $this->tokenizer->tokenize($reader);\n\n        return $this->parseSelectorList($stream);\n    }\n\n    /**\n     * Parses the arguments for \":nth-child()\" and friends.\n     *\n     * @param Token[] $tokens\n     *\n     * @throws SyntaxErrorException\n     */\n    public static function parseSeries(array $tokens): array\n    {\n        foreach ($tokens as $token) {\n            if ($token->isString()) {\n                throw SyntaxErrorException::stringAsFunctionArgument();\n            }\n        }\n\n        $joined = trim(implode('', array_map(static fn (Token $token) => $token->getValue(), $tokens)));\n\n        $int = static function ($string) {\n            if (!is_numeric($string)) {\n                throw SyntaxErrorException::stringAsFunctionArgument();\n            }\n\n            return (int) $string;\n        };\n\n        switch (true) {\n            case 'odd' === $joined:\n                return [2, 1];\n            case 'even' === $joined:\n                return [2, 0];\n            case 'n' === $joined:\n                return [1, 0];\n            case !str_contains($joined, 'n'):\n                return [0, $int($joined)];\n        }\n\n        $split = explode('n', $joined);\n        $first = $split[0] ?? null;\n\n        return [\n            $first ? ('-' === $first || '+' === $first ? $int($first.'1') : $int($first)) : 1,\n            isset($split[1]) && $split[1] ? $int($split[1]) : 0,\n        ];\n    }\n\n    private function parseSelectorList(TokenStream $stream, bool $isArgument = false): array\n    {\n        $stream->skipWhitespace();\n        $selectors = [];\n\n        while (true) {\n            if ($isArgument && $stream->getPeek()->isDelimiter([')'])) {\n                break;\n            }\n\n            $selectors[] = $this->parserSelectorNode($stream, $isArgument);\n\n            if ($stream->getPeek()->isDelimiter([','])) {\n                $stream->getNext();\n                $stream->skipWhitespace();\n            } else {\n                break;\n            }\n        }\n\n        return $selectors;\n    }\n\n    private function parserSelectorNode(TokenStream $stream, bool $isArgument = false): Node\\SelectorNode\n    {\n        [$result, $pseudoElement] = $this->parseSimpleSelector($stream, false, $isArgument);\n\n        while (true) {\n            $stream->skipWhitespace();\n            $peek = $stream->getPeek();\n\n            if (\n                $peek->isFileEnd()\n                || $peek->isDelimiter([','])\n                || ($isArgument && $peek->isDelimiter([')']))\n            ) {\n                break;\n            }\n\n            if (null !== $pseudoElement) {\n                throw SyntaxErrorException::pseudoElementFound($pseudoElement, 'not at the end of a selector');\n            }\n\n            if ($peek->isDelimiter(['+', '>', '~'])) {\n                $combinator = $stream->getNext()->getValue();\n                $stream->skipWhitespace();\n            } else {\n                $combinator = ' ';\n            }\n\n            [$nextSelector, $pseudoElement] = $this->parseSimpleSelector($stream, false, $isArgument);\n            $result = new Node\\CombinedSelectorNode($result, $combinator, $nextSelector);\n        }\n\n        return new Node\\SelectorNode($result, $pseudoElement);\n    }\n\n    /**\n     * @throws SyntaxErrorException\n     * @throws InternalErrorException\n     */\n    private function parseRelativeSelector(TokenStream $stream): array\n    {\n        $stream->skipWhitespace();\n        $subSelector = '';\n        $next = $stream->getNext();\n\n        if ($next->isDelimiter(['+', '>', '~'])) {\n            $combinator = $next->getValue();\n            $stream->skipWhitespace();\n            $next = $stream->getNext();\n        } else {\n            $combinator = ' ';\n        }\n\n        while (true) {\n            if ($next->isString() || $next->isIdentifier() || $next->isNumber() || $next->isDelimiter(['.', '*'])) {\n                $subSelector .= $next->getValue();\n            } elseif ($next->isHash()) {\n                $subSelector .= '#'.$next->getValue();\n            } elseif ($next->isDelimiter([')'])) {\n                $result = $this->parse($subSelector);\n\n                return [$combinator, $result[0]];\n            } else {\n                throw SyntaxErrorException::unexpectedToken('an argument', $next);\n            }\n\n            $next = $stream->getNext();\n        }\n    }\n\n    /**\n     * Parses next simple node (hash, class, pseudo, negation).\n     *\n     * @throws SyntaxErrorException\n     * @throws InternalErrorException\n     */\n    private function parseSimpleSelector(TokenStream $stream, bool $insideNegation = false, bool $isArgument = false): array\n    {\n        $stream->skipWhitespace();\n\n        $selectorStart = \\count($stream->getUsed());\n        $result = $this->parseElementNode($stream);\n        $pseudoElement = null;\n\n        while (true) {\n            $peek = $stream->getPeek();\n            if ($peek->isWhitespace()\n                || $peek->isFileEnd()\n                || $peek->isDelimiter([',', '+', '>', '~'])\n                || ($isArgument && $peek->isDelimiter([')']))\n            ) {\n                break;\n            }\n\n            if (null !== $pseudoElement) {\n                throw SyntaxErrorException::pseudoElementFound($pseudoElement, 'not at the end of a selector');\n            }\n\n            if ($peek->isHash()) {\n                $result = new Node\\HashNode($result, $stream->getNext()->getValue());\n            } elseif ($peek->isDelimiter(['.'])) {\n                $stream->getNext();\n                $result = new Node\\ClassNode($result, $stream->getNextIdentifier());\n            } elseif ($peek->isDelimiter(['['])) {\n                $stream->getNext();\n                $result = $this->parseAttributeNode($result, $stream);\n            } elseif ($peek->isDelimiter([':'])) {\n                $stream->getNext();\n\n                if ($stream->getPeek()->isDelimiter([':'])) {\n                    $stream->getNext();\n                    $pseudoElement = $stream->getNextIdentifier();\n\n                    continue;\n                }\n\n                $identifier = $stream->getNextIdentifier();\n                if (\\in_array(strtolower($identifier), ['first-line', 'first-letter', 'before', 'after'], true)) {\n                    // Special case: CSS 2.1 pseudo-elements can have a single ':'.\n                    // Any new pseudo-element must have two.\n                    $pseudoElement = $identifier;\n\n                    continue;\n                }\n\n                if (!$stream->getPeek()->isDelimiter(['('])) {\n                    $result = new Node\\PseudoNode($result, $identifier);\n                    if ('Pseudo[Element[*]:scope]' === $result->__toString()) {\n                        $used = \\count($stream->getUsed());\n                        if (!(2 === $used\n                           || 3 === $used && $stream->getUsed()[0]->isWhiteSpace()\n                           || $used >= 3 && $stream->getUsed()[$used - 3]->isDelimiter([','])\n                           || $used >= 4\n                                && $stream->getUsed()[$used - 3]->isWhiteSpace()\n                                && $stream->getUsed()[$used - 4]->isDelimiter([','])\n                        )) {\n                            throw SyntaxErrorException::notAtTheStartOfASelector('scope');\n                        }\n                    }\n                    continue;\n                }\n\n                $stream->getNext();\n                $stream->skipWhitespace();\n\n                if ('not' === strtolower($identifier)) {\n                    if ($insideNegation) {\n                        throw SyntaxErrorException::nestedNot();\n                    }\n\n                    [$argument, $argumentPseudoElement] = $this->parseSimpleSelector($stream, true, true);\n                    $next = $stream->getNext();\n\n                    if (null !== $argumentPseudoElement) {\n                        throw SyntaxErrorException::pseudoElementFound($argumentPseudoElement, 'inside ::not()');\n                    }\n\n                    if (!$next->isDelimiter([')'])) {\n                        throw SyntaxErrorException::unexpectedToken('\")\"', $next);\n                    }\n\n                    $result = new Node\\NegationNode($result, $argument);\n                } elseif ('is' === strtolower($identifier)) {\n                    $selectors = $this->parseSelectorList($stream, true);\n\n                    $next = $stream->getNext();\n                    if (!$next->isDelimiter([')'])) {\n                        throw SyntaxErrorException::unexpectedToken('\")\"', $next);\n                    }\n\n                    $result = new Node\\MatchingNode($result, $selectors);\n                } elseif ('where' === strtolower($identifier)) {\n                    $selectors = $this->parseSelectorList($stream, true);\n\n                    $next = $stream->getNext();\n                    if (!$next->isDelimiter([')'])) {\n                        throw SyntaxErrorException::unexpectedToken('\")\"', $next);\n                    }\n\n                    $result = new Node\\SpecificityAdjustmentNode($result, $selectors);\n                } elseif ('has' === strtolower($identifier)) {\n                    [$combinator, $arguments] = $this->parseRelativeSelector($stream);\n                    $result = new Node\\RelationNode($result, $combinator, $arguments);\n                } else {\n                    $arguments = [];\n                    $next = null;\n\n                    while (true) {\n                        $stream->skipWhitespace();\n                        $next = $stream->getNext();\n\n                        if ($next->isIdentifier()\n                            || $next->isString()\n                            || $next->isNumber()\n                            || $next->isDelimiter(['+', '-'])\n                        ) {\n                            $arguments[] = $next;\n                        } elseif ($next->isDelimiter([')'])) {\n                            break;\n                        } else {\n                            throw SyntaxErrorException::unexpectedToken('an argument', $next);\n                        }\n                    }\n\n                    if (!$arguments) {\n                        throw SyntaxErrorException::unexpectedToken('at least one argument', $next);\n                    }\n\n                    $result = new Node\\FunctionNode($result, $identifier, $arguments);\n                }\n            } else {\n                throw SyntaxErrorException::unexpectedToken('selector', $peek);\n            }\n        }\n\n        if (\\count($stream->getUsed()) === $selectorStart) {\n            throw SyntaxErrorException::unexpectedToken('selector', $stream->getPeek());\n        }\n\n        return [$result, $pseudoElement];\n    }\n\n    private function parseElementNode(TokenStream $stream): Node\\ElementNode\n    {\n        $peek = $stream->getPeek();\n\n        if ($peek->isIdentifier() || $peek->isDelimiter(['*'])) {\n            if ($peek->isIdentifier()) {\n                $namespace = $stream->getNext()->getValue();\n            } else {\n                $stream->getNext();\n                $namespace = null;\n            }\n\n            if ($stream->getPeek()->isDelimiter(['|'])) {\n                $stream->getNext();\n                $element = $stream->getNextIdentifierOrStar();\n            } else {\n                $element = $namespace;\n                $namespace = null;\n            }\n        } else {\n            $element = $namespace = null;\n        }\n\n        return new Node\\ElementNode($namespace, $element);\n    }\n\n    private function parseAttributeNode(Node\\NodeInterface $selector, TokenStream $stream): Node\\AttributeNode\n    {\n        $stream->skipWhitespace();\n        $attribute = $stream->getNextIdentifierOrStar();\n\n        if (null === $attribute && !$stream->getPeek()->isDelimiter(['|'])) {\n            throw SyntaxErrorException::unexpectedToken('\"|\"', $stream->getPeek());\n        }\n\n        if ($stream->getPeek()->isDelimiter(['|'])) {\n            $stream->getNext();\n\n            if ($stream->getPeek()->isDelimiter(['='])) {\n                $namespace = null;\n                $stream->getNext();\n                $operator = '|=';\n            } else {\n                $namespace = $attribute;\n                $attribute = $stream->getNextIdentifier();\n                $operator = null;\n            }\n        } else {\n            $namespace = $operator = null;\n        }\n\n        if (null === $operator) {\n            $stream->skipWhitespace();\n            $next = $stream->getNext();\n\n            if ($next->isDelimiter([']'])) {\n                return new Node\\AttributeNode($selector, $namespace, $attribute, 'exists', null);\n            } elseif ($next->isDelimiter(['='])) {\n                $operator = '=';\n            } elseif ($next->isDelimiter(['^', '$', '*', '~', '|', '!'])\n                && $stream->getPeek()->isDelimiter(['='])\n            ) {\n                $operator = $next->getValue().'=';\n                $stream->getNext();\n            } else {\n                throw SyntaxErrorException::unexpectedToken('operator', $next);\n            }\n        }\n\n        $stream->skipWhitespace();\n        $value = $stream->getNext();\n\n        if ($value->isNumber()) {\n            // if the value is a number, it's casted into a string\n            $value = new Token(Token::TYPE_STRING, (string) $value->getValue(), $value->getPosition());\n        }\n\n        if (!($value->isIdentifier() || $value->isString())) {\n            throw SyntaxErrorException::unexpectedToken('string or identifier', $value);\n        }\n\n        $stream->skipWhitespace();\n        $next = $stream->getNext();\n\n        if (!$next->isDelimiter([']'])) {\n            throw SyntaxErrorException::unexpectedToken('\"]\"', $next);\n        }\n\n        return new Node\\AttributeNode($selector, $namespace, $attribute, $operator, $value->getValue());\n    }\n}\n"
  },
  {
    "path": "Parser/ParserInterface.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Parser;\n\nuse Symfony\\Component\\CssSelector\\Node\\SelectorNode;\n\n/**\n * CSS selector parser interface.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n *\n * @internal\n */\ninterface ParserInterface\n{\n    /**\n     * Parses given selector source into an array of tokens.\n     *\n     * @return SelectorNode[]\n     */\n    public function parse(string $source): array;\n}\n"
  },
  {
    "path": "Parser/Reader.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Parser;\n\n/**\n * CSS selector reader.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n *\n * @internal\n */\nclass Reader\n{\n    private int $length;\n    private int $position = 0;\n\n    public function __construct(\n        private string $source,\n    ) {\n        $this->length = \\strlen($source);\n    }\n\n    public function isEOF(): bool\n    {\n        return $this->position >= $this->length;\n    }\n\n    public function getPosition(): int\n    {\n        return $this->position;\n    }\n\n    public function getRemainingLength(): int\n    {\n        return $this->length - $this->position;\n    }\n\n    public function getSubstring(int $length, int $offset = 0): string\n    {\n        return substr($this->source, $this->position + $offset, $length);\n    }\n\n    public function getOffset(string $string): int|false\n    {\n        $position = strpos($this->source, $string, $this->position);\n\n        return false === $position ? false : $position - $this->position;\n    }\n\n    public function findPattern(string $pattern): array|false\n    {\n        $source = substr($this->source, $this->position);\n\n        if (preg_match($pattern, $source, $matches)) {\n            return $matches;\n        }\n\n        return false;\n    }\n\n    public function moveForward(int $length): void\n    {\n        $this->position += $length;\n    }\n\n    public function moveToEnd(): void\n    {\n        $this->position = $this->length;\n    }\n}\n"
  },
  {
    "path": "Parser/Shortcut/ClassParser.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Parser\\Shortcut;\n\nuse Symfony\\Component\\CssSelector\\Node\\ClassNode;\nuse Symfony\\Component\\CssSelector\\Node\\ElementNode;\nuse Symfony\\Component\\CssSelector\\Node\\SelectorNode;\nuse Symfony\\Component\\CssSelector\\Parser\\ParserInterface;\n\n/**\n * CSS selector class parser shortcut.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n *\n * @internal\n */\nclass ClassParser implements ParserInterface\n{\n    public function parse(string $source): array\n    {\n        // Matches an optional namespace, optional element, and required class\n        // $source = 'test|input.ab6bd_field';\n        // $matches = array (size=4)\n        //     0 => string 'test|input.ab6bd_field' (length=22)\n        //     1 => string 'test' (length=4)\n        //     2 => string 'input' (length=5)\n        //     3 => string 'ab6bd_field' (length=11)\n        if (preg_match('/^(?:([a-z]++)\\|)?+([\\w-]++|\\*)?+\\.([\\w-]++)$/i', trim($source), $matches)) {\n            return [\n                new SelectorNode(new ClassNode(new ElementNode($matches[1] ?: null, $matches[2] ?: null), $matches[3])),\n            ];\n        }\n\n        return [];\n    }\n}\n"
  },
  {
    "path": "Parser/Shortcut/ElementParser.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Parser\\Shortcut;\n\nuse Symfony\\Component\\CssSelector\\Node\\ElementNode;\nuse Symfony\\Component\\CssSelector\\Node\\SelectorNode;\nuse Symfony\\Component\\CssSelector\\Parser\\ParserInterface;\n\n/**\n * CSS selector element parser shortcut.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n *\n * @internal\n */\nclass ElementParser implements ParserInterface\n{\n    public function parse(string $source): array\n    {\n        // Matches an optional namespace, required element or `*`\n        // $source = 'testns|testel';\n        // $matches = array (size=3)\n        //     0 => string 'testns|testel' (length=13)\n        //     1 => string 'testns' (length=6)\n        //     2 => string 'testel' (length=6)\n        if (preg_match('/^(?:([a-z]++)\\|)?([\\w-]++|\\*)$/i', trim($source), $matches)) {\n            return [new SelectorNode(new ElementNode($matches[1] ?: null, $matches[2]))];\n        }\n\n        return [];\n    }\n}\n"
  },
  {
    "path": "Parser/Shortcut/EmptyStringParser.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Parser\\Shortcut;\n\nuse Symfony\\Component\\CssSelector\\Node\\ElementNode;\nuse Symfony\\Component\\CssSelector\\Node\\SelectorNode;\nuse Symfony\\Component\\CssSelector\\Parser\\ParserInterface;\n\n/**\n * CSS selector class parser shortcut.\n *\n * This shortcut ensure compatibility with previous version.\n * - The parser fails to parse an empty string.\n * - In the previous version, an empty string matches each tags.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n *\n * @internal\n */\nclass EmptyStringParser implements ParserInterface\n{\n    public function parse(string $source): array\n    {\n        // Matches an empty string\n        if ('' == $source) {\n            return [new SelectorNode(new ElementNode(null, '*'))];\n        }\n\n        return [];\n    }\n}\n"
  },
  {
    "path": "Parser/Shortcut/HashParser.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Parser\\Shortcut;\n\nuse Symfony\\Component\\CssSelector\\Node\\ElementNode;\nuse Symfony\\Component\\CssSelector\\Node\\HashNode;\nuse Symfony\\Component\\CssSelector\\Node\\SelectorNode;\nuse Symfony\\Component\\CssSelector\\Parser\\ParserInterface;\n\n/**\n * CSS selector hash parser shortcut.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n *\n * @internal\n */\nclass HashParser implements ParserInterface\n{\n    public function parse(string $source): array\n    {\n        // Matches an optional namespace, optional element, and required id\n        // $source = 'test|input#ab6bd_field';\n        // $matches = array (size=4)\n        //     0 => string 'test|input#ab6bd_field' (length=22)\n        //     1 => string 'test' (length=4)\n        //     2 => string 'input' (length=5)\n        //     3 => string 'ab6bd_field' (length=11)\n        if (preg_match('/^(?:([a-z]++)\\|)?+([\\w-]++|\\*)?+#([\\w-]++)$/i', trim($source), $matches)) {\n            return [\n                new SelectorNode(new HashNode(new ElementNode($matches[1] ?: null, $matches[2] ?: null), $matches[3])),\n            ];\n        }\n\n        return [];\n    }\n}\n"
  },
  {
    "path": "Parser/Token.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Parser;\n\n/**\n * CSS selector token.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n *\n * @internal\n */\nclass Token\n{\n    public const TYPE_FILE_END = 'eof';\n    public const TYPE_DELIMITER = 'delimiter';\n    public const TYPE_WHITESPACE = 'whitespace';\n    public const TYPE_IDENTIFIER = 'identifier';\n    public const TYPE_HASH = 'hash';\n    public const TYPE_NUMBER = 'number';\n    public const TYPE_STRING = 'string';\n\n    /**\n     * @param self::TYPE_*|null $type\n     */\n    public function __construct(\n        private ?string $type,\n        private ?string $value,\n        private ?int $position,\n    ) {\n    }\n\n    /**\n     * @return self::TYPE_*|null\n     */\n    public function getType(): ?string\n    {\n        return $this->type;\n    }\n\n    public function getValue(): ?string\n    {\n        return $this->value;\n    }\n\n    public function getPosition(): ?int\n    {\n        return $this->position;\n    }\n\n    public function isFileEnd(): bool\n    {\n        return self::TYPE_FILE_END === $this->type;\n    }\n\n    public function isDelimiter(array $values = []): bool\n    {\n        if (self::TYPE_DELIMITER !== $this->type) {\n            return false;\n        }\n\n        if (!$values) {\n            return true;\n        }\n\n        return \\in_array($this->value, $values, true);\n    }\n\n    public function isWhitespace(): bool\n    {\n        return self::TYPE_WHITESPACE === $this->type;\n    }\n\n    public function isIdentifier(): bool\n    {\n        return self::TYPE_IDENTIFIER === $this->type;\n    }\n\n    public function isHash(): bool\n    {\n        return self::TYPE_HASH === $this->type;\n    }\n\n    public function isNumber(): bool\n    {\n        return self::TYPE_NUMBER === $this->type;\n    }\n\n    public function isString(): bool\n    {\n        return self::TYPE_STRING === $this->type;\n    }\n\n    public function __toString(): string\n    {\n        if ($this->value) {\n            return \\sprintf('<%s \"%s\" at %s>', $this->type, $this->value, $this->position);\n        }\n\n        return \\sprintf('<%s at %s>', $this->type, $this->position);\n    }\n}\n"
  },
  {
    "path": "Parser/TokenStream.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Parser;\n\nuse Symfony\\Component\\CssSelector\\Exception\\InternalErrorException;\nuse Symfony\\Component\\CssSelector\\Exception\\SyntaxErrorException;\n\n/**\n * CSS selector token stream.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n *\n * @internal\n */\nclass TokenStream\n{\n    /**\n     * @var Token[]\n     */\n    private array $tokens = [];\n\n    /**\n     * @var Token[]\n     */\n    private array $used = [];\n\n    private int $cursor = 0;\n    private ?Token $peeked;\n    private bool $peeking = false;\n\n    /**\n     * Pushes a token.\n     *\n     * @return $this\n     */\n    public function push(Token $token): static\n    {\n        $this->tokens[] = $token;\n\n        return $this;\n    }\n\n    /**\n     * Freezes stream.\n     *\n     * @return $this\n     */\n    public function freeze(): static\n    {\n        return $this;\n    }\n\n    /**\n     * Returns next token.\n     *\n     * @throws InternalErrorException If there is no more token\n     */\n    public function getNext(): Token\n    {\n        if ($this->peeking) {\n            $this->peeking = false;\n            $this->used[] = $this->peeked;\n\n            return $this->peeked;\n        }\n\n        if (!isset($this->tokens[$this->cursor])) {\n            throw new InternalErrorException('Unexpected token stream end.');\n        }\n\n        return $this->tokens[$this->cursor++];\n    }\n\n    /**\n     * Returns peeked token.\n     */\n    public function getPeek(): Token\n    {\n        if (!$this->peeking) {\n            $this->peeked = $this->getNext();\n            $this->peeking = true;\n        }\n\n        return $this->peeked;\n    }\n\n    /**\n     * Returns used tokens.\n     *\n     * @return Token[]\n     */\n    public function getUsed(): array\n    {\n        return $this->used;\n    }\n\n    /**\n     * Returns next identifier token.\n     *\n     * @throws SyntaxErrorException If next token is not an identifier\n     */\n    public function getNextIdentifier(): string\n    {\n        $next = $this->getNext();\n\n        if (!$next->isIdentifier()) {\n            throw SyntaxErrorException::unexpectedToken('identifier', $next);\n        }\n\n        return $next->getValue();\n    }\n\n    /**\n     * Returns next identifier or null if star delimiter token is found.\n     *\n     * @throws SyntaxErrorException If next token is not an identifier or a star delimiter\n     */\n    public function getNextIdentifierOrStar(): ?string\n    {\n        $next = $this->getNext();\n\n        if ($next->isIdentifier()) {\n            return $next->getValue();\n        }\n\n        if ($next->isDelimiter(['*'])) {\n            return null;\n        }\n\n        throw SyntaxErrorException::unexpectedToken('identifier or \"*\"', $next);\n    }\n\n    /**\n     * Skips next whitespace if any.\n     */\n    public function skipWhitespace(): void\n    {\n        $peek = $this->getPeek();\n\n        if ($peek->isWhitespace()) {\n            $this->getNext();\n        }\n    }\n}\n"
  },
  {
    "path": "Parser/Tokenizer/Tokenizer.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Parser\\Tokenizer;\n\nuse Symfony\\Component\\CssSelector\\Parser\\Handler;\nuse Symfony\\Component\\CssSelector\\Parser\\Reader;\nuse Symfony\\Component\\CssSelector\\Parser\\Token;\nuse Symfony\\Component\\CssSelector\\Parser\\TokenStream;\n\n/**\n * CSS selector tokenizer.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n *\n * @internal\n */\nclass Tokenizer\n{\n    /**\n     * @var Handler\\HandlerInterface[]\n     */\n    private array $handlers;\n\n    public function __construct()\n    {\n        $patterns = new TokenizerPatterns();\n        $escaping = new TokenizerEscaping($patterns);\n\n        $this->handlers = [\n            new Handler\\WhitespaceHandler(),\n            new Handler\\IdentifierHandler($patterns, $escaping),\n            new Handler\\HashHandler($patterns, $escaping),\n            new Handler\\StringHandler($patterns, $escaping),\n            new Handler\\NumberHandler($patterns),\n            new Handler\\CommentHandler(),\n        ];\n    }\n\n    /**\n     * Tokenize selector source code.\n     */\n    public function tokenize(Reader $reader): TokenStream\n    {\n        $stream = new TokenStream();\n\n        while (!$reader->isEOF()) {\n            foreach ($this->handlers as $handler) {\n                if ($handler->handle($reader, $stream)) {\n                    continue 2;\n                }\n            }\n\n            $stream->push(new Token(Token::TYPE_DELIMITER, $reader->getSubstring(1), $reader->getPosition()));\n            $reader->moveForward(1);\n        }\n\n        return $stream\n            ->push(new Token(Token::TYPE_FILE_END, null, $reader->getPosition()))\n            ->freeze();\n    }\n}\n"
  },
  {
    "path": "Parser/Tokenizer/TokenizerEscaping.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Parser\\Tokenizer;\n\n/**\n * CSS selector tokenizer escaping applier.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n *\n * @internal\n */\nclass TokenizerEscaping\n{\n    public function __construct(\n        private TokenizerPatterns $patterns,\n    ) {\n    }\n\n    public function escapeUnicode(string $value): string\n    {\n        $value = $this->replaceUnicodeSequences($value);\n\n        return preg_replace($this->patterns->getSimpleEscapePattern(), '$1', $value);\n    }\n\n    public function escapeUnicodeAndNewLine(string $value): string\n    {\n        $value = preg_replace($this->patterns->getNewLineEscapePattern(), '', $value);\n\n        return $this->escapeUnicode($value);\n    }\n\n    private function replaceUnicodeSequences(string $value): string\n    {\n        return preg_replace_callback($this->patterns->getUnicodeEscapePattern(), static function ($match) {\n            $c = hexdec($match[1]);\n\n            if (0x80 > $c %= 0x200000) {\n                return \\chr($c);\n            }\n            if (0x800 > $c) {\n                return \\chr(0xC0 | $c >> 6).\\chr(0x80 | $c & 0x3F);\n            }\n            if (0x10000 > $c) {\n                return \\chr(0xE0 | $c >> 12).\\chr(0x80 | $c >> 6 & 0x3F).\\chr(0x80 | $c & 0x3F);\n            }\n\n            return '';\n        }, $value);\n    }\n}\n"
  },
  {
    "path": "Parser/Tokenizer/TokenizerPatterns.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Parser\\Tokenizer;\n\n/**\n * CSS selector tokenizer patterns builder.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n *\n * @internal\n */\nclass TokenizerPatterns\n{\n    private string $unicodeEscapePattern;\n    private string $simpleEscapePattern;\n    private string $newLineEscapePattern;\n    private string $escapePattern;\n    private string $stringEscapePattern;\n    private string $nonAsciiPattern;\n    private string $nmCharPattern;\n    private string $nmStartPattern;\n    private string $identifierPattern;\n    private string $hashPattern;\n    private string $numberPattern;\n    private string $quotedStringPattern;\n\n    public function __construct()\n    {\n        $this->unicodeEscapePattern = '\\\\\\\\([0-9a-f]{1,6})(?:\\r\\n|[ \\n\\r\\t\\f])?';\n        $this->simpleEscapePattern = '\\\\\\\\(.)';\n        $this->newLineEscapePattern = '\\\\\\\\(?:\\n|\\r\\n|\\r|\\f)';\n        $this->escapePattern = $this->unicodeEscapePattern.'|\\\\\\\\[^\\n\\r\\f0-9a-f]';\n        $this->stringEscapePattern = $this->newLineEscapePattern.'|'.$this->escapePattern;\n        $this->nonAsciiPattern = '[^\\x00-\\x7F]';\n        $this->nmCharPattern = '[_a-z0-9-]|'.$this->escapePattern.'|'.$this->nonAsciiPattern;\n        $this->nmStartPattern = '[_a-z]|'.$this->escapePattern.'|'.$this->nonAsciiPattern;\n        $this->identifierPattern = '-?(?:'.$this->nmStartPattern.')(?:'.$this->nmCharPattern.')*';\n        $this->hashPattern = '#((?:'.$this->nmCharPattern.')+)';\n        $this->numberPattern = '[+-]?(?:[0-9]*\\.[0-9]+|[0-9]+)';\n        $this->quotedStringPattern = '([^\\n\\r\\f\\\\\\\\%s]|'.$this->stringEscapePattern.')*';\n    }\n\n    public function getNewLineEscapePattern(): string\n    {\n        return '~'.$this->newLineEscapePattern.'~';\n    }\n\n    public function getSimpleEscapePattern(): string\n    {\n        return '~'.$this->simpleEscapePattern.'~';\n    }\n\n    public function getUnicodeEscapePattern(): string\n    {\n        return '~'.$this->unicodeEscapePattern.'~i';\n    }\n\n    public function getIdentifierPattern(): string\n    {\n        return '~^'.$this->identifierPattern.'~i';\n    }\n\n    public function getHashPattern(): string\n    {\n        return '~^'.$this->hashPattern.'~i';\n    }\n\n    public function getNumberPattern(): string\n    {\n        return '~^'.$this->numberPattern.'~';\n    }\n\n    public function getQuotedStringPattern(string $quote): string\n    {\n        return '~^'.\\sprintf($this->quotedStringPattern, $quote).'~i';\n    }\n}\n"
  },
  {
    "path": "README.md",
    "content": "CssSelector Component\n=====================\n\nThe CssSelector component converts CSS selectors to XPath expressions.\n\nResources\n---------\n\n * [Documentation](https://symfony.com/doc/current/components/css_selector.html)\n * [Contributing](https://symfony.com/doc/current/contributing/index.html)\n * [Report issues](https://github.com/symfony/symfony/issues) and\n   [send Pull Requests](https://github.com/symfony/symfony/pulls)\n   in the [main Symfony repository](https://github.com/symfony/symfony)\n\nCredits\n-------\n\nThis component is a port of the Python cssselect library\n[v0.7.1](https://github.com/SimonSapin/cssselect/releases/tag/v0.7.1),\nwhich is distributed under the BSD license.\n"
  },
  {
    "path": "Tests/CssSelectorConverterTest.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Tests;\n\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\CssSelector\\CssSelectorConverter;\nuse Symfony\\Component\\CssSelector\\Exception\\ParseException;\n\nclass CssSelectorConverterTest extends TestCase\n{\n    public function testCssToXPath()\n    {\n        $converter = new CssSelectorConverter();\n\n        $this->assertEquals('descendant-or-self::*', $converter->toXPath(''));\n        $this->assertEquals('descendant-or-self::h1', $converter->toXPath('h1'));\n        $this->assertEquals(\"descendant-or-self::h1[@id = 'foo']\", $converter->toXPath('h1#foo'));\n        $this->assertEquals(\"descendant-or-self::h1[@class and contains(concat(' ', normalize-space(@class), ' '), ' foo ')]\", $converter->toXPath('h1.foo'));\n        $this->assertEquals('descendant-or-self::foo:h1', $converter->toXPath('foo|h1'));\n        $this->assertEquals('descendant-or-self::h1', $converter->toXPath('H1'));\n\n        // Test the cache layer\n        $converter = new CssSelectorConverter();\n        $this->assertEquals('descendant-or-self::h1', $converter->toXPath('H1'));\n    }\n\n    public function testCssToXPathXml()\n    {\n        $converter = new CssSelectorConverter(false);\n\n        $this->assertEquals('descendant-or-self::H1', $converter->toXPath('H1'));\n\n        $converter = new CssSelectorConverter(false);\n        // Test the cache layer\n        $this->assertEquals('descendant-or-self::H1', $converter->toXPath('H1'));\n    }\n\n    public function testParseExceptions()\n    {\n        $this->expectException(ParseException::class);\n        $this->expectExceptionMessage('Expected identifier, but <eof at 3> found.');\n        (new CssSelectorConverter())->toXPath('h1:');\n    }\n\n    public function testLruCacheMovesRecentlyUsedToEnd()\n    {\n        CssSelectorConverter::$maxCachedItems = 5;\n        $htmlCacheProperty = new \\ReflectionProperty(CssSelectorConverter::class, 'htmlCache');\n        $htmlCacheProperty->setValue(null, []);\n\n        $converter = new CssSelectorConverter(true);\n\n        // Fill cache with 5 entries (h0-h4)\n        for ($i = 0; $i < 5; ++$i) {\n            $converter->toXPath(\"h$i\");\n        }\n\n        // Access h0 to move it to end (most recently used)\n        $converter->toXPath('h0');\n\n        // Trigger eviction\n        $converter->toXPath('h5');\n\n        $cache = $htmlCacheProperty->getValue();\n\n        // h0 was accessed recently (moved to end), so it survives eviction\n        $this->assertArrayHasKey(\"descendant-or-self::\\0h0\", $cache);\n        // h5 is the newest entry\n        $this->assertArrayHasKey(\"descendant-or-self::\\0h5\", $cache);\n        // h1 was the oldest untouched entry, should be evicted\n        $this->assertArrayNotHasKey(\"descendant-or-self::\\0h1\", $cache);\n\n        CssSelectorConverter::$maxCachedItems = 1024;\n    }\n\n    #[DataProvider('getCssToXPathWithoutPrefixTestData')]\n    public function testCssToXPathWithoutPrefix($css, $xpath)\n    {\n        $converter = new CssSelectorConverter();\n\n        $this->assertEquals($xpath, $converter->toXPath($css, ''), '->parse() parses an input string and returns a node');\n    }\n\n    public static function getCssToXPathWithoutPrefixTestData(): array\n    {\n        return [\n            ['h1', 'h1'],\n            ['foo|h1', 'foo:h1'],\n            ['h1, h2, h3', 'h1 | h2 | h3'],\n            ['h1:nth-child(3n+1)', \"*/*[(name() = 'h1') and (position() - 1 >= 0 and (position() - 1) mod 3 = 0)]\"],\n            ['h1 > p', 'h1/p'],\n            ['h1#foo', \"h1[@id = 'foo']\"],\n            ['h1.foo', \"h1[@class and contains(concat(' ', normalize-space(@class), ' '), ' foo ')]\"],\n            ['h1[class*=\"foo bar\"]', \"h1[@class and contains(@class, 'foo bar')]\"],\n            ['h1[foo|class*=\"foo bar\"]', \"h1[@foo:class and contains(@foo:class, 'foo bar')]\"],\n            ['h1[class]', 'h1[@class]'],\n            ['h1 .foo', \"h1/descendant-or-self::*/*[@class and contains(concat(' ', normalize-space(@class), ' '), ' foo ')]\"],\n            ['h1 #foo', \"h1/descendant-or-self::*/*[@id = 'foo']\"],\n            ['h1 [class*=foo]', \"h1/descendant-or-self::*/*[@class and contains(@class, 'foo')]\"],\n            ['div>.foo', \"div/*[@class and contains(concat(' ', normalize-space(@class), ' '), ' foo ')]\"],\n            ['div > .foo', \"div/*[@class and contains(concat(' ', normalize-space(@class), ' '), ' foo ')]\"],\n        ];\n    }\n}\n"
  },
  {
    "path": "Tests/Node/AbstractNodeTestCase.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Tests\\Node;\n\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\CssSelector\\Node\\NodeInterface;\n\nabstract class AbstractNodeTestCase extends TestCase\n{\n    #[DataProvider('getToStringConversionTestData')]\n    public function testToStringConversion(NodeInterface $node, $representation)\n    {\n        $this->assertEquals($representation, (string) $node);\n    }\n\n    #[DataProvider('getSpecificityValueTestData')]\n    public function testSpecificityValue(NodeInterface $node, $value)\n    {\n        $this->assertEquals($value, $node->getSpecificity()->getValue());\n    }\n\n    abstract public static function getToStringConversionTestData();\n\n    abstract public static function getSpecificityValueTestData();\n}\n"
  },
  {
    "path": "Tests/Node/AttributeNodeTest.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Tests\\Node;\n\nuse Symfony\\Component\\CssSelector\\Node\\AttributeNode;\nuse Symfony\\Component\\CssSelector\\Node\\ElementNode;\n\nclass AttributeNodeTest extends AbstractNodeTestCase\n{\n    public static function getToStringConversionTestData()\n    {\n        return [\n            [new AttributeNode(new ElementNode(), null, 'attribute', 'exists', null), 'Attribute[Element[*][attribute]]'],\n            [new AttributeNode(new ElementNode(), null, 'attribute', '$=', 'value'), \"Attribute[Element[*][attribute $= 'value']]\"],\n            [new AttributeNode(new ElementNode(), 'namespace', 'attribute', '$=', 'value'), \"Attribute[Element[*][namespace|attribute $= 'value']]\"],\n        ];\n    }\n\n    public static function getSpecificityValueTestData()\n    {\n        return [\n            [new AttributeNode(new ElementNode(), null, 'attribute', 'exists', null), 10],\n            [new AttributeNode(new ElementNode(null, 'element'), null, 'attribute', 'exists', null), 11],\n            [new AttributeNode(new ElementNode(), null, 'attribute', '$=', 'value'), 10],\n            [new AttributeNode(new ElementNode(), 'namespace', 'attribute', '$=', 'value'), 10],\n        ];\n    }\n}\n"
  },
  {
    "path": "Tests/Node/ClassNodeTest.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Tests\\Node;\n\nuse Symfony\\Component\\CssSelector\\Node\\ClassNode;\nuse Symfony\\Component\\CssSelector\\Node\\ElementNode;\n\nclass ClassNodeTest extends AbstractNodeTestCase\n{\n    public static function getToStringConversionTestData()\n    {\n        return [\n            [new ClassNode(new ElementNode(), 'class'), 'Class[Element[*].class]'],\n        ];\n    }\n\n    public static function getSpecificityValueTestData()\n    {\n        return [\n            [new ClassNode(new ElementNode(), 'class'), 10],\n            [new ClassNode(new ElementNode(null, 'element'), 'class'), 11],\n        ];\n    }\n}\n"
  },
  {
    "path": "Tests/Node/CombinedSelectorNodeTest.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Tests\\Node;\n\nuse Symfony\\Component\\CssSelector\\Node\\CombinedSelectorNode;\nuse Symfony\\Component\\CssSelector\\Node\\ElementNode;\n\nclass CombinedSelectorNodeTest extends AbstractNodeTestCase\n{\n    public static function getToStringConversionTestData()\n    {\n        return [\n            [new CombinedSelectorNode(new ElementNode(), '>', new ElementNode()), 'CombinedSelector[Element[*] > Element[*]]'],\n            [new CombinedSelectorNode(new ElementNode(), ' ', new ElementNode()), 'CombinedSelector[Element[*] <followed> Element[*]]'],\n        ];\n    }\n\n    public static function getSpecificityValueTestData()\n    {\n        return [\n            [new CombinedSelectorNode(new ElementNode(), '>', new ElementNode()), 0],\n            [new CombinedSelectorNode(new ElementNode(null, 'element'), '>', new ElementNode()), 1],\n            [new CombinedSelectorNode(new ElementNode(null, 'element'), '>', new ElementNode(null, 'element')), 2],\n        ];\n    }\n}\n"
  },
  {
    "path": "Tests/Node/ElementNodeTest.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Tests\\Node;\n\nuse Symfony\\Component\\CssSelector\\Node\\ElementNode;\n\nclass ElementNodeTest extends AbstractNodeTestCase\n{\n    public static function getToStringConversionTestData()\n    {\n        return [\n            [new ElementNode(), 'Element[*]'],\n            [new ElementNode(null, 'element'), 'Element[element]'],\n            [new ElementNode('namespace', 'element'), 'Element[namespace|element]'],\n        ];\n    }\n\n    public static function getSpecificityValueTestData()\n    {\n        return [\n            [new ElementNode(), 0],\n            [new ElementNode(null, 'element'), 1],\n            [new ElementNode('namespace', 'element'), 1],\n        ];\n    }\n}\n"
  },
  {
    "path": "Tests/Node/FunctionNodeTest.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Tests\\Node;\n\nuse Symfony\\Component\\CssSelector\\Node\\ElementNode;\nuse Symfony\\Component\\CssSelector\\Node\\FunctionNode;\nuse Symfony\\Component\\CssSelector\\Parser\\Token;\n\nclass FunctionNodeTest extends AbstractNodeTestCase\n{\n    public static function getToStringConversionTestData()\n    {\n        return [\n            [new FunctionNode(new ElementNode(), 'function'), 'Function[Element[*]:function()]'],\n            [new FunctionNode(new ElementNode(), 'function', [\n                new Token(Token::TYPE_IDENTIFIER, 'value', 0),\n            ]), \"Function[Element[*]:function(['value'])]\"],\n            [new FunctionNode(new ElementNode(), 'function', [\n                new Token(Token::TYPE_STRING, 'value1', 0),\n                new Token(Token::TYPE_NUMBER, 'value2', 0),\n            ]), \"Function[Element[*]:function(['value1', 'value2'])]\"],\n        ];\n    }\n\n    public static function getSpecificityValueTestData()\n    {\n        return [\n            [new FunctionNode(new ElementNode(), 'function'), 10],\n            [new FunctionNode(new ElementNode(), 'function', [\n                new Token(Token::TYPE_IDENTIFIER, 'value', 0),\n            ]), 10],\n            [new FunctionNode(new ElementNode(), 'function', [\n                new Token(Token::TYPE_STRING, 'value1', 0),\n                new Token(Token::TYPE_NUMBER, 'value2', 0),\n            ]), 10],\n        ];\n    }\n}\n"
  },
  {
    "path": "Tests/Node/HashNodeTest.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Tests\\Node;\n\nuse Symfony\\Component\\CssSelector\\Node\\ElementNode;\nuse Symfony\\Component\\CssSelector\\Node\\HashNode;\n\nclass HashNodeTest extends AbstractNodeTestCase\n{\n    public static function getToStringConversionTestData()\n    {\n        return [\n            [new HashNode(new ElementNode(), 'id'), 'Hash[Element[*]#id]'],\n        ];\n    }\n\n    public static function getSpecificityValueTestData()\n    {\n        return [\n            [new HashNode(new ElementNode(), 'id'), 100],\n            [new HashNode(new ElementNode(null, 'id'), 'class'), 101],\n        ];\n    }\n}\n"
  },
  {
    "path": "Tests/Node/MatchingNodeTest.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Tests\\Node;\n\nuse Symfony\\Component\\CssSelector\\Node\\ClassNode;\nuse Symfony\\Component\\CssSelector\\Node\\ElementNode;\nuse Symfony\\Component\\CssSelector\\Node\\HashNode;\nuse Symfony\\Component\\CssSelector\\Node\\MatchingNode;\n\nclass MatchingNodeTest extends AbstractNodeTestCase\n{\n    public static function getToStringConversionTestData()\n    {\n        return [\n            [new MatchingNode(new ElementNode(), [\n                new ClassNode(new ElementNode(), 'class'),\n                new HashNode(new ElementNode(), 'id'),\n            ]), 'Matching[Element[*]:is(Class[Element[*].class], Hash[Element[*]#id])]'],\n        ];\n    }\n\n    public static function getSpecificityValueTestData()\n    {\n        return [\n            [new MatchingNode(new ElementNode(), [\n                new ClassNode(new ElementNode(), 'class'),\n                new HashNode(new ElementNode(), 'id'),\n            ]), 100],\n            [new MatchingNode(new ClassNode(new ElementNode(), 'class'), [\n                new ClassNode(new ElementNode(), 'class'),\n                new HashNode(new ElementNode(), 'id'),\n            ]), 110],\n            [new MatchingNode(new HashNode(new ElementNode(), 'id'), [\n                new ClassNode(new ElementNode(), 'class'),\n                new HashNode(new ElementNode(), 'id'),\n            ]), 200],\n        ];\n    }\n}\n"
  },
  {
    "path": "Tests/Node/NegationNodeTest.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Tests\\Node;\n\nuse Symfony\\Component\\CssSelector\\Node\\ClassNode;\nuse Symfony\\Component\\CssSelector\\Node\\ElementNode;\nuse Symfony\\Component\\CssSelector\\Node\\NegationNode;\n\nclass NegationNodeTest extends AbstractNodeTestCase\n{\n    public static function getToStringConversionTestData()\n    {\n        return [\n            [new NegationNode(new ElementNode(), new ClassNode(new ElementNode(), 'class')), 'Negation[Element[*]:not(Class[Element[*].class])]'],\n        ];\n    }\n\n    public static function getSpecificityValueTestData()\n    {\n        return [\n            [new NegationNode(new ElementNode(), new ClassNode(new ElementNode(), 'class')), 10],\n        ];\n    }\n}\n"
  },
  {
    "path": "Tests/Node/PseudoNodeTest.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Tests\\Node;\n\nuse Symfony\\Component\\CssSelector\\Node\\ElementNode;\nuse Symfony\\Component\\CssSelector\\Node\\PseudoNode;\n\nclass PseudoNodeTest extends AbstractNodeTestCase\n{\n    public static function getToStringConversionTestData()\n    {\n        return [\n            [new PseudoNode(new ElementNode(), 'pseudo'), 'Pseudo[Element[*]:pseudo]'],\n        ];\n    }\n\n    public static function getSpecificityValueTestData()\n    {\n        return [\n            [new PseudoNode(new ElementNode(), 'pseudo'), 10],\n        ];\n    }\n}\n"
  },
  {
    "path": "Tests/Node/SelectorNodeTest.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Tests\\Node;\n\nuse Symfony\\Component\\CssSelector\\Node\\ElementNode;\nuse Symfony\\Component\\CssSelector\\Node\\SelectorNode;\n\nclass SelectorNodeTest extends AbstractNodeTestCase\n{\n    public static function getToStringConversionTestData()\n    {\n        return [\n            [new SelectorNode(new ElementNode()), 'Selector[Element[*]]'],\n            [new SelectorNode(new ElementNode(), 'pseudo'), 'Selector[Element[*]::pseudo]'],\n        ];\n    }\n\n    public static function getSpecificityValueTestData()\n    {\n        return [\n            [new SelectorNode(new ElementNode()), 0],\n            [new SelectorNode(new ElementNode(), 'pseudo'), 1],\n        ];\n    }\n}\n"
  },
  {
    "path": "Tests/Node/SpecificityAdjustmentNodeTest.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Tests\\Node;\n\nuse Symfony\\Component\\CssSelector\\Node\\ClassNode;\nuse Symfony\\Component\\CssSelector\\Node\\ElementNode;\nuse Symfony\\Component\\CssSelector\\Node\\HashNode;\nuse Symfony\\Component\\CssSelector\\Node\\SpecificityAdjustmentNode;\n\nclass SpecificityAdjustmentNodeTest extends AbstractNodeTestCase\n{\n    public static function getToStringConversionTestData()\n    {\n        return [\n            [new SpecificityAdjustmentNode(new ElementNode(), [\n                new ClassNode(new ElementNode(), 'class'),\n                new HashNode(new ElementNode(), 'id'),\n            ]), 'SpecificityAdjustment[Element[*]:where(Class[Element[*].class], Hash[Element[*]#id])]'],\n        ];\n    }\n\n    public static function getSpecificityValueTestData()\n    {\n        return [\n            [new SpecificityAdjustmentNode(new ElementNode(), [\n                new ClassNode(new ElementNode(), 'class'),\n                new HashNode(new ElementNode(), 'id'),\n            ]), 0],\n            [new SpecificityAdjustmentNode(new ClassNode(new ElementNode(), 'class'), [\n                new ClassNode(new ElementNode(), 'class'),\n                new HashNode(new ElementNode(), 'id'),\n            ]), 10],\n        ];\n    }\n}\n"
  },
  {
    "path": "Tests/Node/SpecificityTest.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Tests\\Node;\n\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\CssSelector\\Node\\Specificity;\n\nclass SpecificityTest extends TestCase\n{\n    #[DataProvider('getValueTestData')]\n    public function testValue(Specificity $specificity, $value)\n    {\n        $this->assertEquals($value, $specificity->getValue());\n    }\n\n    #[DataProvider('getValueTestData')]\n    public function testPlusValue(Specificity $specificity, $value)\n    {\n        $this->assertEquals($value + 123, $specificity->plus(new Specificity(1, 2, 3))->getValue());\n    }\n\n    public static function getValueTestData()\n    {\n        return [\n            [new Specificity(0, 0, 0), 0],\n            [new Specificity(0, 0, 2), 2],\n            [new Specificity(0, 3, 0), 30],\n            [new Specificity(4, 0, 0), 400],\n            [new Specificity(4, 3, 2), 432],\n        ];\n    }\n\n    #[DataProvider('getCompareTestData')]\n    public function testCompareTo(Specificity $a, Specificity $b, $result)\n    {\n        $this->assertEquals($result, $a->compareTo($b));\n    }\n\n    public static function getCompareTestData()\n    {\n        return [\n            [new Specificity(0, 0, 0), new Specificity(0, 0, 0), 0],\n            [new Specificity(0, 0, 1), new Specificity(0, 0, 1), 0],\n            [new Specificity(0, 0, 2), new Specificity(0, 0, 1), 1],\n            [new Specificity(0, 0, 2), new Specificity(0, 0, 3), -1],\n            [new Specificity(0, 4, 0), new Specificity(0, 4, 0), 0],\n            [new Specificity(0, 6, 0), new Specificity(0, 5, 11), 1],\n            [new Specificity(0, 7, 0), new Specificity(0, 8, 0), -1],\n            [new Specificity(9, 0, 0), new Specificity(9, 0, 0), 0],\n            [new Specificity(11, 0, 0), new Specificity(10, 11, 0), 1],\n            [new Specificity(12, 11, 0), new Specificity(13, 0, 0), -1],\n        ];\n    }\n}\n"
  },
  {
    "path": "Tests/Parser/Handler/AbstractHandlerTestCase.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Tests\\Parser\\Handler;\n\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\CssSelector\\Parser\\Reader;\nuse Symfony\\Component\\CssSelector\\Parser\\Token;\nuse Symfony\\Component\\CssSelector\\Parser\\TokenStream;\n\n/**\n * @author Jean-François Simon <contact@jfsimon.fr>\n */\nabstract class AbstractHandlerTestCase extends TestCase\n{\n    #[DataProvider('getHandleValueTestData')]\n    public function testHandleValue($value, Token $expectedToken, $remainingContent)\n    {\n        $reader = new Reader($value);\n        $stream = new TokenStream();\n\n        $this->assertTrue($this->generateHandler()->handle($reader, $stream));\n        $this->assertEquals($expectedToken, $stream->getNext());\n        $this->assertRemainingContent($reader, $remainingContent);\n    }\n\n    #[DataProvider('getDontHandleValueTestData')]\n    public function testDontHandleValue($value)\n    {\n        $reader = new Reader($value);\n        $stream = new TokenStream();\n\n        $this->assertFalse($this->generateHandler()->handle($reader, $stream));\n        $this->assertStreamEmpty($stream);\n        $this->assertRemainingContent($reader, $value);\n    }\n\n    abstract public static function getHandleValueTestData();\n\n    abstract public static function getDontHandleValueTestData();\n\n    abstract protected function generateHandler();\n\n    protected function assertStreamEmpty(TokenStream $stream)\n    {\n        $property = new \\ReflectionProperty($stream, 'tokens');\n\n        $this->assertEquals([], $property->getValue($stream));\n    }\n\n    protected function assertRemainingContent(Reader $reader, $remainingContent)\n    {\n        if ('' === $remainingContent) {\n            $this->assertEquals(0, $reader->getRemainingLength());\n            $this->assertTrue($reader->isEOF());\n        } else {\n            $this->assertEquals(\\strlen($remainingContent), $reader->getRemainingLength());\n            $this->assertEquals(0, $reader->getOffset($remainingContent));\n        }\n    }\n}\n"
  },
  {
    "path": "Tests/Parser/Handler/CommentHandlerTest.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Tests\\Parser\\Handler;\n\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse Symfony\\Component\\CssSelector\\Parser\\Handler\\CommentHandler;\nuse Symfony\\Component\\CssSelector\\Parser\\Reader;\nuse Symfony\\Component\\CssSelector\\Parser\\Token;\nuse Symfony\\Component\\CssSelector\\Parser\\TokenStream;\n\nclass CommentHandlerTest extends AbstractHandlerTestCase\n{\n    #[DataProvider('getHandleValueTestData')]\n    public function testHandleValue($value, Token $unusedArgument, $remainingContent)\n    {\n        $reader = new Reader($value);\n        $stream = new TokenStream();\n\n        $this->assertTrue($this->generateHandler()->handle($reader, $stream));\n        // comments are ignored (not pushed as token in stream)\n        $this->assertStreamEmpty($stream);\n        $this->assertRemainingContent($reader, $remainingContent);\n    }\n\n    public static function getHandleValueTestData()\n    {\n        return [\n            // 2nd argument only exists for inherited method compatibility\n            ['/* comment */', new Token(null, null, null), ''],\n            ['/* comment */foo', new Token(null, null, null), 'foo'],\n        ];\n    }\n\n    public static function getDontHandleValueTestData()\n    {\n        return [\n            ['>'],\n            ['+'],\n            [' '],\n        ];\n    }\n\n    protected function generateHandler()\n    {\n        return new CommentHandler();\n    }\n}\n"
  },
  {
    "path": "Tests/Parser/Handler/HashHandlerTest.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Tests\\Parser\\Handler;\n\nuse Symfony\\Component\\CssSelector\\Parser\\Handler\\HashHandler;\nuse Symfony\\Component\\CssSelector\\Parser\\Token;\nuse Symfony\\Component\\CssSelector\\Parser\\Tokenizer\\TokenizerEscaping;\nuse Symfony\\Component\\CssSelector\\Parser\\Tokenizer\\TokenizerPatterns;\n\nclass HashHandlerTest extends AbstractHandlerTestCase\n{\n    public static function getHandleValueTestData()\n    {\n        return [\n            ['#id', new Token(Token::TYPE_HASH, 'id', 0), ''],\n            ['#123', new Token(Token::TYPE_HASH, '123', 0), ''],\n\n            ['#id.class', new Token(Token::TYPE_HASH, 'id', 0), '.class'],\n            ['#id element', new Token(Token::TYPE_HASH, 'id', 0), ' element'],\n        ];\n    }\n\n    public static function getDontHandleValueTestData()\n    {\n        return [\n            ['id'],\n            ['123'],\n            ['<'],\n            ['<'],\n            ['#'],\n        ];\n    }\n\n    protected function generateHandler()\n    {\n        $patterns = new TokenizerPatterns();\n\n        return new HashHandler($patterns, new TokenizerEscaping($patterns));\n    }\n}\n"
  },
  {
    "path": "Tests/Parser/Handler/IdentifierHandlerTest.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Tests\\Parser\\Handler;\n\nuse Symfony\\Component\\CssSelector\\Parser\\Handler\\IdentifierHandler;\nuse Symfony\\Component\\CssSelector\\Parser\\Token;\nuse Symfony\\Component\\CssSelector\\Parser\\Tokenizer\\TokenizerEscaping;\nuse Symfony\\Component\\CssSelector\\Parser\\Tokenizer\\TokenizerPatterns;\n\nclass IdentifierHandlerTest extends AbstractHandlerTestCase\n{\n    public static function getHandleValueTestData()\n    {\n        return [\n            ['foo', new Token(Token::TYPE_IDENTIFIER, 'foo', 0), ''],\n            ['foo|bar', new Token(Token::TYPE_IDENTIFIER, 'foo', 0), '|bar'],\n            ['foo.class', new Token(Token::TYPE_IDENTIFIER, 'foo', 0), '.class'],\n            ['foo[attr]', new Token(Token::TYPE_IDENTIFIER, 'foo', 0), '[attr]'],\n            ['foo bar', new Token(Token::TYPE_IDENTIFIER, 'foo', 0), ' bar'],\n        ];\n    }\n\n    public static function getDontHandleValueTestData()\n    {\n        return [\n            ['>'],\n            ['+'],\n            [' '],\n            ['*|foo'],\n            ['/* comment */'],\n        ];\n    }\n\n    protected function generateHandler()\n    {\n        $patterns = new TokenizerPatterns();\n\n        return new IdentifierHandler($patterns, new TokenizerEscaping($patterns));\n    }\n}\n"
  },
  {
    "path": "Tests/Parser/Handler/NumberHandlerTest.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Tests\\Parser\\Handler;\n\nuse Symfony\\Component\\CssSelector\\Parser\\Handler\\NumberHandler;\nuse Symfony\\Component\\CssSelector\\Parser\\Token;\nuse Symfony\\Component\\CssSelector\\Parser\\Tokenizer\\TokenizerPatterns;\n\nclass NumberHandlerTest extends AbstractHandlerTestCase\n{\n    public static function getHandleValueTestData()\n    {\n        return [\n            ['12', new Token(Token::TYPE_NUMBER, '12', 0), ''],\n            ['12.34', new Token(Token::TYPE_NUMBER, '12.34', 0), ''],\n            ['+12.34', new Token(Token::TYPE_NUMBER, '+12.34', 0), ''],\n            ['-12.34', new Token(Token::TYPE_NUMBER, '-12.34', 0), ''],\n\n            ['12 arg', new Token(Token::TYPE_NUMBER, '12', 0), ' arg'],\n            ['12]', new Token(Token::TYPE_NUMBER, '12', 0), ']'],\n        ];\n    }\n\n    public static function getDontHandleValueTestData()\n    {\n        return [\n            ['hello'],\n            ['>'],\n            ['+'],\n            [' '],\n            ['/* comment */'],\n        ];\n    }\n\n    protected function generateHandler()\n    {\n        $patterns = new TokenizerPatterns();\n\n        return new NumberHandler($patterns);\n    }\n}\n"
  },
  {
    "path": "Tests/Parser/Handler/StringHandlerTest.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Tests\\Parser\\Handler;\n\nuse Symfony\\Component\\CssSelector\\Parser\\Handler\\StringHandler;\nuse Symfony\\Component\\CssSelector\\Parser\\Token;\nuse Symfony\\Component\\CssSelector\\Parser\\Tokenizer\\TokenizerEscaping;\nuse Symfony\\Component\\CssSelector\\Parser\\Tokenizer\\TokenizerPatterns;\n\nclass StringHandlerTest extends AbstractHandlerTestCase\n{\n    public static function getHandleValueTestData()\n    {\n        return [\n            ['\"hello\"', new Token(Token::TYPE_STRING, 'hello', 1), ''],\n            ['\"1\"', new Token(Token::TYPE_STRING, '1', 1), ''],\n            ['\" \"', new Token(Token::TYPE_STRING, ' ', 1), ''],\n            ['\"\"', new Token(Token::TYPE_STRING, '', 1), ''],\n            [\"'hello'\", new Token(Token::TYPE_STRING, 'hello', 1), ''],\n\n            [\"'foo'bar\", new Token(Token::TYPE_STRING, 'foo', 1), 'bar'],\n        ];\n    }\n\n    public static function getDontHandleValueTestData()\n    {\n        return [\n            ['hello'],\n            ['>'],\n            ['1'],\n            [' '],\n        ];\n    }\n\n    protected function generateHandler()\n    {\n        $patterns = new TokenizerPatterns();\n\n        return new StringHandler($patterns, new TokenizerEscaping($patterns));\n    }\n}\n"
  },
  {
    "path": "Tests/Parser/Handler/WhitespaceHandlerTest.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Tests\\Parser\\Handler;\n\nuse Symfony\\Component\\CssSelector\\Parser\\Handler\\WhitespaceHandler;\nuse Symfony\\Component\\CssSelector\\Parser\\Token;\n\nclass WhitespaceHandlerTest extends AbstractHandlerTestCase\n{\n    public static function getHandleValueTestData()\n    {\n        return [\n            [' ', new Token(Token::TYPE_WHITESPACE, ' ', 0), ''],\n            [\"\\n\", new Token(Token::TYPE_WHITESPACE, \"\\n\", 0), ''],\n            [\"\\t\", new Token(Token::TYPE_WHITESPACE, \"\\t\", 0), ''],\n\n            [' foo', new Token(Token::TYPE_WHITESPACE, ' ', 0), 'foo'],\n            [' .foo', new Token(Token::TYPE_WHITESPACE, ' ', 0), '.foo'],\n        ];\n    }\n\n    public static function getDontHandleValueTestData()\n    {\n        return [\n            ['>'],\n            ['1'],\n            ['a'],\n        ];\n    }\n\n    protected function generateHandler()\n    {\n        return new WhitespaceHandler();\n    }\n}\n"
  },
  {
    "path": "Tests/Parser/ParserTest.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Tests\\Parser;\n\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\CssSelector\\Exception\\SyntaxErrorException;\nuse Symfony\\Component\\CssSelector\\Node\\FunctionNode;\nuse Symfony\\Component\\CssSelector\\Node\\SelectorNode;\nuse Symfony\\Component\\CssSelector\\Parser\\Parser;\nuse Symfony\\Component\\CssSelector\\Parser\\Token;\n\nclass ParserTest extends TestCase\n{\n    #[DataProvider('getParserTestData')]\n    public function testParser($source, $representation)\n    {\n        $parser = new Parser();\n\n        $this->assertEquals($representation, array_map(static fn (SelectorNode $node) => (string) $node->getTree(), $parser->parse($source)));\n    }\n\n    #[DataProvider('getParserExceptionTestData')]\n    public function testParserException($source, $message)\n    {\n        $parser = new Parser();\n\n        try {\n            $parser->parse($source);\n            $this->fail('Parser should throw a SyntaxErrorException.');\n        } catch (SyntaxErrorException $e) {\n            $this->assertEquals($message, $e->getMessage());\n        }\n    }\n\n    #[DataProvider('getPseudoElementsTestData')]\n    public function testPseudoElements($source, $element, $pseudo)\n    {\n        $parser = new Parser();\n        $selectors = $parser->parse($source);\n        $this->assertCount(1, $selectors);\n\n        /** @var SelectorNode $selector */\n        $selector = $selectors[0];\n        $this->assertEquals($element, (string) $selector->getTree());\n        $this->assertEquals($pseudo, (string) $selector->getPseudoElement());\n    }\n\n    #[DataProvider('getSpecificityTestData')]\n    public function testSpecificity($source, $value)\n    {\n        $parser = new Parser();\n        $selectors = $parser->parse($source);\n        $this->assertCount(1, $selectors);\n\n        /** @var SelectorNode $selector */\n        $selector = $selectors[0];\n        $this->assertEquals($value, $selector->getSpecificity()->getValue());\n    }\n\n    #[DataProvider('getParseSeriesTestData')]\n    public function testParseSeries($series, $a, $b)\n    {\n        $parser = new Parser();\n        $selectors = $parser->parse(\\sprintf(':nth-child(%s)', $series));\n        $this->assertCount(1, $selectors);\n\n        /** @var FunctionNode $function */\n        $function = $selectors[0]->getTree();\n        $this->assertEquals([$a, $b], Parser::parseSeries($function->getArguments()));\n    }\n\n    #[DataProvider('getParseSeriesExceptionTestData')]\n    public function testParseSeriesException($series)\n    {\n        $parser = new Parser();\n        $selectors = $parser->parse(\\sprintf(':nth-child(%s)', $series));\n        $this->assertCount(1, $selectors);\n\n        /** @var FunctionNode $function */\n        $function = $selectors[0]->getTree();\n        $this->expectException(SyntaxErrorException::class);\n        Parser::parseSeries($function->getArguments());\n    }\n\n    public static function getParserTestData()\n    {\n        return [\n            ['*', ['Element[*]']],\n            ['*|*', ['Element[*]']],\n            ['*|foo', ['Element[foo]']],\n            ['foo|*', ['Element[foo|*]']],\n            ['foo|bar', ['Element[foo|bar]']],\n            ['#foo#bar', ['Hash[Hash[Element[*]#foo]#bar]']],\n            ['div>.foo', ['CombinedSelector[Element[div] > Class[Element[*].foo]]']],\n            ['div> .foo', ['CombinedSelector[Element[div] > Class[Element[*].foo]]']],\n            ['div >.foo', ['CombinedSelector[Element[div] > Class[Element[*].foo]]']],\n            ['div > .foo', ['CombinedSelector[Element[div] > Class[Element[*].foo]]']],\n            [\"div \\n>  \\t \\t .foo\", ['CombinedSelector[Element[div] > Class[Element[*].foo]]']],\n            ['td.foo,.bar', ['Class[Element[td].foo]', 'Class[Element[*].bar]']],\n            ['td.foo, .bar', ['Class[Element[td].foo]', 'Class[Element[*].bar]']],\n            [\"td.foo\\t\\r\\n\\f ,\\t\\r\\n\\f .bar\", ['Class[Element[td].foo]', 'Class[Element[*].bar]']],\n            ['td.foo,.bar', ['Class[Element[td].foo]', 'Class[Element[*].bar]']],\n            ['td.foo, .bar', ['Class[Element[td].foo]', 'Class[Element[*].bar]']],\n            [\"td.foo\\t\\r\\n\\f ,\\t\\r\\n\\f .bar\", ['Class[Element[td].foo]', 'Class[Element[*].bar]']],\n            ['div, td.foo, div.bar span', ['Element[div]', 'Class[Element[td].foo]', 'CombinedSelector[Class[Element[div].bar] <followed> Element[span]]']],\n            ['div > p', ['CombinedSelector[Element[div] > Element[p]]']],\n            ['td:first', ['Pseudo[Element[td]:first]']],\n            ['td :first', ['CombinedSelector[Element[td] <followed> Pseudo[Element[*]:first]]']],\n            ['a[name]', ['Attribute[Element[a][name]]']],\n            [\"a[ name\\t]\", ['Attribute[Element[a][name]]']],\n            ['a [name]', ['CombinedSelector[Element[a] <followed> Attribute[Element[*][name]]]']],\n            ['[name=\"foo\"]', [\"Attribute[Element[*][name = 'foo']]\"]],\n            [\"[name='foo[1]']\", [\"Attribute[Element[*][name = 'foo[1]']]\"]],\n            [\"[name='foo[0][bar]']\", [\"Attribute[Element[*][name = 'foo[0][bar]']]\"]],\n            ['a[rel=\"include\"]', [\"Attribute[Element[a][rel = 'include']]\"]],\n            ['a[rel = include]', [\"Attribute[Element[a][rel = 'include']]\"]],\n            [\"a[hreflang |= 'en']\", [\"Attribute[Element[a][hreflang |= 'en']]\"]],\n            ['a[hreflang|=en]', [\"Attribute[Element[a][hreflang |= 'en']]\"]],\n            ['div:nth-child(10)', [\"Function[Element[div]:nth-child(['10'])]\"]],\n            [':nth-child(2n+2)', [\"Function[Element[*]:nth-child(['2', 'n', '+2'])]\"]],\n            ['div:nth-of-type(10)', [\"Function[Element[div]:nth-of-type(['10'])]\"]],\n            ['div div:nth-of-type(10) .aclass', [\"CombinedSelector[CombinedSelector[Element[div] <followed> Function[Element[div]:nth-of-type(['10'])]] <followed> Class[Element[*].aclass]]\"]],\n            ['label:only', ['Pseudo[Element[label]:only]']],\n            ['a:lang(fr)', [\"Function[Element[a]:lang(['fr'])]\"]],\n            ['div:contains(\"foo\")', [\"Function[Element[div]:contains(['foo'])]\"]],\n            ['div#foobar', ['Hash[Element[div]#foobar]']],\n            ['div:not(div.foo)', ['Negation[Element[div]:not(Class[Element[div].foo])]']],\n            ['div:has(div.foo)', ['Relation[Element[div]:has(Selector[Class[Element[div].foo]])]']],\n            ['td ~ th', ['CombinedSelector[Element[td] ~ Element[th]]']],\n            ['.foo[data-bar][data-baz=0]', [\"Attribute[Attribute[Class[Element[*].foo][data-bar]][data-baz = '0']]\"]],\n            ['div#foo\\.bar', ['Hash[Element[div]#foo.bar]']],\n            ['div.w-1\\/3', ['Class[Element[div].w-1/3]']],\n            ['#test\\:colon', ['Hash[Element[*]#test:colon]']],\n            [\".a\\xc1b\", [\"Class[Element[*].a\\xc1b]\"]],\n            // unicode escape: \\22 == \"\n            ['*[aval=\"\\'\\22\\'\"]', ['Attribute[Element[*][aval = \\'\\'\"\\'\\']]']],\n            ['*[aval=\"\\'\\22 2\\'\"]', ['Attribute[Element[*][aval = \\'\\'\"2\\'\\']]']],\n            // unicode escape: \\20 ==  (space)\n            ['*[aval=\"\\'\\20  \\'\"]', ['Attribute[Element[*][aval = \\'\\'  \\'\\']]']],\n            [\"*[aval=\\\"'\\\\20\\r\\n '\\\"]\", ['Attribute[Element[*][aval = \\'\\'  \\'\\']]']],\n            [':scope > foo', ['CombinedSelector[Pseudo[Element[*]:scope] > Element[foo]]']],\n            [':scope > foo bar > div', ['CombinedSelector[CombinedSelector[CombinedSelector[Pseudo[Element[*]:scope] > Element[foo]] <followed> Element[bar]] > Element[div]]']],\n            [':scope > #foo #bar', ['CombinedSelector[CombinedSelector[Pseudo[Element[*]:scope] > Hash[Element[*]#foo]] <followed> Hash[Element[*]#bar]]']],\n            [':scope', ['Pseudo[Element[*]:scope]']],\n            ['foo bar, :scope > div', ['CombinedSelector[Element[foo] <followed> Element[bar]]', 'CombinedSelector[Pseudo[Element[*]:scope] > Element[div]]']],\n            ['foo bar,:scope > div', ['CombinedSelector[Element[foo] <followed> Element[bar]]', 'CombinedSelector[Pseudo[Element[*]:scope] > Element[div]]']],\n            ['div:is(.foo, #bar)', ['Matching[Element[div]:is(Selector[Class[Element[*].foo]], Selector[Hash[Element[*]#bar]])]']],\n            [':is(:hover, :visited)', ['Matching[Element[*]:is(Selector[Pseudo[Element[*]:hover]], Selector[Pseudo[Element[*]:visited]])]']],\n            ['div:where(.foo, #bar)', ['SpecificityAdjustment[Element[div]:where(Selector[Class[Element[*].foo]], Selector[Hash[Element[*]#bar]])]']],\n            [':where(:hover, :visited)', ['SpecificityAdjustment[Element[*]:where(Selector[Pseudo[Element[*]:hover]], Selector[Pseudo[Element[*]:visited]])]']],\n        ];\n    }\n\n    public static function getParserExceptionTestData()\n    {\n        return [\n            ['attributes(href)/html/body/a', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_DELIMITER, '(', 10))->getMessage()],\n            ['attributes(href)', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_DELIMITER, '(', 10))->getMessage()],\n            ['html/body/a', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_DELIMITER, '/', 4))->getMessage()],\n            [' ', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_FILE_END, '', 1))->getMessage()],\n            ['div, ', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_FILE_END, '', 5))->getMessage()],\n            [' , div', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_DELIMITER, ',', 1))->getMessage()],\n            ['p, , div', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_DELIMITER, ',', 3))->getMessage()],\n            ['div > ', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_FILE_END, '', 6))->getMessage()],\n            ['  > div', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_DELIMITER, '>', 2))->getMessage()],\n            ['foo|#bar', SyntaxErrorException::unexpectedToken('identifier or \"*\"', new Token(Token::TYPE_HASH, 'bar', 4))->getMessage()],\n            ['#.foo', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_DELIMITER, '#', 0))->getMessage()],\n            ['.#foo', SyntaxErrorException::unexpectedToken('identifier', new Token(Token::TYPE_HASH, 'foo', 1))->getMessage()],\n            [':#foo', SyntaxErrorException::unexpectedToken('identifier', new Token(Token::TYPE_HASH, 'foo', 1))->getMessage()],\n            ['[*]', SyntaxErrorException::unexpectedToken('\"|\"', new Token(Token::TYPE_DELIMITER, ']', 2))->getMessage()],\n            ['[foo|]', SyntaxErrorException::unexpectedToken('identifier', new Token(Token::TYPE_DELIMITER, ']', 5))->getMessage()],\n            ['[#]', SyntaxErrorException::unexpectedToken('identifier or \"*\"', new Token(Token::TYPE_DELIMITER, '#', 1))->getMessage()],\n            ['[foo=#]', SyntaxErrorException::unexpectedToken('string or identifier', new Token(Token::TYPE_DELIMITER, '#', 5))->getMessage()],\n            [':nth-child()', SyntaxErrorException::unexpectedToken('at least one argument', new Token(Token::TYPE_DELIMITER, ')', 11))->getMessage()],\n            ['[href]a', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_IDENTIFIER, 'a', 6))->getMessage()],\n            ['[rel:stylesheet]', SyntaxErrorException::unexpectedToken('operator', new Token(Token::TYPE_DELIMITER, ':', 4))->getMessage()],\n            ['[rel=stylesheet', SyntaxErrorException::unexpectedToken('\"]\"', new Token(Token::TYPE_FILE_END, '', 15))->getMessage()],\n            [':lang(fr', SyntaxErrorException::unexpectedToken('an argument', new Token(Token::TYPE_FILE_END, '', 8))->getMessage()],\n            [':contains(\"foo', SyntaxErrorException::unclosedString(10)->getMessage()],\n            ['foo!', SyntaxErrorException::unexpectedToken('selector', new Token(Token::TYPE_DELIMITER, '!', 3))->getMessage()],\n            [':scope > div :scope header', SyntaxErrorException::notAtTheStartOfASelector('scope')->getMessage()],\n            [':not(:not(a))', SyntaxErrorException::nestedNot()->getMessage()],\n        ];\n    }\n\n    public static function getPseudoElementsTestData()\n    {\n        return [\n            ['foo', 'Element[foo]', ''],\n            ['*', 'Element[*]', ''],\n            [':empty', 'Pseudo[Element[*]:empty]', ''],\n            [':BEfore', 'Element[*]', 'before'],\n            [':aftER', 'Element[*]', 'after'],\n            [':First-Line', 'Element[*]', 'first-line'],\n            [':First-Letter', 'Element[*]', 'first-letter'],\n            ['::befoRE', 'Element[*]', 'before'],\n            ['::AFter', 'Element[*]', 'after'],\n            ['::firsT-linE', 'Element[*]', 'first-line'],\n            ['::firsT-letteR', 'Element[*]', 'first-letter'],\n            ['::Selection', 'Element[*]', 'selection'],\n            ['foo:after', 'Element[foo]', 'after'],\n            ['foo::selection', 'Element[foo]', 'selection'],\n            ['lorem#ipsum ~ a#b.c[href]:empty::selection', 'CombinedSelector[Hash[Element[lorem]#ipsum] ~ Pseudo[Attribute[Class[Hash[Element[a]#b].c][href]]:empty]]', 'selection'],\n            ['video::-webkit-media-controls', 'Element[video]', '-webkit-media-controls'],\n        ];\n    }\n\n    public static function getSpecificityTestData()\n    {\n        return [\n            ['*', 0],\n            [' foo', 1],\n            [':empty ', 10],\n            [':before', 1],\n            ['*:before', 1],\n            [':nth-child(2)', 10],\n            ['.bar', 10],\n            ['[baz]', 10],\n            ['[baz=\"4\"]', 10],\n            ['[baz^=\"4\"]', 10],\n            ['#lipsum', 100],\n            [':not(*)', 0],\n            [':not(foo)', 1],\n            [':not(.foo)', 10],\n            [':not([foo])', 10],\n            [':not(:empty)', 10],\n            [':not(#foo)', 100],\n            ['foo:empty', 11],\n            ['foo:before', 2],\n            ['foo::before', 2],\n            ['foo:empty::before', 12],\n            ['#lorem + foo#ipsum:first-child > bar:first-line', 213],\n            [':is(*)', 0],\n            [':is(foo)', 1],\n            [':is(.foo)', 10],\n            [':is(#foo)', 100],\n            [':is(#foo, :empty, foo)', 100],\n            ['#foo:is(#bar:empty)', 210],\n            [':where(*)', 0],\n            [':where(foo)', 0],\n            [':where(.foo)', 0],\n            [':where(#foo)', 0],\n            [':where(#foo, :empty, foo)', 0],\n            ['#foo:where(#bar:empty)', 100],\n        ];\n    }\n\n    public static function getParseSeriesTestData()\n    {\n        return [\n            ['1n+3', 1, 3],\n            ['1n +3', 1, 3],\n            ['1n + 3', 1, 3],\n            ['1n+ 3', 1, 3],\n            ['1n-3', 1, -3],\n            ['1n -3', 1, -3],\n            ['1n - 3', 1, -3],\n            ['1n- 3', 1, -3],\n            ['n-5', 1, -5],\n            ['odd', 2, 1],\n            ['even', 2, 0],\n            ['3n', 3, 0],\n            ['n', 1, 0],\n            ['+n', 1, 0],\n            ['-n', -1, 0],\n            ['5', 0, 5],\n        ];\n    }\n\n    public static function getParseSeriesExceptionTestData()\n    {\n        return [\n            ['foo'],\n            ['n+'],\n        ];\n    }\n}\n"
  },
  {
    "path": "Tests/Parser/ReaderTest.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Tests\\Parser;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\CssSelector\\Parser\\Reader;\n\nclass ReaderTest extends TestCase\n{\n    public function testIsEOF()\n    {\n        $reader = new Reader('');\n        $this->assertTrue($reader->isEOF());\n\n        $reader = new Reader('hello');\n        $this->assertFalse($reader->isEOF());\n\n        $this->assignPosition($reader, 2);\n        $this->assertFalse($reader->isEOF());\n\n        $this->assignPosition($reader, 5);\n        $this->assertTrue($reader->isEOF());\n    }\n\n    public function testGetRemainingLength()\n    {\n        $reader = new Reader('hello');\n        $this->assertEquals(5, $reader->getRemainingLength());\n\n        $this->assignPosition($reader, 2);\n        $this->assertEquals(3, $reader->getRemainingLength());\n\n        $this->assignPosition($reader, 5);\n        $this->assertEquals(0, $reader->getRemainingLength());\n    }\n\n    public function testGetSubstring()\n    {\n        $reader = new Reader('hello');\n        $this->assertEquals('he', $reader->getSubstring(2));\n        $this->assertEquals('el', $reader->getSubstring(2, 1));\n\n        $this->assignPosition($reader, 2);\n        $this->assertEquals('ll', $reader->getSubstring(2));\n        $this->assertEquals('lo', $reader->getSubstring(2, 1));\n    }\n\n    public function testGetOffset()\n    {\n        $reader = new Reader('hello');\n        $this->assertEquals(2, $reader->getOffset('ll'));\n        $this->assertFalse($reader->getOffset('w'));\n\n        $this->assignPosition($reader, 2);\n        $this->assertEquals(0, $reader->getOffset('ll'));\n        $this->assertFalse($reader->getOffset('he'));\n    }\n\n    public function testFindPattern()\n    {\n        $reader = new Reader('hello');\n\n        $this->assertFalse($reader->findPattern('/world/'));\n        $this->assertEquals(['hello', 'h'], $reader->findPattern('/^([a-z]).*/'));\n\n        $this->assignPosition($reader, 2);\n        $this->assertFalse($reader->findPattern('/^h.*/'));\n        $this->assertEquals(['llo'], $reader->findPattern('/^llo$/'));\n    }\n\n    public function testMoveForward()\n    {\n        $reader = new Reader('hello');\n        $this->assertEquals(0, $reader->getPosition());\n\n        $reader->moveForward(2);\n        $this->assertEquals(2, $reader->getPosition());\n    }\n\n    public function testToEnd()\n    {\n        $reader = new Reader('hello');\n        $reader->moveToEnd();\n        $this->assertTrue($reader->isEOF());\n    }\n\n    private function assignPosition(Reader $reader, int $value)\n    {\n        $position = new \\ReflectionProperty($reader, 'position');\n        $position->setValue($reader, $value);\n    }\n}\n"
  },
  {
    "path": "Tests/Parser/Shortcut/ClassParserTest.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Tests\\Parser\\Shortcut;\n\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\CssSelector\\Node\\SelectorNode;\nuse Symfony\\Component\\CssSelector\\Parser\\Shortcut\\ClassParser;\n\n/**\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n */\nclass ClassParserTest extends TestCase\n{\n    #[DataProvider('getParseTestData')]\n    public function testParse($source, $representation)\n    {\n        $parser = new ClassParser();\n        $selectors = $parser->parse($source);\n        $this->assertCount(1, $selectors);\n\n        /** @var SelectorNode $selector */\n        $selector = $selectors[0];\n        $this->assertEquals($representation, (string) $selector->getTree());\n    }\n\n    public static function getParseTestData()\n    {\n        return [\n            ['.testclass', 'Class[Element[*].testclass]'],\n            ['testel.testclass', 'Class[Element[testel].testclass]'],\n            ['testns|.testclass', 'Class[Element[testns|*].testclass]'],\n            ['testns|*.testclass', 'Class[Element[testns|*].testclass]'],\n            ['testns|testel.testclass', 'Class[Element[testns|testel].testclass]'],\n        ];\n    }\n}\n"
  },
  {
    "path": "Tests/Parser/Shortcut/ElementParserTest.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Tests\\Parser\\Shortcut;\n\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\CssSelector\\Node\\SelectorNode;\nuse Symfony\\Component\\CssSelector\\Parser\\Shortcut\\ElementParser;\n\n/**\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n */\nclass ElementParserTest extends TestCase\n{\n    #[DataProvider('getParseTestData')]\n    public function testParse($source, $representation)\n    {\n        $parser = new ElementParser();\n        $selectors = $parser->parse($source);\n        $this->assertCount(1, $selectors);\n\n        /** @var SelectorNode $selector */\n        $selector = $selectors[0];\n        $this->assertEquals($representation, (string) $selector->getTree());\n    }\n\n    public static function getParseTestData()\n    {\n        return [\n            ['*', 'Element[*]'],\n            ['testel', 'Element[testel]'],\n            ['testns|*', 'Element[testns|*]'],\n            ['testns|testel', 'Element[testns|testel]'],\n        ];\n    }\n}\n"
  },
  {
    "path": "Tests/Parser/Shortcut/EmptyStringParserTest.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Tests\\Parser\\Shortcut;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\CssSelector\\Node\\SelectorNode;\nuse Symfony\\Component\\CssSelector\\Parser\\Shortcut\\EmptyStringParser;\n\n/**\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n */\nclass EmptyStringParserTest extends TestCase\n{\n    public function testParse()\n    {\n        $parser = new EmptyStringParser();\n        $selectors = $parser->parse('');\n        $this->assertCount(1, $selectors);\n\n        /** @var SelectorNode $selector */\n        $selector = $selectors[0];\n        $this->assertEquals('Element[*]', (string) $selector->getTree());\n\n        $selectors = $parser->parse('this will produce an empty array');\n        $this->assertCount(0, $selectors);\n    }\n}\n"
  },
  {
    "path": "Tests/Parser/Shortcut/HashParserTest.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Tests\\Parser\\Shortcut;\n\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\CssSelector\\Node\\SelectorNode;\nuse Symfony\\Component\\CssSelector\\Parser\\Shortcut\\HashParser;\n\n/**\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n */\nclass HashParserTest extends TestCase\n{\n    #[DataProvider('getParseTestData')]\n    public function testParse($source, $representation)\n    {\n        $parser = new HashParser();\n        $selectors = $parser->parse($source);\n        $this->assertCount(1, $selectors);\n\n        /** @var SelectorNode $selector */\n        $selector = $selectors[0];\n        $this->assertEquals($representation, (string) $selector->getTree());\n    }\n\n    public static function getParseTestData()\n    {\n        return [\n            ['#testid', 'Hash[Element[*]#testid]'],\n            ['testel#testid', 'Hash[Element[testel]#testid]'],\n            ['testns|#testid', 'Hash[Element[testns|*]#testid]'],\n            ['testns|*#testid', 'Hash[Element[testns|*]#testid]'],\n            ['testns|testel#testid', 'Hash[Element[testns|testel]#testid]'],\n        ];\n    }\n}\n"
  },
  {
    "path": "Tests/Parser/TokenStreamTest.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Tests\\Parser;\n\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\CssSelector\\Exception\\SyntaxErrorException;\nuse Symfony\\Component\\CssSelector\\Parser\\Token;\nuse Symfony\\Component\\CssSelector\\Parser\\TokenStream;\n\nclass TokenStreamTest extends TestCase\n{\n    public function testGetNext()\n    {\n        $stream = new TokenStream();\n        $stream->push($t1 = new Token(Token::TYPE_IDENTIFIER, 'h1', 0));\n        $stream->push($t2 = new Token(Token::TYPE_DELIMITER, '.', 2));\n        $stream->push($t3 = new Token(Token::TYPE_IDENTIFIER, 'title', 3));\n\n        $this->assertSame($t1, $stream->getNext());\n        $this->assertSame($t2, $stream->getNext());\n        $this->assertSame($t3, $stream->getNext());\n    }\n\n    public function testGetPeek()\n    {\n        $stream = new TokenStream();\n        $stream->push($t1 = new Token(Token::TYPE_IDENTIFIER, 'h1', 0));\n        $stream->push($t2 = new Token(Token::TYPE_DELIMITER, '.', 2));\n        $stream->push($t3 = new Token(Token::TYPE_IDENTIFIER, 'title', 3));\n\n        $this->assertSame($t1, $stream->getPeek());\n        $this->assertSame($t1, $stream->getNext());\n        $this->assertSame($t2, $stream->getPeek());\n        $this->assertSame($t2, $stream->getPeek());\n        $this->assertSame($t2, $stream->getNext());\n    }\n\n    public function testGetNextIdentifier()\n    {\n        $stream = new TokenStream();\n        $stream->push(new Token(Token::TYPE_IDENTIFIER, 'h1', 0));\n\n        $this->assertEquals('h1', $stream->getNextIdentifier());\n    }\n\n    public function testFailToGetNextIdentifier()\n    {\n        $stream = new TokenStream();\n        $stream->push(new Token(Token::TYPE_DELIMITER, '.', 2));\n\n        $this->expectException(SyntaxErrorException::class);\n\n        $stream->getNextIdentifier();\n    }\n\n    public function testGetNextIdentifierOrStar()\n    {\n        $stream = new TokenStream();\n\n        $stream->push(new Token(Token::TYPE_IDENTIFIER, 'h1', 0));\n        $this->assertEquals('h1', $stream->getNextIdentifierOrStar());\n\n        $stream->push(new Token(Token::TYPE_DELIMITER, '*', 0));\n        $this->assertNull($stream->getNextIdentifierOrStar());\n    }\n\n    public function testFailToGetNextIdentifierOrStar()\n    {\n        $stream = new TokenStream();\n        $stream->push(new Token(Token::TYPE_DELIMITER, '.', 2));\n\n        $this->expectException(SyntaxErrorException::class);\n\n        $stream->getNextIdentifierOrStar();\n    }\n\n    public function testSkipWhitespace()\n    {\n        $stream = new TokenStream();\n        $stream->push($t1 = new Token(Token::TYPE_IDENTIFIER, 'h1', 0));\n        $stream->push($t2 = new Token(Token::TYPE_WHITESPACE, ' ', 2));\n        $stream->push($t3 = new Token(Token::TYPE_IDENTIFIER, 'h1', 3));\n\n        $stream->skipWhitespace();\n        $this->assertSame($t1, $stream->getNext());\n\n        $stream->skipWhitespace();\n        $this->assertSame($t3, $stream->getNext());\n    }\n}\n"
  },
  {
    "path": "Tests/XPath/Fixtures/ids.html",
    "content": "<html id=\"html\"><head>\n  <link id=\"link-href\" href=\"foo\" />\n  <link id=\"link-nohref\" />\n</head><body>\n<div id=\"outer-div\">\n <a id=\"name-anchor\" name=\"foo\"></a>\n <a id=\"tag-anchor\" rel=\"tag\" href=\"http://localhost/foo\">link</a>\n <a id=\"nofollow-anchor\" rel=\"nofollow\" href=\"https://example.org\">\n    link</a>\n <ol id=\"first-ol\" class=\"a b c\">\n   <li id=\"first-li\">content</li>\n   <li id=\"second-li\" lang=\"En-us\">\n     <div id=\"li-div\">\n     </div>\n   </li>\n   <li id=\"third-li\" class=\"ab c\"></li>\n   <li id=\"fourth-li\" class=\"ab\nc\"></li>\n   <li id=\"fifth-li\"></li>\n   <li id=\"sixth-li\"></li>\n   <li id=\"seventh-li\">  </li>\n </ol>\n <p id=\"paragraph\">\n   <b id=\"p-b\">hi</b> <em id=\"p-em\">there</em>\n   <b id=\"p-b2\">guy</b>\n   <input type=\"checkbox\" id=\"checkbox-unchecked\" />\n   <input type=\"checkbox\" id=\"checkbox-disabled\" disabled=\"\" />\n   <input type=\"text\" id=\"text-checked\" checked=\"checked\" />\n   <input type=\"hidden\" />\n   <input type=\"hidden\" disabled=\"disabled\" />\n   <input type=\"checkbox\" id=\"checkbox-checked\" checked=\"checked\" />\n   <input type=\"checkbox\" id=\"checkbox-disabled-checked\"\n          disabled=\"disabled\" checked=\"checked\" />\n   <fieldset id=\"fieldset\" disabled=\"disabled\">\n     <input type=\"checkbox\" id=\"checkbox-fieldset-disabled\" />\n     <input type=\"hidden\" />\n   </fieldset>\n </p>\n <ol id=\"second-ol\">\n </ol>\n <map name=\"dummymap\">\n   <area shape=\"circle\" coords=\"200,250,25\" href=\"foo.html\" id=\"area-href\" />\n   <area shape=\"default\" id=\"area-nohref\" />\n </map>\n</div>\n<div id=\"foobar-div\" foobar=\"ab bc\ncde\"><span id=\"foobar-span\"></span></div>\n<section>\n    <span id=\"no-siblings-of-any-type\"></span>\n</section>\n</body>\n</html>\n"
  },
  {
    "path": "Tests/XPath/Fixtures/lang.xml",
    "content": "<test>\n  <a id=\"first\" xml:lang=\"en\">a</a>\n  <b id=\"second\" xml:lang=\"en-US\">b</b>\n  <c id=\"third\" xml:lang=\"en-Nz\">c</c>\n  <d id=\"fourth\" xml:lang=\"En-us\">d</d>\n  <e id=\"fifth\" xml:lang=\"fr\">e</e>\n  <f id=\"sixth\" xml:lang=\"ru\">f</f>\n  <g id=\"seventh\" xml:lang=\"de\">\n    <h id=\"eighth\" xml:lang=\"zh\"/>\n  </g>\n</test>\n"
  },
  {
    "path": "Tests/XPath/Fixtures/shakespear.html",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n\t\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\" debug=\"true\">\n<head>\n\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\n</head>\n<body>\n\t<div id=\"test\">\n\t<div class=\"dialog\">\n\t<h2>As You Like It</h2>\n\t<div id=\"playwright\">\n\t  by William Shakespeare\n\t</div>\n\t<div class=\"dialog scene thirdClass\" id=\"scene1\">\n\t  <h3>ACT I, SCENE III. A room in the palace.</h3>\n\t  <div class=\"dialog\">\n\t  <div class=\"direction\">Enter CELIA and ROSALIND</div>\n\t  </div>\n\t  <div id=\"speech1\" class=\"character\">CELIA</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.1\">Why, cousin! why, Rosalind! Cupid have mercy! not a word?</div>\n\t  </div>\n\t  <div id=\"speech2\" class=\"character\">ROSALIND</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.2\">Not one to throw at a dog.</div>\n\t  </div>\n\t  <div id=\"speech3\" class=\"character\">CELIA</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.3\">No, thy words are too precious to be cast away upon</div>\n\t  <div id=\"scene1.3.4\">curs; throw some of them at me; come, lame me with reasons.</div>\n\t  </div>\n\t  <div id=\"speech4\" class=\"character\">ROSALIND</div>\n\t  <div id=\"speech5\" class=\"character\">CELIA</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.8\">But is all this for your father?</div>\n\t  </div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.5\">Then there were two cousins laid up; when the one</div>\n\t  <div id=\"scene1.3.6\">should be lamed with reasons and the other mad</div>\n\t  <div id=\"scene1.3.7\">without any.</div>\n\t  </div>\n\t  <div id=\"speech6\" class=\"character\">ROSALIND</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.9\">No, some of it is for my child's father. O, how</div>\n\t  <div id=\"scene1.3.10\">full of briers is this working-day world!</div>\n\t  </div>\n\t  <div id=\"speech7\" class=\"character\">CELIA</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.11\">They are but burs, cousin, thrown upon thee in</div>\n\t  <div id=\"scene1.3.12\">holiday foolery: if we walk not in the trodden</div>\n\t  <div id=\"scene1.3.13\">paths our very petticoats will catch them.</div>\n\t  </div>\n\t  <div id=\"speech8\" class=\"character\">ROSALIND</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.14\">I could shake them off my coat: these burs are in my heart.</div>\n\t  </div>\n\t  <div id=\"speech9\" class=\"character\">CELIA</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.15\">Hem them away.</div>\n\t  </div>\n\t  <div id=\"speech10\" class=\"character\">ROSALIND</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.16\">I would try, if I could cry 'hem' and have him.</div>\n\t  </div>\n\t  <div id=\"speech11\" class=\"character\">CELIA</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.17\">Come, come, wrestle with thy affections.</div>\n\t  </div>\n\t  <div id=\"speech12\" class=\"character\">ROSALIND</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.18\">O, they take the part of a better wrestler than myself!</div>\n\t  </div>\n\t  <div id=\"speech13\" class=\"character\">CELIA</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.19\">O, a good wish upon you! you will try in time, in</div>\n\t  <div id=\"scene1.3.20\">despite of a fall. But, turning these jests out of</div>\n\t  <div id=\"scene1.3.21\">service, let us talk in good earnest: is it</div>\n\t  <div id=\"scene1.3.22\">possible, on such a sudden, you should fall into so</div>\n\t  <div id=\"scene1.3.23\">strong a liking with old Sir Rowland's youngest son?</div>\n\t  </div>\n\t  <div id=\"speech14\" class=\"character\">ROSALIND</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.24\">The duke my father loved his father dearly.</div>\n\t  </div>\n\t  <div id=\"speech15\" class=\"character\">CELIA</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.25\">Doth it therefore ensue that you should love his son</div>\n\t  <div id=\"scene1.3.26\">dearly? By this kind of chase, I should hate him,</div>\n\t  <div id=\"scene1.3.27\">for my father hated his father dearly; yet I hate</div>\n\t  <div id=\"scene1.3.28\">not Orlando.</div>\n\t  </div>\n\t  <div id=\"speech16\" class=\"character\">ROSALIND</div>\n\t  <div title=\"wtf\" class=\"dialog\">\n\t  <div id=\"scene1.3.29\">No, faith, hate him not, for my sake.</div>\n\t  </div>\n\t  <div id=\"speech17\" class=\"character\">CELIA</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.30\">Why should I not? doth he not deserve well?</div>\n\t  </div>\n\t  <div id=\"speech18\" class=\"character\">ROSALIND</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.31\">Let me love him for that, and do you love him</div>\n\t  <div id=\"scene1.3.32\">because I do. Look, here comes the duke.</div>\n\t  </div>\n\t  <div id=\"speech19\" class=\"character\">CELIA</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.33\">With his eyes full of anger.</div>\n\t  <div class=\"direction\">Enter DUKE FREDERICK, with Lords</div>\n\t  </div>\n\t  <div id=\"speech20\" class=\"character\">DUKE FREDERICK</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.34\">Mistress, dispatch you with your safest haste</div>\n\t  <div id=\"scene1.3.35\">And get you from our court.</div>\n\t  </div>\n\t  <div id=\"speech21\" class=\"character\">ROSALIND</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.36\">Me, uncle?</div>\n\t  </div>\n\t  <div id=\"speech22\" class=\"character\">DUKE FREDERICK</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.37\">You, cousin</div>\n\t  <div id=\"scene1.3.38\">Within these ten days if that thou be'st found</div>\n\t  <div id=\"scene1.3.39\">So near our public court as twenty miles,</div>\n\t  <div id=\"scene1.3.40\">Thou diest for it.</div>\n\t  </div>\n\t  <div id=\"speech23\" class=\"character\">ROSALIND</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.41\">                  I do beseech your grace,</div>\n\t  <div id=\"scene1.3.42\">Let me the knowledge of my fault bear with me:</div>\n\t  <div id=\"scene1.3.43\">If with myself I hold intelligence</div>\n\t  <div id=\"scene1.3.44\">Or have acquaintance with mine own desires,</div>\n\t  <div id=\"scene1.3.45\">If that I do not dream or be not frantic,--</div>\n\t  <div id=\"scene1.3.46\">As I do trust I am not--then, dear uncle,</div>\n\t  <div id=\"scene1.3.47\">Never so much as in a thought unborn</div>\n\t  <div id=\"scene1.3.48\">Did I offend your highness.</div>\n\t  </div>\n\t  <div id=\"speech24\" class=\"character\">DUKE FREDERICK</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.49\">Thus do all traitors:</div>\n\t  <div id=\"scene1.3.50\">If their purgation did consist in words,</div>\n\t  <div id=\"scene1.3.51\">They are as innocent as grace itself:</div>\n\t  <div id=\"scene1.3.52\">Let it suffice thee that I trust thee not.</div>\n\t  </div>\n\t  <div id=\"speech25\" class=\"character\">ROSALIND</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.53\">Yet your mistrust cannot make me a traitor:</div>\n\t  <div id=\"scene1.3.54\">Tell me whereon the likelihood depends.</div>\n\t  </div>\n\t  <div id=\"speech26\" class=\"character\">DUKE FREDERICK</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.55\">Thou art thy father's daughter; there's enough.</div>\n\t  </div>\n\t  <div id=\"speech27\" class=\"character\">ROSALIND</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.56\">So was I when your highness took his dukedom;</div>\n\t  <div id=\"scene1.3.57\">So was I when your highness banish'd him:</div>\n\t  <div id=\"scene1.3.58\">Treason is not inherited, my lord;</div>\n\t  <div id=\"scene1.3.59\">Or, if we did derive it from our friends,</div>\n\t  <div id=\"scene1.3.60\">What's that to me? my father was no traitor:</div>\n\t  <div id=\"scene1.3.61\">Then, good my liege, mistake me not so much</div>\n\t  <div id=\"scene1.3.62\">To think my poverty is treacherous.</div>\n\t  </div>\n\t  <div id=\"speech28\" class=\"character\">CELIA</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.63\">Dear sovereign, hear me speak.</div>\n\t  </div>\n\t  <div id=\"speech29\" class=\"character\">DUKE FREDERICK</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.64\">Ay, Celia; we stay'd her for your sake,</div>\n\t  <div id=\"scene1.3.65\">Else had she with her father ranged along.</div>\n\t  </div>\n\t  <div id=\"speech30\" class=\"character\">CELIA</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.66\">I did not then entreat to have her stay;</div>\n\t  <div id=\"scene1.3.67\">It was your pleasure and your own remorse:</div>\n\t  <div id=\"scene1.3.68\">I was too young that time to value her;</div>\n\t  <div id=\"scene1.3.69\">But now I know her: if she be a traitor,</div>\n\t  <div id=\"scene1.3.70\">Why so am I; we still have slept together,</div>\n\t  <div id=\"scene1.3.71\">Rose at an instant, learn'd, play'd, eat together,</div>\n\t  <div id=\"scene1.3.72\">And wheresoever we went, like Juno's swans,</div>\n\t  <div id=\"scene1.3.73\">Still we went coupled and inseparable.</div>\n\t  </div>\n\t  <div id=\"speech31\" class=\"character\">DUKE FREDERICK</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.74\">She is too subtle for thee; and her smoothness,</div>\n\t  <div id=\"scene1.3.75\">Her very silence and her patience</div>\n\t  <div id=\"scene1.3.76\">Speak to the people, and they pity her.</div>\n\t  <div id=\"scene1.3.77\">Thou art a fool: she robs thee of thy name;</div>\n\t  <div id=\"scene1.3.78\">And thou wilt show more bright and seem more virtuous</div>\n\t  <div id=\"scene1.3.79\">When she is gone. Then open not thy lips:</div>\n\t  <div id=\"scene1.3.80\">Firm and irrevocable is my doom</div>\n\t  <div id=\"scene1.3.81\">Which I have pass'd upon her; she is banish'd.</div>\n\t  </div>\n\t  <div id=\"speech32\" class=\"character\">CELIA</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.82\">Pronounce that sentence then on me, my liege:</div>\n\t  <div id=\"scene1.3.83\">I cannot live out of her company.</div>\n\t  </div>\n\t  <div id=\"speech33\" class=\"character\">DUKE FREDERICK</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.84\">You are a fool. You, niece, provide yourself:</div>\n\t  <div id=\"scene1.3.85\">If you outstay the time, upon mine honour,</div>\n\t  <div id=\"scene1.3.86\">And in the greatness of my word, you die.</div>\n\t  <div class=\"direction\">Exeunt DUKE FREDERICK and Lords</div>\n\t  </div>\n\t  <div id=\"speech34\" class=\"character\">CELIA</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.87\">O my poor Rosalind, whither wilt thou go?</div>\n\t  <div id=\"scene1.3.88\">Wilt thou change fathers? I will give thee mine.</div>\n\t  <div id=\"scene1.3.89\">I charge thee, be not thou more grieved than I am.</div>\n\t  </div>\n\t  <div id=\"speech35\" class=\"character\">ROSALIND</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.90\">I have more cause.</div>\n\t  </div>\n\t  <div id=\"speech36\" class=\"character\">CELIA</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.91\">                  Thou hast not, cousin;</div>\n\t  <div id=\"scene1.3.92\">Prithee be cheerful: know'st thou not, the duke</div>\n\t  <div id=\"scene1.3.93\">Hath banish'd me, his daughter?</div>\n\t  </div>\n\t  <div id=\"speech37\" class=\"character\">ROSALIND</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.94\">That he hath not.</div>\n\t  </div>\n\t  <div id=\"speech38\" class=\"character\">CELIA</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.95\">No, hath not? Rosalind lacks then the love</div>\n\t  <div id=\"scene1.3.96\">Which teacheth thee that thou and I am one:</div>\n\t  <div id=\"scene1.3.97\">Shall we be sunder'd? shall we part, sweet girl?</div>\n\t  <div id=\"scene1.3.98\">No: let my father seek another heir.</div>\n\t  <div id=\"scene1.3.99\">Therefore devise with me how we may fly,</div>\n\t  <div id=\"scene1.3.100\">Whither to go and what to bear with us;</div>\n\t  <div id=\"scene1.3.101\">And do not seek to take your change upon you,</div>\n\t  <div id=\"scene1.3.102\">To bear your griefs yourself and leave me out;</div>\n\t  <div id=\"scene1.3.103\">For, by this heaven, now at our sorrows pale,</div>\n\t  <div id=\"scene1.3.104\">Say what thou canst, I'll go along with thee.</div>\n\t  </div>\n\t  <div id=\"speech39\" class=\"character\">ROSALIND</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.105\">Why, whither shall we go?</div>\n\t  </div>\n\t  <div id=\"speech40\" class=\"character\">CELIA</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.106\">To seek my uncle in the forest of Arden.</div>\n\t  </div>\n\t  <div id=\"speech41\" class=\"character\">ROSALIND</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.107\">Alas, what danger will it be to us,</div>\n\t  <div id=\"scene1.3.108\">Maids as we are, to travel forth so far!</div>\n\t  <div id=\"scene1.3.109\">Beauty provoketh thieves sooner than gold.</div>\n\t  </div>\n\t  <div id=\"speech42\" class=\"character\">CELIA</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.110\">I'll put myself in poor and mean attire</div>\n\t  <div id=\"scene1.3.111\">And with a kind of umber smirch my face;</div>\n\t  <div id=\"scene1.3.112\">The like do you: so shall we pass along</div>\n\t  <div id=\"scene1.3.113\">And never stir assailants.</div>\n\t  </div>\n\t  <div id=\"speech43\" class=\"character\">ROSALIND</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.114\">Were it not better,</div>\n\t  <div id=\"scene1.3.115\">Because that I am more than common tall,</div>\n\t  <div id=\"scene1.3.116\">That I did suit me all points like a man?</div>\n\t  <div id=\"scene1.3.117\">A gallant curtle-axe upon my thigh,</div>\n\t  <div id=\"scene1.3.118\">A boar-spear in my hand; and--in my heart</div>\n\t  <div id=\"scene1.3.119\">Lie there what hidden woman's fear there will--</div>\n\t  <div id=\"scene1.3.120\">We'll have a swashing and a martial outside,</div>\n\t  <div id=\"scene1.3.121\">As many other mannish cowards have</div>\n\t  <div id=\"scene1.3.122\">That do outface it with their semblances.</div>\n\t  </div>\n\t  <div id=\"speech44\" class=\"character\">CELIA</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.123\">What shall I call thee when thou art a man?</div>\n\t  </div>\n\t  <div id=\"speech45\" class=\"character\">ROSALIND</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.124\">I'll have no worse a name than Jove's own page;</div>\n\t  <div id=\"scene1.3.125\">And therefore look you call me Ganymede.</div>\n\t  <div id=\"scene1.3.126\">But what will you be call'd?</div>\n\t  </div>\n\t  <div id=\"speech46\" class=\"character\">CELIA</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.127\">Something that hath a reference to my state</div>\n\t  <div id=\"scene1.3.128\">No longer Celia, but Aliena.</div>\n\t  </div>\n\t  <div id=\"speech47\" class=\"character\">ROSALIND</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.129\">But, cousin, what if we assay'd to steal</div>\n\t  <div id=\"scene1.3.130\">The clownish fool out of your father's court?</div>\n\t  <div id=\"scene1.3.131\">Would he not be a comfort to our travel?</div>\n\t  </div>\n\t  <div id=\"speech48\" class=\"character\">CELIA</div>\n\t  <div class=\"dialog\">\n\t  <div id=\"scene1.3.132\">He'll go along o'er the wide world with me;</div>\n\t  <div id=\"scene1.3.133\">Leave me alone to woo him. Let's away,</div>\n\t  <div id=\"scene1.3.134\">And get our jewels and our wealth together,</div>\n\t  <div id=\"scene1.3.135\">Devise the fittest time and safest way</div>\n\t  <div id=\"scene1.3.136\">To hide us from pursuit that will be made</div>\n\t  <div id=\"scene1.3.137\">After my flight. Now go we in content</div>\n\t  <div id=\"scene1.3.138\">To liberty and not to banishment.</div>\n\t  <div class=\"direction\">Exeunt</div>\n\t  </div>\n\t</div>\n\t</div>\n</div>\n</body>\n</html>\n"
  },
  {
    "path": "Tests/XPath/TranslatorTest.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\Tests\\XPath;\n\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\TestCase;\nuse Symfony\\Component\\CssSelector\\Exception\\ExpressionErrorException;\nuse Symfony\\Component\\CssSelector\\Exception\\SyntaxErrorException;\nuse Symfony\\Component\\CssSelector\\Node\\ElementNode;\nuse Symfony\\Component\\CssSelector\\Node\\FunctionNode;\nuse Symfony\\Component\\CssSelector\\Parser\\Parser;\nuse Symfony\\Component\\CssSelector\\XPath\\Extension\\HtmlExtension;\nuse Symfony\\Component\\CssSelector\\XPath\\Translator;\nuse Symfony\\Component\\CssSelector\\XPath\\XPathExpr;\n\nclass TranslatorTest extends TestCase\n{\n    #[DataProvider('getXpathLiteralTestData')]\n    public function testXpathLiteral($value, $literal)\n    {\n        $this->assertEquals($literal, Translator::getXpathLiteral($value));\n    }\n\n    #[DataProvider('getCssToXPathTestData')]\n    public function testCssToXPath($css, $xpath)\n    {\n        $translator = new Translator();\n        $translator->registerExtension(new HtmlExtension($translator));\n        $this->assertEquals($xpath, $translator->cssToXPath($css, ''));\n    }\n\n    #[DataProvider('getUnsupportedHasSelectorTestData')]\n    public function testHasUnsupportedSelector(string $css)\n    {\n        $translator = new Translator();\n        $translator->registerExtension(new HtmlExtension($translator));\n\n        $this->expectException(SyntaxErrorException::class);\n\n        $translator->cssToXPath($css, '');\n    }\n\n    public static function getUnsupportedHasSelectorTestData(): iterable\n    {\n        yield 'attribute selector' => ['div:has([data-x])'];\n        yield 'descendant combinator' => ['div:has(.foo .bar)'];\n        yield 'selector list' => ['div:has(.foo, .bar)'];\n        yield 'nested pseudo-class' => ['div:has(:not(.foo))'];\n        yield 'chained combinator' => ['div:has(> .foo > .bar)'];\n    }\n\n    public function testCssToXPathPseudoElement()\n    {\n        $translator = new Translator();\n        $translator->registerExtension(new HtmlExtension($translator));\n\n        $this->expectException(ExpressionErrorException::class);\n\n        $translator->cssToXPath('e::first-line');\n    }\n\n    public function testGetExtensionNotExistsExtension()\n    {\n        $translator = new Translator();\n        $translator->registerExtension(new HtmlExtension($translator));\n\n        $this->expectException(ExpressionErrorException::class);\n\n        $translator->getExtension('fake');\n    }\n\n    public function testAddCombinationNotExistsExtension()\n    {\n        $translator = new Translator();\n        $translator->registerExtension(new HtmlExtension($translator));\n        $parser = new Parser();\n        $xpath = $parser->parse('*')[0];\n        $combinedXpath = $parser->parse('*')[0];\n\n        $this->expectException(ExpressionErrorException::class);\n\n        $translator->addCombination('fake', $xpath, $combinedXpath);\n    }\n\n    public function testAddFunctionNotExistsFunction()\n    {\n        $translator = new Translator();\n        $translator->registerExtension(new HtmlExtension($translator));\n        $xpath = new XPathExpr();\n        $function = new FunctionNode(new ElementNode(), 'fake');\n\n        $this->expectException(ExpressionErrorException::class);\n\n        $translator->addFunction($xpath, $function);\n    }\n\n    public function testAddPseudoClassNotExistsClass()\n    {\n        $translator = new Translator();\n        $translator->registerExtension(new HtmlExtension($translator));\n        $xpath = new XPathExpr();\n\n        $this->expectException(ExpressionErrorException::class);\n\n        $translator->addPseudoClass($xpath, 'fake');\n    }\n\n    public function testAddAttributeMatchingClassNotExistsClass()\n    {\n        $translator = new Translator();\n        $translator->registerExtension(new HtmlExtension($translator));\n        $xpath = new XPathExpr();\n\n        $this->expectException(ExpressionErrorException::class);\n\n        $translator->addAttributeMatching($xpath, '', '', '');\n    }\n\n    #[DataProvider('getXmlLangTestData')]\n    public function testXmlLang($css, array $elementsId)\n    {\n        $translator = new Translator();\n        $document = new \\SimpleXMLElement(file_get_contents(__DIR__.'/Fixtures/lang.xml'));\n        $elements = $document->xpath($translator->cssToXPath($css));\n        $this->assertCount(\\count($elementsId), $elements);\n        foreach ($elements as $element) {\n            $this->assertContains((string) $element->attributes()->id, $elementsId);\n        }\n    }\n\n    #[DataProvider('getHtmlIdsTestData')]\n    public function testHtmlIds($css, array $elementsId)\n    {\n        $translator = new Translator();\n        $translator->registerExtension(new HtmlExtension($translator));\n        $document = new \\DOMDocument();\n        $document->strictErrorChecking = false;\n        $internalErrors = libxml_use_internal_errors(true);\n        $document->loadHTMLFile(__DIR__.'/Fixtures/ids.html');\n        $document = simplexml_import_dom($document);\n        $elements = $document->xpath($translator->cssToXPath($css));\n        $this->assertCount(\\count($elementsId), $elements);\n        foreach ($elements as $element) {\n            if (null !== $element->attributes()->id) {\n                $this->assertContains((string) $element->attributes()->id, $elementsId);\n            }\n        }\n        libxml_clear_errors();\n        libxml_use_internal_errors($internalErrors);\n    }\n\n    #[DataProvider('getHtmlShakespearTestData')]\n    public function testHtmlShakespear($css, $count)\n    {\n        $translator = new Translator();\n        $translator->registerExtension(new HtmlExtension($translator));\n        $document = new \\DOMDocument();\n        $document->strictErrorChecking = false;\n        $document->loadHTMLFile(__DIR__.'/Fixtures/shakespear.html');\n        $document = simplexml_import_dom($document);\n        $bodies = $document->xpath('//body');\n        $elements = $bodies[0]->xpath($translator->cssToXPath($css));\n        $this->assertCount($count, $elements);\n    }\n\n    public function testOnlyOfTypeFindsSingleChildrenOfGivenType()\n    {\n        $translator = new Translator();\n        $translator->registerExtension(new HtmlExtension($translator));\n        $document = new \\DOMDocument();\n        $document->loadHTML(<<<'HTML'\n            <html>\n              <body>\n                <p>\n                  <span>A</span>\n                </p>\n                <p>\n                  <span>B</span>\n                  <span>C</span>\n                </p>\n              </body>\n            </html>\n            HTML\n        );\n\n        $xpath = new \\DOMXPath($document);\n        $nodeList = $xpath->query($translator->cssToXPath('span:only-of-type'));\n\n        $this->assertSame(1, $nodeList->length);\n        $this->assertSame('A', $nodeList->item(0)->textContent);\n    }\n\n    public static function getXpathLiteralTestData()\n    {\n        return [\n            ['foo', \"'foo'\"],\n            [\"foo's bar\", '\"foo\\'s bar\"'],\n            [\"foo's \\\"middle\\\" bar\", 'concat(\\'foo\\', \"\\'\", \\'s \"middle\" bar\\')'],\n            [\"foo's 'middle' \\\"bar\\\"\", 'concat(\\'foo\\', \"\\'\", \\'s \\', \"\\'\", \\'middle\\', \"\\'\", \\' \"bar\"\\')'],\n        ];\n    }\n\n    public static function getCssToXPathTestData()\n    {\n        return [\n            ['*', '*'],\n            ['e', 'e'],\n            ['*|e', 'e'],\n            ['e|f', 'e:f'],\n            ['e[foo]', 'e[@foo]'],\n            ['e[foo|bar]', 'e[@foo:bar]'],\n            ['e[foo=\"bar\"]', \"e[@foo = 'bar']\"],\n            ['e[foo~=\"bar\"]', \"e[@foo and contains(concat(' ', normalize-space(@foo), ' '), ' bar ')]\"],\n            ['e[foo^=\"bar\"]', \"e[@foo and starts-with(@foo, 'bar')]\"],\n            ['e[foo$=\"bar\"]', \"e[@foo and substring(@foo, string-length(@foo)-2) = 'bar']\"],\n            ['e[foo*=\"bar\"]', \"e[@foo and contains(@foo, 'bar')]\"],\n            ['e[foo!=\"bar\"]', \"e[not(@foo) or @foo != 'bar']\"],\n            ['e[foo!=\"bar\"][foo!=\"baz\"]', \"e[(not(@foo) or @foo != 'bar') and (not(@foo) or @foo != 'baz')]\"],\n            ['e[hreflang|=\"en\"]', \"e[@hreflang and (@hreflang = 'en' or starts-with(@hreflang, 'en-'))]\"],\n            ['e:nth-child(1)', \"*/*[(name() = 'e') and (position() = 1)]\"],\n            ['e:nth-last-child(1)', \"*/*[(name() = 'e') and (position() = last() - 0)]\"],\n            ['e:nth-last-child(2n+2)', \"*/*[(name() = 'e') and (last() - position() - 1 >= 0 and (last() - position() - 1) mod 2 = 0)]\"],\n            ['e:nth-of-type(1)', '*/e[position() = 1]'],\n            ['e:nth-last-of-type(1)', '*/e[position() = last() - 0]'],\n            ['div e:nth-last-of-type(1) .aclass', \"div/descendant-or-self::*/e[position() = last() - 0]/descendant-or-self::*/*[@class and contains(concat(' ', normalize-space(@class), ' '), ' aclass ')]\"],\n            ['e:first-child', \"*/*[(name() = 'e') and (position() = 1)]\"],\n            ['e:last-child', \"*/*[(name() = 'e') and (position() = last())]\"],\n            ['e:first-of-type', '*/e[position() = 1]'],\n            ['e:last-of-type', '*/e[position() = last()]'],\n            ['e:only-child', \"*/*[(name() = 'e') and (last() = 1)]\"],\n            ['e:only-of-type', 'e[count(preceding-sibling::e)=0 and count(following-sibling::e)=0]'],\n            ['e:empty', 'e[not(*) and not(string-length())]'],\n            ['e:EmPTY', 'e[not(*) and not(string-length())]'],\n            ['e:root', 'e[not(parent::*)]'],\n            ['e:hover', 'e[0]'],\n            ['e:contains(\"foo\")', \"e[contains(string(.), 'foo')]\"],\n            ['e:ConTains(foo)', \"e[contains(string(.), 'foo')]\"],\n            ['e.warning', \"e[@class and contains(concat(' ', normalize-space(@class), ' '), ' warning ')]\"],\n            ['e#myid', \"e[@id = 'myid']\"],\n            ['e:not(:nth-child(odd))', 'e[not(position() - 1 >= 0 and (position() - 1) mod 2 = 0)]'],\n            ['e:nOT(*)', 'e[0]'],\n            ['e f', 'e/descendant-or-self::*/f'],\n            ['e > f', 'e/f'],\n            ['e + f', \"e/following-sibling::*[(name() = 'f') and (position() = 1)]\"],\n            ['e ~ f', 'e/following-sibling::f'],\n            ['div#container p', \"div[@id = 'container']/descendant-or-self::*/p\"],\n            [':scope > div[dataimg=\"<testmessage>\"]', \"*[1]/div[@dataimg = '<testmessage>']\"],\n            [':scope', '*[1]'],\n            ['e:is(section, article) h1', \"e[(name() = 'section') or (name() = 'article')]/descendant-or-self::*/h1\"],\n            ['e:where(section, article) h1', \"e[(name() = 'section') or (name() = 'article')]/descendant-or-self::*/h1\"],\n            ['div:has(> .foo)', \"div[./*[@class and contains(concat(' ', normalize-space(@class), ' '), ' foo ')]]\"],\n            ['div:has(~ .foo)', \"div[following-sibling::*[@class and contains(concat(' ', normalize-space(@class), ' '), ' foo ')]]\"],\n            ['div:has(+ .foo)', \"div[following-sibling::*[(@class and contains(concat(' ', normalize-space(@class), ' '), ' foo ')) and (position() = 1)]]\"],\n            ['div:has(.foo)', \"div[descendant-or-self::*[@class and contains(concat(' ', normalize-space(@class), ' '), ' foo ')]]\"],\n            ['div:has(#bar)', \"div[descendant-or-self::*[@id = 'bar']]\"],\n        ];\n    }\n\n    public static function getXmlLangTestData()\n    {\n        return [\n            [':lang(\"EN\")', ['first', 'second', 'third', 'fourth']],\n            [':lang(\"en-us\")', ['second', 'fourth']],\n            [':lang(en-nz)', ['third']],\n            [':lang(fr)', ['fifth']],\n            [':lang(ru)', ['sixth']],\n            [\":lang('ZH')\", ['eighth']],\n            [':lang(de) :lang(zh)', ['eighth']],\n            [':lang(en), :lang(zh)', ['first', 'second', 'third', 'fourth', 'eighth']],\n            [':lang(es)', []],\n        ];\n    }\n\n    public static function getHtmlIdsTestData()\n    {\n        return [\n            ['div', ['outer-div', 'li-div', 'foobar-div']],\n            ['DIV', ['outer-div', 'li-div', 'foobar-div']],  // case-insensitive in HTML\n            ['div div', ['li-div']],\n            ['div, div div', ['outer-div', 'li-div', 'foobar-div']],\n            ['a[name]', ['name-anchor']],\n            ['a[NAme]', ['name-anchor']], // case-insensitive in HTML:\n            ['a[rel]', ['tag-anchor', 'nofollow-anchor']],\n            ['a[rel=\"tag\"]', ['tag-anchor']],\n            ['a[href*=\"localhost\"]', ['tag-anchor']],\n            ['a[href*=\"\"]', []],\n            ['a[href^=\"http\"]', ['tag-anchor', 'nofollow-anchor']],\n            ['a[href^=\"http:\"]', ['tag-anchor']],\n            ['a[href^=\"\"]', []],\n            ['a[href$=\"org\"]', ['nofollow-anchor']],\n            ['a[href$=\"\"]', []],\n            ['div[foobar~=\"bc\"]', ['foobar-div']],\n            ['div[foobar~=\"cde\"]', ['foobar-div']],\n            ['[foobar~=\"ab bc\"]', ['foobar-div']],\n            ['[foobar~=\"\"]', []],\n            ['[foobar~=\" \\t\"]', []],\n            ['div[foobar~=\"cd\"]', []],\n            ['*[lang|=\"En\"]', ['second-li']],\n            ['[lang|=\"En-us\"]', ['second-li']],\n            // Attribute values are case-sensitive\n            ['*[lang|=\"en\"]', []],\n            ['[lang|=\"en-US\"]', []],\n            ['*[lang|=\"e\"]', []],\n            // ... :lang() is not.\n            [':lang(\"EN\")', ['second-li', 'li-div']],\n            ['*:lang(en-US)', ['second-li', 'li-div']],\n            [':lang(\"e\")', []],\n            ['li:nth-child(3)', ['third-li']],\n            ['li:nth-child(10)', []],\n            ['li:nth-child(2n)', ['second-li', 'fourth-li', 'sixth-li']],\n            ['li:nth-child(even)', ['second-li', 'fourth-li', 'sixth-li']],\n            ['li:nth-child(2n+0)', ['second-li', 'fourth-li', 'sixth-li']],\n            ['li:nth-child(+2n+1)', ['first-li', 'third-li', 'fifth-li', 'seventh-li']],\n            ['li:nth-child(odd)', ['first-li', 'third-li', 'fifth-li', 'seventh-li']],\n            ['li:nth-child(2n+4)', ['fourth-li', 'sixth-li']],\n            ['li:nth-child(3n+1)', ['first-li', 'fourth-li', 'seventh-li']],\n            ['li:nth-child(n)', ['first-li', 'second-li', 'third-li', 'fourth-li', 'fifth-li', 'sixth-li', 'seventh-li']],\n            ['li:nth-child(n-1)', ['first-li', 'second-li', 'third-li', 'fourth-li', 'fifth-li', 'sixth-li', 'seventh-li']],\n            ['li:nth-child(n+1)', ['first-li', 'second-li', 'third-li', 'fourth-li', 'fifth-li', 'sixth-li', 'seventh-li']],\n            ['li:nth-child(n+3)', ['third-li', 'fourth-li', 'fifth-li', 'sixth-li', 'seventh-li']],\n            ['li:nth-child(-n)', []],\n            ['li:nth-child(-n-1)', []],\n            ['li:nth-child(-n+1)', ['first-li']],\n            ['li:nth-child(-n+3)', ['first-li', 'second-li', 'third-li']],\n            ['li:nth-last-child(0)', []],\n            ['li:nth-last-child(2n)', ['second-li', 'fourth-li', 'sixth-li']],\n            ['li:nth-last-child(even)', ['second-li', 'fourth-li', 'sixth-li']],\n            ['li:nth-last-child(2n+2)', ['second-li', 'fourth-li', 'sixth-li']],\n            ['li:nth-last-child(n)', ['first-li', 'second-li', 'third-li', 'fourth-li', 'fifth-li', 'sixth-li', 'seventh-li']],\n            ['li:nth-last-child(n-1)', ['first-li', 'second-li', 'third-li', 'fourth-li', 'fifth-li', 'sixth-li', 'seventh-li']],\n            ['li:nth-last-child(n-3)', ['first-li', 'second-li', 'third-li', 'fourth-li', 'fifth-li', 'sixth-li', 'seventh-li']],\n            ['li:nth-last-child(n+1)', ['first-li', 'second-li', 'third-li', 'fourth-li', 'fifth-li', 'sixth-li', 'seventh-li']],\n            ['li:nth-last-child(n+3)', ['first-li', 'second-li', 'third-li', 'fourth-li', 'fifth-li']],\n            ['li:nth-last-child(-n)', []],\n            ['li:nth-last-child(-n-1)', []],\n            ['li:nth-last-child(-n+1)', ['seventh-li']],\n            ['li:nth-last-child(-n+3)', ['fifth-li', 'sixth-li', 'seventh-li']],\n            ['ol:first-of-type', ['first-ol']],\n            ['ol:nth-child(4)', ['first-ol']],\n            ['ol:nth-of-type(2)', ['second-ol']],\n            ['ol:nth-last-of-type(1)', ['second-ol']],\n            ['span:only-child', ['foobar-span', 'no-siblings-of-any-type']],\n            ['li div:only-child', ['li-div']],\n            ['div *:only-child', ['li-div', 'foobar-span']],\n            ['p:only-of-type', ['paragraph']],\n            [':only-of-type', ['html', 'li-div', 'foobar-span', 'no-siblings-of-any-type']],\n            ['div#foobar-div :only-of-type', ['foobar-span']],\n            ['a:empty', ['name-anchor']],\n            ['a:EMpty', ['name-anchor']],\n            ['li:empty', ['third-li', 'fourth-li', 'fifth-li', 'sixth-li']],\n            [':root', ['html']],\n            ['html:root', ['html']],\n            ['li:root', []],\n            ['* :root', []],\n            ['*:contains(\"link\")', ['html', 'nil', 'outer-div', 'tag-anchor', 'nofollow-anchor']],\n            [':CONtains(\"link\")', ['html', 'nil', 'outer-div', 'tag-anchor', 'nofollow-anchor']],\n            ['*:contains(\"LInk\")', []],  // case-sensitive\n            ['*:contains(\"e\")', ['html', 'nil', 'outer-div', 'first-ol', 'first-li', 'paragraph', 'p-em']],\n            ['*:contains(\"E\")', []],  // case-sensitive\n            ['.a', ['first-ol']],\n            ['.b', ['first-ol']],\n            ['*.a', ['first-ol']],\n            ['ol.a', ['first-ol']],\n            ['.c', ['first-ol', 'third-li', 'fourth-li']],\n            ['*.c', ['first-ol', 'third-li', 'fourth-li']],\n            ['ol *.c', ['third-li', 'fourth-li']],\n            ['ol li.c', ['third-li', 'fourth-li']],\n            ['li ~ li.c', ['third-li', 'fourth-li']],\n            ['ol > li.c', ['third-li', 'fourth-li']],\n            ['#first-li', ['first-li']],\n            ['li#first-li', ['first-li']],\n            ['*#first-li', ['first-li']],\n            ['li div', ['li-div']],\n            ['li > div', ['li-div']],\n            ['div div', ['li-div']],\n            ['div > div', []],\n            ['div>.c', ['first-ol']],\n            ['div > .c', ['first-ol']],\n            ['div + div', ['foobar-div']],\n            ['a ~ a', ['tag-anchor', 'nofollow-anchor']],\n            ['a[rel=\"tag\"] ~ a', ['nofollow-anchor']],\n            ['ol#first-ol li:last-child', ['seventh-li']],\n            ['ol#first-ol *:last-child', ['li-div', 'seventh-li']],\n            ['#outer-div:first-child', ['outer-div']],\n            ['#outer-div :first-child', ['name-anchor', 'first-li', 'li-div', 'p-b', 'checkbox-fieldset-disabled', 'area-href']],\n            ['a[href]', ['tag-anchor', 'nofollow-anchor']],\n            [':not(*)', []],\n            ['a:not([href])', ['name-anchor']],\n            ['ol :Not(li[class])', ['first-li', 'second-li', 'li-div', 'fifth-li', 'sixth-li', 'seventh-li']],\n            [':is(#first-li, #second-li)', ['first-li', 'second-li']],\n            ['a:is(#name-anchor, #tag-anchor)', ['name-anchor', 'tag-anchor']],\n            [':is(.c)', ['first-ol', 'third-li', 'fourth-li']],\n            ['a:is(:not(#name-anchor))', ['tag-anchor', 'nofollow-anchor']],\n            ['a:not(:is(#name-anchor))', ['tag-anchor', 'nofollow-anchor']],\n            [':where(#first-li, #second-li)', ['first-li', 'second-li']],\n            ['a:where(#name-anchor, #tag-anchor)', ['name-anchor', 'tag-anchor']],\n            [':where(.c)', ['first-ol', 'third-li', 'fourth-li']],\n            ['a:where(:not(#name-anchor))', ['tag-anchor', 'nofollow-anchor']],\n            ['a:not(:where(#name-anchor))', ['tag-anchor', 'nofollow-anchor']],\n            ['a:where(:is(#name-anchor), :where(#tag-anchor))', ['name-anchor', 'tag-anchor']],\n            // HTML-specific\n            [':link', ['link-href', 'tag-anchor', 'nofollow-anchor', 'area-href']],\n            [':visited', []],\n            [':enabled', ['link-href', 'tag-anchor', 'nofollow-anchor', 'checkbox-unchecked', 'text-checked', 'checkbox-checked', 'area-href']],\n            [':disabled', ['checkbox-disabled', 'checkbox-disabled-checked', 'fieldset', 'checkbox-fieldset-disabled']],\n            [':checked', ['checkbox-checked', 'checkbox-disabled-checked']],\n        ];\n    }\n\n    public static function getHtmlShakespearTestData()\n    {\n        return [\n            ['*', 246],\n            ['div:contains(CELIA)', 26],\n            ['div:only-child', 22], // ?\n            ['div:nth-child(even)', 106],\n            ['div:nth-child(2n)', 106],\n            ['div:nth-child(odd)', 137],\n            ['div:nth-child(2n+1)', 137],\n            ['div:nth-child(n)', 243],\n            ['div:last-child', 53],\n            ['div:first-child', 51],\n            ['div > div', 242],\n            ['div + div', 190],\n            ['div ~ div', 190],\n            ['body', 1],\n            ['body div', 243],\n            ['div', 243],\n            ['div div', 242],\n            ['div div div', 241],\n            ['div, div, div', 243],\n            ['div, a, span', 243],\n            ['.dialog', 51],\n            ['div.dialog', 51],\n            ['div .dialog', 51],\n            ['div.character, div.dialog', 99],\n            ['div.direction.dialog', 0],\n            ['div.dialog.direction', 0],\n            ['div.dialog.scene', 1],\n            ['div.scene.scene', 1],\n            ['div.scene .scene', 0],\n            ['div.direction .dialog ', 0],\n            ['div .dialog .direction', 4],\n            ['div.dialog .dialog .direction', 4],\n            ['#speech5', 1],\n            ['div#speech5', 1],\n            ['div #speech5', 1],\n            ['div.scene div.dialog', 49],\n            ['div#scene1 div.dialog div', 142],\n            ['#scene1 #speech1', 1],\n            ['div[class]', 103],\n            ['div[class=dialog]', 50],\n            ['div[class^=dia]', 51],\n            ['div[class$=log]', 50],\n            ['div[class*=sce]', 1],\n            ['div[class|=dialog]', 50], // ? Seems right\n            ['div[class!=madeup]', 243], // ? Seems right\n            ['div[class~=dialog]', 51], // ? Seems right\n            [':scope > div', 1],\n            [':scope > div > div[class=dialog]', 1],\n            [':scope > div div', 242],\n            ['div:is(div#test .dialog) .direction', 4],\n        ];\n    }\n}\n"
  },
  {
    "path": "XPath/Extension/AbstractExtension.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\XPath\\Extension;\n\n/**\n * XPath expression translator abstract extension.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/scrapy/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n *\n * @internal\n */\nabstract class AbstractExtension implements ExtensionInterface\n{\n    public function getNodeTranslators(): array\n    {\n        return [];\n    }\n\n    public function getCombinationTranslators(): array\n    {\n        return [];\n    }\n\n    public function getFunctionTranslators(): array\n    {\n        return [];\n    }\n\n    public function getPseudoClassTranslators(): array\n    {\n        return [];\n    }\n\n    public function getAttributeMatchingTranslators(): array\n    {\n        return [];\n    }\n\n    public function getRelativeCombinationTranslators(): array\n    {\n        return [];\n    }\n}\n"
  },
  {
    "path": "XPath/Extension/AttributeMatchingExtension.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\XPath\\Extension;\n\nuse Symfony\\Component\\CssSelector\\XPath\\Translator;\nuse Symfony\\Component\\CssSelector\\XPath\\XPathExpr;\n\n/**\n * XPath expression translator attribute extension.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n *\n * @internal\n */\nclass AttributeMatchingExtension extends AbstractExtension\n{\n    public function getAttributeMatchingTranslators(): array\n    {\n        return [\n            'exists' => $this->translateExists(...),\n            '=' => $this->translateEquals(...),\n            '~=' => $this->translateIncludes(...),\n            '|=' => $this->translateDashMatch(...),\n            '^=' => $this->translatePrefixMatch(...),\n            '$=' => $this->translateSuffixMatch(...),\n            '*=' => $this->translateSubstringMatch(...),\n            '!=' => $this->translateDifferent(...),\n        ];\n    }\n\n    public function translateExists(XPathExpr $xpath, string $attribute, ?string $value): XPathExpr\n    {\n        return $xpath->addCondition($attribute);\n    }\n\n    public function translateEquals(XPathExpr $xpath, string $attribute, ?string $value): XPathExpr\n    {\n        return $xpath->addCondition(\\sprintf('%s = %s', $attribute, Translator::getXpathLiteral($value)));\n    }\n\n    public function translateIncludes(XPathExpr $xpath, string $attribute, ?string $value): XPathExpr\n    {\n        return $xpath->addCondition($value ? \\sprintf(\n            '%1$s and contains(concat(\\' \\', normalize-space(%1$s), \\' \\'), %2$s)',\n            $attribute,\n            Translator::getXpathLiteral(' '.$value.' ')\n        ) : '0');\n    }\n\n    public function translateDashMatch(XPathExpr $xpath, string $attribute, ?string $value): XPathExpr\n    {\n        return $xpath->addCondition(\\sprintf(\n            '%1$s and (%1$s = %2$s or starts-with(%1$s, %3$s))',\n            $attribute,\n            Translator::getXpathLiteral($value),\n            Translator::getXpathLiteral($value.'-')\n        ));\n    }\n\n    public function translatePrefixMatch(XPathExpr $xpath, string $attribute, ?string $value): XPathExpr\n    {\n        return $xpath->addCondition($value ? \\sprintf(\n            '%1$s and starts-with(%1$s, %2$s)',\n            $attribute,\n            Translator::getXpathLiteral($value)\n        ) : '0');\n    }\n\n    public function translateSuffixMatch(XPathExpr $xpath, string $attribute, ?string $value): XPathExpr\n    {\n        return $xpath->addCondition($value ? \\sprintf(\n            '%1$s and substring(%1$s, string-length(%1$s)-%2$s) = %3$s',\n            $attribute,\n            \\strlen($value) - 1,\n            Translator::getXpathLiteral($value)\n        ) : '0');\n    }\n\n    public function translateSubstringMatch(XPathExpr $xpath, string $attribute, ?string $value): XPathExpr\n    {\n        return $xpath->addCondition($value ? \\sprintf(\n            '%1$s and contains(%1$s, %2$s)',\n            $attribute,\n            Translator::getXpathLiteral($value)\n        ) : '0');\n    }\n\n    public function translateDifferent(XPathExpr $xpath, string $attribute, ?string $value): XPathExpr\n    {\n        return $xpath->addCondition(\\sprintf(\n            $value ? 'not(%1$s) or %1$s != %2$s' : '%s != %s',\n            $attribute,\n            Translator::getXpathLiteral($value)\n        ));\n    }\n\n    public function getName(): string\n    {\n        return 'attribute-matching';\n    }\n}\n"
  },
  {
    "path": "XPath/Extension/CombinationExtension.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\XPath\\Extension;\n\nuse Symfony\\Component\\CssSelector\\XPath\\XPathExpr;\n\n/**\n * XPath expression translator combination extension.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n *\n * @internal\n */\nclass CombinationExtension extends AbstractExtension\n{\n    public function getCombinationTranslators(): array\n    {\n        return [\n            ' ' => $this->translateDescendant(...),\n            '>' => $this->translateChild(...),\n            '+' => $this->translateDirectAdjacent(...),\n            '~' => $this->translateIndirectAdjacent(...),\n        ];\n    }\n\n    public function translateDescendant(XPathExpr $xpath, XPathExpr $combinedXpath): XPathExpr\n    {\n        return $xpath->join('/descendant-or-self::*/', $combinedXpath);\n    }\n\n    public function translateChild(XPathExpr $xpath, XPathExpr $combinedXpath): XPathExpr\n    {\n        return $xpath->join('/', $combinedXpath);\n    }\n\n    public function translateDirectAdjacent(XPathExpr $xpath, XPathExpr $combinedXpath): XPathExpr\n    {\n        return $xpath\n            ->join('/following-sibling::', $combinedXpath)\n            ->addNameTest()\n            ->addCondition('position() = 1');\n    }\n\n    public function translateIndirectAdjacent(XPathExpr $xpath, XPathExpr $combinedXpath): XPathExpr\n    {\n        return $xpath->join('/following-sibling::', $combinedXpath);\n    }\n\n    public function getName(): string\n    {\n        return 'combination';\n    }\n}\n"
  },
  {
    "path": "XPath/Extension/ExtensionInterface.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\XPath\\Extension;\n\nuse Symfony\\Component\\CssSelector\\XPath\\XPathExpr;\n\n/**\n * XPath expression translator extension interface.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/scrapy/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n *\n * @internal\n */\ninterface ExtensionInterface\n{\n    /**\n     * Returns node translators.\n     *\n     * These callables will receive the node as first argument and the translator as second argument.\n     *\n     * @return callable[]\n     */\n    public function getNodeTranslators(): array;\n\n    /**\n     * Returns combination translators.\n     *\n     * @return callable[]\n     */\n    public function getCombinationTranslators(): array;\n\n    /**\n     * Returns function translators.\n     *\n     * @return callable[]\n     */\n    public function getFunctionTranslators(): array;\n\n    /**\n     * Returns pseudo-class translators.\n     *\n     * @return callable[]\n     */\n    public function getPseudoClassTranslators(): array;\n\n    /**\n     * Returns attribute operation translators.\n     *\n     * @return callable[]\n     */\n    public function getAttributeMatchingTranslators(): array;\n\n    /**\n     * Returns combination translators found inside \":has()\" relation.\n     *\n     * @return array<string, callable(XPathExpr, XPathExpr): XPathExpr>\n     */\n    public function getRelativeCombinationTranslators(): array;\n\n    /**\n     * Returns extension name.\n     */\n    public function getName(): string;\n}\n"
  },
  {
    "path": "XPath/Extension/FunctionExtension.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\XPath\\Extension;\n\nuse Symfony\\Component\\CssSelector\\Exception\\ExpressionErrorException;\nuse Symfony\\Component\\CssSelector\\Exception\\SyntaxErrorException;\nuse Symfony\\Component\\CssSelector\\Node\\FunctionNode;\nuse Symfony\\Component\\CssSelector\\Parser\\Parser;\nuse Symfony\\Component\\CssSelector\\XPath\\Translator;\nuse Symfony\\Component\\CssSelector\\XPath\\XPathExpr;\n\n/**\n * XPath expression translator function extension.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n *\n * @internal\n */\nclass FunctionExtension extends AbstractExtension\n{\n    public function getFunctionTranslators(): array\n    {\n        return [\n            'nth-child' => $this->translateNthChild(...),\n            'nth-last-child' => $this->translateNthLastChild(...),\n            'nth-of-type' => $this->translateNthOfType(...),\n            'nth-last-of-type' => $this->translateNthLastOfType(...),\n            'contains' => $this->translateContains(...),\n            'lang' => $this->translateLang(...),\n        ];\n    }\n\n    /**\n     * @throws ExpressionErrorException\n     */\n    public function translateNthChild(XPathExpr $xpath, FunctionNode $function, bool $last = false, bool $addNameTest = true): XPathExpr\n    {\n        try {\n            [$a, $b] = Parser::parseSeries($function->getArguments());\n        } catch (SyntaxErrorException $e) {\n            throw new ExpressionErrorException(\\sprintf('Invalid series: \"%s\".', implode('\", \"', $function->getArguments())), 0, $e);\n        }\n\n        $xpath->addStarPrefix();\n        if ($addNameTest) {\n            $xpath->addNameTest();\n        }\n\n        if (0 === $a) {\n            return $xpath->addCondition('position() = '.($last ? 'last() - '.($b - 1) : $b));\n        }\n\n        if ($a < 0) {\n            if ($b < 1) {\n                return $xpath->addCondition('false()');\n            }\n\n            $sign = '<=';\n        } else {\n            $sign = '>=';\n        }\n\n        $expr = 'position()';\n\n        if ($last) {\n            $expr = 'last() - '.$expr;\n            --$b;\n        }\n\n        if (0 !== $b) {\n            $expr .= ' - '.$b;\n        }\n\n        $conditions = [\\sprintf('%s %s 0', $expr, $sign)];\n\n        if (1 !== $a && -1 !== $a) {\n            $conditions[] = \\sprintf('(%s) mod %d = 0', $expr, $a);\n        }\n\n        return $xpath->addCondition(implode(' and ', $conditions));\n\n        // todo: handle an+b, odd, even\n        // an+b means every-a, plus b, e.g., 2n+1 means odd\n        // 0n+b means b\n        // n+0 means a=1, i.e., all elements\n        // an means every a elements, i.e., 2n means even\n        // -n means -1n\n        // -1n+6 means elements 6 and previous\n    }\n\n    public function translateNthLastChild(XPathExpr $xpath, FunctionNode $function): XPathExpr\n    {\n        return $this->translateNthChild($xpath, $function, true);\n    }\n\n    public function translateNthOfType(XPathExpr $xpath, FunctionNode $function): XPathExpr\n    {\n        return $this->translateNthChild($xpath, $function, false, false);\n    }\n\n    /**\n     * @throws ExpressionErrorException\n     */\n    public function translateNthLastOfType(XPathExpr $xpath, FunctionNode $function): XPathExpr\n    {\n        if ('*' === $xpath->getElement()) {\n            throw new ExpressionErrorException('\"*:nth-of-type()\" is not implemented.');\n        }\n\n        return $this->translateNthChild($xpath, $function, true, false);\n    }\n\n    /**\n     * @throws ExpressionErrorException\n     */\n    public function translateContains(XPathExpr $xpath, FunctionNode $function): XPathExpr\n    {\n        $arguments = $function->getArguments();\n        foreach ($arguments as $token) {\n            if (!($token->isString() || $token->isIdentifier())) {\n                throw new ExpressionErrorException('Expected a single string or identifier for :contains(), got '.implode(', ', $arguments));\n            }\n        }\n\n        return $xpath->addCondition(\\sprintf(\n            'contains(string(.), %s)',\n            Translator::getXpathLiteral($arguments[0]->getValue())\n        ));\n    }\n\n    /**\n     * @throws ExpressionErrorException\n     */\n    public function translateLang(XPathExpr $xpath, FunctionNode $function): XPathExpr\n    {\n        $arguments = $function->getArguments();\n        foreach ($arguments as $token) {\n            if (!($token->isString() || $token->isIdentifier())) {\n                throw new ExpressionErrorException('Expected a single string or identifier for :lang(), got '.implode(', ', $arguments));\n            }\n        }\n\n        return $xpath->addCondition(\\sprintf(\n            'lang(%s)',\n            Translator::getXpathLiteral($arguments[0]->getValue())\n        ));\n    }\n\n    public function getName(): string\n    {\n        return 'function';\n    }\n}\n"
  },
  {
    "path": "XPath/Extension/HtmlExtension.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\XPath\\Extension;\n\nuse Symfony\\Component\\CssSelector\\Exception\\ExpressionErrorException;\nuse Symfony\\Component\\CssSelector\\Node\\FunctionNode;\nuse Symfony\\Component\\CssSelector\\XPath\\Translator;\nuse Symfony\\Component\\CssSelector\\XPath\\XPathExpr;\n\n/**\n * XPath expression translator HTML extension.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n *\n * @internal\n */\nclass HtmlExtension extends AbstractExtension\n{\n    public function __construct(Translator $translator)\n    {\n        $translator\n            ->getExtension('node')\n            ->setFlag(NodeExtension::ELEMENT_NAME_IN_LOWER_CASE, true)\n            ->setFlag(NodeExtension::ATTRIBUTE_NAME_IN_LOWER_CASE, true);\n    }\n\n    public function getPseudoClassTranslators(): array\n    {\n        return [\n            'checked' => $this->translateChecked(...),\n            'link' => $this->translateLink(...),\n            'disabled' => $this->translateDisabled(...),\n            'enabled' => $this->translateEnabled(...),\n            'selected' => $this->translateSelected(...),\n            'invalid' => $this->translateInvalid(...),\n            'hover' => $this->translateHover(...),\n            'visited' => $this->translateVisited(...),\n        ];\n    }\n\n    public function getFunctionTranslators(): array\n    {\n        return [\n            'lang' => $this->translateLang(...),\n        ];\n    }\n\n    public function translateChecked(XPathExpr $xpath): XPathExpr\n    {\n        return $xpath->addCondition(\n            '(@checked '\n            .\"and (name(.) = 'input' or name(.) = 'command')\"\n            .\"and (@type = 'checkbox' or @type = 'radio'))\"\n        );\n    }\n\n    public function translateLink(XPathExpr $xpath): XPathExpr\n    {\n        return $xpath->addCondition(\"@href and (name(.) = 'a' or name(.) = 'link' or name(.) = 'area')\");\n    }\n\n    public function translateDisabled(XPathExpr $xpath): XPathExpr\n    {\n        return $xpath->addCondition(\n            '('\n                .'@disabled and'\n                .'('\n                    .\"(name(.) = 'input' and @type != 'hidden')\"\n                    .\" or name(.) = 'button'\"\n                    .\" or name(.) = 'select'\"\n                    .\" or name(.) = 'textarea'\"\n                    .\" or name(.) = 'command'\"\n                    .\" or name(.) = 'fieldset'\"\n                    .\" or name(.) = 'optgroup'\"\n                    .\" or name(.) = 'option'\"\n                .')'\n            .') or ('\n                .\"(name(.) = 'input' and @type != 'hidden')\"\n                .\" or name(.) = 'button'\"\n                .\" or name(.) = 'select'\"\n                .\" or name(.) = 'textarea'\"\n            .')'\n            .' and ancestor::fieldset[@disabled]'\n        );\n        // todo: in the second half, add \"and is not a descendant of that fieldset element's first legend element child, if any.\"\n    }\n\n    public function translateEnabled(XPathExpr $xpath): XPathExpr\n    {\n        return $xpath->addCondition(\n            '('\n                .'@href and ('\n                    .\"name(.) = 'a'\"\n                    .\" or name(.) = 'link'\"\n                    .\" or name(.) = 'area'\"\n                .')'\n            .') or ('\n                .'('\n                    .\"name(.) = 'command'\"\n                    .\" or name(.) = 'fieldset'\"\n                    .\" or name(.) = 'optgroup'\"\n                .')'\n                .' and not(@disabled)'\n            .') or ('\n                .'('\n                    .\"(name(.) = 'input' and @type != 'hidden')\"\n                    .\" or name(.) = 'button'\"\n                    .\" or name(.) = 'select'\"\n                    .\" or name(.) = 'textarea'\"\n                    .\" or name(.) = 'keygen'\"\n                .')'\n                .' and not (@disabled or ancestor::fieldset[@disabled])'\n            .') or ('\n                .\"name(.) = 'option' and not(\"\n                    .'@disabled or ancestor::optgroup[@disabled]'\n                .')'\n            .')'\n        );\n    }\n\n    /**\n     * @throws ExpressionErrorException\n     */\n    public function translateLang(XPathExpr $xpath, FunctionNode $function): XPathExpr\n    {\n        $arguments = $function->getArguments();\n        foreach ($arguments as $token) {\n            if (!($token->isString() || $token->isIdentifier())) {\n                throw new ExpressionErrorException('Expected a single string or identifier for :lang(), got '.implode(', ', $arguments));\n            }\n        }\n\n        return $xpath->addCondition(\\sprintf(\n            'ancestor-or-self::*[@lang][1][starts-with(concat('\n            .\"translate(@%s, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), '-')\"\n            .', %s)]',\n            'lang',\n            Translator::getXpathLiteral(strtolower($arguments[0]->getValue()).'-')\n        ));\n    }\n\n    public function translateSelected(XPathExpr $xpath): XPathExpr\n    {\n        return $xpath->addCondition(\"(@selected and name(.) = 'option')\");\n    }\n\n    public function translateInvalid(XPathExpr $xpath): XPathExpr\n    {\n        return $xpath->addCondition('0');\n    }\n\n    public function translateHover(XPathExpr $xpath): XPathExpr\n    {\n        return $xpath->addCondition('0');\n    }\n\n    public function translateVisited(XPathExpr $xpath): XPathExpr\n    {\n        return $xpath->addCondition('0');\n    }\n\n    public function getName(): string\n    {\n        return 'html';\n    }\n}\n"
  },
  {
    "path": "XPath/Extension/NodeExtension.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\XPath\\Extension;\n\nuse Symfony\\Component\\CssSelector\\Node;\nuse Symfony\\Component\\CssSelector\\XPath\\Translator;\nuse Symfony\\Component\\CssSelector\\XPath\\XPathExpr;\n\n/**\n * XPath expression translator node extension.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/scrapy/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n *\n * @internal\n */\nclass NodeExtension extends AbstractExtension\n{\n    public const ELEMENT_NAME_IN_LOWER_CASE = 1;\n    public const ATTRIBUTE_NAME_IN_LOWER_CASE = 2;\n    public const ATTRIBUTE_VALUE_IN_LOWER_CASE = 4;\n\n    public function __construct(\n        private int $flags = 0,\n    ) {\n    }\n\n    /**\n     * @return $this\n     */\n    public function setFlag(int $flag, bool $on): static\n    {\n        if ($on && !$this->hasFlag($flag)) {\n            $this->flags += $flag;\n        }\n\n        if (!$on && $this->hasFlag($flag)) {\n            $this->flags -= $flag;\n        }\n\n        return $this;\n    }\n\n    public function hasFlag(int $flag): bool\n    {\n        return (bool) ($this->flags & $flag);\n    }\n\n    public function getNodeTranslators(): array\n    {\n        return [\n            'Selector' => $this->translateSelector(...),\n            'CombinedSelector' => $this->translateCombinedSelector(...),\n            'Negation' => $this->translateNegation(...),\n            'Matching' => $this->translateMatching(...),\n            'SpecificityAdjustment' => $this->translateSpecificityAdjustment(...),\n            'Function' => $this->translateFunction(...),\n            'Pseudo' => $this->translatePseudo(...),\n            'Attribute' => $this->translateAttribute(...),\n            'Class' => $this->translateClass(...),\n            'Hash' => $this->translateHash(...),\n            'Element' => $this->translateElement(...),\n            'Relation' => $this->translateRelation(...),\n        ];\n    }\n\n    public function translateSelector(Node\\SelectorNode $node, Translator $translator): XPathExpr\n    {\n        return $translator->nodeToXPath($node->getTree());\n    }\n\n    public function translateCombinedSelector(Node\\CombinedSelectorNode $node, Translator $translator): XPathExpr\n    {\n        return $translator->addCombination($node->getCombinator(), $node->getSelector(), $node->getSubSelector());\n    }\n\n    public function translateNegation(Node\\NegationNode $node, Translator $translator): XPathExpr\n    {\n        $xpath = $translator->nodeToXPath($node->getSelector());\n        $subXpath = $translator->nodeToXPath($node->getSubSelector());\n        $subXpath->addNameTest();\n\n        if ($subXpath->getCondition()) {\n            return $xpath->addCondition(\\sprintf('not(%s)', $subXpath->getCondition()));\n        }\n\n        return $xpath->addCondition('0');\n    }\n\n    public function translateMatching(Node\\MatchingNode $node, Translator $translator): XPathExpr\n    {\n        $xpath = $translator->nodeToXPath($node->selector);\n\n        foreach ($node->arguments as $argument) {\n            $expr = $translator->nodeToXPath($argument);\n            $expr->addNameTest();\n            if ($condition = $expr->getCondition()) {\n                $xpath->addCondition($condition, 'or');\n            }\n        }\n\n        return $xpath;\n    }\n\n    public function translateSpecificityAdjustment(Node\\SpecificityAdjustmentNode $node, Translator $translator): XPathExpr\n    {\n        $xpath = $translator->nodeToXPath($node->selector);\n\n        foreach ($node->arguments as $argument) {\n            $expr = $translator->nodeToXPath($argument);\n            $expr->addNameTest();\n            if ($condition = $expr->getCondition()) {\n                $xpath->addCondition($condition, 'or');\n            }\n        }\n\n        return $xpath;\n    }\n\n    public function translateFunction(Node\\FunctionNode $node, Translator $translator): XPathExpr\n    {\n        $xpath = $translator->nodeToXPath($node->getSelector());\n\n        return $translator->addFunction($xpath, $node);\n    }\n\n    public function translatePseudo(Node\\PseudoNode $node, Translator $translator): XPathExpr\n    {\n        $xpath = $translator->nodeToXPath($node->getSelector());\n\n        return $translator->addPseudoClass($xpath, $node->getIdentifier());\n    }\n\n    public function translateAttribute(Node\\AttributeNode $node, Translator $translator): XPathExpr\n    {\n        $name = $node->getAttribute();\n        $safe = $this->isSafeName($name);\n\n        if ($this->hasFlag(self::ATTRIBUTE_NAME_IN_LOWER_CASE)) {\n            $name = strtolower($name);\n        }\n\n        if ($node->getNamespace()) {\n            $name = \\sprintf('%s:%s', $node->getNamespace(), $name);\n            $safe = $safe && $this->isSafeName($node->getNamespace());\n        }\n\n        $attribute = $safe ? '@'.$name : \\sprintf('attribute::*[name() = %s]', Translator::getXpathLiteral($name));\n        $value = $node->getValue();\n        $xpath = $translator->nodeToXPath($node->getSelector());\n\n        if ($this->hasFlag(self::ATTRIBUTE_VALUE_IN_LOWER_CASE)) {\n            $value = strtolower($value);\n        }\n\n        return $translator->addAttributeMatching($xpath, $node->getOperator(), $attribute, $value);\n    }\n\n    public function translateClass(Node\\ClassNode $node, Translator $translator): XPathExpr\n    {\n        $xpath = $translator->nodeToXPath($node->getSelector());\n\n        return $translator->addAttributeMatching($xpath, '~=', '@class', $node->getName());\n    }\n\n    public function translateHash(Node\\HashNode $node, Translator $translator): XPathExpr\n    {\n        $xpath = $translator->nodeToXPath($node->getSelector());\n\n        return $translator->addAttributeMatching($xpath, '=', '@id', $node->getId());\n    }\n\n    public function translateElement(Node\\ElementNode $node): XPathExpr\n    {\n        $element = $node->getElement();\n\n        if ($element && $this->hasFlag(self::ELEMENT_NAME_IN_LOWER_CASE)) {\n            $element = strtolower($element);\n        }\n\n        if ($element) {\n            $safe = $this->isSafeName($element);\n        } else {\n            $element = '*';\n            $safe = true;\n        }\n\n        if ($node->getNamespace()) {\n            $element = \\sprintf('%s:%s', $node->getNamespace(), $element);\n            $safe = $safe && $this->isSafeName($node->getNamespace());\n        }\n\n        $xpath = new XPathExpr('', $element);\n\n        if (!$safe) {\n            $xpath->addNameTest();\n        }\n\n        return $xpath;\n    }\n\n    public function translateRelation(Node\\RelationNode $node, Translator $translator): XPathExpr\n    {\n        $combinator = $node->getCombinator();\n\n        return $translator->addRelativeCombination($combinator, $node->getSelector(), $node->getSubSelector());\n    }\n\n    public function getName(): string\n    {\n        return 'node';\n    }\n\n    private function isSafeName(string $name): bool\n    {\n        return 0 < preg_match('~^[a-zA-Z_][a-zA-Z0-9_.-]*$~', $name);\n    }\n}\n"
  },
  {
    "path": "XPath/Extension/PseudoClassExtension.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\XPath\\Extension;\n\nuse Symfony\\Component\\CssSelector\\Exception\\ExpressionErrorException;\nuse Symfony\\Component\\CssSelector\\XPath\\XPathExpr;\n\n/**\n * XPath expression translator pseudo-class extension.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n *\n * @internal\n */\nclass PseudoClassExtension extends AbstractExtension\n{\n    public function getPseudoClassTranslators(): array\n    {\n        return [\n            'root' => $this->translateRoot(...),\n            'scope' => $this->translateScopePseudo(...),\n            'first-child' => $this->translateFirstChild(...),\n            'last-child' => $this->translateLastChild(...),\n            'first-of-type' => $this->translateFirstOfType(...),\n            'last-of-type' => $this->translateLastOfType(...),\n            'only-child' => $this->translateOnlyChild(...),\n            'only-of-type' => $this->translateOnlyOfType(...),\n            'empty' => $this->translateEmpty(...),\n        ];\n    }\n\n    public function translateRoot(XPathExpr $xpath): XPathExpr\n    {\n        return $xpath->addCondition('not(parent::*)');\n    }\n\n    public function translateScopePseudo(XPathExpr $xpath): XPathExpr\n    {\n        return $xpath->addCondition('1');\n    }\n\n    public function translateFirstChild(XPathExpr $xpath): XPathExpr\n    {\n        return $xpath\n            ->addStarPrefix()\n            ->addNameTest()\n            ->addCondition('position() = 1');\n    }\n\n    public function translateLastChild(XPathExpr $xpath): XPathExpr\n    {\n        return $xpath\n            ->addStarPrefix()\n            ->addNameTest()\n            ->addCondition('position() = last()');\n    }\n\n    /**\n     * @throws ExpressionErrorException\n     */\n    public function translateFirstOfType(XPathExpr $xpath): XPathExpr\n    {\n        if ('*' === $xpath->getElement()) {\n            throw new ExpressionErrorException('\"*:first-of-type\" is not implemented.');\n        }\n\n        return $xpath\n            ->addStarPrefix()\n            ->addCondition('position() = 1');\n    }\n\n    /**\n     * @throws ExpressionErrorException\n     */\n    public function translateLastOfType(XPathExpr $xpath): XPathExpr\n    {\n        if ('*' === $xpath->getElement()) {\n            throw new ExpressionErrorException('\"*:last-of-type\" is not implemented.');\n        }\n\n        return $xpath\n            ->addStarPrefix()\n            ->addCondition('position() = last()');\n    }\n\n    public function translateOnlyChild(XPathExpr $xpath): XPathExpr\n    {\n        return $xpath\n            ->addStarPrefix()\n            ->addNameTest()\n            ->addCondition('last() = 1');\n    }\n\n    public function translateOnlyOfType(XPathExpr $xpath): XPathExpr\n    {\n        $element = $xpath->getElement();\n\n        return $xpath->addCondition(\\sprintf('count(preceding-sibling::%s)=0 and count(following-sibling::%s)=0', $element, $element));\n    }\n\n    public function translateEmpty(XPathExpr $xpath): XPathExpr\n    {\n        return $xpath->addCondition('not(*) and not(string-length())');\n    }\n\n    public function getName(): string\n    {\n        return 'pseudo-class';\n    }\n}\n"
  },
  {
    "path": "XPath/Extension/RelationExtension.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\XPath\\Extension;\n\nuse Symfony\\Component\\CssSelector\\XPath\\XPathExpr;\n\n/**\n * XPath expression translator combination extension.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/scrapy/cssselect.\n *\n * @author Franck Ranaivo-Harisoa <franckranaivo@gmail.com>\n *\n * @internal\n */\nclass RelationExtension extends AbstractExtension\n{\n    public function getRelativeCombinationTranslators(): array\n    {\n        return [\n            ' ' => $this->translateRelationDescendant(...),\n            '>' => $this->translateRelationChild(...),\n            '+' => $this->translateRelationDirectAdjacent(...),\n            '~' => $this->translateRelationIndirectAdjacent(...),\n        ];\n    }\n\n    public function translateRelationDescendant(XPathExpr $xpath, XPathExpr $combinedXpath): XPathExpr\n    {\n        return $xpath->join('[descendant-or-self::', $combinedXpath, ']', true);\n    }\n\n    public function translateRelationChild(XPathExpr $xpath, XPathExpr $combinedXpath): XPathExpr\n    {\n        return $xpath->join('[./', $combinedXpath, ']', true);\n    }\n\n    public function translateRelationDirectAdjacent(XPathExpr $xpath, XPathExpr $combinedXpath): XPathExpr\n    {\n        $combinedXpath\n            ->addNameTest()\n            ->addCondition('position() = 1');\n\n        return $xpath\n            ->join('[following-sibling::', $combinedXpath, ']', true);\n    }\n\n    public function translateRelationIndirectAdjacent(XPathExpr $xpath, XPathExpr $combinedXpath): XPathExpr\n    {\n        return $xpath->join('[following-sibling::', $combinedXpath, ']', true);\n    }\n\n    public function getName(): string\n    {\n        return 'relation';\n    }\n}\n"
  },
  {
    "path": "XPath/Translator.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\XPath;\n\nuse Symfony\\Component\\CssSelector\\Exception\\ExpressionErrorException;\nuse Symfony\\Component\\CssSelector\\Node\\FunctionNode;\nuse Symfony\\Component\\CssSelector\\Node\\NodeInterface;\nuse Symfony\\Component\\CssSelector\\Node\\SelectorNode;\nuse Symfony\\Component\\CssSelector\\Parser\\Parser;\nuse Symfony\\Component\\CssSelector\\Parser\\ParserInterface;\n\n/**\n * XPath expression translator interface.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n *\n * @internal\n */\nclass Translator implements TranslatorInterface\n{\n    private ParserInterface $mainParser;\n\n    /**\n     * @var ParserInterface[]\n     */\n    private array $shortcutParsers = [];\n\n    /**\n     * @var Extension\\ExtensionInterface[]\n     */\n    private array $extensions = [];\n\n    private array $nodeTranslators = [];\n    private array $combinationTranslators = [];\n    private array $relativeCombinationTranslators = [];\n    private array $functionTranslators = [];\n    private array $pseudoClassTranslators = [];\n    private array $attributeMatchingTranslators = [];\n\n    public function __construct(?ParserInterface $parser = null)\n    {\n        $this->mainParser = $parser ?? new Parser();\n\n        $this\n            ->registerExtension(new Extension\\NodeExtension())\n            ->registerExtension(new Extension\\CombinationExtension())\n            ->registerExtension(new Extension\\FunctionExtension())\n            ->registerExtension(new Extension\\PseudoClassExtension())\n            ->registerExtension(new Extension\\AttributeMatchingExtension())\n            ->registerExtension(new Extension\\RelationExtension())\n        ;\n    }\n\n    public static function getXpathLiteral(string $element): string\n    {\n        if (!str_contains($element, \"'\")) {\n            return \"'\".$element.\"'\";\n        }\n\n        if (!str_contains($element, '\"')) {\n            return '\"'.$element.'\"';\n        }\n\n        $string = $element;\n        $parts = [];\n        while (true) {\n            if (false !== $pos = strpos($string, \"'\")) {\n                $parts[] = \\sprintf(\"'%s'\", substr($string, 0, $pos));\n                $parts[] = \"\\\"'\\\"\";\n                $string = substr($string, $pos + 1);\n            } else {\n                $parts[] = \"'$string'\";\n                break;\n            }\n        }\n\n        return \\sprintf('concat(%s)', implode(', ', $parts));\n    }\n\n    public function cssToXPath(string $cssExpr, string $prefix = 'descendant-or-self::'): string\n    {\n        $selectors = $this->parseSelectors($cssExpr);\n\n        foreach ($selectors as $index => $selector) {\n            if (null !== $selector->getPseudoElement()) {\n                throw new ExpressionErrorException('Pseudo-elements are not supported.');\n            }\n\n            $selectors[$index] = $this->selectorToXPath($selector, $prefix);\n        }\n\n        return implode(' | ', $selectors);\n    }\n\n    public function selectorToXPath(SelectorNode $selector, string $prefix = 'descendant-or-self::'): string\n    {\n        return ($prefix ?: '').$this->nodeToXPath($selector);\n    }\n\n    /**\n     * @return $this\n     */\n    public function registerExtension(Extension\\ExtensionInterface $extension): static\n    {\n        $this->extensions[$extension->getName()] = $extension;\n\n        $this->nodeTranslators = array_merge($this->nodeTranslators, $extension->getNodeTranslators());\n        $this->combinationTranslators = array_merge($this->combinationTranslators, $extension->getCombinationTranslators());\n        $this->functionTranslators = array_merge($this->functionTranslators, $extension->getFunctionTranslators());\n        $this->pseudoClassTranslators = array_merge($this->pseudoClassTranslators, $extension->getPseudoClassTranslators());\n        $this->attributeMatchingTranslators = array_merge($this->attributeMatchingTranslators, $extension->getAttributeMatchingTranslators());\n        $this->relativeCombinationTranslators = array_merge($this->relativeCombinationTranslators, $extension->getRelativeCombinationTranslators());\n\n        return $this;\n    }\n\n    /**\n     * @throws ExpressionErrorException\n     */\n    public function getExtension(string $name): Extension\\ExtensionInterface\n    {\n        if (!isset($this->extensions[$name])) {\n            throw new ExpressionErrorException(\\sprintf('Extension \"%s\" not registered.', $name));\n        }\n\n        return $this->extensions[$name];\n    }\n\n    /**\n     * @return $this\n     */\n    public function registerParserShortcut(ParserInterface $shortcut): static\n    {\n        $this->shortcutParsers[] = $shortcut;\n\n        return $this;\n    }\n\n    /**\n     * @throws ExpressionErrorException\n     */\n    public function nodeToXPath(NodeInterface $node): XPathExpr\n    {\n        if (!isset($this->nodeTranslators[$node->getNodeName()])) {\n            throw new ExpressionErrorException(\\sprintf('Node \"%s\" not supported.', $node->getNodeName()));\n        }\n\n        return $this->nodeTranslators[$node->getNodeName()]($node, $this);\n    }\n\n    /**\n     * @throws ExpressionErrorException\n     */\n    public function addCombination(string $combiner, NodeInterface $xpath, NodeInterface $combinedXpath): XPathExpr\n    {\n        if (!isset($this->combinationTranslators[$combiner])) {\n            throw new ExpressionErrorException(\\sprintf('Combiner \"%s\" not supported.', $combiner));\n        }\n\n        return $this->combinationTranslators[$combiner]($this->nodeToXPath($xpath), $this->nodeToXPath($combinedXpath));\n    }\n\n    /**\n     * @throws ExpressionErrorException\n     */\n    public function addRelativeCombination(string $combiner, NodeInterface $xpath, NodeInterface $combinedXpath): XPathExpr\n    {\n        if (!isset($this->relativeCombinationTranslators[$combiner])) {\n            throw new ExpressionErrorException(\\sprintf('Combiner \"%s\" not supported.', $combiner));\n        }\n\n        return $this->relativeCombinationTranslators[$combiner]($this->nodeToXPath($xpath), $this->nodeToXPath($combinedXpath));\n    }\n\n    /**\n     * @throws ExpressionErrorException\n     */\n    public function addFunction(XPathExpr $xpath, FunctionNode $function): XPathExpr\n    {\n        if (!isset($this->functionTranslators[$function->getName()])) {\n            throw new ExpressionErrorException(\\sprintf('Function \"%s\" not supported.', $function->getName()));\n        }\n\n        return $this->functionTranslators[$function->getName()]($xpath, $function);\n    }\n\n    /**\n     * @throws ExpressionErrorException\n     */\n    public function addPseudoClass(XPathExpr $xpath, string $pseudoClass): XPathExpr\n    {\n        if (!isset($this->pseudoClassTranslators[$pseudoClass])) {\n            throw new ExpressionErrorException(\\sprintf('Pseudo-class \"%s\" not supported.', $pseudoClass));\n        }\n\n        return $this->pseudoClassTranslators[$pseudoClass]($xpath);\n    }\n\n    /**\n     * @throws ExpressionErrorException\n     */\n    public function addAttributeMatching(XPathExpr $xpath, string $operator, string $attribute, ?string $value): XPathExpr\n    {\n        if (!isset($this->attributeMatchingTranslators[$operator])) {\n            throw new ExpressionErrorException(\\sprintf('Attribute matcher operator \"%s\" not supported.', $operator));\n        }\n\n        return $this->attributeMatchingTranslators[$operator]($xpath, $attribute, $value);\n    }\n\n    /**\n     * @return SelectorNode[]\n     */\n    private function parseSelectors(string $css): array\n    {\n        foreach ($this->shortcutParsers as $shortcut) {\n            $tokens = $shortcut->parse($css);\n\n            if ($tokens) {\n                return $tokens;\n            }\n        }\n\n        return $this->mainParser->parse($css);\n    }\n}\n"
  },
  {
    "path": "XPath/TranslatorInterface.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\XPath;\n\nuse Symfony\\Component\\CssSelector\\Node\\SelectorNode;\n\n/**\n * XPath expression translator interface.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n *\n * @internal\n */\ninterface TranslatorInterface\n{\n    /**\n     * Translates a CSS selector to an XPath expression.\n     */\n    public function cssToXPath(string $cssExpr, string $prefix = 'descendant-or-self::'): string;\n\n    /**\n     * Translates a parsed selector node to an XPath expression.\n     */\n    public function selectorToXPath(SelectorNode $selector, string $prefix = 'descendant-or-self::'): string;\n}\n"
  },
  {
    "path": "XPath/XPathExpr.php",
    "content": "<?php\n\n/*\n * This file is part of the Symfony package.\n *\n * (c) Fabien Potencier <fabien@symfony.com>\n *\n * For the full copyright and license information, please view the LICENSE\n * file that was distributed with this source code.\n */\n\nnamespace Symfony\\Component\\CssSelector\\XPath;\n\n/**\n * XPath expression translator interface.\n *\n * This component is a port of the Python cssselect library,\n * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.\n *\n * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>\n *\n * @internal\n */\nclass XPathExpr\n{\n    public function __construct(\n        private string $path = '',\n        private string $element = '*',\n        private string $condition = '',\n        bool $starPrefix = false,\n    ) {\n        if ($starPrefix) {\n            $this->addStarPrefix();\n        }\n    }\n\n    public function getElement(): string\n    {\n        return $this->element;\n    }\n\n    /**\n     * @return $this\n     */\n    public function addCondition(string $condition, string $operator = 'and'): static\n    {\n        $this->condition = $this->condition ? \\sprintf('(%s) %s (%s)', $this->condition, $operator, $condition) : $condition;\n\n        return $this;\n    }\n\n    public function getCondition(): string\n    {\n        return $this->condition;\n    }\n\n    /**\n     * @return $this\n     */\n    public function addNameTest(): static\n    {\n        if ('*' !== $this->element) {\n            $this->addCondition('name() = '.Translator::getXpathLiteral($this->element));\n            $this->element = '*';\n        }\n\n        return $this;\n    }\n\n    /**\n     * @return $this\n     */\n    public function addStarPrefix(): static\n    {\n        $this->path .= '*/';\n\n        return $this;\n    }\n\n    /**\n     * Joins another XPathExpr with a combiner.\n     *\n     * @return $this\n     */\n    public function join(string $combiner, self $expr, ?string $closingCombiner = null, bool $hasInnerConditions = false): static\n    {\n        $path = $this->__toString().$combiner;\n\n        if ('*/' !== $expr->path) {\n            $path .= $expr->path;\n        }\n\n        $this->path = $path;\n\n        if (!$hasInnerConditions) {\n            $this->element = $expr->element.($closingCombiner ?? '');\n            $this->condition = $expr->condition;\n        } else {\n            $this->element = $expr->element;\n            if ($expr->condition) {\n                $this->element .= '['.$expr->condition.']';\n            }\n            if ($closingCombiner) {\n                $this->element .= $closingCombiner;\n            }\n        }\n\n        return $this;\n    }\n\n    public function __toString(): string\n    {\n        $path = $this->path.$this->element;\n        $condition = '' === $this->condition ? '' : '['.$this->condition.']';\n\n        return $path.$condition;\n    }\n}\n"
  },
  {
    "path": "composer.json",
    "content": "{\n    \"name\": \"symfony/css-selector\",\n    \"type\": \"library\",\n    \"description\": \"Converts CSS selectors to XPath expressions\",\n    \"keywords\": [],\n    \"homepage\": \"https://symfony.com\",\n    \"license\": \"MIT\",\n    \"authors\": [\n        {\n            \"name\": \"Fabien Potencier\",\n            \"email\": \"fabien@symfony.com\"\n        },\n        {\n            \"name\": \"Jean-François Simon\",\n            \"email\": \"jeanfrancois.simon@sensiolabs.com\"\n        },\n        {\n            \"name\": \"Symfony Community\",\n            \"homepage\": \"https://symfony.com/contributors\"\n        }\n    ],\n    \"require\": {\n        \"php\": \">=8.4\"\n    },\n    \"autoload\": {\n        \"psr-4\": { \"Symfony\\\\Component\\\\CssSelector\\\\\": \"\" },\n        \"exclude-from-classmap\": [\n            \"/Tests/\"\n        ]\n    },\n    \"minimum-stability\": \"dev\"\n}\n"
  },
  {
    "path": "phpunit.xml.dist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<phpunit xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:noNamespaceSchemaLocation=\"https://schema.phpunit.de/11.3/phpunit.xsd\"\n         backupGlobals=\"false\"\n         colors=\"true\"\n         bootstrap=\"vendor/autoload.php\"\n         failOnDeprecation=\"true\"\n         failOnRisky=\"true\"\n         failOnWarning=\"true\"\n>\n    <php>\n        <ini name=\"error_reporting\" value=\"-1\" />\n    </php>\n\n    <testsuites>\n        <testsuite name=\"Symfony CssSelector Component Test Suite\">\n            <directory>./Tests/</directory>\n        </testsuite>\n    </testsuites>\n\n    <source ignoreSuppressionOfDeprecations=\"true\">\n        <include>\n            <directory>./</directory>\n        </include>\n        <exclude>\n            <directory>./Resources</directory>\n            <directory>./Tests</directory>\n            <directory>./vendor</directory>\n        </exclude>\n    </source>\n\n    <extensions>\n        <bootstrap class=\"Symfony\\Bridge\\PhpUnit\\SymfonyExtension\" />\n    </extensions>\n</phpunit>\n"
  }
]