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