anchors.js 2.3 KB

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