| ; | |
| var Scalar = require('../nodes/Scalar.js'); | |
| var foldFlowLines = require('./foldFlowLines.js'); | |
| const getFoldOptions = (ctx, isBlock) => ({ | |
| indentAtStart: isBlock ? ctx.indent.length : ctx.indentAtStart, | |
| lineWidth: ctx.options.lineWidth, | |
| minContentWidth: ctx.options.minContentWidth | |
| }); | |
| // Also checks for lines starting with %, as parsing the output as YAML 1.1 will | |
| // presume that's starting a new document. | |
| const containsDocumentMarker = (str) => /^(%|---|\.\.\.)/m.test(str); | |
| function lineLengthOverLimit(str, lineWidth, indentLength) { | |
| if (!lineWidth || lineWidth < 0) | |
| return false; | |
| const limit = lineWidth - indentLength; | |
| const strLen = str.length; | |
| if (strLen <= limit) | |
| return false; | |
| for (let i = 0, start = 0; i < strLen; ++i) { | |
| if (str[i] === '\n') { | |
| if (i - start > limit) | |
| return true; | |
| start = i + 1; | |
| if (strLen - start <= limit) | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| function doubleQuotedString(value, ctx) { | |
| const json = JSON.stringify(value); | |
| if (ctx.options.doubleQuotedAsJSON) | |
| return json; | |
| const { implicitKey } = ctx; | |
| const minMultiLineLength = ctx.options.doubleQuotedMinMultiLineLength; | |
| const indent = ctx.indent || (containsDocumentMarker(value) ? ' ' : ''); | |
| let str = ''; | |
| let start = 0; | |
| for (let i = 0, ch = json[i]; ch; ch = json[++i]) { | |
| if (ch === ' ' && json[i + 1] === '\\' && json[i + 2] === 'n') { | |
| // space before newline needs to be escaped to not be folded | |
| str += json.slice(start, i) + '\\ '; | |
| i += 1; | |
| start = i; | |
| ch = '\\'; | |
| } | |
| if (ch === '\\') | |
| switch (json[i + 1]) { | |
| case 'u': | |
| { | |
| str += json.slice(start, i); | |
| const code = json.substr(i + 2, 4); | |
| switch (code) { | |
| case '0000': | |
| str += '\\0'; | |
| break; | |
| case '0007': | |
| str += '\\a'; | |
| break; | |
| case '000b': | |
| str += '\\v'; | |
| break; | |
| case '001b': | |
| str += '\\e'; | |
| break; | |
| case '0085': | |
| str += '\\N'; | |
| break; | |
| case '00a0': | |
| str += '\\_'; | |
| break; | |
| case '2028': | |
| str += '\\L'; | |
| break; | |
| case '2029': | |
| str += '\\P'; | |
| break; | |
| default: | |
| if (code.substr(0, 2) === '00') | |
| str += '\\x' + code.substr(2); | |
| else | |
| str += json.substr(i, 6); | |
| } | |
| i += 5; | |
| start = i + 1; | |
| } | |
| break; | |
| case 'n': | |
| if (implicitKey || | |
| json[i + 2] === '"' || | |
| json.length < minMultiLineLength) { | |
| i += 1; | |
| } | |
| else { | |
| // folding will eat first newline | |
| str += json.slice(start, i) + '\n\n'; | |
| while (json[i + 2] === '\\' && | |
| json[i + 3] === 'n' && | |
| json[i + 4] !== '"') { | |
| str += '\n'; | |
| i += 2; | |
| } | |
| str += indent; | |
| // space after newline needs to be escaped to not be folded | |
| if (json[i + 2] === ' ') | |
| str += '\\'; | |
| i += 1; | |
| start = i + 1; | |
| } | |
| break; | |
| default: | |
| i += 1; | |
| } | |
| } | |
| str = start ? str + json.slice(start) : json; | |
| return implicitKey | |
| ? str | |
| : foldFlowLines.foldFlowLines(str, indent, foldFlowLines.FOLD_QUOTED, getFoldOptions(ctx, false)); | |
| } | |
| function singleQuotedString(value, ctx) { | |
| if (ctx.options.singleQuote === false || | |
| (ctx.implicitKey && value.includes('\n')) || | |
| /[ \t]\n|\n[ \t]/.test(value) // single quoted string can't have leading or trailing whitespace around newline | |
| ) | |
| return doubleQuotedString(value, ctx); | |
| const indent = ctx.indent || (containsDocumentMarker(value) ? ' ' : ''); | |
| const res = "'" + value.replace(/'/g, "''").replace(/\n+/g, `$&\n${indent}`) + "'"; | |
| return ctx.implicitKey | |
| ? res | |
| : foldFlowLines.foldFlowLines(res, indent, foldFlowLines.FOLD_FLOW, getFoldOptions(ctx, false)); | |
| } | |
| function quotedString(value, ctx) { | |
| const { singleQuote } = ctx.options; | |
| let qs; | |
| if (singleQuote === false) | |
| qs = doubleQuotedString; | |
| else { | |
| const hasDouble = value.includes('"'); | |
| const hasSingle = value.includes("'"); | |
| if (hasDouble && !hasSingle) | |
| qs = singleQuotedString; | |
| else if (hasSingle && !hasDouble) | |
| qs = doubleQuotedString; | |
| else | |
| qs = singleQuote ? singleQuotedString : doubleQuotedString; | |
| } | |
| return qs(value, ctx); | |
| } | |
| // The negative lookbehind avoids a polynomial search, | |
| // but isn't supported yet on Safari: https://caniuse.com/js-regexp-lookbehind | |
| let blockEndNewlines; | |
| try { | |
| blockEndNewlines = new RegExp('(^|(?<!\n))\n+(?!\n|$)', 'g'); | |
| } | |
| catch { | |
| blockEndNewlines = /\n+(?!\n|$)/g; | |
| } | |
| function blockString({ comment, type, value }, ctx, onComment, onChompKeep) { | |
| const { blockQuote, commentString, lineWidth } = ctx.options; | |
| // 1. Block can't end in whitespace unless the last line is non-empty. | |
| // 2. Strings consisting of only whitespace are best rendered explicitly. | |
| if (!blockQuote || /\n[\t ]+$/.test(value)) { | |
| return quotedString(value, ctx); | |
| } | |
| const indent = ctx.indent || | |
| (ctx.forceBlockIndent || containsDocumentMarker(value) ? ' ' : ''); | |
| const literal = blockQuote === 'literal' | |
| ? true | |
| : blockQuote === 'folded' || type === Scalar.Scalar.BLOCK_FOLDED | |
| ? false | |
| : type === Scalar.Scalar.BLOCK_LITERAL | |
| ? true | |
| : !lineLengthOverLimit(value, lineWidth, indent.length); | |
| if (!value) | |
| return literal ? '|\n' : '>\n'; | |
| // determine chomping from whitespace at value end | |
| let chomp; | |
| let endStart; | |
| for (endStart = value.length; endStart > 0; --endStart) { | |
| const ch = value[endStart - 1]; | |
| if (ch !== '\n' && ch !== '\t' && ch !== ' ') | |
| break; | |
| } | |
| let end = value.substring(endStart); | |
| const endNlPos = end.indexOf('\n'); | |
| if (endNlPos === -1) { | |
| chomp = '-'; // strip | |
| } | |
| else if (value === end || endNlPos !== end.length - 1) { | |
| chomp = '+'; // keep | |
| if (onChompKeep) | |
| onChompKeep(); | |
| } | |
| else { | |
| chomp = ''; // clip | |
| } | |
| if (end) { | |
| value = value.slice(0, -end.length); | |
| if (end[end.length - 1] === '\n') | |
| end = end.slice(0, -1); | |
| end = end.replace(blockEndNewlines, `$&${indent}`); | |
| } | |
| // determine indent indicator from whitespace at value start | |
| let startWithSpace = false; | |
| let startEnd; | |
| let startNlPos = -1; | |
| for (startEnd = 0; startEnd < value.length; ++startEnd) { | |
| const ch = value[startEnd]; | |
| if (ch === ' ') | |
| startWithSpace = true; | |
| else if (ch === '\n') | |
| startNlPos = startEnd; | |
| else | |
| break; | |
| } | |
| let start = value.substring(0, startNlPos < startEnd ? startNlPos + 1 : startEnd); | |
| if (start) { | |
| value = value.substring(start.length); | |
| start = start.replace(/\n+/g, `$&${indent}`); | |
| } | |
| const indentSize = indent ? '2' : '1'; // root is at -1 | |
| // Leading | or > is added later | |
| let header = (startWithSpace ? indentSize : '') + chomp; | |
| if (comment) { | |
| header += ' ' + commentString(comment.replace(/ ?[\r\n]+/g, ' ')); | |
| if (onComment) | |
| onComment(); | |
| } | |
| if (!literal) { | |
| const foldedValue = value | |
| .replace(/\n+/g, '\n$&') | |
| .replace(/(?:^|\n)([\t ].*)(?:([\n\t ]*)\n(?![\n\t ]))?/g, '$1$2') // more-indented lines aren't folded | |
| // ^ more-ind. ^ empty ^ capture next empty lines only at end of indent | |
| .replace(/\n+/g, `$&${indent}`); | |
| let literalFallback = false; | |
| const foldOptions = getFoldOptions(ctx, true); | |
| if (blockQuote !== 'folded' && type !== Scalar.Scalar.BLOCK_FOLDED) { | |
| foldOptions.onOverflow = () => { | |
| literalFallback = true; | |
| }; | |
| } | |
| const body = foldFlowLines.foldFlowLines(`${start}${foldedValue}${end}`, indent, foldFlowLines.FOLD_BLOCK, foldOptions); | |
| if (!literalFallback) | |
| return `>${header}\n${indent}${body}`; | |
| } | |
| value = value.replace(/\n+/g, `$&${indent}`); | |
| return `|${header}\n${indent}${start}${value}${end}`; | |
| } | |
| function plainString(item, ctx, onComment, onChompKeep) { | |
| const { type, value } = item; | |
| const { actualString, implicitKey, indent, indentStep, inFlow } = ctx; | |
| if ((implicitKey && value.includes('\n')) || | |
| (inFlow && /[[\]{},]/.test(value))) { | |
| return quotedString(value, ctx); | |
| } | |
| if (/^[\n\t ,[\]{}#&*!|>'"%@`]|^[?-]$|^[?-][ \t]|[\n:][ \t]|[ \t]\n|[\n\t ]#|[\n\t :]$/.test(value)) { | |
| // not allowed: | |
| // - '-' or '?' | |
| // - start with an indicator character (except [?:-]) or /[?-] / | |
| // - '\n ', ': ' or ' \n' anywhere | |
| // - '#' not preceded by a non-space char | |
| // - end with ' ' or ':' | |
| return implicitKey || inFlow || !value.includes('\n') | |
| ? quotedString(value, ctx) | |
| : blockString(item, ctx, onComment, onChompKeep); | |
| } | |
| if (!implicitKey && | |
| !inFlow && | |
| type !== Scalar.Scalar.PLAIN && | |
| value.includes('\n')) { | |
| // Where allowed & type not set explicitly, prefer block style for multiline strings | |
| return blockString(item, ctx, onComment, onChompKeep); | |
| } | |
| if (containsDocumentMarker(value)) { | |
| if (indent === '') { | |
| ctx.forceBlockIndent = true; | |
| return blockString(item, ctx, onComment, onChompKeep); | |
| } | |
| else if (implicitKey && indent === indentStep) { | |
| return quotedString(value, ctx); | |
| } | |
| } | |
| const str = value.replace(/\n+/g, `$&\n${indent}`); | |
| // Verify that output will be parsed as a string, as e.g. plain numbers and | |
| // booleans get parsed with those types in v1.2 (e.g. '42', 'true' & '0.9e-3'), | |
| // and others in v1.1. | |
| if (actualString) { | |
| const test = (tag) => tag.default && tag.tag !== 'tag:yaml.org,2002:str' && tag.test?.test(str); | |
| const { compat, tags } = ctx.doc.schema; | |
| if (tags.some(test) || compat?.some(test)) | |
| return quotedString(value, ctx); | |
| } | |
| return implicitKey | |
| ? str | |
| : foldFlowLines.foldFlowLines(str, indent, foldFlowLines.FOLD_FLOW, getFoldOptions(ctx, false)); | |
| } | |
| function stringifyString(item, ctx, onComment, onChompKeep) { | |
| const { implicitKey, inFlow } = ctx; | |
| const ss = typeof item.value === 'string' | |
| ? item | |
| : Object.assign({}, item, { value: String(item.value) }); | |
| let { type } = item; | |
| if (type !== Scalar.Scalar.QUOTE_DOUBLE) { | |
| // force double quotes on control characters & unpaired surrogates | |
| if (/[\x00-\x08\x0b-\x1f\x7f-\x9f\u{D800}-\u{DFFF}]/u.test(ss.value)) | |
| type = Scalar.Scalar.QUOTE_DOUBLE; | |
| } | |
| const _stringify = (_type) => { | |
| switch (_type) { | |
| case Scalar.Scalar.BLOCK_FOLDED: | |
| case Scalar.Scalar.BLOCK_LITERAL: | |
| return implicitKey || inFlow | |
| ? quotedString(ss.value, ctx) // blocks are not valid inside flow containers | |
| : blockString(ss, ctx, onComment, onChompKeep); | |
| case Scalar.Scalar.QUOTE_DOUBLE: | |
| return doubleQuotedString(ss.value, ctx); | |
| case Scalar.Scalar.QUOTE_SINGLE: | |
| return singleQuotedString(ss.value, ctx); | |
| case Scalar.Scalar.PLAIN: | |
| return plainString(ss, ctx, onComment, onChompKeep); | |
| default: | |
| return null; | |
| } | |
| }; | |
| let res = _stringify(type); | |
| if (res === null) { | |
| const { defaultKeyType, defaultStringType } = ctx.options; | |
| const t = (implicitKey && defaultKeyType) || defaultStringType; | |
| res = _stringify(t); | |
| if (res === null) | |
| throw new Error(`Unsupported default string type ${t}`); | |
| } | |
| return res; | |
| } | |
| exports.stringifyString = stringifyString; | |
Xet Storage Details
- Size:
- 13.3 kB
- Xet hash:
- 031fbdf2ff8c6b6e7ab69c988529b2c2a5c2acb88dd4b7eb27ddb752ecabeafa
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.