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