'use strict'; var parser = require('slick/parser'); module.exports = exports = Selector; /** * CSS selector constructor. * * @param {String} selector text * @param {Array} optionally, precalculated specificity * @api public */ function Selector(text, styleAttribute) { this.text = text; this.spec = undefined; this.styleAttribute = styleAttribute || false; } /** * Get parsed selector. * * @api public */ Selector.prototype.parsed = function() { if (!this.tokens) { this.tokens = parse(this.text); } return this.tokens; }; /** * Lazy specificity getter * * @api public */ Selector.prototype.specificity = function() { var styleAttribute = this.styleAttribute; if (!this.spec) { this.spec = specificity(this.text, this.parsed()); } return this.spec; function specificity(text, parsed) { var expressions = parsed || parse(text); var spec = [styleAttribute ? 1 : 0, 0, 0, 0]; var nots = []; for (var i = 0; i < expressions.length; i++) { var expression = expressions[i]; var pseudos = expression.pseudos; // id awards a point in the second column if (expression.id) { spec[1]++; } // classes and attributes award a point each in the third column if (expression.attributes) { spec[2] += expression.attributes.length; } if (expression.classList) { spec[2] += expression.classList.length; } // tag awards a point in the fourth column if (expression.tag && expression.tag !== '*') { spec[3]++; } // pseudos award a point each in the fourth column if (pseudos) { spec[3] += pseudos.length; for (var p = 0; p < pseudos.length; p++) { if (pseudos[p].name === 'not') { nots.push(pseudos[p].value); spec[3]--; } } } } for (var ii = nots.length; ii--;) { var not = specificity(nots[ii]); for (var jj = 4; jj--;) { spec[jj] += not[jj]; } } return spec; } }; /** * Parses a selector and returns the tokens. * * @param {String} selector * @api private. */ function parse(text) { try { return parser(text)[0]; } catch (e) { return []; } }