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