Efficient Algorithm To Compare Specificity Of CSS Rules
Asked Answered
A

1

7

I was wondering what an efficient algorithm would be in the following scenario:

Given a parsed set of css rules, eg.

     p.pStyle{margin-bottom:20px;font-family:Arial;}
     p{font-family:Verdana;}
     p.anotherPStyle{margin-bottom:10px;}

from a css stylesheet, it is possible that several rule sets apply to a given element (say a <p class="pStyle anotherPStyle">hello</p> in my document).

I need to determine what rules in the stylesheet apply to a given element firstly (so here that is p, pStyle and anotherPStyle), and then create a Comparator that is able to sort the applicable rules by specificity (from most-specific to most-general). NOTE: I already have designed an algorithm to apply the rules once sorted so you needn't solve that problem efficiently.

I've been toying with several ideas, namely one that involves determining the level in the DOM tree that a given rule is specific to....Though I'm not sure if this is the correct way to go?

How does the browser engine do this efficiently? I'm looking to replicate it in Java, but am comfortable with many other languages so any code you can offer is most appreciated.

Thanks

Antisthenes answered 5/10, 2012 at 9:33 Comment(4)
Are you asking how specificity is calculated?Longdrawn
That would be a good place to start, specifically the specificity of each rule in terms of a given element. Once that can be deduced then the sorting of them should be easy?Antisthenes
Specificity is based on selectors, and is calculated on a rule level. See my answer for more details.Longdrawn
After you know which selectors match, you calculate the specificity according to the standard(s): CSS 2.1: w3.org/TR/2009/CR-CSS2-20090908/cascade.html#specificity CSS 3 Selectors: w3.org/TR/css3-selectors/#specificitySerra
H
11

That is determined by specificity. In this case, since they are both equally specific, the declaration that comes last in the file, wins.

Specificity Calculation

Specificity is calculated by ranking the different parts of the selector.

Ranked from most specific to least:

  1. Style attribute - If the rule is found in a style attribute, this rank gets 1.
  2. ID - For each ID found in the selector, this rank gets an additional 1.
  3. Classes, Pseudo-Classes, Attribute selectors - For each one found in the selector, this rank gets an additional 1.
  4. Elements - For each element found in the selector, this rank gets an additional 1.

Where rank n > rank n+1, regardless of how many points each rank has.

Example

ul#nav li.active a

The points are:

  1. 0 - Not a style attribute.
  2. 1 - 1 ID found.
  3. 1 - 1 Classname found.
  4. 3 - 3 Elements found.

Therefore, each property in that selector has a specificity value of [0,0,1,1,3] (We'll get to that extra zero in a minute). That value is more specific than any selector, as long as it might be, without an ID, for example.

Comparison algorithm:

  1. Go from left to right on the ranks.
  2. Compare the ranks on both selectors.
  3. The rank with the higher amount of point, wins.
  4. If the ranks are equal, continue right to the next (less specific) rank.
  5. If all ranks are equal, the one which comes later in the CSS document, wins.

More important notes:

  • The universal selector (*) has no specificity value (0,0,0,0) Pseudo-elements (e.g. :first-line) get 0,0,0,1 unlike their
    pseudo-class brethren which get 0,0,1,0
  • The pseudo-class :not() adds no specificity by itself, only what's inside it's parentheses.
  • The !important directive can be applied on a single declaration, and adds a point to a "0th" rank, which is more specific than anything
    else. So in the example above, adding !important on any rule will
    bump the specificity value for that rule only to [1,0,1,1,2],
    granting it an instant win over any other rules without !important.

Extra Reference

See this great article on the subject


How to determine which styles go to what element

The way the browser does it, is to go over the selector from right to left, and filtering elements out of the DOM as they go.

Going back to the previous example:

    ul#nav li.active a

The browser does the following:

  1. Take an a element.
  2. Now check if it has an ancestor that is a li element with an .active class (this is through the descendant combinator: ancestor descendant).
  3. Now check if it has a higher ancestor that is a ul with an ID of #nav (again, the descendant combinator is used).

If all these conditions are met for a certain element, then the styles are applied to it.

You can read it:

Select any a element
with an ancestor with a class of .active, which is also a li,
which in turn has an ancestor with an ID of #nav, which is also a ul.

You'll need to have a fully function and complete DOM tree to be able to successfully determine which element has what CSS styles.

Hellenize answered 5/10, 2012 at 9:47 Comment(8)
That's an excellent jumping off point for me, thank you very much. Once you have calculated the points for each rule, how for a given element do you see which rules apply to it efficiently? Have you a resource you could add to your answer that would help with that part too? Thanks again for the help, it makes things much clearerAntisthenes
!important is on a declaration level, not an entire ruleset. Since as you say specificity is calculated on a rule level, !important doesn't bump the specificity for the entire rule, just that declaration. And that's where it gets confusing, because specificity is really more of a selector concept than a ruleset concept.Handwoven
And once again, right-to-left is not on a simple selector level! That the class or ID is matched before the type is a coincidence and has nothing to do with right-to-left.Handwoven
@BoltClock: I didn't get that last comment. What do you mean?Longdrawn
Each selector consists of two parts: compound selectors and combinators. Each compound selector is a sequence of simple selectors. Right-to-left evaluation steps through each compound selector, but doesn't necessarily drill down to the simple selector level. For instance, some browsers have funky optimizations that completely do away with the need to check class/ID selectors (last paragraph + comments in this answer).Handwoven
@BoltClock: Could you edit my answer to add that? I'm not sure how to explain it well.Longdrawn
What's the problem in reading the docs? w3.org/TR/css3-selectors/#specificity It's pretty well written and quite compact. Why so many words?Serra
Thanks @Handwoven for contributing. Also, just to say I don't need to precisely emulate the optimizations of a browser, just achieve the same functionality overall. Thanks for your insight.Antisthenes

© 2022 - 2024 — McMap. All rights reserved.