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