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

Convert attributes to xast with proxy fallback

There is a lot of attributes manipulation which is hard to remove at
once. In this diff I added `attributes` object and wrapped it as proxy
for `attrs` field.
This commit is contained in:
Bogdan Chadkin
2021-03-10 23:11:09 +03:00
parent 685d9fa55e
commit 3e1bfd528d
6 changed files with 74 additions and 94 deletions

View File

@ -81,12 +81,10 @@ const computeOwnStyle = (node, stylesheet) => {
const importantStyles = new Map(); const importantStyles = new Map();
// collect attributes // collect attributes
if (node.attrs) { for (const [name, value] of Object.entries(node.attributes)) {
for (const { name, value } of Object.values(node.attrs)) { if (attrsGroups.presentation.includes(name)) {
if (attrsGroups.presentation.includes(name)) { computedStyle[name] = { type: 'static', inherited: false, value };
computedStyle[name] = { type: 'static', inherited: false, value }; importantStyles.set(name, false);
importantStyles.set(name, false);
}
} }
} }
@ -146,11 +144,12 @@ const computeStyle = (node) => {
const stylesheet = []; const stylesheet = [];
for (const styleNode of styleNodes) { for (const styleNode of styleNodes) {
const dynamic = const dynamic =
styleNode.hasAttr('media') && styleNode.attr('media').value !== 'all'; styleNode.attributes.media != null &&
styleNode.attributes.media !== 'all';
if ( if (
styleNode.hasAttr('type') === false || styleNode.attributes.type == null ||
styleNode.attr('type').value === '' || styleNode.attributes.type === '' ||
styleNode.attr('type').value === 'text/css' styleNode.attributes.type === 'text/css'
) { ) {
const children = styleNode.content || []; const children = styleNode.content || [];
for (const child of children) { for (const child of children) {

View File

@ -3,7 +3,6 @@
var CSSClassList = function (node) { var CSSClassList = function (node) {
this.parentNode = node; this.parentNode = node;
this.classNames = new Set(); this.classNames = new Set();
this.classAttr = null;
//this.classValue = null; //this.classValue = null;
}; };
@ -31,32 +30,13 @@ CSSClassList.prototype.clone = function (parentNode) {
}; };
CSSClassList.prototype.hasClass = function () { 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(); this.addClassValueHandler();
}; };
// attr.class.value // attr.class.value
CSSClassList.prototype.addClassValueHandler = function () { CSSClassList.prototype.addClassValueHandler = function () {
Object.defineProperty(this.classAttr, 'value', { Object.defineProperty(this.parentNode.attributes, 'class', {
get: this.getClassValue.bind(this), get: this.getClassValue.bind(this),
set: this.setClassValue.bind(this), set: this.setClassValue.bind(this),
enumerable: true, enumerable: true,
@ -64,17 +44,6 @@ CSSClassList.prototype.addClassValueHandler = function () {
}); });
}; };
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 () { CSSClassList.prototype.getClassValue = function () {
var arrClassNames = Array.from(this.classNames); var arrClassNames = Array.from(this.classNames);
return arrClassNames.join(' '); return arrClassNames.join(' ');

View File

@ -9,7 +9,6 @@ var CSSStyleDeclaration = function (node) {
this.properties = new Map(); this.properties = new Map();
this.hasSynced = false; this.hasSynced = false;
this.styleAttr = null;
this.styleValue = null; this.styleValue = null;
this.parseError = false; this.parseError = false;
@ -39,32 +38,13 @@ CSSStyleDeclaration.prototype.clone = function (parentNode) {
}; };
CSSStyleDeclaration.prototype.hasStyle = function () { 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(); this.addStyleValueHandler();
}; };
// attr.style.value // attr.style.value
CSSStyleDeclaration.prototype.addStyleValueHandler = function () { CSSStyleDeclaration.prototype.addStyleValueHandler = function () {
Object.defineProperty(this.styleAttr, 'value', { Object.defineProperty(this.parentNode.attributes, 'style', {
get: this.getStyleValue.bind(this), get: this.getStyleValue.bind(this),
set: this.setStyleValue.bind(this), set: this.setStyleValue.bind(this),
enumerable: true, enumerable: true,
@ -72,18 +52,6 @@ CSSStyleDeclaration.prototype.addStyleValueHandler = function () {
}); });
}; };
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 () { CSSStyleDeclaration.prototype.getStyleValue = function () {
return this.getCssText(); return this.getCssText();
}; };

View File

@ -78,10 +78,55 @@ module.exports = function (data) {
}; };
sax.onopentag = function (data) { sax.onopentag = function (data) {
const attrsHandler = {
get: (attributes, name) => {
if (attributes.hasOwnProperty(name)) {
return new Proxy(
{
name,
value: attributes[name],
},
{
get: (target, property) => {
if (property === 'name') {
return name;
}
if (property === 'value') {
return attributes[name];
}
},
set: (target, property, value) => {
if (property === 'value') {
attributes[name] = value;
return true;
}
},
}
);
}
},
set: (attributes, name, attr) => {
attributes[name] = attr.value;
return true;
},
};
var element = { var element = {
type: 'element', type: 'element',
name: data.name, name: data.name,
attrs: {}, attributes: {},
// temporary attrs polyfill
// TODO remove after migration
get attrs() {
return new Proxy(element.attributes, attrsHandler);
},
set attrs(value) {
const newAttributes = {};
for (const attr of Object.values(value)) {
newAttributes[attr.name] = attr.value;
}
element.attributes = newAttributes;
},
content: [], content: [],
}; };
@ -100,10 +145,7 @@ module.exports = function (data) {
element.style.hasStyle(); element.style.hasStyle();
} }
element.attrs[name] = { element.attributes[name] = attr.value;
name: name,
value: attr.value,
};
} }
} }

View File

@ -43,9 +43,12 @@ exports.fn = function (item) {
// don't mess with possible styles (hack until CSS parsing is implemented) // don't mess with possible styles (hack until CSS parsing is implemented)
if (inner.hasAttr('class')) return false; if (inner.hasAttr('class')) return false;
if (!Object.keys(intersection).length) { if (!Object.keys(intersection).length) {
intersection = inner.attrs; intersection = inner.attributes;
} else { } else {
intersection = intersectInheritableAttrs(intersection, inner.attrs); intersection = intersectInheritableAttrs(
intersection,
inner.attributes
);
if (!intersection) return false; if (!intersection) return false;
} }
@ -59,22 +62,22 @@ exports.fn = function (item) {
if (intersected) { if (intersected) {
item.content.forEach(function (g) { item.content.forEach(function (g) {
for (const [name, attr] of Object.entries(intersection)) { for (const [name, value] of Object.entries(intersection)) {
if ((!allPath && !hasClip) || name !== 'transform') { if ((!allPath && !hasClip) || name !== 'transform') {
g.removeAttr(name); g.removeAttr(name);
if (name === 'transform') { if (name === 'transform') {
if (!hasTransform) { if (!hasTransform) {
if (item.hasAttr('transform')) { if (item.hasAttr('transform')) {
item.attr('transform').value += ' ' + attr.value; item.attr('transform').value += ' ' + value;
} else { } else {
item.addAttr(attr); item.addAttr({ name, value });
} }
hasTransform = true; hasTransform = true;
} }
} else { } else {
item.addAttr(attr); item.addAttr({ name, value });
} }
} }
} }
@ -94,15 +97,14 @@ exports.fn = function (item) {
function intersectInheritableAttrs(a, b) { function intersectInheritableAttrs(a, b) {
var c = {}; var c = {};
for (const [n, attr] of Object.entries(a)) { for (const [name, value] of Object.entries(a)) {
if ( if (
// eslint-disable-next-line no-prototype-builtins // eslint-disable-next-line no-prototype-builtins
b.hasOwnProperty(n) && b.hasOwnProperty(name) &&
inheritableAttrs.indexOf(n) > -1 && inheritableAttrs.includes(name) &&
attr.name === b[n].name && value === b[name]
attr.value === b[n].value
) { ) {
c[n] = attr; c[name] = value;
} }
} }

View File

@ -324,11 +324,11 @@ describe('svg2js', function () {
it('svg.content[0].eachAttr(function() {}) should be true', function () { it('svg.content[0].eachAttr(function() {}) should be true', function () {
expect( expect(
root.content[3].content[0].eachAttr(function (attr) { root.content[3].content[0].eachAttr(function (attr) {
attr.test = 1; attr.value = '1';
}) })
).to.be.true; ).to.be.true;
expect(root.content[3].content[0].attr('type').test).to.equal(1); expect(root.content[3].content[0].attr('type').value).to.equal('1');
}); });
it('svg.content[1].eachAttr(function() {}) should be false', function () { it('svg.content[1].eachAttr(function() {}) should be false', function () {