77 lines
		
	
	
		
			2.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			77 lines
		
	
	
		
			2.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
| 'use strict';
 | |
| 
 | |
| var identity = require('../nodes/identity.js');
 | |
| var visit = require('../visit.js');
 | |
| 
 | |
| /**
 | |
|  * Verify that the input string is a valid anchor.
 | |
|  *
 | |
|  * Will throw on errors.
 | |
|  */
 | |
| function anchorIsValid(anchor) {
 | |
|     if (/[\x00-\x19\s,[\]{}]/.test(anchor)) {
 | |
|         const sa = JSON.stringify(anchor);
 | |
|         const msg = `Anchor must not contain whitespace or control characters: ${sa}`;
 | |
|         throw new Error(msg);
 | |
|     }
 | |
|     return true;
 | |
| }
 | |
| function anchorNames(root) {
 | |
|     const anchors = new Set();
 | |
|     visit.visit(root, {
 | |
|         Value(_key, node) {
 | |
|             if (node.anchor)
 | |
|                 anchors.add(node.anchor);
 | |
|         }
 | |
|     });
 | |
|     return anchors;
 | |
| }
 | |
| /** Find a new anchor name with the given `prefix` and a one-indexed suffix. */
 | |
| function findNewAnchor(prefix, exclude) {
 | |
|     for (let i = 1; true; ++i) {
 | |
|         const name = `${prefix}${i}`;
 | |
|         if (!exclude.has(name))
 | |
|             return name;
 | |
|     }
 | |
| }
 | |
| function createNodeAnchors(doc, prefix) {
 | |
|     const aliasObjects = [];
 | |
|     const sourceObjects = new Map();
 | |
|     let prevAnchors = null;
 | |
|     return {
 | |
|         onAnchor: (source) => {
 | |
|             aliasObjects.push(source);
 | |
|             prevAnchors ?? (prevAnchors = anchorNames(doc));
 | |
|             const anchor = findNewAnchor(prefix, prevAnchors);
 | |
|             prevAnchors.add(anchor);
 | |
|             return anchor;
 | |
|         },
 | |
|         /**
 | |
|          * With circular references, the source node is only resolved after all
 | |
|          * of its child nodes are. This is why anchors are set only after all of
 | |
|          * the nodes have been created.
 | |
|          */
 | |
|         setAnchors: () => {
 | |
|             for (const source of aliasObjects) {
 | |
|                 const ref = sourceObjects.get(source);
 | |
|                 if (typeof ref === 'object' &&
 | |
|                     ref.anchor &&
 | |
|                     (identity.isScalar(ref.node) || identity.isCollection(ref.node))) {
 | |
|                     ref.node.anchor = ref.anchor;
 | |
|                 }
 | |
|                 else {
 | |
|                     const error = new Error('Failed to resolve repeated object (this should not happen)');
 | |
|                     error.source = source;
 | |
|                     throw error;
 | |
|                 }
 | |
|             }
 | |
|         },
 | |
|         sourceObjects
 | |
|     };
 | |
| }
 | |
| 
 | |
| exports.anchorIsValid = anchorIsValid;
 | |
| exports.anchorNames = anchorNames;
 | |
| exports.createNodeAnchors = createNodeAnchors;
 | |
| exports.findNewAnchor = findNewAnchor;
 |