source: trip-planner-front/node_modules/svgo/lib/stringifier.js@ 6fe77af

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

primeNG components

  • Property mode set to 100644
File size: 7.8 KB
Line 
1'use strict';
2
3/**
4 * @typedef {import('./types').XastParent} XastParent
5 * @typedef {import('./types').XastRoot} XastRoot
6 * @typedef {import('./types').XastElement} XastElement
7 * @typedef {import('./types').XastInstruction} XastInstruction
8 * @typedef {import('./types').XastDoctype} XastDoctype
9 * @typedef {import('./types').XastText} XastText
10 * @typedef {import('./types').XastCdata} XastCdata
11 * @typedef {import('./types').XastComment} XastComment
12 * @typedef {import('./types').StringifyOptions} StringifyOptions
13 */
14
15const { textElems } = require('../plugins/_collections.js');
16
17/**
18 * @typedef {{
19 * width: void | string,
20 * height: void | string,
21 * indent: string,
22 * textContext: null | XastElement,
23 * indentLevel: number,
24 * }} State
25 */
26
27/**
28 * @typedef {Required<StringifyOptions>} Options
29 */
30
31/**
32 * @type {(char: string) => string}
33 */
34const encodeEntity = (char) => {
35 return entities[char];
36};
37
38/**
39 * @type {Options}
40 */
41const defaults = {
42 doctypeStart: '<!DOCTYPE',
43 doctypeEnd: '>',
44 procInstStart: '<?',
45 procInstEnd: '?>',
46 tagOpenStart: '<',
47 tagOpenEnd: '>',
48 tagCloseStart: '</',
49 tagCloseEnd: '>',
50 tagShortStart: '<',
51 tagShortEnd: '/>',
52 attrStart: '="',
53 attrEnd: '"',
54 commentStart: '<!--',
55 commentEnd: '-->',
56 cdataStart: '<![CDATA[',
57 cdataEnd: ']]>',
58 textStart: '',
59 textEnd: '',
60 indent: 4,
61 regEntities: /[&'"<>]/g,
62 regValEntities: /[&"<>]/g,
63 encodeEntity: encodeEntity,
64 pretty: false,
65 useShortTags: true,
66 eol: 'lf',
67 finalNewline: false,
68};
69
70/**
71 * @type {Record<string, string>}
72 */
73const entities = {
74 '&': '&amp;',
75 "'": '&apos;',
76 '"': '&quot;',
77 '>': '&gt;',
78 '<': '&lt;',
79};
80
81/**
82 * convert XAST to SVG string
83 *
84 * @type {(data: XastRoot, config: StringifyOptions) => {
85 * data: string,
86 * info: {
87 * width: void | string,
88 * height: void | string
89 * }
90 * }}
91 */
92const stringifySvg = (data, userOptions = {}) => {
93 /**
94 * @type {Options}
95 */
96 const config = { ...defaults, ...userOptions };
97 const indent = config.indent;
98 let newIndent = ' ';
99 if (typeof indent === 'number' && Number.isNaN(indent) === false) {
100 newIndent = indent < 0 ? '\t' : ' '.repeat(indent);
101 } else if (typeof indent === 'string') {
102 newIndent = indent;
103 }
104 /**
105 * @type {State}
106 */
107 const state = {
108 // TODO remove width and height in v3
109 width: undefined,
110 height: undefined,
111 indent: newIndent,
112 textContext: null,
113 indentLevel: 0,
114 };
115 const eol = config.eol === 'crlf' ? '\r\n' : '\n';
116 if (config.pretty) {
117 config.doctypeEnd += eol;
118 config.procInstEnd += eol;
119 config.commentEnd += eol;
120 config.cdataEnd += eol;
121 config.tagShortEnd += eol;
122 config.tagOpenEnd += eol;
123 config.tagCloseEnd += eol;
124 config.textEnd += eol;
125 }
126 let svg = stringifyNode(data, config, state);
127 if (config.finalNewline && svg.length > 0 && svg[svg.length - 1] !== '\n') {
128 svg += eol;
129 }
130 return {
131 data: svg,
132 info: {
133 width: state.width,
134 height: state.height,
135 },
136 };
137};
138exports.stringifySvg = stringifySvg;
139
140/**
141 * @type {(node: XastParent, config: Options, state: State) => string}
142 */
143const stringifyNode = (data, config, state) => {
144 let svg = '';
145 state.indentLevel += 1;
146 for (const item of data.children) {
147 if (item.type === 'element') {
148 svg += stringifyElement(item, config, state);
149 }
150 if (item.type === 'text') {
151 svg += stringifyText(item, config, state);
152 }
153 if (item.type === 'doctype') {
154 svg += stringifyDoctype(item, config);
155 }
156 if (item.type === 'instruction') {
157 svg += stringifyInstruction(item, config);
158 }
159 if (item.type === 'comment') {
160 svg += stringifyComment(item, config);
161 }
162 if (item.type === 'cdata') {
163 svg += stringifyCdata(item, config, state);
164 }
165 }
166 state.indentLevel -= 1;
167 return svg;
168};
169
170/**
171 * create indent string in accordance with the current node level.
172 *
173 * @type {(config: Options, state: State) => string}
174 */
175const createIndent = (config, state) => {
176 let indent = '';
177 if (config.pretty && state.textContext == null) {
178 indent = state.indent.repeat(state.indentLevel - 1);
179 }
180 return indent;
181};
182
183/**
184 * @type {(node: XastDoctype, config: Options) => string}
185 */
186const stringifyDoctype = (node, config) => {
187 return config.doctypeStart + node.data.doctype + config.doctypeEnd;
188};
189
190/**
191 * @type {(node: XastInstruction, config: Options) => string}
192 */
193const stringifyInstruction = (node, config) => {
194 return (
195 config.procInstStart + node.name + ' ' + node.value + config.procInstEnd
196 );
197};
198
199/**
200 * @type {(node: XastComment, config: Options) => string}
201 */
202const stringifyComment = (node, config) => {
203 return config.commentStart + node.value + config.commentEnd;
204};
205
206/**
207 * @type {(node: XastCdata, config: Options, state: State) => string}
208 */
209const stringifyCdata = (node, config, state) => {
210 return (
211 createIndent(config, state) +
212 config.cdataStart +
213 node.value +
214 config.cdataEnd
215 );
216};
217
218/**
219 * @type {(node: XastElement, config: Options, state: State) => string}
220 */
221const stringifyElement = (node, config, state) => {
222 // beautiful injection for obtaining SVG information :)
223 if (
224 node.name === 'svg' &&
225 node.attributes.width != null &&
226 node.attributes.height != null
227 ) {
228 state.width = node.attributes.width;
229 state.height = node.attributes.height;
230 }
231
232 // empty element and short tag
233 if (node.children.length === 0) {
234 if (config.useShortTags) {
235 return (
236 createIndent(config, state) +
237 config.tagShortStart +
238 node.name +
239 stringifyAttributes(node, config) +
240 config.tagShortEnd
241 );
242 } else {
243 return (
244 createIndent(config, state) +
245 config.tagShortStart +
246 node.name +
247 stringifyAttributes(node, config) +
248 config.tagOpenEnd +
249 config.tagCloseStart +
250 node.name +
251 config.tagCloseEnd
252 );
253 }
254 // non-empty element
255 } else {
256 let tagOpenStart = config.tagOpenStart;
257 let tagOpenEnd = config.tagOpenEnd;
258 let tagCloseStart = config.tagCloseStart;
259 let tagCloseEnd = config.tagCloseEnd;
260 let openIndent = createIndent(config, state);
261 let closeIndent = createIndent(config, state);
262
263 if (state.textContext) {
264 tagOpenStart = defaults.tagOpenStart;
265 tagOpenEnd = defaults.tagOpenEnd;
266 tagCloseStart = defaults.tagCloseStart;
267 tagCloseEnd = defaults.tagCloseEnd;
268 openIndent = '';
269 } else if (textElems.includes(node.name)) {
270 tagOpenEnd = defaults.tagOpenEnd;
271 tagCloseStart = defaults.tagCloseStart;
272 closeIndent = '';
273 state.textContext = node;
274 }
275
276 const children = stringifyNode(node, config, state);
277
278 if (state.textContext === node) {
279 state.textContext = null;
280 }
281
282 return (
283 openIndent +
284 tagOpenStart +
285 node.name +
286 stringifyAttributes(node, config) +
287 tagOpenEnd +
288 children +
289 closeIndent +
290 tagCloseStart +
291 node.name +
292 tagCloseEnd
293 );
294 }
295};
296
297/**
298 * @type {(node: XastElement, config: Options) => string}
299 */
300const stringifyAttributes = (node, config) => {
301 let attrs = '';
302 for (const [name, value] of Object.entries(node.attributes)) {
303 // TODO remove attributes without values support in v3
304 if (value !== undefined) {
305 const encodedValue = value
306 .toString()
307 .replace(config.regValEntities, config.encodeEntity);
308 attrs += ' ' + name + config.attrStart + encodedValue + config.attrEnd;
309 } else {
310 attrs += ' ' + name;
311 }
312 }
313 return attrs;
314};
315
316/**
317 * @type {(node: XastText, config: Options, state: State) => string}
318 */
319const stringifyText = (node, config, state) => {
320 return (
321 createIndent(config, state) +
322 config.textStart +
323 node.value.replace(config.regEntities, config.encodeEntity) +
324 (state.textContext ? '' : config.textEnd)
325 );
326};
Note: See TracBrowser for help on using the repository browser.