Today, your task is to write a program (or a function) that accepts a string and outputs (or returns) four integers.
Input
The input string is a CSS3 selector, and can contain basically any Unicode character.
Output
The output represents the CSS specificity of this selector.
The first number is always 0 (because it's used for inline styles, and this exercise doesn't apply to inline styles)
The second number is the number of ids (
#foo
) present in the selector.The third number is the number of classes (
.foo
), attributes ([bar]
) and pseudo-classes present in the selector.The fourth number is the number of elements (
biz
) and pseudo-elements present in the selector.
Notes:
Universal selector (*) isn't counted anywhere
The pseudo-elements
::before
and::after
can also be written with a single ":" (legacy notation)Input can use the
:not(selector)
pseudo-class. The selector inside doesn't count, even if it contains ids, classes, elements, ...)The "bricks" of the selector are separated by combinators (spaces/tabs,
+
,>
,~
, ex:body > div+a ~*
), but they can also be cumulated (ex:div#foo.bar[baz]:hover::before
)You also have to handle CSS escape sequences (
\
followed by 1 to 6 hexadecimal numbers, followed by a space), and escaped special characters (\
followed by any of these:!"#$%&'()*+,-./:;<=>?@[\]^`{|}~
) properly. Those escapes can be part of any brick of the selector (id, class, etc).You don't need to do anything particular if you receive an invalid selector or a CSS4 selector. Don't bother implementing a CSS3 selector validator.
Here are a few links to learn more about CSS specificiy:
- CSS-tricks article: http://css-tricks.com/specifics-on-css-specificity/
- A tool (incomplete) that counts it: http://specificity.keegan.st/
Examples
// Universal * => 0,0,0,0 // ID #id => 0,1,0,0 // Class .class => 0,0,1,0 // Attributes [foo] => 0,0,1,0 [foo="bar"] => 0,0,1,0 [foo~="bar"] => 0,0,1,0 [foo^="bar"] => 0,0,1,0 [foo$="bar"] => 0,0,1,0 [foo*="bar"] => 0,0,1,0 [foo|="bar"] => 0,0,1,0 [ foo = bar ] => 0,0,1,0 [foo = 'bar'] => 0,0,1,0 (NB: brackets [] can contain anything except an unescaped "]") // Pseudo-classes :root => 0,0,1,0 :nth-child(n) => 0,0,1,0 :nth-last-child(n) => 0,0,1,0 :nth-of-type(n) => 0,0,1,0 :nth-last-of-type(n) => 0,0,1,0 :first-child => 0,0,1,0 :last-child => 0,0,1,0 :first-of-type => 0,0,1,0 :last-of-type => 0,0,1,0 :only-child => 0,0,1,0 :only-of-type => 0,0,1,0 :empty => 0,0,1,0 :link => 0,0,1,0 :visited => 0,0,1,0 :active => 0,0,1,0 :hover => 0,0,1,0 :focus => 0,0,1,0 :target => 0,0,1,0 :lang(fr) => 0,0,1,0 :enabled => 0,0,1,0 :disabled => 0,0,1,0 :checked => 0,0,1,0 :not(selector) => 0,0,1,0 (NB: the keyword after ":" can be anything except a pseudo-element) // Elements body => 0,0,0,1 // Pseudo-elements :before => 0,0,0,1 :after => 0,0,0,1 ::before => 0,0,0,1 ::after => 0,0,0,1 ::first-line => 0,0,0,1 ::first-letter => 0,0,0,1 (NB: parenthesis () can contain anything except an unescaped ")" )
(to be continued)
If you have questions or need examples or test data, please ask in the comments.
Shortest code (in bytes) wins.
Good luck!
1
would be great. (Btw, I think this is actually a pretty good challenge, but an exhaustive list of test cases seems vital to make it work well.) \$\endgroup\$