| 'use strict'; |
| |
| var csstree = require('css-tree'), |
| List = csstree.List, |
| stable = require('stable'), |
| specificity = require('csso/lib/restructure/prepare/specificity'); |
| |
| |
| /** |
| * Flatten a CSS AST to a selectors list. |
| * |
| * @param {Object} cssAst css-tree AST to flatten |
| * @return {Array} selectors |
| */ |
| function flattenToSelectors(cssAst) { |
| var selectors = []; |
| |
| csstree.walk(cssAst, {visit: 'Rule', enter: function(node) { |
| if (node.type !== 'Rule') { |
| return; |
| } |
| |
| var atrule = this.atrule; |
| var rule = node; |
| |
| node.prelude.children.each(function(selectorNode, selectorItem) { |
| var selector = { |
| item: selectorItem, |
| atrule: atrule, |
| rule: rule, |
| pseudos: [] |
| }; |
| |
| selectorNode.children.each(function(selectorChildNode, selectorChildItem, selectorChildList) { |
| if (selectorChildNode.type === 'PseudoClassSelector' || |
| selectorChildNode.type === 'PseudoElementSelector') { |
| selector.pseudos.push({ |
| item: selectorChildItem, |
| list: selectorChildList |
| }); |
| } |
| }); |
| |
| selectors.push(selector); |
| }); |
| }}); |
| |
| return selectors; |
| } |
| |
| /** |
| * Filter selectors by Media Query. |
| * |
| * @param {Array} selectors to filter |
| * @param {Array} useMqs Array with strings of media queries that should pass (<name> <expression>) |
| * @return {Array} Filtered selectors that match the passed media queries |
| */ |
| function filterByMqs(selectors, useMqs) { |
| return selectors.filter(function(selector) { |
| if (selector.atrule === null) { |
| return ~useMqs.indexOf(''); |
| } |
| |
| var mqName = selector.atrule.name; |
| var mqStr = mqName; |
| if (selector.atrule.expression && |
| selector.atrule.expression.children.first().type === 'MediaQueryList') { |
| var mqExpr = csstree.generate(selector.atrule.expression); |
| mqStr = [mqName, mqExpr].join(' '); |
| } |
| |
| return ~useMqs.indexOf(mqStr); |
| }); |
| } |
| |
| /** |
| * Filter selectors by the pseudo-elements and/or -classes they contain. |
| * |
| * @param {Array} selectors to filter |
| * @param {Array} usePseudos Array with strings of single or sequence of pseudo-elements and/or -classes that should pass |
| * @return {Array} Filtered selectors that match the passed pseudo-elements and/or -classes |
| */ |
| function filterByPseudos(selectors, usePseudos) { |
| return selectors.filter(function(selector) { |
| var pseudoSelectorsStr = csstree.generate({ |
| type: 'Selector', |
| children: new List().fromArray(selector.pseudos.map(function(pseudo) { |
| return pseudo.item.data; |
| })) |
| }); |
| return ~usePseudos.indexOf(pseudoSelectorsStr); |
| }); |
| } |
| |
| /** |
| * Remove pseudo-elements and/or -classes from the selectors for proper matching. |
| * |
| * @param {Array} selectors to clean |
| * @return {Array} Selectors without pseudo-elements and/or -classes |
| */ |
| function cleanPseudos(selectors) { |
| selectors.forEach(function(selector) { |
| selector.pseudos.forEach(function(pseudo) { |
| pseudo.list.remove(pseudo.item); |
| }); |
| }); |
| } |
| |
| |
| /** |
| * Compares two selector specificities. |
| * extracted from https://github.com/keeganstreet/specificity/blob/master/specificity.js#L211 |
| * |
| * @param {Array} aSpecificity Specificity of selector A |
| * @param {Array} bSpecificity Specificity of selector B |
| * @return {Number} Score of selector specificity A compared to selector specificity B |
| */ |
| function compareSpecificity(aSpecificity, bSpecificity) { |
| for (var i = 0; i < 4; i += 1) { |
| if (aSpecificity[i] < bSpecificity[i]) { |
| return -1; |
| } else if (aSpecificity[i] > bSpecificity[i]) { |
| return 1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| |
| /** |
| * Compare two simple selectors. |
| * |
| * @param {Object} aSimpleSelectorNode Simple selector A |
| * @param {Object} bSimpleSelectorNode Simple selector B |
| * @return {Number} Score of selector A compared to selector B |
| */ |
| function compareSimpleSelectorNode(aSimpleSelectorNode, bSimpleSelectorNode) { |
| var aSpecificity = specificity(aSimpleSelectorNode), |
| bSpecificity = specificity(bSimpleSelectorNode); |
| return compareSpecificity(aSpecificity, bSpecificity); |
| } |
| |
| function _bySelectorSpecificity(selectorA, selectorB) { |
| return compareSimpleSelectorNode(selectorA.item.data, selectorB.item.data); |
| } |
| |
| |
| /** |
| * Sort selectors stably by their specificity. |
| * |
| * @param {Array} selectors to be sorted |
| * @return {Array} Stable sorted selectors |
| */ |
| function sortSelectors(selectors) { |
| return stable(selectors, _bySelectorSpecificity); |
| } |
| |
| |
| /** |
| * Convert a css-tree AST style declaration to CSSStyleDeclaration property. |
| * |
| * @param {Object} declaration css-tree style declaration |
| * @return {Object} CSSStyleDeclaration property |
| */ |
| function csstreeToStyleDeclaration(declaration) { |
| var propertyName = declaration.property, |
| propertyValue = csstree.generate(declaration.value), |
| propertyPriority = (declaration.important ? 'important' : ''); |
| return { |
| name: propertyName, |
| value: propertyValue, |
| priority: propertyPriority |
| }; |
| } |
| |
| |
| /** |
| * Gets the CSS string of a style element |
| * |
| * @param {Object} element style element |
| * @return {String|Array} CSS string or empty array if no styles are set |
| */ |
| function getCssStr(elem) { |
| return elem.content[0].text || elem.content[0].cdata || []; |
| } |
| |
| /** |
| * Sets the CSS string of a style element |
| * |
| * @param {Object} element style element |
| * @param {String} CSS string to be set |
| * @return {Object} reference to field with CSS |
| */ |
| function setCssStr(elem, css) { |
| // in case of cdata field |
| if(elem.content[0].cdata) { |
| elem.content[0].cdata = css; |
| return elem.content[0].cdata; |
| } |
| |
| // in case of text field + if nothing was set yet |
| elem.content[0].text = css; |
| return elem.content[0].text; |
| } |
| |
| |
| module.exports.flattenToSelectors = flattenToSelectors; |
| |
| module.exports.filterByMqs = filterByMqs; |
| module.exports.filterByPseudos = filterByPseudos; |
| module.exports.cleanPseudos = cleanPseudos; |
| |
| module.exports.compareSpecificity = compareSpecificity; |
| module.exports.compareSimpleSelectorNode = compareSimpleSelectorNode; |
| |
| module.exports.sortSelectors = sortSelectors; |
| |
| module.exports.csstreeToStyleDeclaration = csstreeToStyleDeclaration; |
| |
| module.exports.getCssStr = getCssStr; |
| module.exports.setCssStr = setCssStr; |