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!

BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
Abs
  • 56,052
  • 101
  • 275
  • 409
  • For JavaScript, I set up a starting point in pseudo-code here: [Sorting a set of CSS selectors on the basis of specificity](http://stackoverflow.com/questions/5158631/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! – BoltClock Jun 24 '12 at 22:06

2 Answers2

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.

hakre
  • 193,403
  • 52
  • 435
  • 836
  • Ok I managed to cobble something together I found online. How does this look to you? https://gist.github.com/2719627 - I think it follows all the rules you mentioned for specificity. – Abs May 17 '12 at 15:26
  • @Abs: I could look over it, see my fork: https://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. – hakre May 18 '12 at 10:24
  • 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. – Abs May 18 '12 at 12:21
  • @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. – hakre May 18 '12 at 12:25
  • And keep your gist updated, then we can further improve it, you can even fork my gist again I think. – hakre May 18 '12 at 12:26
  • 1
    The following code might be interesting for you, too. It's regex based CSS parsing in an exisiting component: http://pear.php.net/package/HTML_CSS/docs/1.5.4/__filesource/fsource_HTML_CSS__HTML_CSS-1.5.4CSS.php.html#a1860 – hakre May 18 '12 at 22:57
  • 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: https://gist.github.com/2730880 – Abs May 19 '12 at 13:42
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.