initial commit
[namibia] / public / scripts / ckeditor / _source / core / dom / walker.js
1 /*
2 Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.
3 For licensing, see LICENSE.html or http://ckeditor.com/license
4 */
5
6 (function()
7 {
8         // This function is to be called under a "walker" instance scope.
9         function iterate( rtl, breakOnFalse )
10         {
11                 // Return null if we have reached the end.
12                 if ( this._.end )
13                         return null;
14
15                 var node,
16                         range = this.range,
17                         guard,
18                         userGuard = this.guard,
19                         type = this.type,
20                         getSourceNodeFn = ( rtl ? 'getPreviousSourceNode' : 'getNextSourceNode' );
21
22                 // This is the first call. Initialize it.
23                 if ( !this._.start )
24                 {
25                         this._.start = 1;
26
27                         // Trim text nodes and optmize the range boundaries. DOM changes
28                         // may happen at this point.
29                         range.trim();
30
31                         // A collapsed range must return null at first call.
32                         if ( range.collapsed )
33                         {
34                                 this.end();
35                                 return null;
36                         }
37                 }
38
39                 // Create the LTR guard function, if necessary.
40                 if ( !rtl && !this._.guardLTR )
41                 {
42                         // Gets the node that stops the walker when going LTR.
43                         var limitLTR = range.endContainer,
44                                 blockerLTR = limitLTR.getChild( range.endOffset );
45
46                         this._.guardLTR = function( node, movingOut )
47                         {
48                                 return ( ( !movingOut || !limitLTR.equals( node ) )
49                                         && ( !blockerLTR || !node.equals( blockerLTR ) )
50                                         && ( node.type != CKEDITOR.NODE_ELEMENT || !movingOut || node.getName() != 'body' ) );
51                         };
52                 }
53
54                 // Create the RTL guard function, if necessary.
55                 if ( rtl && !this._.guardRTL )
56                 {
57                         // Gets the node that stops the walker when going LTR.
58                         var limitRTL = range.startContainer,
59                                 blockerRTL = ( range.startOffset > 0 ) && limitRTL.getChild( range.startOffset - 1 );
60
61                         this._.guardRTL = function( node, movingOut )
62                         {
63                                 return ( ( !movingOut || !limitRTL.equals( node ) )
64                                         && ( !blockerRTL || !node.equals( blockerRTL ) )
65                                         && ( node.type != CKEDITOR.NODE_ELEMENT || !movingOut || node.getName() != 'body' ) );
66                         };
67                 }
68
69                 // Define which guard function to use.
70                 var stopGuard = rtl ? this._.guardRTL : this._.guardLTR;
71
72                 // Make the user defined guard function participate in the process,
73                 // otherwise simply use the boundary guard.
74                 if ( userGuard )
75                 {
76                         guard = function( node, movingOut )
77                         {
78                                 if ( stopGuard( node, movingOut ) === false )
79                                         return false;
80
81                                 return userGuard( node, movingOut );
82                         };
83                 }
84                 else
85                         guard = stopGuard;
86
87                 if ( this.current )
88                         node = this.current[ getSourceNodeFn ]( false, type, guard );
89                 else
90                 {
91                         // Get the first node to be returned.
92
93                         if ( rtl )
94                         {
95                                 node = range.endContainer;
96
97                                 if ( range.endOffset > 0 )
98                                 {
99                                         node = node.getChild( range.endOffset - 1 );
100                                         if ( guard( node ) === false )
101                                                 node = null;
102                                 }
103                                 else
104                                         node = ( guard ( node, true ) === false ) ?
105                                                 null : node.getPreviousSourceNode( true, type, guard );
106                         }
107                         else
108                         {
109                                 node = range.startContainer;
110                                 node = node.getChild( range.startOffset );
111
112                                 if ( node )
113                                 {
114                                         if ( guard( node ) === false )
115                                                 node = null;
116                                 }
117                                 else
118                                         node = ( guard ( range.startContainer, true ) === false ) ?
119                                                 null : range.startContainer.getNextSourceNode( true, type, guard ) ;
120                         }
121                 }
122
123                 while ( node && !this._.end )
124                 {
125                         this.current = node;
126
127                         if ( !this.evaluator || this.evaluator( node ) !== false )
128                         {
129                                 if ( !breakOnFalse )
130                                         return node;
131                         }
132                         else if ( breakOnFalse && this.evaluator )
133                                 return false;
134
135                         node = node[ getSourceNodeFn ]( false, type, guard );
136                 }
137
138                 this.end();
139                 return this.current = null;
140         }
141
142         function iterateToLast( rtl )
143         {
144                 var node, last = null;
145
146                 while ( ( node = iterate.call( this, rtl ) ) )
147                         last = node;
148
149                 return last;
150         }
151
152         CKEDITOR.dom.walker = CKEDITOR.tools.createClass(
153         {
154                 /**
155                  * Utility class to "walk" the DOM inside a range boundaries. If
156                  * necessary, partially included nodes (text nodes) are broken to
157                  * reflect the boundaries limits, so DOM and range changes may happen.
158                  * Outside changes to the range may break the walker.
159                  *
160                  * The walker may return nodes that are not totaly included into the
161                  * range boundaires. Let's take the following range representation,
162                  * where the square brackets indicate the boundaries:
163                  *
164                  * [<p>Some <b>sample] text</b>
165                  *
166                  * While walking forward into the above range, the following nodes are
167                  * returned: <p>, "Some ", <b> and "sample". Going
168                  * backwards instead we have: "sample" and "Some ". So note that the
169                  * walker always returns nodes when "entering" them, but not when
170                  * "leaving" them. The guard function is instead called both when
171                  * entering and leaving nodes.
172                  *
173                  * @constructor
174                  * @param {CKEDITOR.dom.range} range The range within which walk.
175                  */
176                 $ : function( range )
177                 {
178                         this.range = range;
179
180                         /**
181                          * A function executed for every matched node, to check whether
182                          * it's to be considered into the walk or not. If not provided, all
183                          * matched nodes are considered good.
184                          * If the function returns "false" the node is ignored.
185                          * @name CKEDITOR.dom.walker.prototype.evaluator
186                          * @property
187                          * @type Function
188                          */
189                         // this.evaluator = null;
190
191                         /**
192                          * A function executed for every node the walk pass by to check
193                          * whether the walk is to be finished. It's called when both
194                          * entering and exiting nodes, as well as for the matched nodes.
195                          * If this function returns "false", the walking ends and no more
196                          * nodes are evaluated.
197                          * @name CKEDITOR.dom.walker.prototype.guard
198                          * @property
199                          * @type Function
200                          */
201                         // this.guard = null;
202
203                         /** @private */
204                         this._ = {};
205                 },
206
207 //              statics :
208 //              {
209 //                      /* Creates a CKEDITOR.dom.walker instance to walk inside DOM boundaries set by nodes.
210 //                       * @param {CKEDITOR.dom.node} startNode The node from wich the walk
211 //                       *              will start.
212 //                       * @param {CKEDITOR.dom.node} [endNode] The last node to be considered
213 //                       *              in the walk. No more nodes are retrieved after touching or
214 //                       *              passing it. If not provided, the walker stops at the
215 //                       *              <body> closing boundary.
216 //                       * @returns {CKEDITOR.dom.walker} A DOM walker for the nodes between the
217 //                       *              provided nodes.
218 //                       */
219 //                      createOnNodes : function( startNode, endNode, startInclusive, endInclusive )
220 //                      {
221 //                              var range = new CKEDITOR.dom.range();
222 //                              if ( startNode )
223 //                                      range.setStartAt( startNode, startInclusive ? CKEDITOR.POSITION_BEFORE_START : CKEDITOR.POSITION_AFTER_END ) ;
224 //                              else
225 //                                      range.setStartAt( startNode.getDocument().getBody(), CKEDITOR.POSITION_AFTER_START ) ;
226 //
227 //                              if ( endNode )
228 //                                      range.setEndAt( endNode, endInclusive ? CKEDITOR.POSITION_AFTER_END : CKEDITOR.POSITION_BEFORE_START ) ;
229 //                              else
230 //                                      range.setEndAt( startNode.getDocument().getBody(), CKEDITOR.POSITION_BEFORE_END ) ;
231 //
232 //                              return new CKEDITOR.dom.walker( range );
233 //                      }
234 //              },
235 //
236                 proto :
237                 {
238                         /**
239                          * Stop walking. No more nodes are retrieved if this function gets
240                          * called.
241                          */
242                         end : function()
243                         {
244                                 this._.end = 1;
245                         },
246
247                         /**
248                          * Retrieves the next node (at right).
249                          * @returns {CKEDITOR.dom.node} The next node or null if no more
250                          *              nodes are available.
251                          */
252                         next : function()
253                         {
254                                 return iterate.call( this );
255                         },
256
257                         /**
258                          * Retrieves the previous node (at left).
259                          * @returns {CKEDITOR.dom.node} The previous node or null if no more
260                          *              nodes are available.
261                          */
262                         previous : function()
263                         {
264                                 return iterate.call( this, 1 );
265                         },
266
267                         /**
268                          * Check all nodes at right, executing the evaluation fuction.
269                          * @returns {Boolean} "false" if the evaluator function returned
270                          *              "false" for any of the matched nodes. Otherwise "true".
271                          */
272                         checkForward : function()
273                         {
274                                 return iterate.call( this, 0, 1 ) !== false;
275                         },
276
277                         /**
278                          * Check all nodes at left, executing the evaluation fuction.
279                          * @returns {Boolean} "false" if the evaluator function returned
280                          *              "false" for any of the matched nodes. Otherwise "true".
281                          */
282                         checkBackward : function()
283                         {
284                                 return iterate.call( this, 1, 1 ) !== false;
285                         },
286
287                         /**
288                          * Executes a full walk forward (to the right), until no more nodes
289                          * are available, returning the last valid node.
290                          * @returns {CKEDITOR.dom.node} The last node at the right or null
291                          *              if no valid nodes are available.
292                          */
293                         lastForward : function()
294                         {
295                                 return iterateToLast.call( this );
296                         },
297
298                         /**
299                          * Executes a full walk backwards (to the left), until no more nodes
300                          * are available, returning the last valid node.
301                          * @returns {CKEDITOR.dom.node} The last node at the left or null
302                          *              if no valid nodes are available.
303                          */
304                         lastBackward : function()
305                         {
306                                 return iterateToLast.call( this, 1 );
307                         },
308
309                         reset : function()
310                         {
311                                 delete this.current;
312                                 this._ = {};
313                         }
314
315                 }
316         });
317
318         /*
319          * Anything whose display computed style is block, list-item, table,
320          * table-row-group, table-header-group, table-footer-group, table-row,
321          * table-column-group, table-column, table-cell, table-caption, or whose node
322          * name is hr, br (when enterMode is br only) is a block boundary.
323          */
324         var blockBoundaryDisplayMatch =
325         {
326                 block : 1,
327                 'list-item' : 1,
328                 table : 1,
329                 'table-row-group' : 1,
330                 'table-header-group' : 1,
331                 'table-footer-group' : 1,
332                 'table-row' : 1,
333                 'table-column-group' : 1,
334                 'table-column' : 1,
335                 'table-cell' : 1,
336                 'table-caption' : 1
337         };
338
339         CKEDITOR.dom.element.prototype.isBlockBoundary = function( customNodeNames )
340         {
341                 var nodeNameMatches = customNodeNames ?
342                         CKEDITOR.tools.extend( {}, CKEDITOR.dtd.$block, customNodeNames || {} ) :
343                         CKEDITOR.dtd.$block;
344
345                 // Don't consider floated formatting as block boundary, fall back to dtd check in that case. (#6297)
346                 return this.getComputedStyle( 'float' ) == 'none' && blockBoundaryDisplayMatch[ this.getComputedStyle( 'display' ) ]
347                                 || nodeNameMatches[ this.getName() ];
348         };
349
350         CKEDITOR.dom.walker.blockBoundary = function( customNodeNames )
351         {
352                 return function( node , type )
353                 {
354                         return ! ( node.type == CKEDITOR.NODE_ELEMENT
355                                                 && node.isBlockBoundary( customNodeNames ) );
356                 };
357         };
358
359         CKEDITOR.dom.walker.listItemBoundary = function()
360         {
361                         return this.blockBoundary( { br : 1 } );
362         };
363
364         /**
365          * Whether the to-be-evaluated node is a bookmark node OR bookmark node
366          * inner contents.
367          * @param {Boolean} contentOnly Whether only test againt the text content of
368          * bookmark node instead of the element itself(default).
369          * @param {Boolean} isReject Whether should return 'false' for the bookmark
370          * node instead of 'true'(default).
371          */
372         CKEDITOR.dom.walker.bookmark = function( contentOnly, isReject )
373         {
374                 function isBookmarkNode( node )
375                 {
376                         return ( node && node.getName
377                                         && node.getName() == 'span'
378                                         && node.data( 'cke-bookmark' ) );
379                 }
380
381                 return function( node )
382                 {
383                         var isBookmark, parent;
384                         // Is bookmark inner text node?
385                         isBookmark = ( node && !node.getName && ( parent = node.getParent() )
386                                                 && isBookmarkNode( parent ) );
387                         // Is bookmark node?
388                         isBookmark = contentOnly ? isBookmark : isBookmark || isBookmarkNode( node );
389                         return !! ( isReject ^ isBookmark );
390                 };
391         };
392
393         /**
394          * Whether the node is a text node containing only whitespaces characters.
395          * @param isReject
396          */
397         CKEDITOR.dom.walker.whitespaces = function( isReject )
398         {
399                 return function( node )
400                 {
401                         var isWhitespace = node && ( node.type == CKEDITOR.NODE_TEXT )
402                                                         && !CKEDITOR.tools.trim( node.getText() );
403                         return !! ( isReject ^ isWhitespace );
404                 };
405         };
406
407         /**
408          * Whether the node is invisible in wysiwyg mode.
409          * @param isReject
410          */
411         CKEDITOR.dom.walker.invisible = function( isReject )
412         {
413                 var whitespace = CKEDITOR.dom.walker.whitespaces();
414                 return function( node )
415                 {
416                         // Nodes that take no spaces in wysiwyg:
417                         // 1. White-spaces but not including NBSP;
418                         // 2. Empty inline elements, e.g. <b></b> we're checking here
419                         // 'offsetHeight' instead of 'offsetWidth' for properly excluding
420                         // all sorts of empty paragraph, e.g. <br />.
421                         var isInvisible = whitespace( node ) || node.is && !node.$.offsetHeight;
422                         return !! ( isReject ^ isInvisible );
423                 };
424         };
425
426         CKEDITOR.dom.walker.nodeType = function( type, isReject )
427         {
428                 return function( node )
429                 {
430                         return !! ( isReject ^ ( node.type == type ) );
431                 };
432         };
433
434         var tailNbspRegex = /^[\t\r\n ]*(?:&nbsp;|\xa0)$/,
435                 isWhitespaces = CKEDITOR.dom.walker.whitespaces(),
436                 isBookmark = CKEDITOR.dom.walker.bookmark(),
437                 toSkip = function( node )
438                 {
439                         return isBookmark( node )
440                                         || isWhitespaces( node )
441                                         || node.type == CKEDITOR.NODE_ELEMENT
442                                         && node.getName() in CKEDITOR.dtd.$inline
443                                         && !( node.getName() in CKEDITOR.dtd.$empty );
444                 };
445
446         // Check if there's a filler node at the end of an element, and return it.
447         CKEDITOR.dom.element.prototype.getBogus = function()
448         {
449                 // Bogus are not always at the end, e.g. <p><a>text<br /></a></p> (#7070).
450                 var tail = this;
451                 do { tail = tail.getPreviousSourceNode(); }
452                 while ( toSkip( tail ) )
453
454                 if ( tail && ( !CKEDITOR.env.ie ? tail.is && tail.is( 'br' )
455                                 : tail.getText && tailNbspRegex.test( tail.getText() ) ) )
456                 {
457                         return tail;
458                 }
459                 return false;
460         };
461
462 })();