Skip to content

Instantly share code, notes, and snippets.

@subzey
Forked from 140bytes/LICENSE.txt
Created March 22, 2012 22:32
Show Gist options
  • Save subzey/2165141 to your computer and use it in GitHub Desktop.
Save subzey/2165141 to your computer and use it in GitHub Desktop.
CSS selector specificity

CSS selector specificity

Calculates CSS selector specificity (rule priority).

Argument: (string) valid CSS selector. Return value: number. (Base used: 256)

The function is sadly too long, 177 bytes. I hope you help me to golf it down to tweet size.

Anyway, I'd be happy if someone use this code in educational purposes and learn something new about CSS "dark places". Or even use this function in the wild.

About 140byt.es

See the 140byt.es site for a showcase of entries (built itself using 140-byte entries!), and follow @140bytes on Twitter.

To learn about byte-saving hacks for your own code, or to contribute what you've learned, head to the wiki.

140byt.es is brought to you by Jed Schmidt, with help from Alex Kloss. It was inspired by work from Thomas Fuchs and Dustin Diaz.

function(
a /* input string */
){
a
/* Invoke `replace` method storing its name into `a`: */
[a='replace'](
/* Get rid of escaped chars and make sure we can use `\w` later:
replace dash, and anything that is not basic ASCII into alphanumeric
chars.
Range should be NUL (\x00) to DEL (\x7F) but it would be hard
to edit. Anyway these "omitted" chars makes selector syntax invalid. */
/\\.|-|[^ -~]/g
/* Omit 2nd arg. "undefined" is a perfectly well alphanumeric string */
)
/* We can safely match braces and quotes now. Replace again: */
[a](
/* :not() itself doesn't counts in specificity, but its contents does.
Chopping it off. Braces are left in string - that's ok, it will not be
matched by any of the following regexps if it doesn't preceeded by
pseudoclass.
Plus sign is placed in order to leave these braces separated
":foo:not(bar)" -> ":foo(bar)" may be occured otherwise.
At the same time erase single and double quoted strings.
Replace it into plus sign is safely enough. */
/:not\b|("|').*?\1/g,
'+'
)
/* No more strings (that may be confused with tag, class or id).
It's time for final replace. */
[a](
/* Pretty big and tangled regexp.
`(#)?\w+`
Matches "foo" and "#foo", i.e., tag name or id, and pass it
as 2nd argument.
If it's an id declaration, "#" also will be passed as 3rd argument.
`[.:]\w+(\s*\(.*?\))?`
Matches class or pseudoclass with optionally following whitespaces
and braces with anything within.
As expressions like ".:pseudo" and ".class(foo)" or ":..class"
may never be encountered in valid CSS selector, we can freely use one
regexp "chunk" for classes and pseudoclasses.
`\[.*?]`
Matches attribute selector. */
/((#)?\w+)|[.:]\w+(\s*\(.*?\))?|\[.*?]/g,
/* Function as a replacer.*/
function(
$, /* Unused argument. Named `$` as ECMA advices */
t, /* Contents of 1st brace pair. */
i /* Contents of 2nd brace pair. */
){
/* If id was matched, `i` is not `undefined`.
If id or tag was matched, `t` is not `undefined`. (That is,
if class, pseudoclass or attr was mathced, `t` is `undefined`).
Shift 1 by 16, 8 or 0 positions left depending on what
was matched and add result (0x10000, 0x100 or 0x1 respectively)
to `a`. (Remember? We have already set `a = 0`)
I hope, no one ever uses 256 class names in one selector. */
a += 1 << 8 * (i ? 2 : !t)
},
/* 3rd argument will be ignored by .replace
But it's the nice place to assign 0 to `a` */
a = 0
);
/* After all parse passes finished, return `a`. It is the specificity */
return a
}
function(a,b){a[b='replace'](/\\.|-|[^ -~]/g,a=0)[b](/:not\b|("|').*?\1/g,'+')[b](/(#)?\w+|(::?\w+(?:\s*\(.*?\))?|\[.*?\]|\.\w+)/g,function($,i,c){a+=i?65536:c?256:1});return a}
function(a,b){a[b='replace'](/\\.|-|[^ -~]/g,a=0)[b](/:not\b|("|').*?\1/g,'+')[b](/(#)?\w+|(::?\w+(?:\s*\(.*?\))?|\[.*?]|\.\w+)/g,function($,i,c){a+=1<<8*(i?2:!!c)});return a}
function(a,b){a[b='replace'](/\\.|-|[^ -~]/g,a=0)[b](/:not\b|("|').*?\1/g,'+')[b](/((#)?\w+)|::?\w+(\s*\(.*?\))?|\[.*?]|\.\w+/g,function($,t,i){a+=1<<8*(i?2:!t)});return a}
function(a,b){a[b='replace'](/\\.|-|[^ -~]/g,a=0)[b](/:not\b|("|').*?\1/g,'+')[b](/((#)?\w+)|[.:]:?\w+(\s*\(.*?\))?|\[.*?]/g,function($,t,i){a+=1<<8*(i?2:!t)});return a}
function(a,b){a[b='replace'](/\\.|-|[^ -~]/g,a=0)[b](/:not\b|("|').*?\1/g,'+')[b](/((#)?\w+)|[.:]+\w+(\s*\(.*?\))?|\[.*?]/g,function($,t,i){a+=1<<8*(i?2:!t)});return a}
function(a){a[a='replace'](/\\.|-|[^ -~]/g)[a](/:not\b|("|').*?\1/g,'+')[a](/((#)?\w+)|[.:]\w+(\s*\(.*?\))?|\[.*?]/g,function($,t,i){a+=1<<8*(i?2:!t)},a=0);return a}
function(a){a[a='replace'](/\\.|-|[^ -~]/g)[a](/:not\b|("|').*?\1/g,'+')[a](/((#)?\w+)|[.:]\w+(\s*\(.*?\))?|\[.*?]/g,function($,t,i){a+=1<<8*(i?2:!t)},a=0);return a}
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2012 subzey <subzey@gmail.com>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.
{
"name": "selectorSpecificity",
"description": "Calculates CSS selector specificity",
"keywords": [
"css",
"selector",
"specificity",
"rule",
"priority"
]
}
<!DOCTYPE html>
<html>
<head>
<style>
.escaped {
font-weight: bold;
background: #dddddd;
}
.id {
color: darkred;
}
.class,
.pseudoclass,
.attr {
color: orange;
}
.not {
color: gray;
}
.tag {
color: blue;
}
.id,
.class,
.pseudoclass,
.attr,
.not,
.tag {
border: solid black 1px;
padding: 1px;
display: inline-block;
}
</style>
</head>
<body>
<pre id="sample"><span class="tag">a</span><span class="id">#a</span><span class="attr">[href="])<span class="escaped">\"</span>weird"]</span> <span class="tag">div</span> + <span class="tag">h1</span> <span class="class">.♫♫♫♫</span><span class="class">.bar<span class="escaped">\:</span>baz</span> ~ <span class="pseudoclass">:foo</span><span class="not">:not(<span class="id">#id</span>)</span> *<span class="pseudoclass">:last-child</span> <span class="pseudoclass">:nth-of-type (2n+1)</span><span class="pseudoclass">::selection</span></pre>
<p>(2 id's, 7 classes, 3 tags)</p>
<div>Expected value: <b>20703</b></div>
<div>Actual value: <b id="ret"></b></div>
<script type="text/javascript">
var selectorSpecificity = function(a){a[a='replace'](/\\.|-|[^ -~]/g)[a](/:not\b|("|').*?\1/g,'+')[a](/((#)?\w+)|[.:]\w+(\s*\(.*?\))?|\[.*?]/g,function($,t,i){a+=1<<8*(i?2:!t)},a=0);return a}
var str = document.getElementById("sample").innerText || document.getElementById("sample").textContent;
document.getElementById("ret").innerHTML = selectorSpecificity(str).toString(16);
</script>
</body>
</html>
@tsaniel
Copy link

tsaniel commented Mar 23, 2012

It's just awesome.
Save 2 bytes by the way:
function(a,b){a[b='replace'](/\\.|-|[^ -~]/g,a=0)[b](/:not\b|("|').*?\1/g,'+')[b](/(#)?\w+|(::?\w+(?:\s*\(.*?\))?|\[.*?]|\.\w+)/g,function($,i,c){a+=1<<8*(i?2:!!c)});return a}

@subzey
Copy link
Author

subzey commented Mar 24, 2012

@tsaniel, Wow! I didn't know that not escaped ] in regexp works fine. Thanks!

@subzey
Copy link
Author

subzey commented Mar 24, 2012

Rearranged regexp and function arguments and saved 3 bytes.

UPD: Relying more on well-formedness of selector, func length reduced by 4 bytes more. Now it's 168 bytes

@atk
Copy link

atk commented Mar 26, 2012

I'm searching for a possiblity to put the :not into the final regex...

@subzey
Copy link
Author

subzey commented Apr 24, 2012

Updated gist. Now it's 165 bytes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment