URI.js
9 years ago
URI.min.js
9 years ago
css.js
9 years ago
css.min.js
9 years ago
csslint.js
9 years ago
csslint.min.js
9 years ago
editor.js
9 years ago
editor.min.js
9 years ago
inspector.js
9 years ago
inspector.min.js
9 years ago
jquery.sizes.js
11 years ago
jquery.sizes.min.js
9 years ago
specificity.js
11 years ago
specificity.min.js
9 years ago
specificity.js
141 lines
| 1 | /** |
| 2 | * Calculates the specificity of CSS selectors |
| 3 | * https://github.com/keeganstreet/specificity - licensed under MIT. |
| 4 | * |
| 5 | * Returns an array of objects with the following properties: |
| 6 | * - selector: the input |
| 7 | * - specificity: e.g. 0,1,0,0 |
| 8 | * - parts: array with details about each part of the selector that counts towards the specificity |
| 9 | */ |
| 10 | var SPECIFICITY = (function() { |
| 11 | var calculate, |
| 12 | calculateSingle; |
| 13 | |
| 14 | calculate = function(input) { |
| 15 | var selectors, |
| 16 | selector, |
| 17 | i, |
| 18 | len, |
| 19 | results = []; |
| 20 | |
| 21 | // Separate input by commas |
| 22 | selectors = input.split(','); |
| 23 | |
| 24 | for (i = 0, len = selectors.length; i < len; i += 1) { |
| 25 | selector = selectors[i]; |
| 26 | if (selector.length > 0) { |
| 27 | results.push(calculateSingle(selector)); |
| 28 | } |
| 29 | } |
| 30 | |
| 31 | return results; |
| 32 | }; |
| 33 | |
| 34 | // Calculate the specificity for a selector by dividing it into simple selectors and counting them |
| 35 | calculateSingle = function(input) { |
| 36 | var selector = input, |
| 37 | findMatch, |
| 38 | typeCount = { |
| 39 | 'a': 0, |
| 40 | 'b': 0, |
| 41 | 'c': 0 |
| 42 | }, |
| 43 | parts = [], |
| 44 | // The following regular expressions assume that selectors matching the preceding regular expressions have been removed |
| 45 | attributeRegex = /(\[[^\]]+\])/g, |
| 46 | idRegex = /(#[^\s\+>~\.\[:]+)/g, |
| 47 | classRegex = /(\.[^\s\+>~\.\[:]+)/g, |
| 48 | pseudoElementRegex = /(::[^\s\+>~\.\[:]+|:first-line|:first-letter|:before|:after)/gi, |
| 49 | // A regex for pseudo classes with brackets - :nth-child(), :nth-last-child(), :nth-of-type(), :nth-last-type(), :lang() |
| 50 | pseudoClassWithBracketsRegex = /(:[\w-]+\([^\)]*\))/gi, |
| 51 | // A regex for other pseudo classes, which don't have brackets |
| 52 | pseudoClassRegex = /(:[^\s\+>~\.\[:]+)/g, |
| 53 | elementRegex = /([^\s\+>~\.\[:]+)/g; |
| 54 | |
| 55 | // Find matches for a regular expression in a string and push their details to parts |
| 56 | // Type is "a" for IDs, "b" for classes, attributes and pseudo-classes and "c" for elements and pseudo-elements |
| 57 | findMatch = function(regex, type) { |
| 58 | var matches, i, len, match, index, length; |
| 59 | if (regex.test(selector)) { |
| 60 | matches = selector.match(regex); |
| 61 | for (i = 0, len = matches.length; i < len; i += 1) { |
| 62 | typeCount[type] += 1; |
| 63 | match = matches[i]; |
| 64 | index = selector.indexOf(match); |
| 65 | length = match.length; |
| 66 | parts.push({ |
| 67 | selector: match, |
| 68 | type: type, |
| 69 | index: index, |
| 70 | length: length |
| 71 | }); |
| 72 | // Replace this simple selector with whitespace so it won't be counted in further simple selectors |
| 73 | selector = selector.replace(match, Array(length + 1).join(' ')); |
| 74 | } |
| 75 | } |
| 76 | }; |
| 77 | |
| 78 | // Remove the negation psuedo-class (:not) but leave its argument because specificity is calculated on its argument |
| 79 | (function() { |
| 80 | var regex = /:not\(([^\)]*)\)/g; |
| 81 | if (regex.test(selector)) { |
| 82 | selector = selector.replace(regex, ' $1 '); |
| 83 | } |
| 84 | }()); |
| 85 | |
| 86 | // Remove anything after a left brace in case a user has pasted in a rule, not just a selector |
| 87 | (function() { |
| 88 | var regex = /{[^]*/gm, |
| 89 | matches, i, len, match; |
| 90 | if (regex.test(selector)) { |
| 91 | matches = selector.match(regex); |
| 92 | for (i = 0, len = matches.length; i < len; i += 1) { |
| 93 | match = matches[i]; |
| 94 | selector = selector.replace(match, Array(match.length + 1).join(' ')); |
| 95 | } |
| 96 | } |
| 97 | }()); |
| 98 | |
| 99 | // Add attribute selectors to parts collection (type b) |
| 100 | findMatch(attributeRegex, 'b'); |
| 101 | |
| 102 | // Add ID selectors to parts collection (type a) |
| 103 | findMatch(idRegex, 'a'); |
| 104 | |
| 105 | // Add class selectors to parts collection (type b) |
| 106 | findMatch(classRegex, 'b'); |
| 107 | |
| 108 | // Add pseudo-element selectors to parts collection (type c) |
| 109 | findMatch(pseudoElementRegex, 'c'); |
| 110 | |
| 111 | // Add pseudo-class selectors to parts collection (type b) |
| 112 | findMatch(pseudoClassWithBracketsRegex, 'b'); |
| 113 | findMatch(pseudoClassRegex, 'b'); |
| 114 | |
| 115 | // Remove universal selector and separator characters |
| 116 | selector = selector.replace(/[\*\s\+>~]/g, ' '); |
| 117 | |
| 118 | // Remove any stray dots or hashes which aren't attached to words |
| 119 | // These may be present if the user is live-editing this selector |
| 120 | selector = selector.replace(/[#\.]/g, ' '); |
| 121 | |
| 122 | // The only things left should be element selectors (type c) |
| 123 | findMatch(elementRegex, 'c'); |
| 124 | |
| 125 | // Order the parts in the order they appear in the original selector |
| 126 | // This is neater for external apps to deal with |
| 127 | parts.sort(function(a, b) { |
| 128 | return a.index - b.index; |
| 129 | }); |
| 130 | |
| 131 | return { |
| 132 | selector: input, |
| 133 | specificity: '0,' + typeCount.a.toString() + ',' + typeCount.b.toString() + ',' + typeCount.c.toString(), |
| 134 | parts: parts |
| 135 | }; |
| 136 | }; |
| 137 | |
| 138 | return { |
| 139 | calculate: calculate |
| 140 | }; |
| 141 | }()); |