Change ve.dm.DocumentSlice to a mixin to ve.dm.LinearData

Document slice only ever contained linear data, with extra functionality
to preserve the range. It pre-dated LinearData, but now we should
refactor it to reflect its purpose.

Change-Id: Ifc908f7526c83a43a51372c8d2494d7260e7facd
This commit is contained in:
Ed Sanders 2013-10-03 12:32:02 +01:00
parent 1957eb3e28
commit 5c31d3215b
10 changed files with 130 additions and 89 deletions

View file

@ -37,7 +37,6 @@
"ve.dm.ModelRegistry",
"ve.dm.Converter",
"ve.dm.DataString",
"ve.dm.DocumentSlice",
"ve.dm.DocumentSynchronizer",
"ve.dm.IndexValueStore",
"ve.dm.NodeFactory",
@ -66,7 +65,7 @@
},
{
"name": "Linear data",
"classes": ["ve.dm.*LinearData"]
"classes": ["ve.dm.*LinearData*"]
},
{
"name": "Models",

View file

@ -294,11 +294,11 @@ $wgResourceModules += array(
've/dm/ve.dm.DataString.js',
've/dm/ve.dm.Document.js',
've/dm/ve.dm.LinearData.js',
've/dm/ve.dm.DocumentSlice.js',
've/dm/ve.dm.DocumentSynchronizer.js',
've/dm/ve.dm.IndexValueStore.js',
've/dm/ve.dm.Converter.js',
've/dm/lineardata/ve.dm.SlicedLinearData.js',
've/dm/lineardata/ve.dm.ElementLinearData.js',
've/dm/lineardata/ve.dm.MetaLinearData.js',

View file

@ -147,10 +147,10 @@ $html = file_get_contents( $page );
<script src="../../modules/ve/dm/ve.dm.DataString.js"></script>
<script src="../../modules/ve/dm/ve.dm.Document.js"></script>
<script src="../../modules/ve/dm/ve.dm.LinearData.js"></script>
<script src="../../modules/ve/dm/ve.dm.DocumentSlice.js"></script>
<script src="../../modules/ve/dm/ve.dm.DocumentSynchronizer.js"></script>
<script src="../../modules/ve/dm/ve.dm.IndexValueStore.js"></script>
<script src="../../modules/ve/dm/ve.dm.Converter.js"></script>
<script src="../../modules/ve/dm/lineardata/ve.dm.SlicedLinearData.js"></script>
<script src="../../modules/ve/dm/lineardata/ve.dm.ElementLinearData.js"></script>
<script src="../../modules/ve/dm/lineardata/ve.dm.MetaLinearData.js"></script>
<script src="../../modules/ve/dm/nodes/ve.dm.GeneratedContentNode.js"></script>

View file

@ -655,7 +655,7 @@ ve.ce.Surface.prototype.onCopy = function ( e ) {
clipboardIndex, clipboardItem,
scrollTop,
view = this,
slice = this.documentView.model.getSlice( this.model.getSelection() ),
slice = this.documentView.model.getSlicedLinearData( this.model.getSelection() ),
clipboardData = e.originalEvent.clipboardData,
$window = $( ve.Element.getWindow( this.$$.context ) );
@ -666,7 +666,7 @@ ve.ce.Surface.prototype.onCopy = function ( e ) {
ve.dm.converter.store = this.documentView.model.getStore();
ve.dm.converter.internalList = this.documentView.model.getInternalList();
ve.dm.converter.getDomSubtreeFromData( slice.getBalancedData(), this.$pasteTarget[0] );
ve.dm.converter.getDomSubtreeFromData( slice.getData(), this.$pasteTarget[0] );
clipboardItem = { 'data': slice, 'hash': null };
clipboardIndex = this.clipboard.push( clipboardItem ) - 1;
@ -813,7 +813,8 @@ ve.ce.Surface.prototype.afterPaste = function () {
if ( !beforePasteData.plain ) {
beforePasteData.plain = this.$pasteTarget.text();
}
slice = new ve.dm.DocumentSlice(
slice = new ve.dm.ElementLinearDataSlice(
new ve.dm.IndexValueStore(),
ve.splitClusters(
// TODO: handle plain text line breaks better
beforePasteData.plain.replace( /\n+/gm, ' ' )
@ -825,7 +826,7 @@ ve.ce.Surface.prototype.afterPaste = function () {
// Try to paste in the orignal data
// Take a copy to prevent the data being annotated a second time in the catch block
// and to prevent actions in the data model affecting view.clipboard
pasteData = ve.copy( slice.getData() );
pasteData = ve.copy( slice.getOriginalData() );
// Annotate
ve.dm.Document.addAnnotationsToData( pasteData, this.model.getInsertionAnnotations() );
@ -839,7 +840,7 @@ ve.ce.Surface.prototype.afterPaste = function () {
} catch ( err ) {
// If that fails, balance the data before pasting
// Take a copy to prevent actions in the data model affecting view.clipboard
pasteData = ve.copy( slice.getBalancedData() );
pasteData = ve.copy( slice.getData() );
// Annotate
ve.dm.Document.addAnnotationsToData( pasteData, this.model.getInsertionAnnotations() );

View file

@ -1,5 +1,5 @@
/*!
* VisualEditor ElementLinearData class.
* VisualEditor ElementLinearData classes.
*
* Class containing element linear data and an index-value store.
*
@ -778,3 +778,27 @@ ve.dm.ElementLinearData.prototype.remapInteralListIndexes = function ( mapping )
}
}
};
/**
* Sliced element linear data storage
*
* @class
* @extends ve.dm.ElementLinearData
* @mixins ve.dm.SlicedLinearData
* @constructor
* @param {ve.dm.IndexValueStore} store Index-value store
* @param {Array} [data] Linear data
* @param {ve.Range} [range] Original context within data
*/
ve.dm.ElementLinearDataSlice = function VeDmElementLinearDataSlice( store, data, range ) {
ve.dm.ElementLinearData.call( this, store, data );
// Mixins
ve.dm.SlicedLinearData.call( this, range );
};
/* Inheritance */
ve.inheritClass( ve.dm.ElementLinearDataSlice, ve.dm.ElementLinearData );
ve.mixinClass( ve.dm.ElementLinearDataSlice, ve.dm.SlicedLinearData );

View file

@ -0,0 +1,58 @@
/*!
* VisualEditor DataModel SlicedLinearData class.
*
* @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* Sliced linear data storage.
*
* @abstract
* @constructor
* @param {ve.Range} [range] Original context within data
*/
ve.dm.SlicedLinearData = function VeDmSlicedLinearData( range ) {
// Clone data
this.data = ve.copy( this.data );
// Properties
this.range = range || new ve.Range( 0, this.data.length );
};
/* Methods */
/**
* Get the range representing the original context within the data
*
* @returns {ve.Range} Original context within data
*/
ve.dm.SlicedLinearData.prototype.getRange = function () {
return this.range;
};
/**
* Get the original data excluding any balancing data that was added.
*
* @method
* @param {number} [offset] Offset to get data from
* @returns {Object|Array} Data from index, or all data (by reference)
*/
ve.dm.SlicedLinearData.prototype.getOriginalData = function ( offset ) {
return offset === undefined ? this.data.slice( this.range.start, this.range.end ) : this.data[this.range.start + offset];
};
/**
* Run all elements in this document slice through getClonedElement(). This should be done if
* you intend to insert the sliced data back into the document as a copy of the original data
* (e.g. for copy and paste).
*/
ve.dm.SlicedLinearData.prototype.cloneElements = function () {
var i, len, node;
for ( i = 0, len = this.getLength(); i < len; i++ ) {
if ( this.isOpenElementData( i ) ) {
node = ve.dm.nodeFactory.create( this.getType( i ), [], this.getData( i ) );
this.data[i] = node.getClonedElement();
}
}
};

View file

@ -824,24 +824,24 @@ ve.dm.Document.prototype.fixupInsertion = function ( data, offset ) {
/**
* Get the document data for a range.
*
* Data will be fixed up so that unopened closings and unclosed openings in the document data slice
* are balanced.
* Data will be fixed up so that unopened closings and unclosed openings in the
* linear data slice are balanced.
*
* @param {ve.Range} range Range to get contents of
* @returns {ve.dm.DocumentSlice} Balanced slice of linear model data
* @returns {ve.dm.ElementLinearDataSlice} Balanced slice of linear model data
*/
ve.dm.Document.prototype.getSlice = function ( range ) {
ve.dm.Document.prototype.getSlicedLinearData = function ( range ) {
var first, last, firstNode, lastNode,
node = this.getNodeFromOffset( range.start ),
selection = this.selectNodes( range, 'siblings' ),
addOpenings = [],
addClosings = [];
if ( selection.length === 0 ) {
return new ve.dm.DocumentSlice( [] );
return new ve.dm.ElementLinearDataSlice( this.getStore(), [] );
}
if ( selection.length === 1 && selection[0].range && selection[0].range.equalsSelection( range ) ) {
// Nothing to fix up
return new ve.dm.DocumentSlice( this.data.slice( range.start, range.end ) );
return new ve.dm.ElementLinearDataSlice( this.getStore(), this.data.slice( range.start, range.end ) );
}
first = selection[0];
@ -882,7 +882,8 @@ ve.dm.Document.prototype.getSlice = function ( range ) {
}
}
return new ve.dm.DocumentSlice(
return new ve.dm.ElementLinearDataSlice(
this.getStore(),
addOpenings.reverse()
.concat( this.data.slice( range.start, range.end ) )
.concat( addClosings ),

View file

@ -1,56 +0,0 @@
/*!
* VisualEditor DataModel DocumentSlice class.
*
* @copyright 2011-2013 VisualEditor Team and others; see AUTHORS.txt
* @license The MIT License (MIT); see LICENSE.txt
*/
/**
* DataModel document slice.
*
* @constructor
* @param {Array} data Balanced sliced data (will be deep copied internally)
* @param {ve.Range} [range] Original context within data
*/
ve.dm.DocumentSlice = function VeDmDocumentSlice( data, range ) {
// Properties
this.data = ve.copy( data );
this.range = range || new ve.Range( 0, data.length );
};
/* Methods */
/**
* Get a deep copy of the sliced data.
*
* @method
* @returns {Array} Document data
*/
ve.dm.DocumentSlice.prototype.getData = function () {
return this.data.slice( this.range.start, this.range.end );
};
/**
* Get a balanced version of the sliced data.
*
* @method
* @returns {Array} Document data
*/
ve.dm.DocumentSlice.prototype.getBalancedData = function () {
return this.data.slice( 0 );
};
/**
* Run all elements in this document slice through getClonedElement(). This should be done if
* you intend to insert the sliced data back into the document as a copy of the original data
* (e.g. for copy and paste).
*/
ve.dm.DocumentSlice.prototype.cloneElements = function () {
var i, len, node;
for ( i = 0, len = this.data.length; i < len; i++ ) {
if ( this.data[i].type && this.data[i].type.charAt( 0 ) !== '/' ) {
node = ve.dm.nodeFactory.create( this.data[i].type, [], this.data[i] );
this.data[i] = node.getClonedElement();
}
}
};

View file

@ -271,19 +271,21 @@ QUnit.test( 'selectNodes', function ( assert ) {
} );
QUnit.test( 'getSlice', function ( assert ) {
var i, data, doc = ve.dm.example.createExampleDocument(),
var i, expectedData, doc = ve.dm.example.createExampleDocument(),
cases = [
{
'msg': 'empty range',
'range': new ve.Range( 2, 2 ),
'expected': []
'expected': [],
'expectedRange': new ve.Range( 0 )
},
{
'msg': 'range with one character',
'range': new ve.Range( 2, 3 ),
'expected': [
['b', [ ve.dm.example.bold ]]
]
],
'expectedRange': new ve.Range( 0, 1 )
},
{
'msg': 'range with two characters',
@ -291,7 +293,8 @@ QUnit.test( 'getSlice', function ( assert ) {
'expected': [
['b', [ ve.dm.example.bold ]],
['c', [ ve.dm.example.italic ]]
]
],
'expectedRange': new ve.Range( 0, 2 )
},
{
'msg': 'range with two characters and a header closing',
@ -301,7 +304,8 @@ QUnit.test( 'getSlice', function ( assert ) {
['b', [ ve.dm.example.bold ]],
['c', [ ve.dm.example.italic ]],
{ 'type': '/heading' }
]
],
'expectedRange': new ve.Range( 1, 4 )
},
{
'msg': 'range with one character, a header closing and a table opening',
@ -312,7 +316,8 @@ QUnit.test( 'getSlice', function ( assert ) {
{ 'type': '/heading' },
{ 'type': 'table' },
{ 'type': '/table' }
]
],
'expectedRange': new ve.Range( 1, 4 )
},
{
'msg': 'range from a paragraph into a list',
@ -328,7 +333,8 @@ QUnit.test( 'getSlice', function ( assert ) {
{ 'type': '/paragraph' },
{ 'type': '/listItem' },
{ 'type': '/list' }
]
],
'expectedRange': new ve.Range( 1, 7 )
},
{
'msg': 'range from a paragraph inside a nested list into the next list',
@ -347,7 +353,8 @@ QUnit.test( 'getSlice', function ( assert ) {
{ 'type': '/list' },
{ 'type': 'list', 'attributes': { 'style': 'number' } },
{ 'type': '/list' }
]
],
'expectedRange': new ve.Range( 5, 12 )
},
{
'msg': 'range from a paragraph inside a nested list out of both lists',
@ -364,7 +371,8 @@ QUnit.test( 'getSlice', function ( assert ) {
{ 'type': '/list' },
{ 'type': '/listItem' },
{ 'type': '/list' }
]
],
'expectedRange': new ve.Range( 5, 11 )
},
{
'msg': 'range from a paragraph inside a nested list out of the outer listItem',
@ -379,16 +387,22 @@ QUnit.test( 'getSlice', function ( assert ) {
{ 'type': '/listItem' },
{ 'type': '/list' },
{ 'type': '/listItem' }
]
],
'expectedRange': new ve.Range( 4, 9 )
}
];
QUnit.expect( cases.length );
QUnit.expect( 2 * cases.length );
for ( i = 0; i < cases.length; i++ ) {
data = ve.dm.example.preprocessAnnotations( cases[i].expected.slice(), doc.getStore() );
expectedData = ve.dm.example.preprocessAnnotations( cases[i].expected.slice(), doc.getStore() ).getData();
assert.deepEqual(
doc.getSlice( cases[i].range ).getBalancedData(),
data.getData(),
cases[i].msg
doc.getSlicedLinearData( cases[i].range ).getData(),
expectedData,
cases[i].msg + ': balanced data'
);
assert.deepEqual(
doc.getSlicedLinearData( cases[i].range ).getRange(),
cases[i].expectedRange,
cases[i].msg + ': range'
);
}
} );

View file

@ -92,10 +92,10 @@
<script src="../../ve/dm/ve.dm.DataString.js"></script>
<script src="../../ve/dm/ve.dm.Document.js"></script>
<script src="../../ve/dm/ve.dm.LinearData.js"></script>
<script src="../../ve/dm/ve.dm.DocumentSlice.js"></script>
<script src="../../ve/dm/ve.dm.DocumentSynchronizer.js"></script>
<script src="../../ve/dm/ve.dm.IndexValueStore.js"></script>
<script src="../../ve/dm/ve.dm.Converter.js"></script>
<script src="../../ve/dm/lineardata/ve.dm.SlicedLinearData.js"></script>
<script src="../../ve/dm/lineardata/ve.dm.ElementLinearData.js"></script>
<script src="../../ve/dm/lineardata/ve.dm.MetaLinearData.js"></script>
<script src="../../ve/dm/nodes/ve.dm.GeneratedContentNode.js"></script>