import { anchorIsValid } from '../doc/anchors.js'; import { isPair, isAlias, isNode, isScalar, isCollection } from '../nodes/Node.js'; import { stringifyComment } from './stringifyComment.js'; import { stringifyString } from './stringifyString.js'; function createStringifyContext(doc, options) { const opt = Object.assign({ blockQuote: true, commentString: stringifyComment, defaultKeyType: null, defaultStringType: 'PLAIN', directives: null, doubleQuotedAsJSON: false, doubleQuotedMinMultiLineLength: 40, falseStr: 'false', flowCollectionPadding: true, indentSeq: true, lineWidth: 80, minContentWidth: 20, nullStr: 'null', simpleKeys: false, singleQuote: null, trueStr: 'true', verifyAliasOrder: true }, doc.schema.toStringOptions, options); let inFlow; switch (opt.collectionStyle) { case 'block': inFlow = false; break; case 'flow': inFlow = true; break; default: inFlow = null; } return { anchors: new Set(), doc, flowCollectionPadding: opt.flowCollectionPadding ? ' ' : '', indent: '', indentStep: typeof opt.indent === 'number' ? ' '.repeat(opt.indent) : ' ', inFlow, options: opt }; } function getTagObject(tags, item) { if (item.tag) { const match = tags.filter(t => t.tag === item.tag); if (match.length > 0) return match.find(t => t.format === item.format) ?? match[0]; } let tagObj = undefined; let obj; if (isScalar(item)) { obj = item.value; const match = tags.filter(t => t.identify?.(obj)); tagObj = match.find(t => t.format === item.format) ?? match.find(t => !t.format); } else { obj = item; tagObj = tags.find(t => t.nodeClass && obj instanceof t.nodeClass); } if (!tagObj) { const name = obj?.constructor?.name ?? typeof obj; throw new Error(`Tag not resolved for ${name} value`); } return tagObj; } // needs to be called before value stringifier to allow for circular anchor refs function stringifyProps(node, tagObj, { anchors, doc }) { if (!doc.directives) return ''; const props = []; const anchor = (isScalar(node) || isCollection(node)) && node.anchor; if (anchor && anchorIsValid(anchor)) { anchors.add(anchor); props.push(`&${anchor}`); } const tag = node.tag ? node.tag : tagObj.default ? null : tagObj.tag; if (tag) props.push(doc.directives.tagString(tag)); return props.join(' '); } function stringify(item, ctx, onComment, onChompKeep) { if (isPair(item)) return item.toString(ctx, onComment, onChompKeep); if (isAlias(item)) { if (ctx.doc.directives) return item.toString(ctx); if (ctx.resolvedAliases?.has(item)) { throw new TypeError(`Cannot stringify circular structure without alias nodes`); } else { if (ctx.resolvedAliases) ctx.resolvedAliases.add(item); else ctx.resolvedAliases = new Set([item]); item = item.resolve(ctx.doc); } } let tagObj = undefined; const node = isNode(item) ? item : ctx.doc.createNode(item, { onTagObj: o => (tagObj = o) }); if (!tagObj) tagObj = getTagObject(ctx.doc.schema.tags, node); const props = stringifyProps(node, tagObj, ctx); if (props.length > 0) ctx.indentAtStart = (ctx.indentAtStart ?? 0) + props.length + 1; const str = typeof tagObj.stringify === 'function' ? tagObj.stringify(node, ctx, onComment, onChompKeep) : isScalar(node) ? stringifyString(node, ctx, onComment, onChompKeep) : node.toString(ctx, onComment, onChompKeep); if (!props) return str; return isScalar(node) || str[0] === '{' || str[0] === '[' ? `${props} ${str}` : `${props}\n${ctx.indent}${str}`; } export { createStringifyContext, stringify };