1*04fd306cSNickeau<?php 2*04fd306cSNickeau/** 3*04fd306cSNickeau * The Sequence parser parses a list of simple selector tokens into the AST. 4*04fd306cSNickeau * 5*04fd306cSNickeau * @license http://www.opensource.org/licenses/mit-license.php The MIT License 6*04fd306cSNickeau * @copyright Copyright 2010-2012 PhpCss Team 7*04fd306cSNickeau */ 8*04fd306cSNickeau 9*04fd306cSNickeaunamespace PhpCss\Parser { 10*04fd306cSNickeau 11*04fd306cSNickeau use PhpCss; 12*04fd306cSNickeau use PhpCss\Ast; 13*04fd306cSNickeau use PhpCss\Scanner; 14*04fd306cSNickeau 15*04fd306cSNickeau /** 16*04fd306cSNickeau * The Sequence parser parses a list of simple selector tokens into the AST. 17*04fd306cSNickeau * 18*04fd306cSNickeau * It delegates to separate parsers for pseudo classes and attributes. 19*04fd306cSNickeau * 20*04fd306cSNickeau * A css combinator delegates to a new instance of this class. 21*04fd306cSNickeau */ 22*04fd306cSNickeau class Sequence extends PhpCss\Parser { 23*04fd306cSNickeau 24*04fd306cSNickeau /** 25*04fd306cSNickeau * Parse the token stream for a simple selector sequence, 26*04fd306cSNickeau * after the first element the type selector is not allowed any more, 27*04fd306cSNickeau * but a combinator is possible. 28*04fd306cSNickeau * 29*04fd306cSNickeau * @throws PhpCss\Exception\ParserException 30*04fd306cSNickeau */ 31*04fd306cSNickeau public function parse(): Ast\Node { 32*04fd306cSNickeau $sequence = new Ast\Selector\Sequence(); 33*04fd306cSNickeau $token = $this->lookahead( 34*04fd306cSNickeau [ 35*04fd306cSNickeau Scanner\Token::IDENTIFIER, 36*04fd306cSNickeau Scanner\Token::ID_SELECTOR, 37*04fd306cSNickeau Scanner\Token::CLASS_SELECTOR, 38*04fd306cSNickeau Scanner\Token::PSEUDO_CLASS, 39*04fd306cSNickeau Scanner\Token::PSEUDO_ELEMENT, 40*04fd306cSNickeau Scanner\Token::ATTRIBUTE_SELECTOR_START, 41*04fd306cSNickeau Scanner\Token::COMBINATOR, 42*04fd306cSNickeau ] 43*04fd306cSNickeau ); 44*04fd306cSNickeau while (isset($token)) { 45*04fd306cSNickeau if ($selector = $this->createSelector($token)) { 46*04fd306cSNickeau $this->read($token->type); 47*04fd306cSNickeau $sequence->simples[] = $selector; 48*04fd306cSNickeau } 49*04fd306cSNickeau switch ($token->type) { 50*04fd306cSNickeau case Scanner\Token::SEPARATOR : 51*04fd306cSNickeau $this->read(Scanner\Token::SEPARATOR); 52*04fd306cSNickeau return $sequence; 53*04fd306cSNickeau case Scanner\Token::PSEUDO_CLASS : 54*04fd306cSNickeau $sequence->simples[] = $this->delegate(PseudoClass::CLASS); 55*04fd306cSNickeau break; 56*04fd306cSNickeau case Scanner\Token::PSEUDO_ELEMENT : 57*04fd306cSNickeau $sequence->simples[] = $this->createPseudoElement($token); 58*04fd306cSNickeau $this->read($token->type); 59*04fd306cSNickeau break; 60*04fd306cSNickeau case Scanner\Token::ATTRIBUTE_SELECTOR_START : 61*04fd306cSNickeau $this->read($token->type); 62*04fd306cSNickeau $sequence->simples[] = $this->delegate(Attribute::CLASS); 63*04fd306cSNickeau break; 64*04fd306cSNickeau case Scanner\Token::COMBINATOR : 65*04fd306cSNickeau case Scanner\Token::WHITESPACE : 66*04fd306cSNickeau $this->read($token->type); 67*04fd306cSNickeau $subSequence = $this->delegate(get_class($this)); 68*04fd306cSNickeau /** 69*04fd306cSNickeau * @var Ast\Selector\Sequence $subSequence 70*04fd306cSNickeau */ 71*04fd306cSNickeau $sequence->combinator = $this->createCombinator( 72*04fd306cSNickeau $token, $subSequence 73*04fd306cSNickeau ); 74*04fd306cSNickeau return $sequence; 75*04fd306cSNickeau } 76*04fd306cSNickeau if ($this->endOfTokens()) { 77*04fd306cSNickeau $token = NULL; 78*04fd306cSNickeau continue; 79*04fd306cSNickeau } 80*04fd306cSNickeau $token = $this->lookahead( 81*04fd306cSNickeau [ 82*04fd306cSNickeau Scanner\Token::ID_SELECTOR, 83*04fd306cSNickeau Scanner\Token::CLASS_SELECTOR, 84*04fd306cSNickeau Scanner\Token::PSEUDO_CLASS, 85*04fd306cSNickeau Scanner\Token::PSEUDO_ELEMENT, 86*04fd306cSNickeau Scanner\Token::ATTRIBUTE_SELECTOR_START, 87*04fd306cSNickeau Scanner\Token::COMBINATOR, 88*04fd306cSNickeau Scanner\Token::WHITESPACE, 89*04fd306cSNickeau Scanner\Token::SEPARATOR, 90*04fd306cSNickeau ] 91*04fd306cSNickeau ); 92*04fd306cSNickeau } 93*04fd306cSNickeau return $sequence; 94*04fd306cSNickeau } 95*04fd306cSNickeau 96*04fd306cSNickeau private function createSelector(Scanner\Token $token) { 97*04fd306cSNickeau switch ($token->type) { 98*04fd306cSNickeau case Scanner\Token::IDENTIFIER : 99*04fd306cSNickeau if (FALSE !== strpos($token->content, '|')) { 100*04fd306cSNickeau [$prefix, $name] = explode('|', $token->content); 101*04fd306cSNickeau } else { 102*04fd306cSNickeau $prefix = ''; 103*04fd306cSNickeau $name = $token->content; 104*04fd306cSNickeau } 105*04fd306cSNickeau if ($name === '*') { 106*04fd306cSNickeau return new Ast\Selector\Simple\Universal($prefix); 107*04fd306cSNickeau } 108*04fd306cSNickeau return new Ast\Selector\Simple\Type($name, $prefix); 109*04fd306cSNickeau case Scanner\Token::ID_SELECTOR : 110*04fd306cSNickeau return new Ast\Selector\Simple\Id(substr($token->content, 1)); 111*04fd306cSNickeau case Scanner\Token::CLASS_SELECTOR : 112*04fd306cSNickeau return new Ast\Selector\Simple\ClassName(substr($token->content, 1)); 113*04fd306cSNickeau } 114*04fd306cSNickeau return NULL; 115*04fd306cSNickeau } 116*04fd306cSNickeau 117*04fd306cSNickeau private function createCombinator( 118*04fd306cSNickeau Scanner\Token $token, 119*04fd306cSNickeau Ast\Selector\Sequence $sequence 120*04fd306cSNickeau ) { 121*04fd306cSNickeau switch (trim($token->content)) { 122*04fd306cSNickeau case '>' : 123*04fd306cSNickeau return new Ast\Selector\Combinator\Child($sequence); 124*04fd306cSNickeau case '+' : 125*04fd306cSNickeau return new Ast\Selector\Combinator\Next($sequence); 126*04fd306cSNickeau case '~' : 127*04fd306cSNickeau return new Ast\Selector\Combinator\Follower($sequence); 128*04fd306cSNickeau default : 129*04fd306cSNickeau return new Ast\Selector\Combinator\Descendant($sequence); 130*04fd306cSNickeau } 131*04fd306cSNickeau } 132*04fd306cSNickeau 133*04fd306cSNickeau /** 134*04fd306cSNickeau * @throws PhpCss\Exception\UnknownPseudoElementException 135*04fd306cSNickeau */ 136*04fd306cSNickeau private function createPseudoElement($token): Ast\Selector\Simple\PseudoElement { 137*04fd306cSNickeau $name = substr($token->content, 2); 138*04fd306cSNickeau switch ($name) { 139*04fd306cSNickeau case 'first-line' : 140*04fd306cSNickeau case 'first-letter' : 141*04fd306cSNickeau case 'after' : 142*04fd306cSNickeau case 'before' : 143*04fd306cSNickeau return new Ast\Selector\Simple\PseudoElement($name); 144*04fd306cSNickeau } 145*04fd306cSNickeau throw new PhpCss\Exception\UnknownPseudoElementException($token); 146*04fd306cSNickeau } 147*04fd306cSNickeau } 148*04fd306cSNickeau} 149