import { resolveBlockScalar } from '../compose/resolve-block-scalar.js'; import { resolveFlowScalar } from '../compose/resolve-flow-scalar.js'; import { YAMLParseError } from '../errors.js'; import { stringifyString } from '../stringify/stringifyString.js'; function resolveAsScalar(token, strict = true, onError) { if (token) { const _onError = (pos, code, message) => { const offset = typeof pos === 'number' ? pos : Array.isArray(pos) ? pos[0] : pos.offset; if (onError) onError(offset, code, message); else throw new YAMLParseError([offset, offset + 1], code, message); }; switch (token.type) { case 'scalar': case 'single-quoted-scalar': case 'double-quoted-scalar': return resolveFlowScalar(token, strict, _onError); case 'block-scalar': return resolveBlockScalar({ options: { strict } }, token, _onError); } } return null; } /** * Create a new scalar token with `value` * * Values that represent an actual string but may be parsed as a different type should use a `type` other than `'PLAIN'`, * as this function does not support any schema operations and won't check for such conflicts. * * @param value The string representation of the value, which will have its content properly indented. * @param context.end Comments and whitespace after the end of the value, or after the block scalar header. If undefined, a newline will be added. * @param context.implicitKey Being within an implicit key may affect the resolved type of the token's value. * @param context.indent The indent level of the token. * @param context.inFlow Is this scalar within a flow collection? This may affect the resolved type of the token's value. * @param context.offset The offset position of the token. * @param context.type The preferred type of the scalar token. If undefined, the previous type of the `token` will be used, defaulting to `'PLAIN'`. */ function createScalarToken(value, context) { const { implicitKey = false, indent, inFlow = false, offset = -1, type = 'PLAIN' } = context; const source = stringifyString({ type, value }, { implicitKey, indent: indent > 0 ? ' '.repeat(indent) : '', inFlow, options: { blockQuote: true, lineWidth: -1 } }); const end = context.end ?? [ { type: 'newline', offset: -1, indent, source: '\n' } ]; switch (source[0]) { case '|': case '>': { const he = source.indexOf('\n'); const head = source.substring(0, he); const body = source.substring(he + 1) + '\n'; const props = [ { type: 'block-scalar-header', offset, indent, source: head } ]; if (!addEndtoBlockProps(props, end)) props.push({ type: 'newline', offset: -1, indent, source: '\n' }); return { type: 'block-scalar', offset, indent, props, source: body }; } case '"': return { type: 'double-quoted-scalar', offset, indent, source, end }; case "'": return { type: 'single-quoted-scalar', offset, indent, source, end }; default: return { type: 'scalar', offset, indent, source, end }; } } /** * Set the value of `token` to the given string `value`, overwriting any previous contents and type that it may have. * * Best efforts are made to retain any comments previously associated with the `token`, * though all contents within a collection's `items` will be overwritten. * * Values that represent an actual string but may be parsed as a different type should use a `type` other than `'PLAIN'`, * as this function does not support any schema operations and won't check for such conflicts. * * @param token Any token. If it does not include an `indent` value, the value will be stringified as if it were an implicit key. * @param value The string representation of the value, which will have its content properly indented. * @param context.afterKey In most cases, values after a key should have an additional level of indentation. * @param context.implicitKey Being within an implicit key may affect the resolved type of the token's value. * @param context.inFlow Being within a flow collection may affect the resolved type of the token's value. * @param context.type The preferred type of the scalar token. If undefined, the previous type of the `token` will be used, defaulting to `'PLAIN'`. */ function setScalarValue(token, value, context = {}) { let { afterKey = false, implicitKey = false, inFlow = false, type } = context; let indent = 'indent' in token ? token.indent : null; if (afterKey && typeof indent === 'number') indent += 2; if (!type) switch (token.type) { case 'single-quoted-scalar': type = 'QUOTE_SINGLE'; break; case 'double-quoted-scalar': type = 'QUOTE_DOUBLE'; break; case 'block-scalar': { const header = token.props[0]; if (header.type !== 'block-scalar-header') throw new Error('Invalid block scalar header'); type = header.source[0] === '>' ? 'BLOCK_FOLDED' : 'BLOCK_LITERAL'; break; } default: type = 'PLAIN'; } const source = stringifyString({ type, value }, { implicitKey: implicitKey || indent === null, indent: indent !== null && indent > 0 ? ' '.repeat(indent) : '', inFlow, options: { blockQuote: true, lineWidth: -1 } }); switch (source[0]) { case '|': case '>': setBlockScalarValue(token, source); break; case '"': setFlowScalarValue(token, source, 'double-quoted-scalar'); break; case "'": setFlowScalarValue(token, source, 'single-quoted-scalar'); break; default: setFlowScalarValue(token, source, 'scalar'); } } function setBlockScalarValue(token, source) { const he = source.indexOf('\n'); const head = source.substring(0, he); const body = source.substring(he + 1) + '\n'; if (token.type === 'block-scalar') { const header = token.props[0]; if (header.type !== 'block-scalar-header') throw new Error('Invalid block scalar header'); header.source = head; token.source = body; } else { const { offset } = token; const indent = 'indent' in token ? token.indent : -1; const props = [ { type: 'block-scalar-header', offset, indent, source: head } ]; if (!addEndtoBlockProps(props, 'end' in token ? token.end : undefined)) props.push({ type: 'newline', offset: -1, indent, source: '\n' }); for (const key of Object.keys(token)) if (key !== 'type' && key !== 'offset') delete token[key]; Object.assign(token, { type: 'block-scalar', indent, props, source: body }); } } /** @returns `true` if last token is a newline */ function addEndtoBlockProps(props, end) { if (end) for (const st of end) switch (st.type) { case 'space': case 'comment': props.push(st); break; case 'newline': props.push(st); return true; } return false; } function setFlowScalarValue(token, source, type) { switch (token.type) { case 'scalar': case 'double-quoted-scalar': case 'single-quoted-scalar': token.type = type; token.source = source; break; case 'block-scalar': { const end = token.props.slice(1); let oa = source.length; if (token.props[0].type === 'block-scalar-header') oa -= token.props[0].source.length; for (const tok of end) tok.offset += oa; delete token.props; Object.assign(token, { type, source, end }); break; } case 'block-map': case 'block-seq': { const offset = token.offset + source.length; const nl = { type: 'newline', offset, indent: token.indent, source: '\n' }; delete token.items; Object.assign(token, { type, source, end: [nl] }); break; } default: { const indent = 'indent' in token ? token.indent : -1; const end = 'end' in token && Array.isArray(token.end) ? token.end.filter(st => st.type === 'space' || st.type === 'comment' || st.type === 'newline') : []; for (const key of Object.keys(token)) if (key !== 'type' && key !== 'offset') delete token[key]; Object.assign(token, { type, indent, source, end }); } } } export { createScalarToken, resolveAsScalar, setScalarValue };