Skip to content

Commit 02c92c9

Browse files
committed
Initial support for CSS Specificity. Still missing is a merge method (see issue MyIntervals#7)
1 parent 69894d6 commit 02c92c9

File tree

6 files changed

+448
-245
lines changed

6 files changed

+448
-245
lines changed

CSSParser.php

Lines changed: 133 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ private function parseCharacter($bIsForIdentifier) {
180180
}
181181

182182
private function parseSelector() {
183-
$oResult = new CSSSelector();
183+
$oResult = new CSSDeclarationBlock();
184184
$oResult->setSelector($this->consumeUntil('{'));
185185
$this->consume('{');
186186
$this->consumeWhiteSpace();
@@ -425,12 +425,12 @@ public function getContents() {
425425
return $this->aContents;
426426
}
427427

428-
protected function allSelectors(&$aResult) {
428+
protected function allDeclarationBlocks(&$aResult) {
429429
foreach($this->aContents as $mContent) {
430-
if($mContent instanceof CSSSelector) {
430+
if($mContent instanceof CSSDeclarationBlock) {
431431
$aResult[] = $mContent;
432432
} else if($mContent instanceof CSSList) {
433-
$mContent->allSelectors($aResult);
433+
$mContent->allDeclarationBlocks($aResult);
434434
}
435435
}
436436
}
@@ -462,14 +462,37 @@ protected function allValues($oElement, &$aResult, $sSearchString = null) {
462462
}
463463
}
464464
}
465+
466+
protected function allSelectors(&$aResult, $sSpecificitySearch = null) {
467+
foreach($this->getAllDeclarationBlocks() as $oBlock) {
468+
foreach($oBlock->getSelectors() as $oSelector) {
469+
if($sSpecificitySearch === null) {
470+
$aResult[] = $oSelector;
471+
} else {
472+
$sComparison = "\$bRes = {$oSelector->getSpecificity()} $sSpecificitySearch;";
473+
eval($sComparison);
474+
if($bRes) {
475+
$aResult[] = $oSelector;
476+
}
477+
}
478+
}
479+
}
480+
}
465481
}
466482

467483
class CSSDocument extends CSSList {
468-
public function getAllSelectors() {
484+
public function getAllDeclarationBlocks() {
469485
$aResult = array();
470-
$this->allSelectors($aResult);
486+
$this->allDeclarationBlocks($aResult);
471487
return $aResult;
472488
}
489+
490+
/**
491+
* @deprecated use getAllSelectors()
492+
*/
493+
public function getAllSelectors() {
494+
return $this->getAllDeclarationBlocks();
495+
}
473496

474497
public function getAllRuleSets() {
475498
$aResult = array();
@@ -489,6 +512,15 @@ public function getAllValues($mElement = null) {
489512
$this->allValues($mElement, $aResult, $sSearchString);
490513
return $aResult;
491514
}
515+
516+
public function getSelectorsBySpecificity($sSpecificitySearch = null) {
517+
if(is_numeric($sSpecificitySearch) || is_numeric($sSpecificitySearch[0])) {
518+
$sSpecificitySearch = "== $sSpecificitySearch";
519+
}
520+
$aResult = array();
521+
$this->allSelectors($aResult, $sSpecificitySearch);
522+
return $aResult;
523+
}
492524
}
493525

494526
class CSSMediaQuery extends CSSList {
@@ -630,37 +662,120 @@ public function __toString() {
630662
}
631663
}
632664

633-
class CSSSelector extends CSSRuleSet {
634-
private $aSelector;
635-
665+
class CSSDeclarationBlock extends CSSRuleSet {
666+
private $aSelectors;
667+
636668
public function __construct() {
637669
parent::__construct();
638-
$this->aSelector = array();
670+
$this->aSelectors = array();
639671
}
640-
641-
public function setSelector($mSelector) {
672+
673+
public function setSelectors($mSelector) {
642674
if(is_array($mSelector)) {
643-
$this->aSelector = $mSelector;
675+
$this->aSelectors = $mSelector;
644676
} else {
645-
$this->aSelector = explode(',', $mSelector);
677+
$this->aSelectors = explode(',', $mSelector);
646678
}
647-
foreach($this->aSelector as $iKey => $sSelector) {
648-
$this->aSelector[$iKey] = trim($sSelector);
679+
foreach($this->aSelectors as $iKey => $mSelector) {
680+
if(!($mSelector instanceof CSSSelector)) {
681+
$this->aSelectors[$iKey] = new CSSSelector($mSelector);
682+
}
649683
}
650684
}
651685

686+
/**
687+
* @deprecated use getSelectors()
688+
*/
652689
public function getSelector() {
653-
return $this->aSelector;
690+
$this->getSelectors();
691+
}
692+
693+
/**
694+
* @deprecated use setSelectors()
695+
*/
696+
public function setSelector($mSelector) {
697+
$this->setSelectors($mSelector);
698+
}
699+
700+
public function getSelectors() {
701+
return $this->aSelectors;
654702
}
655703

656704
public function __toString() {
657-
$sResult = implode(', ', $this->aSelector).' {';
705+
$sResult = implode(', ', $this->aSelectors).' {';
658706
$sResult .= parent::__toString();
659707
$sResult .= '}';
660708
return $sResult;
661709
}
662710
}
663711

712+
class CSSSelector {
713+
const
714+
NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX = '/
715+
(\.[\w]+) # classes
716+
|
717+
\[(\w+) # attributes
718+
|
719+
(\:( # pseudo classes
720+
link|visited|active
721+
|hover|focus
722+
|lang
723+
|target
724+
|enabled|disabled|checked|indeterminate
725+
|root
726+
|nth-child|nth-last-child|nth-of-type|nth-last-of-type
727+
|first-child|last-child|first-of-type|last-of-type
728+
|only-child|only-of-type
729+
|empty|contains
730+
))
731+
/ix',
732+
ELEMENTS_AND_PSEUDO_ELEMENTS_RX = '/
733+
((^|[\s\+\>\~]+)[\w]+ # elements
734+
|
735+
\:{1,2}( # pseudo-elements
736+
after|before
737+
|first-letter|first-line
738+
|selection
739+
)
740+
)/ix';
741+
742+
private $sSelector;
743+
private $iSpecificity;
744+
745+
public function __construct($sSelector, $bCalculateSpecificity = false) {
746+
$this->setSelector($sSelector);
747+
if($bCalculateSpecificity) {
748+
$this->getSpecificity();
749+
}
750+
}
751+
752+
public function getSelector() {
753+
return $this->sSelector;
754+
}
755+
756+
public function setSelector($sSelector) {
757+
$this->sSelector = trim($sSelector);
758+
$this->iSpecificity = null;
759+
}
760+
761+
public function __toString() {
762+
return $this->getSelector();
763+
}
764+
765+
public function getSpecificity() {
766+
if($this->iSpecificity === null) {
767+
$a = 0;
768+
/// @todo should exclude \# as well as "#"
769+
$aMatches;
770+
$b = substr_count($this->sSelector, '#');
771+
$c = preg_match_all(self::NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX, $this->sSelector, $aMatches);
772+
$d = preg_match_all(self::ELEMENTS_AND_PSEUDO_ELEMENTS_RX, $this->sSelector, $aMatches);
773+
$this->iSpecificity = ($a*1000) + ($b*100) + ($c*10) + $d;
774+
}
775+
return $this->iSpecificity;
776+
}
777+
}
778+
664779
class CSSRule {
665780
private $sRule;
666781
private $aValues;

0 commit comments

Comments
 (0)