Skip to content

Commit

Permalink
Add prefixIds plugin (#700)
Browse files Browse the repository at this point in the history
Add prefixIds plugin
  • Loading branch information
strarsis authored and GreLI committed Oct 23, 2017
1 parent d828a62 commit 91d9810
Show file tree
Hide file tree
Showing 13 changed files with 315 additions and 22 deletions.
1 change: 1 addition & 0 deletions .svgo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ plugins:
- minifyStyles
- convertStyleToAttrs
- cleanupIDs
- prefixIds
- removeRasterImages
- removeUselessDefs
- cleanupNumericValues
Expand Down
2 changes: 1 addition & 1 deletion examples/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ FS.readFile(filepath, 'utf8', function(err, data) {
throw err;
}

svgo.optimize(data).then(function(result) {
svgo.optimize(data, {path: filepath}).then(function(result) {

console.log(result);

Expand Down
10 changes: 5 additions & 5 deletions lib/svgo.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ var SVGO = module.exports = function(config) {
this.config = CONFIG(config);
};

SVGO.prototype.optimize = function(svgstr) {
SVGO.prototype.optimize = function(svgstr, info) {
return new Promise((resolve, reject) => {
if (this.config.error) {
reject(this.config.error);
Expand All @@ -40,7 +40,7 @@ SVGO.prototype.optimize = function(svgstr) {

if (++counter < maxPassCount && svgjs.data.length < prevResultSize) {
prevResultSize = svgjs.data.length;
this._optimizeOnce(svgjs.data, optimizeOnceCallback);
this._optimizeOnce(svgjs.data, info, optimizeOnceCallback);
} else {
if (config.datauri) {
svgjs.data = encodeSVGDatauri(svgjs.data, config.datauri);
Expand All @@ -49,11 +49,11 @@ SVGO.prototype.optimize = function(svgstr) {
}
};

this._optimizeOnce(svgstr, optimizeOnceCallback);
this._optimizeOnce(svgstr, info, optimizeOnceCallback);
});
};

SVGO.prototype._optimizeOnce = function(svgstr, callback) {
SVGO.prototype._optimizeOnce = function(svgstr, info, callback) {
var config = this.config;

SVG2JS(svgstr, function(svgjs) {
Expand All @@ -62,7 +62,7 @@ SVGO.prototype._optimizeOnce = function(svgstr, callback) {
return;
}

svgjs = PLUGINS(svgjs, config.plugins);
svgjs = PLUGINS(svgjs, info, config.plugins);

callback(JS2SVG(svgjs, config.js2svg));
});
Expand Down
12 changes: 6 additions & 6 deletions lib/svgo/coa.js
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ module.exports = require('coa').Cmd()

process.stdin
.on('data', chunk => data += chunk)
.once('end', () => processSVGData(config, data, file).then(resolve, reject));
.once('end', () => processSVGData(config, {input: 'string'}, data, file).then(resolve, reject));
});
// file
} else {
Expand All @@ -293,7 +293,7 @@ module.exports = require('coa').Cmd()
} else if (opts.string) {
var data = decodeSVGDatauri(opts.string);

return processSVGData(config, data, output[0]);
return processSVGData(config, {input: 'string'}, data, output[0]);
}
});

Expand Down Expand Up @@ -384,7 +384,7 @@ function processDirectory(config, dir, files, output) {
*/
function optimizeFile(config, file, output) {
return readFile(file, 'utf8').then(
data => processSVGData(config, data, output, file),
data => processSVGData(config, {input: 'file', path: file}, data, output, file),
error => checkOptimizeFileError(config, file, output, error)
);
}
Expand All @@ -397,11 +397,11 @@ function optimizeFile(config, file, output) {
* @param {string} [input] input file name (being used if output is a directory)
* @return {Promise}
*/
function processSVGData(config, data, output, input) {
function processSVGData(config, info, data, output, input) {
var startTime = Date.now(),
prevFileSize = Buffer.byteLength(data, 'utf8');

return svgo.optimize(data).then(function(result) {
return svgo.optimize(data, info).then(function(result) {
if (config.datauri) {
result.data = encodeSVGDatauri(result.data, config.datauri);
}
Expand Down Expand Up @@ -528,4 +528,4 @@ function printErrorAndExit(error) {
console.error(error);
process.exit(1);
return Promise.reject(error); // for tests
}
}
19 changes: 11 additions & 8 deletions lib/svgo/plugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,23 @@
* @module plugins
*
* @param {Object} data input data
* @param {Object} info extra information
* @param {Object} plugins plugins object from config
* @return {Object} output data
*/
module.exports = function(data, plugins) {
module.exports = function(data, info, plugins) {

plugins.forEach(function(group) {

switch(group[0].type) {
case 'perItem':
data = perItem(data, group);
data = perItem(data, info, group);
break;
case 'perItemReverse':
data = perItem(data, group, true);
data = perItem(data, info, group, true);
break;
case 'full':
data = full(data, group);
data = full(data, info, group);
break;
}

Expand All @@ -35,11 +36,12 @@ module.exports = function(data, plugins) {
* Direct or reverse per-item loop.
*
* @param {Object} data input data
* @param {Object} info extra information
* @param {Array} plugins plugins list to process
* @param {Boolean} [reverse] reverse pass?
* @return {Object} output data
*/
function perItem(data, plugins, reverse) {
function perItem(data, info, plugins, reverse) {

function monkeys(items) {

Expand All @@ -56,7 +58,7 @@ function perItem(data, plugins, reverse) {
for (var i = 0; filter && i < plugins.length; i++) {
var plugin = plugins[i];

if (plugin.active && plugin.fn(item, plugin.params) === false) {
if (plugin.active && plugin.fn(item, plugin.params, info) === false) {
filter = false;
}
}
Expand All @@ -82,14 +84,15 @@ function perItem(data, plugins, reverse) {
* "Full" plugins.
*
* @param {Object} data input data
* @param {Object} info extra information
* @param {Array} plugins plugins list to process
* @return {Object} output data
*/
function full(data, plugins) {
function full(data, info, plugins) {

plugins.forEach(function(plugin) {
if (plugin.active) {
data = plugin.fn(data, plugin.params);
data = plugin.fn(data, plugin.params, info);
}
});

Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@
"dependencies": {
"coa": "~2.0.0",
"colors": "~1.1.2",
"css-url-regex": "^1.1.0",
"unquote": "^1.1.0",
"mkdirp": "~0.5.1",
"css-select": "~1.3.0-rc0",
"css-select-base-adapter": "~0.1.0",
"css-tree": "~1.0.0-alpha25",
Expand Down
211 changes: 211 additions & 0 deletions plugins/prefixIds.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
'use strict';

exports.type = 'perItem';

exports.active = false;

exports.params = {
delim: '__'
};

exports.description = 'prefix IDs';


var path = require('path'),
csstree = require('css-tree'),
cssRx = require('css-url-regex')(),
unquote = require('unquote'),
collections = require('./_collections.js'),
referencesProps = collections.referencesProps,
rxId = /^#(.*)$/, // regular expression for matching an ID + extracing its name
addPrefix = null;


// Escapes a string for being used as ID
var escapeIdentifierName = function(str) {
return str.replace(/[\. ]/g, '_');
};

// Matches an #ID value, captures the ID name
var matchId = function(urlVal) {
var idUrlMatches = urlVal.match(rxId);
if (idUrlMatches === null) {
return false;
}
return idUrlMatches[1];
};

// Matches an url(...) value, captures the URL
var matchUrl = function(val) {
var urlMatches = cssRx.exec(val);
if (urlMatches === null) {
return false;
}
return urlMatches[1];
};

// Checks if attribute is empty
var attrNotEmpty = function(attr) {
return (attr && attr.value && attr.value.length > 0);
};

// prefixes an #ID
var prefixId = function(val) {
var idName = matchId(val);
if (!idName) {
return false;
}
return '#' + addPrefix(idName);
};


// attr.value helper methods

// prefixes a normal attribute value
var addPrefixToAttr = function(attr) {
if (!attrNotEmpty(attr)) {
return;
}

attr.value = addPrefix(attr.value);
};

// prefixes an ID attribute value
var addPrefixToIdAttr = function(attr) {
if (!attrNotEmpty(attr)) {
return;
}

var idPrefixed = prefixId(attr.value);
if (!idPrefixed) {
return;
}
attr.value = idPrefixed;
};

// prefixes an URL attribute value
var addPrefixToUrlAttr = function(attr) {
if (!attrNotEmpty(attr)) {
return;
}

// url(...) in value
var urlVal = matchUrl(attr.value);
if (!urlVal) {
return;
}

var idPrefixed = prefixId(urlVal);
if (!idPrefixed) {
return;
}

attr.value = 'url(' + idPrefixed + ')';
};


/**
* Prefixes identifiers
*
* @param {Object} node node
* @param {Object} opts plugin params
* @param {Object} extra plugin extra information
*
* @author strarsis <strarsis@gmail.com>
*/
exports.fn = function(node, opts, extra) {

// prefix, from file name or option
var prefix = 'prefix';
if (opts.prefix) {
if (typeof opts.prefix === 'function') {
prefix = opts.prefix(node, extra);
} else {
prefix = opts.prefix;
}
} else if (extra && extra.path && extra.path.length > 0) {
var filename = path.basename(extra.path);
prefix = filename;
}


// prefixes a normal value
addPrefix = function(name) {
return escapeIdentifierName(prefix + opts.delim + name);
};


// <style/> property values

if (node.elem === 'style') {
if (node.isEmpty()) {
// skip empty <style/>s
return node;
}

var cssStr = node.content[0].text || node.content[0].cdata || [];

var cssAst = {};
try {
cssAst = csstree.parse(cssStr, {
parseValue: true,
parseCustomProperty: false
});
} catch (parseError) {
console.warn('Warning: Parse error of styles of <style/> element, skipped. Error details: ' + parseError);
return node;
}

var idPrefixed = '';
csstree.walk(cssAst, function(node) {

// #ID, .class
if ((node.type === 'IdSelector' ||
node.type === 'ClassSelector') &&
node.name) {
node.name = addPrefix(node.name);
return;
}

// url(...) in value
if (node.type === 'Url' &&
node.value.value && node.value.value.length > 0) {
idPrefixed = prefixId(unquote(node.value.value));
if (!idPrefixed) {
return;
}
node.value.value = idPrefixed;
}

});

// update <style>s
node.content[0].text = csstree.translate(cssAst);
return node;
}


// element attributes

if (!node.attrs) {
return node;
}

// ID
addPrefixToAttr(node.attrs.id);
// Class
addPrefixToAttr(node.attrs.class);

// href
addPrefixToIdAttr(node.attrs.href);
// (xlink:)href (deprecated, must be still supported)
addPrefixToIdAttr(node.attrs['xlink:href']);

// referenceable properties
for (var referencesProp of referencesProps) {
addPrefixToUrlAttr(node.attrs[referencesProp]);
}


return node;
};
Loading

0 comments on commit 91d9810

Please sign in to comment.