Compute the specificity of a CSS selector

8

2

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:


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!

xem

Posted 2014-11-19T14:10:27.253

Reputation: 5 523

3Please add examples (ideally covering all the quirks in the notes). – Martin Ender – 2014-11-19T14:11:16.607

2The list of tests looks really good, but some examples that go beyond a single 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.) – Martin Ender – 2014-11-20T16:26:28.597

Answers

1

Javascript ES6 453 430 bytes

Here goes my shot!

a=>(s){var n=z=[],c=[0,0,0,0],e=s.split(/[\s\+\>\~]+/);return e.forEach((s)=>{n=n.concat(s.replace(/\((.*)\)/,"").split(/(?=\[|::|\.)/))}),n.forEach((s)=>{if(0==s.indexOf("::"))return z.push(s);var n=s.split(/(?=:)/);z=z.concat(n[0].split(/(?=[\#])/)),/before|after/.test(n[1])?z.push(":"+n[1]):n[1]&&z.push(n[1])}),z.forEach((s)=>{/^[a-z]+$/gi.test(s)||"::"==s[0]+s[1]?c[3]++:-1!=":.[".indexOf(s[0])?c[2]++:"#"==s[0]&&c[1]++}),c}

@UPDATE improved code, now it handles :not and :before/:after selectors better, nevertheless haven't tested CSS escape sequences.

josegomezr

Posted 2014-11-19T14:10:27.253

Reputation: 111