All files / src/dm ve.dm.Node.js

96.64% Statements 173/179
88.23% Branches 60/68
95.55% Functions 43/45
96.61% Lines 171/177

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 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825                                  1x   22191x     22191x 22191x     22191x 22191x 22191x                                           1x   1x   1x                                           1x                                 1x                   1x                 1x                   1x                 1x                     1x                     1x                   1x                 1x                 1x                 1x                   1x                 1x                 1x                         1x                     1x                       1x                           1x                 1x                           1x             1x                                 1x                           1x                             1x 82x   82x 89x 52x 52x         82x                                       1x 1110x 1110x   1110x 145x   1110x   1110x     476x 476x 476x   476x 1x 1x   476x   476x 1x     1110x 32x   1110x                             1x             1x 3126x                     1x 812x 812x     812x           1x 12708x           1x 8065x           1x             1x 1001x           1x 28072x           1x 3318x           1x 18113x           1x 191688x           1x 7x           1x 83154x           1x 3456x           1x 1459x           1x 10x           1x 3199x           1x 607x           1x 2611x           1x 2593x           1x 2363x               1x 21685x                 1x                     1x 1494x           1x 1799x           1x 877x           1x 2695x               1x                     1x 31x 96x                     1x 27x                   1x 96x 78x     18x     18x                 1x 2x 2x 2x                 1x 187172x                           1x 30956x 1x     30955x   30955x   30955x 8156x     30955x 30955x                           1x 25605x           1x 116152x 46754x     69398x 1744x       67654x 67654x   67654x 122156x 67654x   54502x   67654x     67654x     1064x   67654x                           1x 404x 404x           404x 4x 400x 2x     404x 75x           26x     49x 49x   378x                     1x 74x    
/*!
 * VisualEditor DataModel Node class.
 *
 * @copyright See AUTHORS.txt
 */
 
/**
 * Generic DataModel node.
 *
 * @abstract
 * @extends ve.dm.Model
 * @mixins OO.EventEmitter
 * @mixins ve.Node
 *
 * @constructor
 * @param {Object} [element] Reference to element in linear model
 */
ve.dm.Node = function VeDmNode( element ) {
	// Parent constructor
	ve.dm.Node.super.apply( this, arguments );
 
	// Mixin constructors
	ve.Node.call( this );
	OO.EventEmitter.call( this );
 
	// Properties
	this.length = 0;
	this.offset = null;
	this.element = element;
};
 
/**
 * @event attributeChange
 * @param {string} key
 * @param {Mixed} oldValue
 * @param {Mixed} newValue
 */
 
/**
 * @event lengthChange
 * @param {number} diff
 */
 
/**
 * @event update
 * @param {boolean} staged Transaction was applied in staging mode
 */
 
/* Inheritance */
 
OO.inheritClass( ve.dm.Node, ve.dm.Model );
 
OO.mixinClass( ve.dm.Node, ve.Node );
 
OO.mixinClass( ve.dm.Node, OO.EventEmitter );
 
/* Static Properties */
 
/**
 * Whether this node handles its own children. After converting a DOM node to a linear model
 * node of this type, the converter checks this property. If it's false, the converter will descend
 * into the DOM node's children, recursively convert them, and attach the resulting nodes as
 * children of the linear model node. If it's true, the converter will not descend, and will
 * expect the node's toDataElement() to have handled the entire DOM subtree.
 *
 * The same is true when converting from linear model data to DOM: if this property is true,
 * toDomElements() will be passed the node's data element and all of its children and will be
 * expected to convert the entire subtree. If it's false, the converter will descend into the
 * child nodes and convert each one individually.
 *
 * If .static.childNodeTypes is set to [], this property is ignored and will be assumed to be true.
 *
 * @static
 * @property {boolean}
 * @inheritable
 */
ve.dm.Node.static.handlesOwnChildren = false;
 
/**
 * Whether this node's children should be ignored when iterating over the model, for example
 * content which is stored inline, but is actually conceptually out-of-band data, such as a
 * reference node which contains the reference content (like `<ref>` tags in MediaWiki).
 *
 * This property should not be used for data which isn't out-of-band but is less accessible in
 * the view, e.g. content in table cells or image captions.
 *
 * If true, this node will be treated as a leaf node even if it has children.
 * Often used in combination with handlesOwnChildren.
 *
 * @static
 * @property {boolean}
 * @inheritable
 */
ve.dm.Node.static.ignoreChildren = false;
 
/**
 * Whether this node can be deleted. If false, ve.dm.Transaction#newFromRemoval will silently
 * ignore any attepts to delete this node.
 *
 * @static
 * @property {boolean}
 * @inheritable
 */
ve.dm.Node.static.isDeletable = true;
 
/**
 * Whether this node type is internal. Internal node types are ignored by the converter.
 *
 * @static
 * @property {boolean}
 * @inheritable
 */
ve.dm.Node.static.isInternal = false;
 
/**
 * Whether this node type has a wrapping element in the linear model. Most node types are wrapped,
 * only special node types are not wrapped.
 *
 * @static
 * @property {boolean}
 * @inheritable
 */
ve.dm.Node.static.isWrapped = true;
 
/**
 * Whether this node type can be unwrapped by user input (e.g. backspace to unwrap a list item)
 *
 * @static
 * @property {boolean}
 * @inheritable
 */
ve.dm.Node.static.isUnwrappable = true;
 
/**
 * Whether this node type is a content node type. This means the node represents content, cannot
 * have children, and can only appear as children of a content container node. Content nodes are
 * also known as inline nodes.
 *
 * @static
 * @property {boolean}
 * @inheritable
 */
ve.dm.Node.static.isContent = false;
 
/**
 * Whether this node type is a metadata node. This means the node represents a leaf node that
 * has no explicit view representation, and should be treated differently for the purposes of
 * round-tripping, copy/paste etc.
 *
 * @static
 * @property {boolean}
 * @inheritable
 */
ve.dm.Node.static.isMetaData = false;
 
/**
 * For a non-content node type, whether this node type can be serialized in a content
 * position (e.g. for round tripping). This value is ignored if isContent is true.
 *
 * @static
 * @property {boolean}
 * @inheritable
 */
ve.dm.Node.static.canSerializeAsContent = false;
 
/**
 * Whether this node type can be focused. Focusable nodes react to selections differently.
 *
 * @static
 * @property {boolean}
 * @inheritable
 */
ve.dm.Node.static.isFocusable = false;
 
/**
 * Whether this node type is alignable.
 *
 * @static
 * @property {boolean}
 * @inheritable
 */
ve.dm.Node.static.isAlignable = false;
 
/**
 * Whether this node type can behave as a table cell.
 *
 * @static
 * @property {boolean}
 * @inheritable
 */
ve.dm.Node.static.isCellable = false;
 
/**
 * Whether this node type can contain content. The children of content container nodes must be
 * content nodes.
 *
 * @static
 * @property {boolean}
 * @inheritable
 */
ve.dm.Node.static.canContainContent = false;
 
/**
 * Whether this node type behaves like a list when diffing.
 *
 * @static
 * @property {boolean}
 * @inheritable
 */
ve.dm.Node.static.isDiffedAsList = false;
 
/**
 * Whether this node type behaves like a leaf when diffing.
 *
 * @static
 * @property {boolean}
 * @inheritable
 */
ve.dm.Node.static.isDiffedAsLeaf = false;
 
/**
 * Whether this node type has significant whitespace. Only applies to content container nodes
 * (i.e. can only be true if canContainContent is also true).
 *
 * If a content node has significant whitespace, the text inside it is not subject to whitespace
 * stripping and preservation.
 *
 * @static
 * @property {boolean}
 * @inheritable
 */
ve.dm.Node.static.hasSignificantWhitespace = false;
 
/**
 * Array of allowed child node types for this node type.
 *
 * An empty array means no children are allowed. null means any node type is allowed as a child.
 *
 * @static
 * @property {string[]|null}
 * @inheritable
 */
ve.dm.Node.static.childNodeTypes = null;
 
/**
 * Array of allowed parent node types for this node type.
 *
 * An empty array means this node type cannot be the child of any node. null means this node type
 * can be the child of any node type.
 *
 * @static
 * @property {string[]|null}
 * @inheritable
 */
ve.dm.Node.static.parentNodeTypes = null;
 
/**
 * Array of suggested parent node types for this node type.
 *
 * These parent node types are allowed but the editor will avoid creating them.
 *
 * An empty array means this node type should not be the child of any node. null means this node type
 * can be the child of any node type.
 *
 * @static
 * @property {string[]|null}
 * @inheritable
 */
ve.dm.Node.static.suggestedParentNodeTypes = null;
 
/**
 * Array of annotation types which can't be applied to this node
 *
 * @static
 * @property {string[]}
 * @inheritable
 */
ve.dm.Node.static.disallowedAnnotationTypes = [];
 
/**
 * Default attributes to set for newly created linear model elements. These defaults will be used
 * when creating a new element in ve.dm.NodeFactory#getDataElement when there is no DOM node or
 * existing linear model element to base the attributes on.
 *
 * This property is an object with attribute names as keys and attribute values as values.
 * Attributes may be omitted, in which case they'll simply be undefined.
 *
 * @static
 * @property {Object}
 * @inheritable
 */
ve.dm.Node.static.defaultAttributes = {};
 
/**
 * Sanitize the node's linear model data, typically if it was generated from an external source (e.g. copied HTML)
 *
 * @param {Object} dataElement Linear model element, modified in place
 */
ve.dm.Node.static.sanitize = function () {
};
 
/**
 * Remap the internal list indexes stored in a linear model data element.
 *
 * The default implementation is empty. Nodes should override this if they store internal list
 * indexes in attributes. To remap, do something like
 * dataElement.attributes.foo = mapping[dataElement.attributes.foo];
 *
 * @static
 * @inheritable
 * @param {Object} dataElement Data element (opening) to remap. Will be modified.
 * @param {Object} mapping Object mapping old internal list indexes to new internal list indexes
 * @param {ve.dm.InternalList} internalList Internal list the indexes are being mapped into.
 *  Used for refreshing attribute values that were computed with getNextUniqueNumber().
 */
ve.dm.Node.static.remapInternalListIndexes = function () {
};
 
/**
 * Remap the internal list keys stored in a linear model data element.
 *
 * The default implementation is empty. Nodes should override this if they store internal list
 * keys in attributes.
 *
 * @static
 * @inheritable
 * @param {Object} dataElement Data element (opening) to remap. Will be modified.
 * @param {ve.dm.InternalList} internalList Internal list the keys are being mapped into.
 */
ve.dm.Node.static.remapInternalListKeys = function () {
};
 
/**
 * Determine if a hybrid element is inline and allowed to be inline in this context
 *
 * We generate block elements for block tags and inline elements for inline
 * tags; unless we're in a content location, in which case we have no choice
 * but to generate an inline element.
 *
 * @static
 * @param {HTMLElement[]} domElements DOM elements being converted
 * @param {ve.dm.Converter} converter
 * @return {boolean} The element is inline
 */
ve.dm.Node.static.isHybridInline = function ( domElements, converter ) {
	var allTagsInline = true;
 
	for ( var i = 0, length = domElements.length; i < length; i++ ) {
		if ( ve.isBlockElement( domElements[ i ] ) ) {
			allTagsInline = false;
			break;
		}
	}
 
	// Force inline in content locations (but not wrappers)
	return ( converter.isExpectingContent() && !converter.isInWrapper() ) ||
		// ..also force inline in wrappers that we can't close
		( converter.isInWrapper() && !converter.canCloseWrapper() ) ||
		// ..otherwise just look at the tag names
		allTagsInline;
};
 
/**
 * Get a clone of the node's document data element.
 *
 * The attributes object will be deep-copied and the .internal.generated
 * property will be removed if present.
 *
 * @static
 * @param {Object} element
 * @param {ve.dm.HashValueStore} store Hash-value store used by element
 * @param {boolean} preserveGenerated Preserve internal.generated property of element
 * @param {boolean} resetAttributes Reset attributes for an empty clone, as defined in #static-resetForClone
 * @return {Object} Cloned element object
 */
ve.dm.Node.static.cloneElement = function ( element, store, preserveGenerated, resetAttributes ) {
	var modified = false,
		clone = ve.copy( element );
 
	if ( !preserveGenerated ) {
		ve.deleteProp( clone, 'internal', 'generated' );
	}
	var originalDomElements = store.value( clone.originalDomElementsHash );
	// Generate a new about attribute to prevent about grouping of cloned nodes
	if ( originalDomElements ) {
		// TODO: The '#mwtNNN' is required by Parsoid. Make the name used here
		// more generic and specify the #mwt pattern in MW code.
		var about = '#mwt' + Math.floor( 1000000000 * Math.random() );
		var domElements = originalDomElements.map( function ( el ) {
			var elClone = el.cloneNode( true );
			// Check for hasAttribute as comments don't have them
			if ( elClone.hasAttribute && elClone.hasAttribute( 'about' ) ) {
				elClone.setAttribute( 'about', about );
				modified = true;
			}
			return elClone;
		} );
		if ( modified ) {
			clone.originalDomElementsHash = store.hash( domElements, domElements.map( ve.getNodeHtml ).join( '' ) );
		}
	}
	if ( resetAttributes ) {
		this.resetAttributesForClone( clone, store );
	}
	return clone;
};
 
/**
 * Reset attributes for a cloned element.
 *
 * This will be used when an element needs to have certain attributes cleared
 * when creating a clone, e.g. when splitting a content branch node by pressing
 * enter, some attributes are preserved (list style) but some are cleared
 * (check list item state).
 *
 * @static
 * @param {Object} clonedElement Cloned element, modified in place
 * @param {ve.dm.HashValueStore} store Hash-value store used by element
 */
ve.dm.Node.static.resetAttributesForClone = function () {};
 
/* Methods */
 
/**
 * @inheritdoc
 */
ve.dm.Node.prototype.getStore = function () {
	return this.doc && this.doc.store;
};
 
/**
 * @see #static-cloneElement
 * Implementations should override the static method, not this one
 *
 * @param {boolean} preserveGenerated Preserve internal.generated property of element
 * @param {boolean} resetAttributes Reset attributes for an empty clone, as defined in #static-resetForClone
 * @return {Object} Cloned element object
 */
ve.dm.Node.prototype.getClonedElement = function ( preserveGenerated, resetAttributes ) {
	var store = this.getStore();
	Iif ( !store ) {
		throw new Error( 'Node must be attached to the document to be cloned.' );
	}
	return this.constructor.static.cloneElement( this.element, store, preserveGenerated, resetAttributes );
};
 
/**
 * @inheritdoc ve.Node
 */
ve.dm.Node.prototype.getChildNodeTypes = function () {
	return this.constructor.static.childNodeTypes;
};
 
/**
 * @inheritdoc ve.Node
 */
ve.dm.Node.prototype.getParentNodeTypes = function () {
	return this.constructor.static.parentNodeTypes;
};
 
/**
 * @inheritdoc ve.Node
 */
ve.dm.Node.prototype.getSuggestedParentNodeTypes = function () {
	return this.constructor.static.suggestedParentNodeTypes;
};
 
/**
 * @inheritdoc ve.Node
 */
ve.dm.Node.prototype.canHaveChildren = function () {
	return ve.dm.nodeFactory.canNodeHaveChildren( this.type );
};
 
/**
 * @inheritdoc ve.Node
 */
ve.dm.Node.prototype.canHaveChildrenNotContent = function () {
	return ve.dm.nodeFactory.canNodeHaveChildrenNotContent( this.type );
};
 
/**
 * @inheritdoc ve.Node
 */
ve.dm.Node.prototype.isInternal = function () {
	return this.constructor.static.isInternal;
};
 
/**
 * @inheritdoc ve.Node
 */
ve.dm.Node.prototype.isMetaData = function () {
	return this.constructor.static.isMetaData;
};
 
/**
 * @inheritdoc ve.Node
 */
ve.dm.Node.prototype.isWrapped = function () {
	return this.constructor.static.isWrapped;
};
 
/**
 * @inheritdoc ve.Node
 */
ve.dm.Node.prototype.isUnwrappable = function () {
	return this.isWrapped() && this.constructor.static.isUnwrappable;
};
 
/**
 * @inheritdoc ve.Node
 */
ve.dm.Node.prototype.canContainContent = function () {
	return this.constructor.static.canContainContent;
};
 
/**
 * @inheritdoc ve.Node
 */
ve.dm.Node.prototype.isContent = function () {
	return this.constructor.static.isContent;
};
 
/**
 * @inheritdoc ve.Node
 */
ve.dm.Node.prototype.isFocusable = function () {
	return this.constructor.static.isFocusable;
};
 
/**
 * @inheritdoc ve.Node
 */
ve.dm.Node.prototype.isAlignable = function () {
	return this.constructor.static.isAlignable;
};
 
/**
 * @inheritdoc ve.Node
 */
ve.dm.Node.prototype.isCellable = function () {
	return this.constructor.static.isCellable;
};
 
/**
 * @inheritdoc ve.Node
 */
ve.dm.Node.prototype.isCellEditable = function () {
	return this.constructor.static.isCellEditable;
};
 
/**
 * @inheritdoc ve.Node
 */
ve.dm.Node.prototype.isDiffedAsList = function () {
	return this.constructor.static.isDiffedAsList;
};
 
/**
 * @inheritdoc ve.Node
 */
ve.dm.Node.prototype.isDiffedAsLeaf = function () {
	return this.constructor.static.isDiffedAsLeaf;
};
 
/**
 * @inheritdoc ve.Node
 */
ve.dm.Node.prototype.isDiffedAsDocument = function () {
	return this.getChildNodeTypes() === null;
};
 
/**
 * Check if the node can have a slug before it.
 *
 * @return {boolean} Whether the node can have a slug before it
 */
ve.dm.Node.prototype.canHaveSlugBefore = function () {
	return !this.canContainContent() && this.getParentNodeTypes() === null;
};
 
/**
 * Check if the node can have a slug after it.
 *
 * @method
 * @return {boolean} Whether the node can have a slug after it
 */
ve.dm.Node.prototype.canHaveSlugAfter = ve.dm.Node.prototype.canHaveSlugBefore;
 
/**
 * A string identifier used to suppress slugs
 *
 * If sequential nodes have the same non-null suppressSlugType, then
 * no slug is shown, e.g. two floated images can return 'float' to
 * suppress the slug between them.
 *
 * @return {string|null} Type
 */
ve.dm.Node.prototype.suppressSlugType = function () {
	return null;
};
 
/**
 * @inheritdoc ve.Node
 */
ve.dm.Node.prototype.hasSignificantWhitespace = function () {
	return this.constructor.static.hasSignificantWhitespace;
};
 
/**
 * @inheritdoc ve.Node
 */
ve.dm.Node.prototype.handlesOwnChildren = function () {
	return this.constructor.static.handlesOwnChildren;
};
 
/**
 * @inheritdoc ve.Node
 */
ve.dm.Node.prototype.shouldIgnoreChildren = function () {
	return this.constructor.static.ignoreChildren;
};
 
/**
 * Check if the node can be the root of a branch exposed in a ve.ce.Surface
 *
 * @return {boolean} Node can be the root of a surfaced branch
 */
ve.dm.Node.prototype.isSurfaceable = function () {
	return this.hasChildren() && !this.canContainContent() && !this.isMetaData() && !this.getChildNodeTypes();
};
 
/**
 * Find the first ancestor with matching type and attribute values.
 *
 * @param {string} type Node type to match
 * @param {Object} [attributes] Node attributes to match
 * @return {ve.dm.Node|null} Ancestor with matching type and attribute values
 */
ve.dm.Node.prototype.findMatchingAncestor = function ( type, attributes ) {
	return this.traverseUpstream( function ( node ) {
		return !node.matches( type, attributes );
	} );
};
 
/**
 * Check if the node has an ancestor with matching type and attribute values.
 *
 * @param {string} type Node type to match
 * @param {Object} [attributes] Node attributes to match
 * @return {boolean} Node has an ancestor with matching type and attribute values
 */
ve.dm.Node.prototype.hasMatchingAncestor = function ( type, attributes ) {
	return !!this.findMatchingAncestor( type, attributes );
};
 
/**
 * Check if the node matches type and attribute values.
 *
 * @param {string} type Node type to match
 * @param {Object} [attributes] Node attributes to match
 * @return {boolean} Node matches type and attribute values
 */
ve.dm.Node.prototype.matches = function ( type, attributes ) {
	if ( this.getType() !== type ) {
		return false;
	}
 
	Iif ( attributes ) {
		return this.compareAttributes( attributes );
	}
	return true;
};
 
/**
 * Check if specific attributes match those in the node
 *
 * @param {Object} attributes Node attributes to match
 * @return {boolean} Attributes sepcified match those in the node
 */
ve.dm.Node.prototype.compareAttributes = function ( attributes ) {
	for ( var key in attributes ) {
		Eif ( this.getAttribute( key ) !== attributes[ key ] ) {
			return false;
		}
	}
	return true;
};
 
/**
 * @inheritdoc ve.Node
 */
ve.dm.Node.prototype.getLength = function () {
	return this.length;
};
 
/**
 * Set the inner length of the node.
 *
 * This should only be called after a relevant change to the document data. Calling this method will
 * not change the document data.
 *
 * @param {number} length Length of content
 * @fires lengthChange
 * @fires update
 * @throws {Error} Invalid content length error if length is less than 0
 */
ve.dm.Node.prototype.setLength = function ( length ) {
	if ( length < 0 ) {
		throw new Error( 'Length cannot be negative' );
	}
	// Compute length adjustment from old length
	var diff = length - this.length;
	// Set new length
	this.length = length;
	// Adjust the parent's length
	if ( this.parent ) {
		this.parent.adjustLength( diff );
	}
	// Emit events
	this.emit( 'lengthChange', diff );
	this.emit( 'update' );
};
 
/**
 * Adjust the length.
 *
 * This should only be called after a relevant change to the document data. Calling this method will
 * not change the document data.
 *
 * @param {number} adjustment Amount to adjust length by
 * @fires lengthChange
 * @fires update
 * @throws {Error} Invalid adjustment error if resulting length is less than 0
 */
ve.dm.Node.prototype.adjustLength = function ( adjustment ) {
	this.setLength( this.length + adjustment );
};
 
/**
 * @inheritdoc ve.Node
 */
ve.dm.Node.prototype.getOffset = function () {
	if ( !this.parent ) {
		return 0;
	}
 
	if ( this.doc.isReadOnly() && this.offset !== null ) {
		return this.offset;
	}
 
	// Find our index in the parent and add up lengths while we do so
	var siblings = this.parent.children;
	var offset = this.parent.getOffset() + ( this.parent === this.root ? 0 : 1 );
	var i, len;
	for ( i = 0, len = siblings.length; i < len; i++ ) {
		if ( siblings[ i ] === this ) {
			break;
		}
		offset += siblings[ i ].getOuterLength();
	}
	Iif ( i === len ) {
		throw new Error( 'Node not found in parent\'s children array' );
	}
	if ( this.doc.isReadOnly() ) {
		// Cache offset, only used in read-only mode (when the offset can't change)
		// This cache is additionally cleared when leaving read-only mode in ve.dm.Document#setReadOnly
		this.offset = offset;
	}
	return offset;
};
 
/**
 * Check if the node can be merged with another.
 *
 * For two nodes to be mergeable, the two nodes must either be the same node or:
 *  - Are comparable according to #compareForMerging (by default, have the same type)
 *  - Have the same depth
 *  - Have similar ancestry (each node upstream must have the same type)
 *
 * @param {ve.dm.Node} node Node to consider merging with
 * @return {boolean} Nodes can be merged
 */
ve.dm.Node.prototype.canBeMergedWith = function ( node ) {
	var n1 = this,
		n2 = node;
 
	// Content node can be merged with node that can contain content, for instance: TextNode
	// and ParagraphNode. When this method is called for such case (one node is a content node and
	// the other one can contain content) make sure to start traversal from node that can contain
	// content (instead of content node itself).
	if ( n1.canContainContent() && n2.isContent() ) {
		n2 = n2.getParent();
	} else if ( n2.canContainContent() && n1.isContent() ) {
		n1 = n1.getParent();
	}
	// Move up from n1 and n2 simultaneously until we find a common ancestor
	while ( n1 !== n2 ) {
		if (
			// Check if we have reached a root (means there's no common ancestor or unequal depth)
			( n1 === null || n2 === null ) ||
			// Ensure that nodes are comparable for merging
			!n1.compareForMerging( n2 )
		) {
			return false;
		}
		// Move up
		n1 = n1.getParent();
		n2 = n2.getParent();
	}
	return true;
};
 
/**
 * Compare with another node for merging (see #canBeMergedWidth)
 *
 * The default implementation just compares node types.
 *
 * @param {ve.dm.Node} otherNode Other node to compare with
 * @return {boolean} Nodes are comparable
 */
ve.dm.Node.prototype.compareForMerging = function ( otherNode ) {
	return this.getType() === otherNode.getType();
};