Merge "Refactor out data processing from ve.dm.Document constructor"

This commit is contained in:
jenkins-bot 2013-10-07 16:36:02 +00:00 committed by Gerrit Code Review
commit ac0f2ba092
4 changed files with 199 additions and 141 deletions

View file

@ -839,7 +839,7 @@ ve.ce.Surface.prototype.afterPaste = function () {
pasteData = ve.copy( slice.getOriginalData() ); pasteData = ve.copy( slice.getOriginalData() );
// Annotate // Annotate
ve.dm.Document.addAnnotationsToData( pasteData, this.model.getInsertionAnnotations() ); ve.dm.Document.static.addAnnotationsToData( pasteData, this.model.getInsertionAnnotations() );
// Transaction // Transaction
tx = ve.dm.Transaction.newFromInsertion( tx = ve.dm.Transaction.newFromInsertion(
@ -853,7 +853,7 @@ ve.ce.Surface.prototype.afterPaste = function () {
pasteData = ve.copy( slice.getData() ); pasteData = ve.copy( slice.getData() );
// Annotate // Annotate
ve.dm.Document.addAnnotationsToData( pasteData, this.model.getInsertionAnnotations() ); ve.dm.Document.static.addAnnotationsToData( pasteData, this.model.getInsertionAnnotations() );
// Transaction // Transaction
tx = ve.dm.Transaction.newFromInsertion( tx = ve.dm.Transaction.newFromInsertion(
@ -1054,7 +1054,7 @@ ve.ce.Surface.prototype.onContentChange = function ( node, previous, next ) {
// Apply insertion annotations // Apply insertion annotations
annotations = this.model.getInsertionAnnotations(); annotations = this.model.getInsertionAnnotations();
if ( annotations instanceof ve.dm.AnnotationSet ) { if ( annotations instanceof ve.dm.AnnotationSet ) {
ve.dm.Document.addAnnotationsToData( data, this.model.getInsertionAnnotations() ); ve.dm.Document.static.addAnnotationsToData( data, this.model.getInsertionAnnotations() );
} }
this.incRenderLock(); this.incRenderLock();
try { try {
@ -1135,7 +1135,7 @@ ve.ce.Surface.prototype.onContentChange = function ( node, previous, next ) {
} }
} }
} }
ve.dm.Document.addAnnotationsToData( data, annotations ); ve.dm.Document.static.addAnnotationsToData( data, annotations );
} }
newRange = next.range; newRange = next.range;
if ( newRange.isCollapsed() ) { if ( newRange.isCollapsed() ) {

View file

@ -16,7 +16,8 @@
* @class * @class
* @extends ve.Document * @extends ve.Document
* @constructor * @constructor
* @param {HTMLDocument|Array|ve.dm.FlatLinearData} documentOrData HTML document, raw linear model data or FlatLinearData to start with * @param {HTMLDocument|Array|ve.dm.ElementLinearData|ve.dm.FlatLinearData} documentOrData HTML document,
* raw linear model data, ElementLinearData or FlatLinearData to be split
* @param {ve.dm.Document} [parentDocument] Document to use as root for created nodes * @param {ve.dm.Document} [parentDocument] Document to use as root for created nodes
* @param {ve.dm.InternalList} [internalList] Internal list to clone; passed when creating a document slice * @param {ve.dm.InternalList} [internalList] Internal list to clone; passed when creating a document slice
*/ */
@ -25,21 +26,11 @@ ve.dm.Document = function VeDmDocument( documentOrData, parentDocument, internal
ve.Document.call( this, new ve.dm.DocumentNode() ); ve.Document.call( this, new ve.dm.DocumentNode() );
// Initialization // Initialization
/* var fullData, result,
* Build a tree of nodes and nodes that will be added to them after a full scan is complete, split = true,
* then from the bottom up add nodes to their potential parents. This avoids massive length
* updates being broadcast upstream constantly while building is underway.
*/
var i, len, offset, node, children, meta, fullData,
doc = parentDocument || this, doc = parentDocument || this,
root = this.getDocumentNode(), root = this.getDocumentNode();
textLength = 0,
inTextNode = false,
// Stack of stacks, each containing a
stack = [[this.documentNode], []],
currentStack = stack[1],
parentStack = stack[0],
currentNode = this.documentNode;
this.documentNode.setRoot( root ); this.documentNode.setRoot( root );
this.documentNode.setDocument( doc ); this.documentNode.setDocument( doc );
this.internalList = internalList ? internalList.clone( this ) : new ve.dm.InternalList( this ); this.internalList = internalList ? internalList.clone( this ) : new ve.dm.InternalList( this );
@ -48,11 +39,18 @@ ve.dm.Document = function VeDmDocument( documentOrData, parentDocument, internal
this.parentDocument = parentDocument; this.parentDocument = parentDocument;
this.completeHistory = []; this.completeHistory = [];
if ( documentOrData instanceof ve.dm.FlatLinearData ) { if ( documentOrData instanceof ve.dm.ElementLinearData ) {
// Pre-split ElementLinearData
split = false;
fullData = documentOrData;
} else if ( documentOrData instanceof ve.dm.FlatLinearData ) {
// Element + Meta linear data
fullData = documentOrData; fullData = documentOrData;
} else if ( !ve.isArray( documentOrData ) && typeof documentOrData === 'object' ) { } else if ( !ve.isArray( documentOrData ) && typeof documentOrData === 'object' ) {
// HTMLDocument
fullData = ve.dm.converter.getDataFromDom( documentOrData, new ve.dm.IndexValueStore(), this.getInternalList() ); fullData = ve.dm.converter.getDataFromDom( documentOrData, new ve.dm.IndexValueStore(), this.getInternalList() );
} else { } else {
// Raw linear model data
fullData = new ve.dm.FlatLinearData( fullData = new ve.dm.FlatLinearData(
new ve.dm.IndexValueStore(), new ve.dm.IndexValueStore(),
ve.isArray( documentOrData ) ? documentOrData : [] ve.isArray( documentOrData ) ? documentOrData : []
@ -60,121 +58,9 @@ ve.dm.Document = function VeDmDocument( documentOrData, parentDocument, internal
} }
this.store = fullData.getStore(); this.store = fullData.getStore();
this.data = new ve.dm.ElementLinearData( this.getStore() ); result = this.constructor.static.splitData( fullData, split, true, this.documentNode );
// Sparse array containing the metadata for each offset this.data = result.elementData;
// Each element is either undefined, or an array of metadata elements this.metadata = result.metaData || new ve.dm.MetaLinearData( this.data.getStore(), new Array( 1 + this.data.getLength() ) );
// Because the indexes in the metadata array represent offsets in the data array, the
// metadata array has one element more than the data array.
this.metadata = new ve.dm.MetaLinearData( this.getStore() );
// Separate element data and metadata and build node tree
for ( i = 0, len = fullData.getLength(); i < len; i++ ) {
// Infer that if an item in the linear model has a type attribute than it must be an element
if ( !fullData.isElementData( i ) ) {
// Text node opening
if ( !inTextNode ) {
// Create a lengthless text node
node = new ve.dm.TextNode();
node.setDocument( doc );
// Put the node on the current inner stack
currentStack.push( node );
currentNode = node;
// Set a flag saying we're inside a text node
inTextNode = true;
}
// Track the length
textLength++;
// Add to element linear data
this.data.push( fullData.getData( i ) );
} else {
// Element data
if ( fullData.isOpenElementData( i ) &&
ve.dm.metaItemFactory.lookup( fullData.getType( i ) )
) {
// Metadata
meta = fullData.getData( i );
offset = this.data.getLength();
// Put the metadata in the meta-linmod
if ( !this.metadata.getData( offset ) ) {
this.metadata.setData( offset, [] );
}
this.metadata.getData( offset ).push( meta );
// Skip close element
i++;
continue;
}
// Add to element linear data
this.data.push( fullData.getData( i ) );
// Text node closing
if ( inTextNode ) {
// Finish the text node by setting the length
currentNode.setLength( textLength );
// Put the state variables back as they were
currentNode = parentStack[parentStack.length - 1];
inTextNode = false;
textLength = 0;
}
// Element open/close
if ( fullData.isOpenElementData( i ) ) {
// Branch or leaf node opening
// Create a childless node
node = ve.dm.nodeFactory.create(
fullData.getType( i ), [], fullData.getData( i )
);
node.setDocument( doc );
// Put the childless node on the current inner stack
currentStack.push( node );
if ( ve.dm.nodeFactory.canNodeHaveChildren( node.getType() ) ) {
// Create a new inner stack for this node
parentStack = currentStack;
currentStack = [];
stack.push( currentStack );
}
currentNode = node;
} else {
// Branch or leaf node closing
if ( ve.dm.nodeFactory.canNodeHaveChildren( currentNode.getType() ) ) {
// Pop this node's inner stack from the outer stack. It'll have all of the
// node's child nodes fully constructed
children = stack.pop();
currentStack = parentStack;
parentStack = stack[stack.length - 2];
if ( !parentStack ) {
// This can only happen if we got unbalanced data
throw new Error( 'Unbalanced input passed to document' );
}
// Attach the children to the node
ve.batchSplice( currentNode, 0, 0, children );
}
currentNode = parentStack[parentStack.length - 1];
}
}
}
// Pad out the metadata length to element data length + 1
if ( this.metadata.getLength() < this.data.getLength() + 1 ) {
this.metadata.data = this.metadata.data.concat(
new Array( 1 + this.data.getLength() - this.metadata.getLength() )
);
}
if ( inTextNode ) {
// Text node ended by end-of-input rather than by an element
currentNode.setLength( textLength );
// Don't bother updating currentNode et al, we don't use them below
}
// State variable that allows nodes to know that they are being
// appended in order. Used by ve.dm.InternalList.
this.buildingNodeTree = true;
// The end state is stack = [ [this.documentNode] [ array, of, its, children ] ]
// so attach all nodes in stack[1] to the root node
ve.batchSplice( this.documentNode, 0, 0, stack[1] );
this.buildingNodeTree = false;
}; };
/* Inheritance */ /* Inheritance */
@ -190,6 +76,173 @@ ve.inheritClass( ve.dm.Document, ve.Document );
/* Static methods */ /* Static methods */
ve.dm.Document.static = {};
/**
* Split data into element data and meta data. Also build a node tree if requried.
*
* @param {ve.dm.FlatLinearData} fullData Full data from converter
* @param {boolean} [split=false] Split out meta and element data, otherwise return fullData by reference
* @param {boolean} [keepMeta=false] Process and return metadata
* @param {ve.dm.Node} [parentNode] Parent node
* @returns {Object} Object containing element linear data and meta linear data (if processed)
*/
ve.dm.Document.static.splitData = function( fullData, split, keepMeta, parentNode ) {
var i, len, offset, node, children, meta, elementData, metaData,
currentStack, parentStack, nodeStack, currentNode, doc,
textLength = 0,
inTextNode = false;
if ( split ) {
elementData = new ve.dm.ElementLinearData( fullData.getStore() );
if ( keepMeta ) {
// Sparse array containing the metadata for each offset
// Each element is either undefined, or an array of metadata elements
// Because the indexes in the metadata array represent offsets in the data array, the
// metadata array has one element more than the data array.
metaData = new ve.dm.MetaLinearData( fullData.getStore() );
}
} else {
// If metadata is not being split out, just return fullData as elementData
elementData = fullData;
}
if ( parentNode ) {
// Build a tree of nodes and nodes that will be added to them after a full scan is complete,
// then from the bottom up add nodes to their potential parents. This avoids massive length
// updates being broadcast upstream constantly while building is underway.
currentStack = [];
parentStack = [parentNode];
// Stack of stacks
nodeStack = [parentStack, currentStack];
currentNode = parentNode;
doc = parentNode.getDocument();
}
// Separate element data and metadata and build node tree
for ( i = 0, len = fullData.getLength(); i < len; i++ ) {
if ( !fullData.isElementData( i ) ) {
if ( split ) {
// Add to element linear data
elementData.push( fullData.getData( i ) );
}
if ( parentNode ) {
// Text node opening
if ( !inTextNode ) {
// Create a lengthless text node
node = new ve.dm.TextNode();
node.setDocument( doc );
// Put the node on the current inner stack
currentStack.push( node );
currentNode = node;
// Set a flag saying we're inside a text node
inTextNode = true;
}
// Track the length
textLength++;
}
} else {
if ( split ) {
// Element data
if ( fullData.isOpenElementData( i ) &&
ve.dm.metaItemFactory.lookup( fullData.getType( i ) )
) {
if ( keepMeta ) {
// Metadata
meta = fullData.getData( i );
offset = elementData.getLength();
// Put the meta data in the meta-linmod
if ( !metaData.getData( offset ) ) {
metaData.setData( offset, [] );
}
metaData.getData( offset ).push( meta );
}
// Skip close element
i++;
continue;
}
// Add to element linear data
elementData.push( fullData.getData( i ) );
}
if ( parentNode ) {
// Text node closing
if ( inTextNode ) {
// Finish the text node by setting the length
currentNode.setLength( textLength );
// Put the state variables back as they were
currentNode = parentStack[parentStack.length - 1];
inTextNode = false;
textLength = 0;
}
// Element open/close
if ( fullData.isOpenElementData( i ) ) {
// Branch or leaf node opening
// Create a childless node
node = ve.dm.nodeFactory.create(
fullData.getType( i ), [], fullData.getData( i )
);
node.setDocument( doc );
// Put the childless node on the current inner stack
currentStack.push( node );
if ( ve.dm.nodeFactory.canNodeHaveChildren( node.getType() ) ) {
// Create a new inner stack for this node
parentStack = currentStack;
currentStack = [];
nodeStack.push( currentStack );
}
currentNode = node;
} else {
// Branch or leaf node closing
if ( ve.dm.nodeFactory.canNodeHaveChildren( currentNode.getType() ) ) {
// Pop this node's inner stack from the outer stack. It'll have all of the
// node's child nodes fully constructed
children = nodeStack.pop();
currentStack = parentStack;
parentStack = nodeStack[nodeStack.length - 2];
if ( !parentStack ) {
// This can only happen if we got unbalanced data
throw new Error( 'Unbalanced input passed to document' );
}
// Attach the children to the node
ve.batchSplice( currentNode, 0, 0, children );
}
currentNode = parentStack[parentStack.length - 1];
}
}
}
}
// Pad out the metadata length to element data length + 1
if ( split && keepMeta && metaData.getLength() < elementData.getLength() + 1 ) {
metaData.data = metaData.data.concat(
new Array( 1 + elementData.getLength() - metaData.getLength() )
);
}
if ( parentNode ) {
if ( inTextNode ) {
// Text node ended by end-of-input rather than by an element
currentNode.setLength( textLength );
// Don't bother updating currentNode et al, we don't use them below
}
// State variable that allows nodes to know that they are being
// appended in order. Used by ve.dm.InternalList.
doc.buildingNodeTree = true;
// The end state is stack = [ [this.documentNode] [ array, of, its, children ] ]
// so attach all nodes in stack[1] to the root node
ve.batchSplice( parentNode, 0, 0, currentStack );
doc.buildingNodeTree = false;
}
return {
'elementData': elementData,
'metaData': metaData
};
};
/** /**
* Apply annotations to content data. * Apply annotations to content data.
* *
@ -199,7 +252,7 @@ ve.inheritClass( ve.dm.Document, ve.Document );
* @param {Array} data Data to apply annotations to * @param {Array} data Data to apply annotations to
* @param {ve.dm.AnnotationSet} annotationSet Annotations to apply * @param {ve.dm.AnnotationSet} annotationSet Annotations to apply
*/ */
ve.dm.Document.addAnnotationsToData = function ( data, annotationSet ) { ve.dm.Document.static.addAnnotationsToData = function ( data, annotationSet ) {
var i, length, newAnnotationSet, store = annotationSet.getStore(); var i, length, newAnnotationSet, store = annotationSet.getStore();
if ( annotationSet.isEmpty() ) { if ( annotationSet.isEmpty() ) {
// Nothing to do // Nothing to do

View file

@ -574,7 +574,7 @@ ve.dm.SurfaceFragment.prototype.insertContent = function ( content, annotate ) {
if ( annotate ) { if ( annotate ) {
annotations = this.document.data.getAnnotationsFromOffset( this.getRange( true ).start - 1 ); annotations = this.document.data.getAnnotationsFromOffset( this.getRange( true ).start - 1 );
if ( annotations.getLength() > 0 ) { if ( annotations.getLength() > 0 ) {
ve.dm.Document.addAnnotationsToData( content, annotations ); ve.dm.Document.static.addAnnotationsToData( content, annotations );
} }
} }
tx = ve.dm.Transaction.newFromInsertion( this.document, this.getRange( true ).start, content ); tx = ve.dm.Transaction.newFromInsertion( this.document, this.getRange( true ).start, content );

View file

@ -9,8 +9,9 @@ QUnit.module( 've.dm.Document' );
/* Tests */ /* Tests */
QUnit.test( 'constructor', 8, function ( assert ) { QUnit.test( 'constructor', 9, function ( assert ) {
var doc = ve.dm.example.createExampleDocument(); var data,
doc = ve.dm.example.createExampleDocument();
assert.equalNodeTree( doc.getDocumentNode(), ve.dm.example.tree, 'node tree matches example data' ); assert.equalNodeTree( doc.getDocumentNode(), ve.dm.example.tree, 'node tree matches example data' );
assert.throws( assert.throws(
function () { function () {
@ -23,7 +24,6 @@ QUnit.test( 'constructor', 8, function ( assert ) {
'unbalanced input causes exception' 'unbalanced input causes exception'
); );
// TODO data provider?
doc = new ve.dm.Document( [ 'a', 'b', 'c', 'd' ] ); doc = new ve.dm.Document( [ 'a', 'b', 'c', 'd' ] );
assert.equalNodeTree( assert.equalNodeTree(
doc.getDocumentNode(), doc.getDocumentNode(),
@ -34,12 +34,17 @@ QUnit.test( 'constructor', 8, function ( assert ) {
'sparse metadata array is created' 'sparse metadata array is created'
); );
doc = new ve.dm.Document( [ { 'type': 'paragraph' }, { 'type': '/paragraph' } ] ); data = new ve.dm.ElementLinearData(
new ve.dm.IndexValueStore(),
[ { 'type': 'paragraph' }, { 'type': '/paragraph' } ]
);
doc = new ve.dm.Document( data );
assert.equalNodeTree( assert.equalNodeTree(
doc.getDocumentNode(), doc.getDocumentNode(),
new ve.dm.DocumentNode( [ new ve.dm.ParagraphNode( [], { 'type': 'paragraph' } ) ] ), new ve.dm.DocumentNode( [ new ve.dm.ParagraphNode( [], { 'type': 'paragraph' } ) ] ),
'empty paragraph no longer has a text node' 'empty paragraph no longer has a text node'
); );
assert.equal( doc.data, data, 'ElementLinearData is stored by reference' );
doc = ve.dm.example.createExampleDocument( 'withMeta' ); doc = ve.dm.example.createExampleDocument( 'withMeta' );
assert.deepEqualWithDomElements( doc.getData(), ve.dm.example.withMetaPlainData, assert.deepEqualWithDomElements( doc.getData(), ve.dm.example.withMetaPlainData,