1 | 'use strict';
|
2 |
|
3 | var Scalar = require('../nodes/Scalar.js');
|
4 | var foldFlowLines = require('./foldFlowLines.js');
|
5 |
|
6 | const getFoldOptions = (ctx, isBlock) => ({
|
7 | indentAtStart: isBlock ? ctx.indent.length : ctx.indentAtStart,
|
8 | lineWidth: ctx.options.lineWidth,
|
9 | minContentWidth: ctx.options.minContentWidth
|
10 | });
|
11 |
|
12 |
|
13 | const containsDocumentMarker = (str) => /^(%|---|\.\.\.)/m.test(str);
|
14 | function lineLengthOverLimit(str, lineWidth, indentLength) {
|
15 | if (!lineWidth || lineWidth < 0)
|
16 | return false;
|
17 | const limit = lineWidth - indentLength;
|
18 | const strLen = str.length;
|
19 | if (strLen <= limit)
|
20 | return false;
|
21 | for (let i = 0, start = 0; i < strLen; ++i) {
|
22 | if (str[i] === '\n') {
|
23 | if (i - start > limit)
|
24 | return true;
|
25 | start = i + 1;
|
26 | if (strLen - start <= limit)
|
27 | return false;
|
28 | }
|
29 | }
|
30 | return true;
|
31 | }
|
32 | function doubleQuotedString(value, ctx) {
|
33 | const json = JSON.stringify(value);
|
34 | if (ctx.options.doubleQuotedAsJSON)
|
35 | return json;
|
36 | const { implicitKey } = ctx;
|
37 | const minMultiLineLength = ctx.options.doubleQuotedMinMultiLineLength;
|
38 | const indent = ctx.indent || (containsDocumentMarker(value) ? ' ' : '');
|
39 | let str = '';
|
40 | let start = 0;
|
41 | for (let i = 0, ch = json[i]; ch; ch = json[++i]) {
|
42 | if (ch === ' ' && json[i + 1] === '\\' && json[i + 2] === 'n') {
|
43 |
|
44 | str += json.slice(start, i) + '\\ ';
|
45 | i += 1;
|
46 | start = i;
|
47 | ch = '\\';
|
48 | }
|
49 | if (ch === '\\')
|
50 | switch (json[i + 1]) {
|
51 | case 'u':
|
52 | {
|
53 | str += json.slice(start, i);
|
54 | const code = json.substr(i + 2, 4);
|
55 | switch (code) {
|
56 | case '0000':
|
57 | str += '\\0';
|
58 | break;
|
59 | case '0007':
|
60 | str += '\\a';
|
61 | break;
|
62 | case '000b':
|
63 | str += '\\v';
|
64 | break;
|
65 | case '001b':
|
66 | str += '\\e';
|
67 | break;
|
68 | case '0085':
|
69 | str += '\\N';
|
70 | break;
|
71 | case '00a0':
|
72 | str += '\\_';
|
73 | break;
|
74 | case '2028':
|
75 | str += '\\L';
|
76 | break;
|
77 | case '2029':
|
78 | str += '\\P';
|
79 | break;
|
80 | default:
|
81 | if (code.substr(0, 2) === '00')
|
82 | str += '\\x' + code.substr(2);
|
83 | else
|
84 | str += json.substr(i, 6);
|
85 | }
|
86 | i += 5;
|
87 | start = i + 1;
|
88 | }
|
89 | break;
|
90 | case 'n':
|
91 | if (implicitKey ||
|
92 | json[i + 2] === '"' ||
|
93 | json.length < minMultiLineLength) {
|
94 | i += 1;
|
95 | }
|
96 | else {
|
97 |
|
98 | str += json.slice(start, i) + '\n\n';
|
99 | while (json[i + 2] === '\\' &&
|
100 | json[i + 3] === 'n' &&
|
101 | json[i + 4] !== '"') {
|
102 | str += '\n';
|
103 | i += 2;
|
104 | }
|
105 | str += indent;
|
106 |
|
107 | if (json[i + 2] === ' ')
|
108 | str += '\\';
|
109 | i += 1;
|
110 | start = i + 1;
|
111 | }
|
112 | break;
|
113 | default:
|
114 | i += 1;
|
115 | }
|
116 | }
|
117 | str = start ? str + json.slice(start) : json;
|
118 | return implicitKey
|
119 | ? str
|
120 | : foldFlowLines.foldFlowLines(str, indent, foldFlowLines.FOLD_QUOTED, getFoldOptions(ctx, false));
|
121 | }
|
122 | function singleQuotedString(value, ctx) {
|
123 | if (ctx.options.singleQuote === false ||
|
124 | (ctx.implicitKey && value.includes('\n')) ||
|
125 | /[ \t]\n|\n[ \t]/.test(value)
|
126 | )
|
127 | return doubleQuotedString(value, ctx);
|
128 | const indent = ctx.indent || (containsDocumentMarker(value) ? ' ' : '');
|
129 | const res = "'" + value.replace(/'/g, "''").replace(/\n+/g, `$&\n${indent}`) + "'";
|
130 | return ctx.implicitKey
|
131 | ? res
|
132 | : foldFlowLines.foldFlowLines(res, indent, foldFlowLines.FOLD_FLOW, getFoldOptions(ctx, false));
|
133 | }
|
134 | function quotedString(value, ctx) {
|
135 | const { singleQuote } = ctx.options;
|
136 | let qs;
|
137 | if (singleQuote === false)
|
138 | qs = doubleQuotedString;
|
139 | else {
|
140 | const hasDouble = value.includes('"');
|
141 | const hasSingle = value.includes("'");
|
142 | if (hasDouble && !hasSingle)
|
143 | qs = singleQuotedString;
|
144 | else if (hasSingle && !hasDouble)
|
145 | qs = doubleQuotedString;
|
146 | else
|
147 | qs = singleQuote ? singleQuotedString : doubleQuotedString;
|
148 | }
|
149 | return qs(value, ctx);
|
150 | }
|
151 |
|
152 |
|
153 | let blockEndNewlines;
|
154 | try {
|
155 | blockEndNewlines = new RegExp('(^|(?<!\n))\n+(?!\n|$)', 'g');
|
156 | }
|
157 | catch {
|
158 | blockEndNewlines = /\n+(?!\n|$)/g;
|
159 | }
|
160 | function blockString({ comment, type, value }, ctx, onComment, onChompKeep) {
|
161 | const { blockQuote, commentString, lineWidth } = ctx.options;
|
162 |
|
163 |
|
164 | if (!blockQuote || /\n[\t ]+$/.test(value) || /^\s*$/.test(value)) {
|
165 | return quotedString(value, ctx);
|
166 | }
|
167 | const indent = ctx.indent ||
|
168 | (ctx.forceBlockIndent || containsDocumentMarker(value) ? ' ' : '');
|
169 | const literal = blockQuote === 'literal'
|
170 | ? true
|
171 | : blockQuote === 'folded' || type === Scalar.Scalar.BLOCK_FOLDED
|
172 | ? false
|
173 | : type === Scalar.Scalar.BLOCK_LITERAL
|
174 | ? true
|
175 | : !lineLengthOverLimit(value, lineWidth, indent.length);
|
176 | if (!value)
|
177 | return literal ? '|\n' : '>\n';
|
178 |
|
179 | let chomp;
|
180 | let endStart;
|
181 | for (endStart = value.length; endStart > 0; --endStart) {
|
182 | const ch = value[endStart - 1];
|
183 | if (ch !== '\n' && ch !== '\t' && ch !== ' ')
|
184 | break;
|
185 | }
|
186 | let end = value.substring(endStart);
|
187 | const endNlPos = end.indexOf('\n');
|
188 | if (endNlPos === -1) {
|
189 | chomp = '-';
|
190 | }
|
191 | else if (value === end || endNlPos !== end.length - 1) {
|
192 | chomp = '+';
|
193 | if (onChompKeep)
|
194 | onChompKeep();
|
195 | }
|
196 | else {
|
197 | chomp = '';
|
198 | }
|
199 | if (end) {
|
200 | value = value.slice(0, -end.length);
|
201 | if (end[end.length - 1] === '\n')
|
202 | end = end.slice(0, -1);
|
203 | end = end.replace(blockEndNewlines, `$&${indent}`);
|
204 | }
|
205 |
|
206 | let startWithSpace = false;
|
207 | let startEnd;
|
208 | let startNlPos = -1;
|
209 | for (startEnd = 0; startEnd < value.length; ++startEnd) {
|
210 | const ch = value[startEnd];
|
211 | if (ch === ' ')
|
212 | startWithSpace = true;
|
213 | else if (ch === '\n')
|
214 | startNlPos = startEnd;
|
215 | else
|
216 | break;
|
217 | }
|
218 | let start = value.substring(0, startNlPos < startEnd ? startNlPos + 1 : startEnd);
|
219 | if (start) {
|
220 | value = value.substring(start.length);
|
221 | start = start.replace(/\n+/g, `$&${indent}`);
|
222 | }
|
223 | const indentSize = indent ? '2' : '1';
|
224 | let header = (literal ? '|' : '>') + (startWithSpace ? indentSize : '') + chomp;
|
225 | if (comment) {
|
226 | header += ' ' + commentString(comment.replace(/ ?[\r\n]+/g, ' '));
|
227 | if (onComment)
|
228 | onComment();
|
229 | }
|
230 | if (literal) {
|
231 | value = value.replace(/\n+/g, `$&${indent}`);
|
232 | return `${header}\n${indent}${start}${value}${end}`;
|
233 | }
|
234 | value = value
|
235 | .replace(/\n+/g, '\n$&')
|
236 | .replace(/(?:^|\n)([\t ].*)(?:([\n\t ]*)\n(?![\n\t ]))?/g, '$1$2')
|
237 |
|
238 | .replace(/\n+/g, `$&${indent}`);
|
239 | const body = foldFlowLines.foldFlowLines(`${start}${value}${end}`, indent, foldFlowLines.FOLD_BLOCK, getFoldOptions(ctx, true));
|
240 | return `${header}\n${indent}${body}`;
|
241 | }
|
242 | function plainString(item, ctx, onComment, onChompKeep) {
|
243 | const { type, value } = item;
|
244 | const { actualString, implicitKey, indent, indentStep, inFlow } = ctx;
|
245 | if ((implicitKey && value.includes('\n')) ||
|
246 | (inFlow && /[[\]{},]/.test(value))) {
|
247 | return quotedString(value, ctx);
|
248 | }
|
249 | if (!value ||
|
250 | /^[\n\t ,[\]{}#&*!|>'"%@`]|^[?-]$|^[?-][ \t]|[\n:][ \t]|[ \t]\n|[\n\t ]#|[\n\t :]$/.test(value)) {
|
251 |
|
252 |
|
253 |
|
254 |
|
255 |
|
256 |
|
257 | return implicitKey || inFlow || !value.includes('\n')
|
258 | ? quotedString(value, ctx)
|
259 | : blockString(item, ctx, onComment, onChompKeep);
|
260 | }
|
261 | if (!implicitKey &&
|
262 | !inFlow &&
|
263 | type !== Scalar.Scalar.PLAIN &&
|
264 | value.includes('\n')) {
|
265 |
|
266 | return blockString(item, ctx, onComment, onChompKeep);
|
267 | }
|
268 | if (containsDocumentMarker(value)) {
|
269 | if (indent === '') {
|
270 | ctx.forceBlockIndent = true;
|
271 | return blockString(item, ctx, onComment, onChompKeep);
|
272 | }
|
273 | else if (implicitKey && indent === indentStep) {
|
274 | return quotedString(value, ctx);
|
275 | }
|
276 | }
|
277 | const str = value.replace(/\n+/g, `$&\n${indent}`);
|
278 |
|
279 |
|
280 |
|
281 | if (actualString) {
|
282 | const test = (tag) => tag.default && tag.tag !== 'tag:yaml.org,2002:str' && tag.test?.test(str);
|
283 | const { compat, tags } = ctx.doc.schema;
|
284 | if (tags.some(test) || compat?.some(test))
|
285 | return quotedString(value, ctx);
|
286 | }
|
287 | return implicitKey
|
288 | ? str
|
289 | : foldFlowLines.foldFlowLines(str, indent, foldFlowLines.FOLD_FLOW, getFoldOptions(ctx, false));
|
290 | }
|
291 | function stringifyString(item, ctx, onComment, onChompKeep) {
|
292 | const { implicitKey, inFlow } = ctx;
|
293 | const ss = typeof item.value === 'string'
|
294 | ? item
|
295 | : Object.assign({}, item, { value: String(item.value) });
|
296 | let { type } = item;
|
297 | if (type !== Scalar.Scalar.QUOTE_DOUBLE) {
|
298 |
|
299 | if (/[\x00-\x08\x0b-\x1f\x7f-\x9f\u{D800}-\u{DFFF}]/u.test(ss.value))
|
300 | type = Scalar.Scalar.QUOTE_DOUBLE;
|
301 | }
|
302 | const _stringify = (_type) => {
|
303 | switch (_type) {
|
304 | case Scalar.Scalar.BLOCK_FOLDED:
|
305 | case Scalar.Scalar.BLOCK_LITERAL:
|
306 | return implicitKey || inFlow
|
307 | ? quotedString(ss.value, ctx)
|
308 | : blockString(ss, ctx, onComment, onChompKeep);
|
309 | case Scalar.Scalar.QUOTE_DOUBLE:
|
310 | return doubleQuotedString(ss.value, ctx);
|
311 | case Scalar.Scalar.QUOTE_SINGLE:
|
312 | return singleQuotedString(ss.value, ctx);
|
313 | case Scalar.Scalar.PLAIN:
|
314 | return plainString(ss, ctx, onComment, onChompKeep);
|
315 | default:
|
316 | return null;
|
317 | }
|
318 | };
|
319 | let res = _stringify(type);
|
320 | if (res === null) {
|
321 | const { defaultKeyType, defaultStringType } = ctx.options;
|
322 | const t = (implicitKey && defaultKeyType) || defaultStringType;
|
323 | res = _stringify(t);
|
324 | if (res === null)
|
325 | throw new Error(`Unsupported default string type ${t}`);
|
326 | }
|
327 | return res;
|
328 | }
|
329 |
|
330 | exports.stringifyString = stringifyString;
|