From 91d98106d4c75a947301e152ad97dc947990e9e2 Mon Sep 17 00:00:00 2001 From: strarsis Date: Mon, 23 Oct 2017 21:14:43 +0200 Subject: [PATCH] Add prefixIds plugin (#700) Add prefixIds plugin --- .svgo.yml | 1 + examples/test.js | 2 +- lib/svgo.js | 10 +- lib/svgo/coa.js | 12 +- lib/svgo/plugins.js | 19 +-- package.json | 3 + plugins/prefixIds.js | 211 ++++++++++++++++++++++++++++++++++ test/plugins/_index.js | 2 +- test/plugins/prefixIds.01.svg | 23 ++++ test/plugins/prefixIds.02.svg | 21 ++++ test/plugins/prefixIds.03.svg | 9 ++ test/plugins/prefixIds.04.svg | 22 ++++ test/svgo/_index.js | 2 +- 13 files changed, 315 insertions(+), 22 deletions(-) create mode 100644 plugins/prefixIds.js create mode 100644 test/plugins/prefixIds.01.svg create mode 100644 test/plugins/prefixIds.02.svg create mode 100644 test/plugins/prefixIds.03.svg create mode 100644 test/plugins/prefixIds.04.svg diff --git a/.svgo.yml b/.svgo.yml index 2be590d88..313bf7c68 100644 --- a/.svgo.yml +++ b/.svgo.yml @@ -27,6 +27,7 @@ plugins: - minifyStyles - convertStyleToAttrs - cleanupIDs + - prefixIds - removeRasterImages - removeUselessDefs - cleanupNumericValues diff --git a/examples/test.js b/examples/test.js index 6dca6ab38..fda337b42 100644 --- a/examples/test.js +++ b/examples/test.js @@ -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); diff --git a/lib/svgo.js b/lib/svgo.js index 5c33972bb..e448c0526 100644 --- a/lib/svgo.js +++ b/lib/svgo.js @@ -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); @@ -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); @@ -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) { @@ -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)); }); diff --git a/lib/svgo/coa.js b/lib/svgo/coa.js index 014cab303..73e133096 100644 --- a/lib/svgo/coa.js +++ b/lib/svgo/coa.js @@ -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 { @@ -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]); } }); @@ -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) ); } @@ -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); } @@ -528,4 +528,4 @@ function printErrorAndExit(error) { console.error(error); process.exit(1); return Promise.reject(error); // for tests -} +} \ No newline at end of file diff --git a/lib/svgo/plugins.js b/lib/svgo/plugins.js index 4e0308daf..a317f5b6c 100644 --- a/lib/svgo/plugins.js +++ b/lib/svgo/plugins.js @@ -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; } @@ -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) { @@ -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; } } @@ -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); } }); diff --git a/package.json b/package.json index 2e29d1d8d..e8bec50d7 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/plugins/prefixIds.js b/plugins/prefixIds.js new file mode 100644 index 000000000..ddda078fc --- /dev/null +++ b/plugins/prefixIds.js @@ -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 + */ +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); + }; + + + // + + + + +@@@ + + + + + + diff --git a/test/plugins/prefixIds.02.svg b/test/plugins/prefixIds.02.svg new file mode 100644 index 000000000..fe22dad7e --- /dev/null +++ b/test/plugins/prefixIds.02.svg @@ -0,0 +1,21 @@ + + + + + + + + + + +@@@ + + + + + + + + + + diff --git a/test/plugins/prefixIds.03.svg b/test/plugins/prefixIds.03.svg new file mode 100644 index 000000000..b87b25ece --- /dev/null +++ b/test/plugins/prefixIds.03.svg @@ -0,0 +1,9 @@ + + + + +@@@ + + + + diff --git a/test/plugins/prefixIds.04.svg b/test/plugins/prefixIds.04.svg new file mode 100644 index 000000000..d3d475597 --- /dev/null +++ b/test/plugins/prefixIds.04.svg @@ -0,0 +1,22 @@ + + + + + + +@@@ + + + + + + diff --git a/test/svgo/_index.js b/test/svgo/_index.js index 053f39980..327c5de72 100644 --- a/test/svgo/_index.js +++ b/test/svgo/_index.js @@ -30,7 +30,7 @@ describe('indentation', function() { js2svg : { pretty: true, indent: 2 } }); - svgo.optimize(orig).then(function(result) { + svgo.optimize(orig, {path: filepath}).then(function(result) { normalize(result.data).should.be.equal(should); done(); });