10
\$\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\$
2
  • 4
    \$\begingroup\$ Please add examples (ideally covering all the quirks in the notes). \$\endgroup\$ Commented Nov 19, 2014 at 14:11
  • 3
    \$\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\$ Commented Nov 20, 2014 at 16:26

2 Answers 2

2
\$\begingroup\$

JavaScript ES6 453 430 bytes

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\$
0
\$\begingroup\$

Python3, 226 bytes:

lambda s:[0,(c:=(x:=sub('(?<=\:not)\(.*?\)|\\.*?\\\\|(?<=\[).*?(?=\])','',s)).count)('#'),c('.')+c('[')+sub('::|(?:\:after)|(?:\:before)','',x).count(':'),len(findall('^\w+|[\s\>\+,]\w+|::|\:after|:before',x))]
from re import*

The test cases included in the link below are derived from the sample selectors in the original question along with the most complex selectors from Code Golf's own source.

Try it online!

\$\endgroup\$

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