svgo-v2 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +294 -0
- package/bin/svgo +10 -0
- package/dist/svgo.browser.js +1 -0
- package/lib/css-tools.js +239 -0
- package/lib/parser.js +259 -0
- package/lib/path.js +347 -0
- package/lib/stringifier.js +326 -0
- package/lib/style.js +283 -0
- package/lib/svgo/coa.js +517 -0
- package/lib/svgo/config.js +138 -0
- package/lib/svgo/css-class-list.js +72 -0
- package/lib/svgo/css-select-adapter.d.ts +2 -0
- package/lib/svgo/css-select-adapter.js +120 -0
- package/lib/svgo/css-style-declaration.js +232 -0
- package/lib/svgo/jsAPI.d.ts +2 -0
- package/lib/svgo/jsAPI.js +443 -0
- package/lib/svgo/plugins.js +109 -0
- package/lib/svgo/tools.js +137 -0
- package/lib/svgo-node.js +106 -0
- package/lib/svgo.js +83 -0
- package/lib/types.ts +172 -0
- package/lib/xast.js +102 -0
- package/package.json +130 -0
- package/plugins/_applyTransforms.js +335 -0
- package/plugins/_collections.js +2168 -0
- package/plugins/_path.js +816 -0
- package/plugins/_transforms.js +379 -0
- package/plugins/addAttributesToSVGElement.js +87 -0
- package/plugins/addClassesToSVGElement.js +87 -0
- package/plugins/cleanupAttrs.js +55 -0
- package/plugins/cleanupEnableBackground.js +75 -0
- package/plugins/cleanupIDs.js +297 -0
- package/plugins/cleanupListOfValues.js +154 -0
- package/plugins/cleanupNumericValues.js +113 -0
- package/plugins/collapseGroups.js +135 -0
- package/plugins/convertColors.js +152 -0
- package/plugins/convertEllipseToCircle.js +39 -0
- package/plugins/convertPathData.js +1023 -0
- package/plugins/convertShapeToPath.js +175 -0
- package/plugins/convertStyleToAttrs.js +132 -0
- package/plugins/convertTransform.js +432 -0
- package/plugins/inlineStyles.js +379 -0
- package/plugins/mergePaths.js +104 -0
- package/plugins/mergeStyles.js +93 -0
- package/plugins/minifyStyles.js +148 -0
- package/plugins/moveElemsAttrsToGroup.js +130 -0
- package/plugins/moveGroupAttrsToElems.js +62 -0
- package/plugins/plugins.js +56 -0
- package/plugins/prefixIds.js +241 -0
- package/plugins/preset-default.js +80 -0
- package/plugins/removeAttributesBySelector.js +99 -0
- package/plugins/removeAttrs.js +159 -0
- package/plugins/removeComments.js +31 -0
- package/plugins/removeDesc.js +41 -0
- package/plugins/removeDimensions.js +43 -0
- package/plugins/removeDoctype.js +42 -0
- package/plugins/removeEditorsNSData.js +68 -0
- package/plugins/removeElementsByAttr.js +78 -0
- package/plugins/removeEmptyAttrs.js +33 -0
- package/plugins/removeEmptyContainers.js +58 -0
- package/plugins/removeEmptyText.js +57 -0
- package/plugins/removeHiddenElems.js +318 -0
- package/plugins/removeMetadata.js +29 -0
- package/plugins/removeNonInheritableGroupAttrs.js +38 -0
- package/plugins/removeOffCanvasPaths.js +138 -0
- package/plugins/removeRasterImages.js +33 -0
- package/plugins/removeScriptElement.js +29 -0
- package/plugins/removeStyleElement.js +29 -0
- package/plugins/removeTitle.js +29 -0
- package/plugins/removeUnknownsAndDefaults.js +218 -0
- package/plugins/removeUnusedNS.js +61 -0
- package/plugins/removeUselessDefs.js +65 -0
- package/plugins/removeUselessStrokeAndFill.js +144 -0
- package/plugins/removeViewBox.js +51 -0
- package/plugins/removeXMLNS.js +30 -0
- package/plugins/removeXMLProcInst.js +30 -0
- package/plugins/reusePaths.js +113 -0
- package/plugins/sortAttrs.js +113 -0
- package/plugins/sortDefsChildren.js +60 -0
package/lib/css-tools.js
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var csstree = require('css-tree-v2'),
|
|
4
|
+
List = csstree.List,
|
|
5
|
+
stable = require('stable'),
|
|
6
|
+
specificity = require('csso/lib/restructure/prepare/specificity');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Flatten a CSS AST to a selectors list.
|
|
10
|
+
*
|
|
11
|
+
* @param {import('css-tree-v2').CssNode} cssAst css-tree-v2 AST to flatten
|
|
12
|
+
* @return {Array} selectors
|
|
13
|
+
*/
|
|
14
|
+
function flattenToSelectors(cssAst) {
|
|
15
|
+
var selectors = [];
|
|
16
|
+
|
|
17
|
+
csstree.walk(cssAst, {
|
|
18
|
+
visit: 'Rule',
|
|
19
|
+
enter: function (node) {
|
|
20
|
+
if (node.type !== 'Rule') {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
var atrule = this.atrule;
|
|
25
|
+
var rule = node;
|
|
26
|
+
|
|
27
|
+
node.prelude.children.each(function (selectorNode, selectorItem) {
|
|
28
|
+
var selector = {
|
|
29
|
+
item: selectorItem,
|
|
30
|
+
atrule: atrule,
|
|
31
|
+
rule: rule,
|
|
32
|
+
pseudos: /** @type {{item: any; list: any[]}[]} */ ([]),
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
selectorNode.children.each(function (
|
|
36
|
+
selectorChildNode,
|
|
37
|
+
selectorChildItem,
|
|
38
|
+
selectorChildList
|
|
39
|
+
) {
|
|
40
|
+
if (
|
|
41
|
+
selectorChildNode.type === 'PseudoClassSelector' ||
|
|
42
|
+
selectorChildNode.type === 'PseudoElementSelector'
|
|
43
|
+
) {
|
|
44
|
+
selector.pseudos.push({
|
|
45
|
+
item: selectorChildItem,
|
|
46
|
+
list: selectorChildList,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
selectors.push(selector);
|
|
52
|
+
});
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
return selectors;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Filter selectors by Media Query.
|
|
61
|
+
*
|
|
62
|
+
* @param {Array} selectors to filter
|
|
63
|
+
* @param {Array} useMqs Array with strings of media queries that should pass (<name> <expression>)
|
|
64
|
+
* @return {Array} Filtered selectors that match the passed media queries
|
|
65
|
+
*/
|
|
66
|
+
function filterByMqs(selectors, useMqs) {
|
|
67
|
+
return selectors.filter(function (selector) {
|
|
68
|
+
if (selector.atrule === null) {
|
|
69
|
+
return ~useMqs.indexOf('');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
var mqName = selector.atrule.name;
|
|
73
|
+
var mqStr = mqName;
|
|
74
|
+
if (
|
|
75
|
+
selector.atrule.expression &&
|
|
76
|
+
selector.atrule.expression.children.first().type === 'MediaQueryList'
|
|
77
|
+
) {
|
|
78
|
+
var mqExpr = csstree.generate(selector.atrule.expression);
|
|
79
|
+
mqStr = [mqName, mqExpr].join(' ');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return ~useMqs.indexOf(mqStr);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Filter selectors by the pseudo-elements and/or -classes they contain.
|
|
88
|
+
*
|
|
89
|
+
* @param {Array} selectors to filter
|
|
90
|
+
* @param {Array} usePseudos Array with strings of single or sequence of pseudo-elements and/or -classes that should pass
|
|
91
|
+
* @return {Array} Filtered selectors that match the passed pseudo-elements and/or -classes
|
|
92
|
+
*/
|
|
93
|
+
function filterByPseudos(selectors, usePseudos) {
|
|
94
|
+
return selectors.filter(function (selector) {
|
|
95
|
+
var pseudoSelectorsStr = csstree.generate({
|
|
96
|
+
type: 'Selector',
|
|
97
|
+
children: new List().fromArray(
|
|
98
|
+
selector.pseudos.map(function (pseudo) {
|
|
99
|
+
return pseudo.item.data;
|
|
100
|
+
})
|
|
101
|
+
),
|
|
102
|
+
});
|
|
103
|
+
return ~usePseudos.indexOf(pseudoSelectorsStr);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Remove pseudo-elements and/or -classes from the selectors for proper matching.
|
|
109
|
+
*
|
|
110
|
+
* @param {Array} selectors to clean
|
|
111
|
+
* @return {void}
|
|
112
|
+
*/
|
|
113
|
+
function cleanPseudos(selectors) {
|
|
114
|
+
selectors.forEach(function (selector) {
|
|
115
|
+
selector.pseudos.forEach(function (pseudo) {
|
|
116
|
+
pseudo.list.remove(pseudo.item);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Compares two selector specificities.
|
|
123
|
+
* extracted from https://github.com/keeganstreet/specificity/blob/master/specificity.js#L211
|
|
124
|
+
*
|
|
125
|
+
* @param {Array} aSpecificity Specificity of selector A
|
|
126
|
+
* @param {Array} bSpecificity Specificity of selector B
|
|
127
|
+
* @return {number} Score of selector specificity A compared to selector specificity B
|
|
128
|
+
*/
|
|
129
|
+
function compareSpecificity(aSpecificity, bSpecificity) {
|
|
130
|
+
for (var i = 0; i < 4; i += 1) {
|
|
131
|
+
if (aSpecificity[i] < bSpecificity[i]) {
|
|
132
|
+
return -1;
|
|
133
|
+
} else if (aSpecificity[i] > bSpecificity[i]) {
|
|
134
|
+
return 1;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return 0;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Compare two simple selectors.
|
|
143
|
+
*
|
|
144
|
+
* @param {Object} aSimpleSelectorNode Simple selector A
|
|
145
|
+
* @param {Object} bSimpleSelectorNode Simple selector B
|
|
146
|
+
* @return {number} Score of selector A compared to selector B
|
|
147
|
+
*/
|
|
148
|
+
function compareSimpleSelectorNode(aSimpleSelectorNode, bSimpleSelectorNode) {
|
|
149
|
+
var aSpecificity = specificity(aSimpleSelectorNode),
|
|
150
|
+
bSpecificity = specificity(bSimpleSelectorNode);
|
|
151
|
+
return compareSpecificity(aSpecificity, bSpecificity);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function _bySelectorSpecificity(selectorA, selectorB) {
|
|
155
|
+
return compareSimpleSelectorNode(selectorA.item.data, selectorB.item.data);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Sort selectors stably by their specificity.
|
|
160
|
+
*
|
|
161
|
+
* @param {Array} selectors to be sorted
|
|
162
|
+
* @return {Array} Stable sorted selectors
|
|
163
|
+
*/
|
|
164
|
+
function sortSelectors(selectors) {
|
|
165
|
+
return stable(selectors, _bySelectorSpecificity);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Convert a css-tree-v2 AST style declaration to CSSStyleDeclaration property.
|
|
170
|
+
*
|
|
171
|
+
* @param {import('css-tree-v2').CssNode} declaration css-tree-v2 style declaration
|
|
172
|
+
* @return {Object} CSSStyleDeclaration property
|
|
173
|
+
*/
|
|
174
|
+
function csstreeToStyleDeclaration(declaration) {
|
|
175
|
+
var propertyName = declaration.property,
|
|
176
|
+
propertyValue = csstree.generate(declaration.value),
|
|
177
|
+
propertyPriority = declaration.important ? 'important' : '';
|
|
178
|
+
return {
|
|
179
|
+
name: propertyName,
|
|
180
|
+
value: propertyValue,
|
|
181
|
+
priority: propertyPriority,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Gets the CSS string of a style element
|
|
187
|
+
*
|
|
188
|
+
* @param {Object} elem style element
|
|
189
|
+
* @return {string} CSS string or empty array if no styles are set
|
|
190
|
+
*/
|
|
191
|
+
function getCssStr(elem) {
|
|
192
|
+
if (
|
|
193
|
+
elem.children.length > 0 &&
|
|
194
|
+
(elem.children[0].type === 'text' || elem.children[0].type === 'cdata')
|
|
195
|
+
) {
|
|
196
|
+
return elem.children[0].value;
|
|
197
|
+
}
|
|
198
|
+
return '';
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Sets the CSS string of a style element
|
|
203
|
+
*
|
|
204
|
+
* @param {Object} elem style element
|
|
205
|
+
* @param {string} css string to be set
|
|
206
|
+
* @return {string} reference to field with CSS
|
|
207
|
+
*/
|
|
208
|
+
function setCssStr(elem, css) {
|
|
209
|
+
if (elem.children.length === 0) {
|
|
210
|
+
elem.children.push({
|
|
211
|
+
type: 'text',
|
|
212
|
+
value: '',
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (elem.children[0].type !== 'text' && elem.children[0].type !== 'cdata') {
|
|
217
|
+
return css;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
elem.children[0].value = css;
|
|
221
|
+
|
|
222
|
+
return css;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
module.exports.flattenToSelectors = flattenToSelectors;
|
|
226
|
+
|
|
227
|
+
module.exports.filterByMqs = filterByMqs;
|
|
228
|
+
module.exports.filterByPseudos = filterByPseudos;
|
|
229
|
+
module.exports.cleanPseudos = cleanPseudos;
|
|
230
|
+
|
|
231
|
+
module.exports.compareSpecificity = compareSpecificity;
|
|
232
|
+
module.exports.compareSimpleSelectorNode = compareSimpleSelectorNode;
|
|
233
|
+
|
|
234
|
+
module.exports.sortSelectors = sortSelectors;
|
|
235
|
+
|
|
236
|
+
module.exports.csstreeToStyleDeclaration = csstreeToStyleDeclaration;
|
|
237
|
+
|
|
238
|
+
module.exports.getCssStr = getCssStr;
|
|
239
|
+
module.exports.setCssStr = setCssStr;
|
package/lib/parser.js
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import('./types').XastNode} XastNode
|
|
5
|
+
* @typedef {import('./types').XastInstruction} XastInstruction
|
|
6
|
+
* @typedef {import('./types').XastDoctype} XastDoctype
|
|
7
|
+
* @typedef {import('./types').XastComment} XastComment
|
|
8
|
+
* @typedef {import('./types').XastRoot} XastRoot
|
|
9
|
+
* @typedef {import('./types').XastElement} XastElement
|
|
10
|
+
* @typedef {import('./types').XastCdata} XastCdata
|
|
11
|
+
* @typedef {import('./types').XastText} XastText
|
|
12
|
+
* @typedef {import('./types').XastParent} XastParent
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
// @ts-ignore sax will be replaced with something else later
|
|
16
|
+
const SAX = require('@trysound/sax');
|
|
17
|
+
const JSAPI = require('./svgo/jsAPI.js');
|
|
18
|
+
const { textElems } = require('../plugins/_collections.js');
|
|
19
|
+
|
|
20
|
+
class SvgoParserError extends Error {
|
|
21
|
+
/**
|
|
22
|
+
* @param message {string}
|
|
23
|
+
* @param line {number}
|
|
24
|
+
* @param column {number}
|
|
25
|
+
* @param source {string}
|
|
26
|
+
* @param file {void | string}
|
|
27
|
+
*/
|
|
28
|
+
constructor(message, line, column, source, file) {
|
|
29
|
+
super(message);
|
|
30
|
+
this.name = 'SvgoParserError';
|
|
31
|
+
this.message = `${file || '<input>'}:${line}:${column}: ${message}`;
|
|
32
|
+
this.reason = message;
|
|
33
|
+
this.line = line;
|
|
34
|
+
this.column = column;
|
|
35
|
+
this.source = source;
|
|
36
|
+
if (Error.captureStackTrace) {
|
|
37
|
+
Error.captureStackTrace(this, SvgoParserError);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
toString() {
|
|
41
|
+
const lines = this.source.split(/\r?\n/);
|
|
42
|
+
const startLine = Math.max(this.line - 3, 0);
|
|
43
|
+
const endLine = Math.min(this.line + 2, lines.length);
|
|
44
|
+
const lineNumberWidth = String(endLine).length;
|
|
45
|
+
const startColumn = Math.max(this.column - 54, 0);
|
|
46
|
+
const endColumn = Math.max(this.column + 20, 80);
|
|
47
|
+
const code = lines
|
|
48
|
+
.slice(startLine, endLine)
|
|
49
|
+
.map((line, index) => {
|
|
50
|
+
const lineSlice = line.slice(startColumn, endColumn);
|
|
51
|
+
let ellipsisPrefix = '';
|
|
52
|
+
let ellipsisSuffix = '';
|
|
53
|
+
if (startColumn !== 0) {
|
|
54
|
+
ellipsisPrefix = startColumn > line.length - 1 ? ' ' : '…';
|
|
55
|
+
}
|
|
56
|
+
if (endColumn < line.length - 1) {
|
|
57
|
+
ellipsisSuffix = '…';
|
|
58
|
+
}
|
|
59
|
+
const number = startLine + 1 + index;
|
|
60
|
+
const gutter = ` ${number.toString().padStart(lineNumberWidth)} | `;
|
|
61
|
+
if (number === this.line) {
|
|
62
|
+
const gutterSpacing = gutter.replace(/[^|]/g, ' ');
|
|
63
|
+
const lineSpacing = (
|
|
64
|
+
ellipsisPrefix + line.slice(startColumn, this.column - 1)
|
|
65
|
+
).replace(/[^\t]/g, ' ');
|
|
66
|
+
const spacing = gutterSpacing + lineSpacing;
|
|
67
|
+
return `>${gutter}${ellipsisPrefix}${lineSlice}${ellipsisSuffix}\n ${spacing}^`;
|
|
68
|
+
}
|
|
69
|
+
return ` ${gutter}${ellipsisPrefix}${lineSlice}${ellipsisSuffix}`;
|
|
70
|
+
})
|
|
71
|
+
.join('\n');
|
|
72
|
+
return `${this.name}: ${this.message}\n\n${code}\n`;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const entityDeclaration = /<!ENTITY\s+(\S+)\s+(?:'([^']+)'|"([^"]+)")\s*>/g;
|
|
77
|
+
|
|
78
|
+
const config = {
|
|
79
|
+
strict: true,
|
|
80
|
+
trim: false,
|
|
81
|
+
normalize: false,
|
|
82
|
+
lowercase: true,
|
|
83
|
+
xmlns: true,
|
|
84
|
+
position: true,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Convert SVG (XML) string to SVG-as-JS object.
|
|
89
|
+
*
|
|
90
|
+
* @type {(data: string, from?: string) => XastRoot}
|
|
91
|
+
*/
|
|
92
|
+
const parseSvg = (data, from) => {
|
|
93
|
+
const sax = SAX.parser(config.strict, config);
|
|
94
|
+
/**
|
|
95
|
+
* @type {XastRoot}
|
|
96
|
+
*/
|
|
97
|
+
const root = new JSAPI({ type: 'root', children: [] });
|
|
98
|
+
/**
|
|
99
|
+
* @type {XastParent}
|
|
100
|
+
*/
|
|
101
|
+
let current = root;
|
|
102
|
+
/**
|
|
103
|
+
* @type {Array<XastParent>}
|
|
104
|
+
*/
|
|
105
|
+
const stack = [root];
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* @type {<T extends XastNode>(node: T) => T}
|
|
109
|
+
*/
|
|
110
|
+
const pushToContent = (node) => {
|
|
111
|
+
const wrapped = new JSAPI(node, current);
|
|
112
|
+
current.children.push(wrapped);
|
|
113
|
+
return wrapped;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @type {(doctype: string) => void}
|
|
118
|
+
*/
|
|
119
|
+
sax.ondoctype = (doctype) => {
|
|
120
|
+
/**
|
|
121
|
+
* @type {XastDoctype}
|
|
122
|
+
*/
|
|
123
|
+
const node = {
|
|
124
|
+
type: 'doctype',
|
|
125
|
+
// TODO parse doctype for name, public and system to match xast
|
|
126
|
+
name: 'svg',
|
|
127
|
+
data: {
|
|
128
|
+
doctype,
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
pushToContent(node);
|
|
132
|
+
const subsetStart = doctype.indexOf('[');
|
|
133
|
+
if (subsetStart >= 0) {
|
|
134
|
+
entityDeclaration.lastIndex = subsetStart;
|
|
135
|
+
let entityMatch = entityDeclaration.exec(data);
|
|
136
|
+
while (entityMatch != null) {
|
|
137
|
+
sax.ENTITIES[entityMatch[1]] = entityMatch[2] || entityMatch[3];
|
|
138
|
+
entityMatch = entityDeclaration.exec(data);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* @type {(data: { name: string, body: string }) => void}
|
|
145
|
+
*/
|
|
146
|
+
sax.onprocessinginstruction = (data) => {
|
|
147
|
+
/**
|
|
148
|
+
* @type {XastInstruction}
|
|
149
|
+
*/
|
|
150
|
+
const node = {
|
|
151
|
+
type: 'instruction',
|
|
152
|
+
name: data.name,
|
|
153
|
+
value: data.body,
|
|
154
|
+
};
|
|
155
|
+
pushToContent(node);
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* @type {(comment: string) => void}
|
|
160
|
+
*/
|
|
161
|
+
sax.oncomment = (comment) => {
|
|
162
|
+
/**
|
|
163
|
+
* @type {XastComment}
|
|
164
|
+
*/
|
|
165
|
+
const node = {
|
|
166
|
+
type: 'comment',
|
|
167
|
+
value: comment.trim(),
|
|
168
|
+
};
|
|
169
|
+
pushToContent(node);
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* @type {(cdata: string) => void}
|
|
174
|
+
*/
|
|
175
|
+
sax.oncdata = (cdata) => {
|
|
176
|
+
/**
|
|
177
|
+
* @type {XastCdata}
|
|
178
|
+
*/
|
|
179
|
+
const node = {
|
|
180
|
+
type: 'cdata',
|
|
181
|
+
value: cdata,
|
|
182
|
+
};
|
|
183
|
+
pushToContent(node);
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* @type {(data: { name: string, attributes: Record<string, { value: string }>}) => void}
|
|
188
|
+
*/
|
|
189
|
+
sax.onopentag = (data) => {
|
|
190
|
+
/**
|
|
191
|
+
* @type {XastElement}
|
|
192
|
+
*/
|
|
193
|
+
let element = {
|
|
194
|
+
type: 'element',
|
|
195
|
+
name: data.name,
|
|
196
|
+
attributes: {},
|
|
197
|
+
children: [],
|
|
198
|
+
};
|
|
199
|
+
for (const [name, attr] of Object.entries(data.attributes)) {
|
|
200
|
+
element.attributes[name] = attr.value;
|
|
201
|
+
}
|
|
202
|
+
element = pushToContent(element);
|
|
203
|
+
current = element;
|
|
204
|
+
stack.push(element);
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* @type {(text: string) => void}
|
|
209
|
+
*/
|
|
210
|
+
sax.ontext = (text) => {
|
|
211
|
+
if (current.type === 'element') {
|
|
212
|
+
// prevent trimming of meaningful whitespace inside textual tags
|
|
213
|
+
if (textElems.includes(current.name)) {
|
|
214
|
+
/**
|
|
215
|
+
* @type {XastText}
|
|
216
|
+
*/
|
|
217
|
+
const node = {
|
|
218
|
+
type: 'text',
|
|
219
|
+
value: text,
|
|
220
|
+
};
|
|
221
|
+
pushToContent(node);
|
|
222
|
+
} else if (/\S/.test(text)) {
|
|
223
|
+
/**
|
|
224
|
+
* @type {XastText}
|
|
225
|
+
*/
|
|
226
|
+
const node = {
|
|
227
|
+
type: 'text',
|
|
228
|
+
value: text.trim(),
|
|
229
|
+
};
|
|
230
|
+
pushToContent(node);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
sax.onclosetag = () => {
|
|
236
|
+
stack.pop();
|
|
237
|
+
current = stack[stack.length - 1];
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* @type {(e: any) => void}
|
|
242
|
+
*/
|
|
243
|
+
sax.onerror = (e) => {
|
|
244
|
+
const error = new SvgoParserError(
|
|
245
|
+
e.reason,
|
|
246
|
+
e.line + 1,
|
|
247
|
+
e.column,
|
|
248
|
+
data,
|
|
249
|
+
from
|
|
250
|
+
);
|
|
251
|
+
if (e.message.indexOf('Unexpected end') === -1) {
|
|
252
|
+
throw error;
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
sax.write(data).close();
|
|
257
|
+
return root;
|
|
258
|
+
};
|
|
259
|
+
exports.parseSvg = parseSvg;
|