xref: /plugin/combo/vendor/carica/phpcss/src/PhpCss/Parser/PseudoClass.php (revision 04fd306c7c155fa133ebb3669986875d65988276)
1*04fd306cSNickeau<?php
2*04fd306cSNickeau/**
3*04fd306cSNickeau* The attribute parser parses a simple attribute selector.
4*04fd306cSNickeau*
5*04fd306cSNickeau* @license http://www.opensource.org/licenses/mit-license.php The MIT License
6*04fd306cSNickeau* @copyright Copyright 2010-2014 PhpCss Team
7*04fd306cSNickeau*/
8*04fd306cSNickeaunamespace PhpCss\Parser {
9*04fd306cSNickeau
10*04fd306cSNickeau  use LogicException;
11*04fd306cSNickeau  use PhpCss;
12*04fd306cSNickeau  use PhpCss\Ast;
13*04fd306cSNickeau  use PhpCss\Exception\UnknownPseudoElementException;
14*04fd306cSNickeau  use PhpCss\Scanner;
15*04fd306cSNickeau  /**
16*04fd306cSNickeau  * The attribute parser parses a simple attribute selector.
17*04fd306cSNickeau  *
18*04fd306cSNickeau  * The attribute value can be an string if a string start is found it delegates to a string
19*04fd306cSNickeau  * parser.
20*04fd306cSNickeau  */
21*04fd306cSNickeau  class PseudoClass extends PhpCss\Parser {
22*04fd306cSNickeau
23*04fd306cSNickeau    private const PARAMETER_NONE = 1;
24*04fd306cSNickeau    private const PARAMETER_IDENTIFIER = 2;
25*04fd306cSNickeau    private const PARAMETER_POSITION = 4;
26*04fd306cSNickeau    private const PARAMETER_SIMPLE_SELECTOR = 8;
27*04fd306cSNickeau    private const PARAMETER_STRING = 16;
28*04fd306cSNickeau    private const PARAMETER_NUMBER = 32;
29*04fd306cSNickeau
30*04fd306cSNickeau    /**
31*04fd306cSNickeau     * @throws PhpCss\Exception\ParserException
32*04fd306cSNickeau     * @throws PhpCss\Exception\UnknownPseudoClassException
33*04fd306cSNickeau     * @throws UnknownPseudoElementException
34*04fd306cSNickeau     */
35*04fd306cSNickeau    public function parse(): Ast\Node {
36*04fd306cSNickeau      $token = $this->read(Scanner\Token::PSEUDO_CLASS);
37*04fd306cSNickeau      $name = substr($token->content, 1);
38*04fd306cSNickeau      if ($mode = $this->getParameterMode($name)) {
39*04fd306cSNickeau        if ($mode === self::PARAMETER_NONE) {
40*04fd306cSNickeau          return new Ast\Selector\Simple\PseudoClass($name);
41*04fd306cSNickeau        }
42*04fd306cSNickeau        $this->read(Scanner\Token::PARENTHESES_START);
43*04fd306cSNickeau        $this->ignore(Scanner\Token::WHITESPACE);
44*04fd306cSNickeau        switch ($mode) {
45*04fd306cSNickeau        case self::PARAMETER_IDENTIFIER :
46*04fd306cSNickeau          $parameterToken = $this->read(Scanner\Token::IDENTIFIER);
47*04fd306cSNickeau          $class = new Ast\Value\Language($parameterToken->content);
48*04fd306cSNickeau          break;
49*04fd306cSNickeau        case self::PARAMETER_POSITION :
50*04fd306cSNickeau          $parameterToken = $this->read(
51*04fd306cSNickeau            array(
52*04fd306cSNickeau              Scanner\Token::IDENTIFIER,
53*04fd306cSNickeau              Scanner\Token::NUMBER,
54*04fd306cSNickeau              Scanner\Token::PSEUDO_CLASS_POSITION
55*04fd306cSNickeau            )
56*04fd306cSNickeau          );
57*04fd306cSNickeau          $class = new Ast\Selector\Simple\PseudoClass(
58*04fd306cSNickeau            $name, $this->createPseudoClassPosition($parameterToken->content)
59*04fd306cSNickeau          );
60*04fd306cSNickeau          break;
61*04fd306cSNickeau        case self::PARAMETER_STRING :
62*04fd306cSNickeau          $this->read(
63*04fd306cSNickeau            [Scanner\Token::SINGLEQUOTE_STRING_START, Scanner\Token::DOUBLEQUOTE_STRING_START]
64*04fd306cSNickeau          );
65*04fd306cSNickeau          $parameter = $this->delegate(Text::CLASS);
66*04fd306cSNickeau          $class = new Ast\Selector\Simple\PseudoClass(
67*04fd306cSNickeau            $name, $parameter
68*04fd306cSNickeau          );
69*04fd306cSNickeau          break;
70*04fd306cSNickeau        case self::PARAMETER_NUMBER :
71*04fd306cSNickeau          $parameter = $this->read(Scanner\Token::NUMBER);
72*04fd306cSNickeau          $class = new Ast\Selector\Simple\PseudoClass(
73*04fd306cSNickeau            $name, new Ast\Value\Number((int)$parameter->content)
74*04fd306cSNickeau          );
75*04fd306cSNickeau          break;
76*04fd306cSNickeau        case self::PARAMETER_SIMPLE_SELECTOR :
77*04fd306cSNickeau          $parameterToken = $this->lookahead(
78*04fd306cSNickeau            array(
79*04fd306cSNickeau              Scanner\Token::IDENTIFIER,
80*04fd306cSNickeau              Scanner\Token::ID_SELECTOR,
81*04fd306cSNickeau              Scanner\Token::CLASS_SELECTOR,
82*04fd306cSNickeau              Scanner\Token::PSEUDO_CLASS,
83*04fd306cSNickeau              Scanner\Token::PSEUDO_ELEMENT,
84*04fd306cSNickeau              Scanner\Token::ATTRIBUTE_SELECTOR_START
85*04fd306cSNickeau            )
86*04fd306cSNickeau          );
87*04fd306cSNickeau          switch ($parameterToken->type) {
88*04fd306cSNickeau          case Scanner\Token::IDENTIFIER :
89*04fd306cSNickeau          case Scanner\Token::ID_SELECTOR :
90*04fd306cSNickeau          case Scanner\Token::CLASS_SELECTOR :
91*04fd306cSNickeau            $this->read($parameterToken->type);
92*04fd306cSNickeau            $parameter = $this->createSelector($parameterToken);
93*04fd306cSNickeau            break;
94*04fd306cSNickeau          case Scanner\Token::PSEUDO_CLASS :
95*04fd306cSNickeau            if ($parameterToken->content === ':not') {
96*04fd306cSNickeau              throw new LogicException(
97*04fd306cSNickeau                'not not allowed in not - @todo implement exception'
98*04fd306cSNickeau              );
99*04fd306cSNickeau            }
100*04fd306cSNickeau            $parameter = $this->delegate(self::CLASS);
101*04fd306cSNickeau            break;
102*04fd306cSNickeau          case Scanner\Token::PSEUDO_ELEMENT :
103*04fd306cSNickeau            $this->read($parameterToken->type);
104*04fd306cSNickeau            $parameter = $this->createPseudoElement($parameterToken);
105*04fd306cSNickeau            break;
106*04fd306cSNickeau          case Scanner\Token::ATTRIBUTE_SELECTOR_START :
107*04fd306cSNickeau            $this->read($parameterToken->type);
108*04fd306cSNickeau            $parameter = $this->delegate(Attribute::CLASS);
109*04fd306cSNickeau            break;
110*04fd306cSNickeau          default :
111*04fd306cSNickeau            $parameter = NULL;
112*04fd306cSNickeau          }
113*04fd306cSNickeau          $class = new Ast\Selector\Simple\PseudoClass(
114*04fd306cSNickeau            $name, $parameter
115*04fd306cSNickeau          );
116*04fd306cSNickeau          break;
117*04fd306cSNickeau        default :
118*04fd306cSNickeau          $class = NULL;
119*04fd306cSNickeau        }
120*04fd306cSNickeau        $this->ignore(Scanner\Token::WHITESPACE);
121*04fd306cSNickeau        $this->read(Scanner\Token::PARENTHESES_END);
122*04fd306cSNickeau        return $class;
123*04fd306cSNickeau      }
124*04fd306cSNickeau      throw new PhpCss\Exception\UnknownPseudoClassException($token);
125*04fd306cSNickeau    }
126*04fd306cSNickeau
127*04fd306cSNickeau    private function getParameterMode($name): ?int {
128*04fd306cSNickeau      switch ($name) {
129*04fd306cSNickeau      case 'not' :
130*04fd306cSNickeau      case 'has' :
131*04fd306cSNickeau        return self::PARAMETER_SIMPLE_SELECTOR;
132*04fd306cSNickeau      case 'lang' :
133*04fd306cSNickeau        return self::PARAMETER_IDENTIFIER;
134*04fd306cSNickeau      case 'nth-child' :
135*04fd306cSNickeau      case 'nth-last-child' :
136*04fd306cSNickeau      case 'nth-of-type' :
137*04fd306cSNickeau      case 'nth-last-of-type' :
138*04fd306cSNickeau        return self::PARAMETER_POSITION;
139*04fd306cSNickeau      case 'contains':
140*04fd306cSNickeau        return self::PARAMETER_STRING;
141*04fd306cSNickeau      case 'gt':
142*04fd306cSNickeau      case 'lt':
143*04fd306cSNickeau        return self::PARAMETER_NUMBER;
144*04fd306cSNickeau      case 'root' :
145*04fd306cSNickeau      case 'first-child' :
146*04fd306cSNickeau      case 'last-child' :
147*04fd306cSNickeau      case 'first-of-type' :
148*04fd306cSNickeau      case 'last-of-type' :
149*04fd306cSNickeau      case 'only-child' :
150*04fd306cSNickeau      case 'only-of-type' :
151*04fd306cSNickeau      case 'empty' :
152*04fd306cSNickeau      case 'link' :
153*04fd306cSNickeau      case 'visited' :
154*04fd306cSNickeau      case 'active' :
155*04fd306cSNickeau      case 'hover' :
156*04fd306cSNickeau      case 'focus' :
157*04fd306cSNickeau      case 'target' :
158*04fd306cSNickeau      case 'enabled' :
159*04fd306cSNickeau      case 'disabled' :
160*04fd306cSNickeau      case 'checked' :
161*04fd306cSNickeau      case 'odd' :
162*04fd306cSNickeau      case 'even' :
163*04fd306cSNickeau        return self::PARAMETER_NONE;
164*04fd306cSNickeau      }
165*04fd306cSNickeau      return NULL;
166*04fd306cSNickeau    }
167*04fd306cSNickeau
168*04fd306cSNickeau    private function createSelector(Scanner\Token $token) {
169*04fd306cSNickeau      switch ($token->type) {
170*04fd306cSNickeau      case Scanner\Token::IDENTIFIER :
171*04fd306cSNickeau        if (FALSE !== strpos($token->content, '|')) {
172*04fd306cSNickeau          [$prefix, $name] = explode('|', $token->content);
173*04fd306cSNickeau        } else {
174*04fd306cSNickeau          $prefix = '';
175*04fd306cSNickeau          $name = $token->content;
176*04fd306cSNickeau        }
177*04fd306cSNickeau        if ($name === '*') {
178*04fd306cSNickeau          return new Ast\Selector\Simple\Universal($prefix);
179*04fd306cSNickeau        }
180*04fd306cSNickeau        return new Ast\Selector\Simple\Type($name, $prefix);
181*04fd306cSNickeau      case Scanner\Token::ID_SELECTOR :
182*04fd306cSNickeau        return new Ast\Selector\Simple\Id(substr($token->content, 1));
183*04fd306cSNickeau      case Scanner\Token::CLASS_SELECTOR :
184*04fd306cSNickeau        return new Ast\Selector\Simple\ClassName(substr($token->content, 1));
185*04fd306cSNickeau      }
186*04fd306cSNickeau      return NULL;
187*04fd306cSNickeau    }
188*04fd306cSNickeau
189*04fd306cSNickeau    /**
190*04fd306cSNickeau     * @throws UnknownPseudoElementException
191*04fd306cSNickeau     */
192*04fd306cSNickeau    private function createPseudoElement(Scanner\Token $token): Ast\Selector\Simple\PseudoElement {
193*04fd306cSNickeau      $name = substr($token->content, 2);
194*04fd306cSNickeau      switch ($name) {
195*04fd306cSNickeau      case 'first-line' :
196*04fd306cSNickeau      case 'first-letter' :
197*04fd306cSNickeau      case 'after' :
198*04fd306cSNickeau      case 'before' :
199*04fd306cSNickeau        return new Ast\Selector\Simple\PseudoElement($name);
200*04fd306cSNickeau      }
201*04fd306cSNickeau      throw new UnknownPseudoElementException($token);
202*04fd306cSNickeau    }
203*04fd306cSNickeau
204*04fd306cSNickeau    private function createPseudoClassPosition($string): Ast\Value\Position {
205*04fd306cSNickeau      $string = str_replace(' ', '', $string);
206*04fd306cSNickeau      if ($string === 'n') {
207*04fd306cSNickeau        $position = new Ast\Value\Position(1, 0);
208*04fd306cSNickeau      } elseif ($string === 'odd') {
209*04fd306cSNickeau        $position = new Ast\Value\Position(2, 1);
210*04fd306cSNickeau      } elseif ($string === 'even') {
211*04fd306cSNickeau        $position = new Ast\Value\Position(2, 0);
212*04fd306cSNickeau      } elseif (preg_match('(^[+-]?\d+$)D', $string)) {
213*04fd306cSNickeau        $position = new Ast\Value\Position(0, (int)$string);
214*04fd306cSNickeau      } elseif (
215*04fd306cSNickeau          preg_match('(^(?P<repeat>\d+)n$)D', $string, $matches) ||
216*04fd306cSNickeau          preg_match('(^(?P<repeat>[+-]?\d*)n(?P<add>[+-]\d+)$)D', $string, $matches)
217*04fd306cSNickeau        ) {
218*04fd306cSNickeau        $position = new Ast\Value\Position(
219*04fd306cSNickeau          isset($matches['repeat']) && $matches['repeat'] !== ''
220*04fd306cSNickeau            ? (int)$matches['repeat'] : 1,
221*04fd306cSNickeau          isset($matches['add']) ? (int)$matches['add'] : 0
222*04fd306cSNickeau        );
223*04fd306cSNickeau      } else {
224*04fd306cSNickeau        throw new LogicException(
225*04fd306cSNickeau          'Invalid pseudo class position - @todo implement exception'
226*04fd306cSNickeau        );
227*04fd306cSNickeau      }
228*04fd306cSNickeau      return $position;
229*04fd306cSNickeau    }
230*04fd306cSNickeau  }
231*04fd306cSNickeau}
232