| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222 | '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;
 |