1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
|
/**
* @license
* Copyright The Closure Library Authors.
* SPDX-License-Identifier: Apache-2.0
*/
/** @fileoverview Calculator for specificity of CSS selectors. */
goog.module('goog.html.CssSpecificity');
goog.module.declareLegacyNamespace();
/**
* Cached mapping from selectors to specificities.
* @type {!Object<string, !Array<number>>}
*/
var specificityCache = {};
/**
* Calculates the specificity of CSS selectors, using a global cache if
* supported.
* @see http://www.w3.org/TR/css3-selectors/#specificity
* @see https://specificity.keegan.st/
* @param {string} selector The CSS selector.
* @return {!Array<number>} The CSS specificity.
* @supported IE9+, other browsers.
*/
function getSpecificity(selector) {
var specificity = specificityCache.hasOwnProperty(selector) ?
specificityCache[selector] :
null;
if (specificity) {
return specificity;
}
if (Object.keys(specificityCache).length > (1 << 16)) {
// Limit the size of cache to (1 << 16) == 65536. Normally HTML pages don't
// have such numbers of selectors.
specificityCache = {};
}
specificity = calculateSpecificity(selector);
specificityCache[selector] = specificity;
return specificity;
}
/**
* Find matches for a regular expression in the selector and increase count.
* @param {string} selector The selector to match the regex with.
* @param {!Array<number>} specificity The current specificity.
* @param {!RegExp} regex The regular expression.
* @param {number} typeIndex Index of type count.
* @return {string}
*/
function replaceWithEmptyText(selector, specificity, regex, typeIndex) {
return selector.replace(regex, function(match) {
specificity[typeIndex] += 1;
// Replace this simple selector with whitespace so it won't be counted
// in further simple selectors.
return Array(match.length + 1).join(' ');
});
}
/**
* Replace escaped characters with plain text, using the "A" character.
* @see https://www.w3.org/TR/CSS21/syndata.html#characters
* @param {string} selector
* @param {!RegExp} regex
* @return {string}
*/
function replaceWithPlainText(selector, regex) {
return selector.replace(regex, function(match) {
return Array(match.length + 1).join('A');
});
}
/**
* Calculates the specificity of CSS selectors
* @see http://www.w3.org/TR/css3-selectors/#specificity
* @see https://github.com/keeganstreet/specificity
* @see https://specificity.keegan.st/
* @param {string} selector
* @return {!Array<number>} The CSS specificity.
*/
function calculateSpecificity(selector) {
var specificity = [0, 0, 0, 0];
// Cannot use RegExp literals for all regular expressions, IE does not accept
// the syntax.
// Matches a backslash followed by six hexadecimal digits followed by an
// optional single whitespace character.
var escapeHexadecimalRegex = new RegExp('\\\\[0-9A-Fa-f]{6}\\s?', 'g');
// Matches a backslash followed by fewer than six hexadecimal digits
// followed by a mandatory single whitespace character.
var escapeHexadecimalRegex2 = new RegExp('\\\\[0-9A-Fa-f]{1,5}\\s', 'g');
// Matches a backslash followed by any character
var escapeSpecialCharacter = /\\./g;
selector = replaceWithPlainText(selector, escapeHexadecimalRegex);
selector = replaceWithPlainText(selector, escapeHexadecimalRegex2);
selector = replaceWithPlainText(selector, escapeSpecialCharacter);
// Remove the negation pseudo-class (:not) but leave its argument because
// specificity is calculated on its argument.
var pseudoClassWithNotRegex = new RegExp(':not\\(([^\\)]*)\\)', 'g');
selector = selector.replace(pseudoClassWithNotRegex, ' $1 ');
// Remove anything after a left brace in case a user has pasted in a rule,
// not just a selector.
var rulesRegex = new RegExp('{[^]*', 'gm');
selector = selector.replace(rulesRegex, '');
// The following regular expressions assume that selectors matching the
// preceding regular expressions have been removed.
// SPECIFICITY 2: Counts attribute selectors.
var attributeRegex = new RegExp('(\\[[^\\]]+\\])', 'g');
selector = replaceWithEmptyText(selector, specificity, attributeRegex, 2);
// SPECIFICITY 1: Counts ID selectors.
var idRegex = new RegExp('(#[^\\#\\s\\+>~\\.\\[:]+)', 'g');
selector = replaceWithEmptyText(selector, specificity, idRegex, 1);
// SPECIFICITY 2: Counts class selectors.
var classRegex = new RegExp('(\\.[^\\s\\+>~\\.\\[:]+)', 'g');
selector = replaceWithEmptyText(selector, specificity, classRegex, 2);
// SPECIFICITY 3: Counts pseudo-element selectors.
var pseudoElementRegex =
/(::[^\s\+>~\.\[:]+|:first-line|:first-letter|:before|:after)/gi;
selector = replaceWithEmptyText(selector, specificity, pseudoElementRegex, 3);
// SPECIFICITY 2: Counts pseudo-class selectors.
// A regex for pseudo classes with brackets. For example:
// :nth-child()
// :nth-last-child()
// :nth-of-type()
// :nth-last-type()
// :lang()
var pseudoClassWithBracketsRegex = /(:[\w-]+\([^\)]*\))/gi;
selector = replaceWithEmptyText(
selector, specificity, pseudoClassWithBracketsRegex, 2);
// A regex for other pseudo classes, which don't have brackets.
var pseudoClassRegex = /(:[^\s\+>~\.\[:]+)/g;
selector = replaceWithEmptyText(selector, specificity, pseudoClassRegex, 2);
// Remove universal selector and separator characters.
selector = selector.replace(/[\*\s\+>~]/g, ' ');
// Remove any stray dots or hashes which aren't attached to words.
// These may be present if the user is live-editing this selector.
selector = selector.replace(/[#\.]/g, ' ');
// SPECIFICITY 3: The only things left should be element selectors.
var elementRegex = /([^\s\+>~\.\[:]+)/g;
selector = replaceWithEmptyText(selector, specificity, elementRegex, 3);
return specificity;
}
exports = {
getSpecificity: getSpecificity
};
|