Socket
Socket
Sign inDemoInstall

svgo

Package Overview
Dependencies
Maintainers
3
Versions
104
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

svgo - npm Package Compare versions

Comparing version 2.2.2 to 2.3.0

lib/xast.js

31

lib/css-tools.js

@@ -189,6 +189,12 @@ 'use strict';

* @param {Object} elem style element
* @return {string|Array} CSS string or empty array if no styles are set
* @return {string} CSS string or empty array if no styles are set
*/
function getCssStr(elem) {
return elem.content[0].text || elem.content[0].cdata || [];
if (
elem.children.length > 0 &&
(elem.children[0].type === 'text' || elem.children[0].type === 'cdata')
) {
return elem.children[0].value;
}
return '';
}

@@ -201,14 +207,19 @@

* @param {string} css string to be set
* @return {Object} reference to field with CSS
* @return {string} 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;
if (elem.children.length === 0) {
elem.children.push({
type: 'text',
value: '',
});
}
// in case of text field + if nothing was set yet
elem.content[0].text = css;
return elem.content[0].text;
if (elem.children[0].type !== 'text' && elem.children[0].type !== 'cdata') {
return css;
}
elem.children[0].value = css;
return css;
}

@@ -215,0 +226,0 @@

@@ -244,16 +244,8 @@ 'use strict';

const stringifyNumber = ({ number, precision }) => {
let result;
if (precision == null) {
result = number.toString();
} else {
result = number.toFixed(precision);
if (result.includes('.')) {
result = result.replace(/\.?0+$/, '');
}
if (precision != null) {
const ratio = 10 ** precision;
number = Math.round(number * ratio) / ratio;
}
// remove zero whole from decimal number
if (result !== '0') {
result = result.replace(/^0/, '').replace(/^-0/, '-');
}
return result;
return number.toString().replace(/^0\./, '.').replace(/^-0\./, '-.');
};

@@ -260,0 +252,0 @@

@@ -145,22 +145,20 @@ 'use strict';

it('should configure precision', () => {
const pathData = [
{ command: 'M', args: [0, -1.9876] },
{ command: 'L', args: [0.3, 3.14159265] },
{ command: 'L', args: [-0.3, -3.14159265] },
{ command: 'L', args: [100, 200] },
];
expect(
stringifyPathData({
pathData: [
{ command: 'M', args: [0, -1.9876] },
{ command: 'L', args: [0.3, 3.14159265] },
{ command: 'L', args: [100, 200] },
],
pathData,
precision: 3,
})
).to.equal('M0-1.988.3 3.142 100 200');
).to.equal('M0-1.988.3 3.142-.3-3.142 100 200');
expect(
stringifyPathData({
pathData: [
{ command: 'M', args: [0, -1.9876] },
{ command: 'L', args: [0.3, 3.14159265] },
{ command: 'L', args: [100, 200] },
],
pathData,
precision: 0,
})
).to.equal('M0-2 0 3 100 200');
).to.equal('M0-2 0 3 0-3 100 200');
});

@@ -167,0 +165,0 @@ it('allows to avoid spaces after arc flags', () => {

@@ -84,8 +84,6 @@ 'use strict';

// collect attributes
if (node.attrs) {
for (const { name, value } of Object.values(node.attrs)) {
if (attrsGroups.presentation.includes(name)) {
computedStyle[name] = { type: 'static', inherited: false, value };
importantStyles.set(name, false);
}
for (const [name, value] of Object.entries(node.attributes)) {
if (attrsGroups.presentation.includes(name)) {
computedStyle[name] = { type: 'static', inherited: false, value };
importantStyles.set(name, false);
}

@@ -150,12 +148,14 @@ }

const dynamic =
styleNode.hasAttr('media') && styleNode.attr('media').value !== 'all';
styleNode.attributes.media != null &&
styleNode.attributes.media !== 'all';
if (
styleNode.hasAttr('type') === false ||
styleNode.attr('type').value === '' ||
styleNode.attr('type').value === 'text/css'
styleNode.attributes.type == null ||
styleNode.attributes.type === '' ||
styleNode.attributes.type === 'text/css'
) {
const children = styleNode.content || [];
const children = styleNode.children;
for (const child of children) {
const css = child.text || child.cdata;
stylesheet.push(...parseStylesheet(css, dynamic));
if (child.type === 'text' || child.type === 'cdata') {
stylesheet.push(...parseStylesheet(child.value, dynamic));
}
}

@@ -172,3 +172,3 @@ }

let parent = node;
while (parent.parentNode && parent.parentNode.elem !== '#document') {
while (parent.parentNode && parent.parentNode.type !== 'root') {
const inheritedStyles = computeOwnStyle(parent.parentNode, stylesheet);

@@ -175,0 +175,0 @@ for (const [name, computed] of Object.entries(inheritedStyles)) {

@@ -5,2 +5,3 @@ 'use strict';

const { computeStyle } = require('./style.js');
const { querySelector } = require('./xast.js');
const svg2js = require('./svgo/svg2js.js');

@@ -34,20 +35,20 @@

`);
expect(computeStyle(root.querySelector('#class'))).to.deep.equal({
expect(computeStyle(querySelector(root, '#class'))).to.deep.equal({
fill: { type: 'static', inherited: false, value: 'red' },
});
expect(computeStyle(root.querySelector('#two-classes'))).to.deep.equal({
expect(computeStyle(querySelector(root, '#two-classes'))).to.deep.equal({
fill: { type: 'static', inherited: false, value: 'green' },
stroke: { type: 'static', inherited: false, value: 'black' },
});
expect(computeStyle(root.querySelector('#attribute'))).to.deep.equal({
expect(computeStyle(querySelector(root, '#attribute'))).to.deep.equal({
fill: { type: 'static', inherited: false, value: 'purple' },
});
expect(computeStyle(root.querySelector('#inline-style'))).to.deep.equal({
expect(computeStyle(querySelector(root, '#inline-style'))).to.deep.equal({
fill: { type: 'static', inherited: false, value: 'grey' },
});
expect(computeStyle(root.querySelector('#inheritance'))).to.deep.equal({
expect(computeStyle(querySelector(root, '#inheritance'))).to.deep.equal({
fill: { type: 'static', inherited: true, value: 'yellow' },
});
expect(
computeStyle(root.querySelector('#nested-inheritance'))
computeStyle(querySelector(root, '#nested-inheritance'))
).to.deep.equal({

@@ -74,14 +75,14 @@ fill: { type: 'static', inherited: true, value: 'blue' },

`);
expect(computeStyle(root.querySelector('#complex-selector'))).to.deep.equal(
{
fill: { type: 'static', inherited: false, value: 'red' },
}
);
expect(
computeStyle(root.querySelector('#attribute-over-inheritance'))
computeStyle(querySelector(root, '#complex-selector'))
).to.deep.equal({
fill: { type: 'static', inherited: false, value: 'red' },
});
expect(
computeStyle(querySelector(root, '#attribute-over-inheritance'))
).to.deep.equal({
fill: { type: 'static', inherited: false, value: 'orange' },
});
expect(
computeStyle(root.querySelector('#style-rule-over-attribute'))
computeStyle(querySelector(root, '#style-rule-over-attribute'))
).to.deep.equal({

@@ -91,3 +92,3 @@ fill: { type: 'static', inherited: false, value: 'blue' },

expect(
computeStyle(root.querySelector('#inline-style-over-style-rule'))
computeStyle(querySelector(root, '#inline-style-over-style-rule'))
).to.deep.equal({

@@ -110,9 +111,4 @@ fill: { type: 'static', inherited: false, value: 'purple' },

`);
expect(computeStyle(root.querySelector('#complex-selector'))).to.deep.equal(
{
fill: { type: 'static', inherited: false, value: 'green' },
}
);
expect(
computeStyle(root.querySelector('#style-rule-over-inline-style'))
computeStyle(querySelector(root, '#complex-selector'))
).to.deep.equal({

@@ -122,4 +118,9 @@ fill: { type: 'static', inherited: false, value: 'green' },

expect(
computeStyle(root.querySelector('#inline-style-over-style-rule'))
computeStyle(querySelector(root, '#style-rule-over-inline-style'))
).to.deep.equal({
fill: { type: 'static', inherited: false, value: 'green' },
});
expect(
computeStyle(querySelector(root, '#inline-style-over-style-rule'))
).to.deep.equal({
fill: { type: 'static', inherited: false, value: 'purple' },

@@ -149,17 +150,17 @@ });

`);
expect(computeStyle(root.querySelector('#media-query'))).to.deep.equal({
expect(computeStyle(querySelector(root, '#media-query'))).to.deep.equal({
fill: { type: 'dynamic', inherited: false },
});
expect(computeStyle(root.querySelector('#hover'))).to.deep.equal({
expect(computeStyle(querySelector(root, '#hover'))).to.deep.equal({
fill: { type: 'dynamic', inherited: false },
});
expect(computeStyle(root.querySelector('#inherited'))).to.deep.equal({
expect(computeStyle(querySelector(root, '#inherited'))).to.deep.equal({
fill: { type: 'dynamic', inherited: true },
});
expect(
computeStyle(root.querySelector('#inherited-overriden'))
computeStyle(querySelector(root, '#inherited-overriden'))
).to.deep.equal({
fill: { type: 'static', inherited: false, value: 'blue' },
});
expect(computeStyle(root.querySelector('#static'))).to.deep.equal({
expect(computeStyle(querySelector(root, '#static'))).to.deep.equal({
fill: { type: 'static', inherited: false, value: 'black' },

@@ -186,9 +187,9 @@ });

`);
expect(computeStyle(root.querySelector('#media-query'))).to.deep.equal({
expect(computeStyle(querySelector(root, '#media-query'))).to.deep.equal({
fill: { type: 'dynamic', inherited: false },
});
expect(computeStyle(root.querySelector('#kinda-static'))).to.deep.equal({
expect(computeStyle(querySelector(root, '#kinda-static'))).to.deep.equal({
fill: { type: 'dynamic', inherited: false },
});
expect(computeStyle(root.querySelector('#static'))).to.deep.equal({
expect(computeStyle(querySelector(root, '#static'))).to.deep.equal({
fill: { type: 'static', inherited: false, value: 'blue' },

@@ -215,9 +216,11 @@ });

`);
expect(computeStyle(root.querySelector('#valid-type'))).to.deep.equal({
expect(computeStyle(querySelector(root, '#valid-type'))).to.deep.equal({
fill: { type: 'static', inherited: false, value: 'red' },
});
expect(computeStyle(root.querySelector('#empty-type'))).to.deep.equal({
expect(computeStyle(querySelector(root, '#empty-type'))).to.deep.equal({
fill: { type: 'static', inherited: false, value: 'green' },
});
expect(computeStyle(root.querySelector('#invalid-type'))).to.deep.equal({});
expect(computeStyle(querySelector(root, '#invalid-type'))).to.deep.equal(
{}
);
});

@@ -247,3 +250,3 @@

`);
expect(computeStyle(root.querySelector('#element'))).to.deep.equal({
expect(computeStyle(querySelector(root, '#element'))).to.deep.equal({
animation: {

@@ -250,0 +253,0 @@ type: 'static',

'use strict';
/**
* SVGO is a Nodejs-based tool for optimizing SVG vector graphics files.
*
* @see https://github.com/svg/svgo
*
* @author Kir Belevich <kir@soulshine.in> (https://github.com/deepsweet)
* @copyright © 2012 Kir Belevich
* @license MIT https://raw.githubusercontent.com/svg/svgo/master/LICENSE
*/
const {

@@ -14,0 +4,0 @@ defaultPlugins,

@@ -59,5 +59,9 @@ 'use strict';

'-r, --recursive',
"Use with '-f'. Optimizes *.svg files in folders recursively."
"Use with '--folder'. Optimizes *.svg files in folders recursively."
)
.option(
'--exclude <PATTERN...>',
"Use with '--folder'. Exclude files matching regular expression pattern."
)
.option(
'-q, --quiet',

@@ -76,3 +80,3 @@ 'Only output error messages, not regular status messages'

if (opts.precision != null) {
const number = Number.parseInt(opts.precision, 0);
const number = Number.parseInt(opts.precision, 10);
if (Number.isNaN(number)) {

@@ -102,3 +106,3 @@ console.error(

if (opts.indent != null) {
const number = Number.parseInt(opts.indent, 0);
const number = Number.parseInt(opts.indent, 10);
if (Number.isNaN(number)) {

@@ -162,2 +166,7 @@ console.error(

// --exclude
config.exclude = opts.exclude
? opts.exclude.map((pattern) => RegExp(pattern))
: [];
// --precision

@@ -298,3 +307,7 @@ if (opts.precision != null) {

const filesInThisFolder = files
.filter((name) => regSVGFile.test(name))
.filter(
(name) =>
regSVGFile.test(name) &&
!config.exclude.some((regExclude) => regExclude.test(name))
)
.map((name) => ({

@@ -301,0 +314,0 @@ inputPath: PATH.resolve(dir, name),

@@ -13,2 +13,3 @@ 'use strict';

'cleanupAttrs',
'mergeStyles',
'inlineStyles',

@@ -63,3 +64,3 @@ 'minifyStyles',

name,
...pluginsMap[name],
active: pluginsMap[name].active,
}));

@@ -66,0 +67,0 @@ for (const plugin of plugins) {

@@ -6,56 +6,13 @@ 'use strict';

this.classNames = new Set();
this.classAttr = null;
//this.classValue = null;
const value = node.attributes.class;
if (value != null) {
this.addClassValueHandler();
this.setClassValue(value);
}
};
/**
* Performs a deep clone of this object.
*
* @param parentNode the parentNode to assign to the cloned result
*/
CSSClassList.prototype.clone = function (parentNode) {
var node = this;
var nodeData = {};
Object.keys(node).forEach(function (key) {
if (key !== 'parentNode') {
nodeData[key] = node[key];
}
});
// Deep-clone node data.
nodeData = JSON.parse(JSON.stringify(nodeData));
var clone = new CSSClassList(parentNode);
Object.assign(clone, nodeData);
return clone;
};
CSSClassList.prototype.hasClass = function () {
this.classAttr = {
// empty class attr
name: 'class',
value: null,
};
this.addClassHandler();
};
// attr.class
CSSClassList.prototype.addClassHandler = function () {
Object.defineProperty(this.parentNode.attrs, 'class', {
get: this.getClassAttr.bind(this),
set: this.setClassAttr.bind(this),
enumerable: true,
configurable: true,
});
this.addClassValueHandler();
};
// attr.class.value
CSSClassList.prototype.addClassValueHandler = function () {
Object.defineProperty(this.classAttr, 'value', {
Object.defineProperty(this.parentNode.attributes, 'class', {
get: this.getClassValue.bind(this),

@@ -68,13 +25,2 @@ set: this.setClassValue.bind(this),

CSSClassList.prototype.getClassAttr = function () {
return this.classAttr;
};
CSSClassList.prototype.setClassAttr = function (newClassAttr) {
this.setClassValue(newClassAttr.value); // must before applying value handler!
this.classAttr = newClassAttr;
this.addClassValueHandler();
};
CSSClassList.prototype.getClassValue = function () {

@@ -95,3 +41,3 @@ var arrClassNames = Array.from(this.classNames);

CSSClassList.prototype.add = function (/* variadic */) {
this.hasClass();
this.addClassValueHandler();
Object.values(arguments).forEach(this._addSingle.bind(this));

@@ -105,3 +51,3 @@ };

CSSClassList.prototype.remove = function (/* variadic */) {
this.hasClass();
this.addClassValueHandler();
Object.values(arguments).forEach(this._removeSingle.bind(this));

@@ -108,0 +54,0 @@ };

@@ -8,3 +8,3 @@ 'use strict';

const isTag = (node) => {
return node.isElem();
return node.type === 'element';
};

@@ -23,11 +23,11 @@

const getAttributeValue = (elem, name) => {
return elem.hasAttr(name) ? elem.attr(name).value : undefined;
return elem.attributes[name];
};
const getChildren = (node) => {
return node.content || [];
return node.children || [];
};
const getName = (elemAst) => {
return elemAst.elem;
return elemAst.name;
};

@@ -45,7 +45,10 @@

const getText = (node) => {
return node.content[0].text || node.content[0].cdata || '';
if (node.children[0].type === 'text' && node.children[0].type === 'cdata') {
return node.children[0].value;
}
return '';
};
const hasAttrib = (elem, name) => {
return getAttributeValue(elem, name) !== undefined;
return elem.attributes[name] !== undefined;
};

@@ -52,0 +55,0 @@

@@ -12,58 +12,16 @@ 'use strict';

this.styleAttr = null;
this.styleValue = null;
this.parseError = false;
const value = node.attributes.style;
if (value != null) {
this.addStyleValueHandler();
this.setStyleValue(value);
}
};
/**
* Performs a deep clone of this object.
*
* @param parentNode the parentNode to assign to the cloned result
*/
CSSStyleDeclaration.prototype.clone = function (parentNode) {
var node = this;
var nodeData = {};
Object.keys(node).forEach(function (key) {
if (key !== 'parentNode') {
nodeData[key] = node[key];
}
});
// Deep-clone node data.
nodeData = JSON.parse(JSON.stringify(nodeData));
var clone = new CSSStyleDeclaration(parentNode);
Object.assign(clone, nodeData);
return clone;
};
CSSStyleDeclaration.prototype.hasStyle = function () {
this.addStyleHandler();
};
// attr.style
CSSStyleDeclaration.prototype.addStyleHandler = function () {
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,
});
this.addStyleValueHandler();
};
// attr.style.value
CSSStyleDeclaration.prototype.addStyleValueHandler = function () {
Object.defineProperty(this.styleAttr, 'value', {
Object.defineProperty(this.parentNode.attributes, 'style', {
get: this.getStyleValue.bind(this),

@@ -76,14 +34,2 @@ set: this.setStyleValue.bind(this),

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 () {

@@ -247,3 +193,3 @@ return this.getCssText();

this.hasStyle();
this.addStyleValueHandler();

@@ -275,3 +221,3 @@ var properties = this.getProperties();

this.hasStyle();
this.addStyleValueHandler();

@@ -278,0 +224,0 @@ var properties = this.getProperties();

@@ -96,20 +96,23 @@ 'use strict';

if (data.content) {
this.indentLevel++;
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);
for (const item of data.children) {
if (item.type === 'element') {
svg += this.createElem(item);
}
if (item.type === 'text') {
svg += this.createText(item);
}
if (item.type === 'doctype') {
svg += this.createDoctype(item);
}
if (item.type === 'instruction') {
svg += this.createProcInst(item);
}
if (item.type === 'comment') {
svg += this.createComment(item);
}
if (item.type === 'cdata') {
svg += this.createCDATA(item);
}
}

@@ -150,3 +153,4 @@

*/
JS2SVG.prototype.createDoctype = function (doctype) {
JS2SVG.prototype.createDoctype = function (node) {
const { doctype } = node.data;
return this.config.doctypeStart + doctype + this.config.doctypeEnd;

@@ -162,9 +166,6 @@ };

*/
JS2SVG.prototype.createProcInst = function (instruction) {
JS2SVG.prototype.createProcInst = function (node) {
const { name, value } = node;
return (
this.config.procInstStart +
instruction.name +
' ' +
instruction.body +
this.config.procInstEnd
this.config.procInstStart + name + ' ' + value + this.config.procInstEnd
);

@@ -180,4 +181,5 @@ };

*/
JS2SVG.prototype.createComment = function (comment) {
return this.config.commentStart + comment + this.config.commentEnd;
JS2SVG.prototype.createComment = function (node) {
const { value } = node;
return this.config.commentStart + value + this.config.commentEnd;
};

@@ -192,5 +194,6 @@

*/
JS2SVG.prototype.createCDATA = function (cdata) {
JS2SVG.prototype.createCDATA = function (node) {
const { value } = node;
return (
this.createIndent() + this.config.cdataStart + cdata + this.config.cdataEnd
this.createIndent() + this.config.cdataStart + value + this.config.cdataEnd
);

@@ -208,9 +211,13 @@ };

// 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;
if (
data.name === 'svg' &&
data.attributes.width != null &&
data.attributes.height != null
) {
this.width = data.attributes.width;
this.height = data.attributes.height;
}
// empty element and short tag
if (data.isEmpty()) {
if (data.children.length === 0) {
if (this.config.useShortTags) {

@@ -220,3 +227,3 @@ return (

this.config.tagShortStart +
data.elem +
data.name +
this.createAttrs(data) +

@@ -229,7 +236,7 @@ this.config.tagShortEnd

this.config.tagShortStart +
data.elem +
data.name +
this.createAttrs(data) +
this.config.tagOpenEnd +
this.config.tagCloseStart +
data.elem +
data.name +
this.config.tagCloseEnd

@@ -271,3 +278,3 @@ );

tagOpenStart +
data.elem +
data.name +
this.createAttrs(data) +

@@ -279,3 +286,3 @@ tagOpenEnd +

tagCloseStart +
data.elem +
data.name +
tagCloseEnd

@@ -293,21 +300,15 @@ );

*/
JS2SVG.prototype.createAttrs = function (elem) {
var attrs = '';
elem.eachAttr(function (attr) {
if (attr.value !== undefined) {
JS2SVG.prototype.createAttrs = function (element) {
let attrs = '';
for (const [name, value] of Object.entries(element.attributes)) {
if (value !== undefined) {
const encodedValue = value
.toString()
.replace(this.config.regValEntities, this.config.encodeEntity);
attrs +=
' ' +
attr.name +
this.config.attrStart +
String(attr.value).replace(
this.config.regValEntities,
this.config.encodeEntity
) +
this.config.attrEnd;
' ' + name + this.config.attrStart + encodedValue + this.config.attrEnd;
} else {
attrs += ' ' + attr.name;
attrs += ' ' + name;
}
}, this);
}
return attrs;

@@ -323,9 +324,10 @@ };

*/
JS2SVG.prototype.createText = function (text) {
JS2SVG.prototype.createText = function (node) {
const { value } = node;
return (
this.createIndent() +
this.config.textStart +
text.replace(this.config.regEntities, this.config.encodeEntity) +
value.replace(this.config.regEntities, this.config.encodeEntity) +
(this.textContext ? '' : this.config.textEnd)
);
};
'use strict';
const { selectAll, selectOne, is } = require('css-select');
const { parseName } = require('./tools.js');
const svgoCssSelectAdapter = require('./css-select-adapter');
const CSSClassList = require('./css-class-list');
const CSSStyleDeclaration = require('./css-style-declaration');

@@ -11,5 +14,42 @@ var cssSelectOpts = {

const attrsHandler = {
get: (attributes, name) => {
// eslint-disable-next-line no-prototype-builtins
if (attributes.hasOwnProperty(name)) {
return {
name,
get value() {
return attributes[name];
},
set value(value) {
attributes[name] = value;
},
};
}
},
set: (attributes, name, attr) => {
attributes[name] = attr.value;
return true;
},
};
var JSAPI = function (data, parentNode) {
Object.assign(this, data);
if (parentNode) {
if (this.type === 'element') {
if (this.attributes == null) {
this.attributes = {};
}
if (this.children == null) {
this.children = [];
}
Object.defineProperty(this, 'class', {
writable: true,
configurable: true,
value: new CSSClassList(this),
});
Object.defineProperty(this, 'style', {
writable: true,
configurable: true,
value: new CSSStyleDeclaration(this),
});
Object.defineProperty(this, 'parentNode', {

@@ -19,2 +59,19 @@ writable: true,

});
// temporary attrs polyfill
// TODO remove after migration
const element = this;
Object.defineProperty(this, 'attrs', {
configurable: true,
get() {
return new Proxy(element.attributes, attrsHandler);
},
set(value) {
const newAttributes = {};
for (const attr of Object.values(value)) {
newAttributes[attr.name] = attr.value;
}
element.attributes = newAttributes;
},
});
}

@@ -30,28 +87,8 @@ };

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];
}
});
const { children, ...nodeData } = this;
// 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();
const clonedNode = new JSAPI(JSON.parse(JSON.stringify(nodeData)), null);
if (children) {
clonedNode.children = children.map((child) => {
const clonedChild = child.clone();
clonedChild.parentNode = clonedNode;

@@ -61,3 +98,2 @@ return clonedChild;

}
return clonedNode;

@@ -74,7 +110,12 @@ };

JSAPI.prototype.isElem = function (param) {
if (!param) return !!this.elem;
if (Array.isArray(param)) return !!this.elem && param.indexOf(this.elem) > -1;
return !!this.elem && this.elem === param;
if (this.type !== 'element') {
return false;
}
if (param == null) {
return true;
}
if (Array.isArray(param)) {
return param.includes(this.name);
}
return this.name === param;
};

@@ -89,3 +130,3 @@

JSAPI.prototype.renameElem = function (name) {
if (name && typeof name === 'string') this.elem = this.local = name;
if (name && typeof name === 'string') this.name = name;

@@ -101,3 +142,3 @@ return this;

JSAPI.prototype.isEmpty = function () {
return !this.content || !this.content.length;
return !this.children || !this.children.length;
};

@@ -120,7 +161,7 @@

/**
* Changes content by removing elements and/or adding new elements.
* Changes children by removing elements and/or adding new elements.
*
* @param {Number} start Index at which to start changing the content.
* @param {Number} start Index at which to start changing the children.
* @param {Number} n Number of elements to remove.
* @param {Array|Object} [insertion] Elements to add to the content.
* @param {Array|Object} [insertion] Elements to add to the children.
* @return {Array} Removed elements.

@@ -138,3 +179,6 @@ */

return this.content.splice.apply(this.content, [start, n].concat(insertion));
return this.children.splice.apply(
this.children,
[start, n].concat(insertion)
);
};

@@ -151,10 +195,19 @@

JSAPI.prototype.hasAttr = function (name, val) {
if (!this.attrs || !Object.keys(this.attrs).length) return false;
if (!arguments.length) return !!this.attrs;
if (val !== undefined)
return !!this.attrs[name] && this.attrs[name].value === val.toString();
return !!this.attrs[name];
if (this.type !== 'element') {
return false;
}
if (Object.keys(this.attributes).length === 0) {
return false;
}
if (name == null) {
return true;
}
// eslint-disable-next-line no-prototype-builtins
if (this.attributes.hasOwnProperty(name) === false) {
return false;
}
if (val !== undefined) {
return this.attributes[name] === val.toString();
}
return true;
};

@@ -194,15 +247,19 @@

function nameTest(attr) {
return attr.local === localName;
const { local } = parseName(attr.name);
return local === localName;
}
function stringValueTest(attr) {
return attr.local === localName && val == attr.value;
const { local } = parseName(attr.name);
return local === localName && val == attr.value;
}
function regexpValueTest(attr) {
return attr.local === localName && val.test(attr.value);
const { local } = parseName(attr.name);
return local === localName && val.test(attr.value);
}
function funcValueTest(attr) {
return attr.local === localName && val(attr.value);
const { local } = parseName(attr.name);
return local === localName && val(attr.value);
}

@@ -220,8 +277,5 @@ };

JSAPI.prototype.attr = function (name, val) {
if (!this.hasAttr() || !arguments.length) return undefined;
if (val !== undefined)
return this.hasAttr(name, val) ? this.attrs[name] : undefined;
return this.attrs[name];
if (this.hasAttr(name, val)) {
return this.attrs[name];
}
};

@@ -240,3 +294,3 @@

var elem = this;
elem && (!elem.hasAttr(name) || !elem.attr(name).value);
elem && (!elem.hasAttr(name) || !elem.attributes[name]);
elem = elem.parentNode

@@ -248,3 +302,3 @@ );

} else if (elem && elem.hasAttr(name)) {
return elem.attrs[name].value;
return elem.attributes[name];
}

@@ -260,18 +314,19 @@ };

*/
JSAPI.prototype.removeAttr = function (name, val, recursive) {
if (!arguments.length) return false;
JSAPI.prototype.removeAttr = function (name, val) {
if (this.type !== 'element') {
return false;
}
if (arguments.length === 0) {
return false;
}
if (Array.isArray(name)) {
name.forEach(this.removeAttr, this);
for (const nameItem of name) {
this.removeAttr(nameItem, val);
}
return false;
}
if (!this.hasAttr(name)) return false;
if (!recursive && val && this.attrs[name].value !== val) return false;
delete this.attrs[name];
if (!Object.keys(this.attrs).length) delete this.attrs;
if (this.hasAttr(name, val) === false) {
return false;
}
delete this.attributes[name];
return true;

@@ -289,11 +344,5 @@ };

if (
attr.name === undefined ||
attr.prefix === undefined ||
attr.local === undefined
)
return false;
if (attr.name === undefined) return false;
this.attrs = this.attrs || {};
this.attrs[attr.name] = attr;
this.attributes[attr.name] = attr.value;

@@ -321,8 +370,11 @@ if (attr.name === 'class') {

JSAPI.prototype.eachAttr = function (callback, context) {
if (!this.hasAttr()) return false;
if (this.type !== 'element') {
return false;
}
if (callback == null) {
return false;
}
for (const attr of Object.values(this.attrs)) {
callback.call(context, attr);
}
return true;

@@ -339,3 +391,5 @@ };

JSAPI.prototype.someAttr = function (callback, context) {
if (!this.hasAttr()) return false;
if (this.type !== 'element') {
return false;
}

@@ -342,0 +396,0 @@ for (const attr of Object.values(this.attrs)) {

'use strict';
const { visit } = require('../xast.js');
/**

@@ -37,2 +39,10 @@ * Plugins engine.

break;
case 'visitor':
for (const plugin of group) {
if (plugin.active) {
const visitor = plugin.fn(data, plugin.params, info);
visit(data, visitor);
}
}
break;
}

@@ -54,5 +64,5 @@ }

function monkeys(items) {
items.content = items.content.filter(function (item) {
items.children = items.children.filter(function (item) {
// reverse pass
if (reverse && item.content) {
if (reverse && item.children) {
monkeys(item);

@@ -73,3 +83,3 @@ }

// direct pass
if (!reverse && item.content) {
if (!reverse && item.children) {
monkeys(item);

@@ -76,0 +86,0 @@ }

'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;
const SAX = require('@trysound/sax');
const JSAPI = require('./jsAPI.js');
const { textElems } = require('../../plugins/_collections.js');
var config = {
const entityDeclaration = /<!ENTITY\s+(\S+)\s+(?:'([^']+)'|"([^"]+)")\s*>/g;
const config = {
strict: true,

@@ -25,13 +24,11 @@ trim: false,

module.exports = function (data) {
var sax = SAX.parser(config.strict, config),
root = new JSAPI({ elem: '#document', content: [] }),
current = root,
stack = [root];
const sax = SAX.parser(config.strict, config);
const root = new JSAPI({ type: 'root', children: [] });
let current = root;
let stack = [root];
function pushToContent(content) {
content = new JSAPI(content, current);
(current.content = current.content || []).push(content);
return content;
function pushToContent(node) {
const wrapped = new JSAPI(node, current);
current.children.push(wrapped);
return wrapped;
}

@@ -41,7 +38,12 @@

pushToContent({
doctype: doctype,
type: 'doctype',
// TODO parse doctype for name, public and system to match xast
name: 'svg',
data: {
doctype,
},
});
var subsetStart = doctype.indexOf('['),
entityMatch;
const subsetStart = doctype.indexOf('[');
let entityMatch;

@@ -59,3 +61,5 @@ if (subsetStart >= 0) {

pushToContent({
processinginstruction: data,
type: 'instruction',
name: data.name,
value: data.body,
});

@@ -66,3 +70,4 @@ };

pushToContent({
comment: comment.trim(),
type: 'comment',
value: comment.trim(),
});

@@ -73,3 +78,4 @@ };

pushToContent({
cdata: cdata,
type: 'cdata',
value: cdata,
});

@@ -79,37 +85,17 @@ };

sax.onopentag = function (data) {
var elem = {
elem: data.name,
prefix: data.prefix,
local: data.local,
attrs: {},
var element = {
type: 'element',
name: data.name,
attributes: {},
children: [],
};
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,
};
}
for (const [name, attr] of Object.entries(data.attributes)) {
element.attributes[name] = attr.value;
}
elem = pushToContent(elem);
current = elem;
element = pushToContent(element);
current = element;
stack.push(elem);
stack.push(element);
};

@@ -119,6 +105,12 @@

// prevent trimming of meaningful whitespace inside textual tags
if (textElems.includes(current.elem) && !data.prefix) {
pushToContent({ text: text });
if (textElems.includes(current.name) && !data.prefix) {
pushToContent({
type: 'text',
value: text,
});
} else if (/\S/.test(text)) {
pushToContent({ text: text.trim() });
pushToContent({
type: 'text',
value: text.trim(),
});
}

@@ -125,0 +117,0 @@ };

@@ -106,3 +106,3 @@ 'use strict';

delimiter != '' &&
(item < 0 || (itemStr.charCodeAt(0) == 46 && prev % 1 !== 0))
(item < 0 || (itemStr.charAt(0) === '.' && prev % 1 !== 0))
) {

@@ -134,5 +134,5 @@ delimiter = '';

if (0 < num && num < 1 && strNum.charCodeAt(0) == 48) {
if (0 < num && num < 1 && strNum.charAt(0) === '0') {
strNum = strNum.slice(1);
} else if (-1 < num && num < 0 && strNum.charCodeAt(1) == 48) {
} else if (-1 < num && num < 0 && strNum.charAt(1) === '0') {
strNum = strNum.charAt(0) + strNum.slice(2);

@@ -143,1 +143,28 @@ }

exports.removeLeadingZero = removeLeadingZero;
const parseName = (name) => {
if (name == null) {
return {
prefix: '',
local: '',
};
}
if (name === 'xmlns') {
return {
prefix: 'xmlns',
local: '',
};
}
const chunks = name.split(':');
if (chunks.length === 1) {
return {
prefix: '',
local: chunks[0],
};
}
return {
prefix: chunks[0],
local: chunks[1],
};
};
exports.parseName = parseName;
{
"name": "svgo",
"version": "2.2.2",
"version": "2.3.0",
"description": "Nodejs-based tool for optimizing SVG vector graphics files",

@@ -53,6 +53,7 @@ "keywords": [

"test": "c8 --reporter=html --reporter=text mocha \"test/*/_index.js\" \"**/*.test.js\" --ignore=\"node_modules/**\"",
"lint": "eslint .",
"lint": "eslint --ignore-path .gitignore . && prettier --list-different \"**/*.js\" --ignore-path .gitignore",
"fix": "eslint --ignore-path .gitignore --fix . && prettier --write \"**/*.js\" --ignore-path .gitignore",
"typecheck": "tsc",
"test-browser": "rollup -c && node ./test/browser.js",
"test-regression": "NO_DIFF=1 node ./test/regression.js",
"test-regression": "node ./test/regression-extract.js && NO_DIFF=1 node ./test/regression.js",
"prepublishOnly": "rm -rf dist && rollup -c"

@@ -64,6 +65,2 @@ },

"eslintConfig": {
"ignorePatterns": [
"dist",
"fixtures"
],
"parserOptions": {

@@ -112,18 +109,18 @@ "ecmaVersion": "2021"

"@rollup/plugin-node-resolve": "^11.2.0",
"@types/mocha": "^8.2.1",
"@types/mocha": "^8.2.2",
"c8": "^7.6.0",
"chai": "^4.3.0",
"chai": "^4.3.4",
"del": "^6.0.0",
"eslint": "^7.20.0",
"get-stream": "^6.0.0",
"mocha": "^8.3.0",
"eslint": "^7.22.0",
"mocha": "^8.3.2",
"mock-stdin": "^1.0.0",
"node-fetch": "^2.6.1",
"pixelmatch": "^5.2.1",
"playwright": "^1.8.1",
"playwright": "^1.9.2",
"pngjs": "^6.0.0",
"prettier": "^2.2.1",
"rollup": "^2.39.0",
"rollup": "^2.42.1",
"rollup-plugin-terser": "^7.0.2",
"tar-stream": "^2.2.0",
"typescript": "^4.2.2"
"typescript": "^4.2.3"
},

@@ -130,0 +127,0 @@ "engines": {

@@ -5,12 +5,3 @@ 'use strict';

var regNumericValues = /[-+]?(\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?/g,
transform2js = require('./_transforms').transform2js,
transformsMultiply = require('./_transforms').transformsMultiply,
transformArc = require('./_transforms').transformArc,
collections = require('./_collections.js'),
referencesProps = collections.referencesProps,
defaultStrokeWidth =
collections.attrsGroupsDefaults.presentation['stroke-width'],
removeLeadingZero = require('../lib/svgo/tools').removeLeadingZero,
prevCtrlPoint;
var prevCtrlPoint;

@@ -27,3 +18,3 @@ /**

const pathData = []; // JS representation of the path data
const newPathData = parsePathData(path.attr('d').value);
const newPathData = parsePathData(path.attributes.d);
for (const { command, args } of newPathData) {

@@ -101,200 +92,2 @@ if (command === 'Z' || command === 'z') {

/**
* Apply transformation(s) to the Path data.
*
* @param {Object} elem current element
* @param {Array} path input path data
* @param {Object} params whether to apply transforms to stroked lines and transform precision (used for stroke width)
* @return {Array} output path data
*/
exports.applyTransforms = function (elem, path, params) {
// if there are no 'stroke' attr and references to other objects such as
// gradiends or clip-path which are also subjects to transform.
if (
!elem.hasAttr('transform') ||
!elem.attr('transform').value ||
// styles are not considered when applying transform
// can be fixed properly with new style engine
elem.hasAttr('style') ||
elem.someAttr(
(attr) =>
referencesProps.includes(attr.name) && attr.value.includes('url(')
)
) {
return path;
}
var matrix = transformsMultiply(transform2js(elem.attr('transform').value)),
stroke = elem.computedAttr('stroke'),
id = elem.computedAttr('id'),
transformPrecision = params.transformPrecision,
newPoint,
scale;
if (stroke && stroke != 'none') {
if (
!params.applyTransformsStroked ||
((matrix.data[0] != matrix.data[3] ||
matrix.data[1] != -matrix.data[2]) &&
(matrix.data[0] != -matrix.data[3] || matrix.data[1] != matrix.data[2]))
)
return path;
// "stroke-width" should be inside the part with ID, otherwise it can be overrided in <use>
if (id) {
var idElem = elem,
hasStrokeWidth = false;
do {
if (idElem.hasAttr('stroke-width')) hasStrokeWidth = true;
} while (
!idElem.hasAttr('id', id) &&
!hasStrokeWidth &&
(idElem = idElem.parentNode)
);
if (!hasStrokeWidth) return path;
}
scale = +Math.sqrt(
matrix.data[0] * matrix.data[0] + matrix.data[1] * matrix.data[1]
).toFixed(transformPrecision);
if (scale !== 1) {
var strokeWidth = elem.computedAttr('stroke-width') || defaultStrokeWidth;
if (
!elem.hasAttr('vector-effect') ||
elem.attr('vector-effect').value !== 'non-scaling-stroke'
) {
if (elem.hasAttr('stroke-width')) {
elem.attrs['stroke-width'].value = elem.attrs['stroke-width'].value
.trim()
.replace(regNumericValues, function (num) {
return removeLeadingZero(num * scale);
});
} else {
elem.addAttr({
name: 'stroke-width',
prefix: '',
local: 'stroke-width',
value: strokeWidth.replace(regNumericValues, function (num) {
return removeLeadingZero(num * scale);
}),
});
}
if (elem.hasAttr('stroke-dashoffset')) {
elem.attrs['stroke-dashoffset'].value = elem.attrs[
'stroke-dashoffset'
].value
.trim()
.replace(regNumericValues, (num) => removeLeadingZero(num * scale));
}
if (elem.hasAttr('stroke-dasharray')) {
elem.attrs['stroke-dasharray'].value = elem.attrs[
'stroke-dasharray'
].value
.trim()
.replace(regNumericValues, (num) => removeLeadingZero(num * scale));
}
}
}
} else if (id) {
// Stroke and stroke-width can be redefined with <use>
return path;
}
path.forEach(function (pathItem) {
if (pathItem.data) {
// h -> l
if (pathItem.instruction === 'h') {
pathItem.instruction = 'l';
pathItem.data[1] = 0;
// v -> l
} else if (pathItem.instruction === 'v') {
pathItem.instruction = 'l';
pathItem.data[1] = pathItem.data[0];
pathItem.data[0] = 0;
}
// if there is a translate() transform
if (
pathItem.instruction === 'M' &&
(matrix.data[4] !== 0 || matrix.data[5] !== 0)
) {
// then apply it only to the first absoluted M
newPoint = transformPoint(
matrix.data,
pathItem.data[0],
pathItem.data[1]
);
set(pathItem.data, newPoint);
set(pathItem.coords, newPoint);
// clear translate() data from transform matrix
matrix.data[4] = 0;
matrix.data[5] = 0;
} else {
if (pathItem.instruction == 'a') {
transformArc(pathItem.data, matrix.data);
// reduce number of digits in rotation angle
if (Math.abs(pathItem.data[2]) > 80) {
var a = pathItem.data[0],
rotation = pathItem.data[2];
pathItem.data[0] = pathItem.data[1];
pathItem.data[1] = a;
pathItem.data[2] = rotation + (rotation > 0 ? -90 : 90);
}
newPoint = transformPoint(
matrix.data,
pathItem.data[5],
pathItem.data[6]
);
pathItem.data[5] = newPoint[0];
pathItem.data[6] = newPoint[1];
} else {
for (var i = 0; i < pathItem.data.length; i += 2) {
newPoint = transformPoint(
matrix.data,
pathItem.data[i],
pathItem.data[i + 1]
);
pathItem.data[i] = newPoint[0];
pathItem.data[i + 1] = newPoint[1];
}
}
pathItem.coords[0] =
pathItem.base[0] + pathItem.data[pathItem.data.length - 2];
pathItem.coords[1] =
pathItem.base[1] + pathItem.data[pathItem.data.length - 1];
}
}
});
// remove transform attr
elem.removeAttr('transform');
return path;
};
/**
* Apply transform 3x3 matrix to x-y point.
*
* @param {Array} matrix transform 3x3 matrix
* @param {Array} point x-y point
* @return {Array} point with new coordinates
*/
function transformPoint(matrix, x, y) {
return [
matrix[0] * x + matrix[2] * y + matrix[4],
matrix[1] * x + matrix[3] * y + matrix[5],
];
}
/**
* Compute Cubic Bézie bounding box.

@@ -556,3 +349,3 @@ *

path.attr('d').value = stringifyPathData({
path.attributes.d = stringifyPathData({
pathData,

@@ -559,0 +352,0 @@ precision: params.floatPrecision,

@@ -264,2 +264,3 @@ 'use strict';

*
* @param {Array} cursor [x, y]
* @param {Array} arc [a, b, rotation in deg]

@@ -269,3 +270,5 @@ * @param {Array} transform transformation matrix

*/
exports.transformArc = function (arc, transform) {
exports.transformArc = function (cursor, arc, transform) {
const x = arc[5] - cursor[0];
const y = arc[6] - cursor[1];
var a = arc[0],

@@ -277,4 +280,4 @@ b = arc[1],

h =
Math.pow(arc[5] * cos + arc[6] * sin, 2) / (4 * a * a) +
Math.pow(arc[6] * cos - arc[5] * sin, 2) / (4 * b * b);
Math.pow(x * cos + y * sin, 2) / (4 * a * a) +
Math.pow(y * cos - x * sin, 2) / (4 * b * b);
if (h > 1) {

@@ -281,0 +284,0 @@ h = Math.sqrt(h);

'use strict';
exports.type = 'full';
const { closestByName } = require('../lib/xast.js');
exports.type = 'perItem';
exports.active = false;

@@ -46,3 +48,3 @@

]
`
`;

@@ -54,38 +56,30 @@ /**

*/
exports.fn = function(data, params) {
exports.fn = (node, params) => {
if (
node.type === 'element' &&
node.name === 'svg' &&
closestByName(node.parentNode, 'svg') == null
) {
if (!params || !(Array.isArray(params.attributes) || params.attribute)) {
console.error(ENOCLS);
return data;
console.error(ENOCLS);
return;
}
var attributes = params.attributes || [ params.attribute ],
svg = data.content[0];
const attributes = params.attributes || [params.attribute];
if (svg.isElem('svg')) {
attributes.forEach(function (attribute) {
if (typeof attribute === 'string') {
if (!svg.hasAttr(attribute)) {
svg.addAttr({
name: attribute,
prefix: '',
local: attribute
});
}
} else if (typeof attribute === 'object') {
Object.keys(attribute).forEach(function (key) {
if (!svg.hasAttr(key)) {
svg.addAttr({
name: key,
value: attribute[key],
prefix: '',
local: key
});
}
});
}
});
for (const attribute of attributes) {
if (typeof attribute === 'string') {
if (node.attributes[attribute] == null) {
node.attributes[attribute] = undefined;
}
}
if (typeof attribute === 'object') {
for (const key of Object.keys(attribute)) {
if (node.attributes[key] == null) {
node.attributes[key] = attribute[key];
}
}
}
}
return data;
}
};

@@ -35,17 +35,22 @@ 'use strict';

*/
exports.fn = function(data, params) {
if (!params || !(Array.isArray(params.classNames) && params.classNames.some(String) || params.className)) {
console.error(ENOCLS);
return data;
}
exports.fn = function (data, params) {
if (
!params ||
!(
(Array.isArray(params.classNames) && params.classNames.some(String)) ||
params.className
)
) {
console.error(ENOCLS);
return data;
}
var classNames = params.classNames || [ params.className ],
svg = data.content[0];
var classNames = params.classNames || [params.className],
svg = data.children[0];
if (svg.isElem('svg')) {
svg.class.add.apply(svg.class, classNames);
}
if (svg.isElem('svg')) {
svg.class.add.apply(svg.class, classNames);
}
return data;
return data;
};

@@ -7,13 +7,14 @@ 'use strict';

exports.description = 'cleanups attributes from newlines, trailing and repeating spaces';
exports.description =
'cleanups attributes from newlines, trailing and repeating spaces';
exports.params = {
newlines: true,
trim: true,
spaces: true
newlines: true,
trim: true,
spaces: true,
};
var regNewlinesNeedSpace = /(\S)\r?\n(\S)/g,
regNewlines = /\r?\n/g,
regSpaces = /\s{2,}/g;
regNewlines = /\r?\n/g,
regSpaces = /\s{2,}/g;

@@ -29,30 +30,25 @@ /**

*/
exports.fn = function(item, params) {
exports.fn = function (item, params) {
if (item.type === 'element') {
for (const name of Object.keys(item.attributes)) {
if (params.newlines) {
// new line which requires a space instead of themselve
item.attributes[name] = item.attributes[name].replace(
regNewlinesNeedSpace,
(match, p1, p2) => p1 + ' ' + p2
);
if (item.isElem()) {
// simple new line
item.attributes[name] = item.attributes[name].replace(regNewlines, '');
}
item.eachAttr(function(attr) {
if (params.trim) {
item.attributes[name] = item.attributes[name].trim();
}
if (params.newlines) {
// new line which requires a space instead of themselve
attr.value = attr.value.replace(regNewlinesNeedSpace, function(match, p1, p2) {
return p1 + ' ' + p2;
});
// simple new line
attr.value = attr.value.replace(regNewlines, '');
}
if (params.trim) {
attr.value = attr.value.trim();
}
if (params.spaces) {
attr.value = attr.value.replace(regSpaces, ' ');
}
});
if (params.spaces) {
item.attributes[name] = item.attributes[name].replace(regSpaces, ' ');
}
}
}
};
'use strict';
const { traverse } = require('../lib/xast.js');
exports.type = 'full';

@@ -7,3 +9,4 @@

exports.description = 'remove or cleanup enable-background attribute when possible';
exports.description =
'remove or cleanup enable-background attribute when possible';

@@ -20,3 +23,3 @@ /**

*
* @param {Object} item current iteration item
* @param {Object} root current iteration item
* @return {Boolean} if false, item will be filtered out

@@ -26,62 +29,48 @@ *

*/
exports.fn = function(data) {
exports.fn = function (root) {
const regEnableBackground = /^new\s0\s0\s([-+]?\d*\.?\d+([eE][-+]?\d+)?)\s([-+]?\d*\.?\d+([eE][-+]?\d+)?)$/;
let hasFilter = false;
const elems = ['svg', 'mask', 'pattern'];
var regEnableBackground = /^new\s0\s0\s([-+]?\d*\.?\d+([eE][-+]?\d+)?)\s([-+]?\d*\.?\d+([eE][-+]?\d+)?)$/,
hasFilter = false,
elems = ['svg', 'mask', 'pattern'];
traverse(root, (node) => {
if (node.type === 'element') {
if (
elems.includes(node.name) &&
node.attributes['enable-background'] != null &&
node.attributes.width != null &&
node.attributes.height != null
) {
const match = node.attributes['enable-background'].match(
regEnableBackground
);
function checkEnableBackground(item) {
if (
item.isElem(elems) &&
item.hasAttr('enable-background') &&
item.hasAttr('width') &&
item.hasAttr('height')
) {
var match = item.attr('enable-background').value.match(regEnableBackground);
if (match) {
if (
item.attr('width').value === match[1] &&
item.attr('height').value === match[3]
) {
if (item.isElem('svg')) {
item.removeAttr('enable-background');
} else {
item.attr('enable-background').value = 'new';
}
}
if (match) {
if (
node.attributes.width === match[1] &&
node.attributes.height === match[3]
) {
if (node.name === 'svg') {
delete node.attributes['enable-background'];
} else {
node.attributes['enable-background'] = 'new';
}
}
}
}
if (node.name === 'filter') {
hasFilter = true;
}
}
});
function checkForFilter(item) {
if (item.isElem('filter')) {
hasFilter = true;
}
}
function monkeys(items, fn) {
items.content.forEach(function(item) {
fn(item);
if (item.content) {
monkeys(item, fn);
}
});
return items;
}
var firstStep = monkeys(data, function(item) {
checkEnableBackground(item);
if (!hasFilter) {
checkForFilter(item);
}
if (hasFilter === false) {
traverse(root, (node) => {
if (node.type === 'element') {
//we don't need 'enable-background' if we have no filters
delete node.attributes['enable-background'];
}
});
}
return hasFilter ? firstStep : monkeys(firstStep, function(item) {
//we don't need 'enable-background' if we have no filters
item.removeAttr('enable-background');
});
return root;
};
'use strict';
const { traverse, traverseBreak } = require('../lib/xast.js');
const { parseName } = require('../lib/svgo/tools.js');
exports.type = 'full';

@@ -10,20 +13,70 @@

exports.params = {
remove: true,
minify: true,
prefix: '',
preserve: [],
preservePrefixes: [],
force: false
remove: true,
minify: true,
prefix: '',
preserve: [],
preservePrefixes: [],
force: false,
};
var referencesProps = new Set(require('./_collections').referencesProps),
regReferencesUrl = /\burl\(("|')?#(.+?)\1\)/,
regReferencesHref = /^#(.+?)$/,
regReferencesBegin = /(\w+)\./,
styleOrScript = ['style', 'script'],
generateIDchars = [
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
],
maxIDindex = generateIDchars.length - 1;
regReferencesUrl = /\burl\(("|')?#(.+?)\1\)/,
regReferencesHref = /^#(.+?)$/,
regReferencesBegin = /(\w+)\./,
styleOrScript = ['style', 'script'],
generateIDchars = [
'a',
'b',
'c',
'd',
'e',
'f',
'g',
'h',
'i',
'j',
'k',
'l',
'm',
'n',
'o',
'p',
'q',
'r',
's',
't',
'u',
'v',
'w',
'x',
'y',
'z',
'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
'K',
'L',
'M',
'N',
'O',
'P',
'Q',
'R',
'S',
'T',
'U',
'V',
'W',
'X',
'Y',
'Z',
],
maxIDindex = generateIDchars.length - 1;

@@ -39,124 +92,135 @@ /**

*/
exports.fn = function(data, params) {
var currentID,
currentIDstring,
IDs = new Map(),
referencesIDs = new Map(),
hasStyleOrScript = false,
preserveIDs = new Set(Array.isArray(params.preserve) ? params.preserve : params.preserve ? [params.preserve] : []),
preserveIDPrefixes = new Set(Array.isArray(params.preservePrefixes) ? params.preservePrefixes : (params.preservePrefixes ? [params.preservePrefixes] : [])),
idValuePrefix = '#',
idValuePostfix = '.';
exports.fn = function (root, params) {
var currentID,
currentIDstring,
IDs = new Map(),
referencesIDs = new Map(),
hasStyleOrScript = false,
preserveIDs = new Set(
Array.isArray(params.preserve)
? params.preserve
: params.preserve
? [params.preserve]
: []
),
preserveIDPrefixes = new Set(
Array.isArray(params.preservePrefixes)
? params.preservePrefixes
: params.preservePrefixes
? [params.preservePrefixes]
: []
),
idValuePrefix = '#',
idValuePostfix = '.';
/**
* Bananas!
*
* @param {Array} items input items
* @return {Array} output items
*/
function monkeys(items) {
for (var i = 0; i < items.content.length && !hasStyleOrScript; i++) {
var item = items.content[i];
traverse(root, (node) => {
if (hasStyleOrScript === true) {
return traverseBreak;
}
// quit if <style> or <script> present ('force' param prevents quitting)
if (!params.force) {
var isNotEmpty = Boolean(item.content);
if (item.isElem(styleOrScript) && isNotEmpty) {
hasStyleOrScript = true;
continue;
}
// quit if <style> or <script> present ('force' param prevents quitting)
if (!params.force) {
if (node.isElem(styleOrScript) && node.children.length !== 0) {
hasStyleOrScript = true;
return;
}
// Don't remove IDs if the whole SVG consists only of defs.
if (item.isElem('svg') && item.content) {
var hasDefsOnly = true;
for (var j = 0; j < item.content.length; j++) {
if (!item.content[j].isElem('defs')) {
hasDefsOnly = false;
break;
}
}
if (hasDefsOnly) {
break;
}
}
}
// …and don't remove any ID if yes
if (item.isElem()) {
item.eachAttr(function(attr) {
var key, match;
// save IDs
if (attr.name === 'id') {
key = attr.value;
if (IDs.has(key)) {
item.removeAttr('id'); // remove repeated id
} else {
IDs.set(key, item);
}
return;
}
// save references
if (referencesProps.has(attr.name) && (match = attr.value.match(regReferencesUrl))) {
key = match[2]; // url() reference
} else if (
attr.local === 'href' && (match = attr.value.match(regReferencesHref)) ||
attr.name === 'begin' && (match = attr.value.match(regReferencesBegin))
) {
key = match[1]; // href reference
}
if (key) {
var ref = referencesIDs.get(key) || [];
ref.push(attr);
referencesIDs.set(key, ref);
}
});
}
// go deeper
if (item.content) {
monkeys(item);
}
// Don't remove IDs if the whole SVG consists only of defs.
if (node.type === 'element' && node.name === 'svg') {
let hasDefsOnly = true;
for (const child of node.children) {
if (child.type !== 'element' || child.name !== 'defs') {
hasDefsOnly = false;
break;
}
}
return items;
if (hasDefsOnly) {
return traverseBreak;
}
}
}
data = monkeys(data);
// …and don't remove any ID if yes
if (node.type === 'element') {
for (const [name, value] of Object.entries(node.attributes)) {
let key;
let match;
if (hasStyleOrScript) {
return data;
// save IDs
if (name === 'id') {
key = value;
if (IDs.has(key)) {
delete node.attributes.id; // remove repeated id
} else {
IDs.set(key, node);
}
} else {
// save references
const { local } = parseName(name);
if (
referencesProps.has(name) &&
(match = value.match(regReferencesUrl))
) {
key = match[2]; // url() reference
} else if (
(local === 'href' && (match = value.match(regReferencesHref))) ||
(name === 'begin' && (match = value.match(regReferencesBegin)))
) {
key = match[1]; // href reference
}
if (key) {
const refs = referencesIDs.get(key) || [];
refs.push({ element: node, name, value });
referencesIDs.set(key, refs);
}
}
}
}
});
const idPreserved = id => preserveIDs.has(id) || idMatchesPrefix(preserveIDPrefixes, id);
if (hasStyleOrScript) {
return root;
}
for (var ref of referencesIDs) {
var key = ref[0];
const idPreserved = (id) =>
preserveIDs.has(id) || idMatchesPrefix(preserveIDPrefixes, id);
if (IDs.has(key)) {
// replace referenced IDs with the minified ones
if (params.minify && !idPreserved(key)) {
do {
currentIDstring = getIDstring(currentID = generateID(currentID), params);
} while (idPreserved(currentIDstring));
for (const [key, refs] of referencesIDs) {
if (IDs.has(key)) {
// replace referenced IDs with the minified ones
if (params.minify && !idPreserved(key)) {
do {
currentIDstring = getIDstring(
(currentID = generateID(currentID)),
params
);
} while (idPreserved(currentIDstring));
IDs.get(key).attr('id').value = currentIDstring;
IDs.get(key).attributes.id = currentIDstring;
for (var attr of ref[1]) {
attr.value = attr.value.includes(idValuePrefix) ?
attr.value.replace(idValuePrefix + key, idValuePrefix + currentIDstring) :
attr.value.replace(key + idValuePostfix, currentIDstring + idValuePostfix);
}
}
// don't remove referenced IDs
IDs.delete(key);
for (const { element, name, value } of refs) {
element.attributes[name] = value.includes(idValuePrefix)
? value.replace(
idValuePrefix + key,
idValuePrefix + currentIDstring
)
: value.replace(
key + idValuePostfix,
currentIDstring + idValuePostfix
);
}
}
// don't remove referenced IDs
IDs.delete(key);
}
// remove non-referenced IDs attributes from elements
if (params.remove) {
for(var keyElem of IDs) {
if (!idPreserved(keyElem[0])) {
keyElem[1].removeAttr('id');
}
}
}
// remove non-referenced IDs attributes from elements
if (params.remove) {
for (var keyElem of IDs) {
if (!idPreserved(keyElem[0])) {
delete keyElem[1].attributes.id;
}
}
return data;
}
return root;
};

@@ -172,6 +236,6 @@

function idMatchesPrefix(prefixArray, currentID) {
if (!currentID) return false;
if (!currentID) return false;
for (var prefix of prefixArray) if (currentID.startsWith(prefix)) return true;
return false;
for (var prefix of prefixArray) if (currentID.startsWith(prefix)) return true;
return false;
}

@@ -186,20 +250,20 @@

function generateID(currentID) {
if (!currentID) return [0];
if (!currentID) return [0];
currentID[currentID.length - 1]++;
currentID[currentID.length - 1]++;
for(var i = currentID.length - 1; i > 0; i--) {
if (currentID[i] > maxIDindex) {
currentID[i] = 0;
for (var i = currentID.length - 1; i > 0; i--) {
if (currentID[i] > maxIDindex) {
currentID[i] = 0;
if (currentID[i - 1] !== undefined) {
currentID[i - 1]++;
}
}
if (currentID[i - 1] !== undefined) {
currentID[i - 1]++;
}
}
if (currentID[0] > maxIDindex) {
currentID[0] = 0;
currentID.unshift(0);
}
return currentID;
}
if (currentID[0] > maxIDindex) {
currentID[0] = 0;
currentID.unshift(0);
}
return currentID;
}

@@ -214,4 +278,4 @@

function getIDstring(arr, params) {
var str = params.prefix;
return str + arr.map(i => generateIDchars[i]).join('');
var str = params.prefix;
return str + arr.map((i) => generateIDchars[i]).join('');
}
'use strict';
const { removeLeadingZero } = require('../lib/svgo/tools.js');
exports.type = 'perItem';

@@ -10,18 +12,18 @@

exports.params = {
floatPrecision: 3,
leadingZero: true,
defaultPx: true,
convertToPx: true
floatPrecision: 3,
leadingZero: true,
defaultPx: true,
convertToPx: true,
};
var regNumericValues = /^([-+]?\d*\.?\d+([eE][-+]?\d+)?)(px|pt|pc|mm|cm|m|in|ft|em|ex|%)?$/,
regSeparator = /\s+,?\s*|,\s*/,
removeLeadingZero = require('../lib/svgo/tools').removeLeadingZero,
absoluteLengths = { // relative to px
cm: 96/2.54,
mm: 96/25.4,
in: 96,
pt: 4/3,
pc: 16
};
const regNumericValues = /^([-+]?\d*\.?\d+([eE][-+]?\d+)?)(px|pt|pc|mm|cm|m|in|ft|em|ex|%)?$/;
const regSeparator = /\s+,?\s*|,\s*/;
const absoluteLengths = {
// relative to px
cm: 96 / 2.54,
mm: 96 / 25.4,
in: 96,
pt: 4 / 3,
pc: 16,
};

@@ -48,94 +50,93 @@ /**

*/
exports.fn = function(item, params) {
exports.fn = function (item, params) {
if (item.type !== 'element') {
return;
}
if (item.attributes.points != null) {
item.attributes.points = roundValues(item.attributes.points);
}
if ( item.hasAttr('points') ) {
roundValues(item.attrs.points);
}
if (item.attributes['enable-background'] != null) {
item.attributes['enable-background'] = roundValues(
item.attributes['enable-background']
);
}
if ( item.hasAttr('enable-background') ) {
roundValues(item.attrs['enable-background']);
}
if (item.attributes.viewBox != null) {
item.attributes.viewBox = roundValues(item.attributes.viewBox);
}
if ( item.hasAttr('viewBox') ) {
roundValues(item.attrs.viewBox);
}
if (item.attributes['stroke-dasharray'] != null) {
item.attributes['stroke-dasharray'] = roundValues(
item.attributes['stroke-dasharray']
);
}
if ( item.hasAttr('stroke-dasharray') ) {
roundValues(item.attrs['stroke-dasharray']);
}
if (item.attributes.dx != null) {
item.attributes.dx = roundValues(item.attributes.dx);
}
if ( item.hasAttr('dx') ) {
roundValues(item.attrs.dx);
}
if (item.attributes.dy != null) {
item.attributes.dy = roundValues(item.attributes.dy);
}
if ( item.hasAttr('dy') ) {
roundValues(item.attrs.dy);
}
if (item.attributes.x != null) {
item.attributes.x = roundValues(item.attributes.x);
}
if ( item.hasAttr('x') ) {
roundValues(item.attrs.x);
}
if (item.attributes.y != null) {
item.attributes.y = roundValues(item.attributes.y);
}
if ( item.hasAttr('y') ) {
roundValues(item.attrs.y);
}
function roundValues(lists) {
var num,
units,
match,
matchNew,
listsArr = lists.split(regSeparator),
roundedList = [];
for (const elem of listsArr) {
match = elem.match(regNumericValues);
matchNew = elem.match(/new/);
function roundValues($prop){
// if attribute value matches regNumericValues
if (match) {
// round it to the fixed precision
(num = +(+match[1]).toFixed(params.floatPrecision)),
(units = match[3] || '');
var num, units,
match,
matchNew,
lists = $prop.value,
listsArr = lists.split(regSeparator),
roundedListArr = [],
roundedList;
// convert absolute values to pixels
if (params.convertToPx && units && units in absoluteLengths) {
var pxNum = +(absoluteLengths[units] * match[1]).toFixed(
params.floatPrecision
);
listsArr.forEach(function(elem){
if (String(pxNum).length < match[0].length)
(num = pxNum), (units = 'px');
}
match = elem.match(regNumericValues);
matchNew = elem.match(/new/);
// and remove leading zero
if (params.leadingZero) {
num = removeLeadingZero(num);
}
// if attribute value matches regNumericValues
if (match) {
// round it to the fixed precision
num = +(+match[1]).toFixed(params.floatPrecision),
units = match[3] || '';
// remove default 'px' units
if (params.defaultPx && units === 'px') {
units = '';
}
// convert absolute values to pixels
if (params.convertToPx && units && (units in absoluteLengths)) {
var pxNum = +(absoluteLengths[units] * match[1]).toFixed(params.floatPrecision);
if (String(pxNum).length < match[0].length)
num = pxNum,
units = 'px';
}
// and remove leading zero
if (params.leadingZero) {
num = removeLeadingZero(num);
}
// remove default 'px' units
if (params.defaultPx && units === 'px') {
units = '';
}
roundedListArr.push(num+units);
}
// if attribute value is "new"(only enable-background).
else if (matchNew) {
roundedListArr.push('new');
} else if (elem) {
roundedListArr.push(elem);
}
});
roundedList = roundedListArr.join(' ');
$prop.value = roundedList;
roundedList.push(num + units);
}
// if attribute value is "new"(only enable-background).
else if (matchNew) {
roundedList.push('new');
} else if (elem) {
roundedList.push(elem);
}
}
return roundedList.join(' ');
}
};

@@ -7,20 +7,22 @@ 'use strict';

exports.description = 'rounds numeric values to the fixed precision, removes default ‘px’ units';
exports.description =
'rounds numeric values to the fixed precision, removes default ‘px’ units';
exports.params = {
floatPrecision: 3,
leadingZero: true,
defaultPx: true,
convertToPx: true
floatPrecision: 3,
leadingZero: true,
defaultPx: true,
convertToPx: true,
};
var regNumericValues = /^([-+]?\d*\.?\d+([eE][-+]?\d+)?)(px|pt|pc|mm|cm|m|in|ft|em|ex|%)?$/,
removeLeadingZero = require('../lib/svgo/tools').removeLeadingZero,
absoluteLengths = { // relative to px
cm: 96/2.54,
mm: 96/25.4,
in: 96,
pt: 4/3,
pc: 16
};
removeLeadingZero = require('../lib/svgo/tools').removeLeadingZero,
absoluteLengths = {
// relative to px
cm: 96 / 2.54,
mm: 96 / 25.4,
in: 96,
pt: 4 / 3,
pc: 16,
};

@@ -37,54 +39,56 @@ /**

*/
exports.fn = function(item, params) {
exports.fn = function (item, params) {
if (item.type === 'element') {
var floatPrecision = params.floatPrecision;
if (item.isElem()) {
if (item.attributes.viewBox != null) {
var nums = item.attributes.viewBox.split(/\s,?\s*|,\s*/g);
item.attributes.viewBox = nums
.map(function (value) {
var num = +value;
return isNaN(num) ? value : +num.toFixed(floatPrecision);
})
.join(' ');
}
var floatPrecision = params.floatPrecision;
for (const [name, value] of Object.entries(item.attributes)) {
// The `version` attribute is a text string and cannot be rounded
if (name === 'version') {
continue;
}
if (item.hasAttr('viewBox')) {
var nums = item.attr('viewBox').value.split(/\s,?\s*|,\s*/g);
item.attr('viewBox').value = nums.map(function(value) {
var num = +value;
return isNaN(num) ? value : +num.toFixed(floatPrecision);
}).join(' ');
}
var match = value.match(regNumericValues);
item.eachAttr(function(attr) {
// The `version` attribute is a text string and cannot be rounded
if (attr.name === 'version') { return }
// if attribute value matches regNumericValues
if (match) {
// round it to the fixed precision
var num = +(+match[1]).toFixed(floatPrecision),
units = match[3] || '';
var match = attr.value.match(regNumericValues);
// convert absolute values to pixels
if (params.convertToPx && units && units in absoluteLengths) {
var pxNum = +(absoluteLengths[units] * match[1]).toFixed(
floatPrecision
);
// if attribute value matches regNumericValues
if (match) {
// round it to the fixed precision
var num = +(+match[1]).toFixed(floatPrecision),
units = match[3] || '';
if (String(pxNum).length < match[0].length) {
num = pxNum;
units = 'px';
}
}
// convert absolute values to pixels
if (params.convertToPx && units && (units in absoluteLengths)) {
var pxNum = +(absoluteLengths[units] * match[1]).toFixed(floatPrecision);
// and remove leading zero
if (params.leadingZero) {
num = removeLeadingZero(num);
}
if (String(pxNum).length < match[0].length) {
num = pxNum;
units = 'px';
}
}
// remove default 'px' units
if (params.defaultPx && units === 'px') {
units = '';
}
// and remove leading zero
if (params.leadingZero) {
num = removeLeadingZero(num);
}
// remove default 'px' units
if (params.defaultPx && units === 'px') {
units = '';
}
attr.value = num + units;
}
});
item.attributes[name] = num + units;
}
}
}
};
'use strict';
const { inheritableAttrs, elemsGroups } = require('./_collections');
exports.type = 'perItemReverse';

@@ -9,9 +11,12 @@

var collections = require('./_collections'),
attrsInheritable = collections.inheritableAttrs,
animationElems = collections.elemsGroups.animation;
function hasAnimatedAttr(item) {
return item.isElem(animationElems) && item.hasAttr('attributeName', this) ||
!item.isEmpty() && item.content.some(hasAnimatedAttr, this);
function hasAnimatedAttr(item, name) {
if (item.type === 'element') {
return (
(elemsGroups.animation.includes(item.name) &&
item.attributes.attributeName === name) ||
(item.children.length !== 0 &&
item.children.some((child) => hasAnimatedAttr(child, name)))
);
}
return false;
}

@@ -42,47 +47,59 @@

*/
exports.fn = function(item) {
exports.fn = function (item) {
// non-empty elements
if (
item.type === 'element' &&
item.name !== 'switch' &&
item.children.length !== 0
) {
item.children.forEach(function (g, i) {
// non-empty groups
if (g.type === 'element' && g.name === 'g' && g.children.length !== 0) {
// move group attibutes to the single child element
if (Object.keys(g.attributes).length !== 0 && g.children.length === 1) {
var inner = g.children[0];
// non-empty elements
if (item.isElem() && !item.isElem('switch') && !item.isEmpty()) {
item.content.forEach(function(g, i) {
// non-empty groups
if (g.isElem('g') && !g.isEmpty()) {
// move group attibutes to the single content element
if (g.hasAttr() && g.content.length === 1) {
var inner = g.content[0];
if (
inner.type === 'element' &&
inner.attributes.id == null &&
g.attributes.filter == null &&
(g.attributes.class == null || inner.attributes.class == null) &&
((g.attributes['clip-path'] == null && g.attributes.mask == null) ||
(inner.type === 'element' &&
inner.name === 'g' &&
g.attributes.transform == null &&
inner.attributes.transform == null))
) {
for (const [name, value] of Object.entries(g.attributes)) {
if (g.children.some((item) => hasAnimatedAttr(item, name)))
return;
if (inner.isElem() && !inner.hasAttr('id') && !g.hasAttr('filter') &&
!(g.hasAttr('class') && inner.hasAttr('class')) && (
!g.hasAttr('clip-path') && !g.hasAttr('mask') ||
inner.isElem('g') && !g.hasAttr('transform') && !inner.hasAttr('transform')
)
) {
g.eachAttr(function(attr) {
if (g.content.some(hasAnimatedAttr, attr.name)) return;
if (inner.attributes[name] == null) {
inner.attributes[name] = value;
} else if (name == 'transform') {
inner.attributes[name] = value + ' ' + inner.attributes[name];
} else if (inner.attributes[name] === 'inherit') {
inner.attributes[name] = value;
} else if (
inheritableAttrs.includes(name) === false &&
inner.attributes[name] !== value
) {
return;
}
if (!inner.hasAttr(attr.name)) {
inner.addAttr(attr);
} else if (attr.name == 'transform') {
inner.attr(attr.name).value = attr.value + ' ' + inner.attr(attr.name).value;
} else if (inner.hasAttr(attr.name, 'inherit')) {
inner.attr(attr.name).value = attr.value;
} else if (
attrsInheritable.indexOf(attr.name) < 0 &&
!inner.hasAttr(attr.name, attr.value)
) {
return;
}
delete g.attributes[name];
}
}
}
g.removeAttr(attr.name);
});
}
}
// collapse groups without attributes
if (!g.hasAttr() && !g.content.some(function(item) { return item.isElem(animationElems) })) {
item.spliceContent(i, 1, g.content);
}
}
});
}
// collapse groups without attributes
if (
Object.keys(g.attributes).length === 0 &&
!g.children.some((item) => item.isElem(elemsGroups.animation))
) {
item.spliceContent(i, 1, g.children);
}
}
});
}
};

@@ -10,15 +10,17 @@ 'use strict';

exports.params = {
currentColor: false,
names2hex: true,
rgb2hex: true,
shorthex: true,
shortname: true
currentColor: false,
names2hex: true,
rgb2hex: true,
shorthex: true,
shortname: true,
};
var collections = require('./_collections'),
rNumber = '([+-]?(?:\\d*\\.\\d+|\\d+\\.?)%?)',
rComma = '\\s*,\\s*',
regRGB = new RegExp('^rgb\\(\\s*' + rNumber + rComma + rNumber + rComma + rNumber + '\\s*\\)$'),
regHEX = /^#(([a-fA-F0-9])\2){3}$/,
none = /\bnone\b/i;
rNumber = '([+-]?(?:\\d*\\.\\d+|\\d+\\.?)%?)',
rComma = '\\s*,\\s*',
regRGB = new RegExp(
'^rgb\\(\\s*' + rNumber + rComma + rNumber + rComma + rNumber + '\\s*\\)$'
),
regHEX = /^#(([a-fA-F0-9])\2){3}$/,
none = /\bnone\b/i;

@@ -51,65 +53,56 @@ /**

*/
exports.fn = function(item, params) {
exports.fn = function (item, params) {
if (item.type === 'element') {
for (const [name, value] of Object.entries(item.attributes)) {
if (collections.colorsProps.includes(name)) {
let val = value;
let match;
if (item.elem) {
// Convert colors to currentColor
if (params.currentColor) {
if (typeof params.currentColor === 'string') {
match = val === params.currentColor;
} else if (params.currentColor.exec) {
match = params.currentColor.exec(val);
} else {
match = !val.match(none);
}
if (match) {
val = 'currentColor';
}
}
item.eachAttr(function(attr) {
// Convert color name keyword to long hex
if (params.names2hex && val.toLowerCase() in collections.colorsNames) {
val = collections.colorsNames[val.toLowerCase()];
}
if (collections.colorsProps.indexOf(attr.name) > -1) {
// Convert rgb() to long hex
if (params.rgb2hex && (match = val.match(regRGB))) {
match = match.slice(1, 4).map(function (m) {
if (m.indexOf('%') > -1) m = Math.round(parseFloat(m) * 2.55);
var val = attr.value,
match;
return Math.max(0, Math.min(m, 255));
});
// Convert colors to currentColor
if (params.currentColor) {
if (typeof params.currentColor === 'string') {
match = val === params.currentColor;
} else if (params.currentColor.exec) {
match = params.currentColor.exec(val);
} else {
match = !val.match(none);
}
if (match) {
val = 'currentColor';
}
}
val = rgb2hex(match);
}
// Convert color name keyword to long hex
if (params.names2hex && val.toLowerCase() in collections.colorsNames) {
val = collections.colorsNames[val.toLowerCase()];
}
// Convert long hex to short hex
if (params.shorthex && (match = val.match(regHEX))) {
val = '#' + match[0][1] + match[0][3] + match[0][5];
}
// Convert rgb() to long hex
if (params.rgb2hex && (match = val.match(regRGB))) {
match = match.slice(1, 4).map(function(m) {
if (m.indexOf('%') > -1)
m = Math.round(parseFloat(m) * 2.55);
// Convert hex to short name
if (params.shortname) {
var lowerVal = val.toLowerCase();
if (lowerVal in collections.colorsShortNames) {
val = collections.colorsShortNames[lowerVal];
}
}
return Math.max(0, Math.min(m, 255));
});
val = rgb2hex(match);
}
// Convert long hex to short hex
if (params.shorthex && (match = val.match(regHEX))) {
val = '#' + match[0][1] + match[0][3] + match[0][5];
}
// Convert hex to short name
if (params.shortname) {
var lowerVal = val.toLowerCase();
if (lowerVal in collections.colorsShortNames) {
val = collections.colorsShortNames[lowerVal];
}
}
attr.value = val;
}
});
item.attributes[name] = val;
}
}
}
};

@@ -131,3 +124,8 @@

function rgb2hex(rgb) {
return '#' + ('00000' + (rgb[0] << 16 | rgb[1] << 8 | rgb[2]).toString(16)).slice(-6).toUpperCase();
return (
'#' +
('00000' + ((rgb[0] << 16) | (rgb[1] << 8) | rgb[2]).toString(16))
.slice(-6)
.toUpperCase()
);
}

@@ -19,22 +19,19 @@ 'use strict';

*/
exports.fn = function(item) {
if (item.isElem('ellipse')) {
var rx = item.hasAttr('rx') && item.attr('rx').value || 0;
var ry = item.hasAttr('ry') && item.attr('ry').value || 0;
exports.fn = function (item) {
if (item.isElem('ellipse')) {
const rx = item.attributes.rx || 0;
const ry = item.attributes.ry || 0;
if (rx === ry ||
rx === 'auto' || ry === 'auto' // SVG2
) {
var radius = rx !== 'auto' ? rx : ry;
item.renameElem('circle');
item.removeAttr(['rx', 'ry']);
item.addAttr({
name: 'r',
value: radius,
prefix: '',
local: 'r',
});
}
if (
rx === ry ||
rx === 'auto' ||
ry === 'auto' // SVG2
) {
var radius = rx !== 'auto' ? rx : ry;
item.renameElem('circle');
delete item.attributes.rx;
delete item.attributes.ry;
item.attributes.r = radius;
}
}
return;
};

@@ -5,3 +5,4 @@ 'use strict';

const { pathElems } = require('./_collections.js');
const { path2js, js2path, applyTransforms } = require('./_path.js');
const { path2js, js2path } = require('./_path.js');
const { applyTransforms } = require('./_applyTransforms.js');
const { cleanupOutData } = require('../lib/svgo/tools');

@@ -60,3 +61,7 @@

exports.fn = function (item, params) {
if (item.isElem(pathElems) && item.hasAttr('d')) {
if (
item.type === 'element' &&
pathElems.includes(item.name) &&
item.attributes.d != null
) {
const computedStyle = computeStyle(item);

@@ -87,8 +92,8 @@ precision = params.floatPrecision;

if (data.length) {
convertToRelative(data);
if (params.applyTransforms) {
data = applyTransforms(item, data, params);
applyTransforms(item, data, params);
}
convertToRelative(data);
data = filters(data, params, {

@@ -115,139 +120,166 @@ maybeHasStrokeAndLinecap,

*/
function convertToRelative(path) {
var point = [0, 0],
subpathPoint = [0, 0],
baseItem;
const convertToRelative = (pathData) => {
let start = [0, 0];
let cursor = [0, 0];
let prevCoords = [0, 0];
path.forEach(function (item, index) {
var instruction = item.instruction,
data = item.data;
for (let i = 0; i < pathData.length; i += 1) {
const pathItem = pathData[i];
let { instruction: command, data: args } = pathItem;
// data !== !z
if (data) {
// already relative
// recalculate current point
if ('mcslqta'.indexOf(instruction) > -1) {
point[0] += data[data.length - 2];
point[1] += data[data.length - 1];
if (instruction === 'm') {
subpathPoint[0] = point[0];
subpathPoint[1] = point[1];
baseItem = item;
}
} else if (instruction === 'h') {
point[0] += data[0];
} else if (instruction === 'v') {
point[1] += data[0];
}
// convert absolute path data coordinates to relative
// if "M" was not transformed from "m"
// moveto (x y)
if (command === 'm') {
// update start and cursor
cursor[0] += args[0];
cursor[1] += args[1];
start[0] = cursor[0];
start[1] = cursor[1];
}
if (command === 'M') {
// M → m
if (instruction === 'M') {
if (index > 0) instruction = 'm';
data[0] -= point[0];
data[1] -= point[1];
subpathPoint[0] = point[0] += data[0];
subpathPoint[1] = point[1] += data[1];
baseItem = item;
// skip first moveto
if (i !== 0) {
command = 'm';
}
args[0] -= cursor[0];
args[1] -= cursor[1];
// update start and cursor
cursor[0] += args[0];
cursor[1] += args[1];
start[0] = cursor[0];
start[1] = cursor[1];
}
// lineto (x y)
if (command === 'l') {
cursor[0] += args[0];
cursor[1] += args[1];
}
if (command === 'L') {
// L → l
// T → t
else if ('LT'.indexOf(instruction) > -1) {
instruction = instruction.toLowerCase();
command = 'l';
args[0] -= cursor[0];
args[1] -= cursor[1];
cursor[0] += args[0];
cursor[1] += args[1];
}
// x y
// 0 1
data[0] -= point[0];
data[1] -= point[1];
// horizontal lineto (x)
if (command === 'h') {
cursor[0] += args[0];
}
if (command === 'H') {
// H → h
command = 'h';
args[0] -= cursor[0];
cursor[0] += args[0];
}
point[0] += data[0];
point[1] += data[1];
// vertical lineto (y)
if (command === 'v') {
cursor[1] += args[0];
}
if (command === 'V') {
// V → v
command = 'v';
args[0] -= cursor[1];
cursor[1] += args[0];
}
// C → c
} else if (instruction === 'C') {
instruction = 'c';
// curveto (x1 y1 x2 y2 x y)
if (command === 'c') {
cursor[0] += args[4];
cursor[1] += args[5];
}
if (command === 'C') {
// C → c
command = 'c';
args[0] -= cursor[0];
args[1] -= cursor[1];
args[2] -= cursor[0];
args[3] -= cursor[1];
args[4] -= cursor[0];
args[5] -= cursor[1];
cursor[0] += args[4];
cursor[1] += args[5];
}
// x1 y1 x2 y2 x y
// 0 1 2 3 4 5
data[0] -= point[0];
data[1] -= point[1];
data[2] -= point[0];
data[3] -= point[1];
data[4] -= point[0];
data[5] -= point[1];
// smooth curveto (x2 y2 x y)
if (command === 's') {
cursor[0] += args[2];
cursor[1] += args[3];
}
if (command === 'S') {
// S → s
command = 's';
args[0] -= cursor[0];
args[1] -= cursor[1];
args[2] -= cursor[0];
args[3] -= cursor[1];
cursor[0] += args[2];
cursor[1] += args[3];
}
point[0] += data[4];
point[1] += data[5];
// quadratic Bézier curveto (x1 y1 x y)
if (command === 'q') {
cursor[0] += args[2];
cursor[1] += args[3];
}
if (command === 'Q') {
// Q → q
command = 'q';
args[0] -= cursor[0];
args[1] -= cursor[1];
args[2] -= cursor[0];
args[3] -= cursor[1];
cursor[0] += args[2];
cursor[1] += args[3];
}
// S → s
// Q → q
} else if ('SQ'.indexOf(instruction) > -1) {
instruction = instruction.toLowerCase();
// smooth quadratic Bézier curveto (x y)
if (command === 't') {
cursor[0] += args[0];
cursor[1] += args[1];
}
if (command === 'T') {
// T → t
command = 't';
args[0] -= cursor[0];
args[1] -= cursor[1];
cursor[0] += args[0];
cursor[1] += args[1];
}
// x1 y1 x y
// 0 1 2 3
data[0] -= point[0];
data[1] -= point[1];
data[2] -= point[0];
data[3] -= point[1];
point[0] += data[2];
point[1] += data[3];
// A → a
} else if (instruction === 'A') {
instruction = 'a';
// rx ry x-axis-rotation large-arc-flag sweep-flag x y
// 0 1 2 3 4 5 6
data[5] -= point[0];
data[6] -= point[1];
point[0] += data[5];
point[1] += data[6];
// H → h
} else if (instruction === 'H') {
instruction = 'h';
data[0] -= point[0];
point[0] += data[0];
// V → v
} else if (instruction === 'V') {
instruction = 'v';
data[0] -= point[1];
point[1] += data[0];
}
item.instruction = instruction;
item.data = data;
// store absolute coordinates for later use
item.coords = point.slice(-2);
// elliptical arc (rx ry x-axis-rotation large-arc-flag sweep-flag x y)
if (command === 'a') {
cursor[0] += args[5];
cursor[1] += args[6];
}
if (command === 'A') {
// A → a
command = 'a';
args[5] -= cursor[0];
args[6] -= cursor[1];
cursor[0] += args[5];
cursor[1] += args[6];
}
// !data === z, reset current point
else if (instruction == 'z') {
if (baseItem) {
item.coords = baseItem.coords;
}
point[0] = subpathPoint[0];
point[1] = subpathPoint[1];
// closepath
if (command === 'Z' || command === 'z') {
// reset cursor
cursor[0] = start[0];
cursor[1] = start[1];
}
item.base = index > 0 ? path[index - 1].coords : [0, 0];
});
pathItem.instruction = command;
pathItem.data = args;
// store absolute coordinates for later use
// base should preserve reference from other element
pathItem.base = prevCoords;
pathItem.coords = [cursor[0], cursor[1]];
prevCoords = pathItem.coords;
}
return path;
}
return pathData;
};

@@ -254,0 +286,0 @@ /**

@@ -16,3 +16,2 @@ 'use strict';

const none = { value: 0 };
const regNumber = /[-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?/g;

@@ -39,11 +38,11 @@

item.isElem('rect') &&
item.hasAttr('width') &&
item.hasAttr('height') &&
!item.hasAttr('rx') &&
!item.hasAttr('ry')
item.attributes.width != null &&
item.attributes.height != null &&
item.attributes.rx == null &&
item.attributes.ry == null
) {
var x = +(item.attr('x') || none).value,
y = +(item.attr('y') || none).value,
width = +item.attr('width').value,
height = +item.attr('height').value;
const x = Number(item.attributes.x || '0');
const y = Number(item.attributes.y || '0');
const width = Number(item.attributes.width);
const height = Number(item.attributes.height);
// Values like '100%' compute to NaN, thus running after

@@ -60,16 +59,15 @@ // cleanupNumericValues when 'px' units has already been removed.

];
item.addAttr({
name: 'd',
value: stringifyPathData({ pathData, precision }),
prefix: '',
local: 'd',
});
item.renameElem('path').removeAttr(['x', 'y', 'width', 'height']);
item.attributes.d = stringifyPathData({ pathData, precision });
item.renameElem('path');
delete item.attributes.x;
delete item.attributes.y;
delete item.attributes.width;
delete item.attributes.height;
}
if (item.isElem('line')) {
var x1 = +(item.attr('x1') || none).value,
y1 = +(item.attr('y1') || none).value,
x2 = +(item.attr('x2') || none).value,
y2 = +(item.attr('y2') || none).value;
const x1 = Number(item.attributes.x1 || '0');
const y1 = Number(item.attributes.y1 || '0');
const x2 = Number(item.attributes.x2 || '0');
const y2 = Number(item.attributes.y2 || '0');
if (isNaN(x1 - y1 + x2 - y2)) return;

@@ -80,9 +78,8 @@ const pathData = [

];
item.addAttr({
name: 'd',
value: stringifyPathData({ pathData, precision }),
prefix: '',
local: 'd',
});
item.renameElem('path').removeAttr(['x1', 'y1', 'x2', 'y2']);
item.attributes.d = stringifyPathData({ pathData, precision });
item.renameElem('path');
delete item.attributes.x1;
delete item.attributes.y1;
delete item.attributes.x2;
delete item.attributes.y2;
}

@@ -92,5 +89,5 @@

(item.isElem('polyline') || item.isElem('polygon')) &&
item.hasAttr('points')
item.attributes.points != null
) {
var coords = (item.attr('points').value.match(regNumber) || []).map(Number);
const coords = (item.attributes.points.match(regNumber) || []).map(Number);
if (coords.length < 4) return false;

@@ -107,15 +104,11 @@ const pathData = [];

}
item.addAttr({
name: 'd',
value: stringifyPathData({ pathData, precision }),
prefix: '',
local: 'd',
});
item.renameElem('path').removeAttr('points');
item.attributes.d = stringifyPathData({ pathData, precision });
item.renameElem('path');
delete item.attributes.points;
}
if (item.isElem('circle') && convertArcs) {
var cx = +(item.attr('cx') || none).value;
var cy = +(item.attr('cy') || none).value;
var r = +(item.attr('r') || none).value;
const cx = Number(item.attributes.cx || '0');
const cy = Number(item.attributes.cy || '0');
const r = Number(item.attributes.r || '0');
if (isNaN(cx - cy + r)) {

@@ -130,16 +123,14 @@ return;

];
item.addAttr({
name: 'd',
value: stringifyPathData({ pathData, precision }),
prefix: '',
local: 'd',
});
item.renameElem('path').removeAttr(['cx', 'cy', 'r']);
item.attributes.d = stringifyPathData({ pathData, precision });
item.renameElem('path');
delete item.attributes.cx;
delete item.attributes.cy;
delete item.attributes.r;
}
if (item.isElem('ellipse') && convertArcs) {
var ecx = +(item.attr('cx') || none).value;
var ecy = +(item.attr('cy') || none).value;
var rx = +(item.attr('rx') || none).value;
var ry = +(item.attr('ry') || none).value;
const ecx = Number(item.attributes.cx || '0');
const ecy = Number(item.attributes.cy || '0');
const rx = Number(item.attributes.rx || '0');
const ry = Number(item.attributes.ry || '0');
if (isNaN(ecx - ecy + rx - ry)) {

@@ -154,10 +145,9 @@ return;

];
item.addAttr({
name: 'd',
value: stringifyPathData({ pathData, precision }),
prefix: '',
local: 'd',
});
item.renameElem('path').removeAttr(['cx', 'cy', 'rx', 'ry']);
item.attributes.d = stringifyPathData({ pathData, precision });
item.renameElem('path');
delete item.attributes.cx;
delete item.attributes.cy;
delete item.attributes.rx;
delete item.attributes.ry;
}
};

@@ -10,31 +10,43 @@ 'use strict';

exports.params = {
keepImportant: false
keepImportant: false,
};
var stylingProps = require('./_collections').attrsGroups.presentation,
rEscape = '\\\\(?:[0-9a-f]{1,6}\\s?|\\r\\n|.)', // Like \" or \2051. Code points consume one space.
rAttr = '\\s*(' + g('[^:;\\\\]', rEscape) + '*?)\\s*', // attribute name like ‘fill’
rSingleQuotes = "'(?:[^'\\n\\r\\\\]|" + rEscape + ")*?(?:'|$)", // string in single quotes: 'smth'
rQuotes = '"(?:[^"\\n\\r\\\\]|' + rEscape + ')*?(?:"|$)', // string in double quotes: "smth"
rQuotedString = new RegExp('^' + g(rSingleQuotes, rQuotes) + '$'),
rEscape = '\\\\(?:[0-9a-f]{1,6}\\s?|\\r\\n|.)', // Like \" or \2051. Code points consume one space.
rAttr = '\\s*(' + g('[^:;\\\\]', rEscape) + '*?)\\s*', // attribute name like ‘fill’
rSingleQuotes = "'(?:[^'\\n\\r\\\\]|" + rEscape + ")*?(?:'|$)", // string in single quotes: 'smth'
rQuotes = '"(?:[^"\\n\\r\\\\]|' + rEscape + ')*?(?:"|$)', // string in double quotes: "smth"
rQuotedString = new RegExp('^' + g(rSingleQuotes, rQuotes) + '$'),
// Parentheses, E.g.: url(data:image/png;base64,iVBO...).
// ':' and ';' inside of it should be threated as is. (Just like in strings.)
rParenthesis =
'\\(' + g('[^\'"()\\\\]+', rEscape, rSingleQuotes, rQuotes) + '*?' + '\\)',
// The value. It can have strings and parentheses (see above). Fallbacks to anything in case of unexpected input.
rValue =
'\\s*(' +
g(
'[^!\'"();\\\\]+?',
rEscape,
rSingleQuotes,
rQuotes,
rParenthesis,
'[^;]*?'
) +
'*?' +
')',
// End of declaration. Spaces outside of capturing groups help to do natural trimming.
rDeclEnd = '\\s*(?:;\\s*|$)',
// Important rule
rImportant = '(\\s*!important(?![-(\\w]))?',
// Final RegExp to parse CSS declarations.
regDeclarationBlock = new RegExp(
rAttr + ':' + rValue + rImportant + rDeclEnd,
'ig'
),
// Comments expression. Honors escape sequences and strings.
regStripComments = new RegExp(
g(rEscape, rSingleQuotes, rQuotes, '/\\*[^]*?\\*/'),
'ig'
);
// Parentheses, E.g.: url(data:image/png;base64,iVBO...).
// ':' and ';' inside of it should be threated as is. (Just like in strings.)
rParenthesis = '\\(' + g('[^\'"()\\\\]+', rEscape, rSingleQuotes, rQuotes) + '*?' + '\\)',
// The value. It can have strings and parentheses (see above). Fallbacks to anything in case of unexpected input.
rValue = '\\s*(' + g('[^!\'"();\\\\]+?', rEscape, rSingleQuotes, rQuotes, rParenthesis, '[^;]*?') + '*?' + ')',
// End of declaration. Spaces outside of capturing groups help to do natural trimming.
rDeclEnd = '\\s*(?:;\\s*|$)',
// Important rule
rImportant = '(\\s*!important(?![-(\\w]))?',
// Final RegExp to parse CSS declarations.
regDeclarationBlock = new RegExp(rAttr + ':' + rValue + rImportant + rDeclEnd, 'ig'),
// Comments expression. Honors escape sequences and strings.
regStripComments = new RegExp(g(rEscape, rSingleQuotes, rQuotes, '/\\*[^]*?\\*/'), 'ig');
/**

@@ -58,69 +70,63 @@ * Convert style in attributes. Cleanups comments and illegal declarations (without colon) as a side effect.

*/
exports.fn = function(item, params) {
exports.fn = function (item, params) {
if (item.type === 'element' && item.attributes.style != null) {
// ['opacity: 1', 'color: #000']
let styles = [];
const newAttributes = {};
if (item.elem && item.hasAttr('style')) {
// ['opacity: 1', 'color: #000']
var styleValue = item.attr('style').value,
styles = [],
attrs = {};
// Strip CSS comments preserving escape sequences and strings.
const styleValue = item.attributes.style.replace(
regStripComments,
(match) => {
return match[0] == '/'
? ''
: match[0] == '\\' && /[-g-z]/i.test(match[1])
? match[1]
: match;
}
);
// Strip CSS comments preserving escape sequences and strings.
styleValue = styleValue.replace(regStripComments, function(match) {
return match[0] == '/' ? '' :
match[0] == '\\' && /[-g-z]/i.test(match[1]) ? match[1] : match;
});
regDeclarationBlock.lastIndex = 0;
// eslint-disable-next-line no-cond-assign
for (var rule; (rule = regDeclarationBlock.exec(styleValue)); ) {
if (!params.keepImportant || !rule[3]) {
styles.push([rule[1], rule[2]]);
}
}
regDeclarationBlock.lastIndex = 0;
// eslint-disable-next-line no-cond-assign
for (var rule; rule = regDeclarationBlock.exec(styleValue);) {
if (!params.keepImportant || !rule[3]) {
styles.push([rule[1], rule[2]]);
}
}
if (styles.length) {
styles = styles.filter(function (style) {
if (style[0]) {
var prop = style[0].toLowerCase(),
val = style[1];
if (styles.length) {
if (rQuotedString.test(val)) {
val = val.slice(1, -1);
}
styles = styles.filter(function(style) {
if (style[0]) {
var prop = style[0].toLowerCase(),
val = style[1];
if (stylingProps.includes(prop)) {
newAttributes[prop] = val;
if (rQuotedString.test(val)) {
val = val.slice(1, -1);
}
return false;
}
}
if (stylingProps.indexOf(prop) > -1) {
return true;
});
attrs[prop] = {
name: prop,
value: val,
local: prop,
prefix: ''
};
Object.assign(item.attributes, newAttributes);
return false;
}
}
return true;
});
Object.assign(item.attrs, attrs);
if (styles.length) {
item.attr('style').value = styles
.map(function(declaration) { return declaration.join(':') })
.join(';');
} else {
item.removeAttr('style');
}
}
if (styles.length) {
item.attributes.style = styles
.map((declaration) => declaration.join(':'))
.join(';');
} else {
delete item.attributes.style;
}
}
}
};
function g() {
return '(?:' + Array.prototype.join.call(arguments, '|') + ')';
return '(?:' + Array.prototype.join.call(arguments, '|') + ')';
}

@@ -10,23 +10,23 @@ 'use strict';

exports.params = {
convertToShorts: true,
// degPrecision: 3, // transformPrecision (or matrix precision) - 2 by default
floatPrecision: 3,
transformPrecision: 5,
matrixToTransform: true,
shortTranslate: true,
shortScale: true,
shortRotate: true,
removeUseless: true,
collapseIntoOne: true,
leadingZero: true,
negativeExtraSpace: false
convertToShorts: true,
// degPrecision: 3, // transformPrecision (or matrix precision) - 2 by default
floatPrecision: 3,
transformPrecision: 5,
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,
matrixToTransform = require('./_transforms.js').matrixToTransform,
degRound,
floatRound,
transformRound;
transform2js = require('./_transforms.js').transform2js,
transformsMultiply = require('./_transforms.js').transformsMultiply,
matrixToTransform = require('./_transforms.js').matrixToTransform,
degRound,
floatRound,
transformRound;

@@ -47,23 +47,19 @@ /**

*/
exports.fn = function(item, params) {
exports.fn = function (item, params) {
if (item.type === 'element') {
// transform
if (item.attributes.transform != null) {
convertTransform(item, 'transform', params);
}
if (item.elem) {
// gradientTransform
if (item.attributes.gradientTransform != null) {
convertTransform(item, 'gradientTransform', params);
}
// transform
if (item.hasAttr('transform')) {
convertTransform(item, 'transform', params);
}
// gradientTransform
if (item.hasAttr('gradientTransform')) {
convertTransform(item, 'gradientTransform', params);
}
// patternTransform
if (item.hasAttr('patternTransform')) {
convertTransform(item, 'patternTransform', params);
}
// patternTransform
if (item.attributes.patternTransform != null) {
convertTransform(item, 'patternTransform', params);
}
}
};

@@ -79,24 +75,24 @@

function convertTransform(item, attrName, params) {
var data = transform2js(item.attr(attrName).value);
params = definePrecision(data, params);
let data = transform2js(item.attributes[attrName]);
params = definePrecision(data, params);
if (params.collapseIntoOne && data.length > 1) {
data = [transformsMultiply(data)];
}
if (params.collapseIntoOne && data.length > 1) {
data = [transformsMultiply(data)];
}
if (params.convertToShorts) {
data = convertToShorts(data, params);
} else {
data.forEach(roundTransform);
}
if (params.convertToShorts) {
data = convertToShorts(data, params);
} else {
data.forEach(roundTransform);
}
if (params.removeUseless) {
data = removeUseless(data);
}
if (params.removeUseless) {
data = removeUseless(data);
}
if (data.length) {
item.attr(attrName).value = js2transform(data, params);
} else {
item.removeAttr(attrName);
}
if (data.length) {
item.attributes[attrName] = js2transform(data, params);
} else {
delete item.attributes[attrName];
}
}

@@ -116,33 +112,45 @@

function definePrecision(data, params) {
var matrixData = data.reduce(getMatrixData, []),
significantDigits = params.transformPrecision;
var matrixData = data.reduce(getMatrixData, []),
significantDigits = params.transformPrecision;
// Clone params so it don't affect other elements transformations.
params = Object.assign({}, params);
// Clone params so it don't affect other elements transformations.
params = Object.assign({}, params);
// Limit transform precision with matrix one. Calculating with larger precision doesn't add any value.
if (matrixData.length) {
params.transformPrecision = Math.min(params.transformPrecision,
Math.max.apply(Math, matrixData.map(floatDigits)) || params.transformPrecision);
// Limit transform precision with matrix one. Calculating with larger precision doesn't add any value.
if (matrixData.length) {
params.transformPrecision = Math.min(
params.transformPrecision,
Math.max.apply(Math, matrixData.map(floatDigits)) ||
params.transformPrecision
);
significantDigits = Math.max.apply(Math, matrixData.map(function(n) {
return String(n).replace(/\D+/g, '').length; // Number of digits in a number. 123.45 → 5
}));
}
// No sense in angle precision more then number of significant digits in matrix.
if (!('degPrecision' in params)) {
params.degPrecision = Math.max(0, Math.min(params.floatPrecision, significantDigits - 2));
}
significantDigits = Math.max.apply(
Math,
matrixData.map(function (n) {
return String(n).replace(/\D+/g, '').length; // Number of digits in a number. 123.45 → 5
})
);
}
// No sense in angle precision more then number of significant digits in matrix.
if (!('degPrecision' in params)) {
params.degPrecision = Math.max(
0,
Math.min(params.floatPrecision, significantDigits - 2)
);
}
floatRound = params.floatPrecision >= 1 && params.floatPrecision < 20 ?
smartRound.bind(this, params.floatPrecision) :
round;
degRound = params.degPrecision >= 1 && params.floatPrecision < 20 ?
smartRound.bind(this, params.degPrecision) :
round;
transformRound = params.transformPrecision >= 1 && params.floatPrecision < 20 ?
smartRound.bind(this, params.transformPrecision) :
round;
floatRound =
params.floatPrecision >= 1 && params.floatPrecision < 20
? smartRound.bind(this, params.floatPrecision)
: round;
degRound =
params.degPrecision >= 1 && params.floatPrecision < 20
? smartRound.bind(this, params.degPrecision)
: round;
transformRound =
params.transformPrecision >= 1 && params.floatPrecision < 20
? smartRound.bind(this, params.transformPrecision)
: round;
return params;
return params;
}

@@ -158,3 +166,3 @@

function getMatrixData(a, b) {
return b.name == 'matrix' ? a.concat(b.data.slice(0, 4)) : a;
return b.name == 'matrix' ? a.concat(b.data.slice(0, 4)) : a;
}

@@ -166,3 +174,3 @@

function floatDigits(n) {
return (n = String(n)).slice(n.indexOf('.')).length - 1;
return (n = String(n)).slice(n.indexOf('.')).length - 1;
}

@@ -178,75 +186,70 @@

function convertToShorts(transforms, params) {
for (var i = 0; i < transforms.length; i++) {
var transform = transforms[i];
for(var i = 0; i < transforms.length; i++) {
// convert matrix to the short aliases
if (params.matrixToTransform && transform.name === 'matrix') {
var decomposed = matrixToTransform(transform, params);
if (
decomposed != transform &&
js2transform(decomposed, params).length <=
js2transform([transform], params).length
) {
transforms.splice.apply(transforms, [i, 1].concat(decomposed));
}
transform = transforms[i];
}
var transform = transforms[i];
// fixed-point numbers
// 12.754997 → 12.755
roundTransform(transform);
// convert matrix to the short aliases
if (
params.matrixToTransform &&
transform.name === 'matrix'
) {
var decomposed = matrixToTransform(transform, params);
if (decomposed != transform &&
js2transform(decomposed, params).length <= js2transform([transform], params).length) {
// convert long translate transform notation to the shorts one
// translate(10 0) → translate(10)
if (
params.shortTranslate &&
transform.name === 'translate' &&
transform.data.length === 2 &&
!transform.data[1]
) {
transform.data.pop();
}
transforms.splice.apply(transforms, [i, 1].concat(decomposed));
}
transform = transforms[i];
}
// convert long scale transform notation to the shorts one
// scale(2 2) → scale(2)
if (
params.shortScale &&
transform.name === 'scale' &&
transform.data.length === 2 &&
transform.data[0] === transform.data[1]
) {
transform.data.pop();
}
// fixed-point numbers
// 12.754997 → 12.755
roundTransform(transform);
// convert long rotate transform notation to the short one
// translate(cx cy) rotate(a) translate(-cx -cy) → rotate(a cx cy)
if (
params.shortRotate &&
transforms[i - 2] &&
transforms[i - 2].name === 'translate' &&
transforms[i - 1].name === 'rotate' &&
transforms[i].name === 'translate' &&
transforms[i - 2].data[0] === -transforms[i].data[0] &&
transforms[i - 2].data[1] === -transforms[i].data[1]
) {
transforms.splice(i - 2, 3, {
name: 'rotate',
data: [
transforms[i - 1].data[0],
transforms[i - 2].data[0],
transforms[i - 2].data[1],
],
});
// convert long translate transform notation to the shorts one
// translate(10 0) → translate(10)
if (
params.shortTranslate &&
transform.name === 'translate' &&
transform.data.length === 2 &&
!transform.data[1]
) {
transform.data.pop();
}
// convert long scale transform notation to the shorts one
// scale(2 2) → scale(2)
if (
params.shortScale &&
transform.name === 'scale' &&
transform.data.length === 2 &&
transform.data[0] === transform.data[1]
) {
transform.data.pop();
}
// convert long rotate transform notation to the short one
// translate(cx cy) rotate(a) translate(-cx -cy) → rotate(a cx cy)
if (
params.shortRotate &&
transforms[i - 2] &&
transforms[i - 2].name === 'translate' &&
transforms[i - 1].name === 'rotate' &&
transforms[i].name === 'translate' &&
transforms[i - 2].data[0] === -transforms[i].data[0] &&
transforms[i - 2].data[1] === -transforms[i].data[1]
) {
transforms.splice(i - 2, 3, {
name: 'rotate',
data: [
transforms[i - 1].data[0],
transforms[i - 2].data[0],
transforms[i - 2].data[1]
]
});
// splice compensation
i -= 2;
}
// splice compensation
i -= 2;
}
}
return transforms;
return transforms;
}

@@ -261,34 +264,32 @@

function removeUseless(transforms) {
return transforms.filter(function (transform) {
// translate(0), rotate(0[, cx, cy]), skewX(0), skewY(0)
if (
(['translate', 'rotate', 'skewX', 'skewY'].indexOf(transform.name) > -1 &&
(transform.data.length == 1 || transform.name == 'rotate') &&
!transform.data[0]) ||
// translate(0, 0)
(transform.name == 'translate' &&
!transform.data[0] &&
!transform.data[1]) ||
// scale(1)
(transform.name == 'scale' &&
transform.data[0] == 1 &&
(transform.data.length < 2 || transform.data[1] == 1)) ||
// matrix(1 0 0 1 0 0)
(transform.name == 'matrix' &&
transform.data[0] == 1 &&
transform.data[3] == 1 &&
!(
transform.data[1] ||
transform.data[2] ||
transform.data[4] ||
transform.data[5]
))
) {
return false;
}
return transforms.filter(function(transform) {
// translate(0), rotate(0[, cx, cy]), skewX(0), skewY(0)
if (
['translate', 'rotate', 'skewX', 'skewY'].indexOf(transform.name) > -1 &&
(transform.data.length == 1 || transform.name == 'rotate') &&
!transform.data[0] ||
// translate(0, 0)
transform.name == 'translate' &&
!transform.data[0] &&
!transform.data[1] ||
// scale(1)
transform.name == 'scale' &&
transform.data[0] == 1 &&
(transform.data.length < 2 || transform.data[1] == 1) ||
// matrix(1 0 0 1 0 0)
transform.name == 'matrix' &&
transform.data[0] == 1 &&
transform.data[3] == 1 &&
!(transform.data[1] || transform.data[2] || transform.data[4] || transform.data[5])
) {
return false;
}
return true;
});
return true;
});
}

@@ -304,35 +305,42 @@

function js2transform(transformJS, params) {
var transformString = '';
var transformString = '';
// collect output value string
transformJS.forEach(function (transform) {
roundTransform(transform);
transformString +=
(transformString && ' ') +
transform.name +
'(' +
cleanupOutData(transform.data, params) +
')';
});
// collect output value string
transformJS.forEach(function(transform) {
roundTransform(transform);
transformString += (transformString && ' ') + transform.name + '(' + cleanupOutData(transform.data, params) + ')';
});
return transformString;
return transformString;
}
function roundTransform(transform) {
switch (transform.name) {
case 'translate':
transform.data = floatRound(transform.data);
break;
case 'rotate':
transform.data = degRound(transform.data.slice(0, 1)).concat(floatRound(transform.data.slice(1)));
break;
case 'skewX':
case 'skewY':
transform.data = degRound(transform.data);
break;
case 'scale':
transform.data = transformRound(transform.data);
break;
case 'matrix':
transform.data = transformRound(transform.data.slice(0, 4)).concat(floatRound(transform.data.slice(4)));
break;
}
return transform;
switch (transform.name) {
case 'translate':
transform.data = floatRound(transform.data);
break;
case 'rotate':
transform.data = degRound(transform.data.slice(0, 1)).concat(
floatRound(transform.data.slice(1))
);
break;
case 'skewX':
case 'skewY':
transform.data = degRound(transform.data);
break;
case 'scale':
transform.data = transformRound(transform.data);
break;
case 'matrix':
transform.data = transformRound(transform.data.slice(0, 4)).concat(
floatRound(transform.data.slice(4))
);
break;
}
return transform;
}

@@ -347,3 +355,3 @@

function round(data) {
return data.map(Math.round);
return data.map(Math.round);
}

@@ -361,11 +369,17 @@

function smartRound(precision, data) {
for (var i = data.length, tolerance = +Math.pow(.1, precision).toFixed(precision); i--;) {
if (data[i].toFixed(precision) != data[i]) {
var rounded = +data[i].toFixed(precision - 1);
data[i] = +Math.abs(rounded - data[i]).toFixed(precision + 1) >= tolerance ?
+data[i].toFixed(precision) :
rounded;
}
for (
var i = data.length,
tolerance = +Math.pow(0.1, precision).toFixed(precision);
i--;
) {
if (data[i].toFixed(precision) != data[i]) {
var rounded = +data[i].toFixed(precision - 1);
data[i] =
+Math.abs(rounded - data[i]).toFixed(precision + 1) >= tolerance
? +data[i].toFixed(precision)
: rounded;
}
return data;
}
return data;
}
'use strict';
const csstree = require('css-tree');
const { querySelectorAll, closestByName } = require('../lib/xast.js');
const cssTools = require('../lib/css-tools');
exports.type = 'full';

@@ -16,5 +20,2 @@

var csstree = require('css-tree'),
cssTools = require('../lib/css-tools');
/**

@@ -39,3 +40,3 @@ * Moves + merges styles from style elements to element styles

*
* @param {Object} document document element
* @param {Object} root document element
* @param {Object} opts plugin params

@@ -45,9 +46,9 @@ *

*/
exports.fn = function (document, opts) {
exports.fn = function (root, opts) {
// collect <style/>s
var styleEls = document.querySelectorAll('style');
var styleEls = querySelectorAll(root, 'style');
//no <styles/>s, nothing to do
if (styleEls === null) {
return document;
if (styleEls.length === 0) {
return root;
}

@@ -61,5 +62,5 @@

if (
styleEl.hasAttr('type') &&
styleEl.attr('type').value !== '' &&
styleEl.attr('type').value !== 'text/css'
styleEl.attributes.type != null &&
styleEl.attributes.type !== '' &&
styleEl.attributes.type !== 'text/css'
) {

@@ -69,3 +70,6 @@ continue;

// skip empty <style/>s or <foreignObject> content.
if (styleEl.isEmpty() || styleEl.closestElem('foreignObject')) {
if (
styleEl.children.length === 0 ||
closestByName(styleEl, 'foreignObject')
) {
continue;

@@ -116,3 +120,3 @@ }

try {
selectedEls = document.querySelectorAll(selectorStr);
selectedEls = querySelectorAll(root, selectorStr);
} catch (selectError) {

@@ -123,3 +127,3 @@ // console.warn('Warning: Syntax error when trying to select \n\n' + selectorStr + '\n\n, skipped. Error details: ' + selectError);

if (selectedEls === null) {
if (selectedEls.length === 0) {
// nothing selected

@@ -191,3 +195,3 @@ continue;

if (!opts.removeMatchedSelectors) {
return document; // no further processing required
return root; // no further processing required
}

@@ -218,3 +222,3 @@

if (typeof selectedEl.class.item(0) === 'undefined') {
selectedEl.removeAttr('class');
delete selectedEl.attributes.class;
}

@@ -224,3 +228,5 @@

if (firstSubSelector.type === 'IdSelector') {
selectedEl.removeAttr('id', firstSubSelector.name);
if (selectedEl.attributes.id === firstSubSelector.name) {
delete selectedEl.attributes.id;
}
}

@@ -257,11 +263,14 @@ }

styleParentEl.spliceContent(
styleParentEl.content.indexOf(style.styleEl),
styleParentEl.children.indexOf(style.styleEl),
1
);
if (styleParentEl.elem === 'defs' && styleParentEl.content.length === 0) {
if (
styleParentEl.name === 'defs' &&
styleParentEl.children.length === 0
) {
// also clean up now empty <def/>s
var defsParentEl = styleParentEl.parentNode;
defsParentEl.spliceContent(
defsParentEl.content.indexOf(styleParentEl),
defsParentEl.children.indexOf(styleParentEl),
1

@@ -278,3 +287,3 @@ );

return document;
return root;
};

@@ -29,16 +29,16 @@ 'use strict';

exports.fn = function (item, params) {
if (!item.isElem() || item.isEmpty()) return;
if (item.type !== 'element' || item.children.length === 0) return;
var prevContentItem = null,
prevContentItemKeys = null;
let prevContentItem = null;
let prevContentItemKeys = null;
item.content = item.content.filter(function (contentItem) {
item.children = item.children.filter(function (contentItem) {
if (
prevContentItem &&
prevContentItem.isElem('path') &&
prevContentItem.isEmpty() &&
prevContentItem.hasAttr('d') &&
prevContentItem.children.length === 0 &&
prevContentItem.attributes.d != null &&
contentItem.isElem('path') &&
contentItem.isEmpty() &&
contentItem.hasAttr('d')
contentItem.children.length === 0 &&
contentItem.attributes.d != null
) {

@@ -55,17 +55,17 @@ const computedStyle = computeStyle(contentItem);

if (!prevContentItemKeys) {
prevContentItemKeys = Object.keys(prevContentItem.attrs);
prevContentItemKeys = Object.keys(prevContentItem.attributes);
}
var contentItemAttrs = Object.keys(contentItem.attrs),
equalData =
prevContentItemKeys.length == contentItemAttrs.length &&
contentItemAttrs.every(function (key) {
return (
key == 'd' ||
(prevContentItem.hasAttr(key) &&
prevContentItem.attr(key).value == contentItem.attr(key).value)
);
}),
prevPathJS = path2js(prevContentItem),
curPathJS = path2js(contentItem);
const contentItemAttrs = Object.keys(contentItem.attributes);
const equalData =
prevContentItemKeys.length == contentItemAttrs.length &&
contentItemAttrs.every(function (key) {
return (
key == 'd' ||
(prevContentItem.attributes[key] != null &&
prevContentItem.attributes[key] == contentItem.attributes[key])
);
});
const prevPathJS = path2js(prevContentItem);
const curPathJS = path2js(contentItem);

@@ -72,0 +72,0 @@ if (equalData && (params.force || !intersects(prevPathJS, curPathJS))) {

'use strict';
const csso = require('csso');
const { traverse } = require('../lib/xast.js');
exports.type = 'full';

@@ -7,18 +10,17 @@

exports.description = 'minifies styles and removes unused styles based on usage data';
exports.description =
'minifies styles and removes unused styles based on usage data';
exports.params = {
// ... CSSO options goes here
// ... CSSO options goes here
// additional
usage: {
force: false, // force to use usage data even if it unsafe (document contains <script> or on* attributes)
ids: true,
classes: true,
tags: true
}
// additional
usage: {
force: false, // force to use usage data even if it unsafe (document contains <script> or on* attributes)
ids: true,
classes: true,
tags: true,
},
};
var csso = require('csso');
/**

@@ -29,128 +31,123 @@ * Minifies styles (<style> element + style attribute) using CSSO

*/
exports.fn = function(ast, options) {
options = options || {};
exports.fn = function (ast, options) {
options = options || {};
var minifyOptionsForStylesheet = cloneObject(options);
var minifyOptionsForAttribute = cloneObject(options);
var elems = findStyleElems(ast);
var minifyOptionsForStylesheet = cloneObject(options);
var minifyOptionsForAttribute = cloneObject(options);
var elems = findStyleElems(ast);
minifyOptionsForStylesheet.usage = collectUsageData(ast, options);
minifyOptionsForAttribute.usage = null;
minifyOptionsForStylesheet.usage = collectUsageData(ast, options);
minifyOptionsForAttribute.usage = null;
elems.forEach(function(elem) {
if (elem.isElem('style')) {
// <style> element
var styleCss = elem.content[0].text || elem.content[0].cdata || [];
var DATA = styleCss.indexOf('>') >= 0 || styleCss.indexOf('<') >= 0 ? 'cdata' : 'text';
elem.content[0][DATA] = csso.minify(styleCss, minifyOptionsForStylesheet).css;
elems.forEach(function (elem) {
if (elem.isElem('style')) {
if (
elem.children[0].type === 'text' ||
elem.children[0].type === 'cdata'
) {
const styleCss = elem.children[0].value;
const minified = csso.minify(styleCss, minifyOptionsForStylesheet).css;
// preserve cdata if necessary
// TODO split cdata -> text optimisation into separate plugin
if (styleCss.indexOf('>') >= 0 || styleCss.indexOf('<') >= 0) {
elem.children[0].type = 'cdata';
elem.children[0].value = minified;
} else {
// style attribute
var elemStyle = elem.attr('style').value;
elem.attr('style').value = csso.minifyBlock(elemStyle, minifyOptionsForAttribute).css;
elem.children[0].type = 'text';
elem.children[0].value = minified;
}
});
}
} else {
// style attribute
var elemStyle = elem.attributes.style;
return ast;
elem.attributes.style = csso.minifyBlock(
elemStyle,
minifyOptionsForAttribute
).css;
}
});
return ast;
};
function cloneObject(obj) {
return {...obj};
return { ...obj };
}
function findStyleElems(ast) {
function walk(items, styles) {
for (var i = 0; i < items.content.length; i++) {
var item = items.content[i];
// go deeper
if (item.content) {
walk(item, styles);
}
if (item.isElem('style') && !item.isEmpty()) {
styles.push(item);
} else if (item.isElem() && item.hasAttr('style')) {
styles.push(item);
}
}
return styles;
const nodesWithStyles = [];
traverse(ast, (node) => {
if (node.type === 'element') {
if (node.name === 'style' && node.children.length !== 0) {
nodesWithStyles.push(node);
} else if (node.attributes.style != null) {
nodesWithStyles.push(node);
}
}
return walk(ast, []);
});
return nodesWithStyles;
}
function shouldFilter(options, name) {
if ('usage' in options === false) {
return true;
}
if ('usage' in options === false) {
return true;
}
if (options.usage && name in options.usage === false) {
return true;
}
if (options.usage && name in options.usage === false) {
return true;
}
return Boolean(options.usage && options.usage[name]);
return Boolean(options.usage && options.usage[name]);
}
function collectUsageData(ast, options) {
let safe = true;
const usageData = {};
let hasData = false;
const rawData = {
ids: Object.create(null),
classes: Object.create(null),
tags: Object.create(null),
};
function walk(items, usageData) {
for (var i = 0; i < items.content.length; i++) {
var item = items.content[i];
traverse(ast, (node) => {
if (node.type === 'element') {
if (node.name === 'script') {
safe = false;
}
// go deeper
if (item.content) {
walk(item, usageData);
}
rawData.tags[node.name] = true;
if (item.isElem('script')) {
safe = false;
}
if (node.attributes.id != null) {
rawData.ids[node.attributes.id] = true;
}
if (item.isElem()) {
usageData.tags[item.elem] = true;
if (node.attributes.class != null) {
node.attributes.class
.replace(/^\s+|\s+$/g, '')
.split(/\s+/)
.forEach((className) => {
rawData.classes[className] = true;
});
}
if (item.hasAttr('id')) {
usageData.ids[item.attr('id').value] = true;
}
if (item.hasAttr('class')) {
item.attr('class').value.replace(/^\s+|\s+$/g, '').split(/\s+/).forEach(function(className) {
usageData.classes[className] = true;
});
}
if (item.attrs && Object.keys(item.attrs).some(function(name) { return /^on/i.test(name); })) {
safe = false;
}
}
}
return usageData;
if (Object.keys(node.attributes).some((name) => /^on/i.test(name))) {
safe = false;
}
}
});
var safe = true;
var usageData = {};
var hasData = false;
var rawData = walk(ast, {
ids: Object.create(null),
classes: Object.create(null),
tags: Object.create(null)
});
if (!safe && options.usage && options.usage.force) {
safe = true;
}
if (!safe && options.usage && options.usage.force) {
safe = true;
for (const [key, data] of Object.entries(rawData)) {
if (shouldFilter(options, key)) {
usageData[key] = Object.keys(data);
hasData = true;
}
}
for (const [key, data] of Object.entries(rawData)) {
if (shouldFilter(options, key)) {
usageData[key] = Object.keys(data);
hasData = true;
}
}
return safe && hasData ? usageData : null;
return safe && hasData ? usageData : null;
}
'use strict';
const { inheritableAttrs, pathElems } = require('./_collections');
exports.type = 'perItemReverse';

@@ -9,5 +11,2 @@

var inheritableAttrs = require('./_collections').inheritableAttrs,
pathElems = require('./_collections.js').pathElems;
/**

@@ -37,61 +36,62 @@ * Collapse content's intersected and inheritable

*/
exports.fn = function(item) {
exports.fn = function (item) {
if (
item.type === 'element' &&
item.name === 'g' &&
item.children.length > 1
) {
var intersection = {},
hasTransform = false,
hasClip =
item.attributes['clip-path'] != null || item.attributes.mask != null,
intersected = item.children.every(function (inner) {
if (
inner.type === 'element' &&
Object.keys(inner.attributes).length !== 0
) {
// don't mess with possible styles (hack until CSS parsing is implemented)
if (inner.attributes.class) return false;
if (!Object.keys(intersection).length) {
intersection = inner.attributes;
} else {
intersection = intersectInheritableAttrs(
intersection,
inner.attributes
);
if (item.isElem('g') && !item.isEmpty() && item.content.length > 1) {
if (!intersection) return false;
}
var intersection = {},
hasTransform = false,
hasClip = item.hasAttr('clip-path') || item.hasAttr('mask'),
intersected = item.content.every(function(inner) {
if (inner.isElem() && inner.hasAttr()) {
// don't mess with possible styles (hack until CSS parsing is implemented)
if (inner.hasAttr('class')) return false;
if (!Object.keys(intersection).length) {
intersection = inner.attrs;
} else {
intersection = intersectInheritableAttrs(intersection, inner.attrs);
return true;
}
}),
allPath = item.children.every(function (inner) {
return inner.isElem(pathElems);
});
if (!intersection) return false;
}
if (intersected) {
item.children.forEach(function (g) {
for (const [name, value] of Object.entries(intersection)) {
if ((!allPath && !hasClip) || name !== 'transform') {
delete g.attributes[name];
return true;
if (name === 'transform') {
if (!hasTransform) {
if (item.attributes.transform != null) {
item.attributes.transform =
item.attributes.transform + ' ' + value;
} else {
item.attributes.transform = value;
}
}),
allPath = item.content.every(function(inner) {
return inner.isElem(pathElems);
});
if (intersected) {
item.content.forEach(function(g) {
for (const [name, attr] of Object.entries(intersection)) {
if (!allPath && !hasClip || name !== 'transform') {
g.removeAttr(name);
if (name === 'transform') {
if (!hasTransform) {
if (item.hasAttr('transform')) {
item.attr('transform').value += ' ' + attr.value;
} else {
item.addAttr(attr);
}
hasTransform = true;
}
} else {
item.addAttr(attr);
}
}
}
});
hasTransform = true;
}
} else {
item.attributes[name] = value;
}
}
}
});
}
}
};

@@ -108,23 +108,18 @@

function intersectInheritableAttrs(a, b) {
var c = {};
var c = {};
for (const [n, attr] of Object.entries(a)) {
if (
// eslint-disable-next-line no-prototype-builtins
b.hasOwnProperty(n) &&
inheritableAttrs.indexOf(n) > -1 &&
attr.name === b[n].name &&
attr.value === b[n].value &&
attr.prefix === b[n].prefix &&
attr.local === b[n].local
) {
c[n] = attr;
}
for (const [name, value] of Object.entries(a)) {
if (
// eslint-disable-next-line no-prototype-builtins
b.hasOwnProperty(name) &&
inheritableAttrs.includes(name) &&
value === b[name]
) {
c[name] = value;
}
}
if (!Object.keys(c).length) return false;
if (!Object.keys(c).length) return false;
return c;
return c;
}
'use strict';
const { pathElems, referencesProps } = require('./_collections.js');
exports.type = 'perItem';

@@ -9,5 +11,3 @@

var collections = require('./_collections.js'),
pathElems = collections.pathElems.concat(['g', 'text']),
referencesProps = collections.referencesProps;
const pathElemsWithGroupsAndText = [...pathElems, 'g', 'text'];

@@ -33,33 +33,30 @@ /**

*/
exports.fn = function(item) {
// move group transform attr to content's pathElems
if (
item.isElem('g') &&
item.hasAttr('transform') &&
!item.isEmpty() &&
!item.someAttr(function(attr) {
return ~referencesProps.indexOf(attr.name) && ~attr.value.indexOf('url(');
}) &&
item.content.every(function(inner) {
return inner.isElem(pathElems) && !inner.hasAttr('id');
})
) {
item.content.forEach(function(inner) {
var attr = item.attr('transform');
if (inner.hasAttr('transform')) {
inner.attr('transform').value = attr.value + ' ' + inner.attr('transform').value;
} else {
inner.addAttr({
'name': attr.name,
'local': attr.local,
'prefix': attr.prefix,
'value': attr.value
});
}
});
item.removeAttr('transform');
exports.fn = function (item) {
// move group transform attr to content's pathElems
if (
item.type === 'element' &&
item.name === 'g' &&
item.children.length !== 0 &&
item.attributes.transform != null &&
Object.entries(item.attributes).some(
([name, value]) =>
referencesProps.includes(name) && value.includes('url(')
) === false &&
item.children.every(
(inner) =>
pathElemsWithGroupsAndText.includes(inner.name) &&
inner.attributes.id == null
)
) {
for (const inner of item.children) {
const value = item.attributes.transform;
if (inner.attributes.transform != null) {
inner.attributes.transform = value + ' ' + inner.attributes.transform;
} else {
inner.attributes.transform = value;
}
}
delete item.attributes.transform;
}
};

@@ -17,2 +17,3 @@ 'use strict';

exports.convertTransform = require('./convertTransform.js');
exports.mergeStyles = require('./mergeStyles.js');
exports.inlineStyles = require('./inlineStyles.js');

@@ -19,0 +20,0 @@ exports.mergePaths = require('./mergePaths.js');

@@ -8,5 +8,5 @@ 'use strict';

exports.params = {
delim: '__',
prefixIds: true,
prefixClassNames: true,
delim: '__',
prefixIds: true,
prefixClassNames: true,
};

@@ -17,9 +17,9 @@

var csstree = require('css-tree'),
collections = require('./_collections.js'),
referencesProps = collections.referencesProps,
rxId = /^#(.*)$/, // regular expression for matching an ID + extracing its name
addPrefix = null;
collections = require('./_collections.js'),
referencesProps = collections.referencesProps,
rxId = /^#(.*)$/, // regular expression for matching an ID + extracing its name
addPrefix = null;
const unquote = (string) => {
const first = string.charAt(0)
const first = string.charAt(0);
if (first === "'" || first === '"') {

@@ -31,123 +31,131 @@ if (first === string.charAt(string.length - 1)) {

return string;
}
};
// Escapes a string for being used as ID
var escapeIdentifierName = function(str) {
return str.replace(/[. ]/g, '_');
var escapeIdentifierName = function (str) {
return str.replace(/[. ]/g, '_');
};
// Matches an #ID value, captures the ID name
var matchId = function(urlVal) {
var idUrlMatches = urlVal.match(rxId);
if (idUrlMatches === null) {
return false;
}
return idUrlMatches[1];
var matchId = function (urlVal) {
var idUrlMatches = urlVal.match(rxId);
if (idUrlMatches === null) {
return false;
}
return idUrlMatches[1];
};
// Matches an url(...) value, captures the URL
var matchUrl = function(val) {
var urlMatches = /url\((.*?)\)/gi.exec(val);
if (urlMatches === null) {
return false;
}
return urlMatches[1];
var matchUrl = function (val) {
var urlMatches = /url\((.*?)\)/gi.exec(val);
if (urlMatches === null) {
return false;
}
return urlMatches[1];
};
// Checks if attribute is empty
var attrNotEmpty = function(attr) {
return (attr && attr.value && attr.value.length > 0);
};
// prefixes an #ID
var prefixId = function(val) {
var idName = matchId(val);
if (!idName) {
return false;
}
return '#' + addPrefix(idName);
var prefixId = function (val) {
var idName = matchId(val);
if (!idName) {
return false;
}
return '#' + addPrefix(idName);
};
// attr.value helper methods
// prefixes a class attribute value
var addPrefixToClassAttr = function(attr) {
if (!attrNotEmpty(attr)) {
return;
}
const addPrefixToClassAttr = (element, name) => {
if (
element.attributes[name] == null ||
element.attributes[name].length === 0
) {
return;
}
attr.value = attr.value.split(/\s+/).map(addPrefix).join(' ');
element.attributes[name] = element.attributes[name]
.split(/\s+/)
.map(addPrefix)
.join(' ');
};
// prefixes an ID attribute value
var addPrefixToIdAttr = function(attr) {
if (!attrNotEmpty(attr)) {
return;
}
const addPrefixToIdAttr = (element, name) => {
if (
element.attributes[name] == null ||
element.attributes[name].length === 0
) {
return;
}
attr.value = addPrefix(attr.value);
element.attributes[name] = addPrefix(element.attributes[name]);
};
// prefixes a href attribute value
var addPrefixToHrefAttr = function(attr) {
if (!attrNotEmpty(attr)) {
return;
}
const addPrefixToHrefAttr = (element, name) => {
if (
element.attributes[name] == null ||
element.attributes[name].length === 0
) {
return;
}
var idPrefixed = prefixId(attr.value);
if (!idPrefixed) {
return;
}
attr.value = idPrefixed;
const idPrefixed = prefixId(element.attributes[name]);
if (!idPrefixed) {
return;
}
element.attributes[name] = idPrefixed;
};
// prefixes an URL attribute value
var addPrefixToUrlAttr = function(attr) {
if (!attrNotEmpty(attr)) {
return;
}
const addPrefixToUrlAttr = (element, name) => {
if (
element.attributes[name] == null ||
element.attributes[name].length === 0
) {
return;
}
// url(...) in value
var urlVal = matchUrl(attr.value);
if (!urlVal) {
return;
}
// url(...) in value
const urlVal = matchUrl(element.attributes[name]);
if (!urlVal) {
return;
}
var idPrefixed = prefixId(urlVal);
if (!idPrefixed) {
return;
}
const idPrefixed = prefixId(urlVal);
if (!idPrefixed) {
return;
}
attr.value = 'url(' + idPrefixed + ')';
element.attributes[name] = 'url(' + idPrefixed + ')';
};
// prefixes begin/end attribute value
var addPrefixToBeginEndAttr = function(attr) {
if (!attrNotEmpty(attr)) {
return;
}
const addPrefixToBeginEndAttr = (element, name) => {
if (
element.attributes[name] == null ||
element.attributes[name].length === 0
) {
return;
}
var parts = attr.value.split('; ').map(function(val) {
val = val.trim();
const parts = element.attributes[name].split('; ').map((val) => {
val = val.trim();
if (val.endsWith('.end') || val.endsWith('.start')) {
var idPostfix = val.split('.'),
id = idPostfix[0],
postfix = idPostfix[1];
if (val.endsWith('.end') || val.endsWith('.start')) {
const [id, postfix] = val.split('.');
var idPrefixed = prefixId(`#${id}`);
let idPrefixed = prefixId(`#${id}`);
if (!idPrefixed) {
return val;
}
if (!idPrefixed) {
return val;
}
idPrefixed = idPrefixed.slice(1);
return `${idPrefixed}.${postfix}`;
} else {
return val;
}
});
idPrefixed = idPrefixed.slice(1);
return `${idPrefixed}.${postfix}`;
} else {
return val;
}
});
attr.value = parts.join('; ');
element.attributes[name] = parts.join('; ');
};

@@ -173,121 +181,122 @@

*/
exports.fn = function(node, opts, extra) {
exports.fn = function (node, opts, extra) {
// skip subsequent passes when multipass is used
if (extra.multipassCount && extra.multipassCount > 0) {
return;
}
// skip subsequent passes when multipass is used
if(extra.multipassCount && extra.multipassCount > 0) {
return node;
// prefix, from file name or option
var prefix = 'prefix';
if (opts.prefix) {
if (typeof opts.prefix === 'function') {
prefix = opts.prefix(node, extra);
} else {
prefix = opts.prefix;
}
} else if (opts.prefix === false) {
prefix = false;
} else if (extra && extra.path && extra.path.length > 0) {
var filename = getBasename(extra.path);
prefix = filename;
}
// prefix, from file name or option
var prefix = 'prefix';
if (opts.prefix) {
if (typeof opts.prefix === 'function') {
prefix = opts.prefix(node, extra);
} else {
prefix = opts.prefix;
}
} else if (opts.prefix === false) {
prefix = false;
} else if (extra && extra.path && extra.path.length > 0) {
var filename = getBasename(extra.path);
prefix = filename;
// prefixes a normal value
addPrefix = function (name) {
if (prefix === false) {
return escapeIdentifierName(name);
}
return escapeIdentifierName(prefix + opts.delim + name);
};
// <style/> property values
// prefixes a normal value
addPrefix = function(name) {
if(prefix === false){
return escapeIdentifierName(name);
}
return escapeIdentifierName(prefix + opts.delim + name);
};
if (node.type === 'element' && node.name === 'style') {
if (node.children.length === 0) {
// skip empty <style/>s
return;
}
var cssStr = '';
if (node.children[0].type === 'text' || node.children[0].type === 'cdata') {
cssStr = node.children[0].value;
}
// <style/> property values
var cssAst = {};
try {
cssAst = csstree.parse(cssStr, {
parseValue: true,
parseCustomProperty: false,
});
} catch (parseError) {
console.warn(
'Warning: Parse error of styles of <style/> element, skipped. Error details: ' +
parseError
);
return;
}
if (node.elem === 'style') {
if (node.isEmpty()) {
// skip empty <style/>s
return node;
}
var idPrefixed = '';
csstree.walk(cssAst, function (node) {
// #ID, .class
if (
((opts.prefixIds && node.type === 'IdSelector') ||
(opts.prefixClassNames && node.type === 'ClassSelector')) &&
node.name
) {
node.name = addPrefix(node.name);
return;
}
var cssStr = node.content[0].text || node.content[0].cdata || [];
var cssAst = {};
try {
cssAst = csstree.parse(cssStr, {
parseValue: true,
parseCustomProperty: false
});
} catch (parseError) {
console.warn('Warning: Parse error of styles of <style/> element, skipped. Error details: ' + parseError);
return node;
// url(...) in value
if (
node.type === 'Url' &&
node.value.value &&
node.value.value.length > 0
) {
idPrefixed = prefixId(unquote(node.value.value));
if (!idPrefixed) {
return;
}
node.value.value = idPrefixed;
}
});
var idPrefixed = '';
csstree.walk(cssAst, function(node) {
// update <style>s
node.children[0].value = csstree.generate(cssAst);
return;
}
// #ID, .class
if (((opts.prefixIds && node.type === 'IdSelector') ||
(opts.prefixClassNames && node.type === 'ClassSelector')) &&
node.name) {
node.name = addPrefix(node.name);
return;
}
// element attributes
// url(...) in value
if (node.type === 'Url' &&
node.value.value && node.value.value.length > 0) {
idPrefixed = prefixId(unquote(node.value.value));
if (!idPrefixed) {
return;
}
node.value.value = idPrefixed;
}
if (node.type !== 'element') {
return;
}
});
// Nodes
// update <style>s
node.content[0].text = csstree.generate(cssAst);
return node;
}
if (opts.prefixIds) {
// ID
addPrefixToIdAttr(node, 'id');
}
if (opts.prefixClassNames) {
// Class
addPrefixToClassAttr(node, 'class');
}
// element attributes
// References
if (!node.attrs) {
return node;
}
// href
addPrefixToHrefAttr(node, 'href');
// (xlink:)href (deprecated, must be still supported)
addPrefixToHrefAttr(node, 'xlink:href');
// Nodes
// (referenceable) properties
for (var referencesProp of referencesProps) {
addPrefixToUrlAttr(node, referencesProp);
}
if(opts.prefixIds) {
// ID
addPrefixToIdAttr(node.attrs.id);
}
if(opts.prefixClassNames) {
// Class
addPrefixToClassAttr(node.attrs.class);
}
// References
// href
addPrefixToHrefAttr(node.attrs.href);
// (xlink:)href (deprecated, must be still supported)
addPrefixToHrefAttr(node.attrs['xlink:href']);
// (referenceable) properties
for (var referencesProp of referencesProps) {
addPrefixToUrlAttr(node.attrs[referencesProp]);
}
addPrefixToBeginEndAttr(node.attrs.begin);
addPrefixToBeginEndAttr(node.attrs.end);
return node;
addPrefixToBeginEndAttr(node, 'begin');
addPrefixToBeginEndAttr(node, 'end');
};

@@ -7,5 +7,5 @@ 'use strict';

exports.description = 'removes attributes of elements that match a css selector';
exports.description =
'removes attributes of elements that match a css selector';
/**

@@ -27,3 +27,3 @@ * Removes attributes of elements that match a css selector.

* ↓
* <rect x="0" y="0" width="100" height="100" stroke="#00ff00"/>
* <rect x="0" y="0" width="100" height="100" stroke="#00ff00"/>
*

@@ -40,3 +40,3 @@ * <caption>A selector removing multiple attributes</caption>

* ↓
* <rect x="0" y="0" width="100" height="100"/>
* <rect x="0" y="0" width="100" height="100"/>
*

@@ -63,12 +63,16 @@ * <caption>Multiple selectors removing attributes</caption>

*/
exports.fn = function(item, params) {
exports.fn = function (item, params) {
var selectors = Array.isArray(params.selectors) ? params.selectors : [params];
var selectors = Array.isArray(params.selectors) ? params.selectors : [params];
selectors.map(function(i) {
if (item.matches(i.selector)) {
item.removeAttr(i.attributes);
selectors.map(({ selector, attributes }) => {
if (item.matches(selector)) {
if (Array.isArray(attributes)) {
for (const name of attributes) {
delete item.attributes[name];
}
});
} else {
delete item.attributes[attributes];
}
}
});
};

@@ -12,5 +12,5 @@ 'use strict';

exports.params = {
elemSeparator: DEFAULT_SEPARATOR,
preserveCurrentColor: false,
attrs: []
elemSeparator: DEFAULT_SEPARATOR,
preserveCurrentColor: false,
attrs: [],
};

@@ -85,68 +85,64 @@

*/
exports.fn = function(item, params) {
// wrap into an array if params is not
if (!Array.isArray(params.attrs)) {
params.attrs = [params.attrs];
}
exports.fn = function (item, params) {
// wrap into an array if params is not
if (!Array.isArray(params.attrs)) {
params.attrs = [params.attrs];
}
if (item.isElem()) {
var elemSeparator = typeof params.elemSeparator == 'string' ? params.elemSeparator : DEFAULT_SEPARATOR;
var preserveCurrentColor = typeof params.preserveCurrentColor == 'boolean' ? params.preserveCurrentColor : false;
if (item.type === 'element') {
var elemSeparator =
typeof params.elemSeparator == 'string'
? params.elemSeparator
: DEFAULT_SEPARATOR;
var preserveCurrentColor =
typeof params.preserveCurrentColor == 'boolean'
? params.preserveCurrentColor
: false;
// prepare patterns
var patterns = params.attrs.map(function(pattern) {
// prepare patterns
var patterns = params.attrs.map(function (pattern) {
// if no element separators (:), assume it's attribute name, and apply to all elements *regardless of value*
if (pattern.indexOf(elemSeparator) === -1) {
pattern = ['.*', elemSeparator, pattern, elemSeparator, '.*'].join('');
// if no element separators (:), assume it's attribute name, and apply to all elements *regardless of value*
if (pattern.indexOf(elemSeparator) === -1) {
pattern = ['.*', elemSeparator, pattern, elemSeparator, '.*'].join('');
// if only 1 separator, assume it's element and attribute name, and apply regardless of attribute value
} else if (pattern.split(elemSeparator).length < 3) {
pattern = [pattern, elemSeparator, '.*'].join('');
}
// if only 1 separator, assume it's element and attribute name, and apply regardless of attribute value
} else if (pattern.split(elemSeparator).length < 3) {
pattern = [pattern, elemSeparator, '.*'].join('');
}
// create regexps for element, attribute name, and attribute value
return pattern.split(elemSeparator).map(function (value) {
// adjust single * to match anything
if (value === '*') {
value = '.*';
}
// create regexps for element, attribute name, and attribute value
return pattern.split(elemSeparator)
.map(function(value) {
return new RegExp(['^', value, '$'].join(''), 'i');
});
});
// adjust single * to match anything
if (value === '*') { value = '.*'; }
// loop patterns
patterns.forEach(function (pattern) {
// matches element
if (pattern[0].test(item.name)) {
// loop attributes
for (const [name, value] of Object.entries(item.attributes)) {
var isFillCurrentColor =
preserveCurrentColor && name == 'fill' && value == 'currentColor';
var isStrokeCurrentColor =
preserveCurrentColor && name == 'stroke' && value == 'currentColor';
return new RegExp(['^', value, '$'].join(''), 'i');
});
});
// loop patterns
patterns.forEach(function(pattern) {
// matches element
if (pattern[0].test(item.elem)) {
// loop attributes
item.eachAttr(function(attr) {
var name = attr.name;
var value = attr.value;
var isFillCurrentColor = preserveCurrentColor && name == 'fill' && value == 'currentColor';
var isStrokeCurrentColor = preserveCurrentColor && name == 'stroke' && value == 'currentColor';
if (!(isFillCurrentColor || isStrokeCurrentColor)) {
// matches attribute name
if (pattern[1].test(name)) {
// matches attribute value
if (pattern[2].test(attr.value)) {
item.removeAttr(name);
}
}
}
});
if (!(isFillCurrentColor || isStrokeCurrentColor)) {
// matches attribute name
if (pattern[1].test(name)) {
// matches attribute value
if (pattern[2].test(value)) {
delete item.attributes[name];
}
}
});
}
}
}
}
});
}
};

@@ -21,8 +21,6 @@ 'use strict';

*/
exports.fn = function(item) {
if (item.comment && item.comment.charAt(0) !== '!') {
return false;
}
exports.fn = function (item) {
if (item.type === 'comment' && item.value.charAt(0) !== '!') {
return false;
}
};

@@ -8,3 +8,3 @@ 'use strict';

exports.params = {
removeAny: true
removeAny: true,
};

@@ -28,7 +28,12 @@

*/
exports.fn = function(item, params) {
return !item.isElem('desc') || !(params.removeAny || item.isEmpty() ||
standardDescs.test(item.content[0].text));
exports.fn = function (item, params) {
return (
!item.isElem('desc') ||
!(
params.removeAny ||
item.children.length === 0 ||
(item.children[0].type === 'text' &&
standardDescs.test(item.children[0].value))
)
);
};

@@ -7,3 +7,4 @@ 'use strict';

exports.description = 'removes width and height in presence of viewBox (opposite to removeViewBox, disable it first)';
exports.description =
'removes width and height in presence of viewBox (opposite to removeViewBox, disable it first)';

@@ -23,29 +24,20 @@ /**

*/
exports.fn = function(item) {
if (item.isElem('svg')) {
if (item.hasAttr('viewBox')) {
item.removeAttr('width');
item.removeAttr('height');
} else if (
item.hasAttr('width') &&
item.hasAttr('height') &&
!isNaN(Number(item.attr('width').value)) &&
!isNaN(Number(item.attr('height').value))
) {
item.addAttr({
name: 'viewBox',
value:
'0 0 ' +
Number(item.attr('width').value) +
' ' +
Number(item.attr('height').value),
prefix: '',
local: 'viewBox'
});
item.removeAttr('width');
item.removeAttr('height');
}
exports.fn = function (item) {
if (item.type === 'element' && item.name === 'svg') {
if (item.attributes.viewBox != null) {
delete item.attributes.width;
delete item.attributes.height;
} else if (
item.attributes.width != null &&
item.attributes.height != null &&
Number.isNaN(Number(item.attributes.width)) === false &&
Number.isNaN(Number(item.attributes.height)) === false
) {
const width = Number(item.attributes.width);
const height = Number(item.attributes.height);
item.attributes.viewBox = `0 0 ${width} ${height}`;
delete item.attributes.width;
delete item.attributes.height;
}
}
};

@@ -34,8 +34,6 @@ 'use strict';

*/
exports.fn = function(item) {
if (item.doctype) {
return false;
}
exports.fn = function (item) {
if (item.type === 'doctype') {
return false;
}
};
'use strict';
const { parseName } = require('../lib/svgo/tools.js');
const { editorNamespaces } = require('./_collections');
exports.type = 'perItem';

@@ -9,7 +12,6 @@

var editorNamespaces = require('./_collections').editorNamespaces,
prefixes = [];
const prefixes = [];
exports.params = {
additionalNamespaces: []
additionalNamespaces: [],
};

@@ -31,37 +33,35 @@

*/
exports.fn = function(item, params) {
exports.fn = function (item, params) {
let namespaces = editorNamespaces;
if (Array.isArray(params.additionalNamespaces)) {
namespaces = [...editorNamespaces, ...params.additionalNamespaces];
}
if (Array.isArray(params.additionalNamespaces)) {
editorNamespaces = editorNamespaces.concat(params.additionalNamespaces);
}
if (item.type === 'element') {
if (item.isElem('svg')) {
for (const [name, value] of Object.entries(item.attributes)) {
const { prefix, local } = parseName(name);
if (prefix === 'xmlns' && namespaces.includes(value)) {
prefixes.push(local);
if (item.elem) {
if (item.isElem('svg')) {
item.eachAttr(function(attr) {
if (attr.prefix === 'xmlns' && editorNamespaces.indexOf(attr.value) > -1) {
prefixes.push(attr.local);
// <svg xmlns:sodipodi="">
item.removeAttr(attr.name);
}
});
// <svg xmlns:sodipodi="">
delete item.attributes[name];
}
}
}
// <* sodipodi:*="">
item.eachAttr(function(attr) {
if (prefixes.indexOf(attr.prefix) > -1) {
item.removeAttr(attr.name);
}
});
// <* sodipodi:*="">
for (const name of Object.keys(item.attributes)) {
const { prefix } = parseName(name);
if (prefixes.includes(prefix)) {
delete item.attributes[name];
}
}
// <sodipodi:*>
if (prefixes.indexOf(item.prefix) > -1) {
return false;
}
// <sodipodi:*>
const { prefix } = parseName(item.name);
if (prefixes.includes(prefix)) {
return false;
}
}
};

@@ -7,7 +7,8 @@ 'use strict';

exports.description = 'removes arbitrary elements by ID or className (disabled by default)';
exports.description =
'removes arbitrary elements by ID or className (disabled by default)';
exports.params = {
id: [],
class: []
class: [],
};

@@ -54,7 +55,7 @@

*/
exports.fn = function(item, params) {
exports.fn = function (item, params) {
// wrap params in an array if not already
['id', 'class'].forEach(function(key) {
['id', 'class'].forEach(function (key) {
if (!Array.isArray(params[key])) {
params[key] = [ params[key] ];
params[key] = [params[key]];
}

@@ -64,3 +65,3 @@ });

// abort if current item is no an element
if (!item.isElem()) {
if (item.type !== 'element') {
return;

@@ -70,13 +71,11 @@ }

// remove element if it's `id` matches configured `id` params
const elemId = item.attr('id');
if (elemId && params.id.length !== 0) {
return params.id.includes(elemId.value) === false;
if (item.attributes.id != null && params.id.length !== 0) {
return params.id.includes(item.attributes.id) === false;
}
// remove element if it's `class` contains any of the configured `class` params
const elemClass = item.attr('class');
if (elemClass && params.class.length !== 0) {
const classList = elemClass.value.split(' ');
return params.class.some(item => classList.includes(item)) === false;
if (item.attributes.class && params.class.length !== 0) {
const classList = item.attributes.class.split(' ');
return params.class.some((item) => classList.includes(item)) === false;
}
};

@@ -20,13 +20,13 @@ 'use strict';

exports.fn = function (item) {
if (item.elem) {
item.eachAttr(function (attr) {
if (item.type === 'element') {
for (const [name, value] of Object.entries(item.attributes)) {
if (
attr.value === '' &&
value === '' &&
// empty conditional processing attributes prevents elements from rendering
attrsGroups.conditionalProcessing.includes(attr.name) === false
attrsGroups.conditionalProcessing.includes(name) === false
) {
item.removeAttr(attr.name);
delete item.attributes[name];
}
});
}
}
};
'use strict';
const { elemsGroups } = require('./_collections');
exports.type = 'perItemReverse';

@@ -9,4 +11,2 @@

var container = require('./_collections').elemsGroups.container;
/**

@@ -29,14 +29,17 @@ * Remove empty containers.

exports.fn = function (item) {
return (
item.isElem(container) === false ||
item.isEmpty() === false ||
item.isElem('svg') ||
// empty patterns may contain reusable configuration
(item.isElem('pattern') && Object.keys(item.attrs).length !== 0) ||
// The 'g' may not have content, but the filter may cause a rectangle
// to be created and filled with pattern.
(item.isElem('g') && item.hasAttr('filter')) ||
// empty <mask> hides masked element
(item.isElem('mask') && item.hasAttr('id'))
);
if (item.type === 'element') {
return (
item.children.length !== 0 ||
elemsGroups.container.includes(item.name) === false ||
item.name === 'svg' ||
// empty patterns may contain reusable configuration
(item.name === 'pattern' && Object.keys(item.attributes).length !== 0) ||
// The 'g' may not have content, but the filter may cause a rectangle
// to be created and filled with pattern.
(item.name === 'g' && item.attributes.filter != null) ||
// empty <mask> hides masked element
(item.name === 'mask' && item.attributes.id != null)
);
}
return true;
};

@@ -10,5 +10,5 @@ 'use strict';

exports.params = {
text: true,
tspan: true,
tref: true
text: true,
tspan: true,
tref: true,
};

@@ -37,25 +37,23 @@

*/
exports.fn = function(item, params) {
exports.fn = function (item, params) {
if (item.type === 'element') {
// Remove empty text element
if (
params.text &&
item.isElem('text') &&
item.isEmpty()
) return false;
if (params.text && item.name === 'text' && item.children.length === 0) {
return false;
}
// Remove empty tspan element
if (
params.tspan &&
item.isElem('tspan') &&
item.isEmpty()
) return false;
if (params.tspan && item.name === 'tspan' && item.children.length === 0) {
return false;
}
// Remove tref with empty xlink:href attribute
if (
params.tref &&
item.isElem('tref') &&
!item.hasAttrLocal('href')
) return false;
params.tref &&
item.name === 'tref' &&
item.attributes['xlink:href'] == null
) {
return false;
}
}
};
'use strict';
const { querySelector, closestByName } = require('../lib/xast.js');
const { computeStyle } = require('../lib/style.js');

@@ -51,3 +52,3 @@ const { parsePathData } = require('../lib/path.js');

exports.fn = function (item, params) {
if (item.elem) {
if (item.type === 'element') {
// Removes hidden elements

@@ -62,3 +63,3 @@ // https://www.w3schools.com/cssref/pr_class_visibility.asp

// keep if any descendant enables visibility
item.querySelector('[visibility=visible]') == null
querySelector(item, '[visibility=visible]') == null
) {

@@ -93,3 +94,3 @@ return false;

// transparent element inside clipPath still affect clipped elements
item.closestElem('clipPath') == null
closestByName(item, 'clipPath') == null
) {

@@ -108,4 +109,4 @@ return false;

item.isElem('circle') &&
item.isEmpty() &&
item.hasAttr('r', '0')
item.children.length === 0 &&
item.attributes.r === '0'
) {

@@ -124,4 +125,4 @@ return false;

item.isElem('ellipse') &&
item.isEmpty() &&
item.hasAttr('rx', '0')
item.children.length === 0 &&
item.attributes.rx === '0'
) {

@@ -140,4 +141,4 @@ return false;

item.isElem('ellipse') &&
item.isEmpty() &&
item.hasAttr('ry', '0')
item.children.length === 0 &&
item.attributes.ry === '0'
) {

@@ -156,4 +157,4 @@ return false;

item.isElem('rect') &&
item.isEmpty() &&
item.hasAttr('width', '0')
item.children.length === 0 &&
item.attributes.width === '0'
) {

@@ -173,4 +174,4 @@ return false;

item.isElem('rect') &&
item.isEmpty() &&
item.hasAttr('height', '0')
item.children.length === 0 &&
item.attributes.height === '0'
) {

@@ -189,3 +190,3 @@ return false;

item.isElem('pattern') &&
item.hasAttr('width', '0')
item.attributes.width === '0'
) {

@@ -204,3 +205,3 @@ return false;

item.isElem('pattern') &&
item.hasAttr('height', '0')
item.attributes.height === '0'
) {

@@ -219,3 +220,3 @@ return false;

item.isElem('image') &&
item.hasAttr('width', '0')
item.attributes.width === '0'
) {

@@ -234,3 +235,3 @@ return false;

item.isElem('image') &&
item.hasAttr('height', '0')
item.attributes.height === '0'
) {

@@ -246,6 +247,6 @@ return false;

if (params.pathEmptyD && item.isElem('path')) {
if (item.hasAttr('d') === false) {
if (item.attributes.d == null) {
return false;
}
const pathData = parsePathData(item.attr('d').value);
const pathData = parsePathData(item.attributes.d);
if (pathData.length === 0) {

@@ -273,3 +274,3 @@ return false;

item.isElem('polyline') &&
!item.hasAttr('points')
item.attributes.points == null
) {

@@ -287,3 +288,3 @@ return false;

item.isElem('polygon') &&
!item.hasAttr('points')
item.attributes.points == null
) {

@@ -290,0 +291,0 @@ return false;

@@ -19,6 +19,4 @@ 'use strict';

*/
exports.fn = function(item) {
return !item.isElem('metadata');
exports.fn = function (item) {
return !item.isElem('metadata');
};

@@ -7,7 +7,10 @@ 'use strict';

exports.description = 'removes non-inheritable group’s presentational attributes';
exports.description =
'removes non-inheritable group’s presentational attributes';
var inheritableAttrs = require('./_collections').inheritableAttrs,
attrsGroups = require('./_collections').attrsGroups,
applyGroups = require('./_collections').presentationNonInheritableGroupAttrs;
const {
inheritableAttrs,
attrsGroups,
presentationNonInheritableGroupAttrs,
} = require('./_collections');

@@ -22,18 +25,14 @@ /**

*/
exports.fn = function(item) {
if (item.isElem('g')) {
item.eachAttr(function(attr) {
if (
~attrsGroups.presentation.indexOf(attr.name) &&
!~inheritableAttrs.indexOf(attr.name) &&
!~applyGroups.indexOf(attr.name)
) {
item.removeAttr(attr.name);
}
});
exports.fn = function (item) {
if (item.type === 'element' && item.name === 'g') {
for (const name of Object.keys(item.attributes)) {
if (
attrsGroups.presentation.includes(name) === true &&
inheritableAttrs.includes(name) === false &&
presentationNonInheritableGroupAttrs.includes(name) === false
) {
delete item.attributes[name];
}
}
}
};

@@ -7,11 +7,12 @@ 'use strict';

exports.description = 'removes elements that are drawn outside of the viewbox (disabled by default)';
exports.description =
'removes elements that are drawn outside of the viewbox (disabled by default)';
const JSAPI = require('../lib/svgo/jsAPI.js');
var _path = require('./_path.js'),
intersects = _path.intersects,
path2js = _path.path2js,
viewBox,
viewBoxJS;
var _path = require('./_path.js'),
intersects = _path.intersects,
path2js = _path.path2js,
viewBox,
viewBoxJS;

@@ -26,29 +27,29 @@ /**

*/
exports.fn = function(item) {
exports.fn = function (item) {
if (
item.type === 'element' &&
item.name === 'path' &&
item.attributes.d != null &&
typeof viewBox !== 'undefined'
) {
// Consider that any item with a transform attribute or a M instruction
// within the viewBox is visible
if (hasTransform(item) || pathMovesWithinViewBox(item.attributes.d)) {
return true;
}
if (item.isElem('path') && item.hasAttr('d') && typeof viewBox !== 'undefined')
{
// Consider that any item with a transform attribute or a M instruction
// within the viewBox is visible
if (hasTransform(item) || pathMovesWithinViewBox(item.attr('d').value))
{
return true;
}
var pathJS = path2js(item);
if (pathJS.length === 2) {
// Use a closed clone of the path if it's too short for intersects()
pathJS = JSON.parse(JSON.stringify(pathJS));
pathJS.push({ instruction: 'z' });
}
var pathJS = path2js(item);
if (pathJS.length === 2)
{
// Use a closed clone of the path if it's too short for intersects()
pathJS = JSON.parse(JSON.stringify(pathJS));
pathJS.push({ instruction: 'z' });
}
return intersects(viewBoxJS, pathJS);
}
if (item.type === 'element' && item.name === 'svg') {
parseViewBox(item);
}
return intersects(viewBoxJS, pathJS);
}
if (item.isElem('svg'))
{
parseViewBox(item);
}
return true;
return true;
};

@@ -62,5 +63,9 @@

*/
function hasTransform(item)
{
return item.hasAttr('transform') || (item.parentNode && hasTransform(item.parentNode));
function hasTransform(item) {
return (
item.attributes.transform != null ||
(item.parentNode &&
item.parentNode.type === 'element' &&
hasTransform(item.parentNode))
);
}

@@ -73,46 +78,43 @@

*/
function parseViewBox(svg)
{
var viewBoxData = '';
if (svg.hasAttr('viewBox'))
{
// Remove commas and plus signs, normalize and trim whitespace
viewBoxData = svg.attr('viewBox').value;
}
else if (svg.hasAttr('height') && svg.hasAttr('width'))
{
viewBoxData = '0 0 ' + svg.attr('width').value + ' ' + svg.attr('height').value;
}
function parseViewBox(svg) {
var viewBoxData = '';
if (svg.attributes.viewBox != null) {
// Remove commas and plus signs, normalize and trim whitespace
viewBoxData = svg.attributes.viewBox;
} else if (svg.attributes.height != null && svg.attributes.width != null) {
viewBoxData = `0 0 ${svg.attributes.width} ${svg.attributes.height}`;
}
// Remove commas and plus signs, normalize and trim whitespace
viewBoxData = viewBoxData.replace(/[,+]|px/g, ' ').replace(/\s+/g, ' ').replace(/^\s*|\s*$/g, '');
// Remove commas and plus signs, normalize and trim whitespace
viewBoxData = viewBoxData
.replace(/[,+]|px/g, ' ')
.replace(/\s+/g, ' ')
.replace(/^\s*|\s*$/g, '');
// Ensure that the dimensions are 4 values separated by space
var m = /^(-?\d*\.?\d+) (-?\d*\.?\d+) (\d*\.?\d+) (\d*\.?\d+)$/.exec(viewBoxData);
if (!m)
{
return;
}
// Ensure that the dimensions are 4 values separated by space
var m = /^(-?\d*\.?\d+) (-?\d*\.?\d+) (\d*\.?\d+) (\d*\.?\d+)$/.exec(
viewBoxData
);
if (!m) {
return;
}
// Store the viewBox boundaries
viewBox = {
left: parseFloat(m[1]),
top: parseFloat(m[2]),
right: parseFloat(m[1]) + parseFloat(m[3]),
bottom: parseFloat(m[2]) + parseFloat(m[4])
};
// Store the viewBox boundaries
viewBox = {
left: parseFloat(m[1]),
top: parseFloat(m[2]),
right: parseFloat(m[1]) + parseFloat(m[3]),
bottom: parseFloat(m[2]) + parseFloat(m[4]),
};
var path = new JSAPI({
elem: 'path',
prefix: '',
local: 'path'
});
path.addAttr({
name: 'd',
prefix: '',
local: 'd',
value: 'M' + m[1] + ' ' + m[2] + 'h' + m[3] + 'v' + m[4] + 'H' + m[1] + 'z'
});
var path = new JSAPI({
type: 'element',
name: 'path',
attributes: {
d: 'M' + m[1] + ' ' + m[2] + 'h' + m[3] + 'v' + m[4] + 'H' + m[1] + 'z',
},
content: [],
});
viewBoxJS = path2js(path);
viewBoxJS = path2js(path);
}

@@ -126,14 +128,17 @@

*/
function pathMovesWithinViewBox(path)
{
var regexp = /M\s*(-?\d*\.?\d+)(?!\d)\s*(-?\d*\.?\d+)/g, m;
while (null !== (m = regexp.exec(path)))
{
if (m[1] >= viewBox.left && m[1] <= viewBox.right && m[2] >= viewBox.top && m[2] <= viewBox.bottom)
{
return true;
}
}
function pathMovesWithinViewBox(path) {
var regexp = /M\s*(-?\d*\.?\d+)(?!\d)\s*(-?\d*\.?\d+)/g,
m;
while (null !== (m = regexp.exec(path))) {
if (
m[1] >= viewBox.left &&
m[1] <= viewBox.right &&
m[2] >= viewBox.top &&
m[2] <= viewBox.bottom
) {
return true;
}
}
return false;
return false;
}

@@ -19,11 +19,11 @@ 'use strict';

*/
exports.fn = function(item) {
if (
item.isElem('image') &&
item.hasAttrLocal('href', /(\.|image\/)(jpg|png|gif)/)
) {
return false;
}
exports.fn = function (item) {
if (
item.type === 'element' &&
item.name === 'image' &&
item.attributes['xlink:href'] != null &&
/(\.|image\/)(jpg|png|gif)/.test(item.attributes['xlink:href'])
) {
return false;
}
};

@@ -19,6 +19,4 @@ 'use strict';

*/
exports.fn = function(item) {
return !item.isElem('script');
exports.fn = function (item) {
return !item.isElem('script');
};

@@ -19,6 +19,4 @@ 'use strict';

*/
exports.fn = function(item) {
return !item.isElem('style');
exports.fn = function (item) {
return !item.isElem('style');
};

@@ -19,6 +19,4 @@ 'use strict';

*/
exports.fn = function(item) {
return !item.isElem('title');
exports.fn = function (item) {
return !item.isElem('title');
};
'use strict';
const { parseName } = require('../lib/svgo/tools.js');
exports.type = 'perItem';

@@ -7,50 +9,50 @@

exports.description = 'removes unknown elements content and attributes, removes attrs with default values';
exports.description =
'removes unknown elements content and attributes, removes attrs with default values';
exports.params = {
unknownContent: true,
unknownAttrs: true,
defaultAttrs: true,
uselessOverrides: true,
keepDataAttrs: true,
keepAriaAttrs: true,
keepRoleAttr: false
unknownContent: true,
unknownAttrs: true,
defaultAttrs: true,
uselessOverrides: true,
keepDataAttrs: true,
keepAriaAttrs: true,
keepRoleAttr: false,
};
var collections = require('./_collections'),
elems = collections.elems,
attrsGroups = collections.attrsGroups,
elemsGroups = collections.elemsGroups,
attrsGroupsDefaults = collections.attrsGroupsDefaults,
attrsInheritable = collections.inheritableAttrs,
applyGroups = collections.presentationNonInheritableGroupAttrs;
elems = collections.elems,
attrsGroups = collections.attrsGroups,
elemsGroups = collections.elemsGroups,
attrsGroupsDefaults = collections.attrsGroupsDefaults,
attrsInheritable = collections.inheritableAttrs,
applyGroups = collections.presentationNonInheritableGroupAttrs;
// collect and extend all references
for (const elem of Object.values(elems)) {
if (elem.attrsGroups) {
elem.attrs = elem.attrs || [];
if (elem.attrsGroups) {
elem.attrs = elem.attrs || [];
elem.attrsGroups.forEach(function(attrsGroupName) {
elem.attrs = elem.attrs.concat(attrsGroups[attrsGroupName]);
elem.attrsGroups.forEach(function (attrsGroupName) {
elem.attrs = elem.attrs.concat(attrsGroups[attrsGroupName]);
var groupDefaults = attrsGroupsDefaults[attrsGroupName];
var groupDefaults = attrsGroupsDefaults[attrsGroupName];
if (groupDefaults) {
elem.defaults = elem.defaults || {};
if (groupDefaults) {
elem.defaults = elem.defaults || {};
for (const [attrName, attr] of Object.entries(groupDefaults)) {
elem.defaults[attrName] = attr;
}
}
});
for (const [attrName, attr] of Object.entries(groupDefaults)) {
elem.defaults[attrName] = attr;
}
}
});
}
}
if (elem.contentGroups) {
elem.content = elem.content || [];
if (elem.contentGroups) {
elem.content = elem.content || [];
elem.contentGroups.forEach(function(contentGroupName) {
elem.content = elem.content.concat(elemsGroups[contentGroupName]);
});
}
elem.contentGroups.forEach(function (contentGroupName) {
elem.content = elem.content.concat(elemsGroups[contentGroupName]);
});
}
}

@@ -68,83 +70,61 @@

*/
exports.fn = function(item, params) {
exports.fn = function (item, params) {
// elems w/o namespace prefix
if (item.type === 'element' && !parseName(item.name).prefix) {
var elem = item.name;
// elems w/o namespace prefix
if (item.isElem() && !item.prefix) {
var elem = item.elem;
// remove unknown element's content
// remove unknown element's content
if (
params.unknownContent &&
elems[elem] && // make sure we know of this element before checking its children
elem !== 'foreignObject' // Don't check foreignObject
) {
item.children.forEach(function (content, i) {
if (
params.unknownContent &&
!item.isEmpty() &&
elems[elem] && // make sure we know of this element before checking its children
elem !== 'foreignObject' // Don't check foreignObject
content.type === 'element' &&
!parseName(content.name).prefix &&
((elems[elem].content && // Do we have a record of its permitted content?
elems[elem].content.indexOf(content.name) === -1) ||
(!elems[elem].content && // we dont know about its permitted content
!elems[content.name])) // check that we know about the element at all
) {
item.content.forEach(function(content, i) {
if (
content.isElem() &&
!content.prefix &&
(
(
elems[elem].content && // Do we have a record of its permitted content?
elems[elem].content.indexOf(content.elem) === -1
) ||
(
!elems[elem].content && // we dont know about its permitted content
!elems[content.elem] // check that we know about the element at all
)
)
) {
item.content.splice(i, 1);
}
});
item.children.splice(i, 1);
}
});
}
// remove element's unknown attrs and attrs with default values
if (elems[elem] && elems[elem].attrs) {
item.eachAttr(function(attr) {
if (
attr.name !== 'xmlns' &&
(attr.prefix === 'xml' || !attr.prefix) &&
(!params.keepDataAttrs || attr.name.indexOf('data-') != 0) &&
(!params.keepAriaAttrs || attr.name.indexOf('aria-') != 0) &&
(!params.keepRoleAttr || attr.name != 'role')
) {
if (
// unknown attrs
(
params.unknownAttrs &&
elems[elem].attrs.indexOf(attr.name) === -1
) ||
// attrs with default values
(
params.defaultAttrs &&
!item.hasAttr('id') &&
elems[elem].defaults &&
elems[elem].defaults[attr.name] === attr.value && (
attrsInheritable.indexOf(attr.name) < 0 ||
!item.parentNode.computedAttr(attr.name)
)
) ||
// useless overrides
(
params.uselessOverrides &&
!item.hasAttr('id') &&
applyGroups.indexOf(attr.name) < 0 &&
attrsInheritable.indexOf(attr.name) > -1 &&
item.parentNode.computedAttr(attr.name, attr.value)
)
) {
item.removeAttr(attr.name);
}
}
});
// remove element's unknown attrs and attrs with default values
if (elems[elem] && elems[elem].attrs) {
for (const [name, value] of Object.entries(item.attributes)) {
const { prefix } = parseName(name);
if (
name !== 'xmlns' &&
(prefix === 'xml' || !prefix) &&
(!params.keepDataAttrs || name.indexOf('data-') != 0) &&
(!params.keepAriaAttrs || name.indexOf('aria-') != 0) &&
(!params.keepRoleAttr || name != 'role')
) {
if (
// unknown attrs
(params.unknownAttrs && elems[elem].attrs.indexOf(name) === -1) ||
// attrs with default values
(params.defaultAttrs &&
item.attributes.id == null &&
elems[elem].defaults &&
elems[elem].defaults[name] === value &&
(attrsInheritable.includes(name) === false ||
!item.parentNode.computedAttr(name))) ||
// useless overrides
(params.uselessOverrides &&
item.attributes.id == null &&
applyGroups.includes(name) === false &&
attrsInheritable.includes(name) === true &&
item.parentNode.computedAttr(name, value))
) {
delete item.attributes[name];
}
}
}
}
}
};
'use strict';
const { traverse } = require('../lib/xast.js');
const { parseName } = require('../lib/svgo/tools.js');
exports.type = 'full';

@@ -17,94 +20,62 @@

*/
exports.fn = function(data) {
exports.fn = function (root) {
let svgElem;
const xmlnsCollection = [];
var svgElem,
xmlnsCollection = [];
/**
* Remove namespace from collection.
*
* @param {String} ns namescape name
*/
function removeNSfromCollection(ns) {
const pos = xmlnsCollection.indexOf(ns);
/**
* Remove namespace from collection.
*
* @param {String} ns namescape name
*/
function removeNSfromCollection(ns) {
// if found - remove ns from the namespaces collection
if (pos > -1) {
xmlnsCollection.splice(pos, 1);
}
}
var pos = xmlnsCollection.indexOf(ns);
traverse(root, (node) => {
if (node.type === 'element') {
if (node.name === 'svg') {
for (const name of Object.keys(node.attributes)) {
const { prefix, local } = parseName(name);
// collect namespaces
if (prefix === 'xmlns' && local) {
xmlnsCollection.push(local);
}
}
// if found - remove ns from the namespaces collection
if (pos > -1) {
xmlnsCollection.splice(pos, 1);
// if svg element has ns-attr
if (xmlnsCollection.length) {
// save svg element
svgElem = node;
}
}
}
if (xmlnsCollection.length) {
const { prefix } = parseName(node.name);
// check node for the ns-attrs
if (prefix) {
removeNSfromCollection(prefix);
}
/**
* Bananas!
*
* @param {Array} items input items
*
* @return {Array} output items
*/
function monkeys(items) {
var i = 0,
length = items.content.length;
while(i < length) {
var item = items.content[i];
if (item.isElem('svg')) {
item.eachAttr(function(attr) {
// collect namespaces
if (attr.prefix === 'xmlns' && attr.local) {
xmlnsCollection.push(attr.local);
}
});
// if svg element has ns-attr
if (xmlnsCollection.length) {
// save svg element
svgElem = item;
}
}
if (xmlnsCollection.length) {
// check item for the ns-attrs
if (item.prefix) {
removeNSfromCollection(item.prefix);
}
// check each attr for the ns-attrs
item.eachAttr(function(attr) {
removeNSfromCollection(attr.prefix);
});
}
// if nothing is found - go deeper
if (xmlnsCollection.length && item.content) {
monkeys(item);
}
i++;
// check each attr for the ns-attrs
for (const name of Object.keys(node.attributes)) {
const { prefix } = parseName(name);
removeNSfromCollection(prefix);
}
return items;
}
}
});
data = monkeys(data);
// remove svg element ns-attributes if they are not used even once
if (xmlnsCollection.length) {
xmlnsCollection.forEach(function(name) {
svgElem.removeAttr('xmlns:' + name);
});
// remove svg element ns-attributes if they are not used even once
if (xmlnsCollection.length) {
for (const name of xmlnsCollection) {
delete svgElem.attributes['xmlns:' + name];
}
}
return data;
return root;
};
'use strict';
const { elemsGroups } = require('./_collections');
exports.type = 'perItem';

@@ -9,4 +11,2 @@

var nonRendering = require('./_collections').elemsGroups.nonRendering;
/**

@@ -20,36 +20,31 @@ * Removes content of defs and properties that aren't rendered directly without ids.

*/
exports.fn = function(item) {
if (item.isElem('defs')) {
if (item.content) {
item.content = getUsefulItems(item, []);
}
if (item.isEmpty()) return false;
} else if (item.isElem(nonRendering) && !item.hasAttr('id')) {
exports.fn = function (item) {
if (item.type === 'element') {
if (item.name === 'defs') {
item.children = getUsefulItems(item, []);
if (item.children.length === 0) {
return false;
}
} else if (
elemsGroups.nonRendering.includes(item.name) &&
item.attributes.id == null
) {
return false;
}
}
};
function getUsefulItems(item, usefulItems) {
for (const child of item.children) {
if (child.type === 'element') {
if (child.attributes.id != null || child.name === 'style') {
usefulItems.push(child);
child.parentNode = item;
} else {
child.children = getUsefulItems(child, usefulItems);
}
}
}
item.content.forEach(function(child) {
if (child.hasAttr('id') || child.isElem('style')) {
usefulItems.push(child);
child.parentNode = item;
} else if (!child.isEmpty()) {
child.content = getUsefulItems(child, usefulItems);
}
});
return usefulItems;
return usefulItems;
}

@@ -10,12 +10,12 @@ 'use strict';

exports.params = {
stroke: true,
fill: true,
removeNone: false,
hasStyleOrScript: false
stroke: true,
fill: true,
removeNone: false,
hasStyleOrScript: false,
};
var shape = require('./_collections').elemsGroups.shape,
regStrokeProps = /^stroke/,
regFillProps = /^fill-/,
styleOrScript = ['style', 'script'];
regStrokeProps = /^stroke/,
regFillProps = /^fill-/,
styleOrScript = ['style', 'script'];

@@ -31,79 +31,64 @@ /**

*/
exports.fn = function(item, params) {
if (item.isElem(styleOrScript)) {
params.hasStyleOrScript = true;
}
exports.fn = function (item, params) {
if (item.isElem(styleOrScript)) {
params.hasStyleOrScript = true;
}
if (!params.hasStyleOrScript && item.isElem(shape) && !item.computedAttr('id')) {
if (
!params.hasStyleOrScript &&
item.isElem(shape) &&
!item.computedAttr('id')
) {
var stroke = params.stroke && item.computedAttr('stroke'),
fill = params.fill && !item.computedAttr('fill', 'none');
var stroke = params.stroke && item.computedAttr('stroke'),
fill = params.fill && !item.computedAttr('fill', 'none');
// remove stroke*
if (
params.stroke &&
(!stroke ||
stroke == 'none' ||
item.computedAttr('stroke-opacity', '0') ||
item.computedAttr('stroke-width', '0'))
) {
// stroke-width may affect the size of marker-end
if (
item.computedAttr('stroke-width', '0') === true ||
item.computedAttr('marker-end') == null
) {
var parentStroke = item.parentNode.computedAttr('stroke'),
declineStroke = parentStroke && parentStroke != 'none';
// remove stroke*
if (
params.stroke &&
(
!stroke ||
stroke == 'none' ||
item.computedAttr('stroke-opacity', '0') ||
item.computedAttr('stroke-width', '0')
)
) {
// stroke-width may affect the size of marker-end
if (
item.computedAttr('stroke-width', '0') === true ||
item.computedAttr('marker-end') == null
) {
var parentStroke = item.parentNode.computedAttr('stroke'),
declineStroke = parentStroke && parentStroke != 'none';
item.eachAttr(function(attr) {
if (regStrokeProps.test(attr.name)) {
item.removeAttr(attr.name);
}
});
if (declineStroke) item.addAttr({
name: 'stroke',
value: 'none',
prefix: '',
local: 'stroke'
});
}
for (const name of Object.keys(item.attributes)) {
if (regStrokeProps.test(name)) {
delete item.attributes[name];
}
}
// remove fill*
if (
params.fill &&
(!fill || item.computedAttr('fill-opacity', '0'))
) {
item.eachAttr(function(attr) {
if (regFillProps.test(attr.name)) {
item.removeAttr(attr.name);
}
});
if (fill) {
if (item.hasAttr('fill'))
item.attr('fill').value = 'none';
else
item.addAttr({
name: 'fill',
value: 'none',
prefix: '',
local: 'fill'
});
}
if (declineStroke) {
item.attributes.stroke = 'none';
}
}
}
if (params.removeNone &&
(!stroke || item.hasAttr('stroke') && item.attr('stroke').value=='none') &&
(!fill || item.hasAttr('fill') && item.attr('fill').value=='none')) {
return false;
// remove fill*
if (params.fill && (!fill || item.computedAttr('fill-opacity', '0'))) {
for (const name of Object.keys(item.attributes)) {
if (regFillProps.test(name)) {
delete item.attributes[name];
}
}
if (fill) {
item.attributes.fill = 'none';
}
}
if (
params.removeNone &&
(!stroke || item.attributes.stroke == 'none') &&
(!fill || item.attributes.fill == 'none')
) {
return false;
}
}
};
'use strict';
const { closestByName } = require('../lib/xast.js');
exports.type = 'perItem';

@@ -9,3 +11,3 @@

var viewBoxElems = ['svg', 'pattern', 'symbol'];
const viewBoxElems = ['svg', 'pattern', 'symbol'];

@@ -29,13 +31,14 @@ /**

if (
item.isElem(viewBoxElems) &&
item.hasAttr('viewBox') &&
item.hasAttr('width') &&
item.hasAttr('height')
item.type === 'element' &&
viewBoxElems.includes(item.name) &&
item.attributes.viewBox != null &&
item.attributes.width != null &&
item.attributes.height != null
) {
// TODO remove width/height for such case instead
if (item.isElem('svg') && item.closestElem('svg')) {
if (item.name === 'svg' && closestByName(item.parentNode, 'svg')) {
return;
}
var nums = item.attr('viewBox').value.split(/[ ,]+/g);
const nums = item.attributes.viewBox.split(/[ ,]+/g);

@@ -45,8 +48,8 @@ if (

nums[1] === '0' &&
item.attr('width').value.replace(/px$/, '') === nums[2] && // could use parseFloat too
item.attr('height').value.replace(/px$/, '') === nums[3]
item.attributes.width.replace(/px$/, '') === nums[2] && // could use parseFloat too
item.attributes.height.replace(/px$/, '') === nums[3]
) {
item.removeAttr('viewBox');
delete item.attributes.viewBox;
}
}
};

@@ -7,3 +7,4 @@ 'use strict';

exports.description = 'removes xmlns attribute (for inline svg, disabled by default)';
exports.description =
'removes xmlns attribute (for inline svg, disabled by default)';

@@ -23,8 +24,6 @@ /**

*/
exports.fn = function(item) {
if (item.isElem('svg') && item.hasAttr('xmlns')) {
item.removeAttr('xmlns');
}
};
exports.fn = function (item) {
if (item.type === 'element' && item.name === 'svg') {
delete item.attributes.xmlns;
}
};

@@ -20,6 +20,7 @@ 'use strict';

*/
exports.fn = function(item) {
return !(item.processinginstruction && item.processinginstruction.name === 'xml');
exports.fn = function (item) {
if (item.type === 'instruction' && item.name === 'xml') {
return false;
}
return true;
};

@@ -1,82 +0,5 @@

/**
* @license
* The MIT License
*
* Copyright © 2012–2016 Kir Belevich
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
*
* Лицензия MIT
*
* Copyright © 2012–2016 Кир Белевич
*
* Данная лицензия разрешает лицам, получившим копию
* данного
* программного обеспечения и сопутствующей
* документации
* (в дальнейшем именуемыми «Программное Обеспечение»),
* безвозмездно
* использовать Программное Обеспечение без
* ограничений, включая
* неограниченное право на использование, копирование,
* изменение,
* добавление, публикацию, распространение,
* сублицензирование
* и/или продажу копий Программного Обеспечения, также
* как и лицам,
* которым предоставляется данное Программное
* Обеспечение,
* при соблюдении следующих условий:
*
* Указанное выше уведомление об авторском праве и
* данные условия
* должны быть включены во все копии или значимые части
* данного
* Программного Обеспечения.
*
* ДАННОЕ ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ ПРЕДОСТАВЛЯЕТСЯ «КАК
* ЕСТЬ»,
* БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ, ЯВНО ВЫРАЖЕННЫХ ИЛИ
* ПОДРАЗУМЕВАЕМЫХ,
* ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИВАЯСЬ ГАРАНТИЯМИ ТОВАРНОЙ
* ПРИГОДНОСТИ,
* СООТВЕТСТВИЯ ПО ЕГО КОНКРЕТНОМУ НАЗНАЧЕНИЮ И
* ОТСУТСТВИЯ НАРУШЕНИЙ
* ПРАВ. НИ В КАКОМ СЛУЧАЕ АВТОРЫ ИЛИ ПРАВООБЛАДАТЕЛИ НЕ
* НЕСУТ
* ОТВЕТСТВЕННОСТИ ПО ИСКАМ О ВОЗМЕЩЕНИИ УЩЕРБА, УБЫТКОВ
* ИЛИ ДРУГИХ
* ТРЕБОВАНИЙ ПО ДЕЙСТВУЮЩИМ КОНТРАКТАМ, ДЕЛИКТАМ ИЛИ
* ИНОМУ,
* ВОЗНИКШИМ ИЗ, ИМЕЮЩИМ ПРИЧИНОЙ ИЛИ СВЯЗАННЫМ С
* ПРОГРАММНЫМ
* ОБЕСПЕЧЕНИЕМ ИЛИ ИСПОЛЬЗОВАНИЕМ ПРОГРАММНОГО
* ОБЕСПЕЧЕНИЯ
* ИЛИ ИНЫМИ ДЕЙСТВИЯМИ С ПРОГРАММНЫМ ОБЕСПЕЧЕНИЕМ.
*/
'use strict';
var JSAPI = require('../lib/svgo/jsAPI');
const { traverse } = require('../lib/xast.js');
const JSAPI = require('../lib/svgo/jsAPI');

@@ -87,5 +10,6 @@ exports.type = 'full';

exports.description = 'Finds <path> elements with the same d, fill, and ' +
'stroke, and converts them to <use> elements ' +
'referencing a single <path> def.';
exports.description =
'Finds <path> elements with the same d, fill, and ' +
'stroke, and converts them to <use> elements ' +
'referencing a single <path> def.';

@@ -98,17 +22,21 @@ /**

*/
exports.fn = function(data) {
exports.fn = function (root) {
const seen = new Map();
let count = 0;
const defs = [];
traverse(data, item => {
if (!item.isElem('path') || !item.hasAttr('d')) {
traverse(root, (node) => {
if (
node.type !== 'element' ||
node.name !== 'path' ||
node.attributes.d == null
) {
return;
}
const d = item.attr('d').value;
const fill = (item.hasAttr('fill') && item.attr('fill').value) || '';
const stroke = (item.hasAttr('stroke') && item.attr('stroke').value) || '';
const d = node.attributes.d;
const fill = node.attributes.fill || '';
const stroke = node.attributes.stroke || '';
const key = d + ';s:' + stroke + ';f:' + fill;
const hasSeen = seen.get(key);
if (!hasSeen) {
seen.set(key, {elem: item, reused: false});
seen.set(key, { elem: node, reused: false });
return;

@@ -118,14 +46,20 @@ }

hasSeen.reused = true;
if (!hasSeen.elem.hasAttr('id')) {
hasSeen.elem.addAttr({name: 'id', local: 'id',
prefix: '', value: 'reuse-' + (count++)});
if (hasSeen.elem.attributes.id == null) {
hasSeen.elem.attributes.id = 'reuse-' + count++;
}
defs.push(hasSeen.elem);
}
convertToUse(item, hasSeen.elem.attr('id').value);
convertToUse(node, hasSeen.elem.attributes.id);
});
if (defs.length > 0) {
const defsTag = new JSAPI({
elem: 'defs', prefix: '', local: 'defs', content: [], attrs: []}, data);
data.content[0].spliceContent(0, 0, defsTag);
const defsTag = new JSAPI(
{
type: 'element',
name: 'defs',
attributes: {},
children: [],
},
root
);
root.children[0].spliceContent(0, 0, defsTag);
for (let def of defs) {

@@ -142,10 +76,10 @@ // Remove class and style before copying to avoid circular refs in

def.class = defClass;
defClone.removeAttr('transform');
delete defClone.attributes.transform;
defsTag.spliceContent(0, 0, defClone);
// Convert the original def to a use so the first usage isn't duplicated.
def = convertToUse(def, defClone.attr('id').value);
def.removeAttr('id');
def = convertToUse(def, defClone.attributes.id);
delete def.attributes.id;
}
}
return data;
return root;
};

@@ -156,20 +90,8 @@

item.renameElem('use');
item.removeAttr('d');
item.removeAttr('stroke');
item.removeAttr('fill');
item.addAttr({name: 'xlink:href', local: 'xlink:href',
prefix: 'none', value: '#' + href});
delete item.attributes.d;
delete item.attributes.stroke;
delete item.attributes.fill;
item.attributes['xlink:href'] = '#' + href;
delete item.pathJS;
return item;
}
/** */
function traverse(parent, callback) {
if (parent.isEmpty()) {
return;
}
for (let child of parent.content) {
callback(child);
traverse(child, callback);
}
}
'use strict';
const { parseName } = require('../lib/svgo/tools.js');
exports.type = 'perItem';

@@ -10,11 +12,21 @@

exports.params = {
order: [
'id',
'width', 'height',
'x', 'x1', 'x2',
'y', 'y1', 'y2',
'cx', 'cy', 'r',
'fill', 'stroke', 'marker',
'd', 'points'
]
order: [
'id',
'width',
'height',
'x',
'x1',
'x2',
'y',
'y1',
'y2',
'cx',
'cy',
'r',
'fill',
'stroke',
'marker',
'd',
'points',
],
};

@@ -30,57 +42,49 @@

*/
exports.fn = function(item, params) {
exports.fn = function (item, params) {
const orderlen = params.order.length + 1;
const xmlnsOrder = params.xmlnsOrder || 'front';
var attrs = [],
sorted = {},
orderlen = params.order.length + 1,
xmlnsOrder = params.xmlnsOrder || 'front';
if (item.type === 'element') {
const attrs = Object.entries(item.attributes);
if (item.elem) {
attrs.sort(([aName], [bName]) => {
const { prefix: aPrefix } = parseName(aName);
const { prefix: bPrefix } = parseName(bName);
if (aPrefix != bPrefix) {
// xmlns attributes implicitly have the prefix xmlns
if (xmlnsOrder == 'front') {
if (aPrefix === 'xmlns') return -1;
if (bPrefix === 'xmlns') return 1;
}
return aPrefix < bPrefix ? -1 : 1;
}
item.eachAttr(function(attr) {
attrs.push(attr);
});
let aindex = orderlen;
let bindex = orderlen;
attrs.sort(function(a, b) {
if (a.prefix != b.prefix) {
// xmlns attributes implicitly have the prefix xmlns
if (xmlnsOrder == 'front') {
if (a.prefix == 'xmlns')
return -1;
if (b.prefix == 'xmlns')
return 1;
}
return a.prefix < b.prefix ? -1 : 1;
}
for (let i = 0; i < params.order.length; i++) {
if (aName == params.order[i]) {
aindex = i;
} else if (aName.indexOf(params.order[i] + '-') === 0) {
aindex = i + 0.5;
}
if (bName == params.order[i]) {
bindex = i;
} else if (bName.indexOf(params.order[i] + '-') === 0) {
bindex = i + 0.5;
}
}
var aindex = orderlen;
var bindex = orderlen;
if (aindex != bindex) {
return aindex - bindex;
}
return aName < bName ? -1 : 1;
});
for (var i = 0; i < params.order.length; i++) {
if (a.name == params.order[i]) {
aindex = i;
} else if (a.name.indexOf(params.order[i] + '-') === 0) {
aindex = i + .5;
}
if (b.name == params.order[i]) {
bindex = i;
} else if (b.name.indexOf(params.order[i] + '-') === 0) {
bindex = i + .5;
}
}
if (aindex != bindex) {
return aindex - bindex;
}
return a.name < b.name ? -1 : 1;
});
attrs.forEach(function (attr) {
sorted[attr.name] = attr;
});
item.attrs = sorted;
}
const sorted = {};
for (const [name, value] of attrs) {
sorted[name] = value;
}
item.attributes = sorted;
}
};

@@ -18,31 +18,25 @@ 'use strict';

*/
exports.fn = function(item) {
if (item.isElem('defs')) {
if (item.content) {
var frequency = item.content.reduce(function (frequency, child) {
if (child.elem in frequency) {
frequency[child.elem]++;
} else {
frequency[child.elem] = 1;
}
return frequency;
}, {});
item.content.sort(function (a, b) {
var frequencyComparison = frequency[b.elem] - frequency[a.elem];
if (frequencyComparison !== 0 ) {
return frequencyComparison;
}
var lengthComparison = b.elem.length - a.elem.length;
if (lengthComparison !== 0) {
return lengthComparison;
}
return a.elem != b.elem ? a.elem > b.elem ? -1 : 1 : 0;
});
}
return true;
}
exports.fn = function (item) {
if (item.isElem('defs')) {
var frequency = item.children.reduce(function (frequency, child) {
if (child.name in frequency) {
frequency[child.name]++;
} else {
frequency[child.name] = 1;
}
return frequency;
}, {});
item.children.sort(function (a, b) {
var frequencyComparison = frequency[b.name] - frequency[a.name];
if (frequencyComparison !== 0) {
return frequencyComparison;
}
var lengthComparison = b.name.length - a.name.length;
if (lengthComparison !== 0) {
return lengthComparison;
}
return a.name != b.name ? (a.name > b.name ? -1 : 1) : 0;
});
return true;
}
};

@@ -125,2 +125,3 @@ <div align="center">

'cleanupAttrs',
'mergeStyles',
'inlineStyles',

@@ -212,2 +213,3 @@ 'minifyStyles',

| [cleanupAttrs](https://github.com/svg/svgo/blob/master/plugins/cleanupAttrs.js) | cleanup attributes from newlines, trailing, and repeating spaces | `enabled` |
| [mergeStyles](https://github.com/svg/svgo/blob/master/plugins/mergeStyles.js) | merge multiple style elements into one | `enabled` |
| [inlineStyles](https://github.com/svg/svgo/blob/master/plugins/inlineStyles.js) | move and merge styles from `<style>` elements to element `style` attributes | `enabled` |

@@ -214,0 +216,0 @@ | [removeDoctype](https://github.com/svg/svgo/blob/master/plugins/removeDoctype.js) | remove `doctype` declaration | `enabled` |

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc