Skip to content
  • P
    Projects
  • G
    Groups
  • S
    Snippets
  • Help

杨树贤 / ucenter

  • This project
    • Loading...
  • Sign in
Go to a project
  • Project
  • Repository
  • Issues 0
  • Merge Requests 0
  • Pipelines
  • Wiki
  • Snippets
  • Settings
  • Activity
  • Graph
  • Charts
  • Create a new issue
  • Jobs
  • Commits
  • Issue Boards
  • Files
  • Commits
  • Branches
  • Tags
  • Contributors
  • Graph
  • Compare
  • Charts
Find file
BlameHistoryPermalink
Switch branch/tag
  • ucenter
  • vendor
  • sabberworm
  • php-css-parser
  • src
  • Property
  • Selector.php
  • 杨树贤's avatar
    初始化项目 · 130b5f67
    杨树贤 committed a year ago
    130b5f67
Selector.php 3.35 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
<?php

namespace Sabberworm\CSS\Property;

/**
 * Class representing a single CSS selector. Selectors have to be split by the comma prior to being passed into this
 * class.
 */
class Selector
{
    /**
     * regexp for specificity calculations
     *
     * @var string
     */
    const NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX = '/
        (\.[\w]+)                   # classes
        |
        \[(\w+)                     # attributes
        |
        (\:(                        # pseudo classes
            link|visited|active
            |hover|focus
            |lang
            |target
            |enabled|disabled|checked|indeterminate
            |root
            |nth-child|nth-last-child|nth-of-type|nth-last-of-type
            |first-child|last-child|first-of-type|last-of-type
            |only-child|only-of-type
            |empty|contains
        ))
        /ix';

    /**
     * regexp for specificity calculations
     *
     * @var string
     */
    const ELEMENTS_AND_PSEUDO_ELEMENTS_RX = '/
        ((^|[\s\+\>\~]+)[\w]+   # elements
        |
        \:{1,2}(                # pseudo-elements
            after|before|first-letter|first-line|selection
        ))
        /ix';

    /**
     * regexp for specificity calculations
     *
     * @var string
     */
    const SELECTOR_VALIDATION_RX = '/
        ^(
            (?:
                [a-zA-Z0-9\x{00A0}-\x{FFFF}_^$|*="\'~\[\]()\-\s\.:#+>]* # any sequence of valid unescaped characters
                (?:\\\\.)?                                              # a single escaped character
                (?:([\'"]).*?(?<!\\\\)\2)?                              # a quoted text like [id="example"]
            )*
        )$
        /ux';

    /**
     * @var string
     */
    private $sSelector;

    /**
     * @var int|null
     */
    private $iSpecificity;

    /**
     * @param string $sSelector
     *
     * @return bool
     */
    public static function isValid($sSelector)
    {
        return preg_match(static::SELECTOR_VALIDATION_RX, $sSelector);
    }

    /**
     * @param string $sSelector
     * @param bool $bCalculateSpecificity
     */
    public function __construct($sSelector, $bCalculateSpecificity = false)
    {
        $this->setSelector($sSelector);
        if ($bCalculateSpecificity) {
            $this->getSpecificity();
        }
    }

    /**
     * @return string
     */
    public function getSelector()
    {
        return $this->sSelector;
    }

    /**
     * @param string $sSelector
     *
     * @return void
     */
    public function setSelector($sSelector)
    {
        $this->sSelector = trim($sSelector);
        $this->iSpecificity = null;
    }

    /**
     * @return string
     */
    public function __toString()
    {
        return $this->getSelector();
    }

    /**
     * @return int
     */
    public function getSpecificity()
    {
        if ($this->iSpecificity === null) {
            $a = 0;
            /// @todo should exclude \# as well as "#"
            $aMatches = null;
            $b = substr_count($this->sSelector, '#');
            $c = preg_match_all(self::NON_ID_ATTRIBUTES_AND_PSEUDO_CLASSES_RX, $this->sSelector, $aMatches);
            $d = preg_match_all(self::ELEMENTS_AND_PSEUDO_ELEMENTS_RX, $this->sSelector, $aMatches);
            $this->iSpecificity = ($a * 1000) + ($b * 100) + ($c * 10) + $d;
        }
        return $this->iSpecificity;
    }
}