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 			// workaround for FF selection bug, where it is possible to move the selection INTO a hr
538 			if (range && range.startContainer
539 					&& 'HR' === range.startContainer.nodeName
540 					&& range.endContainer
541 					&& 'HR' === range.endContainer.nodeName) {
542 				Aloha.getSelection().removeAllRanges();
543 				return true;
544 			}
545 
546 			// Determine the common ancestor container and update the selection
547 			// tree.
548 			range.update();
549 
550 			// Workaround for nasty IE bug that allows the user to select
551 			// text nodes inside areas with contenteditable "false"
552 			if (range && range.startContainer && range.endContainer) {
553 				var inEditable =
554 						jQuery(range.commonAncestorContainer)
555 							.closest('.aloha-editable').length > 0;
556 
557 				if (inEditable) {
558 					var validStartPosition = !(3 === range.startContainer.nodeType &&
559 							!jQuery(range.startContainer.parentNode).contentEditable());
560 
561 					var validEndPosition = !(3 === range.endContainer.nodeType &&
562 							!jQuery(range.endContainer.parentNode).contentEditable());
563 
564 					if (!validStartPosition || !validEndPosition) {
565 						Aloha.getSelection().removeAllRanges();
566 						return true;
567 					}
568 				}
569 			}
570 
571 			// check if aloha-selection-changed event has been prevented
572 			if (this.isSelectionChangedPrevented()) {
573 				return true;
574 			}
575 
576 			Aloha.trigger('aloha-selection-changed-before', [this.rangeObject, event]);
577 
578 			// throw the event that the selection has changed. Plugins now have the
579 			// chance to react on the currentElements[childCount].children.lengthged selection
580 			Aloha.trigger('aloha-selection-changed', [this.rangeObject, event]);
581 
582 			triggerSelectionContextChanged(this.rangeObject, event);
583 
584 			Aloha.trigger('aloha-selection-changed-after', [this.rangeObject, event]);
585 
586 			return true;
587 		},
588 
589 		/**
590 		 * creates an object with x items containing all relevant dom objects.
591 		 * Structure:
592 		 * +
593 		 * |-domobj: <reference to the DOM Object> (NOT jQuery)
594 		 * |-selection: defines if this node is marked by user [none|partial|full]
595 		 * |-children: recursive structure like this ("x.." because it's then shown last in DOM Browsers...)
596 		 * TODO: remove this (was moved to range.js)
597 		 *
598 		 * @param rangeObject "Aloha clean" range object including a commonAncestorContainer
599 		 * @return obj selection
600 		 * @hide
601 		 */
602 		getSelectionTree: function (rangeObject) {
603 			if (!rangeObject) { // if called without any parameters, the method acts as getter for this.selectionTree
604 				return this.rangeObject.getSelectionTree();
605 			}
606 607 			if (!rangeObject.commonAncestorContainer) {
608 				Aloha.Log.error(this, 'the rangeObject is missing the commonAncestorContainer');
609 				return false;
610 			}
611 
612 			this.inselection = false;
613 
614 			// before getting the selection tree, we do a cleanup
615 			if (GENTICS.Utils.Dom.doCleanup({ 'merge': true }, rangeObject)) {
616 				rangeObject.update();
617 				rangeObject.select();
618 			}
619 
620 			return this.recursiveGetSelectionTree(rangeObject, rangeObject.commonAncestorContainer);
621 		},
622 
623 		/**
624 		 * Recursive inner function for generating the selection tree.
625 		 * TODO: remove this (was moved to range.js)
626 		 * @param rangeObject range object
627 		 * @param currentObject current DOM object for which the selection tree shall be generated
628 		 * @return array of SelectionTree objects for the children of the current DOM object
629 		 * @hide
630 		 */
631 		recursiveGetSelectionTree: function (rangeObject, currentObject) {
632 			// get all direct children of the given object
633 			var jQueryCurrentObject = jQuery(currentObject),
634 				childCount = 0,
635 				that = this,
636 				currentElements = [];
637 
638 			jQueryCurrentObject.contents().each(function (index) {
639 				var selectionType = 'none',
640 					startOffset = false,
641 					endOffset = false,
642 					collapsedFound = false,
643 					i,
644 				    elementsLength,
645 					noneFound = false,
646 					partialFound = false,
647 					fullFound = false;
648 
649 				// check for collapsed selections between nodes
650 				if (rangeObject.isCollapsed() && currentObject === rangeObject.startContainer && rangeObject.startOffset == index) {
651 					// insert an extra selectiontree object for the collapsed selection here
652 					currentElements[childCount] = new Aloha.Selection.SelectionTree();
653 					currentElements[childCount].selection = 'collapsed';
654 					currentElements[childCount].domobj = undefined;
655 					that.inselection = false;
656 					collapsedFound = true;
657 					childCount++;
658 				}
659 
660 				if (!that.inselection && !collapsedFound) {
661 					// the start of the selection was not yet found, so look for it now
662 					// check whether the start of the selection is found here
663 
664 					// Try to read the nodeType property and return if we do not have permission
665 					// ie.: frame document to an external URL
666 					var nodeType;
667 					try {
668 						nodeType = this.nodeType;
669 					} catch (e) {
670 						return;
671 					}
672 
673 					// check is dependent on the node type
674 					switch (nodeType) {
675 					case 3:
676 						// text node
677 						if (this === rangeObject.startContainer) {
678 							// the selection starts here
679 							that.inselection = true;
680 
681 							// when the startoffset is > 0, the selection type is only partial
682 683 							selectionType = rangeObject.startOffset > 0 ? 'partial' : 'full';
684 							startOffset = rangeObject.startOffset;
685 							endOffset = this.length;
686 						}
687 						break;
688 					case 1:
689 						// element node
690 						if (this === rangeObject.startContainer && rangeObject.startOffset === 0) {
691 							// the selection starts here
692 							that.inselection = true;
693 							selectionType = 'full';
694 						}
695 						if (currentObject === rangeObject.startContainer && rangeObject.startOffset === index) {
696 							// the selection starts here
697 							that.inselection = true;
698 							selectionType = 'full';
699 						}
700 						break;
701 					}
702 				}
703 
704 				if (that.inselection && !collapsedFound) {
705 					if (selectionType == 'none') {
706 						selectionType = 'full';
707 					}
708 					// we already found the start of the selection, so look for the end of the selection now
709 					// check whether the end of the selection is found here
710 
711 					switch (this.nodeType) {
712 					case 3:
713 						// text node
714 						if (this === rangeObject.endContainer) {
715 							// the selection ends here
716 							that.inselection = false;
717 
718 							// check for partial selection here
719 							if (rangeObject.endOffset < this.length) {
720 								selectionType = 'partial';
721 							}
722 							if (startOffset === false) {
723 								startOffset = 0;
724 							}
725 							endOffset = rangeObject.endOffset;
726 						}
727 						break;
728 					case 1:
729 						// element node
730 						if (this === rangeObject.endContainer && rangeObject.endOffset === 0) {
731 							that.inselection = false;
732 						}
733 						break;
734 					}
735 					if (currentObject === rangeObject.endContainer && rangeObject.endOffset <= index) {
736 						that.inselection = false;
737 						selectionType = 'none';
738 					}
739 				}
740 
741 				// create the current selection tree entry
742 				currentElements[childCount] = new Aloha.Selection.SelectionTree();
743 				currentElements[childCount].domobj = this;
744 				currentElements[childCount].selection = selectionType;
745 				if (selectionType == 'partial') {
746 					currentElements[childCount].startOffset = startOffset;
747 					currentElements[childCount].endOffset = endOffset;
748 				}
749 
750 				// now do the recursion step into the current object
751 				currentElements[childCount].children = that.recursiveGetSelectionTree(rangeObject, this);
752 				elementsLength = currentElements[childCount].children.length;
753 
754 				// check whether a selection was found within the children
755 				if (elementsLength > 0) {
756 					for (i = 0; i < elementsLength; ++i) {
757 						switch (currentElements[childCount].children[i].selection) {
758 						case 'none':
759 							noneFound = true;
760 							break;
761 						case 'full':
762 							fullFound = true;
763 							break;
764 						case 'partial':
765 							partialFound = true;
766 							break;
767 						}
768 					}
769 
770 					if (partialFound || (fullFound && noneFound)) {
771 						// found at least one 'partial' selection in the children, or both 'full' and 'none', so this element is also 'partial' selected
772 						currentElements[childCount].selection = 'partial';
773 					} else if (fullFound && !partialFound && !noneFound) {
774 						// only found 'full' selected children, so this element is also 'full' selected
775 						currentElements[childCount].selection = 'full';
776 					}
777 				}
778 
779 				childCount++;
780 			});
781 
782 			// extra check for collapsed selections at the end of the current element
783 			if (rangeObject.isCollapsed() && currentObject === rangeObject.startContainer && rangeObject.startOffset == currentObject.childNodes.length) {
784 				currentElements[childCount] = new Aloha.Selection.SelectionTree();
785 				currentElements[childCount].selection = 'collapsed';
786 				currentElements[childCount].domobj = undefined;
787 			}
788 
789 			return currentElements;
790 		},
791 
792 		/**
793 		 * Get the currently selected range
794 		 * @return {Aloha.Selection.SelectionRange} currently selected range
795 		 * @method
796 		 */
797 		getRangeObject: function () {
798 			return this.rangeObject;
799 		},
800 
801 		/**
802 		 * method finds out, if a node is within a certain markup or not
803 		 * @param rangeObj Aloha rangeObject
804 		 * @param startOrEnd boolean; defines, if start or endContainer should be used: false for start, true for end
805 		 * @param markupObject jQuery object of the markup to look for
806 		 * @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
807 		 * @param limitObject dom object which limits the search are within the dom. normally this will be the active Editable
808 		 * @return true, if the markup is effective on the range objects start or end node
809 		 * @hide
810 		 */
811 		isRangeObjectWithinMarkup: function (rangeObject, startOrEnd, markupObject, tagComparator, limitObject) {
812 			var domObj = !startOrEnd ? rangeObject.startContainer : rangeObject.endContainer,
813 				that = this,
814 				parents = jQuery(domObj).parents(),
815 				returnVal = false,
816 				i = -1;
817 
818 			// check if a comparison method was passed as parameter ...
819 			if (typeof tagComparator !== 'undefined' && typeof tagComparator !== 'function') {
820 				Aloha.Log.error(this, 'parameter tagComparator is not a function');
821 			}
822 			// ... if not use this as standard tag comparison method
823 			if (typeof tagComparator === 'undefined') {
824 				tagComparator = function (domobj, markupObject) {
825 					return that.standardTextLevelSemanticsComparator(domobj, markupObject); // TODO should actually be this.getStandardTagComparator(markupObject)
826 				};
827 			}
828 829 
			if (parents.length > 0) {
830 				parents.each(function () {
831 					// the limit object was reached (normally the Editable Element)
832 					if (this === limitObject) {
833 						Aloha.Log.debug(that, 'reached limit dom obj');
834 						return false; // break() of jQuery .each(); THIS IS NOT THE FUNCTION RETURN VALUE
835 					}
836 					if (tagComparator(this, markupObject)) {
837 						if (returnVal === false) {
838 							returnVal = [];
839 						}
840 						Aloha.Log.debug(that, 'reached object equal to markup');
841 						i++;
842 						returnVal[i] = this;
843 						return true; // continue() of jQuery .each(); THIS IS NOT THE FUNCTION RETURN VALUE
844 					}
845 				});
846 			}
847 			return returnVal;
848 		},
849 
850 		/**
851 		 * standard method, to compare a domobj and a jquery object for sections and grouping content (e.g. p, h1, h2, ul, ....).
852 		 * is always used when no other tag comparator is passed as parameter
853 		 * @param domobj domobject to compare with markup
854 		 * @param markupObject jQuery object of the markup to compare with domobj
855 		 * @return true if objects are equal and false if not
856 		 * @hide
857 		 */
858 		standardSectionsAndGroupingContentComparator: function (domobj, markupObject) {
859 			if (domobj.nodeType !== 1) {
860 				Aloha.Log.debug(this, 'only element nodes (nodeType == 1) can be compared');
861 				return false;
862 			}
863 			if (!markupObject[0].nodeName) {
864 				return false;
865 			}
866 			var elemMap = Aloha.Selection.replacingElements[domobj.nodeName.toLowerCase()];
867 			return elemMap && elemMap[markupObject[0].nodeName.toLowerCase()];
868 		},
869 
870 		/**
871 		 * standard method, to compare a domobj and a jquery object for their tagName (aka span elements, e.g. b, i, sup, span, ...).
872 		 * is always used when no other tag comparator is passed as parameter
873 		 * @param domobj domobject to compare with markup
874 		 * @param markupObject jQuery object of the markup to compare with domobj
875 		 * @return true if objects are equal and false if not
876 		 * @hide
877 		 */
878 		standardTagNameComparator: function (domobj, markupObject) {
879 			if (domobj.nodeType === 1) {
880 				if (domobj.nodeName != markupObject[0].nodeName) {
881 					return false;
882 				}
883 				return true;
884 			}
885 			Aloha.Log.debug(this, 'only element nodes (nodeType == 1) can be compared');
886 			return false;
887 		},
888 
889 		/**
890 		 * standard method, to compare a domobj and a jquery object for text level semantics (aka span elements, e.g. b, i, sup, span, ...).
891 		 * is always used when no other tag comparator is passed as parameter
892 		 * @param domobj domobject to compare with markup
893 		 * @param markupObject jQuery object of the markup to compare with domobj
894 		 * @return true if objects are equal and false if not
895 		 * @hide
896 		 */
897 		standardTextLevelSemanticsComparator: function (domobj, markupObject) {
898 			// only element nodes can be compared
899 			if (domobj.nodeType === 1) {
900 				if (domobj.nodeName != markupObject[0].nodeName) {
901 					return false;
902 				}
903 				if (!this.standardAttributesComparator(domobj, markupObject)) {
904 					return false;
905 				}
906 				return true;
907 			}
908 			Aloha.Log.debug(this, 'only element nodes (nodeType == 1) can be compared');
909 			return false;
910 		},
911 
912 
913 		/**
914 		 * standard method, to compare attributes of one dom obj and one markup obj (jQuery)
915 		 * @param domobj domobject to compare with markup
916 		 * @param markupObject jQuery object of the markup to compare with domobj
917 		 * @return true if objects are equal and false if not
918 		 * @hide
919 		 */
920 		standardAttributesComparator: function (domobj, markupObject) {
921 			var classesA = Strings.words((domobj && domobj.className) || '');
922 			var classesB = Strings.words((markupObject.length && markupObject[0].className) || '');
923 			Arrays.sortUnique(classesA);
924 			Arrays.sortUnique(classesB);
925 			return Arrays.equal(classesA, classesB);
926 		},
927 
928 		/**
929 		 * method finds out, if a node is within a certain markup or not
930 		 * @param rangeObj Aloha rangeObject
931 		 * @param markupObject jQuery object of the markup to be applied (e.g. created with obj = jQuery('<b></b>'); )
932 		 * @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
933 		 * @return void; TODO: should return true if the markup applied successfully and false if not
934 		 * @hide
935 		 */
936 		changeMarkup: function (rangeObject, markupObject, tagComparator) {
937 			var tagName = markupObject[0].tagName.toLowerCase(),
938 				newCAC,
939 			    limitObject,
940 				backupRangeObject,
941 				relevantMarkupObjectsAtSelectionStart = this.isRangeObjectWithinMarkup(rangeObject, false, markupObject, tagComparator, limitObject),
942 				relevantMarkupObjectsAtSelectionEnd = this.isRangeObjectWithinMarkup(rangeObject, true, markupObject, tagComparator, limitObject),
943 				nextSibling,
944 			    relevantMarkupObjectAfterSelection,
945 				prevSibling,
946 			    relevantMarkupObjectBeforeSelection,
947 				extendedRangeObject;
948 			var parentElement;
949 
950 			// if the element is a replacing element (like p/h1/h2/h3/h4/h5/h6...), which must not wrap each other
951 			// use a clone of rangeObject
952 			if (this.replacingElements[tagName]) {
953 				// backup rangeObject for later selection;
954 				backupRangeObject = rangeObject;
955 
956 				// create a new range object to not modify the orginal
957 				rangeObject = new this.SelectionRange(rangeObject);
958 
959 				// either select the active Editable as new commonAncestorContainer (CAC) or use the body
960 				if (Aloha.activeEditable) {
961 					newCAC = Aloha.activeEditable.obj.get(0);
962 				} else {
963 					newCAC = jQuery('body');
964 				}
965 				// update rangeObject by setting the newCAC and automatically recalculating the selectionTree
966 				rangeObject.update(newCAC);
967 
968 				// store the information, that the markupObject can be replaced (not must be!!) inside the jQuery markup object
969 				markupObject.isReplacingElement = true;
970 			} else {
971 				// if the element is NOT a replacing element, then something needs to be selected, otherwise it can not be wrapped
972 				// therefor the method can return false, if nothing is selected ( = rangeObject is collapsed)
973 				if (rangeObject.isCollapsed()) {
974 					Aloha.Log.debug(this, 'early returning from applying markup because nothing is currently selected');
975 					return false;
976 				}
977 			}
978 
979 			// is Start/End DOM Obj inside the markup to change
980 			if (Aloha.activeEditable) {
981 982 				limitObject = Aloha.activeEditable.obj[0];
983 			} else {
984 				limitObject = jQuery('body');
985 			}
986 
987 			if (!markupObject.isReplacingElement && rangeObject.startOffset === 0) { // don't care about replacers, because they never extend
988 				if (null != (prevSibling = this.getTextNodeSibling(false, rangeObject.commonAncestorContainer.parentNode, rangeObject.startContainer))) {
989 					relevantMarkupObjectBeforeSelection = this.isRangeObjectWithinMarkup({
990 						startContainer: prevSibling,
991 						startOffset: 0
992 					}, false, markupObject, tagComparator, limitObject);
993 				}
994 			}
995 			if (!markupObject.isReplacingElement && (rangeObject.endOffset === rangeObject.endContainer.length)) { // don't care about replacers, because they never extend
996 				if (null != (nextSibling = this.getTextNodeSibling(true, rangeObject.commonAncestorContainer.parentNode, rangeObject.endContainer))) {
997 					relevantMarkupObjectAfterSelection = this.isRangeObjectWithinMarkup({
998 						startContainer: nextSibling,
999 						startOffset: 0
1000 					}, false, markupObject, tagComparator, limitObject);
1001 				}
1002 			}
1003 
1004 			// decide what to do (expand or reduce markup)
1005 			// Alternative A: from markup to no-markup: markup will be removed in selection;
1006 			// reapplied from original markup start to selection start
1007 			if (!markupObject.isReplacingElement && (relevantMarkupObjectsAtSelectionStart && !relevantMarkupObjectsAtSelectionEnd)) {
1008 				Aloha.Log.info(this, 'markup 2 non-markup');
1009 				this.prepareForRemoval(rangeObject.getSelectionTree(), markupObject, tagComparator);
1010 				jQuery(relevantMarkupObjectsAtSelectionStart).addClass('preparedForRemoval');
1011 				this.insertCroppedMarkups(relevantMarkupObjectsAtSelectionStart, rangeObject, false, tagComparator);
1012 			} else if (!markupObject.isReplacingElement && relevantMarkupObjectsAtSelectionStart && relevantMarkupObjectsAtSelectionEnd) {
1013 				// Alternative B: from markup to markup:
1014 				// remove selected markup (=split existing markup if single, shrink if two different)
1015 				Aloha.Log.info(this, 'markup 2 markup');
1016 				this.prepareForRemoval(rangeObject.getSelectionTree(), markupObject, tagComparator);
1017 				this.splitRelevantMarkupObject(relevantMarkupObjectsAtSelectionStart, relevantMarkupObjectsAtSelectionEnd, rangeObject, tagComparator);
1018 			} else if (!markupObject.isReplacingElement && ((!relevantMarkupObjectsAtSelectionStart && relevantMarkupObjectsAtSelectionEnd) || relevantMarkupObjectAfterSelection || relevantMarkupObjectBeforeSelection)) { //
1019 				// Alternative C: from no-markup to markup OR with next2markup:
1020 				// new markup is wrapped from selection start to end of originalmarkup, original is remove afterwards
1021 				Aloha.Log.info(this, 'non-markup 2 markup OR with next2markup');
1022 				// move end of rangeObject to end of relevant markups
1023 				if (relevantMarkupObjectBeforeSelection && relevantMarkupObjectAfterSelection) {
1024 					extendedRangeObject = new Aloha.Selection.SelectionRange(rangeObject);
1025 					extendedRangeObject.startContainer = jQuery(relevantMarkupObjectBeforeSelection[relevantMarkupObjectBeforeSelection.length - 1]).textNodes()[0];
1026 					extendedRangeObject.startOffset = 0;
1027 					extendedRangeObject.endContainer = jQuery(relevantMarkupObjectAfterSelection[relevantMarkupObjectAfterSelection.length - 1]).textNodes().last()[0];
1028 					extendedRangeObject.endOffset = extendedRangeObject.endContainer.length;
1029 					extendedRangeObject.update();
1030 					this.applyMarkup(extendedRangeObject.getSelectionTree(), rangeObject, markupObject, tagComparator);
1031 					Aloha.Log.info(this, 'double extending previous markup(previous and after selection), actually wrapping it ...');
1032 
1033 				} else if (relevantMarkupObjectBeforeSelection && !relevantMarkupObjectAfterSelection && !relevantMarkupObjectsAtSelectionEnd) {
1034 					this.extendExistingMarkupWithSelection(relevantMarkupObjectBeforeSelection, rangeObject, false, tagComparator);
1035 					Aloha.Log.info(this, 'extending previous markup');
1036 1037 
				} else if (relevantMarkupObjectBeforeSelection && !relevantMarkupObjectAfterSelection && relevantMarkupObjectsAtSelectionEnd) {
1038 					extendedRangeObject = new Aloha.Selection.SelectionRange(rangeObject);
1039 					extendedRangeObject.startContainer = jQuery(relevantMarkupObjectBeforeSelection[relevantMarkupObjectBeforeSelection.length - 1]).textNodes()[0];
1040 					extendedRangeObject.startOffset = 0;
1041 					extendedRangeObject.endContainer = jQuery(relevantMarkupObjectsAtSelectionEnd[relevantMarkupObjectsAtSelectionEnd.length - 1]).textNodes().last()[0];
1042 					extendedRangeObject.endOffset = extendedRangeObject.endContainer.length;
1043 					extendedRangeObject.update();
1044 					this.applyMarkup(extendedRangeObject.getSelectionTree(), rangeObject, markupObject, tagComparator);
1045 					Aloha.Log.info(this, 'double extending previous markup(previous and relevant at the end), actually wrapping it ...');
1046 
1047 				} else if (!relevantMarkupObjectBeforeSelection && relevantMarkupObjectAfterSelection) {
1048 					this.extendExistingMarkupWithSelection(relevantMarkupObjectAfterSelection, rangeObject, true, tagComparator);
1049 					Aloha.Log.info(this, 'extending following markup backwards');
1050 
1051 				} else {
1052 					this.extendExistingMarkupWithSelection(relevantMarkupObjectsAtSelectionEnd, rangeObject, true, tagComparator);
1053 				}
1054 			} else if (markupObject.isReplacingElement || (!relevantMarkupObjectsAtSelectionStart && !relevantMarkupObjectsAtSelectionEnd && !relevantMarkupObjectBeforeSelection && !relevantMarkupObjectAfterSelection)) {
1055 				// Alternative D: no-markup to no-markup: easy
1056 				Aloha.Log.info(this, 'non-markup 2 non-markup');
1057 
1058 				// workaround to keep the caret at the right position if it's an empty element
1059 				// applyMarkup was not working correctly and has a lot of overhead we don't need in that case
1060 				if (isCollapsedAndEmptyOrEndBr(rangeObject)) {
1061 					var newMarkup = markupObject.clone();
1062 
1063 					if (isCollapsedAndEndBr(rangeObject)) {
1064 						newMarkup[0].appendChild(Engine.createEndBreak());
1065 					}
1066 
1067 					// setting the focus is needed for mozilla and IE 7 to have a working rangeObject.select()
1068 					if (Aloha.activeEditable && jQuery.browser.mozilla) {
1069 						Aloha.activeEditable.obj.focus();
1070 					}
1071 
1072 					if (Engine.isEditable(rangeObject.startContainer)) {
1073 						Engine.copyAttributes(rangeObject.startContainer, newMarkup[0]);
1074 						jQuery(rangeObject.startContainer).after(newMarkup[0]).remove();
1075 					} else if (Engine.isEditingHost(rangeObject.startContainer)) {
1076 						jQuery(rangeObject.startContainer).append(newMarkup[0]);
1077 						Engine.ensureContainerEditable(newMarkup[0]);
1078 					}
1079 
1080 					backupRangeObject.startContainer = newMarkup[0];
1081 					backupRangeObject.endContainer = newMarkup[0];
1082 					backupRangeObject.startOffset = 0;
1083 					backupRangeObject.endOffset = 0;
1084 					return;
1085 				}
1086 				this.applyMarkup(rangeObject.getSelectionTree(), rangeObject, markupObject, tagComparator, {
1087 					setRangeObject2NewMarkup: true
1088 				});
1089 				backupRangeObject.startContainer = rangeObject.startContainer;
1090 				backupRangeObject.endContainer = rangeObject.endContainer;
1091 				backupRangeObject.startOffset = rangeObject.startOffset;
1092 				backupRangeObject.endOffset = rangeObject.endOffset;
1093 			}
1094 
1095 			if (markupObject.isReplacingElement) {
1096 				//Check if the startContainer is one of the zapped elements
1097 				if (backupRangeObject && backupRangeObject.startContainer.className && backupRangeObject.startContainer.className.indexOf('preparedForRemoval') > -1) {
1098 					//var parentElement = jQuery(backupRangeObject.startContainer).closest(markupObject[0].tagName).get(0);
1099 					parentElement = jQuery(backupRangeObject.startContainer).parents(markupObject[0].tagName).get(0);
1100 					backupRangeObject.startContainer = parentElement;
1101 					rangeObject.startContainer = parentElement;
1102 				}
1103 				//check if the endContainer is one of the zapped elements
1104 				if (backupRangeObject && backupRangeObject.endContainer.className && backupRangeObject.endContainer.className.indexOf('preparedForRemoval') > -1) {
1105 					//var parentElement = jQuery(backupRangeObject.endContainer).closest(markupObject[0].tagName).get(0);
1106 					parentElement = jQuery(backupRangeObject.endContainer).parents(markupObject[0].tagName).get(0);
1107 					backupRangeObject.endContainer = parentElement;
1108 					rangeObject.endContainer = parentElement;
1109 				}
1110 			}
1111 			// remove all marked items
1112 			jQuery('.preparedForRemoval').zap();
1113 
1114 			// recalculate cac and selectionTree
1115 
1116 			// update selection
1117 			if (markupObject.isReplacingElement) {
1118 				//After the zapping we have to check for wrong offsets
1119 				if (e5s.Node.ELEMENT_NODE === backupRangeObject.startContainer.nodeType && backupRangeObject.startContainer.childNodes && backupRangeObject.startContainer.childNodes.length < backupRangeObject.startOffset) {
1120 					backupRangeObject.startOffset = backupRangeObject.startContainer.childNodes.length;
1121 					rangeObject.startOffset = backupRangeObject.startContainer.childNodes.length;
1122 				}
1123 				if (e5s.Node.ELEMENT_NODE === backupRangeObject.endContainer.nodeType && backupRangeObject.endContainer.childNodes && backupRangeObject.endContainer.childNodes.length < backupRangeObject.endOffset) {
1124 					backupRangeObject.endOffset = backupRangeObject.endContainer.childNodes.length;
1125 					rangeObject.endOffset = backupRangeObject.endContainer.childNodes.length;
1126 				}
1127 				rangeObject.endContainer = backupRangeObject.endContainer;
1128 				rangeObject.endOffset = backupRangeObject.endOffset;
1129 				rangeObject.startContainer = backupRangeObject.startContainer;
1130 				rangeObject.startOffset = backupRangeObject.startOffset;
1131 				backupRangeObject.update();
1132 				backupRangeObject.select();
1133 			} else {
1134 				rangeObject.update();
1135 				rangeObject.select();
1136 			}
1137 		},
1138 
1139 		/**
1140 		 * 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
1141 		 * @param relevantMarkupObjectsAtSelectionStart JS Array of dom objects, which are parents to the rangeObject.startContainer
1142 		 * @param relevantMarkupObjectsAtSelectionEnd JS Array of dom objects, which are parents to the rangeObject.endContainer
1143 		 * @param rangeObj Aloha rangeObject
1144 		 * @return true, if rangeObjects and markup objects are identical, false otherwise
1145 		 * @hide
1146 		 */
1147 		areMarkupObjectsAsLongAsRangeObject: function (relevantMarkupObjectsAtSelectionStart, relevantMarkupObjectsAtSelectionEnd, rangeObject) {
1148 			var i, el, textNode, relMarkupEnd, relMarkupStart;
1149 
1150 			if (rangeObject.startOffset !== 0) {
1151 				return false;
1152 			}
1153 
1154 			for (i = 0, relMarkupStart = relevantMarkupObjectsAtSelectionStart.length; i < relMarkupStart; i++) {
1155 				el = jQuery(relevantMarkupObjectsAtSelectionStart[i]);
1156 				if (el.textNodes().first()[0] !== rangeObject.startContainer) {
1157 					return false;
1158 				}
1159 			}
1160 
1161 			for (i = 0, relMarkupEnd = relevantMarkupObjectsAtSelectionEnd.length; i < relMarkupEnd; i++) {
1162 				el = jQuery(relevantMarkupObjectsAtSelectionEnd[i]);
1163 				textNode = el.textNodes().last()[0];
1164 				if (textNode !== rangeObject.endContainer || textNode.length != rangeObject.endOffset) {
1165 					return false;
1166 				}
1167 			}
1168 
1169 			return true;
1170 		},
1171 
1172 		/**
1173 		 * method used to remove/split markup from a "markup2markup" selection
1174 		 * @param relevantMarkupObjectsAtSelectionStart JS Array of dom objects, which are parents to the rangeObject.startContainer
1175 		 * @param relevantMarkupObjectsAtSelectionEnd JS Array of dom objects, which are parents to the rangeObject.endContainer
1176 		 * @param rangeObj Aloha rangeObject
1177 		 * @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
1178 		 * @return true (always, since no "false" case is currently known...but might be added)
1179 		 * @hide
1180 		 */
1181 		splitRelevantMarkupObject: function (relevantMarkupObjectsAtSelectionStart, relevantMarkupObjectsAtSelectionEnd, rangeObject, tagComparator) {
1182 			// mark them to be deleted
1183 			jQuery(relevantMarkupObjectsAtSelectionStart).addClass('preparedForRemoval');
1184 			jQuery(relevantMarkupObjectsAtSelectionEnd).addClass('preparedForRemoval');
1185 
1186 			// check if the rangeObject is identical with the relevantMarkupObjects (in this case the markup can simply be removed)
1187 			if (this.areMarkupObjectsAsLongAsRangeObject(relevantMarkupObjectsAtSelectionStart, relevantMarkupObjectsAtSelectionEnd, rangeObject)) {
1188 				return true;
1189 			}
1190 
1191 			// find intersection (this can always only be one dom element (namely the highest) because all others will be removed
1192 			var relevantMarkupObjectAtSelectionStartAndEnd = this.intersectRelevantMarkupObjects(relevantMarkupObjectsAtSelectionStart, relevantMarkupObjectsAtSelectionEnd);
1193 
1194 			if (relevantMarkupObjectAtSelectionStartAndEnd) {
1195 				this.insertCroppedMarkups([relevantMarkupObjectAtSelectionStartAndEnd], rangeObject, false, tagComparator);
1196 				this.insertCroppedMarkups([relevantMarkupObjectAtSelectionStartAndEnd], rangeObject, true, tagComparator);
1197 			} else {
1198 				this.insertCroppedMarkups(relevantMarkupObjectsAtSelectionStart, rangeObject, false, tagComparator);
1199 				this.insertCroppedMarkups(relevantMarkupObjectsAtSelectionEnd, rangeObject, true, tagComparator);
1200 			}
1201 			return true;
1202 		},
1203 
1204 		/**
1205 		 * method takes two arrays of bottom up dom objects, compares them and returns either the object closest to the root or false
1206 		 * @param relevantMarkupObjectsAtSelectionStart JS Array of dom objects
1207 		 * @param relevantMarkupObjectsAtSelectionEnd JS Array of dom objects
1208 		 * @return dom object closest to the root or false
1209 		 * @hide
1210 		 */
1211 		intersectRelevantMarkupObjects: function (relevantMarkupObjectsAtSelectionStart, relevantMarkupObjectsAtSelectionEnd) {
1212 			var intersection = false, i, elStart, j, elEnd, relMarkupStart, relMarkupEnd;
1213 			if (!relevantMarkupObjectsAtSelectionStart || !relevantMarkupObjectsAtSelectionEnd) {
1214 				return intersection; // we can only intersect, if we have to arrays!
1215 			}
1216 			relMarkupStart = relevantMarkupObjectsAtSelectionStart.length;
1217 			relMarkupEnd = relevantMarkupObjectsAtSelectionEnd.length;
1218 			for (i = 0; i < relMarkupStart; i++) {
1219 				elStart = relevantMarkupObjectsAtSelectionStart[i];
1220 				for (j = 0; j < relMarkupEnd; j++) {
1221 					elEnd = relevantMarkupObjectsAtSelectionEnd[j];
1222 					if (elStart === elEnd) {
1223 						intersection = elStart;
1224 					}
1225 				}
1226 			}
1227 			return intersection;
1228 		},
1229 
1230 		/**
1231 		 * method used to add markup to a nonmarkup2markup selection
1232 		 * @param relevantMarkupObjects JS Array of dom objects effecting either the start or endContainer of a selection (which should be extended)
1233 		 * @param rangeObject Aloha rangeObject the markups should be extended to
1234 		 * @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)
1235 		 * @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
1236 		 * @return true
1237 		 * @hide
1238 		 */
1239 		extendExistingMarkupWithSelection: function (relevantMarkupObjects, rangeObject, startOrEnd, tagComparator) {
1240 			var extendMarkupsAtStart, extendMarkupsAtEnd, objects, i, relMarkupLength, el, textnodes, nodeNr;
1241 			if (!startOrEnd) { // = Start
1242 				// start part of rangeObject should be used, therefor existing markups are cropped at the end
1243 				extendMarkupsAtStart = true;
1244 			}
1245 			if (startOrEnd) { // = End
1246 				// end part of rangeObject should be used, therefor existing markups are cropped at start (beginning)
1247 				extendMarkupsAtEnd = true;
1248 			}
1249 			objects = [];
1250 			for (i = 0, relMarkupLength = relevantMarkupObjects.length; i < relMarkupLength; i++) {
1251 				objects[i] = new this.SelectionRange();
1252 				el = relevantMarkupObjects[i];
1253 				if (extendMarkupsAtEnd && !extendMarkupsAtStart) {
1254 					objects[i].startContainer = rangeObject.startContainer; // jQuery(el).contents()[0];
1255 					objects[i].startOffset = rangeObject.startOffset;
1256 					textnodes = jQuery(el).textNodes(true);
1257 
1258 					nodeNr = textnodes.length - 1;
1259 					objects[i].endContainer = textnodes[nodeNr];
1260 					objects[i].endOffset = textnodes[nodeNr].length;
1261 					objects[i].update();
1262 					this.applyMarkup(objects[i].getSelectionTree(), rangeObject, this.getClonedMarkup4Wrapping(el), tagComparator, {
1263 						setRangeObject2NewMarkup: true
1264 					});
1265 				}
1266 				if (!extendMarkupsAtEnd && extendMarkupsAtStart) {
1267 					textnodes = jQuery(el).textNodes(true);
1268 					objects[i].startContainer = textnodes[0]; // jQuery(el).contents()[0];
1269 					objects[i].startOffset = 0;
1270 					objects[i].endContainer = rangeObject.endContainer;
1271 					objects[i].endOffset = rangeObject.endOffset;
1272 					objects[i].update();
1273 					this.applyMarkup(objects[i].getSelectionTree(), rangeObject, this.getClonedMarkup4Wrapping(el), tagComparator, {
1274 						setRangeObject2NewMarkup: true
1275 					});
1276 				}
1277 			}
1278 			return true;
1279 		},
1280 
1281 		/**
1282 		 * method creates an empty markup jQuery object from a dom object passed as paramter
1283 		 * @param domobj domobject to be cloned, cleaned and emptied
1284 		 * @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
1285 		 * @return jQuery wrapper object to be passed to e.g. this.applyMarkup(...)
1286 		 * @hide
1287 		 */
1288 		getClonedMarkup4Wrapping: function (domobj) {
1289 			var wrapper = jQuery(domobj.outerHTML).removeClass('preparedForRemoval').empty();
1290 			if (wrapper.attr('class').length === 0) {
1291 				wrapper.removeAttr('class');
1292 			}
1293 			return wrapper;
1294 		},
1295 
1296 		/**
1297 		 * method used to subtract the range object from existing markup. in other words: certain markup is removed from the selections defined by the rangeObject
1298 		 * @param relevantMarkupObjects JS Array of dom objects effecting either the start or endContainer of a selection (which should be extended)
1299 1300 		 * @param rangeObject Aloha rangeObject the markups should be removed from
1301 		 * @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)
1302 		 * @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
1303 		 * @return true
1304 		 * @hide
1305 		 */
1306 		insertCroppedMarkups: function (relevantMarkupObjects, rangeObject, startOrEnd, tagComparator) {
1307 			var cropMarkupsAtEnd, cropMarkupsAtStart, textnodes, objects, i, el, textNodes;
1308 			if (!startOrEnd) { // = Start
1309 				// start part of rangeObject should be used, therefor existing markups are cropped at the end
1310 				cropMarkupsAtEnd = true;
1311 			} else { // = End
1312 				// end part of rangeObject should be used, therefor existing markups are cropped at start (beginning)
1313 				cropMarkupsAtStart = true;
1314 			}
1315 			objects = [];
1316 			for (i = 0; i < relevantMarkupObjects.length; i++) {
1317 				objects[i] = new this.SelectionRange();
1318 				el = relevantMarkupObjects[i];
1319 				if (cropMarkupsAtEnd && !cropMarkupsAtStart) {
1320 					textNodes = jQuery(el).textNodes(true);
1321 					objects[i].startContainer = textNodes[0];
1322 					objects[i].startOffset = 0;
1323 					// if the existing markup startContainer & startOffset are equal to the rangeObject startContainer and startOffset,
1324 					// then markupobject does not have to be added again, because it would have no content (zero-length)
1325 					if (objects[i].startContainer === rangeObject.startContainer && objects[i].startOffset === rangeObject.startOffset) {
1326 						continue;
1327 					}
1328 					if (rangeObject.startOffset === 0) {
1329 						objects[i].endContainer = this.getTextNodeSibling(false, el, rangeObject.startContainer);
1330 						objects[i].endOffset = objects[i].endContainer.length;
1331 					} else {
1332 						objects[i].endContainer = rangeObject.startContainer;
1333 						objects[i].endOffset = rangeObject.startOffset;
1334 					}
1335 
1336 					objects[i].update();
1337 
1338 					this.applyMarkup(objects[i].getSelectionTree(), rangeObject, this.getClonedMarkup4Wrapping(el), tagComparator, {
1339 						setRangeObject2NextSibling: true
1340 					});
1341 				}
1342 
1343 				if (!cropMarkupsAtEnd && cropMarkupsAtStart) {
1344 					objects[i].startContainer = rangeObject.endContainer; // jQuery(el).contents()[0];
1345 					objects[i].startOffset = rangeObject.endOffset;
1346 					textnodes = jQuery(el).textNodes(true);
1347 					objects[i].endContainer = textnodes[textnodes.length - 1];
1348 					objects[i].endOffset = textnodes[textnodes.length - 1].length;
1349 					objects[i].update();
1350 					this.applyMarkup(objects[i].getSelectionTree(), rangeObject, this.getClonedMarkup4Wrapping(el), tagComparator, {
1351 						setRangeObject2PreviousSibling: true
1352 					});
1353 				}
1354 			}
1355 			return true;
1356 		},
1357 
1358 		/**
1359 		 * apply a certain markup to the current selection
1360 		 * @param markupObject jQuery object of the markup to be applied (e.g. created with obj = jQuery('<b></b>'); )
1361 		 * @return void
1362 		 * @hide
1363 		 */
1364 		changeMarkupOnSelection: function (markupObject) {
1365 			var rangeObject = this.getRangeObject();
1366 
1367 			// change the markup
1368 			this.changeMarkup(rangeObject, markupObject, this.getStandardTagComparator(markupObject));
1369 
1370 			// merge text nodes
1371 			GENTICS.Utils.Dom.doCleanup({
1372 				'merge': true
1373 			}, rangeObject);
1374 
1375 			// update the range and select it
1376 			rangeObject.update();
1377 			rangeObject.select();
1378 			this.rangeObject = rangeObject;
1379 		},
1380 
1381 		/**
1382 		 * apply a certain markup to the selection Tree
1383 		 * @param selectionTree SelectionTree Object markup should be applied to
1384 		 * @param rangeObject Aloha rangeObject which will be modified to reflect the dom changes, after the markup was applied (only if activated via options)
1385 		 * @param markupObject jQuery object of the markup to be applied (e.g. created with obj = jQuery('<b></b>'); )
1386 		 * @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
1387 		 * @param options JS object, with the following boolean properties: setRangeObject2NewMarkup, setRangeObject2NextSibling, setRangeObject2PreviousSibling
1388 		 * @return void
1389 		 * @hide
1390 		 */
1391 		applyMarkup: function (selectionTree, rangeObject, markupObject, tagComparator, options) {
1392 			var optimizedSelectionTree, i, el, breakpoint;
1393 			options = options || {};
1394 			// first same tags from within fully selected nodes for removal
1395 			this.prepareForRemoval(selectionTree, markupObject, tagComparator);
1396 
1397 			// first let's optimize the selection Tree in useful groups which can be wrapped together
1398 			optimizedSelectionTree = this.optimizeSelectionTree4Markup(selectionTree, markupObject, tagComparator);
1399 			breakpoint = true;
1400 
1401 			// now iterate over grouped elements and either recursively dive into object or wrap it as a whole
1402 			for (i = 0; i < optimizedSelectionTree.length; i++) {
1403 				el = optimizedSelectionTree[i];
1404 				if (el.wrappable) {
1405 					this.wrapMarkupAroundSelectionTree(el.elements, rangeObject, markupObject, tagComparator, options);
1406 				} else {
1407 					Aloha.Log.debug(this, 'dive further into non-wrappable object');
1408 					this.applyMarkup(el.element.children, rangeObject, markupObject, tagComparator, options);
1409 				}
1410 			}
1411 		},
1412 
1413 		/**
1414 		 * returns the type of the given markup (trying to match HTML5)
1415 		 * @param markupObject jQuery object of the markup to be applied (e.g. created with obj = jQuery('<b></b>'); )
1416 		 * @return string name of the markup type
1417 		 * @hide
1418 		 */
1419 		getMarkupType: function (markupObject) {
1420 			var nn = jQuery(markupObject)[0].nodeName.toLowerCase();
1421 			if (markupObject.outerHtml) {
1422 				Aloha.Log.debug(this, 'Node name detected: ' + nn + ' for: ' + markupObject.outerHtml());
1423 			}
1424 			if (nn == '#text') {
1425 				return 'textNode';
1426 			}
1427 			if (this.replacingElements[nn]) {
1428 				return 'sectionOrGroupingContent';
1429 			}
1430 			if (this.tagHierarchy[nn]) {
1431 				return 'textLevelSemantics';
1432 			}
1433 			Aloha.Log.warn(this, 'unknown markup passed to this.getMarkupType(...): ' + markupObject.outerHtml());
1434 		},
1435 
1436 		/**
1437 		 * returns the standard tag comparator for the given markup object
1438 		 * @param markupObject jQuery object of the markup to be applied (e.g. created with obj = jQuery('<b></b>'); )
1439 		 * @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
1440 		 * @hide
1441 		 */
1442 		getStandardTagComparator: function (markupObject) {
1443 			var that = this,
1444 				result;
1445 			switch (this.getMarkupType(markupObject)) {
1446 			case 'textNode':
1447 				result = function (p1, p2) {
1448 					return false;
1449 				};
1450 				break;
1451 
1452 			case 'sectionOrGroupingContent':
1453 				result = function (domobj, markupObject) {
1454 					return that.standardSectionsAndGroupingContentComparator(domobj, markupObject);
1455 				};
1456 				break;
1457 
1458 			//case 'textLevelSemantics' covered by default
1459 			default:
1460 				result = function (domobj, markupObject) {
1461 					return that.standardTextLevelSemanticsComparator(domobj, markupObject);
1462 				};
1463 				break;
1464 			}
1465 			return result;
1466 		},
1467 
1468 		/**
1469 		 * searches for fully selected equal markup tags
1470 		 * @param selectionTree SelectionTree Object markup should be applied to
1471 		 * @param markupObject jQuery object of the markup to be applied (e.g. created with obj = jQuery('<b></b>'); )
1472 		 * @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
1473 		 * @return void
1474 		 * @hide
1475 		 */
1476 		prepareForRemoval: function (selectionTree, markupObject, tagComparator) {
1477 			var that = this, i, el;
1478 
1479 			// check if a comparison method was passed as parameter ...
1480 			if (typeof tagComparator !== 'undefined' && typeof tagComparator !== 'function') {
1481 				Aloha.Log.error(this, 'parameter tagComparator is not a function');
1482 			}
1483 			// ... if not use this as standard tag comparison method
1484 			if (typeof tagComparator === 'undefined') {
1485 				tagComparator = this.getStandardTagComparator(markupObject);
1486 			}
1487 			for (i = 0; i < selectionTree.length; i++) {
1488 				el = selectionTree[i];
1489 				if (el.domobj && (el.selection == 'full' || (el.selection == 'partial' && markupObject.isReplacingElement))) {
1490 					// mark for removal
1491 					if (el.domobj.nodeType === 1 && tagComparator(el.domobj, markupObject)) {
1492 						Aloha.Log.debug(this, 'Marking for removal: ' + el.domobj.nodeName);
1493 						jQuery(el.domobj).addClass('preparedForRemoval');
1494 					}
1495 				}
1496 				if (el.selection != 'none' && el.children.length > 0) {
1497 					this.prepareForRemoval(el.children, markupObject, tagComparator);
1498 				}
1499 
1500 			}
1501 		},
1502 
1503 		/**
1504 		 * searches for fully selected equal markup tags
1505 		 * @param selectionTree SelectionTree Object markup should be applied to
1506 		 * @param rangeObject Aloha rangeObject the markup will be applied to
1507 		 * @param markupObject jQuery object of the markup to be applied (e.g. created with obj = jQuery('<b></b>'); )
1508 		 * @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
1509 		 * @param options JS object, with the following boolean properties: setRangeObject2NewMarkup, setRangeObject2NextSibling, setRangeObject2PreviousSibling
1510 		 * @return void
1511 		 * @hide
1512 		 */
1513 		wrapMarkupAroundSelectionTree: function (selectionTree, rangeObject, markupObject, tagComparator, options) {
1514 			// first let's find out if theoretically the whole selection can be wrapped with one tag and save it for later use
1515 			var objects2wrap = [], // // this will be used later to collect objects
1516 				j = -1, // internal counter,
1517 				breakpoint = true,
1518 				preText = '',
1519 				postText = '',
1520 				prevOrNext,
1521 				textNode2Start,
1522 				textnodes,
1523 				newMarkup,
1524 				i,
1525 			    el,
1526 			    middleText;
1527 
1528 			Aloha.Log.debug(this, 'The formatting <' + markupObject[0].tagName + '> will be wrapped around the selection');
1529 
1530 			// now lets iterate over the elements
1531 			for (i = 0; i < selectionTree.length; i++) {
1532 				el = selectionTree[i];
1533 
1534 				// check if markup is allowed inside the elements parent
1535 				if (el.domobj && !this.canTag1WrapTag2(el.domobj.parentNode.tagName.toLowerCase(), markupObject[0].tagName.toLowerCase())) {
1536 					Aloha.Log.info(this, 'Skipping the wrapping of <' + markupObject[0].tagName.toLowerCase() + '> because this tag is not allowed inside <' + el.domobj.parentNode.tagName.toLowerCase() + '>');
1537 					continue;
1538 				}
1539 
1540 				// skip empty text nodes
1541 				if (el.domobj && el.domobj.nodeType === 3 && jQuery.trim(el.domobj.nodeValue).length === 0) {
1542 					continue;
1543 				}
1544 
1545 				// partial element, can either be a textnode and therefore be wrapped (at least partially)
1546 				// or can be a nodeType == 1 (tag) which must be dived into
1547 				if (el.domobj && el.selection == 'partial' && !markupObject.isReplacingElement) {
1548 					if (el.startOffset !== undefined && el.endOffset === undefined) {
1549 						j++;
1550 						preText += el.domobj.data.substr(0, el.startOffset);
1551 						el.domobj.data = el.domobj.data.substr(el.startOffset, el.domobj.data.length - el.startOffset);
1552 						objects2wrap[j] = el.domobj;
1553 					} else if (el.endOffset !== undefined && el.startOffset === undefined) {
1554 						j++;
1555 						postText += el.domobj.data.substr(el.endOffset, el.domobj.data.length - el.endOffset);
1556 						el.domobj.data = el.domobj.data.substr(0, el.endOffset);
1557 						objects2wrap[j] = el.domobj;
1558 					} else if (el.endOffset !== undefined && el.startOffset !== undefined) {
1559 						if (el.startOffset == el.endOffset) { // do not wrap empty selections
1560 							Aloha.Log.debug(this, 'skipping empty selection');
1561 							continue;
1562 						}
1563 						j++;
1564 						preText += el.domobj.data.substr(0, el.startOffset);
1565 						middleText = el.domobj.data.substr(el.startOffset, el.endOffset - el.startOffset);
1566 						postText += el.domobj.data.substr(el.endOffset, el.domobj.data.length - el.endOffset);
1567 						el.domobj.data = middleText;
1568 						objects2wrap[j] = el.domobj;
1569 					} else {
1570 						// a partially selected item without selectionStart/EndOffset is a nodeType 1 Element on the way to the textnode
1571 						Aloha.Log.debug(this, 'diving into object');
1572 						this.applyMarkup(el.children, rangeObject, markupObject, tagComparator, options);
1573 					}
1574 				}
1575 				// fully selected dom elements can be wrapped as whole element
1576 				if (el.domobj && (el.selection == 'full' || (el.selection == 'partial' && markupObject.isReplacingElement))) {
1577 					j++;
1578 					objects2wrap[j] = el.domobj;
1579 				}
1580 			}
1581 
1582 			if (objects2wrap.length > 0) {
1583 				// wrap collected DOM object with markupObject
1584 				objects2wrap = jQuery(objects2wrap);
1585 
1586 				// make a fix for text nodes in <li>'s in ie
1587 				jQuery.each(objects2wrap, function (index, element) {
1588 					if (jQuery.browser.msie && element.nodeType == 3 && !element.nextSibling && !element.previousSibling && element.parentNode && element.parentNode.nodeName.toLowerCase() == 'li') {
1589 						element.data = jQuery.trim(element.data);
1590 					}
1591 				});
1592 
1593 				newMarkup = objects2wrap.wrapAll(markupObject).parent();
1594 				newMarkup.before(preText).after(postText);
1595 
1596 				if (options.setRangeObject2NewMarkup) { // this is used, when markup is added to normal/normal Text
1597 					textnodes = objects2wrap.textNodes();
1598 
1599 					if (textnodes.index(rangeObject.startContainer) != -1) {
1600 						rangeObject.startOffset = 0;
1601 					}
1602 					if (textnodes.index(rangeObject.endContainer) != -1) {
1603 						rangeObject.endOffset = rangeObject.endContainer.length;
1604 					}
1605 					breakpoint = true;
1606 				}
1607 				if (options.setRangeObject2NextSibling) {
1608 					prevOrNext = true;
1609 					textNode2Start = newMarkup.textNodes(true).last()[0];
1610 					if (objects2wrap.index(rangeObject.startContainer) != -1) {
1611 						rangeObject.startContainer = this.getTextNodeSibling(prevOrNext, newMarkup.parent(), textNode2Start);
1612 						rangeObject.startOffset = 0;
1613 					}
1614 					if (objects2wrap.index(rangeObject.endContainer) != -1) {
1615 						rangeObject.endContainer = this.getTextNodeSibling(prevOrNext, newMarkup.parent(), textNode2Start);
1616 						rangeObject.endOffset = rangeObject.endOffset - textNode2Start.length;
1617 					}
1618 				}
1619 				if (options.setRangeObject2PreviousSibling) {
1620 					prevOrNext = false;
1621 					textNode2Start = newMarkup.textNodes(true).first()[0];
1622 					if (objects2wrap.index(rangeObject.startContainer) != -1) {
1623 						rangeObject.startContainer = this.getTextNodeSibling(prevOrNext, newMarkup.parent(), textNode2Start);
1624 						rangeObject.startOffset = 0;
1625 					}
1626 					if (objects2wrap.index(rangeObject.endContainer) != -1) {
1627 						rangeObject.endContainer = this.getTextNodeSibling(prevOrNext, newMarkup.parent(), textNode2Start);
1628 						rangeObject.endOffset = rangeObject.endContainer.length;
1629 					}
1630 				}
1631 			}
1632 		},
1633 
1634 		/**
1635 		 * takes a text node and return either the next recursive text node sibling or the previous
1636 		 * @param previousOrNext boolean, false for previous, true for next sibling
1637 		 * @param commonAncestorContainer dom object to be used as root for the sibling search
1638 		 * @param currentTextNode dom object of the originating text node
1639 		 * @return dom object of the sibling text node
1640 		 * @hide
1641 		 */
1642 		getTextNodeSibling: function (previousOrNext, commonAncestorContainer, currentTextNode) {
1643 			var textNodes = jQuery(commonAncestorContainer).textNodes(true), newIndex, index;
1644 
1645 			index = textNodes.index(currentTextNode);
1646 			if (index == -1) { // currentTextNode was not found
1647 				return false;
1648 			}
1649 			newIndex = index + (!previousOrNext ? -1 : 1);
1650 			return textNodes[newIndex] || false;
1651 		},
1652 
1653 		/**
1654 		 * takes a selection tree and groups it into markup wrappable selection trees
1655 		 * @param selectionTree rangeObject selection tree
1656 		 * @param markupObject jQuery object of the markup to be applied (e.g. created with obj = jQuery('<b></b>'); )
1657 		 * @return JS array of wrappable selection trees
1658 		 * @hide
1659 		 */
1660 		optimizeSelectionTree4Markup: function (selectionTree, markupObject, tagComparator) {
1661 			var groupMap = [],
1662 				outerGroupIndex = 0,
1663 				innerGroupIndex = 0,
1664 				that = this,
1665 				i,
1666 			    j,
1667 				endPosition,
1668 			    startPosition;
1669 
1670 			if (typeof tagComparator === 'undefined') {
1671 				tagComparator = function (domobj, markupObject) {
1672 					return that.standardTextLevelSemanticsComparator(markupObject);
1673 				};
1674 			}
1675 			for (i = 0; i < selectionTree.length; i++) {
1676 				// we are just interested in selected item, but not in non-selected items
1677 				if (selectionTree[i].domobj && selectionTree[i].selection != 'none') {
1678 					if (markupObject.isReplacingElement && tagComparator(markupObject[0], jQuery(selectionTree[i].domobj))) {
1679 						if (groupMap[outerGroupIndex] !== undefined) {
1680 							outerGroupIndex++;
1681 						}
1682 						groupMap[outerGroupIndex] = {};
1683 						groupMap[outerGroupIndex].wrappable = true;
1684 						groupMap[outerGroupIndex].elements = [];
1685 						groupMap[outerGroupIndex].elements[innerGroupIndex] = selectionTree[i];
1686 						outerGroupIndex++;
1687 
1688 					} else if (this.canMarkupBeApplied2ElementAsWhole([selectionTree[i]], markupObject)) {
1689 						// now check, if the children of our item could be wrapped all together by the markup object
1690 						// if yes, add it to the current group
1691 						if (groupMap[outerGroupIndex] === undefined) {
1692 							groupMap[outerGroupIndex] = {};
1693 							groupMap[outerGroupIndex].wrappable = true;
1694 							groupMap[outerGroupIndex].elements = [];
1695 						}
1696 						if (markupObject.isReplacingElement) { //  && selectionTree[i].domobj.nodeType === 3
1697 							/* we found the node to wrap for a replacing element. however there might
1698 							 * be siblings which should be included as well
1699 							 * although they are actually not selected. example:
1700 							 * li
1701 							 * |-textNode ( .selection = 'none')
1702 							 * |-textNode (cursor inside, therefor .selection = 'partial')
1703 							 * |-textNode ( .selection = 'none')
1704 							 *
1705 							 * in this case it would be useful to select the previous and following textNodes as well (they might result from a previous DOM manipulation)
1706 							 * 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 /> ??
1707 							 * .... many possibilities, here I realize the two described cases
1708 							 */
1709 
1710 							// first find start element starting from the current element going backwards until sibling 0
1711 							startPosition = i;
1712 							for (j = i - 1; j >= 0; j--) {
1713 								if (this.canMarkupBeApplied2ElementAsWhole([selectionTree[j]], markupObject) && this.isMarkupAllowedToStealSelectionTreeElement(selectionTree[j], markupObject)) {
1714 									startPosition = j;
1715 								} else {
1716 									break;
1717 								}
1718 							}
1719 
1720 							// now find the end element starting from the current element going forward until the last sibling
1721 							endPosition = i;
1722 							for (j = i + 1; j < selectionTree.length; j++) {
1723 								if (this.canMarkupBeApplied2ElementAsWhole([selectionTree[j]], markupObject) && this.isMarkupAllowedToStealSelectionTreeElement(selectionTree[j], markupObject)) {
1724 									endPosition = j;
1725 								} else {
1726 									break;
1727 								}
1728 							}
1729 
1730 							// now add the elements to the groupMap
1731 							innerGroupIndex = 0;
1732 							for (j = startPosition; j <= endPosition; j++) {
1733 								groupMap[outerGroupIndex].elements[innerGroupIndex] = selectionTree[j];
1734 								groupMap[outerGroupIndex].elements[innerGroupIndex].selection = 'full';
1735 								innerGroupIndex++;
1736 							}
1737 							innerGroupIndex = 0;
1738 						} else {
1739 							// normal text level semantics object, no siblings need to be selected
1740 							groupMap[outerGroupIndex].elements[innerGroupIndex] = selectionTree[i];
1741 							innerGroupIndex++;
1742 						}
1743 					} else {
1744 						// if no, isolate it in its own group
1745 						if (groupMap[outerGroupIndex] !== undefined) {
1746 							outerGroupIndex++;
1747 						}
1748 						groupMap[outerGroupIndex] = {};
1749 						groupMap[outerGroupIndex].wrappable = false;
1750 						groupMap[outerGroupIndex].element = selectionTree[i];
1751 						innerGroupIndex = 0;
1752 						outerGroupIndex++;
1753 					}
1754 				}
1755 			}
1756 			return groupMap;
1757 		},
1758 
1759 		/**
1760 		 * very tricky method, which decides, if a certain markup (normally a replacing markup element like p, h1, blockquote)
1761 		 * is allowed to extend the user selection to other dom objects (represented as selectionTreeElement)
1762 		 * to understand the purpose: if the user selection is collapsed inside e.g. some text, which is currently not
1763 		 * wrapped by the markup to be applied, and therefor the markup does not have an equal markup to replace, then the DOM
1764 		 * manipulator has to decide which objects to wrap. real example:
1765 		 * <div>
1766 		 *	<h1>headline</h1>
1767 		 *	some text blabla bla<br>
1768 		 *	more text HERE THE | CURSOR BLINKING and <b>even more bold text</b>
1769 		 * </div>
1770 		 * 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
1771 		 * wrap everything inside the div except the <h1>. but for this purpose someone has to decide, if the markup is
1772 		 * allowed to wrap certain dom elements in this case the question would be, if the <p> is allowed to wrap
1773 		 * textNodes, <br> and <b> and <h1>. therefore this tricky method should answer the question for those 3 elements
1774 		 * with true, but for for the <h1> it should return false. and since the method does not know this, there is a configuration
1775 		 * for this
1776 		 *
1777 		 * @param selectionTree rangeObject selection tree element (only one, not an array of)
1778 		 * @param markupObject lowercase string of the tag to be verified (e.g. "b")
1779 		 * @return true if the markup is allowed to wrap the selection tree element, false otherwise
1780 		 * @hide
1781 		 */
1782 		isMarkupAllowedToStealSelectionTreeElement: function (selectionTreeElement, markupObject) {
1783 			if (!selectionTreeElement.domobj) {
1784 				return false;
1785 			}
1786 			var maybeTextNodeName = selectionTreeElement.domobj.nodeName.toLowerCase(),
1787 				nodeName = (maybeTextNodeName == '#text') ? 'textNode' : maybeTextNodeName,
1788 				markupName = markupObject[0].nodeName.toLowerCase(),
1789 				elemMap = this.allowedToStealElements[markupName];
1790 			return elemMap && elemMap[nodeName];
1791 		},
1792 
1793 		/**
1794 		 * checks if a selection can be completey wrapped by a certain html tags (helper method for this.optimizeSelectionTree4Markup
1795 		 * @param selectionTree rangeObject selection tree
1796 		 * @param markupObject lowercase string of the tag to be verified (e.g. "b")
1797 		 * @return true if selection can be applied as whole, false otherwise
1798 		 * @hide
1799 		 */
1800 		canMarkupBeApplied2ElementAsWhole: function (selectionTree, markupObject) {
1801 			var htmlTag, i, el, returnVal;
1802 
1803 			if (markupObject.jquery) {
1804 				htmlTag = markupObject[0].tagName;
1805 			}
1806 			if (markupObject.tagName) {
1807 				htmlTag = markupObject.tagName;
1808 			}
1809 
1810 			returnVal = true;
1811 			for (i = 0; i < selectionTree.length; i++) {
1812 				el = selectionTree[i];
1813 				if (el.domobj && (el.selection != "none" || markupObject.isReplacingElement)) {
1814 					// Aloha.Log.debug(this, 'Checking, if  <' + htmlTag + '> can be applied to ' + el.domobj.nodeName);
1815 					if (!this.canTag1WrapTag2(htmlTag, el.domobj.nodeName)) {
1816 						return false;
1817 					}
1818 					if (el.children.length > 0 && !this.canMarkupBeApplied2ElementAsWhole(el.children, markupObject)) {
1819 						return false;
1820 					}
1821 				}
1822 			}
1823 			return returnVal;
1824 		},
1825 
1826 		/**
1827 		 * checks if a tag 1 (first parameter) can wrap tag 2 (second parameter).
1828 		 * IMPORTANT: the method does not verify, if there have to be other tags in between
1829 		 * 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
1830 		 * @param t1 string: tagname of outer tag to verify, e.g. "b"
1831 		 * @param t2 string: tagname of inner tag to verify, e.g. "b"
1832 		 * @return true if tag 1 can wrap tag 2, false otherwise
1833 		 * @hide
1834 		 */
1835 		canTag1WrapTag2: function (t1, t2) {
1836 			t1 = (t1 == '#text') ? 'textNode' : t1.toLowerCase();
1837 			t2 = (t2 == '#text') ? 'textNode' : t2.toLowerCase();
1838 			var t1Map = this.tagHierarchy[t1];
1839 			if (!t1Map) {
1840 				return true;
1841 			}
1842 			if (!this.tagHierarchy[t2]) {
1843 				return true;
1844 			}
1845 			return t1Map[t2];
1846 		},
1847 
1848 		/**
1849 		 * Check whether it is allowed to insert the given tag at the start of the
1850 		 * current selection. This method will check whether the markup effective for
1851 		 * the start and outside of the editable part (starting with the editable tag
1852 		 * itself) may wrap the given tag.
1853 		 * @param tagName {String} name of the tag which shall be inserted
1854 		 * @return true when it is allowed to insert that tag, false if not
1855 		 * @hide
1856 		 */
1857 		mayInsertTag: function (tagName) {
1858 			var i;
1859 			if (typeof this.rangeObject.unmodifiableMarkupAtStart == 'object') {
1860 				// iterate over all DOM elements outside of the editable part
1861 				for (i = 0; i < this.rangeObject.unmodifiableMarkupAtStart.length; ++i) {
1862 					// check whether an element may not wrap the given
1863 					if (!this.canTag1WrapTag2(this.rangeObject.unmodifiableMarkupAtStart[i].nodeName, tagName)) {
1864 						// found a DOM element which forbids to insert the given tag, we are done
1865 						return false;
1866 					}
1867 				}
1868 
1869 				// all of the found DOM elements allow inserting the given tag
1870 				return true;
1871 			}
1872 			Aloha.Log.warn(this, 'Unable to determine whether tag ' + tagName + ' may be inserted');
1873 			return true;
1874 		},
1875 
1876 		/**
1877 		 * String representation
1878 		 * @return "Aloha.Selection"
1879 		 * @hide
1880 		 */
1881 		toString: function () {
1882 			return 'Aloha.Selection';
1883 		},
1884 
1885 		/**
1886 		 * @namespace Aloha.Selection
1887 		 * @class SelectionRange
1888 		 * @extends GENTICS.Utils.RangeObject
1889 		 * Constructor for a range object.
1890 		 * Optionally you can pass in a range object that's properties will be assigned to the new range object.
1891 		 * @param rangeObject A range object thats properties will be assigned to the new range object.
1892 		 * @constructor
1893 		 */
1894 		SelectionRange: GENTICS.Utils.RangeObject.extend({
1895 			_constructor: function (rangeObject) {
1896 				this._super(rangeObject);
1897 				// If a range object was passed in we apply the values to the new range object
1898 				if (rangeObject) {
1899 					if (rangeObject.commonAncestorContainer) {
1900 						this.commonAncestorContainer = rangeObject.commonAncestorContainer;
1901 					}
1902 					if (rangeObject.selectionTree) {
1903 						this.selectionTree = rangeObject.selectionTree;
1904 					}
1905 					if (rangeObject.limitObject) {
1906 						this.limitObject = rangeObject.limitObject;
1907 					}
1908 					if (rangeObject.markupEffectiveAtStart) {
1909 						this.markupEffectiveAtStart = rangeObject.markupEffectiveAtStart;
1910 					}
1911 					if (rangeObject.unmodifiableMarkupAtStart) {
1912 						this.unmodifiableMarkupAtStart = rangeObject.unmodifiableMarkupAtStart;
1913 					}
1914 					if (rangeObject.splitObject) {
1915 						this.splitObject = rangeObject.splitObject;
1916 					}
1917 				}
1918 			},
1919 
1920 			/**
1921 			 * DOM object of the common ancestor from startContainer and endContainer
1922 			 * @hide
1923 			 */
1924 			commonAncestorContainer: undefined,
1925 
1926 			/**
1927 			 * The selection tree
1928 			 * @hide
1929 			 */
1930 			selectionTree: undefined,
1931 
1932 			/**
1933 			 * Array of DOM objects effective for the start container and inside the
1934 			 * editable part (inside the limit object). relevant for the button status
1935 			 * @hide
1936 			 */
1937 			markupEffectiveAtStart: [],
1938 
1939 			/**
1940 			 * Array of DOM objects effective for the start container, which lies
1941 			 * outside of the editable portion (starting with the limit object)
1942 			 * @hide
1943 			 */
1944 			unmodifiableMarkupAtStart: [],
1945 
1946 			/**
1947 			 * DOM object being the limit for all markup relevant activities
1948 			 * @hide
1949 			 */
1950 			limitObject: undefined,
1951 
1952 			/**
1953 			 * DOM object being split when enter key gets hit
1954 			 * @hide
1955 			 */
1956 			splitObject: undefined,
1957 
1958 			/**
1959 			 * Sets the visible selection in the Browser based on the range object.
1960 			 * If the selection is collapsed, this will result in a blinking cursor,
1961 			 * otherwise in a text selection.
1962 1963 			 * @method
1964 			 */
1965 			select: function () {
1966 				// Call Utils' select()
1967 				this._super();
1968 
1969 				// update the selection
1970 				Aloha.Selection.updateSelection();
1971 			},
1972 
1973 			/**
1974 			 * Method to update a range object internally
1975 			 * @param commonAncestorContainer (DOM Object); optional Parameter; if set, the parameter
1976 			 * will be used instead of the automatically calculated CAC
1977 			 * @return void
1978 			 * @hide
1979 			 */
1980 			update: function (commonAncestorContainer) {
1981 				this.updatelimitObject();
1982 				this.updateMarkupEffectiveAtStart();
1983 				this.updateCommonAncestorContainer(commonAncestorContainer);
1984 
1985 				// reset the selectiontree (must be recalculated)
1986 				this.selectionTree = undefined;
1987 			},
1988 
1989 			/**
1990 			 * Get the selection tree for this range
1991 			 * TODO: remove this (was moved to range.js)
1992 			 * @return selection tree
1993 			 * @hide
1994 			 */
1995 			getSelectionTree: function () {
1996 				// if not yet calculated, do this now
1997 				if (!this.selectionTree) {
1998 					this.selectionTree = Aloha.Selection.getSelectionTree(this);
1999 				}
2000 
2001 				return this.selectionTree;
2002 			},
2003 
2004 			/**
2005 			 * TODO: move this to range.js
2006 			 * Get an array of domobj (in dom tree order) of siblings of the given domobj, which are contained in the selection
2007 			 * @param domobj dom object to start with
2008 			 * @return array of siblings of the given domobj, which are also selected
2009 			 * @hide
2010 			 */
2011 			getSelectedSiblings: function (domobj) {
2012 				var selectionTree = this.getSelectionTree();
2013 
2014 				return this.recursionGetSelectedSiblings(domobj, selectionTree);
2015 			},
2016 
2017 			/**
2018 			 * TODO: move this to range.js
2019 			 * Recursive method to find the selected siblings of the given domobj (which should be selected as well)
2020 			 * @param domobj dom object for which the selected siblings shall be found
2021 			 * @param selectionTree current level of the selection tree
2022 			 * @return array of selected siblings of dom objects or false if none found
2023 			 * @hide
2024 			 */
2025 			recursionGetSelectedSiblings: function (domobj, selectionTree) {
2026 				var selectedSiblings = false,
2027 					foundObj = false,
2028 					i;
2029 
2030 				for (i = 0; i < selectionTree.length; ++i) {
2031 					if (selectionTree[i].domobj === domobj) {
2032 						foundObj = true;
2033 						selectedSiblings = [];
2034 					} else if (!foundObj && selectionTree[i].children) {
2035 						// do the recursion
2036 						selectedSiblings = this.recursionGetSelectedSiblings(domobj, selectionTree[i].children);
2037 						if (selectedSiblings !== false) {
2038 							break;
2039 						}
2040 					} else if (foundObj && selectionTree[i].domobj && selectionTree[i].selection != 'collapsed' && selectionTree[i].selection != 'none') {
2041 						selectedSiblings.push(selectionTree[i].domobj);
2042 					} else if (foundObj && selectionTree[i].selection == 'none') {
2043 						break;
2044 					}
2045 				}
2046 
2047 				return selectedSiblings;
2048 			},
2049 
2050 			/**
2051 			 * TODO: move this to range.js
2052 			 * Method updates member var markupEffectiveAtStart and splitObject, which is relevant primarily for button status and enter key behaviour
2053 			 * @return void
2054 			 * @hide
2055 			 */
2056 			updateMarkupEffectiveAtStart: function () {
2057 				// reset the current markup
2058 				this.markupEffectiveAtStart = [];
2059 				this.unmodifiableMarkupAtStart = [];
2060 
2061 				var parents = this.getStartContainerParents(),
2062 					limitFound = false,
2063 					splitObjectWasSet,
2064 					i,
2065 				    el;
2066 2067 
				for (i = 0; i < parents.length; i++) {
2068 					el = parents[i];
2069 					if (!limitFound && (el !== this.limitObject)) {
2070 						this.markupEffectiveAtStart[i] = el;
2071 						if (!splitObjectWasSet && GENTICS.Utils.Dom.isSplitObject(el)) {
2072 							splitObjectWasSet = true;
2073 							this.splitObject = el;
2074 						}
2075 					} else {
2076 						limitFound = true;
2077 						this.unmodifiableMarkupAtStart.push(el);
2078 					}
2079 				}
2080 				if (!splitObjectWasSet) {
2081 					this.splitObject = false;
2082 				}
2083 				return;
2084 			},
2085 
2086 			/**
2087 			 * TODO: remove this
2088 			 * Method updates member var markupEffectiveAtStart, which is relevant primarily for button status
2089 			 * @return void
2090 			 * @hide
2091 			 */
2092 			updatelimitObject: function () {
2093 				if (Aloha.editables && Aloha.editables.length > 0) {
2094 					var parents = this.getStartContainerParents(),
2095 						editables = Aloha.editables,
2096 						i,
2097 					    el,
2098 					    j,
2099 					    editable;
2100 					for (i = 0; i < parents.length; i++) {
2101 						el = parents[i];
2102 						for (j = 0; j < editables.length; j++) {
2103 							editable = editables[j].obj[0];
2104 							if (el === editable) {
2105 								this.limitObject = el;
2106 								return true;
2107 							}
2108 						}
2109 					}
2110 				}
2111 				this.limitObject = jQuery('body');
2112 				return true;
2113 			},
2114 
2115 			/**
2116 			 * string representation of the range object
2117 			 * @param	verbose	set to true for verbose output
2118 			 * @return string representation of the range object
2119 			 * @hide
2120 			 */
2121 			toString: function (verbose) {
2122 				if (!verbose) {
2123 					return 'Aloha.Selection.SelectionRange';
2124 				}
2125 				return 'Aloha.Selection.SelectionRange {start [' + this.startContainer.nodeValue + '] offset ' + this.startOffset + ', end [' + this.endContainer.nodeValue + '] offset ' + this.endOffset + '}';
2126 			}
2127 
2128 		}) // SelectionRange
2129 
2130 	}); // Selection
2131 
2132 
2133 	/**
2134 	 * This method implements an ugly workaround for a selection problem in ie:
2135 	 * when the cursor shall be placed at the end of a text node in a li element, that is followed by a nested list,
2136 	 * the selection would always snap into the first li of the nested list
2137 	 * therefore, we make sure that the text node ends with a space and place the cursor right before it
2138 	 */
2139 	function nestedListInIEWorkaround(range) {
2140 		var nextSibling;
2141 		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) {
2142 			nextSibling = range.startContainer.nextSibling;
2143 			if ('OL' === nextSibling.nodeName || 'UL' === nextSibling.nodeName) {
2144 				if (range.startContainer.data[range.startContainer.data.length - 1] == ' ') {
2145 					range.startOffset = range.endOffset = range.startOffset - 1;
2146 				} else {
2147 					range.startContainer.data = range.startContainer.data + ' ';
2148 				}
2149 			}
2150 		}
2151 	}
2152 
2153 	function correctRange(range) {
2154 		nestedListInIEWorkaround(range);
2155 		return range;
2156 	}
2157 
2158 	/**
2159 	 * Implements Selection http://html5.org/specs/dom-range.html#selection
2160 	 * @namespace Aloha
2161 	 * @class Selection This singleton class always represents the
2162 	 *        current user selection
2163 	 * @singleton
2164 	 */
2165 	var AlohaSelection = Class.extend({
2166 
2167 		_constructor: function (nativeSelection) {
2168 
2169 			this._nativeSelection = nativeSelection;
2170 			this.ranges = [];
2171 
2172 			// will remember if urged to not change the selection
2173 			this.preventChange = false;
2174 
2175 		},
2176 
2177 		/**
2178 		 * Returns the element that contains the start of the selection. Returns null if there's no selection.
2179 		 * @readonly
2180 		 * @type Node
2181 		 */
2182 		anchorNode: null,
2183 
2184 		/**
2185 		 * Returns the offset of the start of the selection relative to the element that contains the start 
2186 		 * of the selection. Returns 0 if there's no selection.
2187 		 * @readonly
2188 		 * @type int
2189 		 */
2190 		anchorOffset: 0,
2191 
2192 		/**
2193 		 * Returns the element that contains the end of the selection.
2194 		 * Returns null if there's no selection.
2195 		 * @readonly
2196 		 * @type Node
2197 		 */
2198 		focusNode: null,
2199 
2200 		/**
2201 		 * Returns the offset of the end of the selection relative to the element that contains the end 
2202 		 * of the selection. Returns 0 if there's no selection.
2203 		 * @readonly
2204 		 * @type int
2205 		 */
2206 		focusOffset: 0,
2207 
2208 		/**
2209 		 * Returns true if there's no selection or if the selection is empty. Otherwise, returns false.
2210 		 * @readonly
2211 		 * @type boolean
2212 		 */
2213 		isCollapsed: false,
2214 
2215 		/**
2216 		 * Returns the number of ranges in the selection.
2217 		 * @readonly
2218 		 * @type int
2219 		 */
2220 		rangeCount: 0,
2221 
2222 		/**
2223 		 * Replaces the selection with an empty one at the given position.
2224 		 * @throws a WRONG_DOCUMENT_ERR exception if the given node is in a different document.
2225 		 * @param parentNode Node of new selection
2226 		 * @param offest offest of new Selection in parentNode
2227 		 * @void
2228 		 */
2229 		collapse: function (parentNode, offset) {
2230 			this._nativeSelection.collapse(parentNode, offset);
2231 		},
2232 
2233 		/**
2234 		 * Replaces the selection with an empty one at the position of the start of the current selection.
2235 		 * @throws an INVALID_STATE_ERR exception if there is no selection.
2236 		 * @void
2237 		 */
2238 		collapseToStart: function () {
2239 			throw "NOT_IMPLEMENTED";
2240 		},
2241 
2242 		/** 
2243 		 * @void
2244 		 */
2245 		extend: function (parentNode, offset) {
2246 
2247 		},
2248 
2249 		/**
2250 		 * @param alter DOMString 
2251 		 * @param direction DOMString 
2252 		 * @param granularity DOMString 
2253 		 * @void
2254 		 */
2255 		modify: function (alter, direction, granularity) {
2256 
2257 		},
2258 
2259 		/**
2260 		 * Replaces the selection with an empty one at the position of the end of the current selection.
2261 		 * @throws an INVALID_STATE_ERR exception if there is no selection.
2262 		 * @void
2263 		 */
2264 		collapseToEnd: function () {
2265 			this._nativeSelection.collapseToEnd();
2266 		},
2267 
2268 		/**
2269 		 * Replaces the selection with one that contains all the contents of the given element.
2270 		 * @throws a WRONG_DOCUMENT_ERR exception if the given node is in a different document.
2271 		 * @param parentNode Node the Node fully select
2272 		 * @void
2273 		 */
2274 		selectAllChildren: function (parentNode) {
2275 			throw "NOT_IMPLEMENTED";
2276 		},
2277 
2278 		/**
2279 		 * Deletes the contents of the selection
2280 		 */
2281 		deleteFromDocument: function () {
2282 			throw "NOT_IMPLEMENTED";
2283 		},
2284 
2285 		/**
2286 		 * NB!
2287 		 * We have serious problem in IE.
2288 		 * The range that we get in IE is not the same as the range we had set,
2289 		 * so even if we normalize it during getRangeAt, in IE, we will be
2290 		 * correcting the range to the "correct" place, but still not the place
2291 		 * where it was originally set.
2292 		 * 
2293 		 * Returns the given range.
2294 		 * The getRangeAt(index) method returns the indexth range in the list. 
2295 		 * NOTE: Aloha Editor only support 1 range! index can only be 0
2296 		 * @throws INDEX_SIZE_ERR DOM exception if index is less than zero or 
2297 		 * greater or equal to the value returned by the rangeCount.
2298 		 * @param index int 
2299 		 * @return Range return the selected range from index
2300 		 */
2301 		getRangeAt: function (index) {
2302 			return correctRange(this._nativeSelection.getRangeAt(index));
2303 			//if ( index < 0 || this.rangeCount ) {
2304 			//	throw "INDEX_SIZE_ERR DOM";
2305 			//}
2306 			//return this._ranges[index];
2307 		},
2308 
2309 		/**
2310 		 * Adds the given range to the selection.
2311 		 * The addRange(range) method adds the given range Range object to the list of
2312 		 * selections, at the end (so the newly added range is the new last range). 
2313 		 * NOTE: Aloha Editor only support 1 range! The added range will replace the 
2314 		 * range at index 0
2315 		 * see http://html5.org/specs/dom-range.html#selection note about addRange
2316 		 * @throws an INVALID_NODE_TYPE_ERR exception if the given Range has a boundary point
2317 		 * node that's not a Text or Element node, and an INVALID_MODIFICATION_ERR exception 
2318 		 * if it has a boundary point node that doesn't descend from a Document.
2319 		 * @param range Range adds the range to the selection
2320 		 * @void
2321 		 */
2322 		addRange: function (range) {
2323 			// set readonly attributes
2324 			this._nativeSelection.addRange(range);
2325 			// We will correct the range after rangy has processed the native
2326 			// selection range, so that our correction will be the final fix on
2327 			// the range according to the guarentee's that Aloha wants to make
2328 			this._nativeSelection._ranges[0] = correctRange(range);
2329 
2330 			// make sure, the old Aloha selection will be updated (until all implementations use the new AlohaSelection)
2331 			Aloha.Selection.updateSelection();
2332 		},
2333 
2334 		/**
2335 		 * Removes the given range from the selection, if the range was one of the ones in the selection.
2336 		 * NOTE: Aloha Editor only support 1 range! The added range will replace the 
2337 		 * range at with index 0
2338 		 * @param range Range removes the range from the selection
2339 		 * @void
2340 		 */
2341 		removeRange: function (range) {
2342 			this._nativeSelection.removeRange();
2343 		},
2344 
2345 		/**
2346 		 * Removes all the ranges in the selection.
2347 		 * @viod
2348 		 */
2349 		removeAllRanges: function () {
2350 			this._nativeSelection.removeAllRanges();
2351 		},
2352 
2353 		/**
2354 		 * INFO: Method is used for integration with Gentics
2355 		 * Aloha, has no use otherwise Updates the rangeObject
2356 		 * according to the current user selection Method is
2357 		 * always called on selection change
2358 		 * 
2359 		 * @param event
2360 		 *            jQuery browser event object
2361 		 * @return true when rangeObject was modified, false
2362 		 *         otherwise
2363 		 * @hide
2364 		 */
2365 		refresh: function (event) {
2366 
2367 		},
2368 
2369 		/**
2370 		 * String representation
2371 		 * 
2372 		 * @return "Aloha.Selection"
2373 		 * @hide
2374 		 */
2375 		toString: function () {
2376 			return 'Aloha.Selection';
2377 		},
2378 
2379 		getRangeCount: function () {
2380 			return this._nativeSelection.rangeCount;
2381 		}
2382 
2383 	});
2384 
2385 	/**
2386 	 * A wrapper for the function of the same name in the rangy core-depdency.
2387 	 * This function should be preferred as it hides the global rangy object.
2388 	 * For more information look at the following sites:
2389 	 * http://html5.org/specs/dom-range.html
2390 	 * @param window optional - specifices the window to get the selection of
2391 	 */
2392 	Aloha.getSelection = function (target) {
2393 		target = (target !== document || target !== window) ? window : target;
2394 		// Aloha.Selection.refresh()
2395 		// implement Aloha Selection 
2396 		// TODO cache
2397 		return new AlohaSelection(window.rangy.getSelection(target));
2398 	};
2399 
2400 	/**
2401 	 * A wrapper for the function of the same name in the rangy core-depdency.
2402 	 * This function should be preferred as it hides the global rangy object.
2403 	 * Please note: when the range object is not needed anymore,
2404 	 *   invoke the detach method on it. It is currently unknown to me why
2405 	 *   this is required, but that's what it says in the rangy specification.
2406 	 * For more information look at the following sites:
2407 	 * http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html
2408 	 * @param document optional - specifies which document to create the range for
2409 	 */
2410 	Aloha.createRange = function (givenWindow) {
2411 		return window.rangy.createRange(givenWindow);
2412 	};
2413 
2414 	var selection = new Selection();
2415 	Aloha.Selection = selection;
2416 
2417 	return selection;
2418 });
2419