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