import { stringifyCollection } from '../stringify/stringifyCollection.js'; import { addPairToJSMap } from './addPairToJSMap.js'; import { Collection } from './Collection.js'; import { isPair, isScalar, MAP } from './identity.js'; import { Pair, createPair } from './Pair.js'; import { isScalarValue } from './Scalar.js'; function findPair(items, key) { const k = isScalar(key) ? key.value : key; for (const it of items) { if (isPair(it)) { if (it.key === key || it.key === k) return it; if (isScalar(it.key) && it.key.value === k) return it; } } return undefined; } class YAMLMap extends Collection { static get tagName() { return 'tag:yaml.org,2002:map'; } constructor(schema) { super(MAP, schema); this.items = []; } /** * A generic collection parsing method that can be extended * to other node classes that inherit from YAMLMap */ static from(schema, obj, ctx) { const { keepUndefined, replacer } = ctx; const map = new this(schema); const add = (key, value) => { if (typeof replacer === 'function') value = replacer.call(obj, key, value); else if (Array.isArray(replacer) && !replacer.includes(key)) return; if (value !== undefined || keepUndefined) map.items.push(createPair(key, value, ctx)); }; if (obj instanceof Map) { for (const [key, value] of obj) add(key, value); } else if (obj && typeof obj === 'object') { for (const key of Object.keys(obj)) add(key, obj[key]); } if (typeof schema.sortMapEntries === 'function') { map.items.sort(schema.sortMapEntries); } return map; } /** * Adds a value to the collection. * * @param overwrite - If not set `true`, using a key that is already in the * collection will throw. Otherwise, overwrites the previous value. */ add(pair, overwrite) { let _pair; if (isPair(pair)) _pair = pair; else if (!pair || typeof pair !== 'object' || !('key' in pair)) { // In TypeScript, this never happens. _pair = new Pair(pair, pair?.value); } else _pair = new Pair(pair.key, pair.value); const prev = findPair(this.items, _pair.key); const sortEntries = this.schema?.sortMapEntries; if (prev) { if (!overwrite) throw new Error(`Key ${_pair.key} already set`); // For scalars, keep the old node & its comments and anchors if (isScalar(prev.value) && isScalarValue(_pair.value)) prev.value.value = _pair.value; else prev.value = _pair.value; } else if (sortEntries) { const i = this.items.findIndex(item => sortEntries(_pair, item) < 0); if (i === -1) this.items.push(_pair); else this.items.splice(i, 0, _pair); } else { this.items.push(_pair); } } delete(key) { const it = findPair(this.items, key); if (!it) return false; const del = this.items.splice(this.items.indexOf(it), 1); return del.length > 0; } get(key, keepScalar) { const it = findPair(this.items, key); const node = it?.value; return (!keepScalar && isScalar(node) ? node.value : node) ?? undefined; } has(key) { return !!findPair(this.items, key); } set(key, value) { this.add(new Pair(key, value), true); } /** * @param ctx - Conversion context, originally set in Document#toJS() * @param {Class} Type - If set, forces the returned collection type * @returns Instance of Type, Map, or Object */ toJSON(_, ctx, Type) { const map = Type ? new Type() : ctx?.mapAsMap ? new Map() : {}; if (ctx?.onCreate) ctx.onCreate(map); for (const item of this.items) addPairToJSMap(ctx, map, item); return map; } toString(ctx, onComment, onChompKeep) { if (!ctx) return JSON.stringify(this); for (const item of this.items) { if (!isPair(item)) throw new Error(`Map items must all be pairs; found ${JSON.stringify(item)} instead`); } if (!ctx.allNullValues && this.hasAllNullValues(false)) ctx = Object.assign({}, ctx, { allNullValues: true }); return stringifyCollection(this, ctx, { blockItemPrefix: '', flowChars: { start: '{', end: '}' }, itemIndent: ctx.indent || '', onChompKeep, onComment }); } } export { YAMLMap, findPair };