1
0
mirror of https://github.com/svg/svgo.git synced 2025-07-29 20:21:14 +03:00

Format lib with prettier (#1386)

Note: review with hidden whitespaces
This commit is contained in:
Bogdan Chadkin
2021-02-28 10:56:16 +03:00
committed by GitHub
parent a99cc08e4f
commit 71e47370bd
13 changed files with 1312 additions and 1282 deletions

View File

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

View File

@ -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);
}

View File

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

View File

@ -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...>', 'Input files, "-" for STDIN')
.option('-s, --string <STRING>', 'Input SVG data string')
.option('-f, --folder <FOLDER>', 'Input folder, optimize and rewrite all *.svg files')
.option('-o, --output <OUTPUT...>', 'Output file or folder (by default the same as the input), "-" for STDOUT')
.option('-p, --precision <INTEGER>', 'Set number of digits in the fractional part, overrides plugins params')
.option('--config <CONFIG>', 'Custom config file, only .js is supported')
.option('--datauri <FORMAT>', '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 <INTEGER>', '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...>', 'Input files, "-" for STDIN')
.option('-s, --string <STRING>', 'Input SVG data string')
.option(
'-f, --folder <FOLDER>',
'Input folder, optimize and rewrite all *.svg files'
)
.option(
'-o, --output <OUTPUT...>',
'Output file or folder (by default the same as the input), "-" for STDOUT'
)
.option(
'-p, --precision <INTEGER>',
'Set number of digits in the fractional part, overrides plugins params'
)
.option('--config <CONFIG>', 'Custom config file, only .js is supported')
.option(
'--datauri <FORMAT>',
'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 <INTEGER>', '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;

View File

@ -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 },
};
}
}

View File

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

View File

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

View File

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

View File

@ -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: '<!DOCTYPE',
doctypeEnd: '>',
procInstStart: '<?',
procInstEnd: '?>',
tagOpenStart: '<',
tagOpenEnd: '>',
tagCloseStart: '</',
tagCloseEnd: '>',
tagShortStart: '<',
tagShortEnd: '/>',
attrStart: '="',
attrEnd: '"',
commentStart: '<!--',
commentEnd: '-->',
cdataStart: '<![CDATA[',
cdataEnd: ']]>',
textStart: '',
textEnd: '',
indent: 4,
regEntities: /[&'"<>]/g,
regValEntities: /[&"<>]/g,
encodeEntity: encodeEntity,
pretty: false,
useShortTags: true
doctypeStart: '<!DOCTYPE',
doctypeEnd: '>',
procInstStart: '<?',
procInstEnd: '?>',
tagOpenStart: '<',
tagOpenEnd: '>',
tagCloseStart: '</',
tagCloseEnd: '>',
tagShortStart: '<',
tagShortEnd: '/>',
attrStart: '="',
attrEnd: '"',
commentStart: '<!--',
commentEnd: '-->',
cdataStart: '<![CDATA[',
cdataEnd: ']]>',
textStart: '',
textEnd: '',
indent: 4,
regEntities: /[&'"<>]/g,
regValEntities: /[&"<>]/g,
encodeEntity: encodeEntity,
pretty: false,
useShortTags: true,
};
var entities = {
'&': '&amp;',
'\'': '&apos;',
'"': '&quot;',
'>': '&gt;',
'<': '&lt;',
};
'&': '&amp;',
"'": '&apos;',
'"': '&quot;',
'>': '&gt;',
'<': '&lt;',
};
/**
* 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)
);
};

View File

@ -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);
};

View File

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

View File

@ -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 = /<!ENTITY\s+(\S+)\s+(?:'([^']+)'|"([^"]+)")\s*>/g;
JSAPI = require('./jsAPI.js'),
CSSClassList = require('./css-class-list'),
CSSStyleDeclaration = require('./css-style-declaration'),
textElems = require('../../plugins/_collections.js').textElems,
entityDeclaration = /<!ENTITY\s+(\S+)\s+(?:'([^']+)'|"([^"]+)")\s*>/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 };
}
};

View File

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