1 | /*
|
---|
2 | Copyright (c) 2003-2009, 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 || 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 || 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 );
|
---|
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 ) === 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 ) === 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, true );
|
---|
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, false, true ) !== 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, true, true ) !== 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, true );
|
---|
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 | blockBoundaryNodeNameMatch = { hr : 1 };
|
---|
339 |
|
---|
340 | CKEDITOR.dom.element.prototype.isBlockBoundary = function( customNodeNames )
|
---|
341 | {
|
---|
342 | var nodeNameMatches = CKEDITOR.tools.extend( {},
|
---|
343 | blockBoundaryNodeNameMatch, customNodeNames || {} );
|
---|
344 |
|
---|
345 | return blockBoundaryDisplayMatch[ this.getComputedStyle( 'display' ) ] ||
|
---|
346 | nodeNameMatches[ this.getName() ];
|
---|
347 | };
|
---|
348 |
|
---|
349 | CKEDITOR.dom.walker.blockBoundary = function( customNodeNames )
|
---|
350 | {
|
---|
351 | return function( node , type )
|
---|
352 | {
|
---|
353 | return ! ( node.type == CKEDITOR.NODE_ELEMENT
|
---|
354 | && node.isBlockBoundary( customNodeNames ) );
|
---|
355 | };
|
---|
356 | };
|
---|
357 |
|
---|
358 | CKEDITOR.dom.walker.listItemBoundary = function()
|
---|
359 | {
|
---|
360 | return this.blockBoundary( { br : 1 } );
|
---|
361 | };
|
---|
362 | /**
|
---|
363 | * Whether the node is a bookmark node's inner text node.
|
---|
364 | */
|
---|
365 | CKEDITOR.dom.walker.bookmarkContents = function( node )
|
---|
366 | {
|
---|
367 | },
|
---|
368 |
|
---|
369 | /**
|
---|
370 | * Whether the to-be-evaluated node is a bookmark node OR bookmark node
|
---|
371 | * inner contents.
|
---|
372 | * @param {Boolean} contentOnly Whether only test againt the text content of
|
---|
373 | * bookmark node instead of the element itself(default).
|
---|
374 | * @param {Boolean} isReject Whether should return 'false' for the bookmark
|
---|
375 | * node instead of 'true'(default).
|
---|
376 | */
|
---|
377 | CKEDITOR.dom.walker.bookmark = function( contentOnly, isReject )
|
---|
378 | {
|
---|
379 | function isBookmarkNode( node )
|
---|
380 | {
|
---|
381 | return ( node && node.getName
|
---|
382 | && node.getName() == 'span'
|
---|
383 | && node.hasAttribute('_fck_bookmark') );
|
---|
384 | }
|
---|
385 |
|
---|
386 | return function( node )
|
---|
387 | {
|
---|
388 | var isBookmark, parent;
|
---|
389 | // Is bookmark inner text node?
|
---|
390 | isBookmark = ( node && !node.getName && ( parent = node.getParent() )
|
---|
391 | && isBookmarkNode( parent ) );
|
---|
392 | // Is bookmark node?
|
---|
393 | isBookmark = contentOnly ? isBookmark : isBookmark || isBookmarkNode( node );
|
---|
394 | return isReject ^ isBookmark;
|
---|
395 | };
|
---|
396 | };
|
---|
397 |
|
---|
398 | /**
|
---|
399 | * Whether the node contains only white-spaces characters.
|
---|
400 | * @param isReject
|
---|
401 | */
|
---|
402 | CKEDITOR.dom.walker.whitespaces = function( isReject )
|
---|
403 | {
|
---|
404 | return function( node )
|
---|
405 | {
|
---|
406 | var isWhitespace = node && ( node.type == CKEDITOR.NODE_TEXT )
|
---|
407 | && !CKEDITOR.tools.trim( node.getText() );
|
---|
408 | return isReject ^ isWhitespace;
|
---|
409 | };
|
---|
410 | };
|
---|
411 | })();
|
---|