r100763 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r100762‎ | r100763 | r100764 >
Date:23:44, 25 October 2011
Author:tparscal
Status:deferred
Tags:
Comment:
Added support for basic tree updates on insert and remove, and some tests for them.
Modified paths:
  • /trunk/parsers/wikidom/lib/hype/models/es.DocumentModel.js (modified) (history)
  • /trunk/parsers/wikidom/tests/hype/es.DocumentModel.test.js (modified) (history)

Diff [purge]

Index: trunk/parsers/wikidom/tests/hype/es.DocumentModel.test.js
@@ -499,7 +499,7 @@
500500 );
501501 } );
502502
503 -test( 'es.DocumentModel.commit, es.DocumentModel.rollback', 8, function() {
 503+test( 'es.DocumentModel.commit, es.DocumentModel.rollback', 10, function() {
504504 var documentModel = es.DocumentModel.newFromPlainObject( obj );
505505
506506 var elementAttributeChange = documentModel.prepareElementAttributeChange(
@@ -584,6 +584,18 @@
585585 );
586586
587587 // Test 6
 588+ deepEqual(
 589+ documentModel[0].getContent(),
 590+ [
 591+ 'a',
 592+ ['b', { 'type': 'bold', 'hash': '#bold' }],
 593+ ['c', { 'type': 'italic', 'hash': '#italic' }],
 594+ 'd'
 595+ ],
 596+ 'commit keeps model tree up to date'
 597+ );
 598+
 599+ // Test 7
588600 documentModel.rollback( insertion );
589601 deepEqual(
590602 documentModel.getData( new es.Range( 0, 5 ) ),
@@ -597,9 +609,20 @@
598610 'rollback reverses the effect of an insertion transaction on the content'
599611 );
600612
 613+ // Test 8
 614+ deepEqual(
 615+ documentModel[0].getContent(),
 616+ [
 617+ 'a',
 618+ ['b', { 'type': 'bold', 'hash': '#bold' }],
 619+ ['c', { 'type': 'italic', 'hash': '#italic' }]
 620+ ],
 621+ 'rollback keeps model tree up to date'
 622+ );
 623+
601624 var removal = documentModel.prepareRemoval( new es.Range( 2, 4 ) );
602625
603 - // Test 7
 626+ // Test 9
604627 documentModel.commit( removal );
605628 deepEqual(
606629 documentModel.getData( new es.Range( 0, 3 ) ),
@@ -611,7 +634,7 @@
612635 'commit applies a removal transaction to the content'
613636 );
614637
615 - // Test 8
 638+ // Test 10
616639 documentModel.rollback( removal );
617640 deepEqual(
618641 documentModel.getData( new es.Range( 0, 5 ) ),
Index: trunk/parsers/wikidom/lib/hype/models/es.DocumentModel.js
@@ -16,10 +16,13 @@
1717 // Properties
1818 node.data = $.isArray( data ) ? data : [];
1919 node.attributes = $.isPlainObject( attributes ) ? attributes : {};
20 -
21 - // Auto-generate tree
22 - this.growNodeTreeFromData( node, node.data );
23 -
 20+
 21+ // Auto-generate model tree
 22+ var nodes = es.DocumentModel.createNodesFromData( node.data );
 23+ for ( var i = 0; i < nodes.length; i++ ) {
 24+ node.push( nodes[i] );
 25+ }
 26+
2427 return node;
2528 };
2629
@@ -36,19 +39,86 @@
3740 * Each function is called in the context of a state, and takes an operation object as a parameter.
3841 */
3942 es.DocumentModel.operations = ( function() {
 43+ function containsElements( op ) {
 44+ for ( i = 0; i < op.data.length; i++ ) {
 45+ if ( op.data.type !== undefined ) {
 46+ return true;
 47+ }
 48+ }
 49+ return false;
 50+ }
 51+
 52+ function isStructural( offset ) {
 53+ var edge = offset === 0 || offset === this.data.length - 1,
 54+ elementLeft = this.data[offset - 1].type !== undefined,
 55+ elementRight = this.data[offset].type !== undefined;
 56+ if ( edge || ( elementLeft && elementRight ) ) {
 57+ return true;
 58+ }
 59+ return false;
 60+ }
 61+
4062 function retain( op ) {
4163 annotate.call( this, this.cursor + op.length );
4264 this.cursor += op.length;
4365 }
44 -
 66+
4567 function insert( op ) {
46 - es.spliceArray( this.data, this.cursor, op.data );
47 - annotate.call( this, this.cursor + op.data.length );
 68+ if ( isStructural( this.cursor ) ) {
 69+ // TODO: Support tree updates when inserting between elements
 70+ } else {
 71+ // Get the node we are about to insert into
 72+ var node = this.tree.getNodeFromOffset( this.cursor );
 73+ if ( containsElements( op ) ) {
 74+ var nodeParent = node.getParent();
 75+ if ( !nodeParent ) {
 76+ throw 'Missing parent error. Node does not have a parent node.';
 77+ }
 78+ var offset = this.tree.getOffsetFromNode( node ),
 79+ length = node.getElementLength() + op.data.length,
 80+ index = nodeParent.indexOf( node );
 81+ if ( index === -1 ) {
 82+ throw 'Missing child error. Node could not be found in it\'s parent node.';
 83+ }
 84+ // Remove the node we are about to insert into from the model tree
 85+ nodeParent.splice( index, 1 );
 86+ // Perform insert on linear data model
 87+ es.spliceArray( this.data, this.cursor, op.data );
 88+ annotate.call( this, this.cursor + op.data.length );
 89+ // Regenerate nodes for the data we've affected
 90+ var nodes = es.DocumentModel.createNodesFromData(
 91+ this.data.slice( offset, length )
 92+ );
 93+ // Insert new elements into the tree where the old one used to be
 94+ for ( var i = nodes.length; i >= 0; i-- ) {
 95+ this.tree.splice( index, nodes[i] );
 96+ }
 97+ } else {
 98+ // Perform insert on linear data model
 99+ es.spliceArray( this.data, this.cursor, op.data );
 100+ annotate.call( this, this.cursor + op.data.length );
 101+ // Update model tree
 102+ node.adjustContentLength( op.data.length );
 103+ }
 104+ }
48105 this.cursor += op.data.length;
49106 }
50107
51108 function remove( op ) {
52 - this.data.splice( this.cursor, op.data.length );
 109+ if ( isStructural( this.cursor ) && isStructural( this.cursor + op.data.length ) ) {
 110+ // TODO: Support tree updates when removing whole elements
 111+ } else {
 112+ if ( containsElements( op ) ) {
 113+ // TODO: Support tree updates when removing partial elements
 114+ } else {
 115+ // Get the node we are removing content from
 116+ var node = this.tree.getNodeFromOffset( this.cursor );
 117+ // Update model tree
 118+ node.adjustContentLength( -op.data.length );
 119+ // Remove content from linear data model
 120+ this.data.splice( this.cursor, op.data.length );
 121+ }
 122+ }
53123 }
54124
55125 function attribute( op, invert ) {
@@ -183,6 +253,56 @@
184254
185255 /* Static Methods */
186256
 257+/*
 258+ * Create child nodes from an array of data.
 259+ *
 260+ * These child nodes are used for the model tree, which is a space partitioning data structure in
 261+ * which each node contains the length of itself (1 for opening, 1 for closing) and the lengths of
 262+ * it's child nodes.
 263+ */
 264+es.DocumentModel.createNodesFromData = function( data ) {
 265+ var currentNode = new es.DocumentModelNode();
 266+ for ( var i = 0, length = data.length; i < length; i++ ) {
 267+ if ( data[i].type !== undefined ) {
 268+ // It's an element, figure out it's type
 269+ var element = data[i],
 270+ type = element.type,
 271+ open = type[0] !== '/';
 272+ // Trim the "/" off the beginning of closing tag types
 273+ if ( !open ) {
 274+ type = type.substr( 1 );
 275+ }
 276+ if ( open ) {
 277+ // Validate the element type
 278+ if ( !( type in es.DocumentModel.nodeModels ) ) {
 279+ throw 'Unsuported element error. No class registered for element type: ' + type;
 280+ }
 281+ // Create a model node for the element
 282+ var newNode = new es.DocumentModel.nodeModels[element.type]( element );
 283+ // Add the new model node as a child
 284+ currentNode.push( newNode );
 285+ // Descend into the new model node
 286+ currentNode = newNode;
 287+ } else {
 288+ // Return to the parent node
 289+ currentNode = currentNode.getParent();
 290+ }
 291+ } else {
 292+ // It's content, let's start tracking the length
 293+ var start = i;
 294+ // Move forward to the next object, tracking the length as we go
 295+ while ( data[i].type === undefined && i < length ) {
 296+ i++;
 297+ }
 298+ // Now we know how long the current node is
 299+ currentNode.setContentLength( i - start );
 300+ // The while loop left us 1 element to far
 301+ i--;
 302+ }
 303+ }
 304+ return currentNode.slice();
 305+};
 306+
187307 /**
188308 * Creates a document model from a plain object.
189309 *
@@ -407,54 +527,6 @@
408528 return deep ? es.copyArray( data ) : data;
409529 };
410530
411 -/*
412 - * Grow child nodes onto a root node from an array of data.
413 - *
414 - * A model tree is a space partitioning data structure in which each node contains the length of
415 - * itself (1 for opening, 1 for closing) and the lengths of it's child nodes.
416 - */
417 -es.DocumentModel.prototype.growNodeTreeFromData = function( root, data ) {
418 - var currentNode = root;
419 - for ( var i = 0, length = data.length; i < length; i++ ) {
420 - if ( data[i].type !== undefined ) {
421 - // It's an element, figure out it's type
422 - var element = data[i],
423 - type = element.type,
424 - open = type[0] !== '/';
425 - // Trim the "/" off the beginning of closing tag types
426 - if ( !open ) {
427 - type = type.substr( 1 );
428 - }
429 - if ( open ) {
430 - // Validate the element type
431 - if ( !( type in es.DocumentModel.nodeModels ) ) {
432 - throw 'Unsuported element error. No class registered for element type: ' + type;
433 - }
434 - // Create a model node for the element
435 - var newNode = new es.DocumentModel.nodeModels[element.type]( element );
436 - // Add the new model node as a child
437 - currentNode.push( newNode );
438 - // Descend into the new model node
439 - currentNode = newNode;
440 - } else {
441 - // Return to the parent node
442 - currentNode = currentNode.getParent();
443 - }
444 - } else {
445 - // It's content, let's start tracking the length
446 - var start = i;
447 - // Move forward to the next object, tracking the length as we go
448 - while ( data[i].type === undefined && i < length ) {
449 - i++;
450 - }
451 - // Now we know how long the current node is
452 - currentNode.setContentLength( i - start );
453 - // The while loop left us 1 element to far
454 - i--;
455 - }
456 - }
457 -};
458 -
459531 /**
460532 * Gets the content offset of a node.
461533 *
@@ -586,7 +658,8 @@
587659 /*function isStructuralData( data ) {
588660 return data.length >= 2 &&
589661 data[0].type !== undefined && data[0].type.charAt( 0 ) != '/' &&
590 - data[data.length - 1].type !== undefined && data[data.length - 1].type.charAt( 0 ) == '/';
 662+ data[data.length - 1].type !==
 663+ undefined && data[data.length - 1].type.charAt( 0 ) == '/';
591664 }*/
592665 function isStructuralData( data ) {
593666 var i;
@@ -602,7 +675,8 @@
603676 // The following are structural locations:
604677 // * The beginning of the document (offset 0)
605678 // * The end of the document (offset length-1)
606 - // * Any location between elements, i.e. the item before is a closing and the item after is an opening
 679+ // * Any location between elements, i.e. the item before is a closing and the item after is
 680+ // * an opening
607681 return offset <= 0 || offset >= data.length - 1 || (
608682 data[offset - 1].type !== undefined && data[offset - 1].type.charAt( 0 ) == '/' &&
609683 data[offset].type !== undefined && data[offset].type.charAt( 0 ) != '/'
@@ -611,7 +685,8 @@
612686
613687 /**
614688 * Balances mismatched openings/closings in data
615 - * @return data itself if nothing was changed, or a clone of data with balancing changes made. data itself is never touched
 689+ * @return data itself if nothing was changed, or a clone of data with balancing changes made.
 690+ * data itself is never touched
616691 */
617692 function balance( data ) {
618693 var i, stack = [], element, workingData = null;
@@ -638,7 +713,8 @@
639714 // Closing doesn't match what's expected
640715 // This means the input is malformed and cannot possibly
641716 // have been a fragment taken from well-formed data
642 - throw 'Input is malformed: expected /' + element + ' but got ' + data[i].type + ' at index ' + i;
 717+ throw 'Input is malformed: expected /' + element + ' but got ' + data[i].type +
 718+ ' at index ' + i;
643719 }
644720 }
645721 }

Status & tagging log