@@ -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
467483class 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
494526class 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+
664779class CSSRule {
665780 private $ sRule ;
666781 private $ aValues ;
0 commit comments