Skip to content

Commit d08815c

Browse files
authored
Implement simple node clone (#1450)
#1279 faced some problems with node cloning. In this diff I moved class/style live objects to JSAPI and made it non enumerable to avoid deep cloning issues. class list and style declaration classes are not longer need own clone methods.
1 parent 3d4adb6 commit d08815c

File tree

4 files changed

+51
-122
lines changed

4 files changed

+51
-122
lines changed

lib/svgo/css-class-list.js

+7-30
Original file line numberDiff line numberDiff line change
@@ -3,34 +3,11 @@
33
var CSSClassList = function (node) {
44
this.parentNode = node;
55
this.classNames = new Set();
6-
//this.classValue = null;
7-
};
8-
9-
/**
10-
* Performs a deep clone of this object.
11-
*
12-
* @param parentNode the parentNode to assign to the cloned result
13-
*/
14-
CSSClassList.prototype.clone = function (parentNode) {
15-
var node = this;
16-
var nodeData = {};
17-
18-
Object.keys(node).forEach(function (key) {
19-
if (key !== 'parentNode') {
20-
nodeData[key] = node[key];
21-
}
22-
});
23-
24-
// Deep-clone node data.
25-
nodeData = JSON.parse(JSON.stringify(nodeData));
26-
27-
var clone = new CSSClassList(parentNode);
28-
Object.assign(clone, nodeData);
29-
return clone;
30-
};
31-
32-
CSSClassList.prototype.hasClass = function () {
33-
this.addClassValueHandler();
6+
const value = node.attributes.class;
7+
if (value != null) {
8+
this.addClassValueHandler();
9+
this.setClassValue(value);
10+
}
3411
};
3512

3613
// attr.class.value
@@ -59,7 +36,7 @@ CSSClassList.prototype.setClassValue = function (newValue) {
5936
};
6037

6138
CSSClassList.prototype.add = function (/* variadic */) {
62-
this.hasClass();
39+
this.addClassValueHandler();
6340
Object.values(arguments).forEach(this._addSingle.bind(this));
6441
};
6542

@@ -68,7 +45,7 @@ CSSClassList.prototype._addSingle = function (className) {
6845
};
6946

7047
CSSClassList.prototype.remove = function (/* variadic */) {
71-
this.hasClass();
48+
this.addClassValueHandler();
7249
Object.values(arguments).forEach(this._removeSingle.bind(this));
7350
};
7451

lib/svgo/css-style-declaration.js

+7-29
Original file line numberDiff line numberDiff line change
@@ -12,33 +12,11 @@ var CSSStyleDeclaration = function (node) {
1212
this.styleValue = null;
1313

1414
this.parseError = false;
15-
};
16-
17-
/**
18-
* Performs a deep clone of this object.
19-
*
20-
* @param parentNode the parentNode to assign to the cloned result
21-
*/
22-
CSSStyleDeclaration.prototype.clone = function (parentNode) {
23-
var node = this;
24-
var nodeData = {};
25-
26-
Object.keys(node).forEach(function (key) {
27-
if (key !== 'parentNode') {
28-
nodeData[key] = node[key];
29-
}
30-
});
31-
32-
// Deep-clone node data.
33-
nodeData = JSON.parse(JSON.stringify(nodeData));
34-
35-
var clone = new CSSStyleDeclaration(parentNode);
36-
Object.assign(clone, nodeData);
37-
return clone;
38-
};
39-
40-
CSSStyleDeclaration.prototype.hasStyle = function () {
41-
this.addStyleValueHandler();
15+
const value = node.attributes.style;
16+
if (value != null) {
17+
this.addStyleValueHandler();
18+
this.setStyleValue(value);
19+
}
4220
};
4321

4422
// attr.style.value
@@ -210,7 +188,7 @@ CSSStyleDeclaration.prototype.removeProperty = function (propertyName) {
210188
throw Error('1 argument required, but only 0 present.');
211189
}
212190

213-
this.hasStyle();
191+
this.addStyleValueHandler();
214192

215193
var properties = this.getProperties();
216194
this._handleParseError();
@@ -237,7 +215,7 @@ CSSStyleDeclaration.prototype.setProperty = function (
237215
throw Error('propertyName argument required, but only not present.');
238216
}
239217

240-
this.hasStyle();
218+
this.addStyleValueHandler();
241219

242220
var properties = this.getProperties();
243221
this._handleParseError();

lib/svgo/jsAPI.js

+22-32
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
const { selectAll, selectOne, is } = require('css-select');
44
const { parseName } = require('./tools.js');
55
const svgoCssSelectAdapter = require('./css-select-adapter');
6+
const CSSClassList = require('./css-class-list');
7+
const CSSStyleDeclaration = require('./css-style-declaration');
68

79
var cssSelectOpts = {
810
xmlMode: true,
@@ -39,6 +41,21 @@ var JSAPI = function (data, parentNode) {
3941
if (this.children == null) {
4042
this.children = [];
4143
}
44+
Object.defineProperty(this, 'class', {
45+
writable: true,
46+
configurable: true,
47+
value: new CSSClassList(this),
48+
});
49+
Object.defineProperty(this, 'style', {
50+
writable: true,
51+
configurable: true,
52+
value: new CSSStyleDeclaration(this),
53+
});
54+
Object.defineProperty(this, 'parentNode', {
55+
writable: true,
56+
value: parentNode,
57+
});
58+
4259
// temporary attrs polyfill
4360
// TODO remove after migration
4461
const element = this;
@@ -56,12 +73,6 @@ var JSAPI = function (data, parentNode) {
5673
},
5774
});
5875
}
59-
if (parentNode) {
60-
Object.defineProperty(this, 'parentNode', {
61-
writable: true,
62-
value: parentNode,
63-
});
64-
}
6576
};
6677
module.exports = JSAPI;
6778

@@ -71,37 +82,16 @@ module.exports = JSAPI;
7182
* @return {Object} element
7283
*/
7384
JSAPI.prototype.clone = function () {
74-
var node = this;
75-
var nodeData = {};
76-
77-
Object.keys(node).forEach(function (key) {
78-
if (key !== 'class' && key !== 'style' && key !== 'children') {
79-
nodeData[key] = node[key];
80-
}
81-
});
82-
85+
const { children, ...nodeData } = this;
8386
// Deep-clone node data.
84-
nodeData = JSON.parse(JSON.stringify(nodeData));
85-
86-
// parentNode gets set to a proper object by the parent clone,
87-
// but it needs to be true/false now to do the right thing
88-
// in the constructor.
89-
var clonedNode = new JSAPI(nodeData, !!node.parentNode);
90-
91-
if (node.class) {
92-
clonedNode.class = node.class.clone(clonedNode);
93-
}
94-
if (node.style) {
95-
clonedNode.style = node.style.clone(clonedNode);
96-
}
97-
if (node.children) {
98-
clonedNode.children = node.children.map(function (childNode) {
99-
var clonedChild = childNode.clone();
87+
const clonedNode = new JSAPI(JSON.parse(JSON.stringify(nodeData)), null);
88+
if (children) {
89+
clonedNode.children = children.map((child) => {
90+
const clonedChild = child.clone();
10091
clonedChild.parentNode = clonedNode;
10192
return clonedChild;
10293
});
10394
}
104-
10595
return clonedNode;
10696
};
10797

lib/svgo/svg2js.js

+15-31
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
'use strict';
22

3-
var SAX = require('@trysound/sax'),
4-
JSAPI = require('./jsAPI.js'),
5-
CSSClassList = require('./css-class-list'),
6-
CSSStyleDeclaration = require('./css-style-declaration'),
7-
textElems = require('../../plugins/_collections.js').textElems,
8-
entityDeclaration = /<!ENTITY\s+(\S+)\s+(?:'([^']+)'|"([^"]+)")\s*>/g;
9-
10-
var config = {
3+
const SAX = require('@trysound/sax');
4+
const JSAPI = require('./jsAPI.js');
5+
const { textElems } = require('../../plugins/_collections.js');
6+
7+
const entityDeclaration = /<!ENTITY\s+(\S+)\s+(?:'([^']+)'|"([^"]+)")\s*>/g;
8+
9+
const config = {
1110
strict: true,
1211
trim: false,
1312
normalize: false,
@@ -22,10 +21,10 @@ var config = {
2221
* @param {String} data input data
2322
*/
2423
module.exports = function (data) {
25-
var sax = SAX.parser(config.strict, config),
26-
root = new JSAPI({ type: 'root', children: [] }),
27-
current = root,
28-
stack = [root];
24+
const sax = SAX.parser(config.strict, config);
25+
const root = new JSAPI({ type: 'root', children: [] });
26+
let current = root;
27+
let stack = [root];
2928

3029
function pushToContent(node) {
3130
const wrapped = new JSAPI(node, current);
@@ -43,8 +42,8 @@ module.exports = function (data) {
4342
},
4443
});
4544

46-
var subsetStart = doctype.indexOf('['),
47-
entityMatch;
45+
const subsetStart = doctype.indexOf('[');
46+
let entityMatch;
4847

4948
if (subsetStart >= 0) {
5049
entityDeclaration.lastIndex = subsetStart;
@@ -85,23 +84,8 @@ module.exports = function (data) {
8584
children: [],
8685
};
8786

88-
element.class = new CSSClassList(element);
89-
element.style = new CSSStyleDeclaration(element);
90-
91-
if (Object.keys(data.attributes).length) {
92-
for (const [name, attr] of Object.entries(data.attributes)) {
93-
if (name === 'class') {
94-
// has class attribute
95-
element.class.hasClass();
96-
}
97-
98-
if (name === 'style') {
99-
// has style attribute
100-
element.style.hasStyle();
101-
}
102-
103-
element.attributes[name] = attr.value;
104-
}
87+
for (const [name, attr] of Object.entries(data.attributes)) {
88+
element.attributes[name] = attr.value;
10589
}
10690

10791
element = pushToContent(element);

0 commit comments

Comments
 (0)