anchors.js 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
  1. import { isScalar, isCollection } from '../nodes/identity.js';
  2. import { visit } from '../visit.js';
  3. /**
  4. * Verify that the input string is a valid anchor.
  5. *
  6. * Will throw on errors.
  7. */
  8. function anchorIsValid(anchor) {
  9. if (/[\x00-\x19\s,[\]{}]/.test(anchor)) {
  10. const sa = JSON.stringify(anchor);
  11. const msg = `Anchor must not contain whitespace or control characters: ${sa}`;
  12. throw new Error(msg);
  13. }
  14. return true;
  15. }
  16. function anchorNames(root) {
  17. const anchors = new Set();
  18. visit(root, {
  19. Value(_key, node) {
  20. if (node.anchor)
  21. anchors.add(node.anchor);
  22. }
  23. });
  24. return anchors;
  25. }
  26. /** Find a new anchor name with the given `prefix` and a one-indexed suffix. */
  27. function findNewAnchor(prefix, exclude) {
  28. for (let i = 1; true; ++i) {
  29. const name = `${prefix}${i}`;
  30. if (!exclude.has(name))
  31. return name;
  32. }
  33. }
  34. function createNodeAnchors(doc, prefix) {
  35. const aliasObjects = [];
  36. const sourceObjects = new Map();
  37. let prevAnchors = null;
  38. return {
  39. onAnchor: (source) => {
  40. aliasObjects.push(source);
  41. if (!prevAnchors)
  42. prevAnchors = anchorNames(doc);
  43. const anchor = findNewAnchor(prefix, prevAnchors);
  44. prevAnchors.add(anchor);
  45. return anchor;
  46. },
  47. /**
  48. * With circular references, the source node is only resolved after all
  49. * of its child nodes are. This is why anchors are set only after all of
  50. * the nodes have been created.
  51. */
  52. setAnchors: () => {
  53. for (const source of aliasObjects) {
  54. const ref = sourceObjects.get(source);
  55. if (typeof ref === 'object' &&
  56. ref.anchor &&
  57. (isScalar(ref.node) || isCollection(ref.node))) {
  58. ref.node.anchor = ref.anchor;
  59. }
  60. else {
  61. const error = new Error('Failed to resolve repeated object (this should not happen)');
  62. error.source = source;
  63. throw error;
  64. }
  65. }
  66. },
  67. sourceObjects
  68. };
  69. }
  70. export { anchorIsValid, anchorNames, createNodeAnchors, findNewAnchor };