YAMLMap.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. import { stringifyCollection } from '../stringify/stringifyCollection.js';
  2. import { addPairToJSMap } from './addPairToJSMap.js';
  3. import { Collection } from './Collection.js';
  4. import { isPair, isScalar, MAP } from './identity.js';
  5. import { Pair, createPair } from './Pair.js';
  6. import { isScalarValue } from './Scalar.js';
  7. function findPair(items, key) {
  8. const k = isScalar(key) ? key.value : key;
  9. for (const it of items) {
  10. if (isPair(it)) {
  11. if (it.key === key || it.key === k)
  12. return it;
  13. if (isScalar(it.key) && it.key.value === k)
  14. return it;
  15. }
  16. }
  17. return undefined;
  18. }
  19. class YAMLMap extends Collection {
  20. static get tagName() {
  21. return 'tag:yaml.org,2002:map';
  22. }
  23. constructor(schema) {
  24. super(MAP, schema);
  25. this.items = [];
  26. }
  27. /**
  28. * A generic collection parsing method that can be extended
  29. * to other node classes that inherit from YAMLMap
  30. */
  31. static from(schema, obj, ctx) {
  32. const { keepUndefined, replacer } = ctx;
  33. const map = new this(schema);
  34. const add = (key, value) => {
  35. if (typeof replacer === 'function')
  36. value = replacer.call(obj, key, value);
  37. else if (Array.isArray(replacer) && !replacer.includes(key))
  38. return;
  39. if (value !== undefined || keepUndefined)
  40. map.items.push(createPair(key, value, ctx));
  41. };
  42. if (obj instanceof Map) {
  43. for (const [key, value] of obj)
  44. add(key, value);
  45. }
  46. else if (obj && typeof obj === 'object') {
  47. for (const key of Object.keys(obj))
  48. add(key, obj[key]);
  49. }
  50. if (typeof schema.sortMapEntries === 'function') {
  51. map.items.sort(schema.sortMapEntries);
  52. }
  53. return map;
  54. }
  55. /**
  56. * Adds a value to the collection.
  57. *
  58. * @param overwrite - If not set `true`, using a key that is already in the
  59. * collection will throw. Otherwise, overwrites the previous value.
  60. */
  61. add(pair, overwrite) {
  62. let _pair;
  63. if (isPair(pair))
  64. _pair = pair;
  65. else if (!pair || typeof pair !== 'object' || !('key' in pair)) {
  66. // In TypeScript, this never happens.
  67. _pair = new Pair(pair, pair?.value);
  68. }
  69. else
  70. _pair = new Pair(pair.key, pair.value);
  71. const prev = findPair(this.items, _pair.key);
  72. const sortEntries = this.schema?.sortMapEntries;
  73. if (prev) {
  74. if (!overwrite)
  75. throw new Error(`Key ${_pair.key} already set`);
  76. // For scalars, keep the old node & its comments and anchors
  77. if (isScalar(prev.value) && isScalarValue(_pair.value))
  78. prev.value.value = _pair.value;
  79. else
  80. prev.value = _pair.value;
  81. }
  82. else if (sortEntries) {
  83. const i = this.items.findIndex(item => sortEntries(_pair, item) < 0);
  84. if (i === -1)
  85. this.items.push(_pair);
  86. else
  87. this.items.splice(i, 0, _pair);
  88. }
  89. else {
  90. this.items.push(_pair);
  91. }
  92. }
  93. delete(key) {
  94. const it = findPair(this.items, key);
  95. if (!it)
  96. return false;
  97. const del = this.items.splice(this.items.indexOf(it), 1);
  98. return del.length > 0;
  99. }
  100. get(key, keepScalar) {
  101. const it = findPair(this.items, key);
  102. const node = it?.value;
  103. return (!keepScalar && isScalar(node) ? node.value : node) ?? undefined;
  104. }
  105. has(key) {
  106. return !!findPair(this.items, key);
  107. }
  108. set(key, value) {
  109. this.add(new Pair(key, value), true);
  110. }
  111. /**
  112. * @param ctx - Conversion context, originally set in Document#toJS()
  113. * @param {Class} Type - If set, forces the returned collection type
  114. * @returns Instance of Type, Map, or Object
  115. */
  116. toJSON(_, ctx, Type) {
  117. const map = Type ? new Type() : ctx?.mapAsMap ? new Map() : {};
  118. if (ctx?.onCreate)
  119. ctx.onCreate(map);
  120. for (const item of this.items)
  121. addPairToJSMap(ctx, map, item);
  122. return map;
  123. }
  124. toString(ctx, onComment, onChompKeep) {
  125. if (!ctx)
  126. return JSON.stringify(this);
  127. for (const item of this.items) {
  128. if (!isPair(item))
  129. throw new Error(`Map items must all be pairs; found ${JSON.stringify(item)} instead`);
  130. }
  131. if (!ctx.allNullValues && this.hasAllNullValues(false))
  132. ctx = Object.assign({}, ctx, { allNullValues: true });
  133. return stringifyCollection(this, ctx, {
  134. blockItemPrefix: '',
  135. flowChars: { start: '{', end: '}' },
  136. itemIndent: ctx.indent || '',
  137. onChompKeep,
  138. onComment
  139. });
  140. }
  141. }
  142. export { YAMLMap, findPair };