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