All files / src ve.BranchNode.js

97.18% Statements 69/71
91.89% Branches 34/37
100% Functions 8/8
97.18% Lines 69/71

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206                                        1x 15266x         1x                   1x   16943x   16943x 22988x 22988x 15457x                   1x 107564x               1x 29494x                 1x 3577x                 1x   26097x 26097x   11007x   15090x       1025x 1025x 495x   1025x   15090x 15090x       14093x 15906x   14093x                   1x   27425x 27425x   11024x   16401x       968x 968x 422x   968x   16401x 16401x       15444x 15836x   15444x                             1x   7165x 7165x 7165x     7165x 497x   6668x       6668x 6668x 14987x 19646x 19646x     1997x   17649x 2x   17647x 17647x 10886x     8319x 8319x 8319x   2567x     6761x   2102x     2102x     2x    
/*!
 * VisualEditor BranchNode class.
 *
 * @copyright 2011-2020 VisualEditor Team and others; see http://ve.mit-license.org
 */
 
/**
 * Branch node mixin.
 *
 * Extenders are expected to inherit from ve.Node.
 *
 * Branch nodes are immutable, which is why there are no methods for adding or removing children.
 * DataModel classes will add this functionality, and other subclasses will implement behavior that
 * mimics changes made to DataModel nodes.
 *
 * @class
 * @abstract
 * @constructor
 * @param {ve.Node[]} children Array of children to add
 */
ve.BranchNode = function VeBranchNode( children ) {
	this.children = Array.isArray( children ) ? children : [];
};
 
/* Setup */
 
OO.initClass( ve.BranchNode );
 
/* Methods */
 
/**
 * Traverse a branch node depth-first.
 *
 * @param {Function} callback Callback to execute for each traversed node
 * @param {ve.Node} callback.node Node being traversed
 */
ve.BranchNode.prototype.traverse = function ( callback ) {
	var i, len,
		children = this.getChildren();
 
	for ( i = 0, len = children.length; i < len; i++ ) {
		callback.call( this, children[ i ] );
		if ( children[ i ].hasChildren() ) {
			children[ i ].traverse( callback );
		}
	}
};
 
/**
 * Check if the node has children.
 *
 * @return {boolean} Whether the node has children
 */
ve.BranchNode.prototype.hasChildren = function () {
	return true;
};
 
/**
 * Get child nodes.
 *
 * @return {ve.Node[]} List of child nodes
 */
ve.BranchNode.prototype.getChildren = function () {
	return this.children;
};
 
/**
 * Get the index of a child node.
 *
 * @param {ve.dm.Node} node Child node to find index of
 * @return {number} Index of child node or -1 if node was not found
 */
ve.BranchNode.prototype.indexOf = function ( node ) {
	return this.children.indexOf( node );
};
 
/**
 * Set the root node.
 *
 * @see ve.Node#setRoot
 * @param {ve.BranchNode|null} root Node to use as root
 */
ve.BranchNode.prototype.setRoot = function ( root ) {
	var i, len,
		oldRoot = this.root;
	if ( root === oldRoot ) {
		// Nothing to do, don't recurse into all descendants
		return;
	}
	if ( oldRoot ) {
		// Null the root, then recurse into children, then emit unroot.
		// That way, at emit time, all this node's ancestors and descendants have
		// null root.
		this.root = null;
		for ( i = 0, len = this.children.length; i < len; i++ ) {
			this.children[ i ].setRoot( null );
		}
		this.emit( 'unroot', oldRoot );
	}
	this.root = root;
	if ( root ) {
		// We've set the new root, so recurse into children, then emit root.
		// That way, at emit time, all this node's ancestors and descendants have
		// the new root.
		for ( i = 0, len = this.children.length; i < len; i++ ) {
			this.children[ i ].setRoot( root );
		}
		this.emit( 'root', root );
	}
};
 
/**
 * Set the document the node is a part of.
 *
 * @see ve.Node#setDocument
 * @param {ve.Document} doc Document this node is a part of
 */
ve.BranchNode.prototype.setDocument = function ( doc ) {
	var i, len,
		oldDoc = this.doc;
	if ( doc === this.doc ) {
		// Nothing to do, don't recurse into all descendants
		return;
	}
	if ( oldDoc ) {
		// Null the doc, then recurse into children, then notify the doc.
		// That way, at notify time, all this node's ancestors and descendants have
		// null doc.
		this.doc = null;
		for ( i = 0, len = this.children.length; i < len; i++ ) {
			this.children[ i ].setDocument( null );
		}
		oldDoc.nodeDetached( this );
	}
	this.doc = doc;
	if ( doc ) {
		// We've set the new doc, so recurse into children, then notify the doc.
		// That way, at notify time, all this node's ancestors and descendants have
		// the new doc.
		for ( i = 0, len = this.children.length; i < len; i++ ) {
			this.children[ i ].setDocument( doc );
		}
		doc.nodeAttached( this );
	}
};
 
/**
 * Get a node from an offset.
 *
 * This method is pretty expensive. If you need to get different slices of the same content, get
 * the content first, then slice it up locally.
 *
 * @param {number} offset Offset get node for
 * @param {boolean} [shallow] Do not iterate into child nodes of child nodes
 * @return {ve.Node|null} Node at offset, or null if none was found
 * @throws {Error} If offset is out of bounds
 */
ve.BranchNode.prototype.getNodeFromOffset = function ( offset, shallow ) {
	var i, length, nodeLength, childNode,
		currentNode = this,
		nodeOffset = 0;
	Iif ( typeof offset !== 'number' ) {
		throw new Error( 'Offset must be a number' );
	}
	if ( offset === 0 ) {
		return currentNode;
	}
	Iif ( offset < 0 ) {
		throw new Error( 'Offset out of bounds' );
	}
	// TODO a lot of logic is duplicated in selectNodes(), abstract that into a traverser or something
	SIBLINGS:
	while ( currentNode.children.length ) {
		for ( i = 0, length = currentNode.children.length; i < length; i++ ) {
			childNode = currentNode.children[ i ];
			if ( offset === nodeOffset ) {
				// The requested offset is right before childNode, so it's not
				// inside any of currentNode's children, but is inside currentNode
				return currentNode;
			}
			if ( childNode instanceof ve.ce.InternalListNode ) {
				break SIBLINGS;
			}
			nodeLength = childNode.getOuterLength();
			if ( offset >= nodeOffset && offset < nodeOffset + nodeLength ) {
				if ( !shallow && childNode.hasChildren() && childNode.getChildren().length ) {
					// One of the children contains the node; increment to
					// enter the node, then iterate through children
					nodeOffset += 1;
					currentNode = childNode;
					continue SIBLINGS;
				} else {
					return childNode;
				}
			}
			nodeOffset += nodeLength;
		}
		Eif ( offset === nodeOffset ) {
			// The requested offset is right before currentNode.children[i], so it's
			// not inside any of currentNode's children, but is inside currentNode
			return currentNode;
		}
	}
	return null;
};