8
\$\begingroup\$

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!

\$\endgroup\$
  • 3
    \$\begingroup\$ Please add examples (ideally covering all the quirks in the notes). \$\endgroup\$ – Martin Ender Nov 19 '14 at 14:11
  • 2
    \$\begingroup\$ The 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.) \$\endgroup\$ – Martin Ender Nov 20 '14 at 16:26
1
\$\begingroup\$

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.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.