1 /* selection.js is part of Aloha Editor project http://aloha-editor.org
  2  *
  3  * Aloha Editor is a WYSIWYG HTML5 inline editing library and editor. 
  4  * Copyright (c) 2010-2012 Gentics Software GmbH, Vienna, Austria.
  5  * Contributors http://aloha-editor.org/contribution.php 
  6  * 
  7  * Aloha Editor is free software; you can redistribute it and/or
  8  * modify it under the terms of the GNU General Public License
  9  * as published by the Free Software Foundation; either version 2
 10  * of the License, or any later version.
 11  *
 12  * Aloha Editor is distributed in the hope that it will be useful,
 13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 15  * GNU General Public License for more details.
 16  *
 17  * You should have received a copy of the GNU General Public License
 18  * along with this program; if not, write to the Free Software
 19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 20  * 
 21  * As an additional permission to the GNU GPL version 2, you may distribute
 22  * non-source (e.g., minimized or compacted) forms of the Aloha-Editor
 23  * source code without the copy of the GNU GPL normally required,
 24  * provided you include this license notice and a URL through which
 25  26  * recipients can access the Corresponding Source.
 27  */
 28 define([
 29 	'aloha/core',
 30 	'jquery',
 31 	'util/class',
 32 	'util/range',
 33 	'util/arrays',
 34 	'util/strings',
 35 	'aloha/console',
 36 	'PubSub',
 37 	'aloha/engine',
 38 	'aloha/ecma5shims',
 39 	'aloha/rangy-core'
 40 ], function (
 41 	Aloha,
 42 	jQuery,
 43 	Class,
 44 	Range,
 45 	Arrays,
 46 	Strings,
 47 	console,
 48 	PubSub,
 49 	Engine,
 50 	e5s
 51 ) {
 52 	"use strict";
 53 
 54 	var GENTICS = window.GENTICS;
 55 
 56 	function isCollapsedAndEmptyOrEndBr(rangeObject) {
 57 		var firstChild;
 58 		if (rangeObject.startContainer !== rangeObject.endContainer) {
 59 			return false;
 60 		}
 61 		// check whether the container starts in an element node
 62 		if (rangeObject.startContainer.nodeType != 1) {
 63 			return false;
 64 		}
 65 		firstChild = rangeObject.startContainer.firstChild;
 66 		return (!firstChild || (!firstChild.nextSibling && firstChild.nodeName == 'BR'));
 67 	}
 68 
 69 	function isCollapsedAndEndBr(rangeObject) {
 70 		if (rangeObject.startContainer !== rangeObject.endContainer) {
 71 			return false;
 72 		}
 73 		if (rangeObject.startContainer.nodeType != 1) {
 74 			return false;
 75 		}
 76 		return Engine.isEndBreak(rangeObject.startContainer);
 77 	}
 78 
 79 	var prevStartContext = null;
 80 	var prevEndContext = null;
 81 
 82 	function makeContextHtml(node, parents) {
 83 		var result = [],
 84 			parent,
 85 			len,
 86 			i;
 87 		if (1 === node.nodeType && node.nodeName !== 'BODY' && node.nodeName !== 'HTML') {
 88 			result.push(node.cloneNode(false).outerHTML);
 89 		} else {
 90 			result.push('#' + node.nodeType);
 91 		}
 92 		for (i = 0, len = parents.length; i < len; i++) {
 93 			parent = parents[i];
 94 			if (parent.nodeName === 'BODY' || parent.nodeName === 'HTML') {
 95 				// Although we limit the ancestors in most cases to the
 96 				// active editable, in some cases (copy&paste) the
 97 				// parent may be outside.
 98 				// On IE7 this means the following code may clone the
 99 				// HTML node too, which causes the browser to crash.
100 				// On other browsers, this is just an optimization
101 				// because the body and html elements should probably
102 				// not be considered part of the context of an edit
103 				// operation.
104 				break;
105 			}
106 			result.push(parent.cloneNode(false).outerHTML);
107 		}
108 		return result.join('');
109 	}
110 
111 	function getChangedContext(node, context) {
112 		var until = Aloha.activeEditable ? Aloha.activeEditable.obj.parent()[0] : null;
113 		var parents = jQuery(node).parentsUntil(until).get();
114 		var html = makeContextHtml(node, parents);
115 		var equal = (context && node === context.node && Arrays.equal(context.parents, parents) && html === context.html);
116 		return equal ? null : {
117 			node: node,
118 			parents: parents,
119 			html: html
120 		};
121 	}
122 
123 	function triggerSelectionContextChanged(rangeObject, event) {
124 		var startContainer = rangeObject.startContainer;
125 		var endContainer = rangeObject.endContainer;
126 		if (!startContainer || !endContainer) {
127 			console.warn("aloha/selection", "encountered range object without start or end container");
128 			return;
129 		}
130 		var startContext = getChangedContext(startContainer, prevStartContext);
131 		var endContext = getChangedContext(endContainer, prevEndContext);
132 		if (!startContext && !endContext) {
133 			return;
134 		}
135 		prevStartContext = startContext;
136 		prevEndContext = endContext;
137 
138 		/**
139 		 * @api documented in the guides
140 		 */
141 		PubSub.pub('aloha.selection.context-change', {
142 			range: rangeObject,
143 			event: event
144 		});
145 	}
146 
147 	/**
148 	 * @namespace Aloha
149 	 * @class Selection
150 	 * This singleton class always represents the current user selection
151 	 * @singleton
152 	 */
153 	var Selection = Class.extend({
154 		_constructor: function () {
155 			// Pseudo Range Clone being cleaned up for better HTML wrapping support
156 			this.rangeObject = {};
157 
158 			this.preventSelectionChangedFlag = false; // will remember if someone urged us to skip the next aloha-selection-changed event
159 
160 			// define basics first
161 			this.tagHierarchy = {
162 				'textNode': {},
163 				'abbr': {
164 					'textNode': true
165 				},
166 				'b': {
167 					'textNode': true,
168 					'b': true,
169 					'i': true,
170 					'em': true,
171 					'sup': true,
172 					'sub': true,
173 					'br': true,
174 					'span': true,
175 					'img': true,
176 					'a': true,
177 					'del': true,
178 					'ins': true,
179 					'u': true,
180 					'cite': true,
181 					'q': true,
182 					'code': true,
183 					'abbr': true,
184 					'strong': true
185 				},
186 				'pre': {
187 					'textNode': true,
188 					'b': true,
189 					'i': true,
190 					'em': true,
191 					'sup': true,
192 					'sub': true,
193 					'br': true,
194 					'span': true,
195 					'img': true,
196 					'a': true,
197 					'del': true,
198 					'ins': true,
199 					'u': true,
200 					'cite': true,
201 					'q': true,
202 					'code': true,
203 					'abbr': true
204 				},
205 				'blockquote': {
206 					'textNode': true,
207 					'b': true,
208 					'i': true,
209 					'em': true,
210 					'sup': true,
211 					'sub': true,
212 					'br': true,
213 					'span': true,
214 					'img': true,
215 					'a': true,
216 					'del': true,
217 					'ins': true,
218 					'u': true,
219 					'cite': true,
220 221 					'q': true,
222 					'code': true,
223 					'abbr': true,
224 					'p': true,
225 					'h1': true,
226 					'h2': true,
227 					'h3': true,
228 					'h4': true,
229 					'h5': true,
230 					'h6': true
231 				},
232 				'ins': {
233 					'textNode': true,
234 					'b': true,
235 					'i': true,
236 					'em': true,
237 					'sup': true,
238 					'sub': true,
239 					'br': true,
240 					'span': true,
241 					'img': true,
242 					'a': true,
243 					'u': true,
244 					'p': true,
245 					'h1': true,
246 					'h2': true,
247 					'h3': true,
248 					'h4': true,
249 					'h5': true,
250 					'h6': true
251 				},
252 				'ul': {
253 					'li': true
254 				},
255 				'ol': {
256 					'li': true
257 				},
258 				'li': {
259 					'textNode': true,
260 					'b': true,
261 					'i': true,
262 					'em': true,
263 					'sup': true,
264 					'sub': true,
265 					'br': true,
266 					'span': true,
267 					'img': true,
268 					'ul': true,
269 					'ol': true,
270 					'h1': true,
271 					'h2': true,
272 					'h3': true,
273 					'h4': true,
274 					'h5': true,
275 					'h6': true,
276 					'del': true,
277 					'ins': true,
278 					'u': true,
279 					'a': true
280 				},
281 				'tr': {
282 					'td': true,
283 					'th': true
284 				},
285 				'table': {
286 					'tr': true
287 				},
288 				'div': {
289 					'textNode': true,
290 					'b': true,
291 					'i': true,
292 					'em': true,
293 					'sup': true,
294 					'sub': true,
295 					'br': true,
296 					'span': true,
297 					'img': true,
298 					'ul': true,
299 					'ol': true,
300 					'table': true,
301 					'h1': true,
302 					'h2': true,
303 					'h3': true,
304 					'h4': true,
305 					'h5': true,
306 					'h6': true,
307 					'del': true,
308 					'ins': true,
309 					'u': true,
310 					'p': true,
311 					'div': true,
312 					'pre': true,
313 					'blockquote': true,
314 					'a': true
315 				},
316 				'h1': {
317 					'textNode': true,
318 					'b': true,
319 					'i': true,
320 					'em': true,
321 					'sup': true,
322 					'sub': true,
323 					'br': true,
324 					'span': true,
325 					'img': true,
326 					'a': true,
327 					'del': true,
328 					'ins': true,
329 					'u': true
330 				}
331 			};
332 
333 			// now reference the basics for all other equal tags (important: don't forget to include
334 			// the basics itself as reference: 'b' : this.tagHierarchy.b
335 			this.tagHierarchy = {
336 				'textNode': this.tagHierarchy.textNode,
337 				'abbr': this.tagHierarchy.abbr,
338 				'br': this.tagHierarchy.textNode,
339 				'img': this.tagHierarchy.textNode,
340 				'b': this.tagHierarchy.b,
341 				'strong': this.tagHierarchy.b,
342 				'code': this.tagHierarchy.b,
343 				'q': this.tagHierarchy.b,
344 				'blockquote': this.tagHierarchy.blockquote,
345 				'cite': this.tagHierarchy.b,
346 				'i': this.tagHierarchy.b,
347 				'em': this.tagHierarchy.b,
348 				'sup': this.tagHierarchy.b,
349 				'sub': this.tagHierarchy.b,
350 				'span': this.tagHierarchy.b,
351 				'del': this.tagHierarchy.del,
352 				'ins': this.tagHierarchy.ins,
353 				'u': this.tagHierarchy.b,
354 				'p': this.tagHierarchy.b,
355 				'pre': this.tagHierarchy.pre,
356 				'a': this.tagHierarchy.b,
357 				'ul': this.tagHierarchy.ul,
358 				'ol': this.tagHierarchy.ol,
359 				'li': this.tagHierarchy.li,
360 				'td': this.tagHierarchy.li,
361 				'div': this.tagHierarchy.div,
362 				'h1': this.tagHierarchy.h1,
363 				'h2': this.tagHierarchy.h1,
364 				'h3': this.tagHierarchy.h1,
365 				'h4': this.tagHierarchy.h1,
366 				'h5': this.tagHierarchy.h1,
367 				'h6': this.tagHierarchy.h1,
368 				'table': this.tagHierarchy.table
369 			};
370 
371 			// When applying this elements to selection they will replace the assigned elements
372 			this.replacingElements = {
373 				'h1': {
374 					'p': true,
375 					'h1': true,
376 					'h2': true,
377 					'h3': true,
378 					'h4': true,
379 					'h5': true,
380 					'h6': true,
381 					'pre': true,
382 					'blockquote': true
383 				}
384 			};
385 			this.replacingElements = {
386 				'h1': this.replacingElements.h1,
387 				'h2': this.replacingElements.h1,
388 				'h3': this.replacingElements.h1,
389 				'h4': this.replacingElements.h1,
390 				'h5': this.replacingElements.h1,
391 				'h6': this.replacingElements.h1,
392 				'pre': this.replacingElements.h1,
393 				'p': this.replacingElements.h1,
394 				'blockquote': this.replacingElements.h1
395 			};
396 			this.allowedToStealElements = {
397 				'h1': {
398 					'textNode': true
399 				}
400 			};
401 			this.allowedToStealElements = {
402 				'h1': this.allowedToStealElements.h1,
403 				'h2': this.allowedToStealElements.h1,
404 				'h3': this.allowedToStealElements.h1,
405 				'h4': this.allowedToStealElements.h1,
406 				'h5': this.allowedToStealElements.h1,
407 				'h6': this.allowedToStealElements.h1,
408 				'p': this.tagHierarchy.b
409 			};
410 		},
411 
412 		/**
413 		 * Class definition of a SelectionTree (relevant for all formatting / markup changes)
414 		 * TODO: remove this (was moved to range.js)
415 		 * Structure:
416 		 * +
417 		 * |-domobj: <reference to the DOM Object> (NOT jQuery)
418 		 * |-selection: defines if this node is marked by user [none|partial|full]
419 		 * |-children: recursive structure like this
420 		 * @hide
421 		 */
422 		SelectionTree: function () {
423 			this.domobj = {};
424 			this.selection = undefined;
425 			this.children = [];
426 		},
427 
428 		/**
429 		 * INFO: Method is used for integration with Gentics Aloha, has no use otherwise
430 		 * Updates the rangeObject according to the current user selection
431 		 * Method is always called on selection change
432 		 * @param objectClicked Object that triggered the selectionChange event
433 		 * @return true when rangeObject was modified, false otherwise
434 		 * @hide
435 		 */
436 		onChange: function (objectClicked, event, timeout) {
437 			if (this.updateSelectionTimeout) {
438 				window.clearTimeout(this.updateSelectionTimeout);
439 			}
440 
441 			// We have to update the selection in a timeout due to an IE
442 			// bug that is is caused by selecting some text and then
443 			// clicking once inside the selection (which collapses the
444 			// selection inside the previous selection).
445 			var selection = this;
446 			this.updateSelectionTimeout = window.setTimeout(function () {
447 				var range = new Aloha.Selection.SelectionRange(true);
448 				// We have to work around an IE bug that causes the user
449 				// selection to be incorrectly set on the body element
450 				// when the updateSelectionTimeout triggers. The
451 				// selection corrects itself after waiting a while.
452 				if (!range.startContainer || 'HTML' === range.startContainer.nodeName || 'BODY' === range.startContainer.nodeName) {
453 					if (!this.updateSelectionTimeout) {
454 						// First wait 5 millis, then 20 millis, 50 millis, 110 millis etc.
455 						selection.onChange(objectClicked, event, 10 + (timeout || 5) * 2);
456 					}
457 					return;
458 				}
459 				Aloha.Selection._updateSelection(event, range);
460 			}, timeout || 5);
461 		},
462 
463 		/**
464 		 * prevents the next aloha-selection-changed event from being triggered
465 		 */
466 		preventSelectionChanged: function () {
467 			this.preventSelectionChangedFlag = true;
468 		},
469 470 
		/**
471 		 * will return wheter selection change event was prevented or not, and reset the preventSelectionChangedFlag
472 		 * @return {Boolean} true if aloha-selection-change event was prevented
473 		 */
474 		isSelectionChangedPrevented: function () {
475 			var prevented = this.preventSelectionChangedFlag;
476 			this.preventSelectionChangedFlag = false;
477 			return prevented;
478 		},
479 
480 		/**
481 		 * Checks if the current rangeObject common ancector container is edtiable
482 		 * @return {Boolean} true if current common ancestor is editable
483 		 */
484 		isSelectionEditable: function () {
485 			return (this.rangeObject.commonAncestorContainer && jQuery(this.rangeObject.commonAncestorContainer).contentEditable());
486 		},
487 
488 		/**
489 		 * This method checks, if the current rangeObject common ancestor container has a 'data-aloha-floatingmenu-visible' Attribute.
490 		 * Needed in Floating Menu for exceptional display of floatingmenu.
491 		 */
492 		isFloatingMenuVisible: function () {
493 			var visible = jQuery(Aloha.Selection.rangeObject.commonAncestorContainer).attr('data-aloha-floatingmenu-visible');
494 			if (visible !== 'undefined') {
495 				if (visible === 'true') {
496 					return true;
497 				}
498 				return false;
499 			}
500 			return false;
501 		},
502 
503 		/**
504 		 * INFO: Method is used for integration with Gentics Aloha, has no use otherwise
505 		 * Updates the rangeObject according to the current user selection
506 		 * Method is always called on selection change
507 		 * @param event jQuery browser event object
508 		 * @return true when rangeObject was modified, false otherwise
509 		 * @hide
510 		 */
511 		updateSelection: function (event) {
512 			return this._updateSelection(event, null);
513 		},
514 
515 		/**
516 		 * Internal version of updateSelection that adds the range parameter to be
517 		 * able to work around an IE bug that caused the current user selection
518 		 * sometimes to be on the body element.
519 		 * @param {Object} event
520 		 * @param {Object} range a substitute for the current user selection. if not provided,
521 		 *   the current user selection will be used.
522 		 * @hide
523 		 */
524 		_updateSelection: function (event, range) {
525 			if (event && event.originalEvent &&
526 					true === event.originalEvent.stopSelectionUpdate) {
527 				return false;
528 			}
529 
530 			if (typeof range === 'undefined') {
531 				return false;
532 			}
533 
534 			this.rangeObject = range =
535 					range || new Aloha.Selection.SelectionRange(true);
536 
537 			// Determine the common ancestor container and update the selection
538 			// tree.
539 			range.update();
540 
541 			// Workaround for nasty IE bug that allows the user to select
542 			// text nodes inside areas with contenteditable "false"
543 			if (range && range.startContainer && range.endContainer) {
544 				var inEditable =
545 						jQuery(range.commonAncestorContainer)
546 							.closest('.aloha-editable').length;
547 
548 				if (inEditable) {
549 					var validStartPosition = !(3 === range.startContainer.nodeType &&
550 							!jQuery(range.startContainer.parentNode).contentEditable());
551 
552 					var validEndPosition = !(3 === range.endContainer.nodeType &&
553 							!jQuery(range.endContainer.parentNode).contentEditable());
554 
555 					if (!validStartPosition || !validEndPosition) {
556 						Aloha.getSelection().removeAllRanges();
557 						return true;
558 					}
559 				}
560 			}
561 
562 			// check if aloha-selection-changed event has been prevented
563 			if (this.isSelectionChangedPrevented()) {
564 				return true;
565 			}
566 
567 			Aloha.trigger('aloha-selection-changed-before', [this.rangeObject, event]);
568 
569 			// throw the event that the selection has changed. Plugins now have the
570 			// chance to react on the currentElements[childCount].children.lengthged selection
571 			Aloha.trigger('aloha-selection-changed', [this.rangeObject, event]);
572 
573 			triggerSelectionContextChanged(this.rangeObject, event);
574 
575 			Aloha.trigger('aloha-selection-changed-after', [this.rangeObject, event]);
576 
577 			return true;
578 		},
579 
580 		/**
581 		 * creates an object with x items containing all relevant dom objects.
582 		 * Structure:
583 		 * +
584 		 * |-domobj: <reference to the DOM Object> (NOT jQuery)
585 		 * |-selection: defines if this node is marked by user [none|partial|full]
586 		 * |-children: recursive structure like this ("x.." because it's then shown last in DOM Browsers...)
587 		 * TODO: remove this (was moved to range.js)
588 		 *
589 		 * @param rangeObject "Aloha clean" range object including a commonAncestorContainer
590 		 * @return obj selection
591 		 * @hide
592 		 */
593 		getSelectionTree: function (rangeObject) {
594 			if (!rangeObject) { // if called without any parameters, the method acts as getter for this.selectionTree
595 				return this.rangeObject.getSelectionTree();
596 			}
597 			if (!rangeObject.commonAncestorContainer) {
598 				Aloha.Log.error(this, 'the rangeObject is missing the commonAncestorContainer');
599 				return false;
600 			}
601 
602 603 			this.inselection = false;
604 
605 			// before getting the selection tree, we do a cleanup
606 			if (GENTICS.Utils.Dom.doCleanup({ 'merge': true }, rangeObject)) {
607 				rangeObject.update();
608 				rangeObject.select();
609 			}
610 
611 			return this.recursiveGetSelectionTree(rangeObject, rangeObject.commonAncestorContainer);
612 		},
613 
614 		/**
615 		 * Recursive inner function for generating the selection tree.
616 		 * TODO: remove this (was moved to range.js)
617 		 * @param rangeObject range object
618 		 * @param currentObject current DOM object for which the selection tree shall be generated
619 		 * @return array of SelectionTree objects for the children of the current DOM object
620 		 * @hide
621 		 */
622 		recursiveGetSelectionTree: function (rangeObject, currentObject) {
623 			// get all direct children of the given object
624 			var jQueryCurrentObject = jQuery(currentObject),
625 				childCount = 0,
626 				that = this,
627 				currentElements = [];
628 
629 			jQueryCurrentObject.contents().each(function (index) {
630 				var selectionType = 'none',
631 					startOffset = false,
632 					endOffset = false,
633 					collapsedFound = false,
634 					i,
635 				    elementsLength,
636 					noneFound = false,
637 					partialFound = false,
638 					fullFound = false;
639 
640 				// check for collapsed selections between nodes
641 				if (rangeObject.isCollapsed() && currentObject === rangeObject.startContainer && rangeObject.startOffset == index) {
642 					// insert an extra selectiontree object for the collapsed selection here
643 					currentElements[childCount] = new Aloha.Selection.SelectionTree();
644 					currentElements[childCount].selection = 'collapsed';
645 646 					currentElements[childCount].domobj = undefined;
647 					that.inselection = false;
648 					collapsedFound = true;
649 					childCount++;
650 				}
651 
652 				if (!that.inselection && !collapsedFound) {
653 					// the start of the selection was not yet found, so look for it now
654 					// check whether the start of the selection is found here
655 
656 					// Try to read the nodeType property and return if we do not have permission
657 					// ie.: frame document to an external URL
658 					var nodeType;
659 					try {
660 						nodeType = this.nodeType;
661 					} catch (e) {
662 						return;
663 					}
664 
665 					// check is dependent on the node type
666 					switch (nodeType) {
667 					case 3:
668 						// text node
669 						if (this === rangeObject.startContainer) {
670 							// the selection starts here
671 							that.inselection = true;
672 
673 							// when the startoffset is > 0, the selection type is only partial
674 							selectionType = rangeObject.startOffset > 0 ? 'partial' : 'full';
675 							startOffset = rangeObject.startOffset;
676 							endOffset = this.length;
677 						}
678 						break;
679 					case 1:
680 						// element node
681 						if (this === rangeObject.startContainer && rangeObject.startOffset === 0) {
682 							// the selection starts here
683 							that.inselection = true;
684 							selectionType = 'full';
685 						}
686 						if (currentObject === rangeObject.startContainer && rangeObject.startOffset === index) {
687 							// the selection starts here
688 							that.inselection = true;
689 							selectionType = 'full';
690 						}
691 						break;
692 					}
693 				}
694 
695 				if (that.inselection && !collapsedFound) {
696 					if (selectionType == 'none') {
697 						selectionType = 'full';
698 					}
699 					// we already found the start of the selection, so look for the end of the selection now
700 					// check whether the end of the selection is found here
701 
702 					switch (this.nodeType) {
703 					case 3:
704 						// text node
705 						if (this === rangeObject.endContainer) {
706 							// the selection ends here
707 							that.inselection = false;
708 
709 							// check for partial selection here
710 							if (rangeObject.endOffset < this.length) {
711 								selectionType = 'partial';
712 							}
713 							if (startOffset === false) {
714 								startOffset = 0;
715 							}
716 							endOffset = rangeObject.endOffset;
717 						}
718 						break;
719 					case 1:
720 						// element node
721 						if (this === rangeObject.endContainer && rangeObject.endOffset === 0) {
722 							that.inselection = false;
723 						}
724 						break;
725 					}
726 					if (currentObject === rangeObject.endContainer && rangeObject.endOffset <= index) {
727 						that.inselection = false;
728 						selectionType = 'none';
729 					}
730 				}
731 
732 				// create the current selection tree entry
733 				currentElements[childCount] = new Aloha.Selection.SelectionTree();
734 				currentElements[childCount].domobj = this;
735 				currentElements[childCount].selection = selectionType;
736 				if (selectionType == 'partial') {
737 					currentElements[childCount].startOffset = startOffset;
738 					currentElements[childCount].endOffset = endOffset;
739 				}
740 
741 				// now do the recursion step into the current object
742 				currentElements[childCount].children = that.recursiveGetSelectionTree(rangeObject, this);
743 				elementsLength = currentElements[childCount].children.length;
744 
745 				// check whether a selection was found within the children
746 				if (elementsLength > 0) {
747 					for (i = 0; i < elementsLength; ++i) {
748 						switch (currentElements[childCount].children[i].selection) {
749 						case 'none':
750 							noneFound = true;
751 							break;
752 						case 'full':
753 							fullFound = true;
754 							break;
755 						case 'partial':
756 							partialFound = true;
757 							break;
758 						}
759 					}
760 
761 					if (partialFound || (fullFound && noneFound)) {
762 						// found at least one 'partial' selection in the children, or both 'full' and 'none', so this element is also 'partial' selected
763 						currentElements[childCount].selection = 'partial';
764 					} else if (fullFound && !partialFound && !noneFound) {
765 						// only found 'full' selected children, so this element is also 'full' selected
766 						currentElements[childCount].selection = 'full';
767 					}
768 				}
769 
770 				childCount++;
771 			});
772 
773 			// extra check for collapsed selections at the end of the current element
774 			if (rangeObject.isCollapsed() && currentObject === rangeObject.startContainer && rangeObject.startOffset == currentObject.childNodes.length) {
775 				currentElements[childCount] = new Aloha.Selection.SelectionTree();
776 				currentElements[childCount].selection = 'collapsed';
777 				currentElements[childCount].domobj = undefined;
778 			}
779 
780 			return currentElements;
781 		},
782 
783 		/**
784 		 * Get the currently selected range
785 		 * @return {Aloha.Selection.SelectionRange} currently selected range
786 		 * @method
787 		 */
788 		getRangeObject: function () {
789 			return this.rangeObject;
790 		},
791 
792 		/**
793 		 * method finds out, if a node is within a certain markup or not
794 		 * @param rangeObj Aloha rangeObject
795 		 * @param startOrEnd boolean; defines, if start or endContainer should be used: false for start, true for end
796 		 * @param markupObject jQuery object of the markup to look for
797 		 * @param tagComparator method, which is used to compare the dom object and the jQuery markup object. the method must accept 2 parameters, the first is the domobj, the second is the jquery object. if no method is specified, the method this.standardTextLevelSemanticsComparator is used
798 		 * @param limitObject dom object which limits the search are within the dom. normally this will be the active Editable
799 		 * @return true, if the markup is effective on the range objects start or end node
800 		 * @hide
801 		 */
802 		isRangeObjectWithinMarkup: function (rangeObject, startOrEnd, markupObject, tagComparator, limitObject) {
803 			var domObj = !startOrEnd ? rangeObject.startContainer : rangeObject.endContainer,
804 				that = this,
805 				parents = jQuery(domObj).parents(),
806 				returnVal = false,
807 				i = -1;
808 
809 			// check if a comparison method was passed as parameter ...
810 			if (typeof tagComparator !== 'undefined' && typeof tagComparator !== 'function') {
811 				Aloha.Log.error(this, 'parameter tagComparator is not a function');
812 			}
813 			// ... if not use this as standard tag comparison method
814 			if (typeof tagComparator === 'undefined') {
815 				tagComparator = function (domobj, markupObject) {
816 					return that.standardTextLevelSemanticsComparator(domobj, markupObject); // TODO should actually be this.getStandardTagComparator(markupObject)
817 				};
818 			}
819 
820 			if (parents.length > 0) {
821 				parents.each(function () {
822 					// the limit object was reached (normally the Editable Element)
823 					if (this === limitObject) {
824 						Aloha.Log.debug(that, 'reached limit dom obj');
825 						return false; // break() of jQuery .each(); THIS IS NOT THE FUNCTION RETURN VALUE
826 					}
827 					if (tagComparator(this, markupObject)) {
828 						if (returnVal === false) {
829 							returnVal = [];
830 						}
831 						Aloha.Log.debug(that, 'reached object equal to markup');
832 						i++;
833 						returnVal[i] = this;
834 						return true; // continue() of jQuery .each(); THIS IS NOT THE FUNCTION RETURN VALUE
835 					}
836 				});
837 			}
838 			return returnVal;
839 		},
840 
841 		/**
842 		 * standard method, to compare a domobj and a jquery object for sections and grouping content (e.g. p, h1, h2, ul, ....).
843 		 * is always used when no other tag comparator is passed as parameter
844 		 * @param domobj domobject to compare with markup
845 		 * @param markupObject jQuery object of the markup to compare with domobj
846 		 * @return true if objects are equal and false if not
847 		 * @hide
848 		 */
849 		standardSectionsAndGroupingContentComparator: function (domobj, markupObject) {
850 			if (domobj.nodeType !== 1) {
851 				Aloha.Log.debug(this, 'only element nodes (nodeType == 1) can be compared');
852 				return false;
853 			}
854 			if (!markupObject[0].nodeName) {
855 				return false;
856 			}
857 			var elemMap = Aloha.Selection.replacingElements[domobj.nodeName.toLowerCase()];
858 			return elemMap && elemMap[markupObject[0].nodeName.toLowerCase()];
859 		},
860 
861 		/**
862 		 * standard method, to compare a domobj and a jquery object for their tagName (aka span elements, e.g. b, i, sup, span, ...).
863 		 * is always used when no other tag comparator is passed as parameter
864 		 * @param domobj domobject to compare with markup
865 		 * @param markupObject jQuery object of the markup to compare with domobj
866 		 * @return true if objects are equal and false if not
867 		 * @hide
868 		 */
869 		standardTagNameComparator: function (domobj, markupObject) {
870 			if (domobj.nodeType === 1) {
871 				if (domobj.nodeName != markupObject[0].nodeName) {
872 					return false;
873 				}
874 				return true;
875 			}
876 			Aloha.Log.debug(this, 'only element nodes (nodeType == 1) can be compared');
877 			return false;
878 		},
879 
880 		/**
881 		 * standard method, to compare a domobj and a jquery object for text level semantics (aka span elements, e.g. b, i, sup, span, ...).
882 		 * is always used when no other tag comparator is passed as parameter
883 		 * @param domobj domobject to compare with markup
884 		 * @param markupObject jQuery object of the markup to compare with domobj
885 		 * @return true if objects are equal and false if not
886 		 * @hide
887 		 */
888 		standardTextLevelSemanticsComparator: function (domobj, markupObject) {
889 			// only element nodes can be compared
890 			if (domobj.nodeType === 1) {
891 				if (domobj.nodeName != markupObject[0].nodeName) {
892 					return false;
893 				}
894 				if (!this.standardAttributesComparator(domobj, markupObject)) {
895 					return false;
896 				}
897 				return true;
898 			}
899 			Aloha.Log.debug(this, 'only element nodes (nodeType == 1) can be compared');
900 			return false;
901 		},
902 
903 
904 		/**
905 		 * standard method, to compare attributes of one dom obj and one markup obj (jQuery)
906 		 * @param domobj domobject to compare with markup
907 		 * @param markupObject jQuery object of the markup to compare with domobj
908 		 * @return true if objects are equal and false if not
909 		 * @hide
910 		 */
911 		standardAttributesComparator: function (domobj, markupObject) {
912 			var classesA = Strings.words((domobj && domobj.className) || '');
913 			var classesB = Strings.words((markupObject.length && markupObject[0].className) || '');
914 			Arrays.sortUnique(classesA);
915 			Arrays.sortUnique(classesB);
916 			return Arrays.equal(classesA, classesB);
917 		},
918 
919 		/**
920 		 * method finds out, if a node is within a certain markup or not
921 		 * @param rangeObj Aloha rangeObject
922 		 * @param markupObject jQuery object of the markup to be applied (e.g. created with obj = jQuery('<b></b>'); )
923 		 * @param tagComparator method, which is used to compare the dom object and the jQuery markup object. the method must accept 2 parameters, the first is the domobj, the second is the jquery object. if no method is specified, the method this.standardTextLevelSemanticsComparator is used
924 		 * @return void; TODO: should return true if the markup applied successfully and false if not
925 		 * @hide
926 		 */
927 		changeMarkup: function (rangeObject, markupObject, tagComparator) {
928 			var tagName = markupObject[0].tagName.toLowerCase(),
929 				newCAC,
930 			    limitObject,
931 				backupRangeObject,
932 				relevantMarkupObjectsAtSelectionStart = this.isRangeObjectWithinMarkup(rangeObject, false, markupObject, tagComparator, limitObject),
933 				relevantMarkupObjectsAtSelectionEnd = this.isRangeObjectWithinMarkup(rangeObject, true, markupObject, tagComparator, limitObject),
934 				nextSibling,
935 			    relevantMarkupObjectAfterSelection,
936 				prevSibling,
937 			    relevantMarkupObjectBeforeSelection,
938 				extendedRangeObject;
939 			var parentElement;
940 
941 			// if the element is a replacing element (like p/h1/h2/h3/h4/h5/h6...), which must not wrap each other
942 			// use a clone of rangeObject
943 			if (this.replacingElements[tagName]) {
944 				// backup rangeObject for later selection;
945 946 				backupRangeObject = rangeObject;
947 
948 				// create a new range object to not modify the orginal
949 				rangeObject = new this.SelectionRange(rangeObject);
950 
951 				// either select the active Editable as new commonAncestorContainer (CAC) or use the body
952 				if (Aloha.activeEditable) {
953 					newCAC = Aloha.activeEditable.obj.get(0);
954 				} else {
955 					newCAC = jQuery('body');
956 				}
957 				// update rangeObject by setting the newCAC and automatically recalculating the selectionTree
958 				rangeObject.update(newCAC);
959 
960 				// store the information, that the markupObject can be replaced (not must be!!) inside the jQuery markup object
961 				markupObject.isReplacingElement = true;
962 			} else {
963 				// if the element is NOT a replacing element, then something needs to be selected, otherwise it can not be wrapped
964 				// therefor the method can return false, if nothing is selected ( = rangeObject is collapsed)
965 				if (rangeObject.isCollapsed()) {
966 					Aloha.Log.debug(this, 'early returning from applying markup because nothing is currently selected');
967 					return false;
968 				}
969 			}
970 
971 			// is Start/End DOM Obj inside the markup to change
972 			if (Aloha.activeEditable) {
973 				limitObject = Aloha.activeEditable.obj[0];
974 			} else {
975 				limitObject = jQuery('body');
976 			}
977 
978 			if (!markupObject.isReplacingElement && rangeObject.startOffset === 0) { // don't care about replacers, because they never extend
979 				if (null != (prevSibling = this.getTextNodeSibling(false, rangeObject.commonAncestorContainer.parentNode, rangeObject.startContainer))) {
980 					relevantMarkupObjectBeforeSelection = this.isRangeObjectWithinMarkup({
981 						startContainer: prevSibling,
982 						startOffset: 0
983 					}, false, markupObject, tagComparator, limitObject);
984 				}
985 			}
986 			if (!markupObject.isReplacingElement && (rangeObject.endOffset === rangeObject.endContainer.length)) { // don't care about replacers, because they never extend
987 				if (null != (nextSibling = this.getTextNodeSibling(true, rangeObject.commonAncestorContainer.parentNode, rangeObject.endContainer))) {
988 					relevantMarkupObjectAfterSelection = this.isRangeObjectWithinMarkup({
989 						startContainer: nextSibling,
990 						startOffset: 0
991 					}, false, markupObject, tagComparator, limitObject);
992 				}
993 			}
994 
995 996 			// decide what to do (expand or reduce markup)
997 			// Alternative A: from markup to no-markup: markup will be removed in selection;
998 			// reapplied from original markup start to selection start
999 			if (!markupObject.isReplacingElement && (relevantMarkupObjectsAtSelectionStart && !relevantMarkupObjectsAtSelectionEnd)) {
1000 				Aloha.Log.info(this, 'markup 2 non-markup');
1001 				this.prepareForRemoval(rangeObject.getSelectionTree(), markupObject, tagComparator);
1002 				jQuery(relevantMarkupObjectsAtSelectionStart).addClass('preparedForRemoval');
1003 				this.insertCroppedMarkups(relevantMarkupObjectsAtSelectionStart, rangeObject, false, tagComparator);
1004 			} else if (!markupObject.isReplacingElement && relevantMarkupObjectsAtSelectionStart && relevantMarkupObjectsAtSelectionEnd) {
1005 				// Alternative B: from markup to markup:
1006 				// remove selected markup (=split existing markup if single, shrink if two different)
1007 				Aloha.Log.info(this, 'markup 2 markup');
1008 				this.prepareForRemoval(rangeObject.getSelectionTree(), markupObject, tagComparator);
1009 				this.splitRelevantMarkupObject(relevantMarkupObjectsAtSelectionStart, relevantMarkupObjectsAtSelectionEnd, rangeObject, tagComparator);
1010 			} else if (!markupObject.isReplacingElement && ((!relevantMarkupObjectsAtSelectionStart && relevantMarkupObjectsAtSelectionEnd) || relevantMarkupObjectAfterSelection || relevantMarkupObjectBeforeSelection)) { //
1011 				// Alternative C: from no-markup to markup OR with next2markup:
1012 				// new markup is wrapped from selection start to end of originalmarkup, original is remove afterwards
1013 				Aloha.Log.info(this, 'non-markup 2 markup OR with next2markup');
1014 				// move end of rangeObject to end of relevant markups
1015 				if (relevantMarkupObjectBeforeSelection && relevantMarkupObjectAfterSelection) {
1016 					extendedRangeObject = new Aloha.Selection.SelectionRange(rangeObject);
1017 					extendedRangeObject.startContainer = jQuery(relevantMarkupObjectBeforeSelection[relevantMarkupObjectBeforeSelection.length - 1]).textNodes()[0];
1018 					extendedRangeObject.startOffset = 0;
1019 					extendedRangeObject.endContainer = jQuery(relevantMarkupObjectAfterSelection[relevantMarkupObjectAfterSelection.length - 1]).textNodes().last()[0];
1020 					extendedRangeObject.endOffset = extendedRangeObject.endContainer.length;
1021 					extendedRangeObject.update();
1022 					this.applyMarkup(extendedRangeObject.getSelectionTree(), rangeObject, markupObject, tagComparator);
1023 					Aloha.Log.info(this, 'double extending previous markup(previous and after selection), actually wrapping it ...');
1024 
1025 				} else if (relevantMarkupObjectBeforeSelection && !relevantMarkupObjectAfterSelection && !relevantMarkupObjectsAtSelectionEnd) {
1026 					this.extendExistingMarkupWithSelection(relevantMarkupObjectBeforeSelection, rangeObject, false, tagComparator);
1027 					Aloha.Log.info(this, 'extending previous markup');
1028 
1029 				} else if (relevantMarkupObjectBeforeSelection && !relevantMarkupObjectAfterSelection && relevantMarkupObjectsAtSelectionEnd) {
1030 					extendedRangeObject = new Aloha.Selection.SelectionRange(rangeObject);
1031 					extendedRangeObject.startContainer = jQuery(relevantMarkupObjectBeforeSelection[relevantMarkupObjectBeforeSelection.length - 1]).textNodes()[0];
1032 					extendedRangeObject.startOffset = 0;
1033 					extendedRangeObject.endContainer = jQuery(relevantMarkupObjectsAtSelectionEnd[relevantMarkupObjectsAtSelectionEnd.length - 1]).textNodes().last()[0];
1034 					extendedRangeObject.endOffset = extendedRangeObject.endContainer.length;
1035 					extendedRangeObject.update();
1036 					this.applyMarkup(extendedRangeObject.getSelectionTree(), rangeObject, markupObject, tagComparator);
1037 					Aloha.Log.info(this, 'double extending previous markup(previous and relevant at the end), actually wrapping it ...');
1038 
1039 				} else if (!relevantMarkupObjectBeforeSelection && relevantMarkupObjectAfterSelection) {
1040 					this.extendExistingMarkupWithSelection(relevantMarkupObjectAfterSelection, rangeObject, true, tagComparator);
1041 					Aloha.Log.info(this, 'extending following markup backwards');
1042 
1043 				} else {
1044 					this.extendExistingMarkupWithSelection(relevantMarkupObjectsAtSelectionEnd, rangeObject, true, tagComparator);
1045 				}
1046 			} else if (markupObject.isReplacingElement || (!relevantMarkupObjectsAtSelectionStart && !relevantMarkupObjectsAtSelectionEnd && !relevantMarkupObjectBeforeSelection && !relevantMarkupObjectAfterSelection)) {
1047 				// Alternative D: no-markup to no-markup: easy
1048 				Aloha.Log.info(this, 'non-markup 2 non-markup');
1049 
1050 				// workaround to keep the caret at the right position if it's an empty element
1051 				// applyMarkup was not working correctly and has a lot of overhead we don't need in that case
1052 				if (isCollapsedAndEmptyOrEndBr(rangeObject)) {
1053 					var newMarkup = markupObject.clone();
1054 
1055 					if (isCollapsedAndEndBr(rangeObject)) {
1056 						newMarkup[0].appendChild(Engine.createEndBreak());
1057 					}
1058 
1059 					// setting the focus is needed for mozilla and IE 7 to have a working rangeObject.select()
1060 					if (Aloha.activeEditable && jQuery.browser.mozilla) {
1061 						Aloha.activeEditable.obj.focus();
1062 					}
1063 
1064 					if (Engine.isEditable(rangeObject.startContainer)) {
1065 						Engine.copyAttributes(rangeObject.startContainer, newMarkup[0]);
1066 						jQuery(rangeObject.startContainer).after(newMarkup[0]).remove();
1067 					} else if (Engine.isEditingHost(rangeObject.startContainer)) {
1068 						jQuery(rangeObject.startContainer).append(newMarkup[0]);
1069 						Engine.ensureContainerEditable(newMarkup[0]);
1070 					}
1071 
1072 					backupRangeObject.startContainer = newMarkup[0];
1073 					backupRangeObject.endContainer = newMarkup[0];
1074 					backupRangeObject.startOffset = 0;
1075 					backupRangeObject.endOffset = 0;
1076 					return;
1077 				}
1078 				this.applyMarkup(rangeObject.getSelectionTree(), rangeObject, markupObject, tagComparator, {
1079 					setRangeObject2NewMarkup: true
1080 				});
1081 				backupRangeObject.startContainer = rangeObject.startContainer;
1082 				backupRangeObject.endContainer = rangeObject.endContainer;
1083 				backupRangeObject.startOffset = rangeObject.startOffset;
1084 				backupRangeObject.endOffset = rangeObject.endOffset;
1085 			}
1086 
1087 			if (markupObject.isReplacingElement) {
1088 				//Check if the startContainer is one of the zapped elements
1089 				if (backupRangeObject && backupRangeObject.startContainer.className && backupRangeObject.startContainer.className.indexOf('preparedForRemoval') > -1) {
1090 					//var parentElement = jQuery(backupRangeObject.startContainer).closest(markupObject[0].tagName).get(0);
1091 					parentElement = jQuery(backupRangeObject.startContainer).parents(markupObject[0].tagName).get(0);
1092 					backupRangeObject.startContainer = parentElement;
1093 					rangeObject.startContainer = parentElement;
1094 				}
1095 				//check if the endContainer is one of the zapped elements
1096 				if (backupRangeObject && backupRangeObject.endContainer.className && backupRangeObject.endContainer.className.indexOf('preparedForRemoval') > -1) {
1097 					//var parentElement = jQuery(backupRangeObject.endContainer).closest(markupObject[0].tagName).get(0);
1098 					parentElement = jQuery(backupRangeObject.endContainer).parents(markupObject[0].tagName).get(0);
1099 					backupRangeObject.endContainer = parentElement;
1100 					rangeObject.endContainer = parentElement;
1101 				}
1102 			}
1103 			// remove all marked items
1104 			jQuery('.preparedForRemoval').zap();
1105 
1106 			// recalculate cac and selectionTree
1107 
1108 			// update selection
1109 			if (markupObject.isReplacingElement) {
1110 				//After the zapping we have to check for wrong offsets
1111 				if (e5s.Node.ELEMENT_NODE === backupRangeObject.startContainer.nodeType && backupRangeObject.startContainer.childNodes && backupRangeObject.startContainer.childNodes.length < backupRangeObject.startOffset) {
1112 					backupRangeObject.startOffset = backupRangeObject.startContainer.childNodes.length;
1113 					rangeObject.startOffset = backupRangeObject.startContainer.childNodes.length;
1114 				}
1115 				if (e5s.Node.ELEMENT_NODE === backupRangeObject.endContainer.nodeType && backupRangeObject.endContainer.childNodes && backupRangeObject.endContainer.childNodes.length < backupRangeObject.endOffset) {
1116 					backupRangeObject.endOffset = backupRangeObject.endContainer.childNodes.length;
1117 					rangeObject.endOffset = backupRangeObject.endContainer.childNodes.length;
1118 				}
1119 				rangeObject.endContainer = backupRangeObject.endContainer;
1120 				rangeObject.endOffset = backupRangeObject.endOffset;
1121 				rangeObject.startContainer = backupRangeObject.startContainer;
1122 				rangeObject.startOffset = backupRangeObject.startOffset;
1123 				backupRangeObject.update();
1124 				backupRangeObject.select();
1125 			} else {
1126 				rangeObject.update();
1127 				rangeObject.select();
1128 			}
1129 		},
1130 
1131 		/**
1132 		 * method compares a JS array of domobjects with a range object and decides, if the rangeObject spans the whole markup objects. method is used to decide if a markup2markup selection can be completely remove or if it must be splitted into 2 separate markups
1133 		 * @param relevantMarkupObjectsAtSelectionStart JS Array of dom objects, which are parents to the rangeObject.startContainer
1134 		 * @param relevantMarkupObjectsAtSelectionEnd JS Array of dom objects, which are parents to the rangeObject.endContainer
1135 		 * @param rangeObj Aloha rangeObject
1136 		 * @return true, if rangeObjects and markup objects are identical, false otherwise
1137 		 * @hide
1138 		 */
1139 		areMarkupObjectsAsLongAsRangeObject: function (relevantMarkupObjectsAtSelectionStart, relevantMarkupObjectsAtSelectionEnd, rangeObject) {
1140 			var i, el, textNode, relMarkupEnd, relMarkupStart;
1141 
1142 			if (rangeObject.startOffset !== 0) {
1143 				return false;
1144 			}
1145 
1146 			for (i = 0, relMarkupStart = relevantMarkupObjectsAtSelectionStart.length; i < relMarkupStart; i++) {
1147 				el = jQuery(relevantMarkupObjectsAtSelectionStart[i]);
1148 				if (el.textNodes().first()[0] !== rangeObject.startContainer) {
1149 					return false;
1150 				}
1151 			}
1152 
1153 			for (i = 0, relMarkupEnd = relevantMarkupObjectsAtSelectionEnd.length; i < relMarkupEnd; i++) {
1154 				el = jQuery(relevantMarkupObjectsAtSelectionEnd[i]);
1155 				textNode = el.textNodes().last()[0];
1156 				if (textNode !== rangeObject.endContainer || textNode.length != rangeObject.endOffset) {
1157 					return false;
1158 				}
1159 			}
1160 
1161 			return true;
1162 		},
1163 
1164 		/**
1165 		 * method used to remove/split markup from a "markup2markup" selection
1166 		 * @param relevantMarkupObjectsAtSelectionStart JS Array of dom objects, which are parents to the rangeObject.startContainer
1167 		 * @param relevantMarkupObjectsAtSelectionEnd JS Array of dom objects, which are parents to the rangeObject.endContainer
1168 		 * @param rangeObj Aloha rangeObject
1169 		 * @param tagComparator method, which is used to compare the dom object and the jQuery markup object. the method must accept 2 parameters, the first is the domobj, the second is the jquery object. if no method is specified, the method this.standardTextLevelSemanticsComparator is used
1170 		 * @return true (always, since no "false" case is currently known...but might be added)
1171 		 * @hide
1172 		 */
1173 		splitRelevantMarkupObject: function (relevantMarkupObjectsAtSelectionStart, relevantMarkupObjectsAtSelectionEnd, rangeObject, tagComparator) {
1174 			// mark them to be deleted
1175 			jQuery(relevantMarkupObjectsAtSelectionStart).addClass('preparedForRemoval');
1176 			jQuery(relevantMarkupObjectsAtSelectionEnd).addClass('preparedForRemoval');
1177 
1178 			// check if the rangeObject is identical with the relevantMarkupObjects (in this case the markup can simply be removed)
1179 			if (this.areMarkupObjectsAsLongAsRangeObject(relevantMarkupObjectsAtSelectionStart, relevantMarkupObjectsAtSelectionEnd, rangeObject)) {
1180 				return true;
1181 			}
1182 
1183 			// find intersection (this can always only be one dom element (namely the highest) because all others will be removed
1184 			var relevantMarkupObjectAtSelectionStartAndEnd = this.intersectRelevantMarkupObjects(relevantMarkupObjectsAtSelectionStart, relevantMarkupObjectsAtSelectionEnd);
1185 
1186 			if (relevantMarkupObjectAtSelectionStartAndEnd) {
1187 				this.insertCroppedMarkups([relevantMarkupObjectAtSelectionStartAndEnd], rangeObject, false, tagComparator);
1188 				this.insertCroppedMarkups([relevantMarkupObjectAtSelectionStartAndEnd], rangeObject, true, tagComparator);
1189 			} else {
1190 				this.insertCroppedMarkups(relevantMarkupObjectsAtSelectionStart, rangeObject, false, tagComparator);
1191 				this.insertCroppedMarkups(relevantMarkupObjectsAtSelectionEnd, rangeObject, true, tagComparator);
1192 			}
1193 			return true;
1194 		},
1195 
1196 		/**
1197 		 * method takes two arrays of bottom up dom objects, compares them and returns either the object closest to the root or false
1198 		 * @param relevantMarkupObjectsAtSelectionStart JS Array of dom objects
1199 		 * @param relevantMarkupObjectsAtSelectionEnd JS Array of dom objects
1200 		 * @return dom object closest to the root or false
1201 		 * @hide
1202 		 */
1203 		intersectRelevantMarkupObjects: function (relevantMarkupObjectsAtSelectionStart, relevantMarkupObjectsAtSelectionEnd) {
1204 			var intersection = false, i, elStart, j, elEnd, relMarkupStart, relMarkupEnd;
1205 			if (!relevantMarkupObjectsAtSelectionStart || !relevantMarkupObjectsAtSelectionEnd) {
1206 				return intersection; // we can only intersect, if we have to arrays!
1207 			}
1208 			relMarkupStart = relevantMarkupObjectsAtSelectionStart.length;
1209 			relMarkupEnd = relevantMarkupObjectsAtSelectionEnd.length;
1210 			for (i = 0; i < relMarkupStart; i++) {
1211 				elStart = relevantMarkupObjectsAtSelectionStart[i];
1212 				for (j = 0; j < relMarkupEnd; j++) {
1213 					elEnd = relevantMarkupObjectsAtSelectionEnd[j];
1214 					if (elStart === elEnd) {
1215 						intersection = elStart;
1216 					}
1217 				}
1218 			}
1219 			return intersection;
1220 		},
1221 
1222 		/**
1223 		 * method used to add markup to a nonmarkup2markup selection
1224 		 * @param relevantMarkupObjects JS Array of dom objects effecting either the start or endContainer of a selection (which should be extended)
1225 		 * @param rangeObject Aloha rangeObject the markups should be extended to
1226 		 * @param startOrEnd boolean; defines, if the existing markups should be extended forwards or backwards (is propably redundant and could be found out by comparing start or end container with the markup array dom objects)
1227 		 * @param tagComparator method, which is used to compare the dom object and the jQuery markup object. the method must accept 2 parameters, the first is the domobj, the second is the jquery object. if no method is specified, the method this.standardTextLevelSemanticsComparator is used
1228 		 * @return true
1229 		 * @hide
1230 		 */
1231 		extendExistingMarkupWithSelection: function (relevantMarkupObjects, rangeObject, startOrEnd, tagComparator) {
1232 			var extendMarkupsAtStart, extendMarkupsAtEnd, objects, i, relMarkupLength, el, textnodes, nodeNr;
1233 			if (!startOrEnd) { // = Start
1234 				// start part of rangeObject should be used, therefor existing markups are cropped at the end
1235 				extendMarkupsAtStart = true;
1236 			}
1237 			if (startOrEnd) { // = End
1238 				// end part of rangeObject should be used, therefor existing markups are cropped at start (beginning)
1239 				extendMarkupsAtEnd = true;
1240 			}
1241 			objects = [];
1242 			for (i = 0, relMarkupLength = relevantMarkupObjects.length; i < relMarkupLength; i++) {
1243 				objects[i] = new this.SelectionRange();
1244 				el = relevantMarkupObjects[i];
1245 				if (extendMarkupsAtEnd && !extendMarkupsAtStart) {
1246 					objects[i].startContainer = rangeObject.startContainer; // jQuery(el).contents()[0];
1247 					objects[i].startOffset = rangeObject.startOffset;
1248 					textnodes = jQuery(el).textNodes(true);
1249 
1250 					nodeNr = textnodes.length - 1;
1251 					objects[i].endContainer = textnodes[nodeNr];
1252 					objects[i].endOffset = textnodes[nodeNr].length;
1253 					objects[i].update();
1254 					this.applyMarkup(objects[i].getSelectionTree(), rangeObject, this.getClonedMarkup4Wrapping(el), tagComparator, {
1255 						setRangeObject2NewMarkup: true
1256 					});
1257 				}
1258 				if (!extendMarkupsAtEnd && extendMarkupsAtStart) {
1259 					textnodes = jQuery(el).textNodes(true);
1260 					objects[i].startContainer = textnodes[0]; // jQuery(el).contents()[0];
1261 					objects[i].startOffset = 0;
1262 					objects[i].endContainer = rangeObject.endContainer;
1263 					objects[i].endOffset = rangeObject.endOffset;
1264 					objects[i].update();
1265 					this.applyMarkup(objects[i].getSelectionTree(), rangeObject, this.getClonedMarkup4Wrapping(el), tagComparator, {
1266 						setRangeObject2NewMarkup: true
1267 					});
1268 				}
1269 			}
1270 			return true;
1271 		},
1272 
1273 		/**
1274 		 * method creates an empty markup jQuery object from a dom object passed as paramter
1275 		 * @param domobj domobject to be cloned, cleaned and emptied
1276 		 * @param tagComparator method, which is used to compare the dom object and the jQuery markup object. the method must accept 2 parameters, the first is the domobj, the second is the jquery object. if no method is specified, the method this.standardTextLevelSemanticsComparator is used
1277 		 * @return jQuery wrapper object to be passed to e.g. this.applyMarkup(...)
1278 		 * @hide
1279 		 */
1280 		getClonedMarkup4Wrapping: function (domobj) {
1281 			var wrapper = jQuery(domobj.outerHTML).removeClass('preparedForRemoval').empty();
1282 			if (wrapper.attr('class').length === 0) {
1283 				wrapper.removeAttr('class');
1284 			}
1285 			return wrapper;
1286 		},
1287 
1288 		/**
1289 		 * method used to subtract the range object from existing markup. in other words: certain markup is removed from the selections defined by the rangeObject
1290 		 * @param relevantMarkupObjects JS Array of dom objects effecting either the start or endContainer of a selection (which should be extended)
1291 		 * @param rangeObject Aloha rangeObject the markups should be removed from
1292 		 * @param startOrEnd boolean; defines, if the existing markups should be reduced at the beginning of the tag or at the end (is propably redundant and could be found out by comparing start or end container with the markup array dom objects)
1293 		 * @param tagComparator method, which is used to compare the dom object and the jQuery markup object. the method must accept 2 parameters, the first is the domobj, the second is the jquery object. if no method is specified, the method this.standardTextLevelSemanticsComparator is used
1294 		 * @return true
1295 		 * @hide
1296 		 */
1297 		insertCroppedMarkups: function (relevantMarkupObjects, rangeObject, startOrEnd, tagComparator) {
1298 			var cropMarkupsAtEnd, cropMarkupsAtStart, textnodes, objects, i, el, textNodes;
1299 			if (!startOrEnd) { // = Start
1300 				// start part of rangeObject should be used, therefor existing markups are cropped at the end
1301 				cropMarkupsAtEnd = true;
1302 			} else { // = End
1303 				// end part of rangeObject should be used, therefor existing markups are cropped at start (beginning)
1304 				cropMarkupsAtStart = true;
1305 			}
1306 			objects = [];
1307 			for (i = 0; i < relevantMarkupObjects.length; i++) {
1308 				objects[i] = new this.SelectionRange();
1309 				el = relevantMarkupObjects[i];
1310 				if (cropMarkupsAtEnd && !cropMarkupsAtStart) {
1311 					textNodes = jQuery(el).textNodes(true);
1312 					objects[i].startContainer = textNodes[0];
1313 					objects[i].startOffset = 0;
1314 					// if the existing markup startContainer & startOffset are equal to the rangeObject startContainer and startOffset,
1315 					// then markupobject does not have to be added again, because it would have no content (zero-length)
1316 					if (objects[i].startContainer === rangeObject.startContainer && objects[i].startOffset === rangeObject.startOffset) {
1317 						continue;
1318 					}
1319 					if (rangeObject.startOffset === 0) {
1320 						objects[i].endContainer = this.getTextNodeSibling(false, el, rangeObject.startContainer);
1321 						objects[i].endOffset = objects[i].endContainer.length;
1322 					} else {
1323 						objects[i].endContainer = rangeObject.startContainer;
1324 						objects[i].endOffset = rangeObject.startOffset;
1325 					}
1326 
1327 					objects[i].update();
1328 
1329 					this.applyMarkup(objects[i].getSelectionTree(), rangeObject, this.getClonedMarkup4Wrapping(el), tagComparator, {
1330 						setRangeObject2NextSibling: true
1331 					});
1332 				}
1333 
1334 				if (!cropMarkupsAtEnd && cropMarkupsAtStart) {
1335 					objects[i].startContainer = rangeObject.endContainer; // jQuery(el).contents()[0];
1336 					objects[i].startOffset = rangeObject.endOffset;
1337 					textnodes = jQuery(el).textNodes(true);
1338 					objects[i].endContainer = textnodes[textnodes.length - 1];
1339 					objects[i].endOffset = textnodes[textnodes.length - 1].length;
1340 					objects[i].update();
1341 					this.applyMarkup(objects[i].getSelectionTree(), rangeObject, this.getClonedMarkup4Wrapping(el), tagComparator, {
1342 						setRangeObject2PreviousSibling: true
1343 					});
1344 				}
1345 			}
1346 			return true;
1347 		},
1348 
1349 		/**
1350 		 * apply a certain markup to the current selection
1351 		 * @param markupObject jQuery object of the markup to be applied (e.g. created with obj = jQuery('<b></b>'); )
1352 		 * @return void
1353 		 * @hide
1354 		 */
1355 		changeMarkupOnSelection: function (markupObject) {
1356 			var rangeObject = this.getRangeObject();
1357 
1358 			// change the markup
1359 			this.changeMarkup(rangeObject, markupObject, this.getStandardTagComparator(markupObject));
1360 
1361 			// merge text nodes
1362 			GENTICS.Utils.Dom.doCleanup({
1363 				'merge': true
1364 			}, rangeObject);
1365 
1366 			// update the range and select it
1367 			rangeObject.update();
1368 			rangeObject.select();
1369 			this.rangeObject = rangeObject;
1370 		},
1371 
1372 		/**
1373 		 * apply a certain markup to the selection Tree
1374 		 * @param selectionTree SelectionTree Object markup should be applied to
1375 		 * @param rangeObject Aloha rangeObject which will be modified to reflect the dom changes, after the markup was applied (only if activated via options)
1376 		 * @param markupObject jQuery object of the markup to be applied (e.g. created with obj = jQuery('<b></b>'); )
1377 		 * @param tagComparator method, which is used to compare the dom object and the jQuery markup object. the method must accept 2 parameters, the first is the domobj, the second is the jquery object. if no method is specified, the method this.standardTextLevelSemanticsComparator is used
1378 		 * @param options JS object, with the following boolean properties: setRangeObject2NewMarkup, setRangeObject2NextSibling, setRangeObject2PreviousSibling
1379 		 * @return void
1380 		 * @hide
1381 		 */
1382 		applyMarkup: function (selectionTree, rangeObject, markupObject, tagComparator, options) {
1383 			var optimizedSelectionTree, i, el, breakpoint;
1384 			options = options || {};
1385 			// first same tags from within fully selected nodes for removal
1386 			this.prepareForRemoval(selectionTree, markupObject, tagComparator);
1387 
1388 			// first let's optimize the selection Tree in useful groups which can be wrapped together
1389 			optimizedSelectionTree = this.optimizeSelectionTree4Markup(selectionTree, markupObject, tagComparator);
1390 			breakpoint = true;
1391 
1392 			// now iterate over grouped elements and either recursively dive into object or wrap it as a whole
1393 			for (i = 0; i < optimizedSelectionTree.length; i++) {
1394 				el = optimizedSelectionTree[i];
1395 				if (el.wrappable) {
1396 					this.wrapMarkupAroundSelectionTree(el.elements, rangeObject, markupObject, tagComparator, options);
1397 				} else {
1398 					Aloha.Log.debug(this, 'dive further into non-wrappable object');
1399 					this.applyMarkup(el.element.children, rangeObject, markupObject, tagComparator, options);
1400 				}
1401 			}
1402 		},
1403 
1404 		/**
1405 		 * returns the type of the given markup (trying to match HTML5)
1406 		 * @param markupObject jQuery object of the markup to be applied (e.g. created with obj = jQuery('<b></b>'); )
1407 		 * @return string name of the markup type
1408 		 * @hide
1409 		 */
1410 		getMarkupType: function (markupObject) {
1411 			var nn = jQuery(markupObject)[0].nodeName.toLowerCase();
1412 			if (markupObject.outerHtml) {
1413 				Aloha.Log.debug(this, 'Node name detected: ' + nn + ' for: ' + markupObject.outerHtml());
1414 			}
1415 			if (nn == '#text') {
1416 				return 'textNode';
1417 			}
1418 			if (this.replacingElements[nn]) {
1419 				return 'sectionOrGroupingContent';
1420 			}
1421 			if (this.tagHierarchy[nn]) {
1422 				return 'textLevelSemantics';
1423 			}
1424 			Aloha.Log.warn(this, 'unknown markup passed to this.getMarkupType(...): ' + markupObject.outerHtml());
1425 		},
1426 
1427 		/**
1428 		 * returns the standard tag comparator for the given markup object
1429 		 * @param markupObject jQuery object of the markup to be applied (e.g. created with obj = jQuery('<b></b>'); )
1430 1431 		 * @return function tagComparator method, which is used to compare the dom object and the jQuery markup object. the method must accept 2 parameters, the first is the domobj, the second is the jquery object. if no method is specified, the method this.standardTextLevelSemanticsComparator is used
1432 		 * @hide
1433 		 */
1434 		getStandardTagComparator: function (markupObject) {
1435 			var that = this,
1436 				result;
1437 			switch (this.getMarkupType(markupObject)) {
1438 			case 'textNode':
1439 				result = function (p1, p2) {
1440 					return false;
1441 				};
1442 				break;
1443 
1444 			case 'sectionOrGroupingContent':
1445 				result = function (domobj, markupObject) {
1446 					return that.standardSectionsAndGroupingContentComparator(domobj, markupObject);
1447 				};
1448 				break;
1449 
1450 			//case 'textLevelSemantics' covered by default
1451 			default:
1452 				result = function (domobj, markupObject) {
1453 					return that.standardTextLevelSemanticsComparator(domobj, markupObject);
1454 				};
1455 				break;
1456 			}
1457 			return result;
1458 		},
1459 
1460 		/**
1461 		 * searches for fully selected equal markup tags
1462 		 * @param selectionTree SelectionTree Object markup should be applied to
1463 		 * @param markupObject jQuery object of the markup to be applied (e.g. created with obj = jQuery('<b></b>'); )
1464 		 * @param tagComparator method, which is used to compare the dom object and the jQuery markup object. the method must accept 2 parameters, the first is the domobj, the second is the jquery object. if no method is specified, the method this.standardTextLevelSemanticsComparator is used
1465 		 * @return void
1466 		 * @hide
1467 		 */
1468 		prepareForRemoval: function (selectionTree, markupObject, tagComparator) {
1469 			var that = this, i, el;
1470 
1471 			// check if a comparison method was passed as parameter ...
1472 			if (typeof tagComparator !== 'undefined' && typeof tagComparator !== 'function') {
1473 				Aloha.Log.error(this, 'parameter tagComparator is not a function');
1474 			}
1475 			// ... if not use this as standard tag comparison method
1476 			if (typeof tagComparator === 'undefined') {
1477 				tagComparator = this.getStandardTagComparator(markupObject);
1478 			}
1479 			for (i = 0; i < selectionTree.length; i++) {
1480 				el = selectionTree[i];
1481 				if (el.domobj && (el.selection == 'full' || (el.selection == 'partial' && markupObject.isReplacingElement))) {
1482 					// mark for removal
1483 					if (el.domobj.nodeType === 1 && tagComparator(el.domobj, markupObject)) {
1484 						Aloha.Log.debug(this, 'Marking for removal: ' + el.domobj.nodeName);
1485 						jQuery(el.domobj).addClass('preparedForRemoval');
1486 					}
1487 				}
1488 				if (el.selection != 'none' && el.children.length > 0) {
1489 					this.prepareForRemoval(el.children, markupObject, tagComparator);
1490 				}
1491 
1492 			}
1493 		},
1494 
1495 		/**
1496 		 * searches for fully selected equal markup tags
1497 		 * @param selectionTree SelectionTree Object markup should be applied to
1498 		 * @param rangeObject Aloha rangeObject the markup will be applied to
1499 		 * @param markupObject jQuery object of the markup to be applied (e.g. created with obj = jQuery('<b></b>'); )
1500 		 * @param tagComparator method, which is used to compare the dom object and the jQuery markup object. the method must accept 2 parameters, the first is the domobj, the second is the jquery object. if no method is specified, the method this.standardTextLevelSemanticsComparator is used
1501 		 * @param options JS object, with the following boolean properties: setRangeObject2NewMarkup, setRangeObject2NextSibling, setRangeObject2PreviousSibling
1502 		 * @return void
1503 		 * @hide
1504 		 */
1505 		wrapMarkupAroundSelectionTree: function (selectionTree, rangeObject, markupObject, tagComparator, options) {
1506 			// first let's find out if theoretically the whole selection can be wrapped with one tag and save it for later use
1507 			var objects2wrap = [], // // this will be used later to collect objects
1508 				j = -1, // internal counter,
1509 				breakpoint = true,
1510 				preText = '',
1511 				postText = '',
1512 				prevOrNext,
1513 				textNode2Start,
1514 				textnodes,
1515 				newMarkup,
1516 				i,
1517 			    el,
1518 			    middleText;
1519 
1520 			Aloha.Log.debug(this, 'The formatting <' + markupObject[0].tagName + '> will be wrapped around the selection');
1521 
1522 			// now lets iterate over the elements
1523 			for (i = 0; i < selectionTree.length; i++) {
1524 				el = selectionTree[i];
1525 
1526 				// check if markup is allowed inside the elements parent
1527 				if (el.domobj && !this.canTag1WrapTag2(el.domobj.parentNode.tagName.toLowerCase(), markupObject[0].tagName.toLowerCase())) {
1528 					Aloha.Log.info(this, 'Skipping the wrapping of <' + markupObject[0].tagName.toLowerCase() + '> because this tag is not allowed inside <' + el.domobj.parentNode.tagName.toLowerCase() + '>');
1529 					continue;
1530 				}
1531 
1532 				// skip empty text nodes
1533 				if (el.domobj && el.domobj.nodeType === 3 && jQuery.trim(el.domobj.nodeValue).length === 0) {
1534 					continue;
1535 				}
1536 
1537 				// partial element, can either be a textnode and therefore be wrapped (at least partially)
1538 				// or can be a nodeType == 1 (tag) which must be dived into
1539 				if (el.domobj && el.selection == 'partial' && !markupObject.isReplacingElement) {
1540 					if (el.startOffset !== undefined && el.endOffset === undefined) {
1541 						j++;
1542 						preText += el.domobj.data.substr(0, el.startOffset);
1543 						el.domobj.data = el.domobj.data.substr(el.startOffset, el.domobj.data.length - el.startOffset);
1544 						objects2wrap[j] = el.domobj;
1545 					} else if (el.endOffset !== undefined && el.startOffset === undefined) {
1546 						j++;
1547 						postText += el.domobj.data.substr(el.endOffset, el.domobj.data.length - el.endOffset);
1548 						el.domobj.data = el.domobj.data.substr(0, el.endOffset);
1549 						objects2wrap[j] = el.domobj;
1550 					} else if (el.endOffset !== undefined && el.startOffset !== undefined) {
1551 						if (el.startOffset == el.endOffset) { // do not wrap empty selections
1552 							Aloha.Log.debug(this, 'skipping empty selection');
1553 							continue;
1554 						}
1555 						j++;
1556 						preText += el.domobj.data.substr(0, el.startOffset);
1557 						middleText = el.domobj.data.substr(el.startOffset, el.endOffset - el.startOffset);
1558 						postText += el.domobj.data.substr(el.endOffset, el.domobj.data.length - el.endOffset);
1559 						el.domobj.data = middleText;
1560 						objects2wrap[j] = el.domobj;
1561 					} else {
1562 						// a partially selected item without selectionStart/EndOffset is a nodeType 1 Element on the way to the textnode
1563 						Aloha.Log.debug(this, 'diving into object');
1564 						this.applyMarkup(el.children, rangeObject, markupObject, tagComparator, options);
1565 					}
1566 				}
1567 				// fully selected dom elements can be wrapped as whole element
1568 				if (el.domobj && (el.selection == 'full' || (el.selection == 'partial' && markupObject.isReplacingElement))) {
1569 					j++;
1570 					objects2wrap[j] = el.domobj;
1571 				}
1572 			}
1573 
1574 			if (objects2wrap.length > 0) {
1575 				// wrap collected DOM object with markupObject
1576 				objects2wrap = jQuery(objects2wrap);
1577 
1578 				// make a fix for text nodes in <li>'s in ie
1579 				jQuery.each(objects2wrap, function (index, element) {
1580 					if (jQuery.browser.msie && element.nodeType == 3 && !element.nextSibling && !element.previousSibling && element.parentNode && element.parentNode.nodeName.toLowerCase() == 'li') {
1581 						element.data = jQuery.trim(element.data);
1582 					}
1583 				});
1584 
1585 				newMarkup = objects2wrap.wrapAll(markupObject).parent();
1586 				newMarkup.before(preText).after(postText);
1587 
1588 				if (options.setRangeObject2NewMarkup) { // this is used, when markup is added to normal/normal Text
1589 					textnodes = objects2wrap.textNodes();
1590 
1591 					if (textnodes.index(rangeObject.startContainer) != -1) {
1592 						rangeObject.startOffset = 0;
1593 					}
1594 					if (textnodes.index(rangeObject.endContainer) != -1) {
1595 						rangeObject.endOffset = rangeObject.endContainer.length;
1596 					}
1597 					breakpoint = true;
1598 				}
1599 				if (options.setRangeObject2NextSibling) {
1600 					prevOrNext = true;
1601 					textNode2Start = newMarkup.textNodes(true).last()[0];
1602 					if (objects2wrap.index(rangeObject.startContainer) != -1) {
1603 						rangeObject.startContainer = this.getTextNodeSibling(prevOrNext, newMarkup.parent(), textNode2Start);
1604 						rangeObject.startOffset = 0;
1605 					}
1606 					if (objects2wrap.index(rangeObject.endContainer) != -1) {
1607 						rangeObject.endContainer = this.getTextNodeSibling(prevOrNext, newMarkup.parent(), textNode2Start);
1608 						rangeObject.endOffset = rangeObject.endOffset - textNode2Start.length;
1609 					}
1610 				}
1611 				if (options.setRangeObject2PreviousSibling) {
1612 					prevOrNext = false;
1613 					textNode2Start = newMarkup.textNodes(true).first()[0];
1614 					if (objects2wrap.index(rangeObject.startContainer) != -1) {
1615 						rangeObject.startContainer = this.getTextNodeSibling(prevOrNext, newMarkup.parent(), textNode2Start);
1616 						rangeObject.startOffset = 0;
1617 					}
1618 					if (objects2wrap.index(rangeObject.endContainer) != -1) {
1619 						rangeObject.endContainer = this.getTextNodeSibling(prevOrNext, newMarkup.parent(), textNode2Start);
1620 						rangeObject.endOffset = rangeObject.endContainer.length;
1621 					}
1622 				}
1623 			}
1624 		},
1625 
1626 		/**
1627 		 * takes a text node and return either the next recursive text node sibling or the previous
1628 		 * @param previousOrNext boolean, false for previous, true for next sibling
1629 		 * @param commonAncestorContainer dom object to be used as root for the sibling search
1630 		 * @param currentTextNode dom object of the originating text node
1631 		 * @return dom object of the sibling text node
1632 		 * @hide
1633 		 */
1634 		getTextNodeSibling: function (previousOrNext, commonAncestorContainer, currentTextNode) {
1635 			var textNodes = jQuery(commonAncestorContainer).textNodes(true), newIndex, index;
1636 
1637 			index = textNodes.index(currentTextNode);
1638 			if (index == -1) { // currentTextNode was not found
1639 				return false;
1640 			}
1641 			newIndex = index + (!previousOrNext ? -1 : 1);
1642 			return textNodes[newIndex] || false;
1643 		},
1644 
1645 		/**
1646 		 * takes a selection tree and groups it into markup wrappable selection trees
1647 		 * @param selectionTree rangeObject selection tree
1648 		 * @param markupObject jQuery object of the markup to be applied (e.g. created with obj = jQuery('<b></b>'); )
1649 		 * @return JS array of wrappable selection trees
1650 		 * @hide
1651 		 */
1652 		optimizeSelectionTree4Markup: function (selectionTree, markupObject, tagComparator) {
1653 			var groupMap = [],
1654 				outerGroupIndex = 0,
1655 				innerGroupIndex = 0,
1656 				that = this,
1657 				i,
1658 			    j,
1659 				endPosition,
1660 			    startPosition;
1661 
1662 			if (typeof tagComparator === 'undefined') {
1663 				tagComparator = function (domobj, markupObject) {
1664 					return that.standardTextLevelSemanticsComparator(markupObject);
1665 				};
1666 			}
1667 			for (i = 0; i < selectionTree.length; i++) {
1668 				// we are just interested in selected item, but not in non-selected items
1669 				if (selectionTree[i].domobj && selectionTree[i].selection != 'none') {
1670 					if (markupObject.isReplacingElement && tagComparator(markupObject[0], jQuery(selectionTree[i].domobj))) {
1671 						if (groupMap[outerGroupIndex] !== undefined) {
1672 							outerGroupIndex++;
1673 						}
1674 						groupMap[outerGroupIndex] = {};
1675 						groupMap[outerGroupIndex].wrappable = true;
1676 						groupMap[outerGroupIndex].elements = [];
1677 						groupMap[outerGroupIndex].elements[innerGroupIndex] = selectionTree[i];
1678 						outerGroupIndex++;
1679 
1680 					} else if (this.canMarkupBeApplied2ElementAsWhole([selectionTree[i]], markupObject)) {
1681 						// now check, if the children of our item could be wrapped all together by the markup object
1682 						// if yes, add it to the current group
1683 						if (groupMap[outerGroupIndex] === undefined) {
1684 							groupMap[outerGroupIndex] = {};
1685 							groupMap[outerGroupIndex].wrappable = true;
1686 							groupMap[outerGroupIndex].elements = [];
1687 						}
1688 						if (markupObject.isReplacingElement) { //  && selectionTree[i].domobj.nodeType === 3
1689 							/* we found the node to wrap for a replacing element. however there might
1690 							 * be siblings which should be included as well
1691 							 * although they are actually not selected. example:
1692 							 * li
1693 							 * |-textNode ( .selection = 'none')
1694 							 * |-textNode (cursor inside, therefor .selection = 'partial')
1695 							 * |-textNode ( .selection = 'none')
1696 							 *
1697 							 * in this case it would be useful to select the previous and following textNodes as well (they might result from a previous DOM manipulation)
1698 							 * Think about other cases, where the parent is the Editable. In this case we propably only want to select from and until the next <br /> ??
1699 							 * .... many possibilities, here I realize the two described cases
1700 							 */
1701 
1702 							// first find start element starting from the current element going backwards until sibling 0
1703 							startPosition = i;
1704 							for (j = i - 1; j >= 0; j--) {
1705 								if (this.canMarkupBeApplied2ElementAsWhole([selectionTree[j]], markupObject) && this.isMarkupAllowedToStealSelectionTreeElement(selectionTree[j], markupObject)) {
1706 									startPosition = j;
1707 								} else {
1708 									break;
1709 								}
1710 							}
1711 
1712 							// now find the end element starting from the current element going forward until the last sibling
1713 							endPosition = i;
1714 							for (j = i + 1; j < selectionTree.length; j++) {
1715 								if (this.canMarkupBeApplied2ElementAsWhole([selectionTree[j]], markupObject) && this.isMarkupAllowedToStealSelectionTreeElement(selectionTree[j], markupObject)) {
1716 									endPosition = j;
1717 								} else {
1718 									break;
1719 								}
1720 							}
1721 
1722 							// now add the elements to the groupMap
1723 							innerGroupIndex = 0;
1724 							for (j = startPosition; j <= endPosition; j++) {
1725 								groupMap[outerGroupIndex].elements[innerGroupIndex] = selectionTree[j];
1726 								groupMap[outerGroupIndex].elements[innerGroupIndex].selection = 'full';
1727 								innerGroupIndex++;
1728 							}
1729 							innerGroupIndex = 0;
1730 						} else {
1731 							// normal text level semantics object, no siblings need to be selected
1732 							groupMap[outerGroupIndex].elements[innerGroupIndex] = selectionTree[i];
1733 							innerGroupIndex++;
1734 						}
1735 					} else {
1736 						// if no, isolate it in its own group
1737 						if (groupMap[outerGroupIndex] !== undefined) {
1738 							outerGroupIndex++;
1739 						}
1740 						groupMap[outerGroupIndex] = {};
1741 						groupMap[outerGroupIndex].wrappable = false;
1742 						groupMap[outerGroupIndex].element = selectionTree[i];
1743 						innerGroupIndex = 0;
1744 						outerGroupIndex++;
1745 					}
1746 				}
1747 			}
1748 			return groupMap;
1749 		},
1750 
1751 		/**
1752 		 * very tricky method, which decides, if a certain markup (normally a replacing markup element like p, h1, blockquote)
1753 		 * is allowed to extend the user selection to other dom objects (represented as selectionTreeElement)
1754 		 * to understand the purpose: if the user selection is collapsed inside e.g. some text, which is currently not
1755 		 * wrapped by the markup to be applied, and therefor the markup does not have an equal markup to replace, then the DOM
1756 		 * manipulator has to decide which objects to wrap. real example:
1757 		 * <div>
1758 		 *	<h1>headline</h1>
1759 		 *	some text blabla bla<br>
1760 		 *	more text HERE THE | CURSOR BLINKING and <b>even more bold text</b>
1761 		 * </div>
1762 		 * when the user now wants to apply e.g. a <p> tag, what will be wrapped? it could be useful if the manipulator would actually
1763 		 * wrap everything inside the div except the <h1>. but for this purpose someone has to decide, if the markup is
1764 		 * allowed to wrap certain dom elements in this case the question would be, if the <p> is allowed to wrap
1765 		 * textNodes, <br> and <b> and <h1>. therefore this tricky method should answer the question for those 3 elements
1766 		 * with true, but for for the <h1> it should return false. and since the method does not know this, there is a configuration
1767 		 * for this
1768 		 *
1769 		 * @param selectionTree rangeObject selection tree element (only one, not an array of)
1770 		 * @param markupObject lowercase string of the tag to be verified (e.g. "b")
1771 		 * @return true if the markup is allowed to wrap the selection tree element, false otherwise
1772 		 * @hide
1773 		 */
1774 		isMarkupAllowedToStealSelectionTreeElement: function (selectionTreeElement, markupObject) {
1775 			if (!selectionTreeElement.domobj) {
1776 				return false;
1777 			}
1778 			var maybeTextNodeName = selectionTreeElement.domobj.nodeName.toLowerCase(),
1779 				nodeName = (maybeTextNodeName == '#text') ? 'textNode' : maybeTextNodeName,
1780 				markupName = markupObject[0].nodeName.toLowerCase(),
1781 				elemMap = this.allowedToStealElements[markupName];
1782 			return elemMap && elemMap[nodeName];
1783 		},
1784 
1785 		/**
1786 		 * checks if a selection can be completey wrapped by a certain html tags (helper method for this.optimizeSelectionTree4Markup
1787 		 * @param selectionTree rangeObject selection tree
1788 		 * @param markupObject lowercase string of the tag to be verified (e.g. "b")
1789 		 * @return true if selection can be applied as whole, false otherwise
1790 		 * @hide
1791 		 */
1792 		canMarkupBeApplied2ElementAsWhole: function (selectionTree, markupObject) {
1793 			var htmlTag, i, el, returnVal;
1794 
1795 			if (markupObject.jquery) {
1796 				htmlTag = markupObject[0].tagName;
1797 			}
1798 			if (markupObject.tagName) {
1799 				htmlTag = markupObject.tagName;
1800 			}
1801 
1802 			returnVal = true;
1803 			for (i = 0; i < selectionTree.length; i++) {
1804 				el = selectionTree[i];
1805 				if (el.domobj && (el.selection != "none" || markupObject.isReplacingElement)) {
1806 					// Aloha.Log.debug(this, 'Checking, if  <' + htmlTag + '> can be applied to ' + el.domobj.nodeName);
1807 					if (!this.canTag1WrapTag2(htmlTag, el.domobj.nodeName)) {
1808 						return false;
1809 					}
1810 					if (el.children.length > 0 && !this.canMarkupBeApplied2ElementAsWhole(el.children, markupObject)) {
1811 						return false;
1812 					}
1813 				}
1814 			}
1815 			return returnVal;
1816 		},
1817 
1818 		/**
1819 		 * checks if a tag 1 (first parameter) can wrap tag 2 (second parameter).
1820 		 * IMPORTANT: the method does not verify, if there have to be other tags in between
1821 		 * Example: this.canTag1WrapTag2("table", "td") will return true, because the method does not take into account, that there has to be a "tr" in between
1822 		 * @param t1 string: tagname of outer tag to verify, e.g. "b"
1823 		 * @param t2 string: tagname of inner tag to verify, e.g. "b"
1824 		 * @return true if tag 1 can wrap tag 2, false otherwise
1825 		 * @hide
1826 		 */
1827 		canTag1WrapTag2: function (t1, t2) {
1828 			t1 = (t1 == '#text') ? 'textNode' : t1.toLowerCase();
1829 			t2 = (t2 == '#text') ? 'textNode' : t2.toLowerCase();
1830 			var t1Map = this.tagHierarchy[t1];
1831 			if (!t1Map) {
1832 				return true;
1833 			}
1834 			if (!this.tagHierarchy[t2]) {
1835 				return true;
1836 			}
1837 			return t1Map[t2];
1838 		},
1839 
1840 		/**
1841 		 * Check whether it is allowed to insert the given tag at the start of the
1842 		 * current selection. This method will check whether the markup effective for
1843 		 * the start and outside of the editable part (starting with the editable tag
1844 		 * itself) may wrap the given tag.
1845 		 * @param tagName {String} name of the tag which shall be inserted
1846 		 * @return true when it is allowed to insert that tag, false if not
1847 		 * @hide
1848 		 */
1849 		mayInsertTag: function (tagName) {
1850 			var i;
1851 			if (typeof this.rangeObject.unmodifiableMarkupAtStart == 'object') {
1852 				// iterate over all DOM elements outside of the editable part
1853 				for (i = 0; i < this.rangeObject.unmodifiableMarkupAtStart.length; ++i) {
1854 					// check whether an element may not wrap the given
1855 					if (!this.canTag1WrapTag2(this.rangeObject.unmodifiableMarkupAtStart[i].nodeName, tagName)) {
1856 						// found a DOM element which forbids to insert the given tag, we are done
1857 						return false;
1858 					}
1859 				}
1860 
1861 				// all of the found DOM elements allow inserting the given tag
1862 				return true;
1863 			}
1864 			Aloha.Log.warn(this, 'Unable to determine whether tag ' + tagName + ' may be inserted');
1865 			return true;
1866 		},
1867 
1868 		/**
1869 		 * String representation
1870 		 * @return "Aloha.Selection"
1871 		 * @hide
1872 		 */
1873 		toString: function () {
1874 			return 'Aloha.Selection';
1875 		},
1876 
1877 		/**
1878 		 * @namespace Aloha.Selection
1879 		 * @class SelectionRange
1880 		 * @extends GENTICS.Utils.RangeObject
1881 		 * Constructor for a range object.
1882 		 * Optionally you can pass in a range object that's properties will be assigned to the new range object.
1883 		 * @param rangeObject A range object thats properties will be assigned to the new range object.
1884 		 * @constructor
1885 		 */
1886 		SelectionRange: GENTICS.Utils.RangeObject.extend({
1887 			_constructor: function (rangeObject) {
1888 				this._super(rangeObject);
1889 				// If a range object was passed in we apply the values to the new range object
1890 				if (rangeObject) {
1891 					if (rangeObject.commonAncestorContainer) {
1892 						this.commonAncestorContainer = rangeObject.commonAncestorContainer;
1893 					}
1894 					if (rangeObject.selectionTree) {
1895 						this.selectionTree = rangeObject.selectionTree;
1896 					}
1897 					if (rangeObject.limitObject) {
1898 						this.limitObject = rangeObject.limitObject;
1899 					}
1900 					if (rangeObject.markupEffectiveAtStart) {
1901 						this.markupEffectiveAtStart = rangeObject.markupEffectiveAtStart;
1902 					}
1903 					if (rangeObject.unmodifiableMarkupAtStart) {
1904 						this.unmodifiableMarkupAtStart = rangeObject.unmodifiableMarkupAtStart;
1905 					}
1906 					if (rangeObject.splitObject) {
1907 						this.splitObject = rangeObject.splitObject;
1908 					}
1909 				}
1910 			},
1911 
1912 			/**
1913 			 * DOM object of the common ancestor from startContainer and endContainer
1914 			 * @hide
1915 			 */
1916 			commonAncestorContainer: undefined,
1917 
1918 			/**
1919 			 * The selection tree
1920 			 * @hide
1921 			 */
1922 			selectionTree: undefined,
1923 
1924 			/**
1925 			 * Array of DOM objects effective for the start container and inside the
1926 			 * editable part (inside the limit object). relevant for the button status
1927 			 * @hide
1928 			 */
1929 			markupEffectiveAtStart: [],
1930 
1931 			/**
1932 			 * Array of DOM objects effective for the start container, which lies
1933 			 * outside of the editable portion (starting with the limit object)
1934 			 * @hide
1935 			 */
1936 			unmodifiableMarkupAtStart: [],
1937 
1938 			/**
1939 			 * DOM object being the limit for all markup relevant activities
1940 			 * @hide
1941 			 */
1942 			limitObject: undefined,
1943 
1944 			/**
1945 			 * DOM object being split when enter key gets hit
1946 			 * @hide
1947 			 */
1948 			splitObject: undefined,
1949 
1950 			/**
1951 			 * Sets the visible selection in the Browser based on the range object.
1952 			 * If the selection is collapsed, this will result in a blinking cursor,
1953 			 * otherwise in a text selection.
1954 			 * @method
1955 			 */
1956 			select: function () {
1957 				// Call Utils' select()
1958 				this._super();
1959 
1960 				// update the selection
1961 				Aloha.Selection.updateSelection();
1962 			},
1963 
1964 			/**
1965 			 * Method to update a range object internally
1966 			 * @param commonAncestorContainer (DOM Object); optional Parameter; if set, the parameter
1967 			 * will be used instead of the automatically calculated CAC
1968 			 * @return void
1969 			 * @hide
1970 			 */
1971 			update: function (commonAncestorContainer) {
1972 				this.updatelimitObject();
1973 				this.updateMarkupEffectiveAtStart();
1974 				this.updateCommonAncestorContainer(commonAncestorContainer);
1975 
1976 				// reset the selectiontree (must be recalculated)
1977 				this.selectionTree = undefined;
1978 			},
1979 
1980 			/**
1981 			 * Get the selection tree for this range
1982 			 * TODO: remove this (was moved to range.js)
1983 			 * @return selection tree
1984 			 * @hide
1985 			 */
1986 			getSelectionTree: function () {
1987 				// if not yet calculated, do this now
1988 				if (!this.selectionTree) {
1989 					this.selectionTree = Aloha.Selection.getSelectionTree(this);
1990 				}
1991 
1992 				return this.selectionTree;
1993 			},
1994 
1995 			/**
1996 			 * TODO: move this to range.js
1997 			 * Get an array of domobj (in dom tree order) of siblings of the given domobj, which are contained in the selection
1998 			 * @param domobj dom object to start with
1999 			 * @return array of siblings of the given domobj, which are also selected
2000 			 * @hide
2001 			 */
2002 			getSelectedSiblings: function (domobj) {
2003 				var selectionTree = this.getSelectionTree();
2004 
2005 				return this.recursionGetSelectedSiblings(domobj, selectionTree);
2006 			},
2007 
2008 			/**
2009 			 * TODO: move this to range.js
2010 			 * Recursive method to find the selected siblings of the given domobj (which should be selected as well)
2011 			 * @param domobj dom object for which the selected siblings shall be found
2012 			 * @param selectionTree current level of the selection tree
2013 			 * @return array of selected siblings of dom objects or false if none found
2014 			 * @hide
2015 			 */
2016 			recursionGetSelectedSiblings: function (domobj, selectionTree) {
2017 				var selectedSiblings = false,
2018 					foundObj = false,
2019 					i;
2020 
2021 				for (i = 0; i < selectionTree.length; ++i) {
2022 					if (selectionTree[i].domobj === domobj) {
2023 						foundObj = true;
2024 						selectedSiblings = [];
2025 					} else if (!foundObj && selectionTree[i].children) {
2026 						// do the recursion
2027 						selectedSiblings = this.recursionGetSelectedSiblings(domobj, selectionTree[i].children);
2028 						if (selectedSiblings !== false) {
2029 							break;
2030 						}
2031 					} else if (foundObj && selectionTree[i].domobj && selectionTree[i].selection != 'collapsed' && selectionTree[i].selection != 'none') {
2032 						selectedSiblings.push(selectionTree[i].domobj);
2033 					} else if (foundObj && selectionTree[i].selection == 'none') {
2034 						break;
2035 					}
2036 				}
2037 
2038 				return selectedSiblings;
2039 			},
2040 
2041 			/**
2042 			 * TODO: move this to range.js
2043 			 * Method updates member var markupEffectiveAtStart and splitObject, which is relevant primarily for button status and enter key behaviour
2044 			 * @return void
2045 			 * @hide
2046 			 */
2047 			updateMarkupEffectiveAtStart: function () {
2048 				// reset the current markup
2049 				this.markupEffectiveAtStart = [];
2050 				this.unmodifiableMarkupAtStart = [];
2051 
2052 				var parents = this.getStartContainerParents(),
2053 					limitFound = false,
2054 					splitObjectWasSet,
2055 					i,
2056 				    el;
2057 
2058 				for (i = 0; i < parents.length; i++) {
2059 					el = parents[i];
2060 					if (!limitFound && (el !== this.limitObject)) {
2061 						this.markupEffectiveAtStart[i] = el;
2062 						if (!splitObjectWasSet && GENTICS.Utils.Dom.isSplitObject(el)) {
2063 							splitObjectWasSet = true;
2064 							this.splitObject = el;
2065 						}
2066 					} else {
2067 						limitFound = true;
2068 						this.unmodifiableMarkupAtStart.push(el);
2069 					}
2070 				}
2071 				if (!splitObjectWasSet) {
2072 					this.splitObject = false;
2073 				}
2074 				return;
2075 			},
2076 
2077 			/**
2078 			 * TODO: remove this
2079 			 * Method updates member var markupEffectiveAtStart, which is relevant primarily for button status
2080 			 * @return void
2081 			 * @hide
2082 			 */
2083 			updatelimitObject: function () {
2084 				if (Aloha.editables && Aloha.editables.length > 0) {
2085 					var parents = this.getStartContainerParents(),
2086 						editables = Aloha.editables,
2087 						i,
2088 					    el,
2089 					    j,
2090 					    editable;
2091 					for (i = 0; i < parents.length; i++) {
2092 						el = parents[i];
2093 						for (j = 0; j < editables.length; j++) {
2094 							editable = editables[j].obj[0];
2095 							if (el === editable) {
2096 								this.limitObject = el;
2097 								return true;
2098 							}
2099 						}
2100 					}
2101 				}
2102 				this.limitObject = jQuery('body');
2103 				return true;
2104 			},
2105 
2106 			/**
2107 			 * string representation of the range object
2108 			 * @param	verbose	set to true for verbose output
2109 			 * @return string representation of the range object
2110 			 * @hide
2111 			 */
2112 			toString: function (verbose) {
2113 				if (!verbose) {
2114 					return 'Aloha.Selection.SelectionRange';
2115 				}
2116 				return 'Aloha.Selection.SelectionRange {start [' + this.startContainer.nodeValue + '] offset ' + this.startOffset + ', end [' + this.endContainer.nodeValue + '] offset ' + this.endOffset + '}';
2117 			}
2118 
2119 		}) // SelectionRange
2120 
2121 	}); // Selection
2122 
2123 
2124 	/**
2125 	 * This method implements an ugly workaround for a selection problem in ie:
2126 	 * when the cursor shall be placed at the end of a text node in a li element, that is followed by a nested list,
2127 	 * the selection would always snap into the first li of the nested list
2128 	 * therefore, we make sure that the text node ends with a space and place the cursor right before it
2129 	 */
2130 	function nestedListInIEWorkaround(range) {
2131 		var nextSibling;
2132 		if (jQuery.browser.msie && range.startContainer === range.endContainer && range.startOffset === range.endOffset && range.startContainer.nodeType == 3 && range.startOffset == range.startContainer.data.length && range.startContainer.nextSibling) {
2133 			nextSibling = range.startContainer.nextSibling;
2134 			if ('OL' === nextSibling.nodeName || 'UL' === nextSibling.nodeName) {
2135 				if (range.startContainer.data[range.startContainer.data.length - 1] == ' ') {
2136 					range.startOffset = range.endOffset = range.startOffset - 1;
2137 				} else {
2138 					range.startContainer.data = range.startContainer.data + ' ';
2139 				}
2140 			}
2141 		}
2142 	}
2143 
2144 	function correctRange(range) {
2145 		nestedListInIEWorkaround(range);
2146 		return range;
2147 	}
2148 
2149 	/**
2150 	 * Implements Selection http://html5.org/specs/dom-range.html#selection
2151 	 * @namespace Aloha
2152 	 * @class Selection This singleton class always represents the
2153 	 *        current user selection
2154 	 * @singleton
2155 	 */
2156 	var AlohaSelection = Class.extend({
2157 
2158 		_constructor: function (nativeSelection) {
2159 
2160 			this._nativeSelection = nativeSelection;
2161 			this.ranges = [];
2162 
2163 			// will remember if urged to not change the selection
2164 			this.preventChange = false;
2165 
2166 		},
2167 
2168 2169 		/**
2170 		 * Returns the element that contains the start of the selection. Returns null if there's no selection.
2171 		 * @readonly
2172 		 * @type Node
2173 		 */
2174 		anchorNode: null,
2175 
2176 		/**
2177 		 * Returns the offset of the start of the selection relative to the element that contains the start 
2178 		 * of the selection. Returns 0 if there's no selection.
2179 		 * @readonly
2180 		 * @type int
2181 		 */
2182 		anchorOffset: 0,
2183 
2184 		/**
2185 		 * Returns the element that contains the end of the selection.
2186 		 * Returns null if there's no selection.
2187 		 * @readonly
2188 		 * @type Node
2189 		 */
2190 		focusNode: null,
2191 
2192 		/**
2193 		 * Returns the offset of the end of the selection relative to the element that contains the end 
2194 		 * of the selection. Returns 0 if there's no selection.
2195 		 * @readonly
2196 		 * @type int
2197 		 */
2198 		focusOffset: 0,
2199 
2200 		/**
2201 		 * Returns true if there's no selection or if the selection is empty. Otherwise, returns false.
2202 		 * @readonly
2203 		 * @type boolean
2204 		 */
2205 		isCollapsed: false,
2206 
2207 		/**
2208 		 * Returns the number of ranges in the selection.
2209 		 * @readonly
2210 		 * @type int
2211 		 */
2212 		rangeCount: 0,
2213 
2214 		/**
2215 		 * Replaces the selection with an empty one at the given position.
2216 		 * @throws a WRONG_DOCUMENT_ERR exception if the given node is in a different document.
2217 		 * @param parentNode Node of new selection
2218 		 * @param offest offest of new Selection in parentNode
2219 		 * @void
2220 		 */
2221 		collapse: function (parentNode, offset) {
2222 			this._nativeSelection.collapse(parentNode, offset);
2223 		},
2224 2225 
		/**
2226 		 * Replaces the selection with an empty one at the position of the start of the current selection.
2227 		 * @throws an INVALID_STATE_ERR exception if there is no selection.
2228 		 * @void
2229 		 */
2230 		collapseToStart: function () {
2231 			throw "NOT_IMPLEMENTED";
2232 		},
2233 
2234 		/** 
2235 		 * @void
2236 		 */
2237 		extend: function (parentNode, offset) {
2238 
2239 		},
2240 
2241 		/**
2242 		 * @param alter DOMString 
2243 		 * @param direction DOMString 
2244 		 * @param granularity DOMString 
2245 		 * @void
2246 		 */
2247 		modify: function (alter, direction, granularity) {
2248 
2249 		},
2250 
2251 		/**
2252 		 * Replaces the selection with an empty one at the position of the end of the current selection.
2253 		 * @throws an INVALID_STATE_ERR exception if there is no selection.
2254 		 * @void
2255 		 */
2256 		collapseToEnd: function () {
2257 			throw "NOT_IMPLEMENTED";
2258 		},
2259 
2260 		/**
2261 		 * Replaces the selection with one that contains all the contents of the given element.
2262 		 * @throws a WRONG_DOCUMENT_ERR exception if the given node is in a different document.
2263 		 * @param parentNode Node the Node fully select
2264 		 * @void
2265 		 */
2266 		selectAllChildren: function (parentNode) {
2267 			throw "NOT_IMPLEMENTED";
2268 		},
2269 
2270 		/**
2271 		 * Deletes the contents of the selection
2272 		 */
2273 		deleteFromDocument: function () {
2274 			throw "NOT_IMPLEMENTED";
2275 		},
2276 
2277 		/**
2278 		 * NB!
2279 		 * We have serious problem in IE.
2280 		 * The range that we get in IE is not the same as the range we had set,
2281 		 * so even if we normalize it during getRangeAt, in IE, we will be
2282 		 * correcting the range to the "correct" place, but still not the place
2283 		 * where it was originally set.
2284 		 * 
2285 		 * Returns the given range.
2286 		 * The getRangeAt(index) method returns the indexth range in the list. 
2287 		 * NOTE: Aloha Editor only support 1 range! index can only be 0
2288 		 * @throws INDEX_SIZE_ERR DOM exception if index is less than zero or 
2289 		 * greater or equal to the value returned by the rangeCount.
2290 		 * @param index int 
2291 		 * @return Range return the selected range from index
2292 		 */
2293 		getRangeAt: function (index) {
2294 			return correctRange(this._nativeSelection.getRangeAt(index));
2295 			//if ( index < 0 || this.rangeCount ) {
2296 			//	throw "INDEX_SIZE_ERR DOM";
2297 			//}
2298 			//return this._ranges[index];
2299 		},
2300 
2301 		/**
2302 		 * Adds the given range to the selection.
2303 		 * The addRange(range) method adds the given range Range object to the list of
2304 		 * selections, at the end (so the newly added range is the new last range). 
2305 		 * NOTE: Aloha Editor only support 1 range! The added range will replace the 
2306 		 * range at index 0
2307 		 * see http://html5.org/specs/dom-range.html#selection note about addRange
2308 		 * @throws an INVALID_NODE_TYPE_ERR exception if the given Range has a boundary point
2309 		 * node that's not a Text or Element node, and an INVALID_MODIFICATION_ERR exception 
2310 		 * if it has a boundary point node that doesn't descend from a Document.
2311 		 * @param range Range adds the range to the selection
2312 		 * @void
2313 		 */
2314 		addRange: function (range) {
2315 			// set readonly attributes
2316 			this._nativeSelection.addRange(range);
2317 			// We will correct the range after rangy has processed the native
2318 			// selection range, so that our correction will be the final fix on
2319 			// the range according to the guarentee's that Aloha wants to make
2320 			this._nativeSelection._ranges[0] = correctRange(range);
2321 
2322 			// make sure, the old Aloha selection will be updated (until all implementations use the new AlohaSelection)
2323 			Aloha.Selection.updateSelection();
2324 		},
2325 
2326 		/**
2327 		 * Removes the given range from the selection, if the range was one of the ones in the selection.
2328 		 * NOTE: Aloha Editor only support 1 range! The added range will replace the 
2329 		 * range at with index 0
2330 		 * @param range Range removes the range from the selection
2331 		 * @void
2332 		 */
2333 		removeRange: function (range) {
2334 			this._nativeSelection.removeRange();
2335 		},
2336 
2337 		/**
2338 		 * Removes all the ranges in the selection.
2339 		 * @viod
2340 		 */
2341 		removeAllRanges: function () {
2342 			this._nativeSelection.removeAllRanges();
2343 		},
2344 
2345 		/**
2346 		 * INFO: Method is used for integration with Gentics
2347 		 * Aloha, has no use otherwise Updates the rangeObject
2348 		 * according to the current user selection Method is
2349 		 * always called on selection change
2350 		 * 
2351 		 * @param event
2352 		 *            jQuery browser event object
2353 		 * @return true when rangeObject was modified, false
2354 		 *         otherwise
2355 		 * @hide
2356 		 */
2357 		refresh: function (event) {
2358 
2359 		},
2360 2361 
		/**
2362 		 * String representation
2363 		 * 
2364 		 * @return "Aloha.Selection"
2365 		 * @hide
2366 		 */
2367 		toString: function () {
2368 			return 'Aloha.Selection';
2369 		},
2370 
2371 		getRangeCount: function () {
2372 			return this._nativeSelection.rangeCount;
2373 		}
2374 
2375 	});
2376 
2377 	/**
2378 	 * A wrapper for the function of the same name in the rangy core-depdency.
2379 	 * This function should be preferred as it hides the global rangy object.
2380 	 * For more information look at the following sites:
2381 	 * http://html5.org/specs/dom-range.html
2382 	 * @param window optional - specifices the window to get the selection of
2383 	 */
2384 	Aloha.getSelection = function (target) {
2385 		target = (target !== document || target !== window) ? window : target;
2386 		// Aloha.Selection.refresh()
2387 		// implement Aloha Selection 
2388 		// TODO cache
2389 		return new AlohaSelection(window.rangy.getSelection(target));
2390 	};
2391 
2392 	/**
2393 	 * A wrapper for the function of the same name in the rangy core-depdency.
2394 	 * This function should be preferred as it hides the global rangy object.
2395 	 * Please note: when the range object is not needed anymore,
2396 	 *   invoke the detach method on it. It is currently unknown to me why
2397 	 *   this is required, but that's what it says in the rangy specification.
2398 	 * For more information look at the following sites:
2399 	 * http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html
2400 	 * @param document optional - specifies which document to create the range for
2401 	 */
2402 	Aloha.createRange = function (givenWindow) {
2403 		return window.rangy.createRange(givenWindow);
2404 	};
2405 
2406 	var selection = new Selection();
2407 	Aloha.Selection = selection;
2408 
2409 	return selection;
2410 });
2411