Skip to content

Commit

Permalink
Merge e6dbda6 into 0bc82fa
Browse files Browse the repository at this point in the history
  • Loading branch information
strarsis committed Mar 23, 2017
2 parents 0bc82fa + e6dbda6 commit 2fba26c
Show file tree
Hide file tree
Showing 25 changed files with 1,183 additions and 4 deletions.
1 change: 1 addition & 0 deletions .svgo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ plugins:
- removeXMLNS
- removeEditorsNSData
- cleanupAttrs
- inlineStyles
- minifyStyles
- convertStyleToAttrs
- cleanupIDs
Expand Down
98 changes: 98 additions & 0 deletions docs/how-it-works/en.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,104 @@ And of course, writing plugins would not have been so cool without some sugar AP
* @param {Object} [context] callback context
* @return {Boolean} false if there are no any attributes


##### querySelectorAll(selectors)
* Evaluate a string of CSS selectors against the element and returns matched elements
* @param {String} selectors CSS selector(s) string
* @return {Array} null if no elements matched

##### querySelector(selectors)
* Evaluate a string of CSS selectors against the element and returns only the first matched element
* @param {String} selectors CSS selector(s) string
* @return {Array} null if no element matched

##### matches(selector)
* Test if a selector matches a given element
* @param {String} selector CSS selector string
* @return {Boolean} true if element would be selected by selector string, false if it does not


##### style.getCssText()
* Get the textual representation of the declaration block (equivalent to .cssText attribute).
* @return {String} Textual representation of the declaration block (empty string for no properties)

##### style.getPropertyPriority(propertyName)
* Return the optional priority, "important".
* @param {String} propertyName representing the property name to be checked.
* @return {String} priority that represents the priority (e.g. "important") if one exists. If none exists, returns the empty string.

##### style.getPropertyValue(propertyName)
* Return the property value given a property name.
* @param {String} propertyName representing the property name to be checked.
* @return {String} value containing the value of the property. If not set, returns the empty string.

##### style.item(index)
* Return a property name.
* @param {Number} index of the node to be fetched. The index is zero-based.
* @return {String} propertyName that is the name of the CSS property at the specified index.

##### style.getProperties()
* Return all properties of the node.
* @return {Map} properties that is a Map with propertyName as key and property (propertyValue + propertyPriority) as value.

##### style.removeProperty(propertyName)
* Remove a property from the CSS declaration block.
* @param {String} propertyName representing the property name to be removed.
* @return {String} oldValue equal to the value of the CSS property before it was removed.

##### style.setProperty(propertyName, value, priority)
* Modify an existing CSS property or creates a new CSS property in the declaration block.
* @param {String} propertyName representing the CSS property name to be modified.
* @param {String} [value] containing the new property value. If not specified, treated as the empty string. value must not contain "!important" -- that should be set using the priority parameter.
* @param {String} [priority] allowing the "important" CSS priority to be set. If not specified, treated as the empty string.
* @return {undefined}


##### css-tools.flattenToSelectors(cssAst)
* Flatten a CSS AST to a selectors list.
* @param {Object} CSS AST to flatten
* @return {Array} selectors

##### css-tools.filterByMqs(selectors, useMqs)
* Filter selectors by Media Query.
* @param {Array} Selectors to filter
* @param {Array} Strings of media queries that should pass (<name> <expression>)
* @return {Array} Filtered selectors that match the passed media queries

##### css-tools.filterByPseudos(selectors, useMqs)
* Filter selectors by the pseudo-elements and/or -classes they contain.
* @param {Array} Selectors to filter
* @param {Array} 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

##### css-tools.cleanPseudos(selectors)
* 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

##### css-tools.compareSpecificity(aSpecificity, bSpecificity)
* Compare two selector specificities.
* @param {Array} Specificity of selector A
* @param {Array} Specificity of selector B
* @return {Number} Score of selector specificity A compared to selector specificity B

##### css-tools.compareSimpleSelectorNode(aSimpleSelectorNode, bSimpleSelectorNode)
* Compare two simple selectors.
* @param {Object} Simple selector A
* @param {Object} Simple selector B
* @return {Number} Score of selector A compared to selector B

##### css-tools.sortSelectors(selectors)
* Sort selectors stably by their specificity.
* @param {Array} Selectors to be sorted
* @return {Array} Stable sorted selectors

##### css-tools.cssoToStyleDeclaration(cssoDeclaration)
* Convert a CSSO AST style declaration to CSSStyleDeclaration property.
* @param {Object} CSSO style declaration
* @return {Object} CSSStyleDeclaration property


#### 3.3 tests

There is nothing easier than testing your plugin:
Expand Down
181 changes: 181 additions & 0 deletions lib/css-tools.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
'use strict';

var csstree = require('css-tree'),
stable = require('stable'),
specificity = require('csso/lib/restructure/prepare/specificity'),
List = require('css-tree/lib/utils/list');


/**
* Flatten a CSS AST to a selectors list.
*
* @param {Object} cssAst CSSO AST to flatten
* @return {Array} selectors
*/
function flattenToSelectors(cssAst) {
var selectors = [];

csstree.walkRules(cssAst, function(node) {
if (node.type !== 'Rule') {
return;
}

var atrule = this.atrule;
var rule = node;

node.selector.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('') > -1;
}

var mqName = selector.atrule.name;
var mqStr = mqName;
if(selector.atrule.expression.type === 'MediaQueryList') {
var mqExpr = csstree.translate(selector.atrule.expression);
mqStr = [mqName, mqExpr].join(' ');
}

return useMqs.indexOf(mqStr) > -1;
});
}

/**
* 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.translate({
type: 'Selector',
children: new List().fromArray(selector.pseudos.map(function(pseudo) {
return pseudo.item.data;
}))
});
return usePseudos.indexOf(pseudoSelectorsStr) > -1;
});
}

/**
* 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 CSSO AST style declaration to CSSStyleDeclaration property.
*
* @param {Object} cssoDeclaration CSSO style declaration
* @return {Object} CSSStyleDeclaration property
*/
function cssoToStyleDeclaration(cssoDeclaration) {
var propertyName = cssoDeclaration.property,
propertyValue = csstree.translate(cssoDeclaration.value),
propertyPriority = (cssoDeclaration.important ? 'important' : '');
return { name: propertyName, value: propertyValue, priority: propertyPriority };
}


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.cssoToStyleDeclaration = cssoToStyleDeclaration;
53 changes: 53 additions & 0 deletions lib/svgo/css-select-adapter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
'use strict';

var baseCssAdapter = require('css-select-base-adapter');

/**
* DOMUtils API for SVGO AST (used by css-select)
*/
var svgoCssSelectAdapterMin = {

// is the node a tag?
// isTag: ( node:Node ) => isTag:Boolean
isTag: function(node) {
return node.isElem();
},

// get the parent of the node
// getParent: ( node:Node ) => parentNode:Node
// returns null when no parent exists
getParent: function(node) {
return node.parentNode || null;
},

// get the node's children
// getChildren: ( node:Node ) => children:[Node]
getChildren: function(node) {
return node.content || [];
},

// get the name of the tag
// getName: ( elem:ElementNode ) => tagName:String
getName: function(elemAst) {
return elemAst.elem;
},

// get the text content of the node, and its children if it has any
// getText: ( node:Node ) => text:String
// returns empty string when there is no text
getText: function(node) {
return node.content[0].text || node.content[0].cdata || '';
},

// get the attribute value
// getAttributeValue: ( elem:ElementNode, name:String ) => value:String
// returns null when attribute doesn't exist
getAttributeValue: function(elem, name) {
return elem.hasAttr(name) ? elem.attr(name).value : null;
}
};

// use base adapter for default implementation
var svgoCssSelectAdapter = baseCssAdapter(svgoCssSelectAdapterMin);

module.exports = svgoCssSelectAdapter;
Loading

0 comments on commit 2fba26c

Please sign in to comment.