1
0
mirror of https://github.com/svg/svgo.git synced 2025-07-28 09:22:00 +03:00

global refactoring dump

This commit is contained in:
deepsweet
2013-04-09 22:06:27 +03:00
parent f01c7f4b98
commit 2816424ed7
36 changed files with 932 additions and 981 deletions

250
.svgo.yml
View File

@ -1,216 +1,40 @@
plugins:
- name: removeDoctype
active: true
type: perItem
# - name
#
# or:
# - name: false
# - name: true
#
# or:
# - name:
# param1: 1
# param2: 2
- name: removeXMLProcInst
active: true
type: perItem
- name: removeComments
active: true
type: perItem
- name: removeMetadata
active: true
type: perItem
- name: removeEditorsNSData
active: true
type: perItem
- name: cleanupAttrs
active: true
type: perItem
params:
newlines: true
trim: true
spaces: true
- name: convertStyleToAttrs
active: true
type: perItem
- name: removeRasterImages
active: false
type: perItem
- name: cleanupNumericValues
active: true
type: perItem
params:
floatPrecision: 3
leadingZero: true
defaultPx: true
- name: convertColors
active: true
type: perItem
params:
names2hex: true
rgb2hex: true
shorthex: true
- name: removeUnknownsAndDefaults
active: true
type: perItem
params:
SVGid: true
unknownContent: true
unknownAttrs: true
defaultAttrs: true
- name: removeNonInheritableGroupAttrs
active: true
type: perItem
- name: removeUselessStrokeAndFill
active: true
type: perItem
params:
stroke: true
fill: true
- name: removeViewBox
active: true
type: perItem
- name: cleanupEnableBackground
active: true
type: perItem
- name: removeHiddenElems
active: true
type: perItem
params:
displayNone: true
opacity0: true
circleR0: true
ellipseRX0: true
ellipseRY0: true
rectWidth0: true
rectHeight0: true
patternWidth0: true
patternHeight0: true
imageWidth0: true
imageHeight0: true
pathEmptyD: true
polylineEmptyPoints: true
polygonEmptyPoints: true
- name: removeEmptyText
active: true
type: perItem
params:
text: true
tspan: true
tref: true
- name: moveElemsAttrsToGroup
active: true
type: perItemReverse
- name: collapseGroups
active: true
type: perItemReverse
- name: moveGroupAttrsToElems
active: true
type: perItemReverse
- name: convertPathData
active: true
type: perItem
params:
applyTransforms: true
straightCurves: true
lineShorthands: true
curveSmoothShorthands: true
floatPrecision: 3
removeUseless: true
collapseRepeated: true
leadingZero: true
negativeExtraSpace: true
- name: convertTransform
active: true
type: perItem
params:
convertToShorts: true
floatPrecision: 3
matrixToTransform: true
shortTranslate: true
shortScale: true
shortRotate: true
removeUseless: true
collapseIntoOne: true
leadingZero: true
negativeExtraSpace: false
- name: removeEmptyAttrs
active: true
type: perItem
- name: removeEmptyContainers
active: true
type: perItemReverse
- name: cleanupIDs
active: true
type: full
params:
remove: true
minify: true
- name: removeUnusedNS
active: true
type: full
- name: cropAndCenterAlongPath
active: false
type: full
params:
hcrop: true
vcenter: true
floatPrecision: 3
leadingZero: true
negativeExtraSpace: true
svg2js:
strict: true
trim: true
normalize: true
lowercase: true
xmlns: true
position: false
js2svg:
doctypeStart: "<!DOCTYPE"
doctypeEnd: ">"
procInstStart: "<?"
procInstEnd: "?>"
tagOpenStart: "<"
tagOpenEnd: ">"
tagCloseStart: "</"
tagCloseEnd: ">"
tagShortStart: "<"
tagShortEnd: "/>"
attrStart: "=\""
attrEnd: "\""
commentStart: "<!--"
commentEnd: "-->"
cdataStart: "<![CDATA["
cdataEnd: "]]>"
textStart: ""
textEnd: ""
indent: " "
entities:
"&": "&amp;"
"'": "&apos;"
"\"": "&quot;"
">": "&gt;"
"<": "&lt;"
pretty: false
- removeDoctype
- removeXMLProcInst
- removeComments
- removeMetadata
- removeEditorsNSData
- cleanupAttrs
- convertStyleToAttrs
- removeRasterImages
- cleanupNumericValues
- convertColors
- removeUnknownsAndDefaults
- removeNonInheritableGroupAttrs
- removeUselessStrokeAndFill
- removeViewBox
- cleanupEnableBackground
- removeHiddenElems
- removeEmptyText
- moveElemsAttrsToGroup
- collapseGroups
- moveGroupAttrsToElems
- convertPathData
- convertTransform
- removeEmptyAttrs
- removeEmptyContainers
- cleanupIDs
- removeUnusedNS
- cropAndCenterAlongPath

View File

@ -5,118 +5,32 @@
*
* @see http://deepsweet.github.com/svgo/
*
* @module svgo
*
* @author Kir Belevich <kir@soulshine.in> (https://github.com/deepsweet)
* @copyright © 2012 Kir Belevich
* @license MIT https://raw.github.com/deepsweet/svgo/master/LICENSE
*/
var INHERIT = require('inherit'),
Q = require('q'),
FS = require('fs'),
CONFIG = require('./svgo/config'),
var CONFIG = require('./svgo/config'),
SVG2JS = require('./svgo/svg2js'),
PLUGINS = require('./svgo/plugins'),
JS2SVG = require('./svgo/js2svg'),
decodeSVGDatauri = require('./svgo/tools').decodeSVGDatauri;
JS2SVG = require('./svgo/js2svg');
/**
* @class SVGO.
*/
module.exports = INHERIT(/** @lends SVGO.prototype */{
/**
* @param {Object} [config] custom config to extend default
*
* @constructs
*
* @private
*/
__constructor: function(config) {
var SVGO = module.exports = function(config) {
this.config = CONFIG(config);
},
};
/**
* Optimize SVG data from string.
*
* @param {String} str input string
*
* @return {Object} output string deferred promise
*/
fromString: function(str) {
SVGO.prototype.optimize = function(svgstr, callback) {
var startTime = Date.now();
var config = this.config;
str = decodeSVGDatauri(str);
SVG2JS(svgstr, function(svgjs) {
return this.config
.then(function(config) {
svgjs = PLUGINS(svgjs, config.plugins);
return SVG2JS(str, config.svg2js)
.then(function(jsdata) {
var result = JS2SVG(PLUGINS(jsdata, config.plugins), config.js2svg);
result.info.inBytes = Buffer.byteLength(str, 'utf-8');
result.info.outBytes = Buffer.byteLength(result.data, 'utf-8');
result.info.time = Date.now() - startTime;
return result;
callback(JS2SVG(svgjs, config.js2svg));
});
});
},
/**
* Optimize SVG data from Stream.
*
* @param {Object} stream input stream
*
* @return {Object} output string deferred promise
*/
fromStream: function(stream) {
var deferred = Q.defer(),
inputData = '',
self = this;
stream.pause();
stream
.on('data', function(chunk) {
inputData += chunk;
})
.once('end', function() {
deferred.resolve(inputData);
})
.resume();
return deferred.promise
.then(function(str) {
return self.fromString(str);
});
},
/**
* Optimize SVG data from file.
*
* @param {String} path file path
*
* @return {Object} output string deferred promise
*/
fromFile: function(path) {
return this.fromStream(FS.createReadStream(path, { encoding: 'utf8' }));
}
});
};

View File

@ -1,13 +1,13 @@
'use strict';
require('colors');
require('js-yaml');
var FS = require('fs'),
QFS = require('q-fs'),
PATH = require('path'),
UTIL = require('util'),
SVGO = require('../svgo'),
info = JSON.parse(require('fs').readFileSync(__dirname + '/../../package.json')),
PKG = require('../../package.json'),
encodeSVGDatauri = require('./tools').encodeSVGDatauri,
regSVGFile = /\.svg$/;
@ -15,20 +15,18 @@ var FS = require('fs'),
* Command-Option-Argument.
*
* @see https://github.com/veged/coa
*
* @module coa
*/
module.exports = require('coa').Cmd()
.helpful()
.name(info.name)
.title(info.description)
.name(PKG.name)
.title(PKG.description)
.opt()
.name('version').title('Version')
.short('v').long('version')
.only()
.flag()
.act(function() {
return info.version;
return PKG.version;
})
.end()
.opt()
@ -99,109 +97,144 @@ module.exports = require('coa').Cmd()
var input = args && args.input ? args.input : opts.input,
output = args && args.output ? args.output : opts.output,
string = opts.string,
folder = opts.folder,
svgo;
config;
// w/o anything
if (
(!input || input === '-') &&
!string &&
!opts.string &&
!opts.stdin &&
!opts.folder &&
process.stdin.isTTY
) return this.usage();
// --config
if (opts.config) {
// string
if (opts.config.charAt(0) === '{') {
config = JSON.parse(opts.config);
// external file
} else {
config = require(opts.config);
}
}
// --pretty
if (opts.pretty) {
config = config || {};
config.js2svg = config.js2svg || {};
config.js2svg.pretty = true;
}
// --folder
if (folder) {
optimizeFolder(folder, opts);
if (opts.folder) {
optimizeFolder(opts.folder, config);
return;
}
// --string
if (string) {
svgo = new SVGO({ coa: opts }).fromString(string);
}
// --inpput
if (input) {
// STDIN
else if (input === '-') {
svgo = new SVGO({ coa: opts }).fromStream(process.stdin);
}
if (input === '-') {
var data = '';
process.stdin.pause();
process.stdin
.on('data', function(chunk) {
data += chunk;
})
.once('end', function() {
optimizeFromString(data, config, input, output);
})
.resume();
// file
else if (input) {
svgo = new SVGO({ coa: opts }).fromFile(input);
} else {
FS.readFile(input, 'utf8', function(err, data) {
if (err) {
throw err;
}
return svgo.then(function(result) {
optimizeFromString(data, config, input, output);
});
// --datauri
if (opts.datauri) {
// convert to Data URI base64 string
result.data = encodeSVGDatauri(result.data);
}
// --string
} else if (opts.string) {
optimizeFromString(opts.string, config, input, output);
}
});
function optimizeFromString(svgstr, config, input, output) {
var startTime = Date.now(config),
time,
inBytes = Buffer.byteLength(svgstr, 'utf8'),
outBytes,
svgo = new SVGO(config);
svgo.optimize(svgstr, function(result) {
outBytes = Buffer.byteLength(result.data, 'utf8');
time = Date.now() - startTime;
// stdout
if (output === '-' || (input === '-' && !output)) {
process.stdout.write(result.data + '\n');
}
// file
else {
} else {
// if input is from file - overwrite it
// overwrite input file if there is no output
if (!output && input) {
output = input;
}
UTIL.puts('\r');
saveFileAndPrintInfo(result, output, opts.pretty);
saveFileAndPrintInfo(result.data, output, inBytes, outBytes, time);
}
})
.done();
});
/**
* Save file and print info.
*
* @param {Object} result SVGO result
* @param {String} output output filename
* @param {Boolean} pretty is pretty printed?
*/
function saveFileAndPrintInfo(result, output, pretty) {
// output file
output = FS.createWriteStream(output, { encoding: 'utf8' });
output.write(result.data);
output.end();
// print time info
printTimeInfo(result.info.time);
// print optimization profit info
printProfitInfo(result.info.inBytes, result.info.outBytes);
}
/**
* Print time info.
*
* @param {Number} time working time in ms
*/
function saveFileAndPrintInfo(data, path, inBytes, outBytes, time) {
FS.writeFile(path, data, 'utf8', function() {
// print time info
printTimeInfo(time);
// print optimization profit info
printProfitInfo(inBytes, outBytes);
});
}
function printTimeInfo(time) {
UTIL.puts('Done in ' + time + ' ms!');
}
/**
* Print optimization profit info.
*
* @param {Number} inBytes input file byteLength
* @param {Number} outBytes output file byteLength
*/
function printProfitInfo(inBytes, outBytes) {
var profitPercents = 100 - outBytes * 100 / inBytes;
@ -215,55 +248,66 @@ function printProfitInfo(inBytes, outBytes) {
}
/**
* Optimize all *.svg files in a specific folder.
*
* @param {String} folder folder path
* @param {Object} opts COA options
*/
function optimizeFolder(folder, opts) {
function optimizeFolder(path, config) {
var svgo = new SVGO({ coa: opts });
var svgo = new SVGO(config);
folder = PATH.resolve(process.cwd(), folder);
// absoluted folder path
path = PATH.resolve(process.cwd(), path);
UTIL.puts('\n' + folder + ':\n');
UTIL.puts('\n' + path + ':\n');
// list directory
QFS.list(folder).then(function(list) {
// list folder content
FS.readdir(path, function(err, files) {
list.forEach(function(item) {
if (err) {
throw err;
}
var filename = item;
files.forEach(function(filename) {
item = folder + '/' + item;
// absoluted file path
var filepath = PATH.resolve(path, filename);
// checkif item is a file
QFS.isFile(item)
.then(function(isFile) {
// check if file name matches *.svg
if (regSVGFile.test(filepath)) {
// and file name matches *.svg
if (isFile && regSVGFile.test(item)) {
FS.readFile(filepath, 'utf8', function(err, data) {
// then optimize it and output profit information
return svgo.fromFile(item)
.then(function(result) {
if (err) {
throw err;
}
var startTime = Date.now(),
time,
inBytes = Buffer.byteLength(data, 'utf8'),
outBytes;
svgo.optimize(data, function(result) {
outBytes = Buffer.byteLength(result.data, 'utf8');
time = Date.now() - startTime;
FS.writeFile(filepath, result.data, 'utf8', function() {
UTIL.puts(filename + ':');
saveFileAndPrintInfo(result, item, opts.pretty);
// print time info
printTimeInfo(time);
// print optimization profit info
printProfitInfo(inBytes, outBytes);
});
});
});
}
})
.fail(function(e) {
UTIL.puts(filename + ':\n' + String('Error! "' + e.message + '"').red + '\n');
});
});
})
.done();
}

View File

@ -1,177 +1,166 @@
'use strict';
var QFS = require('q-fs'),
PATH = require('path'),
YAML = require('js-yaml'),
extend = require('./tools').extend,
defaultConfigPath = PATH.resolve(__dirname, '../../.svgo.yml');
require('js-yaml');
var EXTEND = require('node.extend');
/**
* Read and/or extend default config file,
* prepare and optimize plugins array.
*
* @module config
*
* @param {Object} [params] config object to extend or coa params
*
* @return {Object} config deferred promise
* @param {Object} [config] input config
* @return {Object} output config
*/
module.exports = function(params) {
module.exports = function(config) {
return _getConfig(params).then(function(config) {
var defaults = require('../../.svgo.yml');
config.plugins = preparePluginsArray(config.plugins);
config.plugins = optimizePluginsArray(config.plugins);
defaults.plugins = preparePluginsArray(defaults.plugins);
return config;
if (config) {
defaults = extendConfig(defaults, config);
}
});
defaults.plugins = optimizePluginsArray(defaults.plugins);
return defaults;
};
/**
* Get default or extended config.
*
* @param {Object} [params] config object to extend or coa params
*
* @return {Object} default or extended config
*
* @private
*/
function _getConfig(params) {
// if there are no any params then return default config
if (!params) return readConfig(defaultConfigPath);
// COA params
if (params.coa) {
params = params.coa;
return readConfig(defaultConfigPath)
.then(function(defaultConfig) {
// --pretty
if (params.pretty) defaultConfig.js2svg.pretty = true;
// --disable
if (params.disable) return changePluginsState(params.disable, false, defaultConfig);
// --enable
if (params.enable) return changePluginsState(params.enable, true, defaultConfig);
// --config
if (params.config) {
var localConfigPath = PATH.resolve(process.cwd, params.config);
// check for the local config file
return QFS.exists(localConfigPath)
.then(function(exist) {
// if it doesn't exists then return default
if (!exist) return defaultConfig;
// if it exists then return extended default
return readConfig(localConfigPath);
});
}
return defaultConfig;
});
// inline {} params
} else {
// return extended default
return readConfig(defaultConfigPath)
.then(function(defaultConfig) {
return extend(true, defaultConfig, params);
});
}
}
/**
* Read and YAML.parse config file by path.
*
* @param {String} path config path
*
* @return {Object} read config deferred promise
*/
function readConfig(path) {
return QFS.read(path)
.then(function(data) {
return YAML.load(data.toString());
});
}
/**
* Require() all plugins in array and convert it to array of arrays.
* Require() all plugins in array.
*
* @param {Array} plugins input plugins array
*
* @return {Array} input plugins array of arrays
*/
function preparePluginsArray(plugins) {
return plugins.map(function(plugin) {
plugin.fn = require('../../plugins/' + plugin.name)[plugin.name];
var plugin,
key;
return plugins.map(function(item) {
// {}
if (typeof item === 'object') {
key = Object.keys(item)[0];
plugin = require('../../plugins/' + key);
// name: {}
if (typeof item[key] === 'object') {
plugin.params = EXTEND(plugin.params || {}, item[key]);
// name: false
} else if (item[key] === false) {
plugin.active = false;
// name: true
} else if (item[key] === true) {
plugin.active = true;
}
plugin.name = key;
// name
} else {
plugin = require('../../plugins/' + item);
plugin.name = item;
}
return plugin;
return [plugin];
});
}
/**
* Extend plugins with the custom config object.
*
* @param {Array} plugins input plugins
* @param {Object} config config
* @return {Array} output plugins
*/
function extendConfig(defaults, config) {
var key;
// plugins
if (config.plugins) {
config.plugins.forEach(function(item) {
// {}
if (typeof item === 'object') {
key = Object.keys(item)[0];
defaults.plugins.forEach(function(plugin) {
if (plugin.name === key) {
// name: {}
if (typeof item[key] === 'object') {
plugin.params = EXTEND(plugin.params || {}, item[key]);
plugin.active = true;
// name: false
} else if (item[key] === false) {
plugin.active = false;
// name: true
} else if (item[key] === true) {
plugin.active = true;
}
}
});
}
});
}
// svg2js
if (config.svg2js) {
defaults.svg2js = config.svg2js;
}
// js2svg
if (config.js2svg) {
defaults.js2svg = config.js2svg;
}
return defaults;
}
/**
* Try to group sequential elements of plugins array.
*
* @param {Object} plugins input plugins array
*
* @return {Array} output plugins array
* @param {Object} plugins input plugins
* @return {Array} output plugins
*/
function optimizePluginsArray(plugins) {
var prev;
plugins = plugins.filter(function(item) {
plugins = plugins.map(function(item) {
return [item];
});
return plugins.filter(function(item) {
if (prev && item[0].type === prev[0].type) {
prev.push(item[0]);
return false;
}
prev = item;
return true;
});
return plugins;
}
/**
* Change plugins state by names array.
*
* @param {Array} names plugins names
* @param {Boolean} state active state
* @param {Object} config original config
*
* @return {Object} changed config
*/
function changePluginsState(names, state, config) {
config.plugins.forEach(function(plugin) {
if (names.indexOf(plugin.name) > -1) {
plugin.active = state;
}
});
return config;
}

View File

@ -1,51 +1,59 @@
'use strict';
var INHERIT = require('inherit'),
extend = require('./tools').extend;
var EXTEND = require('./tools').extend;
var defaults = {
doctypeStart: '<!DOCTYPE',
doctypeEnd: '>',
procInstStart: '<?',
procInstEnd: '?>',
tagOpenStart: '<',
tagOpenEnd: '>',
tagCloseStart: '</',
tagCloseEnd: '>',
tagShortStart: '<',
tagShortEnd: '/>',
attrStart: '="',
attrEnd: '"',
commentStart: '<!--',
commentEnd: '-->',
cdataStart: '<![CDATA[',
cdataEnd: ']]>',
textStart: '',
textEnd: '',
indent: ' ',
entities: {
'&': '&amp;',
'\'': '&apos;',
'"': '&quot;',
'>': '&gt;',
'<': '&lt;',
},
pretty: false
};
/**
* Convert SVG-as-JS object to SVG (XML) string.
*
* @module js2svg
*
* @param {Object} jsdata input data
* @param {Object} data input data
* @param {Object} config config
*
* @return {Object} output data
*/
module.exports = function(jsdata, config) {
module.exports = function(data, config) {
return new Converter(config).run(jsdata);
return new JS2SVG(config).convert(data);
};
/**
* @class Converter
*/
var Converter = INHERIT(/** @lends Nodes.prototype */{
function JS2SVG(config) {
/**
* @constructs
*
* @private
*/
__constructor: function(config) {
if (config) {
this.config = EXTEND(true, {}, defaults, config);
} else {
this.config = defaults;
}
/**
* Shallow copy of converter config.
*
* @type {Object}
*/
this.config = extend({}, config);
/**
* Current indent level hack.
*
* @type {Number}
*/
this.indentLevel = 0;
// pretty
if (this.config.pretty) {
this.config.doctypeEnd += '\n';
this.config.procInstEnd += '\n';
@ -57,16 +65,18 @@ var Converter = INHERIT(/** @lends Nodes.prototype */{
this.config.textEnd += '\n';
}
},
this.indentLevel = 0;
/**
}
/**
* Start conversion.
*
* @param {Object} svg-as-js data object
* @param {Object} data input data
*
* @return {String}
*/
run: function(data) {
JS2SVG.prototype.convert = function(data) {
var svg = '';
@ -104,14 +114,14 @@ var Converter = INHERIT(/** @lends Nodes.prototype */{
}
};
},
};
/**
/**
* Create indent string in accordance with the current node level.
*
* @return {String}
*/
createIndent: function() {
JS2SVG.prototype.createIndent = function() {
var indent = '';
@ -123,31 +133,31 @@ var Converter = INHERIT(/** @lends Nodes.prototype */{
return indent;
},
};
/**
/**
* Create doctype tag.
*
* @param {String} doctype doctype body string
*
* @return {String}
*/
createDoctype: function(doctype) {
JS2SVG.prototype.createDoctype = function(doctype) {
return this.config.doctypeStart +
doctype +
this.config.doctypeEnd;
},
};
/**
/**
* Create XML Processing Instruction tag.
*
* @param {Object} instruction instruction object
*
* @return {String}
*/
createProcInst: function(instruction) {
JS2SVG.prototype.createProcInst = function(instruction) {
return this.config.procInstStart +
instruction.name +
@ -155,46 +165,46 @@ var Converter = INHERIT(/** @lends Nodes.prototype */{
instruction.body +
this.config.procInstEnd;
},
};
/**
/**
* Create comment tag.
*
* @param {String} comment comment body
*
* @return {String}
*/
createComment: function(comment) {
JS2SVG.prototype.createComment = function(comment) {
return this.config.commentStart +
comment +
this.config.commentEnd;
},
};
/**
/**
* Create CDATA section.
*
* @param {String} cdata CDATA body
*
* @return {String}
*/
createCDATA: function(cdata) {
JS2SVG.prototype.createCDATA = function(cdata) {
return this.config.cdataStart +
cdata +
this.config.cdataEnd;
},
};
/**
/**
* Create element tag.
*
* @param {Object} data element object
*
* @return {String}
*/
createElem: function(data) {
JS2SVG.prototype.createElem = function(data) {
// beautiful injection for obtaining SVG information :)
if (
@ -223,7 +233,7 @@ var Converter = INHERIT(/** @lends Nodes.prototype */{
data.elem +
this.createAttrs(data) +
this.config.tagOpenEnd +
this.run(data).data +
this.convert(data).data +
this.createIndent() +
this.config.tagCloseStart +
data.elem +
@ -231,16 +241,16 @@ var Converter = INHERIT(/** @lends Nodes.prototype */{
}
},
};
/**
/**
* Create element attributes.
*
* @param {Object} elem attributes object
*
* @return {String}
*/
createAttrs: function(elem) {
JS2SVG.prototype.createAttrs = function(elem) {
var attrs = '';
@ -256,16 +266,16 @@ var Converter = INHERIT(/** @lends Nodes.prototype */{
return attrs;
},
};
/**
/**
* Create text node.
*
* @param {String} text text
*
* @return {String}
*/
createText: function(text) {
JS2SVG.prototype.createText = function(text) {
// convert entities back
for (var entity in this.config.entities) {
@ -277,6 +287,4 @@ var Converter = INHERIT(/** @lends Nodes.prototype */{
text +
this.config.textEnd;
}
});
};

View File

@ -1,35 +1,21 @@
'use strict';
var INHERIT = require('inherit'),
extend = require('./tools').extend;
var EXTEND = require('node.extend');
var JSAPI = module.exports = function(data) {
EXTEND(this, data);
};
/**
* @module jsAPI
*
* @class SVG-as-JS Nodes API.
*/
module.exports = INHERIT(/** @lends Nodes.prototype */{
/**
* @constructs
*
* @private
*/
__constructor: function(data) {
extend(this, data);
},
/**
* Determine if item is an element
* (any, with a specific name or in a names array).
*
* @param {String|Array} [param] element name or names arrays
*
* @return {Boolean}
*/
isElem: function(param) {
JSAPI.prototype.isElem = function(param) {
if (!param) return !!this.elem;
@ -37,29 +23,28 @@ module.exports = INHERIT(/** @lends Nodes.prototype */{
return !!this.elem && this.elem === param;
},
};
/**
/**
* Determine if element is empty.
*
* @return {Boolean}
*/
isEmpty: function() {
JSAPI.prototype.isEmpty = function() {
return !this.content || !this.content.length;
},
};
/**
/**
* Determine if element has an attribute
* (any, or by name or by name + value).
*
* @param {String} [name] attribute name
* @param {String} [val] attribute value (will be toString()'ed)
*
* @return {Boolean}
*/
hasAttr: function(name, val) {
JSAPI.prototype.hasAttr = function(name, val) {
if (!this.attrs || !Object.keys(this.attrs).length) return false;
@ -69,18 +54,17 @@ module.exports = INHERIT(/** @lends Nodes.prototype */{
return !!this.attrs[name];
},
};
/**
/**
* Get a specific attribute from an element
* (by name or name + value).
*
* @param {String} name attribute name
* @param {String} [val] attribute value (will be toString()'ed)
*
* @return {Object|Undefined}
*/
attr: function(name, val) {
JSAPI.prototype.attr = function(name, val) {
if (!this.hasAttr() || !arguments.length) return undefined;
@ -88,17 +72,16 @@ module.exports = INHERIT(/** @lends Nodes.prototype */{
return this.attrs[name];
},
};
/**
/**
* Remove a specific attribute.
*
* @param {String} name attribute name
* @param {String} [val] attribute value
*
* @return {Boolean}
*/
removeAttr: function(name, val) {
JSAPI.prototype.removeAttr = function(name, val) {
if (!arguments.length) return false;
@ -112,16 +95,15 @@ module.exports = INHERIT(/** @lends Nodes.prototype */{
return true;
},
};
/**
/**
* Add attribute.
*
* @param {Object} attr attribute object
*
* @return {Object} created attribute
*/
addAttr: function(attr) {
JSAPI.prototype.addAttr = function(attr) {
if (!attr ||
(attr && attr.name === undefined) ||
@ -131,20 +113,20 @@ module.exports = INHERIT(/** @lends Nodes.prototype */{
) return false;
this.attrs = this.attrs || {};
this.attrs[attr.name] = attr;
return (this.attrs[attr.name] = attr);
return this.attrs[attr.name];
},
};
/**
/**
* Iterates over all attributes.
*
* @param {Function} callback callback
* @param {Object} [context] callback context
*
* @return {Boolean} false if there are no any attributes
*/
eachAttr: function(callback, context) {
JSAPI.prototype.eachAttr = function(callback, context) {
if (!this.hasAttr()) return false;
@ -154,6 +136,4 @@ module.exports = INHERIT(/** @lends Nodes.prototype */{
return true;
}
});
};

View File

@ -7,7 +7,6 @@
*
* @param {Object} data input data
* @param {Object} plugins plugins object from config
*
* @return {Object} output data
*/
module.exports = function(data, plugins) {
@ -38,7 +37,6 @@ module.exports = function(data, plugins) {
* @param {Object} data input data
* @param {Array} plugins plugins list to process
* @param {Boolean} [reverse] reverse pass?
*
* @return {Object} output data
*/
function perItem(data, plugins, reverse) {
@ -89,7 +87,6 @@ function perItem(data, plugins, reverse) {
*
* @param {Object} data input data
* @param {Array} plugins plugins list to process
*
* @return {Object} output data
*/
function full(data, plugins) {

View File

@ -1,23 +1,26 @@
'use strict';
var Q = require('q'),
SAX = require('sax'),
var SAX = require('sax'),
JSAPI = require('./jsAPI');
var config = {
strict: true,
trim: true,
normalize: true,
lowercase: true,
xmlns: true,
position: false
};
/**
* Convert SVG (XML) string to SVG-as-JS object.
*
* @module svg2js
*
* @param {String} svg input data
* @param {Object} config sax xml parser config
*
* @return {Object} output data deferred promise
* @param {String} data input data
* @param {Function} callback
*/
module.exports = function(svg, config) {
module.exports = function(data, callback) {
var deferred = Q.defer(),
sax = SAX.parser(config.strict, config),
var sax = SAX.parser(config.strict, config),
root = {},
current = root,
stack = [];
@ -109,23 +112,21 @@ module.exports = function(svg, config) {
sax.onerror = function(e) {
deferred.reject(new Error('svg2js: ' + e.message));
// https://github.com/isaacs/sax-js#events
// "The error will be hanging out on parser.error,
// and must be deleted before parsing can continue"
this.error = null;
throw new Error('svg2js: ' + e.message);
};
sax.onend = function() {
deferred.resolve(root);
callback(root);
};
sax.write(svg).close();
return deferred.promise;
sax.write(data).close();
};

View File

@ -32,25 +32,20 @@
"example": "./examples"
},
"scripts": {
"test": "./node_modules/.bin/mocha --reporter spec --recursive",
"test": "./node_modules/.bin/mocha --reporter spec test/plugins test/svg2js",
"cover": "./node_modules/.bin/istanbul instrument --output lib-cov --no-compact --variable global.__coverage__ lib && ./node_modules/.bin/mocha --reporter mocha-istanbul --recursive",
"jshint": "jshint --show-non-errors ."
},
"dependencies": {
"sax": "~0.5.0",
"q": "~0.8.10",
"q-fs": "~0.1.0",
"coa": "~0.3.7",
"inherit": "",
"node.extend": "",
"js-yaml": "",
"colors": "~0.6.0"
"colors": "~0.6.0",
"node.extend": ""
},
"devDependencies": {
"mocha": "~1.8.0",
"mocha-as-promised": "~1.2.0",
"chai": "~1.5.0",
"chai-as-promised": "~3.2.3",
"istanbul": "~0.1.0",
"mocha-istanbul": ""
},

View File

@ -1,5 +1,15 @@
'use strict';
exports.type = 'perItem';
exports.active = true;
exports.params = {
newlines: true,
trim: true,
spaces: true
};
var regNewlines = /\n/g,
regSpaces = /\s{2,}/g;
@ -12,7 +22,7 @@ var regNewlines = /\n/g,
*
* @author Kir Belevich
*/
exports.cleanupAttrs = function(item, params) {
exports.fn = function(item, params) {
if (item.isElem()) {

View File

@ -1,5 +1,9 @@
'use strict';
exports.type = 'perItem';
exports.active = true;
var regEnableBackground = /^new\s0\s0\s([\-+]?\d*\.?\d+([eE][\-+]?\d+)?)\s([\-+]?\d*\.?\d+([eE][\-+]?\d+)?)$/,
elems = ['svg', 'mask', 'pattern'];
@ -18,7 +22,7 @@ var regEnableBackground = /^new\s0\s0\s([\-+]?\d*\.?\d+([eE][\-+]?\d+)?)\s([\-+]
*
* @author Kir Belevich
*/
exports.cleanupEnableBackground = function(item) {
exports.fn = function(item) {
if (
item.isElem(elems) &&

View File

@ -1,5 +1,14 @@
'use strict';
exports.type = 'full';
exports.active = true;
exports.params = {
remove: true,
minify: true
};
var referencesProps = require('./_collections').referencesProps,
regReferencesUrl = /^url\(#(.+?)\)$/,
regReferencesHref = /^#(.+?)$/,
@ -19,7 +28,7 @@ var referencesProps = require('./_collections').referencesProps,
*
* @author Kir Belevich
*/
exports.cleanupIDs = function(data, params) {
exports.fn = function(data, params) {
var currentID,
currentIDstring,

View File

@ -1,5 +1,15 @@
'use strict';
exports.type = 'perItem';
exports.active = true;
exports.params = {
floatPrecision: 3,
leadingZero: true,
defaultPx: true
};
var regNumericValues = /^([\-+]?\d*\.?\d+([eE][\-+]?\d+)?)(px|pt|pc|mm|cm|m|in|ft|em|ex|%)?$/,
removeLeadingZero = require('../lib/svgo/tools').removeLeadingZero;
@ -13,7 +23,7 @@ var regNumericValues = /^([\-+]?\d*\.?\d+([eE][\-+]?\d+)?)(px|pt|pc|mm|cm|m|in|f
*
* @author Kir Belevich
*/
exports.cleanupNumericValues = function(item, params) {
exports.fn = function(item, params) {
if (item.isElem()) {

View File

@ -1,5 +1,9 @@
'use strict';
exports.type = 'perItemReverse';
exports.active = true;
var flattenOneLevel = require('../lib/svgo/tools').flattenOneLevel;
/*
@ -25,7 +29,7 @@ var flattenOneLevel = require('../lib/svgo/tools').flattenOneLevel;
*
* @author Kir Belevich
*/
exports.collapseGroups = function(item) {
exports.fn = function(item) {
// non-empty elements
if (item.elem && !item.isEmpty()) {

View File

@ -1,26 +1,19 @@
'use strict';
exports.type = 'perItem';
exports.active = true;
exports.params = {
names2hex: true,
rgb2hex: true,
shorthex: true
};
var collections = require('./_collections'),
regRGB = /^rgb\((\d+%?),\s*(\d+%?),\s*(\d+%?)\)$/,
regHEX = /^\#(([a-fA-F0-9])\2){3}$/;
/**
* Convert [r, g, b] to #rrggbb.
*
* @see https://gist.github.com/983535
*
* @example
* rgb2hex([255, 255, 255]) // '#ffffff'
*
* @param {Array} rgb [r, g, b]
* @return {String} #rrggbb
*
* @author Jed Schmidt
*/
function rgb2hex(rgb) {
return '#' + ((256 + rgb[0] << 8 | rgb[1]) << 8 | rgb[2]).toString(16).slice(1);
}
/**
* Convert different colors formats in element attributes to hex.
*
@ -44,7 +37,7 @@ function rgb2hex(rgb) {
*
* @author Kir Belevich
*/
exports.convertColors = function(item, params) {
exports.fn = function(item, params) {
if (item.elem) {
@ -88,3 +81,20 @@ exports.convertColors = function(item, params) {
}
};
/**
* Convert [r, g, b] to #rrggbb.
*
* @see https://gist.github.com/983535
*
* @example
* rgb2hex([255, 255, 255]) // '#ffffff'
*
* @param {Array} rgb [r, g, b]
* @return {String} #rrggbb
*
* @author Jed Schmidt
*/
function rgb2hex(rgb) {
return '#' + ((256 + rgb[0] << 8 | rgb[1]) << 8 | rgb[2]).toString(16).slice(1);
}

View File

@ -1,5 +1,21 @@
'use strict';
exports.type = 'perItem';
exports.active = true;
exports.params = {
applyTransforms: true,
straightCurves: true,
lineShorthands: true,
curveSmoothShorthands: true,
floatPrecision: 3,
removeUseless: true,
collapseRepeated: true,
leadingZero: true,
negativeExtraSpace: true
};
var pathElems = require('./_collections.js').pathElems,
path2js = require('./_path.js').path2js,
js2path = require('./_path.js').js2path,
@ -22,7 +38,7 @@ var pathElems = require('./_collections.js').pathElems,
*
* @author Kir Belevich
*/
exports.convertPathData = function(item, params) {
exports.fn = function(item, params) {
if (item.isElem(pathElems) && item.hasAttr('d')) {

View File

@ -1,6 +1,10 @@
'use strict';
var extend = require('../lib/svgo/tools').extend,
exports.type = 'perItem';
exports.active = true;
var EXTEND = require('node.extend'),
stylingProps = require('./_collections').stylingProps,
regCleanupStyle = /(:|;)\s+/g;
@ -22,7 +26,7 @@ var extend = require('../lib/svgo/tools').extend,
*
* @author Kir Belevich
*/
exports.convertStyleToAttrs = function(item) {
exports.fn = function(item) {
if (item.elem && item.hasAttr('style')) {
// ['opacity: 1', 'color: #000']
@ -57,7 +61,7 @@ exports.convertStyleToAttrs = function(item) {
return true;
});
extend(item.attrs, attrs);
EXTEND(item.attrs, attrs);
if (styles.length) {
item.attr('style').value = styles.join(';')

View File

@ -1,5 +1,22 @@
'use strict';
exports.type = 'perItem';
exports.active = true;
exports.params = {
convertToShorts: true,
floatPrecision: 3,
matrixToTransform: true,
shortTranslate: true,
shortScale: true,
shortRotate: true,
removeUseless: true,
collapseIntoOne: true,
leadingZero: true,
negativeExtraSpace: false
};
var cleanupOutData = require('../lib/svgo/tools').cleanupOutData,
transform2js = require('./_transforms.js').transform2js,
transformsMultiply = require('./_transforms.js').transformsMultiply,
@ -19,7 +36,7 @@ var cleanupOutData = require('../lib/svgo/tools').cleanupOutData,
*
* @author Kir Belevich
*/
exports.convertTransform = function(item, params) {
exports.fn = function(item, params) {
if (item.elem) {

View File

@ -1,5 +1,17 @@
'use strict';
exports.type = 'full';
exports.active = false;
exports.params = {
hcrop: true,
vcenter: true,
floatPrecision: 3,
leadingZero: true,
negativeExtraSpace: true
};
var relative2absolute = require('./_path.js').relative2absolute,
computeCubicBoundingBox = require('./_path.js').computeCubicBoundingBox,
computeQuadraticBoundingBox = require('./_path.js').computeQuadraticBoundingBox,
@ -7,7 +19,7 @@ var relative2absolute = require('./_path.js').relative2absolute,
js2path = require('./_path.js').js2path,
extend = require('../lib/svgo/tools').extend;
exports.cropAndCenterAlongPath = function(data, params) {
exports.fn = function(data, params) {
data.content.forEach(function(item) {

View File

@ -1,5 +1,9 @@
'use strict';
exports.type = 'perItemReverse';
exports.active = true;
var inheritableAttrs = require('./_collections').inheritableAttrs,
pathElems = require('./_collections.js').pathElems;
@ -27,7 +31,7 @@ var inheritableAttrs = require('./_collections').inheritableAttrs,
*
* @author Kir Belevich
*/
exports.moveElemsAttrsToGroup = function(item) {
exports.fn = function(item) {
if (item.isElem('g') && !item.isEmpty() && item.content.length > 1) {

View File

@ -1,5 +1,9 @@
'use strict';
exports.type = 'perItemReverse';
exports.active = true;
var pathElems = require('./_collections.js').pathElems;
/**
@ -21,7 +25,7 @@ var pathElems = require('./_collections.js').pathElems;
*
* @author Kir Belevich
*/
exports.moveGroupAttrsToElems = function(item) {
exports.fn = function(item) {
// move group transform attr to content's pathElems
if (

View File

@ -1,5 +1,9 @@
'use strict';
exports.type = 'perItem';
exports.active = true;
/**
* Remove comments.
*
@ -12,7 +16,7 @@
*
* @author Kir Belevich
*/
exports.removeComments = function(item) {
exports.fn = function(item) {
if (item.comment && item.comment.charAt(0) !== '!') {
return false;

View File

@ -1,5 +1,9 @@
'use strict';
exports.type = 'perItem';
exports.active = true;
/**
* Remove DOCTYPE declaration.
*
@ -25,7 +29,7 @@
*
* @author Kir Belevich
*/
exports.removeDoctype = function(item) {
exports.fn = function(item) {
// remove doctype only if custom XML entities declaration block does not presents
// http://en.wikipedia.org/wiki/Document_Type_Definition#Entity_declarations

View File

@ -1,5 +1,9 @@
'use strict';
exports.type = 'perItem';
exports.active = true;
var editorNamespaces = require('./_collections').editorNamespaces,
prefixes = [];
@ -16,7 +20,7 @@ var editorNamespaces = require('./_collections').editorNamespaces,
*
* @author Kir Belevich
*/
exports.removeEditorsNSData = function(item) {
exports.fn = function(item) {
if (item.elem) {

View File

@ -1,5 +1,9 @@
'use strict';
exports.type = 'perItem';
exports.active = true;
/**
* Remove attributes with empty values.
*
@ -8,7 +12,7 @@
*
* @author Kir Belevich
*/
exports.removeEmptyAttrs = function(item) {
exports.fn = function(item) {
if (item.elem) {

View File

@ -1,5 +1,9 @@
'use strict';
exports.type = 'perItemReverse';
exports.active = true;
var container = require('./_collections').elemsGroups.container;
/**
@ -18,7 +22,7 @@ var container = require('./_collections').elemsGroups.container;
*
* @author Kir Belevich
*/
exports.removeEmptyContainers = function(item) {
exports.fn = function(item) {
return !(item.isElem(container) && !item.isElem('svg') && item.isEmpty());

View File

@ -1,5 +1,15 @@
'use strict';
exports.type = 'perItem';
exports.active = true;
exports.params = {
text: true,
tspan: true,
tref: true
};
/**
* Remove empty Text elements.
*
@ -21,7 +31,7 @@
*
* @author Kir Belevich
*/
exports.removeEmptyText = function(item, params) {
exports.fn = function(item, params) {
// Remove empty text element
if (

View File

@ -1,5 +1,26 @@
'use strict';
exports.type = 'perItem';
exports.active = true;
exports.params = {
displayNone: true,
opacity0: true,
circleR0: true,
ellipseRX0: true,
ellipseRY0: true,
rectWidth0: true,
rectHeight0: true,
patternWidth0: true,
patternHeight0: true,
imageWidth0: true,
imageHeight0: true,
pathEmptyD: true,
polylineEmptyPoints: true,
polygonEmptyPoints: true
};
/**
* Remove hidden elements with disabled rendering:
* - display="none"
@ -19,7 +40,7 @@
*
* @author Kir Belevich
*/
exports.removeHiddenElems = function(item, params) {
exports.fn = function(item, params) {
if (item.elem) {

View File

@ -1,5 +1,9 @@
'use strict';
exports.type = 'perItem';
exports.active = true;
/**
* Remove <metadata>.
*
@ -10,7 +14,7 @@
*
* @author Kir Belevich
*/
exports.removeMetadata = function(item) {
exports.fn = function(item) {
return !item.isElem('metadata');

View File

@ -1,5 +1,9 @@
'use strict';
exports.type = 'perItem';
exports.active = true;
var inheritableAttrs = require('./_collections').inheritableAttrs,
presentationAttrs = require('./_collections').attrsGroups.presentation,
excludedAttrs = ['display', 'opacity'];
@ -12,7 +16,7 @@ var inheritableAttrs = require('./_collections').inheritableAttrs,
*
* @author Kir Belevich
*/
exports.removeNonInheritableGroupAttrs = function(item) {
exports.fn = function(item) {
if (item.isElem('g')) {

View File

@ -1,5 +1,9 @@
'use strict';
exports.type = 'perItem';
exports.active = false;
/**
* Remove raster images references in <image>.
*
@ -10,7 +14,7 @@
*
* @author Kir Belevich
*/
exports.removeRasterImages = function(item) {
exports.fn = function(item) {
if (
item.isElem('image') &&

View File

@ -1,5 +1,16 @@
'use strict';
exports.type = 'perItem';
exports.active = true;
exports.params = {
SVGid: true,
unknownContent: true,
unknownAttrs: true,
defaultAttrs: true
};
var collections = require('./_collections'),
elems = collections.elems,
attrsGroups = collections.attrsGroups,
@ -48,7 +59,7 @@ for (var elem in elems) {
*
* @author Kir Belevich
*/
exports.removeUnknownsAndDefaults = function(item, params) {
exports.fn = function(item, params) {
// elems w/o namespace prefix
if (item.isElem() && !item.prefix) {

View File

@ -1,5 +1,9 @@
'use strict';
exports.type = 'full';
exports.active = true;
/**
* Remove unused namespaces declaration.
*
@ -8,7 +12,7 @@
*
* @author Kir Belevich
*/
exports.removeUnusedNS = function(data) {
exports.fn = function(data) {
var svgElem,
xmlnsCollection = [];

View File

@ -1,5 +1,14 @@
'use strict';
exports.type = 'peItem';
exports.active = true;
exports.params = {
stroke: true,
fill: true
};
var regStrokeProps = /^stroke/,
regFillProps = /^fill/;
@ -12,7 +21,7 @@ var regStrokeProps = /^stroke/,
*
* @author Kir Belevich
*/
exports.removeUselessStrokeAndFill = function(item, params) {
exports.fn = function(item, params) {
if (item.isElem()) {

View File

@ -1,5 +1,9 @@
'use strict';
exports.type = 'perItem';
exports.active = true;
var regViewBox = /^0\s0\s([\-+]?\d*\.?\d+([eE][\-+]?\d+)?)\s([\-+]?\d*\.?\d+([eE][\-+]?\d+)?)$/,
viewBoxElems = ['svg', 'pattern'];
@ -18,7 +22,7 @@ var regViewBox = /^0\s0\s([\-+]?\d*\.?\d+([eE][\-+]?\d+)?)\s([\-+]?\d*\.?\d+([eE
*
* @author Kir Belevich
*/
exports.removeViewBox = function(item) {
exports.fn = function(item) {
if (
item.isElem(viewBoxElems) &&

View File

@ -1,5 +1,9 @@
'use strict';
exports.type = 'perItem';
exports.active = true;
/**
* Remove XML Processing Instruction.
*
@ -11,7 +15,7 @@
*
* @author Kir Belevich
*/
exports.removeXMLProcInst = function(item) {
exports.fn = function(item) {
return !(item.processinginstruction && item.processinginstruction.name === 'xml');