See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401]
1 <?php 2 3 namespace Sabberworm\CSS\Property; 4 5 /** 6 * Class representing a single CSS selector. Selectors have to be split by the comma prior to being passed into this 7 * class. 8 */ 9 class Selector 10 { 11 /** 12 * regexp for specificity calculations 13 * 14 * @var string 15 */ 16 const NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX = '/ 17 (\.[\w]+) # classes 18 | 19 \[(\w+) # attributes 20 | 21 (\:( # pseudo classes 22 link|visited|active 23 |hover|focus 24 |lang 25 |target 26 |enabled|disabled|checked|indeterminate 27 |root 28 |nth-child|nth-last-child|nth-of-type|nth-last-of-type 29 |first-child|last-child|first-of-type|last-of-type 30 |only-child|only-of-type 31 |empty|contains 32 )) 33 /ix'; 34 35 /** 36 * regexp for specificity calculations 37 * 38 * @var string 39 */ 40 const ELEMENTS_AND_PSEUDO_ELEMENTS_RX = '/ 41 ((^|[\s\+\>\~]+)[\w]+ # elements 42 | 43 \:{1,2}( # pseudo-elements 44 after|before|first-letter|first-line|selection 45 )) 46 /ix'; 47 48 /** 49 * regexp for specificity calculations 50 * 51 * @var string 52 */ 53 const SELECTOR_VALIDATION_RX = '/ 54 ^( 55 (?: 56 [a-zA-Z0-9\x{00A0}-\x{FFFF}_^$|*="\'~\[\]()\-\s\.:#+>]* # any sequence of valid unescaped characters 57 (?:\\\\.)? # a single escaped character 58 (?:([\'"]).*?(?<!\\\\)\2)? # a quoted text like [id="example"] 59 )* 60 )$ 61 /ux'; 62 63 /** 64 * @var string 65 */ 66 private $sSelector; 67 68 /** 69 * @var int|null 70 */ 71 private $iSpecificity; 72 73 /** 74 * @param string $sSelector 75 * 76 * @return bool 77 */ 78 public static function isValid($sSelector) 79 { 80 return preg_match(static::SELECTOR_VALIDATION_RX, $sSelector); 81 } 82 83 /** 84 * @param string $sSelector 85 * @param bool $bCalculateSpecificity 86 */ 87 public function __construct($sSelector, $bCalculateSpecificity = false) 88 { 89 $this->setSelector($sSelector); 90 if ($bCalculateSpecificity) { 91 $this->getSpecificity(); 92 } 93 } 94 95 /** 96 * @return string 97 */ 98 public function getSelector() 99 { 100 return $this->sSelector; 101 } 102 103 /** 104 * @param string $sSelector 105 * 106 * @return void 107 */ 108 public function setSelector($sSelector) 109 { 110 $this->sSelector = trim($sSelector); 111 $this->iSpecificity = null; 112 } 113 114 /** 115 * @return string 116 */ 117 public function __toString() 118 { 119 return $this->getSelector(); 120 } 121 122 /** 123 * @return int 124 */ 125 public function getSpecificity() 126 { 127 if ($this->iSpecificity === null) { 128 $a = 0; 129 /// @todo should exclude \# as well as "#" 130 $aMatches = null; 131 $b = substr_count($this->sSelector, '#'); 132 $c = preg_match_all(self::NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX, $this->sSelector, $aMatches); 133 $d = preg_match_all(self::ELEMENTS_AND_PSEUDO_ELEMENTS_RX, $this->sSelector, $aMatches); 134 $this->iSpecificity = ($a * 1000) + ($b * 100) + ($c * 10) + $d; 135 } 136 return $this->iSpecificity; 137 } 138 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body