r113530 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r113529‎ | r113530 | r113531 >
Date:21:54, 9 March 2012
Author:tparscal
Status:deferred
Tags:
Comment:
Removed duplicate static methods and members that were copied to ve.dm - lets just leave them in ve.dm.DocumentNode for now.
Modified paths:
  • /trunk/extensions/VisualEditor/modules/ve/dm/ve.dm.js (modified) (history)

Diff [purge]

Index: trunk/extensions/VisualEditor/modules/ve/dm/ve.dm.js
@@ -3,549 +3,4 @@
44 *
55 * All classes and functions will be attached to this object to keep the global namespace clean.
66 */
7 -ve.dm = {
8 -
9 - /* Static Members */
10 -
11 - /**
12 - * Mapping of symbolic names and node model constructors.
13 - */
14 - 'nodeModels': {},
15 - /**
16 - * Mapping of symbolic names and nesting rules.
17 - *
18 - * Each rule is an object with the follwing properties:
19 - * parents and children properties may contain one of two possible values:
20 - * {Array} List symbolic names of allowed element types (if empty, none will be allowed)
21 - * {Null} Any element type is allowed (as long as the other element also allows it)
22 - *
23 - * @example Paragraph rules
24 - * {
25 - * 'parents': null,
26 - * 'children': []
27 - * }
28 - * @example List rules
29 - * {
30 - * 'parents': null,
31 - * 'children': ['listItem']
32 - * }
33 - * @example ListItem rules
34 - * {
35 - * 'parents': ['list'],
36 - * 'children': null
37 - * }
38 - * @example TableCell rules
39 - * {
40 - * 'parents': ['tableRow'],
41 - * 'children': null
42 - * }
43 - */
44 - 'nodeRules': {
45 - 'document': {
46 - 'parents': null,
47 - 'children': null
48 - }
49 - },
50 -
51 - /* Static Methods */
52 -
53 - /*
54 - * Create child nodes from an array of data.
55 - *
56 - * These child nodes are used for the model tree, which is a space partitioning data structure
57 - * in which each node contains the length of itself (1 for opening, 1 for closing) and the
58 - * lengths of it's child nodes.
59 - */
60 - 'createNodesFromData': function( data ) {
61 - var currentNode = new ve.dm.BranchNode();
62 - for ( var i = 0, length = data.length; i < length; i++ ) {
63 - if ( data[i].type !== undefined ) {
64 - // It's an element, figure out it's type
65 - var element = data[i],
66 - type = element.type,
67 - open = type.charAt( 0 ) !== '/';
68 - // Trim the "/" off the beginning of closing tag types
69 - if ( !open ) {
70 - type = type.substr( 1 );
71 - }
72 - if ( open ) {
73 - // Validate the element type
74 - if ( !( type in ve.dm.DocumentNode.nodeModels ) ) {
75 - throw 'Unsuported element error. No class registered for element type: ' +
76 - type;
77 - }
78 - // Create a model node for the element
79 - var newNode = new ve.dm.DocumentNode.nodeModels[element.type]( element, 0 );
80 - // Add the new model node as a child
81 - currentNode.push( newNode );
82 - // Descend into the new model node
83 - currentNode = newNode;
84 - } else {
85 - // Return to the parent node
86 - currentNode = currentNode.getParent();
87 - if ( currentNode === null ) {
88 - throw 'createNodesFromData() received unbalanced data: found closing ' +
89 - 'without matching opening at index ' + i;
90 - }
91 - }
92 - } else {
93 - // It's content, let's start tracking the length
94 - var start = i;
95 - // Move forward to the next object, tracking the length as we go
96 - while ( data[i].type === undefined && i < length ) {
97 - i++;
98 - }
99 - // Now we know how long the current node is
100 - currentNode.setContentLength( i - start );
101 - // The while loop left us 1 element to far
102 - i--;
103 - }
104 - }
105 - return currentNode.getChildren().slice( 0 );
106 - },
107 - /**
108 - * Creates a document model from a plain object.
109 - *
110 - * @static
111 - * @method
112 - * @param {Object} obj Object to create new document model from
113 - * @returns {ve.dm.DocumentNode} Document model created from obj
114 - */
115 - 'newFromPlainObject': function( obj ) {
116 - if ( obj.type === 'document' ) {
117 - var data = [],
118 - attributes = ve.isPlainObject( obj.attributes ) ?
119 - ve.copyObject( obj.attributes ) : {};
120 - for ( var i = 0; i < obj.children.length; i++ ) {
121 - data = data.concat(
122 - ve.dm.DocumentNode.flattenPlainObjectElementNode( obj.children[i] )
123 - );
124 - }
125 - return new ve.dm.DocumentNode( data, attributes );
126 - }
127 - throw 'Invalid object error. Object is not a valid document object.';
128 - },
129 - /**
130 - * Generates a hash of an annotation object based on it's name and data.
131 - *
132 - * @static
133 - * @method
134 - * @param {Object} annotation Annotation object to generate hash for
135 - * @returns {String} Hash of annotation
136 - */
137 - 'getHash': ( window.JSON && typeof JSON.stringify === 'function' ) ?
138 - JSON.stringify : ve.dm.JsonSerializer.stringify,
139 - /**
140 - * Gets the index of the first instance of a given annotation.
141 - *
142 - * This method differs from ve.inArray because it compares hashes instead of references.
143 - *
144 - * @static
145 - * @method
146 - * @param {Array} annotations Annotations to search through
147 - * @param {Object} annotation Annotation to search for
148 - * @param {Boolean} typeOnly Whether to only consider the type
149 - * @returns {Integer} Index of annotation in annotations, or -1 if annotation was not found
150 - */
151 - 'getIndexOfAnnotation': function( annotations, annotation, typeOnly ) {
152 - if ( annotation === undefined || annotation.type === undefined ) {
153 - throw 'Invalid annotation error. Can not find non-annotation data in character.';
154 - }
155 - if ( ve.isArray( annotations ) ) {
156 - // Find the index of a comparable annotation (checking for same value, not reference)
157 - for ( var i = 0; i < annotations.length; i++ ) {
158 - // Skip over character data - used when this is called on a content data item
159 - if ( typeof annotations[i] === 'string' ) {
160 - continue;
161 - }
162 - if (
163 - (
164 - typeOnly &&
165 - annotations[i].type === annotation.type
166 - ) ||
167 - (
168 - !typeOnly &&
169 - annotations[i].hash === (
170 - annotation.hash || ve.dm.DocumentNode.getHash( annotation )
171 - )
172 - )
173 - ) {
174 - return i;
175 - }
176 - }
177 - }
178 - return -1;
179 - },
180 - /**
181 - * Gets a list of indexes of annotations that match a regular expression.
182 - *
183 - * @static
184 - * @method
185 - * @param {Array} annotations Annotations to search through
186 - * @param {RegExp} pattern Regular expression pattern to match with
187 - * @returns {Integer[]} List of indexes in annotations that match
188 - */
189 - 'getMatchingAnnotations': function( annotations, pattern ) {
190 - if ( !( pattern instanceof RegExp ) ) {
191 - throw 'Invalid annotation error. Can not find non-annotation data in character.';
192 - }
193 - var matches = [];
194 - if ( ve.isArray( annotations ) ) {
195 - // Find the index of a comparable annotation (checking for same value, not reference)
196 - for ( var i = 0; i < annotations.length; i++ ) {
197 - // Skip over character data - used when this is called on a content data item
198 - if ( typeof annotations[i] === 'string' ) {
199 - continue;
200 - }
201 - if ( pattern.test( annotations[i].type ) ) {
202 - matches.push( annotations[i] );
203 - }
204 - }
205 - }
206 - return matches;
207 - },
208 - /**
209 - * Sorts annotations of a character.
210 - *
211 - * This method modifies data in place. The string portion of the annotation character will always
212 - * remain at the beginning.
213 - *
214 - * @static
215 - * @method
216 - * @param {Array} character Annotated character to be sorted
217 - */
218 - 'sortCharacterAnnotations': function( character ) {
219 - if ( !ve.isArray( character ) ) {
220 - return;
221 - }
222 - character.sort( function( a, b ) {
223 - var aHash = a.hash || ve.dm.DocumentNode.getHash( a ),
224 - bHash = b.hash || ve.dm.DocumentNode.getHash( b );
225 - return typeof a === 'string' ? -1 :
226 - ( typeof b === 'string' ? 1 : ( aHash == bHash ? 0 : ( aHash < bHash ? -1 : 1 ) ) );
227 - } );
228 - },
229 - /**
230 - * Adds annotation hashes to content data.
231 - *
232 - * This method modifies data in place.
233 - *
234 - * @method
235 - * @param {Array} data Data to add annotation hashes to
236 - */
237 - 'addAnnotationHashesToData': function( data ) {
238 - for ( var i = 0; i < data.length; i++ ) {
239 - if ( ve.isArray( data[i] ) ) {
240 - for ( var j = 1; j < data.length; j++ ) {
241 - if ( data[i][j].hash === undefined ) {
242 - data[i][j].hash = ve.dm.DocumentNode.getHash( data[i][j] );
243 - }
244 - }
245 - }
246 - }
247 - },
248 - /**
249 - * Applies annotations to content data.
250 - *
251 - * This method modifies data in place.
252 - *
253 - * @method
254 - * @param {Array} data Data to remove annotations from
255 - * @param {Array} annotations Annotations to apply
256 - */
257 - 'addAnnotationsToData': function( data, annotations ) {
258 - if ( annotations && annotations.length ) {
259 - for ( var i = 0; i < data.length; i++ ) {
260 - if ( ve.isArray( data[i] ) ) {
261 - data[i] = [data[i]];
262 - }
263 - data[i] = [data[i]].concat( annotations );
264 - }
265 - }
266 - },
267 - /**
268 - * Removes annotations from content data.
269 - *
270 - * This method modifies data in place.
271 - *
272 - * @method
273 - * @param {Array} data Data to remove annotations from
274 - * @param {Array} [annotations] Annotations to remove (all will be removed if undefined)
275 - */
276 - 'removeAnnotationsFromData': function( data, annotations ) {
277 - for ( var i = 0; i < data.length; i++ ) {
278 - if ( ve.isArray( data[i] ) ) {
279 - data[i] = data[i][0];
280 - }
281 - }
282 - },
283 - /**
284 - * Creates an ve.ContentModel object from a plain content object.
285 - *
286 - * A plain content object contains plain text and a series of annotations to be applied to ranges of
287 - * the text.
288 - *
289 - * @example
290 - * {
291 - * 'text': '1234',
292 - * 'annotations': [
293 - * // Makes "23" bold
294 - * {
295 - * 'type': 'bold',
296 - * 'range': {
297 - * 'start': 1,
298 - * 'end': 3
299 - * }
300 - * }
301 - * ]
302 - * }
303 - *
304 - * @static
305 - * @method
306 - * @param {Object} obj Plain content object, containing a "text" property and optionally
307 - * an "annotations" property, the latter of which being an array of annotation objects including
308 - * range information
309 - * @returns {Array}
310 - */
311 - 'flattenPlainObjectContentNode': function( obj ) {
312 - if ( !ve.isPlainObject( obj ) ) {
313 - // Use empty content
314 - return [];
315 - } else {
316 - // Convert string to array of characters
317 - var data = obj.text.split('');
318 - // Render annotations
319 - if ( ve.isArray( obj.annotations ) ) {
320 - for ( var i = 0, length = obj.annotations.length; i < length; i++ ) {
321 - var src = obj.annotations[i];
322 - // Build simplified annotation object
323 - var dst = { 'type': src.type };
324 - if ( 'data' in src ) {
325 - dst.data = ve.copyObject( src.data );
326 - }
327 - // Add a hash to the annotation for faster comparison
328 - dst.hash = ve.dm.DocumentNode.getHash( dst );
329 - // Apply annotation to range
330 - if ( src.range.start < 0 ) {
331 - // TODO: The start can not be lower than 0! Throw error?
332 - // Clamp start value
333 - src.range.start = 0;
334 - }
335 - if ( src.range.end > data.length ) {
336 - // TODO: The end can not be higher than the length! Throw error?
337 - // Clamp end value
338 - src.range.end = data.length;
339 - }
340 - for ( var j = src.range.start; j < src.range.end; j++ ) {
341 - // Auto-convert to array
342 - if ( typeof data[j] === 'string' ) {
343 - data[j] = [data[j]];
344 - }
345 - // Append
346 - data[j].push( dst );
347 - }
348 - }
349 - }
350 - return data;
351 - }
352 - },
353 - /**
354 - * Flatten a plain node object into a data array, recursively.
355 - *
356 - * TODO: where do we document this whole structure - aka "WikiDom"?
357 - *
358 - * @static
359 - * @method
360 - * @param {Object} obj Plain node object to flatten
361 - * @returns {Array} Flattened version of obj
362 - */
363 - 'flattenPlainObjectElementNode': function( obj ) {
364 - var i,
365 - data = [],
366 - element = { 'type': obj.type };
367 - if ( ve.isPlainObject( obj.attributes ) ) {
368 - element.attributes = ve.copyObject( obj.attributes );
369 - }
370 - // Open element
371 - data.push( element );
372 - if ( ve.isPlainObject( obj.content ) ) {
373 - // Add content
374 - data = data.concat( ve.dm.DocumentNode.flattenPlainObjectContentNode( obj.content ) );
375 - } else if ( ve.isArray( obj.children ) ) {
376 - // Add children - only do this if there is no content property
377 - for ( i = 0; i < obj.children.length; i++ ) {
378 - // TODO: Figure out if all this concatenating is inefficient. I think it is
379 - data = data.concat( ve.dm.DocumentNode.flattenPlainObjectElementNode( obj.children[i] ) );
380 - }
381 - }
382 - // Close element - TODO: Do we need attributes here or not?
383 - data.push( { 'type': '/' + obj.type } );
384 - return data;
385 - },
386 - /**
387 - * Get a plain object representation of content data.
388 - *
389 - * @method
390 - * @returns {Object} Plain object representation
391 - */
392 - 'getExpandedContentData': function( data ) {
393 - var stack = [];
394 - // Text and annotations
395 - function start( offset, annotation ) {
396 - // Make a new verion of the annotation object and push it to the stack
397 - var obj = {
398 - 'type': annotation.type,
399 - 'range': { 'start': offset }
400 - };
401 - if ( annotation.data ) {
402 - obj.data = ve.copyObject( annotation.data );
403 - }
404 - stack.push( obj );
405 - }
406 - function end( offset, annotation ) {
407 - for ( var i = stack.length - 1; i >= 0; i-- ) {
408 - if ( !stack[i].range.end ) {
409 - if ( annotation ) {
410 - // We would just compare hashes, but the stack doesn't contain any
411 - if ( stack[i].type === annotation.type &&
412 - ve.compareObjects( stack[i].data, annotation.data ) ) {
413 - stack[i].range.end = offset;
414 - break;
415 - }
416 - } else {
417 - stack[i].range.end = offset;
418 - }
419 - }
420 - }
421 - }
422 - var left = '',
423 - right,
424 - leftPlain,
425 - rightPlain,
426 - obj = { 'text': '' },
427 - offset = 0,
428 - i,
429 - j;
430 - for ( i = 0; i < data.length; i++ ) {
431 - right = data[i];
432 - leftPlain = typeof left === 'string';
433 - rightPlain = typeof right === 'string';
434 - // Open or close annotations
435 - if ( !leftPlain && rightPlain ) {
436 - // [formatted][plain] pair, close any annotations for left
437 - end( i - offset );
438 - } else if ( leftPlain && !rightPlain ) {
439 - // [plain][formatted] pair, open any annotations for right
440 - for ( j = 1; j < right.length; j++ ) {
441 - start( i - offset, right[j] );
442 - }
443 - } else if ( !leftPlain && !rightPlain ) {
444 - // [formatted][formatted] pair, open/close any differences
445 - for ( j = 1; j < left.length; j++ ) {
446 - if ( ve.dm.DocumentNode.getIndexOfAnnotation( data[i] , left[j], true ) === -1 ) {
447 - end( i - offset, left[j] );
448 - }
449 - }
450 - for ( j = 1; j < right.length; j++ ) {
451 - if ( ve.dm.DocumentNode.getIndexOfAnnotation( data[i - 1], right[j], true ) === -1 ) {
452 - start( i - offset, right[j] );
453 - }
454 - }
455 - }
456 - obj.text += rightPlain ? right : right[0];
457 - left = right;
458 - }
459 - if ( data.length ) {
460 - end( i - offset );
461 - }
462 - if ( stack.length ) {
463 - obj.annotations = stack;
464 - }
465 - // Copy attributes if there are any set
466 - if ( !ve.isEmptyObject( this.attributes ) ) {
467 - obj.attributes = ve.extendObject( true, {}, this.attributes );
468 - }
469 - return obj;
470 - },
471 - /**
472 - * Checks if a data at a given offset is content.
473 - *
474 - * @example Content data:
475 - * <paragraph> a b c </paragraph> <list> <listItem> d e f </listItem> </list>
476 - * ^ ^ ^ ^ ^ ^
477 - *
478 - * @static
479 - * @method
480 - * @param {Array} data Data to evaluate offset within
481 - * @param {Integer} offset Offset in data to check
482 - * @returns {Boolean} If data at offset is content
483 - */
484 - 'isContentData': function( data, offset ) {
485 - // Shortcut: if there's already content there, we will trust it's supposed to be there
486 - return typeof data[offset] === 'string' || ve.isArray( data[offset] );
487 - },
488 - /**
489 - * Checks if a data at a given offset is an element.
490 - *
491 - * @example Element data:
492 - * <paragraph> a b c </paragraph> <list> <listItem> d e f </listItem> </list>
493 - * ^ ^ ^ ^ ^ ^
494 - *
495 - * @static
496 - * @method
497 - * @param {Array} data Data to evaluate offset within
498 - * @param {Integer} offset Offset in data to check
499 - * @returns {Boolean} If data at offset is an element
500 - */
501 - 'isElementData': function( data, offset ) {
502 - // TODO: Is there a safer way to check if it's a plain object without sacrificing speed?
503 - return offset >= 0 && offset < data.length && data[offset].type !== undefined;
504 - },
505 - /**
506 - * Checks if an offset within given data is structural.
507 - *
508 - * Structural offsets are those at the beginning, end or surrounded by elements. This differs
509 - * from a location at which an element is present in that elements can be safely inserted at a
510 - * structural location, but not nessecarily where an element is present.
511 - *
512 - * @example Structural offsets:
513 - * <paragraph> a b c </paragraph> <list> <listItem> d e f </listItem> </list>
514 - * ^ ^ ^ ^ ^
515 - *
516 - * @static
517 - * @method
518 - * @param {Array} data Data to evaluate offset within
519 - * @param {Integer} offset Offset to check
520 - * @returns {Boolean} Whether offset is structural or not
521 - */
522 - 'isStructuralOffset': function( data, offset ) {
523 - // Edges are always structural
524 - if ( offset === 0 || offset === data.length ) {
525 - return true;
526 - }
527 - // Structual offsets will have elements on each side
528 - if ( data[offset - 1].type !== undefined && data[offset].type !== undefined ) {
529 - if ( '/' + data[offset - 1].type === data[offset].type ) {
530 - return false;
531 - }
532 - return true;
533 - }
534 - return false;
535 - },
536 - /**
537 - * Checks if elements are present within data.
538 - *
539 - * @static
540 - * @method
541 - * @param {Array} data Data to look for elements within
542 - * @returns {Boolean} If elements exist in data
543 - */
544 - 'containsElementData': function( data ) {
545 - for ( var i = 0, length = data.length; i < length; i++ ) {
546 - if ( data[i].type !== undefined ) {
547 - return true;
548 - }
549 - }
550 - return false;
551 - }
552 -};
 7+ve.dm = {};

Status & tagging log