diff --git a/lib/css-tools.js b/lib/css-tools.js index 6517892b..715f696c 100644 --- a/lib/css-tools.js +++ b/lib/css-tools.js @@ -1,10 +1,9 @@ 'use strict'; -var csstree = require('css-tree'), - List = csstree.List, - stable = require('stable'), - specificity = require('csso/lib/restructure/prepare/specificity'); - +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. @@ -13,39 +12,48 @@ var csstree = require('css-tree'), * @return {Array} selectors */ function flattenToSelectors(cssAst) { - var selectors = []; + var selectors = []; - csstree.walk(cssAst, {visit: 'Rule', enter: function(node) { - if (node.type !== 'Rule') { - return; - } + csstree.walk(cssAst, { + visit: 'Rule', + enter: function (node) { + if (node.type !== 'Rule') { + return; + } - var atrule = this.atrule; - var rule = node; + var atrule = this.atrule; + var rule = node; - node.prelude.children.each(function(selectorNode, selectorItem) { - var selector = { - item: selectorItem, - atrule: atrule, - rule: rule, - pseudos: [] - }; + 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 - }); - } + 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; + selectors.push(selector); + }); + }, + }); + + return selectors; } /** @@ -56,21 +64,23 @@ function flattenToSelectors(cssAst) { * @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(''); - } + 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(' '); - } + 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); - }); + return ~useMqs.indexOf(mqStr); + }); } /** @@ -81,15 +91,17 @@ function filterByMqs(selectors, useMqs) { * @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); + 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); + }); } /** @@ -99,14 +111,13 @@ function filterByPseudos(selectors, usePseudos) { * @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); - }); + 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 @@ -116,18 +127,17 @@ function cleanPseudos(selectors) { * @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; - } + 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; + return 0; } - /** * Compare two simple selectors. * @@ -136,16 +146,15 @@ function compareSpecificity(aSpecificity, bSpecificity) { * @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); + var aSpecificity = specificity(aSimpleSelectorNode), + bSpecificity = specificity(bSimpleSelectorNode); + return compareSpecificity(aSpecificity, bSpecificity); } function _bySelectorSpecificity(selectorA, selectorB) { - return compareSimpleSelectorNode(selectorA.item.data, selectorB.item.data); + return compareSimpleSelectorNode(selectorA.item.data, selectorB.item.data); } - /** * Sort selectors stably by their specificity. * @@ -153,10 +162,9 @@ function _bySelectorSpecificity(selectorA, selectorB) { * @return {Array} Stable sorted selectors */ function sortSelectors(selectors) { - return stable(selectors, _bySelectorSpecificity); + return stable(selectors, _bySelectorSpecificity); } - /** * Convert a css-tree AST style declaration to CSSStyleDeclaration property. * @@ -164,17 +172,16 @@ function sortSelectors(selectors) { * @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 - }; + 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 * @@ -182,7 +189,7 @@ function csstreeToStyleDeclaration(declaration) { * @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 || []; + return elem.content[0].text || elem.content[0].cdata || []; } /** @@ -193,18 +200,17 @@ function getCssStr(elem) { * @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 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; + // 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; diff --git a/lib/svgo-node.js b/lib/svgo-node.js index 71f973b4..b7e26a2c 100644 --- a/lib/svgo-node.js +++ b/lib/svgo-node.js @@ -8,7 +8,7 @@ const { createContentItem, } = require('./svgo.js'); -const importConfig = async configFile => { +const importConfig = async (configFile) => { const config = require(configFile); if (config == null || typeof config !== 'object' || Array.isArray(config)) { throw Error(`Invalid config file "${configFile}"`); @@ -23,7 +23,7 @@ const isFile = async (file) => { } catch { return false; } -} +}; const loadConfig = async (configFile, cwd = process.cwd()) => { if (configFile != null) { @@ -36,7 +36,7 @@ const loadConfig = async (configFile, cwd = process.cwd()) => { let dir = cwd; // eslint-disable-next-line no-constant-condition while (true) { - const file = path.join(dir, "svgo.config.js"); + const file = path.join(dir, 'svgo.config.js'); if (await isFile(file)) { return await importConfig(file); } diff --git a/lib/svgo.js b/lib/svgo.js index b037d90c..e18de1b2 100755 --- a/lib/svgo.js +++ b/lib/svgo.js @@ -13,7 +13,7 @@ const { defaultPlugins, resolvePluginConfig, - extendDefaultPlugins + extendDefaultPlugins, } = require('./svgo/config.js'); const svg2js = require('./svgo/svg2js.js'); const js2svg = require('./svgo/js2svg.js'); @@ -28,12 +28,12 @@ const optimize = (input, config) => { config = {}; } if (typeof config !== 'object') { - throw Error('Config should be an object') + throw Error('Config should be an object'); } const maxPassCount = config.multipass ? 10 : 1; let prevResultSize = Number.POSITIVE_INFINITY; let svgjs = null; - const info = {} + const info = {}; if (config.path != null) { info.path = config.path; } @@ -48,9 +48,13 @@ const optimize = (input, config) => { } const plugins = config.plugins || defaultPlugins; if (Array.isArray(plugins) === false) { - throw Error('Invalid plugins list. Provided \'plugins\' in config should be an array.'); + throw Error( + "Invalid plugins list. Provided 'plugins' in config should be an array." + ); } - const resolvedPlugins = plugins.map(plugin => resolvePluginConfig(plugin, config)) + const resolvedPlugins = plugins.map((plugin) => + resolvePluginConfig(plugin, config) + ); svgjs = invokePlugins(svgjs, info, resolvedPlugins); svgjs = js2svg(svgjs, config.js2svg); if (svgjs.error) { @@ -58,7 +62,7 @@ const optimize = (input, config) => { } if (svgjs.data.length < prevResultSize) { input = svgjs.data; - prevResultSize = svgjs.data.length + prevResultSize = svgjs.data.length; } else { if (config.datauri) { svgjs.data = encodeSVGDatauri(svgjs.data, config.datauri); diff --git a/lib/svgo/coa.js b/lib/svgo/coa.js index 4f605e8b..091f4348 100644 --- a/lib/svgo/coa.js +++ b/lib/svgo/coa.js @@ -14,182 +14,228 @@ const regSVGFile = /\.svg$/i; * @param {string} path */ function checkIsDir(path) { - try { - return FS.lstatSync(path).isDirectory(); - } catch(e) { - return false; - } + try { + return FS.lstatSync(path).isDirectory(); + } catch (e) { + return false; + } } module.exports = function makeProgram(program) { - program - .name(PKG.name) - .description(PKG.description, { - INPUT: 'Alias to --input' - }) - .version(PKG.version, '-v, --version') - .arguments('[INPUT...]') - .option('-i, --input ', 'Input files, "-" for STDIN') - .option('-s, --string ', 'Input SVG data string') - .option('-f, --folder ', 'Input folder, optimize and rewrite all *.svg files') - .option('-o, --output ', 'Output file or folder (by default the same as the input), "-" for STDOUT') - .option('-p, --precision ', 'Set number of digits in the fractional part, overrides plugins params') - .option('--config ', 'Custom config file, only .js is supported') - .option('--datauri ', 'Output as Data URI string (base64), URI encoded (enc) or unencoded (unenc)') - .option('--multipass', 'Pass over SVGs multiple times to ensure all optimizations are applied') - .option('--pretty', 'Make SVG pretty printed') - .option('--indent ', 'Indent number when pretty printing SVGs') - .option('-r, --recursive', 'Use with \'-f\'. Optimizes *.svg files in folders recursively.') - .option('-q, --quiet', 'Only output error messages, not regular status messages') - .option('--show-plugins', 'Show available plugins and exit') - .action(action); -} + program + .name(PKG.name) + .description(PKG.description, { + INPUT: 'Alias to --input', + }) + .version(PKG.version, '-v, --version') + .arguments('[INPUT...]') + .option('-i, --input ', 'Input files, "-" for STDIN') + .option('-s, --string ', 'Input SVG data string') + .option( + '-f, --folder ', + 'Input folder, optimize and rewrite all *.svg files' + ) + .option( + '-o, --output ', + 'Output file or folder (by default the same as the input), "-" for STDOUT' + ) + .option( + '-p, --precision ', + 'Set number of digits in the fractional part, overrides plugins params' + ) + .option('--config ', 'Custom config file, only .js is supported') + .option( + '--datauri ', + 'Output as Data URI string (base64), URI encoded (enc) or unencoded (unenc)' + ) + .option( + '--multipass', + 'Pass over SVGs multiple times to ensure all optimizations are applied' + ) + .option('--pretty', 'Make SVG pretty printed') + .option('--indent ', 'Indent number when pretty printing SVGs') + .option( + '-r, --recursive', + "Use with '-f'. Optimizes *.svg files in folders recursively." + ) + .option( + '-q, --quiet', + 'Only output error messages, not regular status messages' + ) + .option('--show-plugins', 'Show available plugins and exit') + .action(action); +}; async function action(args, opts, command) { - var input = opts.input || args; - var output = opts.output; - var config = {} + var input = opts.input || args; + var output = opts.output; + var config = {}; - if (opts.precision != null) { - const number = Number.parseInt(opts.precision, 0); - if (Number.isNaN(number)) { - console.error("error: option '-p, --precision' argument must be an integer number"); - process.exit(1) - } else { - opts.precision = number; - } + if (opts.precision != null) { + const number = Number.parseInt(opts.precision, 0); + if (Number.isNaN(number)) { + console.error( + "error: option '-p, --precision' argument must be an integer number" + ); + process.exit(1); + } else { + opts.precision = number; } + } - if (opts.datauri != null) { - if (opts.datauri !== 'base64' && opts.datauri !== 'enc' && opts.datauri !== 'unenc') { - console.error("error: option '--datauri' must have one of the following values: 'base64', 'enc' or 'unenc'") - process.exit(1) - } - } - - if (opts.indent != null) { - const number = Number.parseInt(opts.indent, 0); - if (Number.isNaN(number)) { - console.error("error: option '--indent' argument must be an integer number"); - process.exit(1); - } else { - opts.indent = number; - } - } - - // --show-plugins - if (opts.showPlugins) { - showAvailablePlugins(); - return; - } - - // w/o anything + if (opts.datauri != null) { if ( - (input.length === 0 || input[0] === '-') && - !opts.string && - !opts.stdin && - !opts.folder && - process.stdin.isTTY === true + opts.datauri !== 'base64' && + opts.datauri !== 'enc' && + opts.datauri !== 'unenc' ) { - return command.help(); + console.error( + "error: option '--datauri' must have one of the following values: 'base64', 'enc' or 'unenc'" + ); + process.exit(1); } + } - if (typeof process == 'object' && process.versions && process.versions.node && PKG && PKG.engines.node) { - var nodeVersion = String(PKG.engines.node).match(/\d*(\.\d+)*/)[0]; - if (parseFloat(process.versions.node) < parseFloat(nodeVersion)) { - throw Error(`${PKG.name} requires Node.js version ${nodeVersion} or higher.`); + if (opts.indent != null) { + const number = Number.parseInt(opts.indent, 0); + if (Number.isNaN(number)) { + console.error( + "error: option '--indent' argument must be an integer number" + ); + process.exit(1); + } else { + opts.indent = number; + } + } + + // --show-plugins + if (opts.showPlugins) { + showAvailablePlugins(); + return; + } + + // w/o anything + if ( + (input.length === 0 || input[0] === '-') && + !opts.string && + !opts.stdin && + !opts.folder && + process.stdin.isTTY === true + ) { + return command.help(); + } + + if ( + typeof process == 'object' && + process.versions && + process.versions.node && + PKG && + PKG.engines.node + ) { + var nodeVersion = String(PKG.engines.node).match(/\d*(\.\d+)*/)[0]; + if (parseFloat(process.versions.node) < parseFloat(nodeVersion)) { + throw Error( + `${PKG.name} requires Node.js version ${nodeVersion} or higher.` + ); + } + } + + // --config + const loadedConfig = await loadConfig(opts.config); + if (loadedConfig != null) { + config = loadedConfig; + } + + // --quiet + if (opts.quiet) { + config.quiet = opts.quiet; + } + + // --recursive + if (opts.recursive) { + config.recursive = opts.recursive; + } + + // --precision + if (opts.precision != null) { + var precision = Math.min(Math.max(0, opts.precision), 20); + config.floatPrecision = precision; + } + + // --multipass + if (opts.multipass) { + config.multipass = true; + } + + // --pretty + if (opts.pretty) { + config.js2svg = config.js2svg || {}; + config.js2svg.pretty = true; + if (opts.indent != null) { + config.js2svg.indent = opts.indent; + } + } + + // --output + if (output) { + if (input.length && input[0] != '-') { + if (output.length == 1 && checkIsDir(output[0])) { + var dir = output[0]; + for (var i = 0; i < input.length; i++) { + output[i] = checkIsDir(input[i]) + ? input[i] + : PATH.resolve(dir, PATH.basename(input[i])); } + } else if (output.length < input.length) { + output = output.concat(input.slice(output.length)); + } } + } else if (input.length) { + output = input; + } else if (opts.string) { + output = '-'; + } - // --config - const loadedConfig = await loadConfig(opts.config); - if (loadedConfig != null) { - config = loadedConfig; + if (opts.datauri) { + config.datauri = opts.datauri; + } + + // --folder + if (opts.folder) { + var ouputFolder = (output && output[0]) || opts.folder; + await optimizeFolder(config, opts.folder, ouputFolder); + } + + // --input + if (input.length !== 0) { + // STDIN + if (input[0] === '-') { + return new Promise((resolve, reject) => { + var data = '', + file = output[0]; + + process.stdin + .on('data', (chunk) => (data += chunk)) + .once('end', () => + processSVGData(config, { input: 'string' }, data, file).then( + resolve, + reject + ) + ); + }); + // file + } else { + await Promise.all( + input.map((file, n) => optimizeFile(config, file, output[n])) + ); } - // --quiet - if (opts.quiet) { - config.quiet = opts.quiet; - } - - // --recursive - if (opts.recursive) { - config.recursive = opts.recursive; - } - - // --precision - if (opts.precision != null) { - var precision = Math.min(Math.max(0, opts.precision), 20); - config.floatPrecision = precision; - } - - // --multipass - if (opts.multipass) { - config.multipass = true; - } - - // --pretty - if (opts.pretty) { - config.js2svg = config.js2svg || {}; - config.js2svg.pretty = true; - if (opts.indent != null) { - config.js2svg.indent = opts.indent; - } - } - - // --output - if (output) { - if (input.length && input[0] != '-') { - if (output.length == 1 && checkIsDir(output[0])) { - var dir = output[0]; - for (var i = 0; i < input.length; i++) { - output[i] = checkIsDir(input[i]) ? input[i] : PATH.resolve(dir, PATH.basename(input[i])); - } - } else if (output.length < input.length) { - output = output.concat(input.slice(output.length)); - } - } - } else if (input.length) { - output = input; - } else if (opts.string) { - output = '-'; - } - - if (opts.datauri) { - config.datauri = opts.datauri; - } - - // --folder - if (opts.folder) { - var ouputFolder = output && output[0] || opts.folder; - await optimizeFolder(config, opts.folder, ouputFolder); - } - - // --input - if (input.length !== 0) { - // STDIN - if (input[0] === '-') { - return new Promise((resolve, reject) => { - var data = '', - file = output[0]; - - process.stdin - .on('data', chunk => data += chunk) - .once('end', () => processSVGData(config, {input: 'string'}, data, file).then(resolve, reject)); - }); - // file - } else { - await Promise.all( - input.map((file, n) => optimizeFile(config, file, output[n])) - ); - } - // --string - } else if (opts.string) { - var data = decodeSVGDatauri(opts.string); + } else if (opts.string) { + var data = decodeSVGDatauri(opts.string); - return processSVGData(config, {input: 'string'}, data, output[0]); - } + return processSVGData(config, { input: 'string' }, data, output[0]); + } } /** @@ -200,10 +246,12 @@ async function action(args, opts, command) { * @return {Promise} */ function optimizeFolder(config, dir, output) { - if (!config.quiet) { - console.log(`Processing directory '${dir}':\n`); - } - return FS.promises.readdir(dir).then(files => processDirectory(config, dir, files, output)); + if (!config.quiet) { + console.log(`Processing directory '${dir}':\n`); + } + return FS.promises + .readdir(dir) + .then((files) => processDirectory(config, dir, files, output)); } /** @@ -215,12 +263,22 @@ function optimizeFolder(config, dir, output) { * @return {Promise} */ function processDirectory(config, dir, files, output) { - // take only *.svg files, recursively if necessary - var svgFilesDescriptions = getFilesDescriptions(config, dir, files, output); + // take only *.svg files, recursively if necessary + var svgFilesDescriptions = getFilesDescriptions(config, dir, files, output); - return svgFilesDescriptions.length ? - Promise.all(svgFilesDescriptions.map(fileDescription => optimizeFile(config, fileDescription.inputPath, fileDescription.outputPath))) : - Promise.reject(new Error(`No SVG files have been found in '${dir}' directory.`)); + return svgFilesDescriptions.length + ? Promise.all( + svgFilesDescriptions.map((fileDescription) => + optimizeFile( + config, + fileDescription.inputPath, + fileDescription.outputPath + ) + ) + ) + : Promise.reject( + new Error(`No SVG files have been found in '${dir}' directory.`) + ); } /** @@ -232,27 +290,32 @@ function processDirectory(config, dir, files, output) { * @return {Array} */ function getFilesDescriptions(config, dir, files, output) { - const filesInThisFolder = files - .filter(name => regSVGFile.test(name)) - .map(name => ({ - inputPath: PATH.resolve(dir, name), - outputPath: PATH.resolve(output, name), - })); + const filesInThisFolder = files + .filter((name) => regSVGFile.test(name)) + .map((name) => ({ + inputPath: PATH.resolve(dir, name), + outputPath: PATH.resolve(output, name), + })); - return config.recursive ? - [].concat( - filesInThisFolder, - files - .filter(name => checkIsDir(PATH.resolve(dir, name))) - .map(subFolderName => { - const subFolderPath = PATH.resolve(dir, subFolderName); - const subFolderFiles = FS.readdirSync(subFolderPath); - const subFolderOutput = PATH.resolve(output, subFolderName); - return getFilesDescriptions(config, subFolderPath, subFolderFiles, subFolderOutput); - }) - .reduce((a, b) => [].concat(a, b), []) - ) : - filesInThisFolder; + return config.recursive + ? [].concat( + filesInThisFolder, + files + .filter((name) => checkIsDir(PATH.resolve(dir, name))) + .map((subFolderName) => { + const subFolderPath = PATH.resolve(dir, subFolderName); + const subFolderFiles = FS.readdirSync(subFolderPath); + const subFolderOutput = PATH.resolve(output, subFolderName); + return getFilesDescriptions( + config, + subFolderPath, + subFolderFiles, + subFolderOutput + ); + }) + .reduce((a, b) => [].concat(a, b), []) + ) + : filesInThisFolder; } /** @@ -263,10 +326,11 @@ function getFilesDescriptions(config, dir, files, output) { * @return {Promise} */ function optimizeFile(config, file, output) { - return FS.promises.readFile(file, 'utf8').then( - data => processSVGData(config, {input: 'file', path: file}, data, output, file), - error => checkOptimizeFileError(config, file, output, error) - ); + return FS.promises.readFile(file, 'utf8').then( + (data) => + processSVGData(config, { input: 'file', path: file }, data, output, file), + (error) => checkOptimizeFileError(config, file, output, error) + ); } /** @@ -278,33 +342,42 @@ function optimizeFile(config, file, output) { * @return {Promise} */ function processSVGData(config, info, data, output, input) { - var startTime = Date.now(), - prevFileSize = Buffer.byteLength(data, 'utf8'); + var startTime = Date.now(), + prevFileSize = Buffer.byteLength(data, 'utf8'); - const result = optimize(data, { ...config, ...info }); - if (result.error) { - let message = result.error; - if (result.path != null) { - message += `\nFile: ${result.path}` - } - throw Error(message) + const result = optimize(data, { ...config, ...info }); + if (result.error) { + let message = result.error; + if (result.path != null) { + message += `\nFile: ${result.path}`; } - if (config.datauri) { - result.data = encodeSVGDatauri(result.data, config.datauri); - } - var resultFileSize = Buffer.byteLength(result.data, 'utf8'), - processingTime = Date.now() - startTime; + throw Error(message); + } + if (config.datauri) { + result.data = encodeSVGDatauri(result.data, config.datauri); + } + var resultFileSize = Buffer.byteLength(result.data, 'utf8'), + processingTime = Date.now() - startTime; - return writeOutput(input, output, result.data).then(function() { - if (!config.quiet && output != '-') { - if (input) { - console.log(`\n${PATH.basename(input)}:`); - } - printTimeInfo(processingTime); - printProfitInfo(prevFileSize, resultFileSize); + return writeOutput(input, output, result.data).then( + function () { + if (!config.quiet && output != '-') { + if (input) { + console.log(`\n${PATH.basename(input)}:`); } + printTimeInfo(processingTime); + printProfitInfo(prevFileSize, resultFileSize); + } }, - error => Promise.reject(new Error(error.code === 'ENOTDIR' ? `Error: output '${output}' is not a directory.` : error))); + (error) => + Promise.reject( + new Error( + error.code === 'ENOTDIR' + ? `Error: output '${output}' is not a directory.` + : error + ) + ) + ); } /** @@ -315,23 +388,24 @@ function processSVGData(config, info, data, output, input) { * @return {Promise} */ function writeOutput(input, output, data) { - if (output == '-') { - console.log(data); - return Promise.resolve(); - } + if (output == '-') { + console.log(data); + return Promise.resolve(); + } - FS.mkdirSync(PATH.dirname(output), { recursive: true }); + FS.mkdirSync(PATH.dirname(output), { recursive: true }); - return FS.promises.writeFile(output, data, 'utf8').catch(error => checkWriteFileError(input, output, data, error)); + return FS.promises + .writeFile(output, data, 'utf8') + .catch((error) => checkWriteFileError(input, output, data, error)); } - /** * Write a time taken by optimization. * @param {number} time time in milliseconds. */ function printTimeInfo(time) { - console.log(`Done in ${time} ms!`); + console.log(`Done in ${time} ms!`); } /** @@ -340,14 +414,17 @@ function printTimeInfo(time) { * @param {number} outBytes size after optimization. */ function printProfitInfo(inBytes, outBytes) { - var profitPercents = 100 - outBytes * 100 / inBytes; + var profitPercents = 100 - (outBytes * 100) / inBytes; - console.log( - (Math.round((inBytes / 1024) * 1000) / 1000) + ' KiB' + - (profitPercents < 0 ? ' + ' : ' - ') + - chalk.green(Math.abs((Math.round(profitPercents * 10) / 10)) + '%') + ' = ' + - (Math.round((outBytes / 1024) * 1000) / 1000) + ' KiB' - ); + console.log( + Math.round((inBytes / 1024) * 1000) / 1000 + + ' KiB' + + (profitPercents < 0 ? ' + ' : ' - ') + + chalk.green(Math.abs(Math.round(profitPercents * 10) / 10) + '%') + + ' = ' + + Math.round((outBytes / 1024) * 1000) / 1000 + + ' KiB' + ); } /** @@ -359,12 +436,14 @@ function printProfitInfo(inBytes, outBytes) { * @return {Promise} */ function checkOptimizeFileError(config, input, output, error) { - if (error.code == 'EISDIR') { - return optimizeFolder(config, input, output); - } else if (error.code == 'ENOENT') { - return Promise.reject(new Error(`Error: no such file or directory '${error.path}'.`)); - } - return Promise.reject(error); + if (error.code == 'EISDIR') { + return optimizeFolder(config, input, output); + } else if (error.code == 'ENOENT') { + return Promise.reject( + new Error(`Error: no such file or directory '${error.path}'.`) + ); + } + return Promise.reject(error); } /** @@ -376,22 +455,26 @@ function checkOptimizeFileError(config, input, output, error) { * @return {Promise} */ function checkWriteFileError(input, output, data, error) { - if (error.code == 'EISDIR' && input) { - return FS.promises.writeFile(PATH.resolve(output, PATH.basename(input)), data, 'utf8'); - } else { - return Promise.reject(error); - } + if (error.code == 'EISDIR' && input) { + return FS.promises.writeFile( + PATH.resolve(output, PATH.basename(input)), + data, + 'utf8' + ); + } else { + return Promise.reject(error); + } } /** * Show list of available plugins with short description. */ function showAvailablePlugins() { - const list = Object.entries(pluginsMap) - .sort(([a], [b]) => a.localeCompare(b)) - .map(([name, plugin]) => ` [ ${chalk.green(name)} ] ${plugin.description}`) - .join('\n'); - console.log('Currently available plugins:\n' + list); + const list = Object.entries(pluginsMap) + .sort(([a], [b]) => a.localeCompare(b)) + .map(([name, plugin]) => ` [ ${chalk.green(name)} ] ${plugin.description}`) + .join('\n'); + console.log('Currently available plugins:\n' + list); } module.exports.checkIsDir = checkIsDir; diff --git a/lib/svgo/config.js b/lib/svgo/config.js index 41de417f..790c2de2 100644 --- a/lib/svgo/config.js +++ b/lib/svgo/config.js @@ -53,11 +53,14 @@ const pluginsOrder = [ 'removeOffCanvasPaths', 'reusePaths', ]; -const defaultPlugins = pluginsOrder.filter(name => pluginsMap[name].active); +const defaultPlugins = pluginsOrder.filter((name) => pluginsMap[name].active); exports.defaultPlugins = defaultPlugins; const extendDefaultPlugins = (plugins) => { - const extendedPlugins = pluginsOrder.map(name => ({ name, ...pluginsMap[name] })); + const extendedPlugins = pluginsOrder.map((name) => ({ + name, + ...pluginsMap[name], + })); for (const plugin of plugins) { const resolvedPlugin = resolvePluginConfig(plugin, {}); const index = pluginsOrder.indexOf(resolvedPlugin.name); @@ -68,7 +71,7 @@ const extendDefaultPlugins = (plugins) => { } } return extendedPlugins; -} +}; exports.extendDefaultPlugins = extendDefaultPlugins; const resolvePluginConfig = (plugin, config) => { @@ -86,7 +89,7 @@ const resolvePluginConfig = (plugin, config) => { ...pluginConfig, name: plugin, active: true, - params: { ...pluginConfig.params, ...configParams } + params: { ...pluginConfig.params, ...configParams }, }; } if (typeof plugin === 'object' && plugin != null) { @@ -98,7 +101,7 @@ const resolvePluginConfig = (plugin, config) => { return { active: true, ...plugin, - params: { configParams, ...plugin.params } + params: { configParams, ...plugin.params }, }; } else { // resolve builtin plugin specified as object without implementation @@ -110,7 +113,7 @@ const resolvePluginConfig = (plugin, config) => { ...pluginConfig, active: true, ...plugin, - params: { ...pluginConfig.params, ...configParams, ...plugin.params } + params: { ...pluginConfig.params, ...configParams, ...plugin.params }, }; } } diff --git a/lib/svgo/css-class-list.js b/lib/svgo/css-class-list.js index 263c5069..7cf38b5a 100644 --- a/lib/svgo/css-class-list.js +++ b/lib/svgo/css-class-list.js @@ -1,10 +1,10 @@ 'use strict'; -var CSSClassList = function(node) { - this.parentNode = node; - this.classNames = new Set(); - this.classAttr = null; - //this.classValue = null; +var CSSClassList = function (node) { + this.parentNode = node; + this.classNames = new Set(); + this.classAttr = null; + //this.classValue = null; }; /** @@ -12,121 +12,115 @@ var CSSClassList = function(node) { * * @param parentNode the parentNode to assign to the cloned result */ -CSSClassList.prototype.clone = function(parentNode) { - var node = this; - var nodeData = {}; +CSSClassList.prototype.clone = function (parentNode) { + var node = this; + var nodeData = {}; - Object.keys(node).forEach(function(key) { - if (key !== 'parentNode') { - nodeData[key] = node[key]; - } - }); + Object.keys(node).forEach(function (key) { + if (key !== 'parentNode') { + nodeData[key] = node[key]; + } + }); - // Deep-clone node data. - nodeData = JSON.parse(JSON.stringify(nodeData)); + // Deep-clone node data. + nodeData = JSON.parse(JSON.stringify(nodeData)); - var clone = new CSSClassList(parentNode); - Object.assign(clone, nodeData); - return clone; + var clone = new CSSClassList(parentNode); + Object.assign(clone, nodeData); + return clone; }; -CSSClassList.prototype.hasClass = function() { - this.classAttr = { // empty class attr - 'name': 'class', - 'value': null - }; +CSSClassList.prototype.hasClass = function () { + this.classAttr = { + // empty class attr + name: 'class', + value: null, + }; - this.addClassHandler(); + this.addClassHandler(); }; - // attr.class -CSSClassList.prototype.addClassHandler = function() { +CSSClassList.prototype.addClassHandler = function () { + Object.defineProperty(this.parentNode.attrs, 'class', { + get: this.getClassAttr.bind(this), + set: this.setClassAttr.bind(this), + enumerable: true, + configurable: true, + }); - Object.defineProperty(this.parentNode.attrs, 'class', { - get: this.getClassAttr.bind(this), - set: this.setClassAttr.bind(this), - enumerable: true, - configurable: true - }); - - this.addClassValueHandler(); + this.addClassValueHandler(); }; // attr.class.value -CSSClassList.prototype.addClassValueHandler = function() { - - Object.defineProperty(this.classAttr, 'value', { - get: this.getClassValue.bind(this), - set: this.setClassValue.bind(this), - enumerable: true, - configurable: true - }); +CSSClassList.prototype.addClassValueHandler = function () { + Object.defineProperty(this.classAttr, 'value', { + get: this.getClassValue.bind(this), + set: this.setClassValue.bind(this), + enumerable: true, + configurable: true, + }); }; -CSSClassList.prototype.getClassAttr = function() { - return this.classAttr; +CSSClassList.prototype.getClassAttr = function () { + return this.classAttr; }; -CSSClassList.prototype.setClassAttr = function(newClassAttr) { - this.setClassValue(newClassAttr.value); // must before applying value handler! +CSSClassList.prototype.setClassAttr = function (newClassAttr) { + this.setClassValue(newClassAttr.value); // must before applying value handler! - this.classAttr = newClassAttr; - this.addClassValueHandler(); + this.classAttr = newClassAttr; + this.addClassValueHandler(); }; -CSSClassList.prototype.getClassValue = function() { - var arrClassNames = Array.from(this.classNames); - return arrClassNames.join(' '); +CSSClassList.prototype.getClassValue = function () { + var arrClassNames = Array.from(this.classNames); + return arrClassNames.join(' '); }; -CSSClassList.prototype.setClassValue = function(newValue) { - if(typeof newValue === 'undefined') { - this.classNames.clear(); - return; - } - var arrClassNames = newValue.split(' '); - this.classNames = new Set(arrClassNames); +CSSClassList.prototype.setClassValue = function (newValue) { + if (typeof newValue === 'undefined') { + this.classNames.clear(); + return; + } + var arrClassNames = newValue.split(' '); + this.classNames = new Set(arrClassNames); }; - -CSSClassList.prototype.add = function(/* variadic */) { - this.hasClass(); - Object.values(arguments).forEach(this._addSingle.bind(this)); +CSSClassList.prototype.add = function (/* variadic */) { + this.hasClass(); + Object.values(arguments).forEach(this._addSingle.bind(this)); }; -CSSClassList.prototype._addSingle = function(className) { - this.classNames.add(className); +CSSClassList.prototype._addSingle = function (className) { + this.classNames.add(className); }; - -CSSClassList.prototype.remove = function(/* variadic */) { - this.hasClass(); - Object.values(arguments).forEach(this._removeSingle.bind(this)); +CSSClassList.prototype.remove = function (/* variadic */) { + this.hasClass(); + Object.values(arguments).forEach(this._removeSingle.bind(this)); }; -CSSClassList.prototype._removeSingle = function(className) { +CSSClassList.prototype._removeSingle = function (className) { + this.classNames.delete(className); +}; + +CSSClassList.prototype.item = function (index) { + var arrClassNames = Array.from(this.classNames); + return arrClassNames[index]; +}; + +CSSClassList.prototype.toggle = function (className, force) { + if (this.contains(className) || force === false) { this.classNames.delete(className); + } + this.classNames.add(className); }; - -CSSClassList.prototype.item = function(index) { - var arrClassNames = Array.from(this.classNames); - return arrClassNames[index]; +CSSClassList.prototype.contains = function (className) { + return this.classNames.has(className); }; -CSSClassList.prototype.toggle = function(className, force) { - if(this.contains(className) || force === false) { - this.classNames.delete(className); - } - this.classNames.add(className); -}; - -CSSClassList.prototype.contains = function(className) { - return this.classNames.has(className); -}; - - module.exports = CSSClassList; diff --git a/lib/svgo/css-select-adapter.js b/lib/svgo/css-select-adapter.js index c37678cb..9cbb2c2f 100644 --- a/lib/svgo/css-select-adapter.js +++ b/lib/svgo/css-select-adapter.js @@ -6,45 +6,44 @@ 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(); + }, - // 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 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 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 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 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; - } + // 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 diff --git a/lib/svgo/css-style-declaration.js b/lib/svgo/css-style-declaration.js index fb6d7811..6d07952d 100644 --- a/lib/svgo/css-style-declaration.js +++ b/lib/svgo/css-style-declaration.js @@ -1,19 +1,18 @@ 'use strict'; var csstree = require('css-tree'), - csstools = require('../css-tools'); + csstools = require('../css-tools'); +var CSSStyleDeclaration = function (node) { + this.parentNode = node; -var CSSStyleDeclaration = function(node) { - this.parentNode = node; + this.properties = new Map(); + this.hasSynced = false; - this.properties = new Map(); - this.hasSynced = false; + this.styleAttr = null; + this.styleValue = null; - this.styleAttr = null; - this.styleValue = null; - - this.parseError = false; + this.parseError = false; }; /** @@ -21,124 +20,120 @@ var CSSStyleDeclaration = function(node) { * * @param parentNode the parentNode to assign to the cloned result */ -CSSStyleDeclaration.prototype.clone = function(parentNode) { - var node = this; - var nodeData = {}; +CSSStyleDeclaration.prototype.clone = function (parentNode) { + var node = this; + var nodeData = {}; - Object.keys(node).forEach(function(key) { - if (key !== 'parentNode') { - nodeData[key] = node[key]; - } - }); + Object.keys(node).forEach(function (key) { + if (key !== 'parentNode') { + nodeData[key] = node[key]; + } + }); - // Deep-clone node data. - nodeData = JSON.parse(JSON.stringify(nodeData)); + // Deep-clone node data. + nodeData = JSON.parse(JSON.stringify(nodeData)); - var clone = new CSSStyleDeclaration(parentNode); - Object.assign(clone, nodeData); - return clone; + var clone = new CSSStyleDeclaration(parentNode); + Object.assign(clone, nodeData); + return clone; }; -CSSStyleDeclaration.prototype.hasStyle = function() { - this.addStyleHandler(); +CSSStyleDeclaration.prototype.hasStyle = function () { + this.addStyleHandler(); }; - - - // attr.style -CSSStyleDeclaration.prototype.addStyleHandler = function() { +CSSStyleDeclaration.prototype.addStyleHandler = function () { + this.styleAttr = { + // empty style attr + name: 'style', + value: null, + }; - this.styleAttr = { // empty style attr - 'name': 'style', - 'value': null - }; + Object.defineProperty(this.parentNode.attrs, 'style', { + get: this.getStyleAttr.bind(this), + set: this.setStyleAttr.bind(this), + enumerable: true, + configurable: true, + }); - Object.defineProperty(this.parentNode.attrs, 'style', { - get: this.getStyleAttr.bind(this), - set: this.setStyleAttr.bind(this), - enumerable: true, - configurable: true - }); - - this.addStyleValueHandler(); + this.addStyleValueHandler(); }; // attr.style.value -CSSStyleDeclaration.prototype.addStyleValueHandler = function() { +CSSStyleDeclaration.prototype.addStyleValueHandler = function () { + Object.defineProperty(this.styleAttr, 'value', { + get: this.getStyleValue.bind(this), + set: this.setStyleValue.bind(this), + enumerable: true, + configurable: true, + }); +}; - Object.defineProperty(this.styleAttr, 'value', { - get: this.getStyleValue.bind(this), - set: this.setStyleValue.bind(this), - enumerable: true, - configurable: true +CSSStyleDeclaration.prototype.getStyleAttr = function () { + return this.styleAttr; +}; + +CSSStyleDeclaration.prototype.setStyleAttr = function (newStyleAttr) { + this.setStyleValue(newStyleAttr.value); // must before applying value handler! + + this.styleAttr = newStyleAttr; + this.addStyleValueHandler(); + this.hasSynced = false; // raw css changed +}; + +CSSStyleDeclaration.prototype.getStyleValue = function () { + return this.getCssText(); +}; + +CSSStyleDeclaration.prototype.setStyleValue = function (newValue) { + this.properties.clear(); // reset all existing properties + this.styleValue = newValue; + this.hasSynced = false; // raw css changed +}; + +CSSStyleDeclaration.prototype._loadCssText = function () { + if (this.hasSynced) { + return; + } + this.hasSynced = true; // must be set here to prevent loop in setProperty(...) + + if (!this.styleValue || this.styleValue.length === 0) { + return; + } + var inlineCssStr = this.styleValue; + + var declarations = {}; + try { + declarations = csstree.parse(inlineCssStr, { + context: 'declarationList', + parseValue: false, }); -}; + } catch (parseError) { + this.parseError = parseError; + return; + } + this.parseError = false; -CSSStyleDeclaration.prototype.getStyleAttr = function() { - return this.styleAttr; -}; - -CSSStyleDeclaration.prototype.setStyleAttr = function(newStyleAttr) { - this.setStyleValue(newStyleAttr.value); // must before applying value handler! - - this.styleAttr = newStyleAttr; - this.addStyleValueHandler(); - this.hasSynced = false; // raw css changed -}; - -CSSStyleDeclaration.prototype.getStyleValue = function() { - return this.getCssText(); -}; - -CSSStyleDeclaration.prototype.setStyleValue = function(newValue) { - this.properties.clear(); // reset all existing properties - this.styleValue = newValue; - this.hasSynced = false; // raw css changed -}; - - - - -CSSStyleDeclaration.prototype._loadCssText = function() { - if (this.hasSynced) { - return; - } - this.hasSynced = true; // must be set here to prevent loop in setProperty(...) - - if (!this.styleValue || this.styleValue.length === 0) { - return; - } - var inlineCssStr = this.styleValue; - - var declarations = {}; + var self = this; + declarations.children.each(function (declaration) { try { - declarations = csstree.parse(inlineCssStr, { - context: 'declarationList', - parseValue: false - }); - } catch (parseError) { - this.parseError = parseError; - return; + var styleDeclaration = csstools.csstreeToStyleDeclaration(declaration); + self.setProperty( + styleDeclaration.name, + styleDeclaration.value, + styleDeclaration.priority + ); + } catch (styleError) { + if (styleError.message !== 'Unknown node type: undefined') { + self.parseError = styleError; + } } - this.parseError = false; - - var self = this; - declarations.children.each(function(declaration) { - try { - var styleDeclaration = csstools.csstreeToStyleDeclaration(declaration); - self.setProperty(styleDeclaration.name, styleDeclaration.value, styleDeclaration.priority); - } catch(styleError) { - if(styleError.message !== 'Unknown node type: undefined') { - self.parseError = styleError; - } - } - }); + }); }; - // only reads from properties /** @@ -146,39 +141,43 @@ CSSStyleDeclaration.prototype._loadCssText = function() { * * @return {String} Textual representation of the declaration block (empty string for no properties) */ -CSSStyleDeclaration.prototype.getCssText = function() { - var properties = this.getProperties(); +CSSStyleDeclaration.prototype.getCssText = function () { + var properties = this.getProperties(); - if (this.parseError) { - // in case of a parse error, pass through original styles - return this.styleValue; - } + if (this.parseError) { + // in case of a parse error, pass through original styles + return this.styleValue; + } - var cssText = []; - properties.forEach(function(property, propertyName) { - var strImportant = property.priority === 'important' ? '!important' : ''; - cssText.push(propertyName.trim() + ':' + property.value.trim() + strImportant); - }); - return cssText.join(';'); + var cssText = []; + properties.forEach(function (property, propertyName) { + var strImportant = property.priority === 'important' ? '!important' : ''; + cssText.push( + propertyName.trim() + ':' + property.value.trim() + strImportant + ); + }); + return cssText.join(';'); }; -CSSStyleDeclaration.prototype._handleParseError = function() { - if (this.parseError) { - console.warn('Warning: Parse error when parsing inline styles, style properties of this element cannot be used. The raw styles can still be get/set using .attr(\'style\').value. Error details: ' + this.parseError); - } +CSSStyleDeclaration.prototype._handleParseError = function () { + if (this.parseError) { + console.warn( + "Warning: Parse error when parsing inline styles, style properties of this element cannot be used. The raw styles can still be get/set using .attr('style').value. Error details: " + + this.parseError + ); + } }; +CSSStyleDeclaration.prototype._getProperty = function (propertyName) { + if (typeof propertyName === 'undefined') { + throw Error('1 argument required, but only 0 present.'); + } -CSSStyleDeclaration.prototype._getProperty = function(propertyName) { - if(typeof propertyName === 'undefined') { - throw Error('1 argument required, but only 0 present.'); - } + var properties = this.getProperties(); + this._handleParseError(); - var properties = this.getProperties(); - this._handleParseError(); - - var property = properties.get(propertyName.trim()); - return property; + var property = properties.get(propertyName.trim()); + return property; }; /** @@ -187,9 +186,9 @@ CSSStyleDeclaration.prototype._getProperty = function(propertyName) { * @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. */ -CSSStyleDeclaration.prototype.getPropertyPriority = function(propertyName) { - var property = this._getProperty(propertyName); - return property ? property.priority : ''; +CSSStyleDeclaration.prototype.getPropertyPriority = function (propertyName) { + var property = this._getProperty(propertyName); + return property ? property.priority : ''; }; /** @@ -198,9 +197,9 @@ CSSStyleDeclaration.prototype.getPropertyPriority = function(propertyName) { * @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. */ -CSSStyleDeclaration.prototype.getPropertyValue = function(propertyName) { - var property = this._getProperty(propertyName); - return property ? property.value : null; +CSSStyleDeclaration.prototype.getPropertyValue = function (propertyName) { + var property = this._getProperty(propertyName); + return property ? property.value : null; }; /** @@ -209,15 +208,15 @@ CSSStyleDeclaration.prototype.getPropertyValue = function(propertyName) { * @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. */ -CSSStyleDeclaration.prototype.item = function(index) { - if(typeof index === 'undefined') { - throw Error('1 argument required, but only 0 present.'); - } +CSSStyleDeclaration.prototype.item = function (index) { + if (typeof index === 'undefined') { + throw Error('1 argument required, but only 0 present.'); + } - var properties = this.getProperties(); - this._handleParseError(); + var properties = this.getProperties(); + this._handleParseError(); - return Array.from(properties.keys())[index]; + return Array.from(properties.keys())[index]; }; /** @@ -225,12 +224,11 @@ CSSStyleDeclaration.prototype.item = function(index) { * * @return {Map} properties that is a Map with propertyName as key and property (propertyValue + propertyPriority) as value. */ -CSSStyleDeclaration.prototype.getProperties = function() { - this._loadCssText(); - return this.properties; +CSSStyleDeclaration.prototype.getProperties = function () { + this._loadCssText(); + return this.properties; }; - // writes to properties /** @@ -239,19 +237,19 @@ CSSStyleDeclaration.prototype.getProperties = function() { * @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. */ -CSSStyleDeclaration.prototype.removeProperty = function(propertyName) { - if(typeof propertyName === 'undefined') { - throw Error('1 argument required, but only 0 present.'); - } +CSSStyleDeclaration.prototype.removeProperty = function (propertyName) { + if (typeof propertyName === 'undefined') { + throw Error('1 argument required, but only 0 present.'); + } - this.hasStyle(); + this.hasStyle(); - var properties = this.getProperties(); - this._handleParseError(); + var properties = this.getProperties(); + this._handleParseError(); - var oldValue = this.getPropertyValue(propertyName); - properties.delete(propertyName.trim()); - return oldValue; + var oldValue = this.getPropertyValue(propertyName); + properties.delete(propertyName.trim()); + return oldValue; }; /** @@ -262,24 +260,27 @@ CSSStyleDeclaration.prototype.removeProperty = function(propertyName) { * @param {String} [priority] allowing the "important" CSS priority to be set. If not specified, treated as the empty string. * @return {undefined} */ -CSSStyleDeclaration.prototype.setProperty = function(propertyName, value, priority) { - if(typeof propertyName === 'undefined') { - throw Error('propertyName argument required, but only not present.'); - } +CSSStyleDeclaration.prototype.setProperty = function ( + propertyName, + value, + priority +) { + if (typeof propertyName === 'undefined') { + throw Error('propertyName argument required, but only not present.'); + } - this.hasStyle(); + this.hasStyle(); - var properties = this.getProperties(); - this._handleParseError(); + var properties = this.getProperties(); + this._handleParseError(); - var property = { - value: value.trim(), - priority: priority.trim() - }; - properties.set(propertyName.trim(), property); + var property = { + value: value.trim(), + priority: priority.trim(), + }; + properties.set(propertyName.trim(), property); - return property; + return property; }; - module.exports = CSSStyleDeclaration; diff --git a/lib/svgo/js2svg.js b/lib/svgo/js2svg.js index f29838c7..8cb7f484 100644 --- a/lib/svgo/js2svg.js +++ b/lib/svgo/js2svg.js @@ -1,42 +1,42 @@ 'use strict'; var EOL = require('os').EOL, - textElems = require('../../plugins/_collections.js').textElems; + textElems = require('../../plugins/_collections.js').textElems; var defaults = { - doctypeStart: '', - procInstStart: '', - tagOpenStart: '<', - tagOpenEnd: '>', - tagCloseStart: '', - tagShortStart: '<', - tagShortEnd: '/>', - attrStart: '="', - attrEnd: '"', - commentStart: '', - cdataStart: '', - textStart: '', - textEnd: '', - indent: 4, - regEntities: /[&'"<>]/g, - regValEntities: /[&"<>]/g, - encodeEntity: encodeEntity, - pretty: false, - useShortTags: true + doctypeStart: '', + procInstStart: '', + tagOpenStart: '<', + tagOpenEnd: '>', + tagCloseStart: '', + tagShortStart: '<', + tagShortEnd: '/>', + attrStart: '="', + attrEnd: '"', + commentStart: '', + cdataStart: '', + textStart: '', + textEnd: '', + indent: 4, + regEntities: /[&'"<>]/g, + regValEntities: /[&"<>]/g, + encodeEntity: encodeEntity, + pretty: false, + useShortTags: true, }; var entities = { - '&': '&', - '\'': ''', - '"': '"', - '>': '>', - '<': '<', - }; + '&': '&', + "'": ''', + '"': '"', + '>': '>', + '<': '<', +}; /** * Convert SVG-as-JS object to SVG (XML) string. @@ -46,45 +46,41 @@ var entities = { * * @return {Object} output data */ -module.exports = function(data, config) { - - return new JS2SVG(config).convert(data); - +module.exports = function (data, config) { + return new JS2SVG(config).convert(data); }; function JS2SVG(config) { + if (config) { + this.config = Object.assign({}, defaults, config); + } else { + this.config = Object.assign({}, defaults); + } - if (config) { - this.config = Object.assign({}, defaults, config); - } else { - this.config = Object.assign({}, defaults); - } + var indent = this.config.indent; + if (typeof indent == 'number' && !isNaN(indent)) { + this.config.indent = indent < 0 ? '\t' : ' '.repeat(indent); + } else if (typeof indent != 'string') { + this.config.indent = ' '; + } - var indent = this.config.indent; - if (typeof indent == 'number' && !isNaN(indent)) { - this.config.indent = (indent < 0) ? '\t' : ' '.repeat(indent); - } else if (typeof indent != 'string') { - this.config.indent = ' '; - } - - if (this.config.pretty) { - this.config.doctypeEnd += EOL; - this.config.procInstEnd += EOL; - this.config.commentEnd += EOL; - this.config.cdataEnd += EOL; - this.config.tagShortEnd += EOL; - this.config.tagOpenEnd += EOL; - this.config.tagCloseEnd += EOL; - this.config.textEnd += EOL; - } - - this.indentLevel = 0; - this.textContext = null; + if (this.config.pretty) { + this.config.doctypeEnd += EOL; + this.config.procInstEnd += EOL; + this.config.commentEnd += EOL; + this.config.cdataEnd += EOL; + this.config.tagShortEnd += EOL; + this.config.tagOpenEnd += EOL; + this.config.tagCloseEnd += EOL; + this.config.textEnd += EOL; + } + this.indentLevel = 0; + this.textContext = null; } function encodeEntity(char) { - return entities[char]; + return entities[char]; } /** @@ -94,44 +90,38 @@ function encodeEntity(char) { * * @return {String} */ -JS2SVG.prototype.convert = function(data) { +JS2SVG.prototype.convert = function (data) { + var svg = ''; - var svg = ''; + if (data.content) { + this.indentLevel++; - if (data.content) { + data.content.forEach(function (item) { + if (item.elem) { + svg += this.createElem(item); + } else if (item.text) { + svg += this.createText(item.text); + } else if (item.doctype) { + svg += this.createDoctype(item.doctype); + } else if (item.processinginstruction) { + svg += this.createProcInst(item.processinginstruction); + } else if (item.comment) { + svg += this.createComment(item.comment); + } else if (item.cdata) { + svg += this.createCDATA(item.cdata); + } + }, this); + } - this.indentLevel++; - - data.content.forEach(function(item) { - - if (item.elem) { - svg += this.createElem(item); - } else if (item.text) { - svg += this.createText(item.text); - } else if (item.doctype) { - svg += this.createDoctype(item.doctype); - } else if (item.processinginstruction) { - svg += this.createProcInst(item.processinginstruction); - } else if (item.comment) { - svg += this.createComment(item.comment); - } else if (item.cdata) { - svg += this.createCDATA(item.cdata); - } - - }, this); - - } - - this.indentLevel--; - - return { - data: svg, - info: { - width: this.width, - height: this.height - } - }; + this.indentLevel--; + return { + data: svg, + info: { + width: this.width, + height: this.height, + }, + }; }; /** @@ -139,16 +129,14 @@ JS2SVG.prototype.convert = function(data) { * * @return {String} */ -JS2SVG.prototype.createIndent = function() { +JS2SVG.prototype.createIndent = function () { + var indent = ''; - var indent = ''; - - if (this.config.pretty && !this.textContext) { - indent = this.config.indent.repeat(this.indentLevel - 1); - } - - return indent; + if (this.config.pretty && !this.textContext) { + indent = this.config.indent.repeat(this.indentLevel - 1); + } + return indent; }; /** @@ -158,12 +146,8 @@ JS2SVG.prototype.createIndent = function() { * * @return {String} */ -JS2SVG.prototype.createDoctype = function(doctype) { - - return this.config.doctypeStart + - doctype + - this.config.doctypeEnd; - +JS2SVG.prototype.createDoctype = function (doctype) { + return this.config.doctypeStart + doctype + this.config.doctypeEnd; }; /** @@ -173,14 +157,14 @@ JS2SVG.prototype.createDoctype = function(doctype) { * * @return {String} */ -JS2SVG.prototype.createProcInst = function(instruction) { - - return this.config.procInstStart + - instruction.name + - ' ' + - instruction.body + - this.config.procInstEnd; - +JS2SVG.prototype.createProcInst = function (instruction) { + return ( + this.config.procInstStart + + instruction.name + + ' ' + + instruction.body + + this.config.procInstEnd + ); }; /** @@ -190,12 +174,8 @@ JS2SVG.prototype.createProcInst = function(instruction) { * * @return {String} */ -JS2SVG.prototype.createComment = function(comment) { - - return this.config.commentStart + - comment + - this.config.commentEnd; - +JS2SVG.prototype.createComment = function (comment) { + return this.config.commentStart + comment + this.config.commentEnd; }; /** @@ -205,13 +185,10 @@ JS2SVG.prototype.createComment = function(comment) { * * @return {String} */ -JS2SVG.prototype.createCDATA = function(cdata) { - - return this.createIndent() + - this.config.cdataStart + - cdata + - this.config.cdataEnd; - +JS2SVG.prototype.createCDATA = function (cdata) { + return ( + this.createIndent() + this.config.cdataStart + cdata + this.config.cdataEnd + ); }; /** @@ -221,80 +198,79 @@ JS2SVG.prototype.createCDATA = function(cdata) { * * @return {String} */ -JS2SVG.prototype.createElem = function(data) { +JS2SVG.prototype.createElem = function (data) { + // beautiful injection for obtaining SVG information :) + if (data.isElem('svg') && data.hasAttr('width') && data.hasAttr('height')) { + this.width = data.attr('width').value; + this.height = data.attr('height').value; + } - // beautiful injection for obtaining SVG information :) - if ( - data.isElem('svg') && - data.hasAttr('width') && - data.hasAttr('height') - ) { - this.width = data.attr('width').value; - this.height = data.attr('height').value; - } - - // empty element and short tag - if (data.isEmpty()) { - if (this.config.useShortTags) { - return this.createIndent() + - this.config.tagShortStart + - data.elem + - this.createAttrs(data) + - this.config.tagShortEnd; - } else { - return this.createIndent() + - this.config.tagShortStart + - data.elem + - this.createAttrs(data) + - this.config.tagOpenEnd + - this.config.tagCloseStart + - data.elem + - this.config.tagCloseEnd; - } - // non-empty element + // empty element and short tag + if (data.isEmpty()) { + if (this.config.useShortTags) { + return ( + this.createIndent() + + this.config.tagShortStart + + data.elem + + this.createAttrs(data) + + this.config.tagShortEnd + ); } else { - var tagOpenStart = this.config.tagOpenStart, - tagOpenEnd = this.config.tagOpenEnd, - tagCloseStart = this.config.tagCloseStart, - tagCloseEnd = this.config.tagCloseEnd, - openIndent = this.createIndent(), - closeIndent = this.createIndent(), - processedData = '', - dataEnd = ''; - - if (this.textContext) { - tagOpenStart = defaults.tagOpenStart; - tagOpenEnd = defaults.tagOpenEnd; - tagCloseStart = defaults.tagCloseStart; - tagCloseEnd = defaults.tagCloseEnd; - openIndent = ''; - } else if (data.isElem(textElems)) { - tagOpenEnd = defaults.tagOpenEnd; - tagCloseStart = defaults.tagCloseStart; - closeIndent = ''; - this.textContext = data; - } - - processedData += this.convert(data).data; - - if (this.textContext == data) { - this.textContext = null; - } - - return openIndent + - tagOpenStart + - data.elem + - this.createAttrs(data) + - tagOpenEnd + - processedData + - dataEnd + - closeIndent + - tagCloseStart + - data.elem + - tagCloseEnd; + return ( + this.createIndent() + + this.config.tagShortStart + + data.elem + + this.createAttrs(data) + + this.config.tagOpenEnd + + this.config.tagCloseStart + + data.elem + + this.config.tagCloseEnd + ); + } + // non-empty element + } else { + var tagOpenStart = this.config.tagOpenStart, + tagOpenEnd = this.config.tagOpenEnd, + tagCloseStart = this.config.tagCloseStart, + tagCloseEnd = this.config.tagCloseEnd, + openIndent = this.createIndent(), + closeIndent = this.createIndent(), + processedData = '', + dataEnd = ''; + if (this.textContext) { + tagOpenStart = defaults.tagOpenStart; + tagOpenEnd = defaults.tagOpenEnd; + tagCloseStart = defaults.tagCloseStart; + tagCloseEnd = defaults.tagCloseEnd; + openIndent = ''; + } else if (data.isElem(textElems)) { + tagOpenEnd = defaults.tagOpenEnd; + tagCloseStart = defaults.tagCloseStart; + closeIndent = ''; + this.textContext = data; } + processedData += this.convert(data).data; + + if (this.textContext == data) { + this.textContext = null; + } + + return ( + openIndent + + tagOpenStart + + data.elem + + this.createAttrs(data) + + tagOpenEnd + + processedData + + dataEnd + + closeIndent + + tagCloseStart + + data.elem + + tagCloseEnd + ); + } }; /** @@ -304,29 +280,26 @@ JS2SVG.prototype.createElem = function(data) { * * @return {String} */ -JS2SVG.prototype.createAttrs = function(elem) { +JS2SVG.prototype.createAttrs = function (elem) { + var attrs = ''; - var attrs = ''; - - elem.eachAttr(function(attr) { - - if (attr.value !== undefined) { - attrs += ' ' + - attr.name + - this.config.attrStart + - String(attr.value).replace(this.config.regValEntities, this.config.encodeEntity) + - this.config.attrEnd; - } - else { - attrs += ' ' + - attr.name; - } - - - }, this); - - return attrs; + elem.eachAttr(function (attr) { + if (attr.value !== undefined) { + attrs += + ' ' + + attr.name + + this.config.attrStart + + String(attr.value).replace( + this.config.regValEntities, + this.config.encodeEntity + ) + + this.config.attrEnd; + } else { + attrs += ' ' + attr.name; + } + }, this); + return attrs; }; /** @@ -336,11 +309,11 @@ JS2SVG.prototype.createAttrs = function(elem) { * * @return {String} */ -JS2SVG.prototype.createText = function(text) { - - return this.createIndent() + - this.config.textStart + - text.replace(this.config.regEntities, this.config.encodeEntity) + - (this.textContext ? '' : this.config.textEnd); - +JS2SVG.prototype.createText = function (text) { + return ( + this.createIndent() + + this.config.textStart + + text.replace(this.config.regEntities, this.config.encodeEntity) + + (this.textContext ? '' : this.config.textEnd) + ); }; diff --git a/lib/svgo/jsAPI.js b/lib/svgo/jsAPI.js index 282ab6b4..df9993be 100644 --- a/lib/svgo/jsAPI.js +++ b/lib/svgo/jsAPI.js @@ -5,57 +5,58 @@ const svgoCssSelectAdapter = require('./css-select-adapter'); var cssSelectOpts = { xmlMode: true, - adapter: svgoCssSelectAdapter + adapter: svgoCssSelectAdapter, }; -var JSAPI = module.exports = function(data, parentNode) { - Object.assign(this, data); - if (parentNode) { - Object.defineProperty(this, 'parentNode', { - writable: true, - value: parentNode - }); - } +var JSAPI = function (data, parentNode) { + Object.assign(this, data); + if (parentNode) { + Object.defineProperty(this, 'parentNode', { + writable: true, + value: parentNode, + }); + } }; +module.exports = JSAPI; /** * Perform a deep clone of this node. * * @return {Object} element */ -JSAPI.prototype.clone = function() { - var node = this; - var nodeData = {}; +JSAPI.prototype.clone = function () { + var node = this; + var nodeData = {}; - Object.keys(node).forEach(function(key) { - if (key !== 'class' && key !== 'style' && key !== 'content') { - nodeData[key] = node[key]; - } + Object.keys(node).forEach(function (key) { + if (key !== 'class' && key !== 'style' && key !== 'content') { + nodeData[key] = node[key]; + } + }); + + // Deep-clone node data. + nodeData = JSON.parse(JSON.stringify(nodeData)); + + // parentNode gets set to a proper object by the parent clone, + // but it needs to be true/false now to do the right thing + // in the constructor. + var clonedNode = new JSAPI(nodeData, !!node.parentNode); + + if (node.class) { + clonedNode.class = node.class.clone(clonedNode); + } + if (node.style) { + clonedNode.style = node.style.clone(clonedNode); + } + if (node.content) { + clonedNode.content = node.content.map(function (childNode) { + var clonedChild = childNode.clone(); + clonedChild.parentNode = clonedNode; + return clonedChild; }); + } - // Deep-clone node data. - nodeData = JSON.parse(JSON.stringify(nodeData)); - - // parentNode gets set to a proper object by the parent clone, - // but it needs to be true/false now to do the right thing - // in the constructor. - var clonedNode = new JSAPI(nodeData, !!node.parentNode); - - if (node.class) { - clonedNode.class = node.class.clone(clonedNode); - } - if (node.style) { - clonedNode.style = node.style.clone(clonedNode); - } - if (node.content) { - clonedNode.content = node.content.map(function(childNode) { - var clonedChild = childNode.clone(); - clonedChild.parentNode = clonedNode; - return clonedChild; - }); - } - - return clonedNode; + return clonedNode; }; /** @@ -65,14 +66,12 @@ JSAPI.prototype.clone = function() { * @param {String|Array} [param] element name or names arrays * @return {Boolean} */ -JSAPI.prototype.isElem = function(param) { +JSAPI.prototype.isElem = function (param) { + if (!param) return !!this.elem; - if (!param) return !!this.elem; - - if (Array.isArray(param)) return !!this.elem && (param.indexOf(this.elem) > -1); - - return !!this.elem && this.elem === param; + if (Array.isArray(param)) return !!this.elem && param.indexOf(this.elem) > -1; + return !!this.elem && this.elem === param; }; /** @@ -81,13 +80,10 @@ JSAPI.prototype.isElem = function(param) { * @param {String} name new element name * @return {Object} element */ -JSAPI.prototype.renameElem = function(name) { - - if (name && typeof name === 'string') - this.elem = this.local = name; - - return this; +JSAPI.prototype.renameElem = function (name) { + if (name && typeof name === 'string') this.elem = this.local = name; + return this; }; /** @@ -95,10 +91,8 @@ JSAPI.prototype.renameElem = function(name) { * * @return {Boolean} */ - JSAPI.prototype.isEmpty = function() { - - return !this.content || !this.content.length; - +JSAPI.prototype.isEmpty = function () { + return !this.content || !this.content.length; }; /** @@ -107,12 +101,12 @@ JSAPI.prototype.renameElem = function(name) { * * @return {?Object} */ - JSAPI.prototype.closestElem = function(elemName) { - var elem = this; +JSAPI.prototype.closestElem = function (elemName) { + var elem = this; - while ((elem = elem.parentNode) && !elem.isElem(elemName)); + while ((elem = elem.parentNode) && !elem.isElem(elemName)); - return elem; + return elem; }; /** @@ -123,18 +117,17 @@ JSAPI.prototype.renameElem = function(name) { * @param {Array|Object} [insertion] Elements to add to the content. * @return {Array} Removed elements. */ - JSAPI.prototype.spliceContent = function(start, n, insertion) { +JSAPI.prototype.spliceContent = function (start, n, insertion) { + if (arguments.length < 2) return []; - if (arguments.length < 2) return []; - - if (!Array.isArray(insertion)) - insertion = Array.apply(null, arguments).slice(2); - - insertion.forEach(function(inner) { inner.parentNode = this }, this); - - return this.content.splice.apply(this.content, [start, n].concat(insertion)); + if (!Array.isArray(insertion)) + insertion = Array.apply(null, arguments).slice(2); + insertion.forEach(function (inner) { + inner.parentNode = this; + }, this); + return this.content.splice.apply(this.content, [start, n].concat(insertion)); }; /** @@ -145,16 +138,15 @@ JSAPI.prototype.renameElem = function(name) { * @param {String} [val] attribute value (will be toString()'ed) * @return {Boolean} */ - JSAPI.prototype.hasAttr = function(name, val) { +JSAPI.prototype.hasAttr = function (name, val) { + if (!this.attrs || !Object.keys(this.attrs).length) return false; - if (!this.attrs || !Object.keys(this.attrs).length) return false; + if (!arguments.length) return !!this.attrs; - if (!arguments.length) return !!this.attrs; - - if (val !== undefined) return !!this.attrs[name] && this.attrs[name].value === val.toString(); - - return !!this.attrs[name]; + if (val !== undefined) + return !!this.attrs[name] && this.attrs[name].value === val.toString(); + return !!this.attrs[name]; }; /** @@ -165,39 +157,44 @@ JSAPI.prototype.renameElem = function(name) { * @param {Number|String|RegExp|Function} [val] attribute value (will be toString()'ed or executed, otherwise ignored) * @return {Boolean} */ - JSAPI.prototype.hasAttrLocal = function(localName, val) { +JSAPI.prototype.hasAttrLocal = function (localName, val) { + if (!this.attrs || !Object.keys(this.attrs).length) return false; - if (!this.attrs || !Object.keys(this.attrs).length) return false; + if (!arguments.length) return !!this.attrs; - if (!arguments.length) return !!this.attrs; + var callback; - var callback; + switch (val != null && val.constructor && val.constructor.name) { + case 'Number': // same as String + case 'String': + callback = stringValueTest; + break; + case 'RegExp': + callback = regexpValueTest; + break; + case 'Function': + callback = funcValueTest; + break; + default: + callback = nameTest; + } + return this.someAttr(callback); - switch (val != null && val.constructor && val.constructor.name) { - case 'Number': // same as String - case 'String': callback = stringValueTest; break; - case 'RegExp': callback = regexpValueTest; break; - case 'Function': callback = funcValueTest; break; - default: callback = nameTest; - } - return this.someAttr(callback); + function nameTest(attr) { + return attr.local === localName; + } - function nameTest(attr) { - return attr.local === localName; - } + function stringValueTest(attr) { + return attr.local === localName && val == attr.value; + } - function stringValueTest(attr) { - return attr.local === localName && val == attr.value; - } - - function regexpValueTest(attr) { - return attr.local === localName && val.test(attr.value); - } - - function funcValueTest(attr) { - return attr.local === localName && val(attr.value); - } + function regexpValueTest(attr) { + return attr.local === localName && val.test(attr.value); + } + function funcValueTest(attr) { + return attr.local === localName && val(attr.value); + } }; /** @@ -208,14 +205,13 @@ JSAPI.prototype.renameElem = function(name) { * @param {String} [val] attribute value (will be toString()'ed) * @return {Object|Undefined} */ - JSAPI.prototype.attr = function(name, val) { +JSAPI.prototype.attr = function (name, val) { + if (!this.hasAttr() || !arguments.length) return undefined; - if (!this.hasAttr() || !arguments.length) return undefined; - - if (val !== undefined) return this.hasAttr(name, val) ? this.attrs[name] : undefined; - - return this.attrs[name]; + if (val !== undefined) + return this.hasAttr(name, val) ? this.attrs[name] : undefined; + return this.attrs[name]; }; /** @@ -224,17 +220,20 @@ JSAPI.prototype.renameElem = function(name) { * @param {String} name attribute name * @return {Object|Undefined} */ - JSAPI.prototype.computedAttr = function(name, val) { - if (!arguments.length) return; +JSAPI.prototype.computedAttr = function (name, val) { + if (!arguments.length) return; - for (var elem = this; elem && (!elem.hasAttr(name) || !elem.attr(name).value); elem = elem.parentNode); - - if (val != null) { - return elem ? elem.hasAttr(name, val) : false; - } else if (elem && elem.hasAttr(name)) { - return elem.attrs[name].value; - } + for ( + var elem = this; + elem && (!elem.hasAttr(name) || !elem.attr(name).value); + elem = elem.parentNode + ); + if (val != null) { + return elem ? elem.hasAttr(name, val) : false; + } else if (elem && elem.hasAttr(name)) { + return elem.attrs[name].value; + } }; /** @@ -244,25 +243,23 @@ JSAPI.prototype.renameElem = function(name) { * @param {String} [val] attribute value * @return {Boolean} */ - JSAPI.prototype.removeAttr = function(name, val, recursive) { +JSAPI.prototype.removeAttr = function (name, val, recursive) { + if (!arguments.length) return false; - if (!arguments.length) return false; + if (Array.isArray(name)) { + name.forEach(this.removeAttr, this); + return false; + } - if (Array.isArray(name)) { - name.forEach(this.removeAttr, this); - return false; - } + if (!this.hasAttr(name)) return false; - if (!this.hasAttr(name)) return false; + if (!recursive && val && this.attrs[name].value !== val) return false; - if (!recursive && val && this.attrs[name].value !== val) return false; + delete this.attrs[name]; - delete this.attrs[name]; - - if (!Object.keys(this.attrs).length) delete this.attrs; - - return true; + if (!Object.keys(this.attrs).length) delete this.attrs; + return true; }; /** @@ -271,27 +268,30 @@ JSAPI.prototype.renameElem = function(name) { * @param {Object} [attr={}] attribute object * @return {Object|Boolean} created attribute or false if no attr was passed in */ - JSAPI.prototype.addAttr = function(attr) { - attr = attr || {}; +JSAPI.prototype.addAttr = function (attr) { + attr = attr || {}; - if (attr.name === undefined || - attr.prefix === undefined || - attr.local === undefined - ) return false; + if ( + attr.name === undefined || + attr.prefix === undefined || + attr.local === undefined + ) + return false; - this.attrs = this.attrs || {}; - this.attrs[attr.name] = attr; + this.attrs = this.attrs || {}; + this.attrs[attr.name] = attr; - if(attr.name === 'class') { // newly added class attribute - this.class.hasClass(); - } + if (attr.name === 'class') { + // newly added class attribute + this.class.hasClass(); + } - if(attr.name === 'style') { // newly added style attribute - this.style.hasStyle(); - } - - return this.attrs[attr.name]; + if (attr.name === 'style') { + // newly added style attribute + this.style.hasStyle(); + } + return this.attrs[attr.name]; }; /** @@ -301,16 +301,14 @@ JSAPI.prototype.renameElem = function(name) { * @param {Object} [context] callback context * @return {Boolean} false if there are no any attributes */ - JSAPI.prototype.eachAttr = function(callback, context) { +JSAPI.prototype.eachAttr = function (callback, context) { + if (!this.hasAttr()) return false; - if (!this.hasAttr()) return false; - - for (const attr of Object.values(this.attrs)) { - callback.call(context, attr); - } - - return true; + for (const attr of Object.values(this.attrs)) { + callback.call(context, attr); + } + return true; }; /** @@ -320,16 +318,14 @@ JSAPI.prototype.renameElem = function(name) { * @param {Object} [context] callback context * @return {Boolean} false if there are no any attributes */ - JSAPI.prototype.someAttr = function(callback, context) { +JSAPI.prototype.someAttr = function (callback, context) { + if (!this.hasAttr()) return false; - if (!this.hasAttr()) return false; - - for (const attr of Object.values(this.attrs)) { - if (callback.call(context, attr)) return true; - } - - return false; + for (const attr of Object.values(this.attrs)) { + if (callback.call(context, attr)) return true; + } + return false; }; /** @@ -338,12 +334,10 @@ JSAPI.prototype.renameElem = function(name) { * @param {String} selectors CSS selector(s) string * @return {Array} null if no elements matched */ - JSAPI.prototype.querySelectorAll = function(selectors) { - - var matchedEls = selectAll(selectors, this, cssSelectOpts); - - return matchedEls.length > 0 ? matchedEls : null; +JSAPI.prototype.querySelectorAll = function (selectors) { + var matchedEls = selectAll(selectors, this, cssSelectOpts); + return matchedEls.length > 0 ? matchedEls : null; }; /** @@ -352,10 +346,8 @@ JSAPI.prototype.renameElem = function(name) { * @param {String} selectors CSS selector(s) string * @return {Array} null if no element matched */ - JSAPI.prototype.querySelector = function(selectors) { - - return selectOne(selectors, this, cssSelectOpts); - +JSAPI.prototype.querySelector = function (selectors) { + return selectOne(selectors, this, cssSelectOpts); }; /** @@ -364,8 +356,6 @@ JSAPI.prototype.renameElem = function(name) { * @param {String} selector CSS selector string * @return {Boolean} true if element would be selected by selector string, false if it does not */ - JSAPI.prototype.matches = function(selector) { - - return is(this, selector, cssSelectOpts); - +JSAPI.prototype.matches = function (selector) { + return is(this, selector, cssSelectOpts); }; diff --git a/lib/svgo/plugins.js b/lib/svgo/plugins.js index 26656d12..b4b32e06 100644 --- a/lib/svgo/plugins.js +++ b/lib/svgo/plugins.js @@ -10,7 +10,7 @@ * @param {Object} plugins plugins object from config * @return {Object} output data */ -module.exports = function(data, info, plugins) { +module.exports = function (data, info, plugins) { // Try to group sequential elements of plugins array // to optimize ast traversing const groups = []; @@ -24,7 +24,7 @@ module.exports = function(data, info, plugins) { } } for (const group of groups) { - switch(group[0].type) { + switch (group[0].type) { case 'perItem': data = perItem(data, info, group); break; @@ -49,42 +49,36 @@ module.exports = function(data, info, plugins) { * @return {Object} output data */ function perItem(data, info, plugins, reverse) { + function monkeys(items) { + items.content = items.content.filter(function (item) { + // reverse pass + if (reverse && item.content) { + monkeys(item); + } - function monkeys(items) { + // main filter + var filter = true; - items.content = items.content.filter(function(item) { + for (var i = 0; filter && i < plugins.length; i++) { + var plugin = plugins[i]; - // reverse pass - if (reverse && item.content) { - monkeys(item); - } + if (plugin.active && plugin.fn(item, plugin.params, info) === false) { + filter = false; + } + } - // main filter - var filter = true; + // direct pass + if (!reverse && item.content) { + monkeys(item); + } - for (var i = 0; filter && i < plugins.length; i++) { - var plugin = plugins[i]; + return filter; + }); - if (plugin.active && plugin.fn(item, plugin.params, info) === false) { - filter = false; - } - } - - // direct pass - if (!reverse && item.content) { - monkeys(item); - } - - return filter; - - }); - - return items; - - } - - return monkeys(data); + return items; + } + return monkeys(data); } /** @@ -96,13 +90,11 @@ function perItem(data, info, plugins, reverse) { * @return {Object} output data */ function full(data, info, plugins) { + plugins.forEach(function (plugin) { + if (plugin.active) { + data = plugin.fn(data, plugin.params, info); + } + }); - plugins.forEach(function(plugin) { - if (plugin.active) { - data = plugin.fn(data, plugin.params, info); - } - }); - - return data; - + return data; } diff --git a/lib/svgo/svg2js.js b/lib/svgo/svg2js.js index 83f46659..17cf8e15 100644 --- a/lib/svgo/svg2js.js +++ b/lib/svgo/svg2js.js @@ -1,19 +1,19 @@ 'use strict'; var SAX = require('@trysound/sax'), - JSAPI = require('./jsAPI.js'), - CSSClassList = require('./css-class-list'), - CSSStyleDeclaration = require('./css-style-declaration'), - textElems = require('../../plugins/_collections.js').textElems, - entityDeclaration = //g; + JSAPI = require('./jsAPI.js'), + CSSClassList = require('./css-class-list'), + CSSStyleDeclaration = require('./css-style-declaration'), + textElems = require('../../plugins/_collections.js').textElems, + entityDeclaration = //g; var config = { - strict: true, - trim: false, - normalize: false, - lowercase: true, - xmlns: true, - position: true + strict: true, + trim: false, + normalize: false, + lowercase: true, + xmlns: true, + position: true, }; /** @@ -21,132 +21,118 @@ var config = { * * @param {String} data input data */ -module.exports = function(data) { +module.exports = function (data) { + var sax = SAX.parser(config.strict, config), + root = new JSAPI({ elem: '#document', content: [] }), + current = root, + stack = [root]; - var sax = SAX.parser(config.strict, config), - root = new JSAPI({ elem: '#document', content: [] }), - current = root, - stack = [root]; + function pushToContent(content) { + content = new JSAPI(content, current); - function pushToContent(content) { + (current.content = current.content || []).push(content); - content = new JSAPI(content, current); + return content; + } - (current.content = current.content || []).push(content); + sax.ondoctype = function (doctype) { + pushToContent({ + doctype: doctype, + }); - return content; + var subsetStart = doctype.indexOf('['), + entityMatch; - } + if (subsetStart >= 0) { + entityDeclaration.lastIndex = subsetStart; - sax.ondoctype = function(doctype) { - - pushToContent({ - doctype: doctype - }); - - var subsetStart = doctype.indexOf('['), - entityMatch; - - if (subsetStart >= 0) { - entityDeclaration.lastIndex = subsetStart; - - while ((entityMatch = entityDeclaration.exec(data)) != null) { - sax.ENTITIES[entityMatch[1]] = entityMatch[2] || entityMatch[3]; - } - } - }; - - sax.onprocessinginstruction = function(data) { - - pushToContent({ - processinginstruction: data - }); - - }; - - sax.oncomment = function(comment) { - - pushToContent({ - comment: comment.trim() - }); - - }; - - sax.oncdata = function(cdata) { - - pushToContent({ - cdata: cdata - }); - - }; - - sax.onopentag = function(data) { - - var elem = { - elem: data.name, - prefix: data.prefix, - local: data.local, - attrs: {} - }; - - elem.class = new CSSClassList(elem); - elem.style = new CSSStyleDeclaration(elem); - - if (Object.keys(data.attributes).length) { - for (const [name, attr] of Object.entries(data.attributes)) { - - if (name === 'class') { // has class attribute - elem.class.hasClass(); - } - - if (name === 'style') { // has style attribute - elem.style.hasStyle(); - } - - elem.attrs[name] = { - name: name, - value: attr.value, - prefix: attr.prefix, - local: attr.local - }; - } - } - - elem = pushToContent(elem); - current = elem; - - stack.push(elem); - - }; - - sax.ontext = function(text) { - // prevent trimming of meaningful whitespace inside textual tags - if (textElems.includes(current.elem) && !data.prefix) { - pushToContent({ text: text }); - } else if (/\S/.test(text)) { - pushToContent({ text: text.trim() }); + while ((entityMatch = entityDeclaration.exec(data)) != null) { + sax.ENTITIES[entityMatch[1]] = entityMatch[2] || entityMatch[3]; } + } + }; + + sax.onprocessinginstruction = function (data) { + pushToContent({ + processinginstruction: data, + }); + }; + + sax.oncomment = function (comment) { + pushToContent({ + comment: comment.trim(), + }); + }; + + sax.oncdata = function (cdata) { + pushToContent({ + cdata: cdata, + }); + }; + + sax.onopentag = function (data) { + var elem = { + elem: data.name, + prefix: data.prefix, + local: data.local, + attrs: {}, }; - sax.onclosetag = function() { - stack.pop(); - current = stack[stack.length - 1]; - }; + elem.class = new CSSClassList(elem); + elem.style = new CSSStyleDeclaration(elem); - sax.onerror = function(e) { - - e.message = 'Error in parsing SVG: ' + e.message; - if (e.message.indexOf('Unexpected end') < 0) { - throw e; + if (Object.keys(data.attributes).length) { + for (const [name, attr] of Object.entries(data.attributes)) { + if (name === 'class') { + // has class attribute + elem.class.hasClass(); } - }; + if (name === 'style') { + // has style attribute + elem.style.hasStyle(); + } - try { - sax.write(data).close(); - return root; - } catch (e) { - return { error: e.message }; + elem.attrs[name] = { + name: name, + value: attr.value, + prefix: attr.prefix, + local: attr.local, + }; + } } + elem = pushToContent(elem); + current = elem; + + stack.push(elem); + }; + + sax.ontext = function (text) { + // prevent trimming of meaningful whitespace inside textual tags + if (textElems.includes(current.elem) && !data.prefix) { + pushToContent({ text: text }); + } else if (/\S/.test(text)) { + pushToContent({ text: text.trim() }); + } + }; + + sax.onclosetag = function () { + stack.pop(); + current = stack[stack.length - 1]; + }; + + sax.onerror = function (e) { + e.message = 'Error in parsing SVG: ' + e.message; + if (e.message.indexOf('Unexpected end') < 0) { + throw e; + } + }; + + try { + sax.write(data).close(); + return root; + } catch (e) { + return { error: e.message }; + } }; diff --git a/lib/svgo/tools.js b/lib/svgo/tools.js index 3401a850..ec183209 100644 --- a/lib/svgo/tools.js +++ b/lib/svgo/tools.js @@ -7,20 +7,20 @@ * @param {String} type Data URI type * @return {String} output string */ -exports.encodeSVGDatauri = function(str, type) { - var prefix = 'data:image/svg+xml'; - if (!type || type === 'base64') { - // base64 - prefix += ';base64,'; - str = prefix + Buffer.from(str).toString('base64'); - } else if (type === 'enc') { - // URI encoded - str = prefix + ',' + encodeURIComponent(str); - } else if (type === 'unenc') { - // unencoded - str = prefix + ',' + str; - } - return str; +exports.encodeSVGDatauri = function (str, type) { + var prefix = 'data:image/svg+xml'; + if (!type || type === 'base64') { + // base64 + prefix += ';base64,'; + str = prefix + Buffer.from(str).toString('base64'); + } else if (type === 'enc') { + // URI encoded + str = prefix + ',' + encodeURIComponent(str); + } else if (type === 'unenc') { + // unencoded + str = prefix + ',' + str; + } + return str; }; /** @@ -29,32 +29,32 @@ exports.encodeSVGDatauri = function(str, type) { * @param {string} str input string * @return {String} output string */ -exports.decodeSVGDatauri = function(str) { - var regexp = /data:image\/svg\+xml(;charset=[^;,]*)?(;base64)?,(.*)/; - var match = regexp.exec(str); +exports.decodeSVGDatauri = function (str) { + var regexp = /data:image\/svg\+xml(;charset=[^;,]*)?(;base64)?,(.*)/; + var match = regexp.exec(str); - // plain string - if (!match) return str; + // plain string + if (!match) return str; - var data = match[3]; + var data = match[3]; - if (match[2]) { - // base64 - str = Buffer.from(data, 'base64').toString('utf8'); - } else if (data.charAt(0) === '%') { - // URI encoded - str = decodeURIComponent(data); - } else if (data.charAt(0) === '<') { - // unencoded - str = data; - } - return str; + if (match[2]) { + // base64 + str = Buffer.from(data, 'base64').toString('utf8'); + } else if (data.charAt(0) === '%') { + // URI encoded + str = decodeURIComponent(data); + } else if (data.charAt(0) === '<') { + // unencoded + str = data; + } + return str; }; -exports.intersectArrays = function(a, b) { - return a.filter(function(n) { - return b.indexOf(n) > -1; - }); +exports.intersectArrays = function (a, b) { + return a.filter(function (n) { + return b.indexOf(n) > -1; + }); }; /** @@ -68,48 +68,46 @@ exports.intersectArrays = function(a, b) { * @param {string?} command path data instruction * @return {string} */ -exports.cleanupOutData = function(data, params, command) { - var str = '', - delimiter, - prev; +exports.cleanupOutData = function (data, params, command) { + var str = '', + delimiter, + prev; - data.forEach(function(item, i) { - // space delimiter by default - delimiter = ' '; + data.forEach(function (item, i) { + // space delimiter by default + delimiter = ' '; - // no extra space in front of first number - if (i == 0) delimiter = ''; + // no extra space in front of first number + if (i == 0) delimiter = ''; - // no extra space after 'arcto' command flags(large-arc and sweep flags) - // a20 60 45 0 1 30 20 → a20 60 45 0130 20 - if (params.noSpaceAfterFlags && (command == 'A' || command == 'a')) { - var pos = i % 7; - if (pos == 4 || pos == 5) delimiter = ''; - } + // no extra space after 'arcto' command flags(large-arc and sweep flags) + // a20 60 45 0 1 30 20 → a20 60 45 0130 20 + if (params.noSpaceAfterFlags && (command == 'A' || command == 'a')) { + var pos = i % 7; + if (pos == 4 || pos == 5) delimiter = ''; + } - // remove floating-point numbers leading zeros - // 0.5 → .5 - // -0.5 → -.5 - if (params.leadingZero) { - item = removeLeadingZero(item); - } + // remove floating-point numbers leading zeros + // 0.5 → .5 + // -0.5 → -.5 + if (params.leadingZero) { + item = removeLeadingZero(item); + } - // no extra space in front of negative number or - // in front of a floating number if a previous number is floating too - if ( - params.negativeExtraSpace && - delimiter != '' && - (item < 0 || - (String(item).charCodeAt(0) == 46 && prev % 1 !== 0) - ) - ) { - delimiter = ''; - } - // save prev item value - prev = item; - str += delimiter + item; - }); - return str; + // no extra space in front of negative number or + // in front of a floating number if a previous number is floating too + if ( + params.negativeExtraSpace && + delimiter != '' && + (item < 0 || (String(item).charCodeAt(0) == 46 && prev % 1 !== 0)) + ) { + delimiter = ''; + } + // save prev item value + prev = item; + str += delimiter + item; + }); + return str; }; /** @@ -125,13 +123,14 @@ exports.cleanupOutData = function(data, params, command) { * * @return {String} output number as string */ -var removeLeadingZero = exports.removeLeadingZero = function(num) { - var strNum = num.toString(); +var removeLeadingZero = function (num) { + var strNum = num.toString(); - if (0 < num && num < 1 && strNum.charCodeAt(0) == 48) { - strNum = strNum.slice(1); - } else if (-1 < num && num < 0 && strNum.charCodeAt(1) == 48) { - strNum = strNum.charAt(0) + strNum.slice(2); - } - return strNum; + if (0 < num && num < 1 && strNum.charCodeAt(0) == 48) { + strNum = strNum.slice(1); + } else if (-1 < num && num < 0 && strNum.charCodeAt(1) == 48) { + strNum = strNum.charAt(0) + strNum.slice(2); + } + return strNum; }; +exports.removeLeadingZero = removeLeadingZero;