Index: trunk/parsers/wikidom/tests/hype/es.DocumentModel.test.js |
— | — | @@ -204,4 +204,5 @@ |
205 | 205 | |
206 | 206 | deepEqual( documentModel.getData(), data, 'Flattening plain objects results in correct data' ); |
207 | 207 | deepEqual( documentModel.slice( 0 ), tree, 'Nodes contain correct lengths' ); |
| 208 | + equal( documentModel[2].getContent(), ['a'], 'Content can be extracted from document' ); |
208 | 209 | } ); |
Index: trunk/parsers/wikidom/lib/hype/models/es.DocumentModel.js |
— | — | @@ -20,6 +20,13 @@ |
21 | 21 | // Initialization |
22 | 22 | node.rebuildChildNodes(); |
23 | 23 | |
| 24 | + // Sparse array of offsets and nodes located at those offsets |
| 25 | + node.offsetCache = []; |
| 26 | + node.on( 'update', function() { |
| 27 | + // Clear offset cache when content is changed |
| 28 | + node.offsetCache = []; |
| 29 | + } ); |
| 30 | + |
24 | 31 | return node; |
25 | 32 | }; |
26 | 33 | |
— | — | @@ -252,6 +259,10 @@ |
253 | 260 | /** |
254 | 261 | * Gets the content offset of a node. |
255 | 262 | * |
| 263 | + * This method uses a cache (this.offsetCache) to make repeated lookups for the same node quicker, |
| 264 | + * but this cache is cleared when the document is updated, so be careful to consolidate update |
| 265 | + * events if possible when depending on this method to be fast. |
| 266 | + * |
256 | 267 | * @method |
257 | 268 | * @param {es.DocumentModelNode} node Node to get offset of |
258 | 269 | * @param {Boolean} [deep=false] Whether to scan recursively |
— | — | @@ -262,20 +273,27 @@ |
263 | 274 | if ( from === undefined ) { |
264 | 275 | from = this; |
265 | 276 | } |
| 277 | + var cachedOffset = this.offsetCache.indexOf( node ); |
| 278 | + if ( cachedOffset !== -1 ) { |
| 279 | + return cachedOffset; |
| 280 | + } |
266 | 281 | var offset = 0; |
267 | | - for ( var i = 0; i < this.length; i++ ) { |
268 | | - if ( node === this[i] ) { |
| 282 | + for ( var i = 0; i < from.length; i++ ) { |
| 283 | + if ( node === from[i] ) { |
| 284 | + this.offsetCache[offset] = node; |
269 | 285 | return offset; |
270 | 286 | } |
271 | | - if ( deep && node.length ) { |
272 | | - var length = this.offsetOf( node, deep, node ); |
273 | | - if ( length !== null ) { |
274 | | - return offset + length; |
| 287 | + if ( deep && from[i].length ) { |
| 288 | + var length = this.offsetOf( node, true, from[i] ); |
| 289 | + if ( length !== -1 ) { |
| 290 | + offset += length; |
| 291 | + this.offsetCache[offset] = node; |
| 292 | + return offset; |
275 | 293 | } |
276 | 294 | } |
277 | | - offset += node.getContentLength() + 2; |
| 295 | + offset += from[i].getElementLength(); |
278 | 296 | } |
279 | | - return false; |
| 297 | + return -1; |
280 | 298 | }; |
281 | 299 | |
282 | 300 | /** |
— | — | @@ -302,11 +320,19 @@ |
303 | 321 | * @param {Boolean} [deep=false] Whether to scan recursively |
304 | 322 | * @returns {Array|null} List of content and elements inside node or null if node is not found |
305 | 323 | */ |
306 | | -es.DocumentModel.prototype.getContent = function( node, deep ) { |
307 | | - var offset = this.offsetOf( node, deep ); |
308 | | - if ( offset !== false ) { |
309 | | - return this.data.slice( offset + 1, offset + node.getContentLength() ); |
| 324 | +es.DocumentModel.prototype.getContent = function( node, range ) { |
| 325 | + if ( range ) { |
| 326 | + range.normalize(); |
| 327 | + } else { |
| 328 | + range = { |
| 329 | + 'start': 0, |
| 330 | + 'end': this.contentLength |
| 331 | + }; |
310 | 332 | } |
| 333 | + var offset = this.offsetOf( node, true ); |
| 334 | + if ( offset !== -1 ) { |
| 335 | + return this.data.slice( offset + 1, offset + node.getContentLength() + 1 ); |
| 336 | + } |
311 | 337 | return null; |
312 | 338 | }; |
313 | 339 | |
Index: trunk/parsers/wikidom/lib/hype/bases/es.DocumentModelNode.js |
— | — | @@ -130,3 +130,23 @@ |
131 | 131 | es.DocumentModelNode.prototype.getElementLength = function() { |
132 | 132 | return this.contentLength + 2; |
133 | 133 | }; |
| 134 | + |
| 135 | +/** |
| 136 | + * Gets the content length. |
| 137 | + * |
| 138 | + * FIXME: This method makes assumptions that a node with a data property is a DocumentModel, which |
| 139 | + * may be an issue if sub-classes of DocumentModelNode other than DocumentModel have a data property |
| 140 | + * as well. A safer way of determining this would be helpful in preventing future bugs. |
| 141 | + * |
| 142 | + * @method |
| 143 | + * @param {es.Range} range Range of content to get |
| 144 | + * @returns {Integer} Length of content |
| 145 | + */ |
| 146 | +es.DocumentModelNode.prototype.getContent = function( range ) { |
| 147 | + // Find root |
| 148 | + var root = this.data ? this : ( this.root.data ? this.root : null ); |
| 149 | + if ( root ) { |
| 150 | + return root.getContent( this, range ); |
| 151 | + } |
| 152 | + return []; |
| 153 | +}; |
Index: trunk/parsers/wikidom/lib/hype/bases/es.ModelNode.js |
— | — | @@ -32,7 +32,7 @@ |
33 | 33 | |
34 | 34 | // Properties |
35 | 35 | node.parent = undefined; |
36 | | - node.root = this; |
| 36 | + node.root = node; |
37 | 37 | |
38 | 38 | return node; |
39 | 39 | }; |
— | — | @@ -198,7 +198,7 @@ |
199 | 199 | * Gets the root node in the tree this node is currently attached to. |
200 | 200 | * |
201 | 201 | * @method |
202 | | - * @returns {es.DocumentModelNode} Root node |
| 202 | + * @returns {es.ModelNode} Root node |
203 | 203 | */ |
204 | 204 | es.ModelNode.prototype.getRoot = function() { |
205 | 205 | return this.root; |