mediawiki-extensions-Visual.../modules/ve2/dm/ve.dm.Node.js
Trevor Parscal 32bddaf088 Added ve.dm.Transaction.newFromRemoval
Also:
* Refactored tests
* Added tests for ve.dm.Transaction.newFromInsertion
* Added tests for ve.dm.Transaction.newFromRemoval
* Fixed problems with ve.dm.Transaction.newFromInsertion
* Added ve.dm.Node.canBeMergedWith which is partially a port of ve.Node.getCommonAncestorPaths merged with canMerge from within ve.dm.DocumentNode.prepareRemoval from the old ve codebase

Change-Id: Ibbc3887d08286d8ab33fd6296487802d65b319fa
2012-05-31 14:39:34 -07:00

198 lines
4.7 KiB
JavaScript

/**
* Generic DataModel node.
*
* @class
* @abstract
* @constructor
* @extends {ve.Node}
* @param {String} type Symbolic name of node type
* @param {Integer} [length] Length of content data in document
* @param {Object} [attributes] Reference to map of attribute key/value pairs
*/
ve.dm.Node = function( type, length, attributes ) {
// Inheritance
ve.Node.call( this, type );
// Properties
this.length = length || 0;
this.attributes = attributes || {};
this.doc = undefined;
};
/* Methods */
/**
* Gets a list of allowed child node types.
*
* @method
* @returns {String[]|null} List of node types allowed as children or null if any type is allowed
*/
ve.dm.Node.prototype.getChildNodeTypes = function() {
return ve.dm.factory.getChildNodeTypes( this.type );
};
/**
* Gets a list of allowed parent node types.
*
* @method
* @returns {String[]|null} List of node types allowed as parents or null if any type is allowed
*/
ve.dm.Node.prototype.getParentNodeTypes = function() {
return ve.dm.factory.getParentNodeTypes( this.type );
};
/**
* Checks if this node can have child nodes.
*
* @method
* @returns {Boolean} Node can have children
*/
ve.dm.Node.prototype.canHaveChildren = function() {
return ve.dm.factory.canNodeHaveChildren( this.type );
};
/**
* Checks if this node can have child nodes which can also have child nodes.
*
* @method
* @returns {Boolean} Node can have grandchildren
*/
ve.dm.Node.prototype.canHaveGrandchildren = function() {
return ve.dm.factory.canNodeHaveGrandchildren( this.type );
};
/**
* Checks if this node represents a wrapped element in the linear model.
*
* @method
* @returns {Boolean} Node represents a wrapped element
*/
ve.dm.Node.prototype.isWrapped = function() {
return ve.dm.factory.isNodeWrapped( this.type );
};
/**
* Checks if this node can contain content.
*
* @method
* @returns {Boolean} Node can contain content
*/
ve.dm.Node.prototype.canContainContent = function() {
return ve.dm.factory.canNodeContainContent( this.type );
};
/**
* Checks if this node is content.
*
* @method
* @returns {Boolean} Node is content
*/
ve.dm.Node.prototype.isContent = function() {
return ve.dm.factory.isNodeContent( this.type );
};
/**
* Gets the inner length.
*
* @method
* @returns {Integer} Length of the node's contents
*/
ve.dm.Node.prototype.getLength = function() {
return this.length;
};
/**
* Gets the outer length, including any opening/closing elements.
*
* @method
* @returns {Integer} Length of the entire node
*/
ve.dm.Node.prototype.getOuterLength = function() {
return this.length + ( this.isWrapped() ? 2 : 0 );
};
/**
* Sets the inner length.
*
* @method
* @param {Integer} length Length of content
* @throws Invalid content length error if length is less than 0
* @emits lengthChange (diff)
* @emits update
*/
ve.dm.Node.prototype.setLength = function( length ) {
if ( length < 0 ) {
throw '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.
*
* @method
* @param {Integer} adjustment Amount to adjust length by
* @throws Invalid adjustment error if resulting length is less than 0
* @emits lengthChange (diff)
* @emits update
*/
ve.dm.Node.prototype.adjustLength = function( adjustment ) {
this.setLength( this.length + adjustment );
};
/**
* Gets an element attribute value.
*
* @method
* @returns {Mixed} Value of attribute, or undefined if no such attribute exists
*/
ve.dm.Node.prototype.getAttribute = function( key ) {
return this.attributes[key];
};
/**
* Checks if this node can be merged with another.
*
* For two nodes to be mergeable, this node and the given node must either be the same node or:
* - Have the same type
* - Have the same depth
* - Have similar ancestory (each node upstream must have the same type)
*
* @method
* @param {ve.dm.Node} node Node to consider merging with
* @returns {Boolean} Nodes can be merged
*/
ve.dm.Node.prototype.canBeMergedWith = function( node ) {
var n1 = this,
n2 = node;
// 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 types match
n1.getType() !== n2.getType()
) {
return false;
}
// Move up
n1 = n1.getParent();
n2 = n2.getParent();
}
return true;
};
/* Inheritance */
ve.extendClass( ve.dm.Node, ve.Node );