source: trip-planner-front/node_modules/svgo/lib/parser.js@ 188ee53

Last change on this file since 188ee53 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 6.4 KB
Line 
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
16const SAX = require('@trysound/sax');
17const JSAPI = require('./svgo/jsAPI.js');
18const { textElems } = require('../plugins/_collections.js');
19
20class 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
76const entityDeclaration = /<!ENTITY\s+(\S+)\s+(?:'([^']+)'|"([^"]+)")\s*>/g;
77
78const 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 */
92const 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};
259exports.parseSvg = parseSvg;
Note: See TracBrowser for help on using the repository browser.