Order CSS based on Selector Specificity
Asked Answered
I

2

6

I have parsed a given CSS file/string into a JSON object like so:

{
    "#header": {
        "color": "#000000"
    },
    "#header h1": {
        "color": "#000"
    },
    "h1": {
        "color": "#4fb6e5"
    }
}

What I want to do now is re-order them based on Specificty. In this case, the #header h1 should come after the h1 in the JSON object as this is how they'll be applied in the browser.

How can I do this? Are there any existing libraries for this? Or any useful libraries to help with this?

I can use both Javascript/jQuery or PHP to do this. I'm looking for implementation advice and hopefully this has already been done!

Ian answered 17/5, 2012 at 13:8 Comment(1)
For JavaScript, I set up a starting point in pseudo-code here: Sorting a set of CSS selectors on the basis of specificity but there's no implementation yet. If you've not written a JS library to do this yet, I suppose I could take up the challenge!Trapshooting
U
3

Short answers:

Are there any existing libraries for this?

No, not one I'm aware of that does this "out-of-the-box" for you.

Or any useful libraries to help with this?

Yes, there is json_decode and uksort:

$specificity = function($selector) {

    /*
     * @link http://www.w3.org/TR/selectors/#specificity
     *
     * 9. Calculating a selector's specificity
     *
     * A selector's specificity is calculated as follows:
     * 
     *  * count the number of ID selectors in the selector (= a)
     *  * count the number of class selectors, attributes selectors, and 
     *    pseudo-classes in the selector (= b)
     *  * count the number of type selectors and pseudo-elements in the 
     *    selector (= c)
     *  * ignore the universal selector
     *
     * Selectors inside the negation pseudo-class are counted like any other, 
     * but the negation itself does not count as a pseudo-class.
     *
     * Concatenating the three numbers a-b-c (in a number system with a large 
     * base) gives the specificity.
     */
    ...
    return (int) $result;
}

$compare = function($a, $b) use ($specificity) {
    return $specificity($a) - $specificity($b)
};

$array = json_decode('{"yours" : "json"}', true);

uksort($array, $compare);

echo json_encode((object) $array);

As this code example shows, it only explains how to calculate the specificity in a comment and it does not contain the code. That's just because I don't have that code at hand, but I have put in there the specification how this is done (at least for CSS 3).

If you're looking for a CSS selector parser, I know about XDOM (because I wrote it). It's available on github: https://github.com/hakre/XDOM - It's a 100% CSS 3 compatible CSS selector parser.

To my best knowledge that is most of what you get as of today in terms of ready-made solutions. All other CSS selector parsers I know of are not compatible with the CSS 3 standard in full because they don't follow the W3C specification. Which might be good for you: If you don't need strict CSS3 compatbility, you might find some other code-chunks that suit your needs already.

Unmeaning answered 17/5, 2012 at 13:21 Comment(7)
Ok I managed to cobble something together I found online. How does this look to you? gist.github.com/2719627 - I think it follows all the rules you mentioned for specificity.Ian
@Abs: I could look over it, see my fork: gist.github.com/2724460 - I've left some comments in there and you can see some changes which might be useful. Generally you need to add tests to that function, otherwise I'd say it looks very fragile. Some test-input values should make it work much better.Unmeaning
thank you for taking a look at it, the return is in the wrong place! Fixing that! Also I reset the score because I use this function with one set of selectors at a time, I think that should be fine.Ian
@Abs: Make the function to work on one selector at a time only. You can then easily use it on multiple ones with array_map or foreach . Just write the function for the specificity and integrate it later on into your processing.Unmeaning
And keep your gist updated, then we can further improve it, you can even fork my gist again I think.Unmeaning
The following code might be interesting for you, too. It's regex based CSS parsing in an exisiting component: pear.php.net/package/HTML_CSS/docs/1.5.4/__filesource/…Unmeaning
Thanks for that, this is basically the final code I am using. Not much change as it seems to work really well. Needs some unit testing though: gist.github.com/2730880Ian
K
0

Actually there is a standard algorithm for ordering by specificity, I think you might want to look at that.

CSS Specificity

Also can be useful some sort of heuristic formula, for example check if there is a leading # or count the amount of spaces and dots.

Knickerbocker answered 17/5, 2012 at 13:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.