<script type="text/javascript">
tinyMCE.init({
    // General options
    mode : "textareas",
    theme : "advanced",
    plugins : "autolink,lists,spellchecker,pagebreak,style,layer,table,save,advhr,advimage,advlink,emotions,iespell,inlinepopups,insertdatetime,preview,media,searchreplace,print,contextmenu,paste,directionality,fullscreen,noneditable,visualchars,nonbreaking,xhtmlxtras,template,imagemanager,filemanager",
    
    // Theme options
    theme_advanced_buttons1 : "save,newdocument,|,bold,italic,underline,strikethrough,|,justifyleft,justifycenter,justifyright,justifyfull,|,styleselect,formatselect,fontselect,fontsizeselect",
    theme_advanced_buttons2 : "cut,copy,paste,pastetext,pasteword,|,search,replace,|,bullist,numlist,|,outdent,indent,blockquote,|,undo,redo,|,link,unlink,anchor,image,cleanup,help,code,|,insertdate,inserttime,preview,|,forecolor,backcolor",
    theme_advanced_buttons3 : "tablecontrols,|,hr,removeformat,visualaid,|,sub,sup,|,charmap,emotions,iespell,media,advhr,|,print,|,ltr,rtl,|,fullscreen",
    theme_advanced_buttons4 : "insertlayer,moveforward,movebackward,absolute,|,styleprops,spellchecker,|,cite,abbr,acronym,del,ins,attribs,|,visualchars,nonbreaking,template,blockquote,pagebreak,|,insertfile,insertimage",
    theme_advanced_toolbar_location : "top",
    theme_advanced_toolbar_align : "left",
    theme_advanced_statusbar_location : "bottom",
    theme_advanced_resizing : true,
    width: "100%",
    height: "400"
});

// just a place for the custom Formatter.js to be posted:
    CustomFormatter = function(ed) {
		
		//if(DEBUG) console.log('ed.iframe_id:'+ed.iframe_id);
		var iframe_id = (ed.id == 'content_ifr') ? ed.id : ed.id+'_ifr';
		
		// Formate immer aus dem Standardformater uebernehmen
		var formats = ed.formatter.formats;
		var	each = tinymce.each,
			dom = ed.dom,
			selection = ed.selection,
			TreeWalker = tinymce.dom.TreeWalker,
			rangeUtils = new tinymce.dom.RangeUtils(dom),
			isValid = ed.schema.isValidChild,
			isBlock = dom.isBlock,
			forcedRootBlock = ed.settings.forced_root_block,
			nodeIndex = dom.nodeIndex,
			INVISIBLE_CHAR = '\uFEFF',
			MCE_ATTR_RE = /^(src|href|style)$/,
			FALSE = false,
			TRUE = true,
			undefined;

		function getParents(node, selector) {
			return dom.getParents(node, selector, dom.getRoot());
		};

		function isCaretNode(node) {
			return node.nodeType === 1 && (node.face === 'mceinline' || node.style.fontFamily === 'mceinline');
		};

		// Public functions

		/**
		 * Returns the format by name or all formats if no name is specified.
		 *
		 * @method get
		 * @param {String} name Optional name to retrive by.
		 * @return {Array/Object} Array/Object with all registred formats or a specific format.
		 */
		function get(name) {
			return ed.formatter.get(name);
		};
	
		/**
		 * Process a list of nodes and set class-attributes of paragraph(s).
		 */
		function processParagraph(node,format){
			
			// falls nicht mit Cursor selektiert wurde wird der TinyMce-Body als Knoten genommen -> nix tun
			var bodyid = (typeof ed.getParam('body_id') !== "undefined") ? ed.getParam('body_id') : 'tinymce';
			
			if (node.nodeName.toLowerCase() == 'body' && node.id && node.id == bodyid) {
				
				$(node).find('p').each(function (index, domEle) {

					if (!ed.changed && typeof setChanged !== "undefined"){
						setChanged(true, document.getElementById(ed.id));
					}
					
					// falls paragraph als css class zugewiesen, muss sie bleiben
					if ( $(this).hasClass('paragraph') ){
						$(this).get(0).className = format.classes+' paragraph';
					}
					else
						$(this).get(0).className = format.classes;
	
		 		});
				
				return;
			}
			
			//if(DEBUG) console.log("processParagraph started"+node+'|'+node.nodeParent);
			
			// Suche solange nach parentNode bis p-Tag gefunden wurde, setzt class-Attribute
			// durchlaufe Vaterknoten und Prüfe auf p-Tag
			var count = 0; 
			var end = false;
			var nodeParent = node;
			if (node.nodeName.toLowerCase() == 'p'){
				var ptag = node;
			}
			// ermittle p-tag der weiter oben im knoten steht
			else while(!end && count < 20){
				count++;
				var nodeParent = nodeParent.parentNode;
				
				if (DEBUG && node && node.nodeParent) console.log('nodeParent.nodeName.toLowerCase():'+nodeParent.nodeName.toLowerCase()+ 'found');
				
				// Endkriterium 1, p-Tag gefunden
				if (nodeParent.nodeName.toLowerCase() == "p") {
					//merke Knoten!
					var ptag = nodeParent;
					var end = true;
				}
				
				// Endkriterium 2, Parent ist Editorbody
				var bodyid = (typeof ed.getParam('body_id') !== "undefined") ? ed.getParam('body_id') : 'tinymce';
				if (nodeParent.nodeName.toLowerCase() == "body" && nodeParent.id == bodyid){
					end = true;
					if(DEBUG) console.log('No p-Tag found. Not able to set class-attribute on Paragraph!');
					break;
			    }
			}
			
			// setzte Formatierungseigenschaften
			if (typeof ptag != "undefined") {
				if (typeof setChanged !== "undefined"){
					setChanged(true, document.getElementById(ed.id));
				} 
				// falls paragraph als css class zugewiesen, muss sie bleiben
				if (tinymce.inArray(ptag.className.split(' '),'paragraph') !== -1){
					ptag.className = format.classes+' paragraph';
				}
				else
					ptag.className = format.classes;
				
				//if(DEBUG) console.log(ptag.className);
			}
			//else if(DEBUG) console.log('ptag not found');
			return;
		}
			
		/**
		 * Removes all Span-Tags from childNodes recursively and moves the childNodes to the parentNode
		 *
		 * @method removeSpans
		 * @param {Object} node  Node to remove Span-Childs from.
		 */
		function removeSpans(node){
		    for (var i=0; i<node.childNodes.length; i++) {
		        var el = node.childNodes[i];
				//if(DEBUG) console.log(el.nodeName);
		        if (el.nodeName.toLowerCase() == 'span') {
					//if(DEBUG) console.log('span found');
					childNodes = el.childNodes;
					// var f = document.createDocumentFragment();
					// fuer IE7 Elementerzeugung auf dem aktuellen iframe
					
					
					var f = document.getElementById(iframe_id).contentWindow.document.createDocumentFragment();
					while(el.firstChild){
						f.appendChild(el.firstChild);
					}
					el.parentNode.replaceChild(f,el);
				}
		        else if (el.childNodes.length > 0) {
					removeSpans(el);
				}
				else {
					//if(DEBUG) console.log('span not found');
				}
		    }
		}
		
		/**
		 * Removes all Span-Tags from childNodes recursively and moves the childNodes to the parentNode
		 *
		 * @method removeSpans
		 * @param {Object} node  Node to remove Span-Childs from.
		 * @return {bool}	1
		 */
		function checkForSpans(node){
		    for (var i=0; i<node.childNodes.length; i++) {
		        var el = node.childNodes[i];
				//if(DEBUG) console.log(el.nodeName);
		        if (el.nodeName.toLowerCase() == 'span') {
					childNodes = el.childNodes;
					var f = document.getElementById(iframe_id).contentWindow.document.createDocumentFragment();
					while(el.firstChild){
						f.appendChild(el.firstChild);
					}
					el.parentNode.replaceChild(f,el);
				}
		        else if (el.childNodes.length > 0) {
					checkForSpans(el);
				}
				else {
					//if(DEBUG) console.log('span not found');
				}
		    }
		    return 1;
		}
		

		/**
		 * Applies the specified format to the current selection or specified node.
		 *
		 * @method apply
		 * @param {String} name Name of format to apply.
		 * @param {Object} vars Optional list of variables to replace within format before applying it.
		 * @param {Node} node Optional node to apply the format to defaults to current selection.
		 * @param {Array/Object} options Optional Array with Flags, defines f.e. if we are using paragraph formating (access options.paragraphs)
		 */
		function apply(name, vars, node, options) {
			var formatList = ed.formatter.get(name);
			var format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed();
			//if(DEBUG)console.log(options+'|'+options.length+'|'+options.paragraphs+'|'+options.calldirect);
			
			// call processParagraph directly with node
			if (options && options.paragraphs && options.calldirect){
				processParagraph(node,format);
				return;
			}

			/**
			 * Moves the start to the first suitable text node.
			 */
			function moveStart(rng) {
				var container = rng.startContainer,
					offset = rng.startOffset,
					walker, node;

				// Move startContainer/startOffset in to a suitable node
				if (container.nodeType == 1 || container.nodeValue === "") {
					walker = new TreeWalker(container.childNodes[offset]);
					for (node = walker.current(); node; node = walker.next()) {
						if (node.nodeType == 3 && !isBlock(node.parentNode) && !isWhiteSpaceNode(node)) {
							rng.setStart(node, 0);
							break;
						}
					}
				}

				return rng;
			};

			function setElementFormat(elm, fmt) {
				fmt = fmt || format;

				if (elm) {
					if (fmt.onformat) {
						fmt.onformat(elm, fmt, vars, node);
					}

					each(fmt.styles, function(value, name) {
						dom.setStyle(elm, name, replaceVars(value, vars));
					});

					each(fmt.attributes, function(value, name) {
						dom.setAttrib(elm, name, replaceVars(value, vars));
					});

					each(fmt.classes, function(value) {
						value = replaceVars(value, vars);

						if (!dom.hasClass(elm, value))
							dom.addClass(elm, value);
					});
				}
			};
			function adjustSelectionToVisibleSelection() {
				function findSelectionEnd(start, end) {
					var walker = new TreeWalker(end);
					for (node = walker.current(); node; node = walker.prev()) {
						if (node.childNodes.length > 1 || node == start) {
							return node;
						}
					}
				};

				// Adjust selection so that a end container with a end offset of zero is not included in the selection
				// as this isn't visible to the user.
				var rng = ed.selection.getRng();
				var start = rng.startContainer;
				var end = rng.endContainer;

				if (start != end && rng.endOffset == 0) {
					var newEnd = findSelectionEnd(start, end);
					var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length;

					rng.setEnd(newEnd, endOffset);
				}

				return rng;
			}
			
			function applyRngStyle(rng, bookmark, node_specific) {
				var newWrappers = [], wrapName = 'span', wrapElm;
			    var processstop = FALSE;
				var siblings_collections = [];
				
				// Setup wrapper element
				wrapElm = dom.create(wrapName);
				setElementFormat(wrapElm);
	
	
				/**
				 * Process a list of nodes wrap them.
				 */
				function process(node,nodes) {
					var currentWrapElm;
					
					// Beende Funktion process?
					if (processstop) return;
					
					if(DEBUG) console.log('process(node) started! : '+node+'('+node.nodeValue+')');
					
					if (node.parentNode == null) return;
				    var nodeName = node.nodeName.toLowerCase(), parentName = node.parentNode.nodeName.toLowerCase(), found;
				    
					// Stop on empty textnodes
					if (node.nodeType == 3 && node.nodeValue == 0) return;
					
					// Stop on bookmark nodes
					if (isWhiteSpaceNode(node) || isBookmarkNode(node)) return;
					
					// Stop wrapping on br elements
					if (isEq(nodeName, 'br')) {
						currentWrapElm = 0;
	
						// Remove any br elements when we wrap things
						if (format.block)
							dom.remove(node);
	
						return;
					}

					if(DEBUG) console.log('NODE:'+node,',',node.nodeName, wrapName, parentName);
					
					// Is it valid to wrap this item
					// TODO evtl. verschiedene Elmente als Valid configurieren, damit die Schleife abgearbeitet wird (z.B. wrapName + parentName = span)
					// Aktuell immer true, da wir geschachtelte Spans an anderer Stelle vermeiden, die Abarbeitung aber hier stattfinden muss.
					// Es mag aber Fälle geben, wo dies zu Problemen führt. Zu beobachten!!
					if ( ( /*( wrapName == 'span' && parentName == 'span' && nodeName== '#text') ||*/ true || isValid(wrapName, nodeName) && isValid(parentName, wrapName)) &&
								!(node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279)&& node.id !== '_mce_caret' ) {
													
						// Start wrapping
						if (!currentWrapElm) {
							// Wrap the node
							currentWrapElm = wrapElm.cloneNode(FALSE);
							
							nodex = node;
							spannode = end = FALSE;
							
							if(DEBUG) console.log('NODEX:'+nodex);
							
							if (nodeName !== "#text" && nodeName !== wrapName) {
								// Suche Span-Tags und entferne diese, umhängen der Kinder an parentNode
								removeSpans(node);
							}
							
							if (nodeName == wrapName) {
								spannode = node;
							}
							// durchlaufe Vaterknoten und Suche Span-Tag (spannode)
							else while(!end  && nodex){
								var nodex = nodex.parentNode;
								
								if (nodex.nodeName.toLowerCase() == wrapName && nodex.className) {
									//merke Knoten!
									spannode = nodex;
									end = nodex.className;
								}
								
								// Endkriterium, Parent ist Editorbody
								var bodyid = (typeof ed.getParam('body_id') !== "undefined") ? ed.getParam('body_id') : 'tinymce';
								if (nodex.nodeName.toLowerCase() == "body" && nodex.id == bodyid){
									end = nodex.className;
									break;
							    }
							}
							
							if (spannode == node){
								if(DEBUG) console.log('spannode == node');

								// Falls kein class-Attribut vergeben wird Span entfernen
								if (!format.classes[0] || format.classes[0] == ''){
									var childNodes = node.childNodes;
									var f = document.getElementById(iframe_id).contentWindow.document.createDocumentFragment();
									while (node.firstChild) {
										//console.log('CCC:'+node.firstChild.nodeValue);
										f.appendChild(node.firstChild);
										//newWrappers.push(node.firstChild);
									}
									newWrappers.push(node.parentNode);
									node.parentNode.replaceChild(f,node);
								}
								else {
									node.className = '';
									setElementFormat(node);
									newWrappers.push(node);
								}
							}
							else if (spannode) {
	
								// Ermittle alle Textknoten unterhalb Spannode
								//
								// Falls Textknoten in Range: mit neuem Span-Tag + zu setzendem Class-Attribut umschliessen
								// Andernfalls: mit Span-Tag + Class-Attribut von Spannnode umschliessen
								//
								// Am Ende Spannode entfernen
								
								if(DEBUG) console.log('spannode gefunden'+format.classes.length+' class:'+spannode.className+' id:'+spannode.id);
								
								// Fall spannode hat gleiches Class-Attribut wie das zu setzende: nichts ist zu tun
							    for (i=0; i<format.classes.length; i++){
								   	if(format.classes[i]==spannode.className){
										if(DEBUG) console.log("Classname of first parent-span found matches span to be set ("+spannode.className+") -> nothing to do");
										currentWrapElm = 0;
										return;
								    }
								}

								textnodes = getTextNodesArray(tinymce,spannode);
								
								// ermittle Kindknoten, die nicht Textknoten sind
								all_nodes = [];
								nottextnodes = [];
								each(nodes, function (node_elem){
									if (node_elem.nodeType !== 3){
										nottextnodes.push(node_elem);
									} ;
							    });
								
								// ermittle Textknoten der Kindknoten, die nicht Textknoten sind
								each(nottextnodes, function (nottextnode){
									textnodes_ntn = getTextNodesArray(tinymce,nottextnode);
									all_nodes = textnodes_ntn.concat(all_nodes);
							    });
								all_nodes = nodes.concat(all_nodes);
								
								// für jeden Textknoten prüfen, wie er eingepackt werden muss + einpacken
								each(textnodes, function (textnode){

									elem = dom.create(wrapName);
									if (contains(all_nodes,textnode) == -1){
										// Andernfalls: mit Span-Tag + Class-Attribut von Spannnode umschliessen
										elem.className = spannode.className;
										textnode.parentNode.insertBefore(elem, textnode);
										elem.appendChild(textnode);
									}
									else {
										//Falls Textknoten in Range:
										
										// Falls kein class-Attribut vergeben keinen Span erzeugen, Knoten bleibt bestehen bzw. Text bleibt erhalten
										if (!format.classes[0] || format.classes[0] == '') {

											textnode.parentNode.insertBefore(textnode, textnode);
										}
										// sonst mit neuem Span-Tag + zu setzendem Class-Attribut umschliessen
										else {
											setElementFormat(elem);
											textnode.parentNode.insertBefore(elem, textnode);
											elem.appendChild(textnode);
										}
									}

									newWrappers.push(elem);
							    });

								// entferne spannode und füge Kinder an spannode.parentNode
								var childNodes = spannode.childNodes;
								var f = document.getElementById(iframe_id).contentWindow.document.createDocumentFragment();
								while(spannode.firstChild){
									f.appendChild(spannode.firstChild);
								}
								spannode.parentNode.replaceChild(f,spannode);
								
								//processstop = TRUE;	
							}
							// spannode not found
							else {
								if(DEBUG) console.log('spannode nicht gefunden');
								
								// Falls kein class-Attribut vergeben wird aufhoeren
								if (!format.classes[0] || format.classes[0] == '') {
									return;
								}
									
								// Falls node P-Tag: entferne alle tiefer liegenden Span-Tags, packe alle Kinder von P in Span-Tags ein
								if (node.nodeName.toLowerCase() == 'p') {
									removeSpans(node);
									var childNodes = node.childNodes;
									var f = document.getElementById(iframe_id).contentWindow.document.createDocumentFragment();
									while (node.firstChild) {
										elem = dom.create(wrapName);
										setElementFormat(elem);
										//elem.id="p_tag_Child";
										elem.appendChild(node.firstChild);
										newWrappers.push(elem);
										f.appendChild(elem);
									}
									node.appendChild(f);
								}
								// Ansonsten: packe Knoten komplett in Span-Tag ein
								else {
									node.parentNode.insertBefore(currentWrapElm, node);
									currentWrapElm.appendChild(node);
									newWrappers.push(currentWrapElm);
								}
							}
						}
					} else if (nodeName == 'li' && bookmark) {
							// Start wrapping - if we are in a list node and have a bookmark, then we will always begin by wrapping in a new element.
							currentWrapElm = applyStyleToList(node, bookmark, wrapElm, newWrappers, process);
					} else {
						// Start a new wrapper for possible children
						currentWrapElm = 0;
	
						if(DEBUG) console.log('Start a new wrapper for possible children');
						
						// TODO Prüfe, ob hier Spans geschachtelt werden
						// evtl. removeSpans aufrufen!!!
						//removeSpans(node);
						
						if(DEBUG) console.log('element may not be wrapped');
						checkForSpans(node);
						
						//each(tinymce.grep(node.childNodes), process);
						nodes_x = tinymce.grep(node.childNodes)
						for (n=0, l = nodes_x.length; n < l; n++) {
							process(nodes_x[n],nodes_x);
						}
						// End the last wrapper
						currentWrapElm = 0;
					}
				}
				
				
				/**
				 * Speichert das Siblings-Array nodes im siblings_collections-Array.
				 * Auf dieses wird später zur Bearbeitung zugegriffen.
				 * 
				 * @method callback_nodes
				 * @param {Object} nodes
				 */
				function callback_nodes(nodes) {
					var currentWrapElm;
					siblings_collections.push(nodes);
				};
				
				// wandere Range ab und ermittle alle Knoten
				rangeUtils.walk(rng, callback_nodes);

				// Führe die Funktion process für jeden Knoten des übergebenen Arrays nodes aus
				each(siblings_collections,function(nodes){
					var currentWrapElm;

					for (n=0, l = nodes.length; n < l; n++) {
						//if(DEBUG) console.log("NR:"+nodes.length+'|'+n);
						if (options && options.paragraphs) {
							if(DEBUG) console.log('processParagraph called.');
							processParagraph(nodes[n],format);
						}
						else {
							process(nodes[n], nodes);
						}
						//if(DEBUG) console.log("NR2:"+nodes.length+'|'+n);
					}
				});				
				
				// Objekt (zu welchem der Editorinhalt gehört) darf nun gespeichert werden
				if (typeof setChanged !== "undefined"){
					setChanged(true, document.getElementById(ed.id));
				} 
				
				// IR-Comment: nötig????
				// Wrap links inside as well, for example color inside a link when the wrapper is around the link
				if (format.wrap_links === false) {
					each(newWrappers, function(node) {
						function process(node) {
							var i, currentWrapElm, children;

							if (node.nodeName === 'A') {
								currentWrapElm = wrapElm.cloneNode(FALSE);
								newWrappers.push(currentWrapElm);

								children = tinymce.grep(node.childNodes);
								for (i = 0; i < children.length; i++)
									currentWrapElm.appendChild(children[i]);

								node.appendChild(currentWrapElm);
							}

							each(tinymce.grep(node.childNodes), process);
						};

						process(node);
					});
				}

				// Post-Processing der in newWrappers gespeicherten, bearbeiteten Knoten
				each(newWrappers, function(node) {
					var childCount;

					function getChildCount(node) {
						var count = 0;

						each(node.childNodes, function(node) {
							if (!isWhiteSpaceNode(node) && !isBookmarkNode(node))
								count++;
						});

						return count;
					};

					function mergeStyles(node) {
						var child, clone;

						each(node.childNodes, function(node) {
							if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
								child = node;
								return FALSE; // break loop
							}
						});

						// If child was found and of the same type as the current node
						if (child && matchName(child, format)) {
							clone = child.cloneNode(FALSE);
							setElementFormat(clone);

							dom.replace(clone, node, TRUE);
							dom.remove(child, 1);
						}

						return clone || node;
					};

					childCount = getChildCount(node);

					// Remove empty nodes but only if there is multiple wrappers and they are not block
					// elements so never remove single <h1></h1> since that would remove the currrent empty block element where the caret is at
					if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) {
						dom.remove(node, 1);
						return;
					}

					if (format.inline || format.wrapper) {
						// Merges the current node with it's children of similar type to reduce the number of elements
						if (!format.exact && childCount === 1)
							//mergen ausgeschaltet, da nur ein Class-Attribut je HTML-Element (P, SPAN)
							//node = mergeStyles(node);

						// Remove/merge children
						each(formatList, function(format) {
							// Merge all children of similar type will move styles from child to parent
							// this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
							// will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
							each(dom.select(format.inline, node), function(child) {
								var parent;

								// When wrap_links is set to false we don't want
								// to remove the format on children within links
								if (format.wrap_links === false) {
									parent = child.parentNode;

									do {
										if (parent.nodeName === 'A')
											return;
									} while (parent = parent.parentNode);
								}

								removeFormat(format, vars, child, format.exact ? child : null);
							});
						});

						// Look for parent with similar style format
						dom.getParent(node.parentNode, function(parent) {
							if (ed.formatter.matchNode(parent, name, vars)) {
								dom.remove(node, 1);
								node = 0;
								return TRUE;
							}
						});

						// Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
						if (node) {
							node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
							node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
						}
					}
				});
			};
			
				
			if (format) {
				// Standardfall
				if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
					// Obtain selection node before selection is unselected by applyRngStyle()
					var curSelNode = ed.selection.getNode();

					// Apply formatting to selection
					ed.selection.setRng(adjustSelectionToVisibleSelection());
					bookmark = selection.getBookmark();
					applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark);

					// Colored nodes should be underlined so that the color of the underline matches the text color.
					if (format.styles && (format.styles.color || format.styles.textDecoration)) {
						tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes');
						processUnderlineAndColor(curSelNode);
					}
				} else
					performCaretAction('apply', name, vars);
				
				// Bookmarks explizit entfernen, da obiger Code (tinymce update) die Bookmarks belässt
				$(ed.getBody()).find('span[data-mce-type=bookmark]').remove();
			}	
		};

		/**
		 * Removes the specified format from the current selection or specified node.
		 *
		 * @method remove
		 * @param {String} name Name of format to remove.
		 * @param {Object} vars Optional list of variables to replace within format before removing it.
		 * @param {Node} node Optional node to remove the format from defaults to current selection.
		 */
		function remove(name, vars, node) {
			var formatList = get(name), format = formatList[0], bookmark, i, rng;

			// Merges the styles for each node
			function process(node) {
				var children, i, l;

				// Grab the children first since the nodelist might be changed
				children = tinymce.grep(node.childNodes);

				// Process current node
				for (i = 0, l = formatList.length; i < l; i++) {
					if (removeFormat(formatList[i], vars, node, node))
						break;
				}

				// Process the children
				if (format.deep) {
					for (i = 0, l = children.length; i < l; i++){
						process(children[i]);
					}
				}
			};

			// Ab hier unveränderte Funktionen, die aber nicht auf den Standardformatter verweisen können,
			// da dort die Methoden nicht public sind

			function findFormatRoot(container) {
				var formatRoot;

				// Find format root
				each(getParents(container.parentNode).reverse(), function(parent) {
					var format;

					// Find format root element
					if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
						// Is the node matching the format we are looking for
						format = matchNode(parent, name, vars);
						if (format && format.split !== false)
							formatRoot = parent;
					}
				});

				return formatRoot;
			};

			function wrapAndSplit(format_root, container, target, split) {
				var parent, clone, lastClone, firstClone, i, formatRootParent;

				// Format root found then clone formats and split it
				if (format_root) {
					formatRootParent = format_root.parentNode;

					for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
						clone = parent.cloneNode(FALSE);

						for (i = 0; i < formatList.length; i++) {
							if (removeFormat(formatList[i], vars, clone, clone)) {
								clone = 0;
								break;
							}
						}

						// Build wrapper node
						if (clone) {
							if (lastClone)
								clone.appendChild(lastClone);

							if (!firstClone)
								firstClone = clone;

							lastClone = clone;
						}
					}

					// Never split block elements if the format is mixed
					if (split && (!format.mixed || !isBlock(format_root)))
						container = dom.split(format_root, container);

					// Wrap container in cloned formats
					if (lastClone) {
						target.parentNode.insertBefore(lastClone, target);
						firstClone.appendChild(target);
					}
				}

				return container;
			};

			function splitToFormatRoot(container) {
				return wrapAndSplit(findFormatRoot(container), container, container, true);
			};

			// Handle node
			if (node) {
				if (node.nodeType) {
					rng = dom.createRng();
					rng.setStartBefore(node);
					rng.setEndAfter(node);
					removeRngStyle(rng);
				} else {
					removeRngStyle(node);
				}

				return;
			}

			if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
				bookmark = selection.getBookmark();
				removeRngStyle(selection.getRng(TRUE));
				selection.moveToBookmark(bookmark);

				// Check if start element still has formatting then we are at: "<b>text|</b>text" and need to move the start into the next text node
				if (format.inline && match(name, vars, selection.getStart())) {
					moveStart(selection.getRng(true));
				}

				ed.nodeChanged();
			} else
				performCaretAction('remove', name, vars);

			// When you remove formatting from a table cell in WebKit (cell, not the contents of a cell) there is a rendering issue with column width
			if (tinymce.isWebKit) {
				ed.execCommand('mceCleanup');
			}
		};

		/**
		 * Toggles the specifed format on/off.
		 *
		 * @method toggle
		 * @param {String} name Name of format to apply/remove.
		 * @param {Object} vars Optional list of variables to replace within format before applying/removing it.
		 * @param {Node} node Optional node to apply the format to or remove from. Defaults to current selection.
		 */
		function toggle(name, vars, node) {
			var fmt = get(name);

			if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0]['toggle']))
				remove(name, vars, node);
			else
				apply(name, vars, node);
		};


		/**
		 * Returns true/false if the specified format can be applied to the current selection or not. It will currently only check the state for selector formats, it returns true on all other format types.
		 *
		 * @method canApply
		 * @param {String} name Name of format to check.
		 * @return {boolean} true/false if the specified format can be applied to the current selection/node.
		 */
		function canApply(name) {
			var formatList = ed.formatter.get(name), startNode, parents, i, x, selector;

			if (formatList) {
				startNode = selection.getStart();
				parents = getParents(startNode);

				for (x = formatList.length - 1; x >= 0; x--) {
					selector = formatList[x].selector;

					// Format is not selector based, then always return TRUE
					if (!selector)
						return TRUE;

					for (i = parents.length - 1; i >= 0; i--) {
						if (dom.is(parents[i], selector))
							return TRUE;
					}
				}
			}

			return FALSE;
		};

		// Expose to public
		tinymce.extend(this, {
			apply : apply,
			toggle : toggle,
			canApply : canApply
		});

		// Private functions

		/**
		 * Checks if the specified nodes name matches the format inline/block or selector.
		 *
		 * @private
		 * @param {Node} node Node to match agains the specified format.
		 * @param {Object} format Format object o match with.
		 * @return {boolean} true/false if the format matches.
		 */
		function matchName(node, format) {
			// Check for inline match
			if (isEq(node, format.inline))
				return TRUE;

			// Check for block match
			if (isEq(node, format.block))
				return TRUE;

			// Check for selector match
			if (format.selector)
				return dom.is(node, format.selector);
		};

		/**
		 * Compares two string/nodes regardless of their case.
		 *
		 * @private
		 * @param {String/Node} Node or string to compare.
		 * @param {String/Node} Node or string to compare.
		 * @return {boolean} True/false if they match.
		 */
		function isEq(str1, str2) {
			str1 = str1 || '';
			str2 = str2 || '';

			str1 = str1.nodeName || str1;
			str2 = str2.nodeName || str2;

			return str1.toLowerCase() == str2.toLowerCase();
		};

		/**
		 * Returns the style by name on the specified node. This method modifies the style
		 * contents to make it more easy to match. This will resolve a few browser issues.
		 *
		 * @private
		 * @param {Node} node to get style from.
		 * @param {String} name Style name to get.
		 * @return {String} Style item value.
		 */
		function getStyle(node, name) {
			var styleVal = dom.getStyle(node, name);

			// Force the format to hex
			if (name == 'color' || name == 'backgroundColor')
				styleVal = dom.toHex(styleVal);

			// Opera will return bold as 700
			if (name == 'fontWeight' && styleVal == 700)
				styleVal = 'bold';

			return '' + styleVal;
		};

		/**
		 * Replaces variables in the value. The variable format is %var.
		 *
		 * @private
		 * @param {String} value Value to replace variables in.
		 * @param {Object} vars Name/value array with variables to replace.
		 * @return {String} New value with replaced variables.
		 */
		function replaceVars(value, vars) {
			if (typeof(value) != "string")
				value = value(vars);
			else if (vars) {
				value = value.replace(/%(\w+)/g, function(str, name) {
					return vars[name] || str;
				});
			}

			return value;
		};

		function isWhiteSpaceNode(node) {
			return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue);
		};

		function wrap(node, name, attrs) {
			var wrapper = dom.create(name, attrs);

			node.parentNode.insertBefore(wrapper, node);
			wrapper.appendChild(node);

			return wrapper;
		};

		/**
		 * Expands the specified range like object to depending on format.
		 *
		 * For example on block formats it will move the start/end position
		 * to the beginning of the current block.
		 *
		 * @private
		 * @param {Object} rng Range like object.
		 * @param {Array} formats Array with formats to expand by.
		 * @return {Object} Expanded range like object.
		 */
		function expandRng(rng, format, remove) {
			var startContainer = rng.startContainer,
				startOffset = rng.startOffset,
				endContainer = rng.endContainer,
				endOffset = rng.endOffset, sibling, lastIdx, leaf, endPoint;

			// This function walks up the tree if there is no siblings before/after the node
			function findParentContainer(start) {
				var container, parent, child, sibling, siblingName;

				container = parent = start ? startContainer : endContainer;
				siblingName = start ? 'previousSibling' : 'nextSibling';
				root = dom.getRoot();

				// If it's a text node and the offset is inside the text
				if (container.nodeType == 3 && !isWhiteSpaceNode(container)) {
					if (start ? startOffset > 0 : endOffset < container.nodeValue.length) {
						return container;
					}
				}

				for (;;) {
					// Stop expanding on block elements or root depending on format
					if (parent == root || (!format[0].block_expand && isBlock(parent)))
						return parent;

					// Walk left/right
					for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) {
						if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling)) {
							return parent;
						}
					}

					// Check if we can move up are we at root level or body level
					parent = parent.parentNode;
				}

				return container;
			};
			
			// This function walks down the tree to find the leaf at the selection.
			// The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node.
			function findLeaf(node, offset) {
				if (offset === undefined)
					offset = node.nodeType === 3 ? node.length : node.childNodes.length;
				while (node && node.hasChildNodes()) {
					node = node.childNodes[offset];
					if (node)
						offset = node.nodeType === 3 ? node.length : node.childNodes.length;
				}
				return { node: node, offset: offset };
			}

			// If index based start position then resolve it
			if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
				lastIdx = startContainer.childNodes.length - 1;
				startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];

				if (startContainer.nodeType == 3)
					startOffset = 0;
			}

			// If index based end position then resolve it
			if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
				lastIdx = endContainer.childNodes.length - 1;
				endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];

				if (endContainer.nodeType == 3)
					endOffset = endContainer.nodeValue.length;
			}

			// Exclude bookmark nodes if possible
			if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) {
				startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode;
				startContainer = startContainer.nextSibling || startContainer;

				if (startContainer.nodeType == 3)
					startOffset = 0;
			}

			if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) {
				endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode;
				endContainer = endContainer.previousSibling || endContainer;

				if (endContainer.nodeType == 3)
					endOffset = endContainer.length;
			}

			if (format[0].inline) {
				if (rng.collapsed) {
					function findWordEndPoint(container, offset, start) {
						var walker, node, pos, lastTextNode;

						function findSpace(node, offset) {
							var pos, pos2, str = node.nodeValue;

							if (typeof(offset) == "undefined") {
								offset = start ? str.length : 0;
							}

							if (start) {
								pos = str.lastIndexOf(' ', offset);
								pos2 = str.lastIndexOf('\u00a0', offset);
								pos = pos > pos2 ? pos : pos2;

								// Include the space on remove to avoid tag soup
								if (pos !== -1 && !remove) {
									pos++;
								}
							} else {
								pos = str.indexOf(' ', offset);
								pos2 = str.indexOf('\u00a0', offset);
								pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2;
							}

							return pos;
						};

						if (container.nodeType === 3) {
							pos = findSpace(container, offset);

							if (pos !== -1) {
								return {container : container, offset : pos};
							}

							lastTextNode = container;
						}

						// Walk the nodes inside the block
						walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody());
						while (node = walker[start ? 'prev' : 'next']()) {
							if (node.nodeType === 3) {
								lastTextNode = node;
								pos = findSpace(node);

								if (pos !== -1) {
									return {container : node, offset : pos};
								}
							} else if (isBlock(node)) {
								break;
							}
						}

						if (lastTextNode) {
							if (start) {
								offset = 0;
							} else {
								offset = lastTextNode.length;
							}

							return {container: lastTextNode, offset: offset};
						}
					}

					// Expand left to closest word boundery
					endPoint = findWordEndPoint(startContainer, startOffset, true);
					if (endPoint) {
						startContainer = endPoint.container;
						startOffset = endPoint.offset;
					}

					// Expand right to closest word boundery
					endPoint = findWordEndPoint(endContainer, endOffset);
					if (endPoint) {
						endContainer = endPoint.container;
						endOffset = endPoint.offset;
					}
				}

				// Avoid applying formatting to a trailing space.
				leaf = findLeaf(endContainer, endOffset);
				if (leaf.node) {
					while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling)
						leaf = findLeaf(leaf.node.previousSibling);

					if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 &&
							leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') {

						if (leaf.offset > 1) {
							endContainer = leaf.node;
							endContainer.splitText(leaf.offset - 1);
						} else if (leaf.node.previousSibling) {
							// TODO: Figure out why this is in here
							//endContainer = leaf.node.previousSibling;
						}
					}
				}
			}

			// Move start/end point up the tree if the leaves are sharp and if we are in different containers
			// Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
			// This will reduce the number of wrapper elements that needs to be created
			// Move start point up the tree
			if (format[0].inline || format[0].block_expand) {
				if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) {
					startContainer = findParentContainer(true);
				}

				if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) {
					endContainer = findParentContainer();
				}
			}

			// Expand start/end container to matching selector
			if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
				function findSelectorEndPoint(container, sibling_name) {
					var parents, i, y, curFormat;

					if (container.nodeType == 3 && container.nodeValue.length == 0 && container[sibling_name])
						container = container[sibling_name];

					parents = getParents(container);
					for (i = 0; i < parents.length; i++) {
						for (y = 0; y < format.length; y++) {
							curFormat = format[y];

							// If collapsed state is set then skip formats that doesn't match that
							if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed)
								continue;

							if (dom.is(parents[i], curFormat.selector))
								return parents[i];
						}
					}

					return container;
				};

				// Find new startContainer/endContainer if there is better one
				startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
				endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
			}

			// Expand start/end container to matching block element or text node
			if (format[0].block || format[0].selector) {
				function findBlockEndPoint(container, sibling_name, sibling_name2) {
					var node;

					// Expand to block of similar type
					if (!format[0].wrapper)
						node = dom.getParent(container, format[0].block);

					// Expand to first wrappable block element or any block element
					if (!node)
						node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock);

					// Exclude inner lists from wrapping
					if (node && format[0].wrapper)
						node = getParents(node, 'ul,ol').reverse()[0] || node;

					// Didn't find a block element look for first/last wrappable element
					if (!node) {
						node = container;

						while (node[sibling_name] && !isBlock(node[sibling_name])) {
							node = node[sibling_name];

							// Break on BR but include it will be removed later on
							// we can't remove it now since we need to check if it can be wrapped
							if (isEq(node, 'br'))
								break;
						}
					}

					return node || container;
				};

				// Find new startContainer/endContainer if there is better one
				startContainer = findBlockEndPoint(startContainer, 'previousSibling');
				endContainer = findBlockEndPoint(endContainer, 'nextSibling');

				// Non block element then try to expand up the leaf
				if (format[0].block) {
					if (!isBlock(startContainer))
						startContainer = findParentContainer(true);

					if (!isBlock(endContainer))
						endContainer = findParentContainer();
				}
			}

			// Setup index for startContainer
			if (startContainer.nodeType == 1) {
				startOffset = nodeIndex(startContainer);
				startContainer = startContainer.parentNode;
			}

			// Setup index for endContainer
			if (endContainer.nodeType == 1) {
				endOffset = nodeIndex(endContainer) + 1;
				endContainer = endContainer.parentNode;
			}

			// Return new range like object
			return {
				startContainer : startContainer,
				startOffset : startOffset,
				endContainer : endContainer,
				endOffset : endOffset
			};
		}

		/**
		 * Removes the specified format for the specified node. It will also remove the node if it doesn't have
		 * any attributes if the format specifies it to do so.
		 *
		 * @private
		 * @param {Object} format Format object with items to remove from node.
		 * @param {Object} vars Name/value object with variables to apply to format.
		 * @param {Node} node Node to remove the format styles on.
		 * @param {Node} compare_node Optional compare node, if specidied the styles will be compared to that node.
		 * @return {Boolean} True/false if the node was removed or not.
		 */
		function removeFormat(format, vars, node, compare_node) {
			var i, attrs, stylesModified;

			// Check if node matches format
			if (!matchName(node, format))
				return FALSE;

			// Should we compare with format attribs and styles
			if (format.remove != 'all') {
				// Remove styles
				each(format.styles, function(value, name) {
					value = replaceVars(value, vars);

					// Indexed array
					if (typeof(name) === 'number') {
						name = value;
						compare_node = 0;
					}

					if (!compare_node || isEq(getStyle(compare_node, name), value))
						dom.setStyle(node, name, '');

					stylesModified = 1;
				});

				// Remove style attribute if it's empty
				if (stylesModified && dom.getAttrib(node, 'style') == '') {
					node.removeAttribute('style');
					node.removeAttribute('data-mce-style');
				}

				// Remove attributes
				each(format.attributes, function(value, name) {
					var valueOut;

					value = replaceVars(value, vars);

					// Indexed array
					if (typeof(name) === 'number') {
						name = value;
						compare_node = 0;
					}

					if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
						// Keep internal classes
						if (name == 'class') {
							value = dom.getAttrib(node, name);
							if (value) {
								// Build new class value where everything is removed except the internal prefixed classes
								valueOut = '';
								each(value.split(/\s+/), function(cls) {
									if (/mce\w+/.test(cls))
										valueOut += (valueOut ? ' ' : '') + cls;
								});

								// We got some internal classes left
								if (valueOut) {
									dom.setAttrib(node, name, valueOut);
									return;
								}
							}
						}

						// IE6 has a bug where the attribute doesn't get removed correctly
						if (name == "class")
							node.removeAttribute('className');

						// Remove mce prefixed attributes
						if (MCE_ATTR_RE.test(name))
							node.removeAttribute('data-mce-' + name);

						node.removeAttribute(name);
					}
				});

				// Remove classes
				each(format.classes, function(value) {
					value = replaceVars(value, vars);

					if (!compare_node || dom.hasClass(compare_node, value))
						dom.removeClass(node, value);
				});

				// Check for non internal attributes
				attrs = dom.getAttribs(node);
				for (i = 0; i < attrs.length; i++) {
					if (attrs[i].nodeName.indexOf('_') !== 0)
						return FALSE;
				}
			}

			// Remove the inline child if it's empty for example <b> or <span>
			if (format.remove != 'none') {
				removeNode(node, format);
				return TRUE;
			}
		};


		/**
		 * Returns the next/previous non whitespace node.
		 *
		 * @private
		 * @param {Node} node Node to start at.
		 * @param {boolean} next (Optional) Include next or previous node defaults to previous.
		 * @param {boolean} inc (Optional) Include the current node in checking. Defaults to false.
		 * @return {Node} Next or previous node or undefined if it wasn't found.
		 */
		function getNonWhiteSpaceSibling(node, next, inc) {
			if (node) {
				next = next ? 'nextSibling' : 'previousSibling';

				for (node = inc ? node : node[next]; node; node = node[next]) {
					if (node.nodeType == 1 || !isWhiteSpaceNode(node))
						return node;
				}
			}
		};

		/**
		 * Checks if the specified node is a bookmark node or not.
		 *
		 * @param {Node} node Node to check if it's a bookmark node or not.
		 * @return {Boolean} true/false if the node is a bookmark node.
		 */
		function isBookmarkNode(node) {
			return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark';
		};

		/**
		 * Merges the next/previous sibling element if they match.
		 *
		 * @private
		 * @param {Node} prev Previous node to compare/merge.
		 * @param {Node} next Next node to compare/merge.
		 * @return {Node} Next node if we didn't merge and prev node if we did.
		 */
		function mergeSiblings(prev, next) {
			var marker, sibling, tmpSibling;

			/**
			 * Compares two nodes and checks if it's attributes and styles matches.
			 * This doesn't compare classes as items since their order is significant.
			 *
			 * @private
			 * @param {Node} node1 First node to compare with.
			 * @param {Node} node2 Secont node to compare with.
			 * @return {boolean} True/false if the nodes are the same or not.
			 */
			function compareElements(node1, node2) {
				// Not the same name
				if (node1.nodeName != node2.nodeName)
					return FALSE;

				/**
				 * Returns all the nodes attributes excluding internal ones, styles and classes.
				 *
				 * @private
				 * @param {Node} node Node to get attributes from.
				 * @return {Object} Name/value object with attributes and attribute values.
				 */
				function getAttribs(node) {
					var attribs = {};

					each(dom.getAttribs(node), function(attr) {
						var name = attr.nodeName.toLowerCase();

						// Don't compare internal attributes or style
						if (name.indexOf('_') !== 0 && name !== 'style')
							attribs[name] = dom.getAttrib(node, name);
					});

					return attribs;
				};

				/**
				 * Compares two objects checks if it's key + value exists in the other one.
				 *
				 * @private
				 * @param {Object} obj1 First object to compare.
				 * @param {Object} obj2 Second object to compare.
				 * @return {boolean} True/false if the objects matches or not.
				 */
				function compareObjects(obj1, obj2) {
					var value, name;

					for (name in obj1) {
						// Obj1 has item obj2 doesn't have
						if (obj1.hasOwnProperty(name)) {
							value = obj2[name];

							// Obj2 doesn't have obj1 item
							if (value === undefined)
								return FALSE;

							// Obj2 item has a different value
							if (obj1[name] != value)
								return FALSE;

							// Delete similar value
							delete obj2[name];
						}
					}

					// Check if obj 2 has something obj 1 doesn't have
					for (name in obj2) {
						// Obj2 has item obj1 doesn't have
						if (obj2.hasOwnProperty(name))
							return FALSE;
					}

					return TRUE;
				};

				// Attribs are not the same
				if (!compareObjects(getAttribs(node1), getAttribs(node2)))
					return FALSE;

				// Styles are not the same
				if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style'))))
					return FALSE;

				return TRUE;
			};

			// Check if next/prev exists and that they are elements
			if (prev && next) {
				function findElementSibling(node, sibling_name) {
					for (sibling = node; sibling; sibling = sibling[sibling_name]) {
						if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0)
							return node;

						if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
							return sibling;
					}

					return node;
				};

				// If previous sibling is empty then jump over it
				prev = findElementSibling(prev, 'previousSibling');
				next = findElementSibling(next, 'nextSibling');

				// Compare next and previous nodes
				if (compareElements(prev, next)) {
					// Append nodes between
					for (sibling = prev.nextSibling; sibling && sibling != next;) {
						tmpSibling = sibling;
						sibling = sibling.nextSibling;
						prev.appendChild(tmpSibling);
					}

					// Remove next node
					dom.remove(next);

					// Move children into prev node
					each(tinymce.grep(next.childNodes), function(node) {
						prev.appendChild(node);
					});

					return prev;
				}
			}

			return next;
		};

		/**
		 * Returns true/false if the specified node is a text block or not.
		 *
		 * @private
		 * @param {Node} node Node to check.
		 * @return {boolean} True/false if the node is a text block.
		 */
		function isTextBlock(name) {
			return /^(h[1-6]|p|div|pre|address)$/.test(name);
		};

		function getContainer(rng, start) {
			var container, offset, lastIdx;

			container = rng[start ? 'startContainer' : 'endContainer'];
			offset = rng[start ? 'startOffset' : 'endOffset'];

			if (container.nodeType == 1) {
				lastIdx = container.childNodes.length - 1;

				if (!start && offset)
					offset--;

				container = container.childNodes[offset > lastIdx ? lastIdx : offset];
			}

			return container;
		};

		function performCaretAction(type, name, vars) {
			var invisibleChar, caretContainerId = '_mce_caret', debug = ed.settings.caret_debug;

			// Setup invisible character use zero width space on Gecko since it doesn't change the heigt of the container
			invisibleChar = tinymce.isGecko ? '\u200B' : INVISIBLE_CHAR;

			// Creates a caret container bogus element
			function createCaretContainer(fill) {
				var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''});

				if (fill) {
					caretContainer.appendChild(ed.getDoc().createTextNode(invisibleChar));
				}

				return caretContainer;
			};

			function isCaretContainerEmpty(node, nodes) {
				while (node) {
					if ((node.nodeType === 3 && node.nodeValue !== invisibleChar) || node.childNodes.length > 1) {
						return false;
					}

					// Collect nodes
					if (nodes && node.nodeType === 1) {
						nodes.push(node);
					}

					node = node.firstChild;
				}

				return true;
			};
			
			// Returns any parent caret container element
			function getParentCaretContainer(node) {
				while (node) {
					if (node.id === caretContainerId) {
						return node;
					}

					node = node.parentNode;
				}
			};

			// Finds the first text node in the specified node
			function findFirstTextNode(node) {
				var walker;

				if (node) {
					walker = new TreeWalker(node, node);

					for (node = walker.current(); node; node = walker.next()) {
						if (node.nodeType === 3) {
							return node;
						}
					}
				}
			};

			// Removes the caret container for the specified node or all on the current document
			function removeCaretContainer(node, move_caret) {
				var child, rng;

				if (!node) {
					node = getParentCaretContainer(selection.getStart());

					if (!node) {
						while (node = dom.get(caretContainerId)) {
							removeCaretContainer(node, false);
						}
					}
				} else {
					rng = selection.getRng(true);

					if (isCaretContainerEmpty(node)) {
						if (move_caret !== false) {
							rng.setStartBefore(node);
							rng.setEndBefore(node);
						}

						dom.remove(node);
					} else {
						child = findFirstTextNode(node);
						child = child.deleteData(0, 1);
						dom.remove(node, 1);
					}

					selection.setRng(rng);
				}
			};
			
			// Applies formatting to the caret postion
			function applyCaretFormat() {
				var rng, caretContainer, textNode, offset, bookmark, container, text;

				rng = selection.getRng(true);
				offset = rng.startOffset;
				container = rng.startContainer;
				text = container.nodeValue;

				caretContainer = getParentCaretContainer(selection.getStart());
				if (caretContainer) {
					textNode = findFirstTextNode(caretContainer);
				}

				// Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character
				if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) {
					// Get bookmark of caret position
					bookmark = selection.getBookmark();

					// Collapse bookmark range (WebKit)
					rng.collapse(true);

					// Expand the range to the closest word and split it at those points
					rng = expandRng(rng, get(name));
					rng = rangeUtils.split(rng);

					// Apply the format to the range
					apply(name, vars, rng);

					// Move selection back to caret position
					selection.moveToBookmark(bookmark);
				} else {
					if (!caretContainer || textNode.nodeValue !== invisibleChar) {
						caretContainer = createCaretContainer(true);
						textNode = caretContainer.firstChild;

						rng.insertNode(caretContainer);
						offset = 1;

						apply(name, vars, caretContainer);
					} else {
						apply(name, vars, caretContainer);
					}

					// Move selection to text node
					selection.setCursorLocation(textNode, offset);
				}
			};

			function removeCaretFormat() {
				var rng = selection.getRng(true), container, offset, bookmark,
					hasContentAfter, node, formatNode, parents = [], i, caretContainer;

				container = rng.startContainer;
				offset = rng.startOffset;
				node = container;

				if (container.nodeType == 3) {
					if (offset != container.nodeValue.length || container.nodeValue === invisibleChar) {
						hasContentAfter = true;
					}

					node = node.parentNode;
				}

				while (node) {
					if (matchNode(node, name, vars)) {
						formatNode = node;
						break;
					}

					if (node.nextSibling) {
						hasContentAfter = true;
					}

					parents.push(node);
					node = node.parentNode;
				}

				// Node doesn't have the specified format
				if (!formatNode) {
					return;
				}

				// Is there contents after the caret then remove the format on the element
				if (hasContentAfter) {
					// Get bookmark of caret position
					bookmark = selection.getBookmark();

					// Collapse bookmark range (WebKit)
					rng.collapse(true);

					// Expand the range to the closest word and split it at those points
					rng = expandRng(rng, get(name), true);
					rng = rangeUtils.split(rng);

					// Remove the format from the range
					remove(name, vars, rng);

					// Move selection back to caret position
					selection.moveToBookmark(bookmark);
				} else {
					caretContainer = createCaretContainer();

					node = caretContainer;
					for (i = parents.length - 1; i >= 0; i--) {
						node.appendChild(parents[i].cloneNode(false));
						node = node.firstChild;
					}

					// Insert invisible character into inner most format element
					node.appendChild(dom.doc.createTextNode(invisibleChar));
					node = node.firstChild;

					// Insert caret container after the formated node
					dom.insertAfter(caretContainer, formatNode);

					// Move selection to text node
					selection.setCursorLocation(node, 1);
				}
			};

			// Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements
			ed.onBeforeGetContent.addToTop(function() {
				var nodes = [], i;

				if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) {
					// Mark children
					i = nodes.length;
					while (i--) {
						dom.setAttrib(nodes[i], 'data-mce-bogus', '1');
					}
				}
			});

			// Remove caret container on mouse up and on key up
			tinymce.each('onMouseUp onKeyUp'.split(' '), function(name) {
				ed[name].addToTop(function() {
					removeCaretContainer();
				});
			});

			// Remove caret container on keydown and it's a backspace, enter or left/right arrow keys
			ed.onKeyDown.addToTop(function(ed, e) {
				var keyCode = e.keyCode;

				if (keyCode == 8 || keyCode == 37 || keyCode == 39) {
					removeCaretContainer(getParentCaretContainer(selection.getStart()));
				}
			});

			// Do apply or remove caret format
			if (type == "apply") {
				applyCaretFormat();
			} else {
				removeCaretFormat();
			}
		};
	};



</script>

<form method="post" action="dump.php">
    <textarea name="content"></textarea>
</form>