Index: trunk/parsers/wikidom/tests/transactions/test.js |
— | — | @@ -43,13 +43,13 @@ |
44 | 44 | var transactions = { |
45 | 45 | 'by calling constructor manually': new es.Content.Transaction( |
46 | 46 | content, |
47 | | - new es.Content.Operation( 'retain', 5 ), |
48 | | - new es.Content.Operation( 'insert', 'used to be' ), |
49 | | - new es.Content.Operation( 'remove', 2 ), |
50 | | - new es.Content.Operation( 'retain', 18 ) |
| 47 | + new es.Content.Operation.Retain( 5 ), |
| 48 | + new es.Content.Operation.Insert( es.Content.newFromText( 'used to be' ) ), |
| 49 | + new es.Content.Operation.Remove( 2 ), |
| 50 | + new es.Content.Operation.Retain( 18 ) |
51 | 51 | ), |
52 | 52 | 'using es.Content.Transaction.newFromReplace': new es.Content.Transaction.newFromReplace( |
53 | | - content, new es.Range( 5, 7), 'used to be' |
| 53 | + content, new es.Range( 5, 7), new es.Content.newFromText( 'used to be' ) |
54 | 54 | ) |
55 | 55 | }; |
56 | 56 | var before = content.getData(), |
— | — | @@ -87,7 +87,7 @@ |
88 | 88 | "p", |
89 | 89 | "h", |
90 | 90 | "!" |
91 | | - ] |
| 91 | + ]; |
92 | 92 | for ( var method in transactions ) { |
93 | 93 | var transaction = transactions[method]; |
94 | 94 | deepEqual( |
— | — | @@ -102,3 +102,62 @@ |
103 | 103 | ); |
104 | 104 | } |
105 | 105 | } ); |
| 106 | + |
| 107 | +/* |
| 108 | +test( 'Annotating', 4, function() { |
| 109 | + var transactions = { |
| 110 | + 'by calling constructor manually': new es.Content.Transaction( |
| 111 | + content, |
| 112 | + new es.Content.Operation( 'retain', 4 ), |
| 113 | + new es.Content.Operation( 'begin', { 'type': 'italic' } ), |
| 114 | + new es.Content.Operation( 'retain', 3 ), |
| 115 | + new es.Content.Operation( 'end', { 'type': 'italic' } ), |
| 116 | + new es.Content.Operation( 'retain', 18 ) |
| 117 | + ), |
| 118 | + 'using es.Content.Transaction.newFromAnnotate': new es.Content.Transaction.newFromAnnotate( |
| 119 | + content, new es.Range( 4, 7), 'add', { 'type': 'italic' } |
| 120 | + ) |
| 121 | + }; |
| 122 | + var before = content.getData(), |
| 123 | + after = [ |
| 124 | + ["T", { "type": "italic" }], |
| 125 | + ["h", { "type": "italic" }], |
| 126 | + ["i", { "type": "italic" }], |
| 127 | + ["s", { "type": "italic" }], |
| 128 | + [" ", { "type": "italic" }], |
| 129 | + ["i", { "type": "italic" }], |
| 130 | + ["s", { "type": "italic" }], |
| 131 | + " ", |
| 132 | + ["a", { "type": "xlink", "data": { "url":"http://www.a.com" } }], |
| 133 | + [" ", { "type": "xlink", "data": { "url":"http://www.a.com" } }], |
| 134 | + ["t", { "type": "xlink", "data": { "url":"http://www.a.com" } }, { "type": "bold" }], |
| 135 | + ["e", { "type": "xlink", "data": { "url":"http://www.a.com" } }, { "type": "bold" }], |
| 136 | + ["s", { "type": "xlink", "data": { "url":"http://www.a.com" } }, { "type": "bold" }], |
| 137 | + ["t", { "type": "xlink", "data": { "url":"http://www.a.com" } }, { "type": "bold" }], |
| 138 | + " ", |
| 139 | + "p", |
| 140 | + "a", |
| 141 | + "r", |
| 142 | + "a", |
| 143 | + "g", |
| 144 | + "r", |
| 145 | + "a", |
| 146 | + "p", |
| 147 | + "h", |
| 148 | + "!" |
| 149 | + ]; |
| 150 | + for ( var method in transactions ) { |
| 151 | + var transaction = transactions[method]; |
| 152 | + deepEqual( |
| 153 | + transaction.commit( content ).getData(), |
| 154 | + after, |
| 155 | + 'Committing transaction built with ' + method |
| 156 | + ); |
| 157 | + deepEqual( |
| 158 | + transaction.rollback( content ).getData(), |
| 159 | + before, |
| 160 | + 'Rolling back transaction built ' + method |
| 161 | + ); |
| 162 | + } |
| 163 | +} ); |
| 164 | +*/ |
\ No newline at end of file |
Index: trunk/parsers/wikidom/lib/es/es.Content.Transaction.js |
— | — | @@ -10,29 +10,30 @@ |
11 | 11 | es.Content.Transaction = function( content, operations ) { |
12 | 12 | this.content = content; |
13 | 13 | this.operations = []; |
| 14 | + this.length = 0; |
14 | 15 | if ( arguments.length > 1 ) { |
15 | 16 | // Support variadic arguments |
16 | 17 | if ( !$.isArray( operations ) ) { |
17 | 18 | operations = Array.prototype.slice.call( arguments, 1 ); |
18 | 19 | } |
19 | | - var range = new es.Range(); |
20 | 20 | for ( var i = 0; i < operations.length; i++ ) { |
21 | | - var operation = operations[i]; |
22 | | - switch ( operation.getType() ) { |
23 | | - case 'retain': |
24 | | - case 'remove': |
25 | | - range.to = range.from + operation.getLength(); |
26 | | - if ( !operation.hasContent() ) { |
27 | | - operation.setContent( content.getContent( range ) ); |
28 | | - } |
29 | | - range.from = range.to; |
30 | | - break; |
31 | | - } |
32 | | - this.operations.push( operation ); |
| 21 | + this.add( operations[i] ); |
33 | 22 | } |
34 | 23 | } |
35 | 24 | }; |
36 | 25 | |
| 26 | +es.Content.Transaction.prototype.add = function( operation ) { |
| 27 | + // Auto-add content to operations which affect a range but don't have content (yet) |
| 28 | + var range = new es.Range( this.length, this.length + operation.getLength() ); |
| 29 | + if ( operation.getLength() && !operation.hasContent() ) { |
| 30 | + if ( !operation.hasContent() ) { |
| 31 | + operation.setContent( content.getContent( range ) ); |
| 32 | + } |
| 33 | + } |
| 34 | + this.length += operation.getAdvance(); |
| 35 | + this.operations.push( operation ); |
| 36 | +}; |
| 37 | + |
37 | 38 | /** |
38 | 39 | * Builds a transaction that removes and or inserts content. |
39 | 40 | * |
— | — | @@ -41,84 +42,84 @@ |
42 | 43 | * @param insert {es.Content} Content to insert (optional) |
43 | 44 | */ |
44 | 45 | es.Content.Transaction.newFromReplace = function( content, range, insert ) { |
45 | | - var operations = []; |
| 46 | + var transaction = new es.Content.Transaction( content ); |
46 | 47 | range.normalize(); |
47 | 48 | if ( content.getLength() ) { |
48 | 49 | // Delete range |
49 | 50 | if ( range.start ) { |
50 | 51 | // Use content up until the range begins |
51 | | - operations.push( new es.Content.Operation( 'retain', range.start ) ); |
| 52 | + transaction.add( new es.Content.Operation.Retain( range.start ) ); |
52 | 53 | } |
53 | 54 | // Skip over the range |
54 | 55 | if ( range.getLength() ) { |
55 | | - operations.push( new es.Content.Operation( 'remove', range.getLength() ) ); |
| 56 | + transaction.add( new es.Content.Operation.Remove( range.getLength() ) ); |
56 | 57 | } |
57 | 58 | } |
58 | 59 | if ( insert ) { |
59 | 60 | // Add content to the output |
60 | | - operations.push( new es.Content.Operation( 'insert', insert ) ); |
| 61 | + transaction.add( new es.Content.Operation.Insert( insert ) ); |
61 | 62 | } |
62 | 63 | // Retain remaining content |
63 | 64 | if ( range.end < content.getLength() ) { |
64 | | - operations.push( |
65 | | - new es.Content.Operation( 'retain', content.getLength() - range.end ) |
66 | | - ); |
| 65 | + transaction.add( new es.Content.Operation.Retain( content.getLength() - range.end ) ); |
67 | 66 | } |
68 | | - return new es.Content.Transaction( content, operations ); |
| 67 | + return transaction; |
69 | 68 | }; |
70 | 69 | |
| 70 | +/** |
| 71 | + * Builds a transaction that applies annotations |
| 72 | + * |
| 73 | + * TODO: Support method argument |
| 74 | + * |
| 75 | + * @param content {es.Content} Content to operate on |
| 76 | + * @param range {es.Range} Range of content to annotate, content will be retained |
| 77 | + * @param method {String} Mode of application; "add", "remove", or "toggle" |
| 78 | + * @param annotation {Object} Annotation to apply |
| 79 | + */ |
| 80 | +es.Content.Transaction.newFromAnnotate = function( content, range, method, annotation ) { |
| 81 | + var transaction = new es.Content.Transaction(); |
| 82 | + range.normalize(); |
| 83 | + if ( content.getLength() ) { |
| 84 | + if ( range.start ) { |
| 85 | + // Use content up until the range begins |
| 86 | + transaction.add( new es.Content.Operation.Retain( range.start ) ); |
| 87 | + } |
| 88 | + if ( range.getLength() ) { |
| 89 | + // Apply annotation to range |
| 90 | + transaction.add( new es.Content.Operation.Begin( annotation ) ); |
| 91 | + transaction.add( new es.Content.Operation.Retain( range.getLength() ) ); |
| 92 | + transaction.add( new es.Content.Operation.End( annotation ) ); |
| 93 | + } |
| 94 | + // Retain remaining content |
| 95 | + if ( range.end < content.getLength() ) { |
| 96 | + transaction.add( new es.Content.Operation.Begin( content.getLength() - range.end ) ); |
| 97 | + } |
| 98 | + } |
| 99 | + return transaction; |
| 100 | +}; |
| 101 | + |
71 | 102 | es.Content.Transaction.prototype.commit = function() { |
72 | | - var range = new es.Range(), |
73 | | - to = new es.Content(); |
| 103 | + var offset = 0, |
| 104 | + content = new es.Content(), |
| 105 | + annotations = []; |
74 | 106 | for ( var i = 0; i < this.operations.length; i++ ) { |
75 | | - var operation = this.operations[i]; |
76 | | - switch (operation.getType()) { |
77 | | - case 'retain': |
78 | | - range.to = range.from + operation.getLength(); |
79 | | - // Automatically add content to operation |
80 | | - if ( !operation.hasContent() ) { |
81 | | - operation.setContent( this.content.getContent( range ) ); |
82 | | - } |
83 | | - to.insert( to.getLength(), operation.getContent().getData() ); |
84 | | - range.from = range.to; |
85 | | - break; |
86 | | - case 'insert': |
87 | | - to.insert( to.getLength(), operation.getContent().getData() ); |
88 | | - break; |
89 | | - case 'remove': |
90 | | - range.to = range.from + operation.getLength(); |
91 | | - // Automatically add content to operation |
92 | | - if ( !operation.hasContent() ) { |
93 | | - operation.setContent( this.content.getContent( range ) ); |
94 | | - } |
95 | | - range.from = range.to; |
96 | | - break; |
97 | | - } |
| 107 | + var length = this.operations[i].commit( content, annotations ); |
| 108 | + // TODO: Apply annotations in stack |
| 109 | + offset += length; |
98 | 110 | } |
99 | | - return to; |
| 111 | + return content; |
100 | 112 | }; |
101 | 113 | |
102 | 114 | es.Content.Transaction.prototype.rollback = function() { |
103 | | - var range = new es.Range(), |
104 | | - to = new es.Content(); |
| 115 | + var offset = 0, |
| 116 | + content = new es.Content(), |
| 117 | + annotations = []; |
105 | 118 | for ( var i = 0; i < this.operations.length; i++ ) { |
106 | | - var operation = this.operations[i]; |
107 | | - switch (operation.getType()) { |
108 | | - case 'retain': |
109 | | - range.to = range.from + operation.getLength(); |
110 | | - to.insert( to.getLength(), operation.getContent().getData() ); |
111 | | - range.from = range.to; |
112 | | - break; |
113 | | - case 'remove': |
114 | | - to.insert( to.getLength(), operation.getContent().getData() ); |
115 | | - break; |
116 | | - case 'insert': |
117 | | - range.to = range.from + operation.getLength(); |
118 | | - range.from = range.to; |
119 | | - break; |
120 | | - } |
| 119 | + var length = this.operations[i].rollback( content, annotations ); |
| 120 | + // TODO: Apply annotations in stack |
| 121 | + offset += length; |
121 | 122 | } |
122 | | - return to; |
| 123 | + return content; |
123 | 124 | }; |
124 | 125 | |
125 | 126 | es.Content.Transaction.prototype.optimize = function() { |
Index: trunk/parsers/wikidom/lib/es/es.Content.Operation.js |
— | — | @@ -3,29 +3,11 @@ |
4 | 4 | * |
5 | 5 | * @class |
6 | 6 | * @constructor |
7 | | - * @param type {String} Type of operation, e.g. insert, delete, annotate |
8 | | - * @param content {Mixed} Either {Integer} length, {String} text or {es.Content} content |
9 | | - * @param data {Object} Data of operation, e.g. range |
10 | | - * @property type {String} Type of operation |
11 | | - * @property content {es.Content} Content of operation |
12 | | - * @property data {Object} Data of operation |
13 | 7 | */ |
| 8 | + |
14 | 9 | es.Content.Operation = function( type, content, data ) { |
15 | 10 | this.type = type; |
16 | | - switch ( typeof content ) { |
17 | | - case 'number': |
18 | | - this.content = null; |
19 | | - this.length = content; |
20 | | - break; |
21 | | - case 'string': |
22 | | - this.content = es.Content.newFromText( content ); |
23 | | - this.length = this.content.getLength(); |
24 | | - break; |
25 | | - case 'object': |
26 | | - this.content = content; |
27 | | - this.length = this.content.getLength(); |
28 | | - break; |
29 | | - } |
| 11 | + this.content = content || null; |
30 | 12 | this.data = data || null; |
31 | 13 | }; |
32 | 14 | |
— | — | @@ -46,9 +28,135 @@ |
47 | 29 | }; |
48 | 30 | |
49 | 31 | es.Content.Operation.prototype.getLength = function() { |
50 | | - return this.content ? this.content.getLength() : this.length; |
| 32 | + return 0; |
51 | 33 | }; |
52 | 34 | |
| 35 | +es.Content.Operation.prototype.getAdvance = function() { |
| 36 | + return 0 |
| 37 | +}; |
| 38 | + |
53 | 39 | es.Content.Operation.prototype.getData = function() { |
54 | 40 | return this.data; |
55 | 41 | }; |
| 42 | + |
| 43 | +es.Content.Operation.prototype.setData = function() { |
| 44 | + return this.data; |
| 45 | +}; |
| 46 | + |
| 47 | +es.Content.Operation.prototype.commit = function( content, annotations ) { |
| 48 | + throw 'es.Content.Operation.commit not implemented in this subclass.'; |
| 49 | +}; |
| 50 | + |
| 51 | +es.Content.Operation.prototype.rollback = function( content, annotations ) { |
| 52 | + throw 'es.Content.Operation.rollback not implemented in this subclass.'; |
| 53 | +}; |
| 54 | + |
| 55 | +/** |
| 56 | + * @class |
| 57 | + * @constructor |
| 58 | + */ |
| 59 | +es.Content.Operation.Retain = function( length ) { |
| 60 | + es.Content.Operation.call( this, 'remove' ); |
| 61 | + this.type = 'retain'; |
| 62 | + this.content = null; |
| 63 | + this.length = length; |
| 64 | +}; |
| 65 | +es.extend( es.Content.Operation.Retain, es.Content.Operation ); |
| 66 | + |
| 67 | +es.Content.Operation.Retain.prototype.getLength = function() { |
| 68 | + return this.length; |
| 69 | +}; |
| 70 | + |
| 71 | +es.Content.Operation.Retain.prototype.getAdvance = function() { |
| 72 | + return this.length; |
| 73 | +}; |
| 74 | + |
| 75 | +es.Content.Operation.Retain.prototype.commit = function( content, annotations ) { |
| 76 | + content.insert( content.getLength(), this.content.getData() ); |
| 77 | + return this.length; |
| 78 | +}; |
| 79 | + |
| 80 | +es.Content.Operation.Retain.prototype.rollback = es.Content.Operation.Retain.prototype.commit; |
| 81 | + |
| 82 | +/** |
| 83 | + * @class |
| 84 | + * @constructor |
| 85 | + */ |
| 86 | +es.Content.Operation.Insert = function( content ) { |
| 87 | + es.Content.Operation.call( this, 'insert', content ); |
| 88 | +}; |
| 89 | +es.extend( es.Content.Operation.Insert, es.Content.Operation ); |
| 90 | + |
| 91 | +es.Content.Operation.Insert.prototype.getLength = function() { |
| 92 | + return this.content.getLength(); |
| 93 | +}; |
| 94 | + |
| 95 | +es.Content.Operation.Insert.prototype.commit = function( content, annotations ) { |
| 96 | + content.insert( content.getLength(), this.content.getData() ); |
| 97 | + return 0; |
| 98 | +}; |
| 99 | + |
| 100 | +es.Content.Operation.Insert.prototype.rollback = function( content, annotations ) { |
| 101 | + return this.content.getLength(); |
| 102 | +}; |
| 103 | + |
| 104 | +/** |
| 105 | + * @class |
| 106 | + * @constructor |
| 107 | + */ |
| 108 | +es.Content.Operation.Remove = function( length ) { |
| 109 | + es.Content.Operation.call( this, 'remove' ); |
| 110 | + this.length = length; |
| 111 | +}; |
| 112 | +es.extend( es.Content.Operation.Remove, es.Content.Operation ); |
| 113 | + |
| 114 | +es.Content.Operation.Remove.prototype.getLength = function() { |
| 115 | + return this.length; |
| 116 | +}; |
| 117 | + |
| 118 | +es.Content.Operation.Remove.prototype.getAdvance = function() { |
| 119 | + return this.length; |
| 120 | +}; |
| 121 | + |
| 122 | +es.Content.Operation.Remove.prototype.commit = es.Content.Operation.Insert.prototype.rollback; |
| 123 | +es.Content.Operation.Remove.prototype.rollback = es.Content.Operation.Insert.prototype.commit; |
| 124 | + |
| 125 | +/** |
| 126 | + * @class |
| 127 | + * @constructor |
| 128 | + */ |
| 129 | +es.Content.Operation.Begin = function( annotation ) { |
| 130 | + es.Content.Operation.call( this, 'begin', null, annotation ); |
| 131 | +}; |
| 132 | +es.extend( es.Content.Operation.Begin, es.Content.Operation ); |
| 133 | + |
| 134 | +es.Content.Operation.Begin.prototype.commit = function( content, annotations ) { |
| 135 | + annotations.push( this.data ); |
| 136 | + return 0; |
| 137 | +}; |
| 138 | + |
| 139 | +es.Content.Operation.Begin.prototype.rollback = function( content, annotations ) { |
| 140 | + var index; |
| 141 | + for ( var i = 0; i < annotations.length; i++ ) { |
| 142 | + if ( es.Content.compareObjects( annotations[i], this.data ) ) { |
| 143 | + index = i; |
| 144 | + } |
| 145 | + } |
| 146 | + if ( index === undefined ) { |
| 147 | + throw 'Annotation stack error. Annotation is missing.'; |
| 148 | + } |
| 149 | + annotations.splice( index, 1 ); |
| 150 | + return 0; |
| 151 | +}; |
| 152 | + |
| 153 | +/** |
| 154 | + * @class |
| 155 | + * @constructor |
| 156 | + */ |
| 157 | +es.Content.Operation.End = function( annotation ) { |
| 158 | + es.Content.Operation.call( this, 'end', null, annotation ); |
| 159 | +}; |
| 160 | +es.extend( es.Content.Operation.End, es.Content.Operation ); |
| 161 | + |
| 162 | +es.Content.Operation.End.prototype.commit = es.Content.Operation.Begin.prototype.rollback; |
| 163 | +es.Content.Operation.End.prototype.rollback = es.Content.Operation.Begin.prototype.commit; |