| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104 |
- <?php
- namespace Gt\CssXPath;
- class SingleSelectorConverter {
- private ThreadMatcher $threadMatcher;
- private PseudoSelectorConverter $pseudoSelectorConverter;
- private AttributeSelectorConverter $attributeSelectorConverter;
- public function __construct(
- ?ThreadMatcher $threadMatcher = null,
- ?PseudoSelectorConverter $pseudoSelectorConverter = null,
- ?AttributeSelectorConverter $attributeSelectorConverter = null,
- ) {
- $this->threadMatcher = $threadMatcher ?? new ThreadMatcher();
- $this->pseudoSelectorConverter = $pseudoSelectorConverter
- ?? new PseudoSelectorConverter();
- $this->attributeSelectorConverter = $attributeSelectorConverter
- ?? new AttributeSelectorConverter();
- }
- public function convert(
- string $css,
- string $prefix,
- bool $htmlMode
- ):string {
- $thread = array_values(
- array_filter(
- $this->threadMatcher->collate(Translator::CSS_REGEX, $css)
- )
- );
- $expression = new XPathExpression($prefix);
- foreach($thread as $index => $token) {
- $next = $thread[$index + 1] ?? null;
- $this->applyToken($token, $next, $expression, $htmlMode);
- }
- return $expression->toString();
- }
- /**
- * @param array<string, mixed> $token
- * @param array<string, mixed>|null $next
- */
- private function applyToken(
- array $token,
- ?array $next,
- XPathExpression $expression,
- bool $htmlMode
- ):void {
- $handlers = [
- "star" => fn() => $expression
- ->appendElement((string)$token["content"], $htmlMode),
- "element" => fn() => $expression
- ->appendElement((string)$token["content"], $htmlMode),
- "pseudo" => fn() => $this->pseudoSelectorConverter
- ->apply($token, $next, $expression, $htmlMode),
- "child" => fn() => $this->appendAxis($expression, "/"),
- "id" => fn() => $this->appendId($expression, (string)$token["content"]),
- "class" => fn() => $this
- ->appendClass($expression, (string)$token["content"]),
- "sibling" => fn() => $this->appendAxis(
- $expression,
- "/following-sibling::*[1]/self::"
- ),
- "subsequentsibling" => fn() => $this->appendAxis(
- $expression,
- "/following-sibling::"
- ),
- "attribute" => fn() => $this->attributeSelectorConverter
- ->apply($token, $expression, $htmlMode),
- "descendant" => fn() => $this->appendAxis($expression, "//"),
- ];
- $handler = $handlers[$token["type"]] ?? null;
- if($handler !== null) {
- $handler();
- }
- }
- private function appendAxis(XPathExpression $expression, string $axis):void {
- $expression->appendFragment($axis);
- $expression->markElementMissing();
- }
- private function appendId(
- XPathExpression $expression,
- string $identifier
- ):void {
- $expression->ensureElement();
- $expression->appendFragment("[@id='{$identifier}']");
- }
- private function appendClass(
- XPathExpression $expression,
- string $className
- ):void {
- $expression->ensureElement();
- $expression->appendFragment(
- "[contains(concat(' ',normalize-space(@class),' '),' {$className} ')]"
- );
- }
- }
|