
wmp.editor.modules = {};

wmp.editor.baseModule = new Class({
legalChilds: [],
illlegalChilds: [],
allowedAttributes: [],
attributes: $H({}),
nodeClass: "",
manipulators: {},
extenders: {},
specificity: 0,
initialize: function(options) {
	this.editor = options.editor;
	this.options = new Hash({});
	this.options.extend(options);
	this.values = {}
	this.nodes = {}
	
	if (this.options.convert)
		this.node = this.convert($(options.node));
	else if (this.options.node)
		this.node = $(options.node);
	else
		this.create();
	(this.extenders=new Hash(this.extenders)).each(function(extender, index){
		for (var n in extender) {
			if (n == "initialize")
				extender.initialize.bind(this)();
			if (n == "manipulators")
				this.manipulators = $merge(extender[n], this.manipulators);
			else if ($type(extender[n]) == 'function')
				this[n] = extender[n];
		}
	}, this);
	
	this.node._module = this;
	this.fix();
	this.setNeededStyles();
},

addExtender: function() {
	
},

create: function() {
	console.log("create", this.title);
	this.node = this.editor.c(this.nodeName);
	if (this.defaultContent)
		this.fixSetDefaultContent();
	return this.node;
},

convert: function(node) {
	console.log("convert to", this.title, "from", node);
	if (node._module) {
		var oldmod = this.editor.getModule(node);
		oldmod.kill();
	}
	return this.node = $(wmp.xml.renameNode(node, this.nodeName));
},

//guiactivateme: function() {},
//guideactivateme: function() {},

guiactivate: function() {
	if (this.active)
		return;
	this.fix();
	this.inputs = {}
	this.menus = {}
	//class-selector
	if (this.classes) {
		this.guicreatemenu();
		for(var n in this.classes) {
			var g = this.classes[n];
			var opt = {
				options: g.names,
				selectedValues:  this.node.className.split(' '),
				onchange: this.setStyles.bind(this)
			}
			if (g.select == "multiple")
				var input = new wmp.gui.multiselect(opt);
			else
				var input = new wmp.gui.select(opt);
			this.inputs["class"+n] = input;

			this.menu.addInput({
				input: input,
				title: g.title
			});
		}
	}
	//other manipulators
	for (n in this.manipulators) {
		this.guicreatemenu();
		var m = this.manipulators[n];
		if (!m)
			continue;
		if (m.type=="item") {
			this.menu.addItem({title:m.title, func:this[m.action].bind(this), icon: m.icon});
			continue;
		}
                /*
		var opts = $H({
			value: this.getproperty((m.property || n)),
			options: this[m.options] || m.options, //selectbox options
			onEnter: this.editor.focus.bind(this.editor),
			onchange: this[m.onchange||"setproperty"].bind(this, (m.property || n))
		}).combine(m).erase("title").getClean();
                */
               var opts = {}
               $extend(opts, m);
               $extend(opts,{
			value: this.getproperty((m.property || n)),
			options: this[m.options] || m.options, //selectbox options
			onEnter: this.editor.focus.bind(this.editor),
			onchange: this[m.onchange||"setproperty"].bind(this, (m.property || n))
		});
                this.inputs[n] = new wmp.gui[m.type||"string"](opts);		
		this.menu.addInput({title:m.title,input: this.inputs[n],closed:m.closed});
	}
	//custom manipulator initialization
	if (this.guiactivateme) {
		this.guicreatemenu();
		this.guiactivateme();
	}
	this.active = true;
},
getproperty: function(pid) {
	if (this["getproperty_" + pid])
		return this["getproperty_" + pid]();
	return this.values[pid] || this.node.getProperty(pid);
},
setproperty: function(pid) {
	var v = this.inputs[pid] ? this.inputs[pid].getValue() : null;
	if (this["setproperty_" + pid])
		return this["setproperty_" + pid](v);
	this.node.setProperty(pid,v);
},


guicreatemenu: function() {
	this.editor.hideHelp();
	if (!this.menu)
		this.menu = this.editor.addMenu({title:this.title, editor: this.editor, top: true});
},

guideactivate: function() {
	if (!this.active)
		return;
	if (this.guideactivateme)
		this.guideactivateme();
	if (this.menu)
		this.menu.remove();
	delete(this.menu); 
	for(var n in this.menus) {
		this.menus[n].remove();
		delete(this.menus[n]); 
	}
	this.active = false;
},

select: function() {
	this.fix();
	this.editor.selectNode(this.nodes.content || this.node);
},

focus: function() {
	this.fix();
	this.editor.selectNode(this.nodes.content || this.node, "start");
},

hasFocus: function() {
	if (this.editor.modules.active && this.editor.modules.active.contains(this))
		return true;
},

kill: function() {
	this.guideactivate();
	this.node.removeEvents();
	this.node._module = null;
	this.node = null;
},

remove: function() {
	if (!this.node)
		return;
	this.node.destroy();
	this.kill();
},

removeButNotTheContent: function() {
	if (this.nodes.content) {
		if (this.nodes.content != this.node)
			this.nodes.content.inject(this.node, 'after')
		wmp.xml.unwrapNode(this.nodes.content);
	}
	this.nodes.content = null;
	this.remove();
},

split: function() {
	var sel = wmp.selection.getInfo(this.editor.doc);
	//if (this.editor.sel.selection!="")
	//	return;
	return wmp.xml.splitNode(sel.range.startContainer, sel.range.startOffset, this.node.parentNode);
},

splitAndConvertToP: function() {
	var next = this.split();
	if ($(next).get('tag')!="p")
		next = wmp.xml.renameNode(next, "p");
	var m = this.editor.getModule(next);
	this.fix();
	m.focus();
},

splitAndInsert: function(moduleName) {
	//if (this.editor.sel.selection!="")
	//	return;
	var newNode = this.split();
	if ($type(moduleName) != "string")
		var moduleName = "p";
	var module = this.editor.createModuleOnly(moduleName);
	module.node.inject(this.node, 'after');
	this.editor.getModule(newNode).removeIfEmpty(true); //remove second part if empty
	this.removeIfEmpty(true); //remove first part if empty
	module.focus();
},

removeIfEmpty: function(force) {
	//removes the node if it has no content. Works recursive on children
	var c,x=0, n=(this.nodes.content||this.node);
	while(c = n.childNodes[x++]) {
		var module = this.editor.getModule(c);
		if (module)
			module.removeIfEmpty(force);
	}
	if (!force && this.canBeEmpty)
		return;
	//console.log("removeempty", this.node, n.getChildren());
	if ((this.legalChilds.contains("inline") || this.type=="inline") && wmp.xml.hasRealContent(n)) {
		return;
	}
	if (/*this.legalChilds.contains("block") && */n.getChildren().length!=0) {
		return;
	}
	this.remove();
	return true;
},

appendPOnEnter: function(event) {
	if (event.key == "enter") {
		event.stop();
		this.editor.createModule("p");
		return false;
	}
	return true;
},


handleKeypress: function(event) {
	return true;
},


rename: function(newname) {
	this.editor.saveSelection();
	console.log(this.title + ": renamed to " + newname);
	this.node._module = null;
	this.node = $(wmp.xml.renameNode(this.node, newname));
	this.node._module = this;
	this.fixme();
	this.editor.restoreSelection(null,this.node);
},
xml2html: null,
exportXml: function() {
	//small fix to prevent empty inline elements. todo: should be removed if empty elements are removed automatically
	if (this.type=="inline" && this.node.innerHTML.trim().length < 1)
		return ' ';
	var xml =  "<" + this.nodeName;
	if (this.node.className.length>2)
		xml += ' class="' + this.node.className + '"';
	xml += this.exportXmlAttributes();
	
	xml += ">";
	xml += this.exportXmlChildren();
	xml += "</" + this.nodeName + ">";
	return xml;
},
exportXmlAttributes: function(allowedAttributes, attributes) {
	var xml="";
	(allowedAttributes||this.allowedAttributes).each(function(attribute){
		var v = this.node.getProperty(attribute);
		if (v)
			xml += ' ' + attribute + '="' + this.xmlEscape(v) + '"';
	}, this);
	$H((attributes||this.attributes)).each(function(obj, name){
		var v = this.node.getProperty(name);
		if ((v && !obj.noexport) || obj.exportempty)
			xml += ' ' + name + '="' + this.xmlEscape(v||"") + '"';
	}, this);
	return xml;
},
xmlEscape: function(str, notquotes) {
	str = str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
	if (!notquotes)
		return str.replace(/\"/g, '&quot;').replace(/\'/g, '&apos;');
	return str;
},
exportXmlChildren: function(node) {
	var x=0, c, xml="";
	while(c = (node||this.node).childNodes[x++]) {
		if ($type(c) != "element") {
			xml += this.xmlEscape(c.nodeValue, true);
		}
		else {
			var module = this.editor.getModule(c);
			if (module)
				xml += module.exportXml();
		}
	}
	if (this.type != "inline")
		return xml.trim();
	else
		return xml.replace(/\s{2,}/g, ' ');//xml.clean(); clean does also trim--> error
},
isChildAllowed: function(module) {
	if (this.illlegalChilds.contains(module.name))
		return false;
	if (this.legalChilds.contains("block") && module.type == "block")
		return true;
	if (this.legalChilds.contains("inline") && module.type == "inline")
		return true;
	if (this.legalChilds.contains(module.name))
		return true;
	return false;
},

isInlineChildAllowed: function(name) {
	//todo: remove this crappy function
	if (this.illlegalChilds.contains(name))
		return false;
	if (this.legalChilds.contains("inline"))
		return true;
	if (this.legalChilds.contains(name))
		return true;
	return false;
},

makeUneditable: function(node, containsStuff) {
	//console.log("makeUneditable", node.contentEditable);
	if (node.contentEditable=="false" || node.contentEditable === false)
		return;
	if (!this.editor.hasContentEditable)
		node.style.MozUserFocus = "ignore";
		//node.style.MozUserSelect = "none";
	node.contentEditable = "false";
	//if (!containsStuff)
	//	node.addEvent("mouseup", this.blockuneditable.bind(this)); //todo remove on kill
	
	//ie: makes the thing only selectable as a whole
	//this.node.setAttribute("ATOMICSELECTION", true);
	//ie
	var m = this;
	/*node.onbeforeeditfocus = function() {
		console.log("onbeforeeditfocus", node);
		var e = m.editor.win.event;
		e.cancelBubble = true;
		e.returnValue = false;
		//m.focus();
		return false;
	}*/
	node.oncontrolselect = function() {
		//controlselect gets fired if i try to resize
		console.log("oncontrolselect", node);
		var e = m.editor.win.event;
		e.cancelBubble = true;
		e.returnValue = false;
		m.focus();
		return false;
	}
},
makeEditable: function(node) {
	if (node.contentEditable=="true" || node.contentEditable === true)
		return;
	//TODO
	if (!this.editor.hasContentEditable)
		node.style.MozUserFocus = "normal";
	node.contentEditable = "true";
},
/*
blockuneditable: function(event) {
	console.log("blockuneditable click");
	event.preventDefault();
	//this.focus();
},*/
/* ------------------------------  styles  ------------------------------*/

setStyles: function() {
	var classes = (this.nodeClass||"").split(" ");
	if (this.html5 && !this.editor.canhtml5)
		classes.push(this.nodeName);
	for(var n in this.classes) {
		classes.include(this.inputs["class"+n].getSelectedValues());
	}
	if (this.otherclasses)
		classes.combine(this.otherclasses);
	this.node.className = classes.join(" ").clean();
	if (this.onSetStyles)
		this.onSetStyles();
},
setNeededStyles: function() {
	var classes = (this.nodeClass||"").split(" ");
	if (this.html5 && !this.editor.canhtml5)
		classes.push(this.nodeName);
	if (this.arbitraryClasses)
		classes.combine(this.node.className.split(" "));
	for(var n in this.classes) {
		var preset = this.classes[n].preset;
		var ok = false;
		for(var className in this.classes[n].names) {
			if (this.node.hasClass(className)) {
				var ok = true;
				classes.include(className);
			}
		}
		if (!ok && this.classes[n].select != "multiple" && preset) {
			classes.include(preset);
		}
	}
	if (this.otherclasses)
		classes.combine(this.otherclasses);
	classes = classes.join(" ").clean();
	if (classes.length>0) {
		if (this.node.className != classes) {
			this.node.className = classes;
			this.logfix("setNeededStyles: " + classes, this.node);
		}
	}
	else if (this.node.className) {
		this.logfix("setNeededStyles: removeclass ", this.node.className);
		this.node.removeAttribute("class");
	}
},

/* ------------------------------  fix  ------------------------------*/
fix: function() {
	var html;
	while (this.old_innerHTML != (html = this.node.innerHTML)) {
		//console.log("fixing", this.node);
		this.old_innerHTML = html;
		if(this.fixme())
			return true; // returns true if element was removed
		this.fixStripAttributes();
		this.scanForErrors();
	}
},

fixme: function() {
	//should return true if element gets removed
},

build: function(rebuild) {
	this.makeUneditable(this.node, true);
	var s = this.tree;
	for (t in s) {
		var c = s[t];
		if (!c.node) {
			c.node = this.editor.c(c.name||"div");
		}
		if (!c.node.parentNode || rebuild) {
			var rebuild=true; //append all children if one is missing
			if (c.editable)
				this.makeEditable(c.node);
			else
				this.makeUneditable(c.node);
			var props = $H({}).combine(c).erase("name").erase("node").erase("editable").erase("default").getClean();
			c.node.set(props);
			this.node.appendChild(c.node);
		}
		this.nodes[t]=c.node;
	}
},

scanForErrors: function() {
	//this.logfix("scanForErrors");
	var x=0, c, m, node = this.nodes.content || this.node;
	while (c = node.childNodes[x++]) {
		if (m = this.editor.getModule(c)) {
			m.fix();
		}
		else
			this.editor.getModuleConvert(c);
		if (!c || !c.parentNode) //node has been deleted
			break; //x--; x=0;
	}
},
	
/*FIXING----------------------------------------------------------------------*/
logfix: function(name, add) {
	console.log("fix: " + name +  " (in " + this.title + ")", add || "");
},

fixInline: function(node) {
	var n = node || this.nodes.content || this.node
	this.fixRemoveDoubleBrs(n);
	this.fixMergeAdjacentNodes(n);
},

fixStripAttributes: function(node, allowedAttributes) {
	var x=0, a;
	var node = node || this.node;
	if (node._strippedAttributes)
		return;
	node._strippedAttributes=true;
	var exclusions = ["style", "class", "contenteditable", "tabindex"];
	var allowed = allowedAttributes || this.allowedAttributes;
	while (x < node.attributes.length) {
		var a = node.attributes.item(x++);
		var n = a.name.toLowerCase();
		if (n.substring(0,1)!="_" && !exclusions.contains(n) && !allowed.contains(n) && !this.attributes[n]) {
			//in IE, every property is an attribute aaarrrghhh 
			if (Browser.Engine.trident && (a.value == "null" || !node.getAttribute(a.name, 2) || a.value.indexOf("function") != -1 || typeof a.value != "string")) //|| n.substring(0,2) == "on"
				break;
			if (n=="id" && this.editor.options.allowIds)
				break;
			this.logfix("StripAttributes", n + '="' + a.value + '"');
			node.removeAttributeNode(a);
			x--;
		}
	}
	
	//styles
	//todo: remove this and put into classes
	var kept="";
	var rstyles = node.style.cssText || ""; //node.getProperty("style") || "";
	var styles = rstyles.toLowerCase().replace(new RegExp("-moz-user-focus:.{4,8}|user-focus:.{4,8}|position:.?static", "g"), "");
	var keepstyles = {
		userFocus: node.style.userFocus,
		MozUserFocus: node.style.MozUserFocus
	}
	if (styles.length>5) {
		node.removeAttribute("style");
		for (var n in keepstyles) {
			if (keepstyles[n]) {
				kept += n + ":"+keepstyles[n]+";";
				node.style[n] = keepstyles[n];
			}
		}
		this.logfix("fixStyle", "removed '" + styles +  "', kept '" + kept + "'");
	}
},

fixCleanSpaces: function(node) {
	//remove &nbsp; &#160;
	var nodes = node.childNodes;
	var x=0, c;
	while (c = nodes[x++]) {
		if (c.nodeType == 3 && c.nodeValue.test("\u00A0")) {
			this.logfix("CleanSpaces", c.nodeValue);
			c.nodeValue = c.nodeValue.replace(new RegExp("\u00A0", "g"), " ");
			c.nodeValue = c.nodeValue.replace(new RegExp(" +", "g"), " ");
		}
	}
},

fixRemoveIllegal: function(node) {
	var node = node || this.nodes.content || this.node;
	var c = node.firstChild;
	while (c) {
		if (c.className && c.className.test("firebug")) {
			//firebug debugboxes should not be removed
		}
		else if (c.nodeType == 1) {
			var module = this.editor.getModule(c);
			if (!module) {
				if (module = this.editor.getModuleConvert(c)) {
					var c = node.firstChild;
					continue;
				}
				this.logfix("RemoveIllegal->unknown", c);
				wmp.xml.unwrapNode(c);
				var c = node.firstChild;
				continue;
			}
			else if (!this.isChildAllowed(module)) {
				this.logfix("RemoveIllegal", module);
				module.removeButNotTheContent();
				var c = node.firstChild;
				continue;
			}
		}
		else if (c.nodeType != 3) {
			this.logfix("RemoveIllegal->no element/text", module);
			c.parentNode.removeChild(c);
			var c = node.firstChild;
			continue;
		}
		var c = c.nextSibling;
	}
},
fixMoveOutIllegal: function(node) {
	var node = node || this.nodes.content || this.node;
	var c = node.lastChild;
	//todo: wenn was rausgemovt wurde, soll die node da gespalten werden!
	//lastChild, because then the  nodes are in the right order if moved out
	while (c) {
		if (c.nodeType == 1) {
			var module = this.editor.getModule(c);
			if (!module) {
				if (module = this.editor.getModuleConvert(c)) {
					var c = node.lastChild;
					continue;
				}
				this.logfix("MoveOutIllegal->unwrap", c);
				wmp.xml.unwrapNode(c);
				var c = node.lastChild;
				continue;
			}
			else if(!this.isChildAllowed(module)) {
				if (module.type!="block" || !this.fixMoveOut(module)) {
					this.logfix("MoveOutIllegal->unwrap2", c);
					module.removeButNotTheContent();
				}
				var c = node.lastChild;
				continue;
			}
		}
		else if (c.nodeType != 3) {
			this.logfix("RemoveIllegal->no element/text", c);
			c.parentNode.removeChild(c);
			var c = node.lastChild;
			continue;
		}
		var c = c.previousSibling;
	}
},

fixMoveOut: function(module) {
	var node = module.node;
	var oldParent = node.parentNode;
	if (!oldParent || !oldParent.parentNode)
		return false;
	var parent = oldParent.parentNode;
	while (parent) {
		var m = this.editor.getModule(parent);
		if (!m || !m.isChildAllowed(module)) {
			var oldParent = parent;
			parent = parent.parentNode;
		}
		else {
			parent.insertBefore(node, oldParent.nextSibling);
			m.old_innerHTML = "fix again";
			this.logfix("MoveOut", node);
			return true;
		}
	}
	return false;
},

fiximportTextToElement: function(from, to) {
	var move_a = [], x=0, c;
	while(c = from.childNodes[x++]) {
		if (c==to)
			var after = true;
		// todo: test this.nodes
		else if ($type(c) != "whitespace" && !$H(this.nodes).hasValue(c)) {
			if (after)
				to.appendChild(c);
			else if (last)
				var last = to.insertBefore(c, last.nextSibling);
			else
				var last = to.insertBefore(c, to.firstChild);
			var moved = true;
			x--;
		}
	}
	if (moved) {
		if (this.hasFocus())
			this.editor.selectNode(to, "start");
		this.logfix("importTextToElement", c);
	}
},

fixCapsuleInline: function(node, capsuleName) {
	var node = node || this.nodes.content || this.node;
	if (!capsuleName)
		var capsuleName = "p";
	var c = node.firstChild;
	while(c) {
		if (!c.parentNode) {
			var c = node.firstChild;
			continue;
		}
		var next = c.nextSibling;
		if ($type(c) == "whitespace") {
			c.parentNode.removeChild(c);
			c = next;
			continue;
		}
		var mod = this.editor.getModule(c);
		if ($type(c) == "textnode" || (mod && mod.type == "inline")) {
			if (!p)
				var p = this.editor.c(capsuleName);
			p.appendChild(c);
		}
		else if (p) {
			if (wmp.xml.hasVisibleContent(p)) {
				var oldp = p;
				node.insertBefore(p, c);
				p = null;
			}
		}
		c = next;
	}
	if (oldp || (p && wmp.xml.hasVisibleContent(p))) {
		this.logfix("CapsuleInline: '"+(p || oldp).innerHTML+"'", (p || oldp));
		if (p && wmp.xml.hasVisibleContent(p))
			node.appendChild(p);
		if (this.hasFocus())
			this.editor.selectNode(p || oldp, "end");
	}
},

fixRemoveDoubleBrs:  function(node) {
	//todo: nicht wasserdicht
	/*
	var brs = node.getElementsBySelector("br");
	var x=0, br;
	while(br = brs[x++]) {
		if (br && br.previousSibling && br.get('tag') == "br" && wmp.xml.nextNode(br, node)) {
			//do not remove if br is at the end of a block
			if (br.getNext() && br.getNext().getNext()) {
				this.logfix("RemoveDoubleBrs");
				br.dispose();
			}
		}
	}*/
	if (this.type=="inline")
		return;
	if (node.firstChild && node.firstChild.nodeName.toLowerCase() == "br" && node.firstChild.nextSibling) {
		//remove brs at the beginning, but do not remove brs if they are the only content
		$(node.firstChild).dispose();
		this.logfix("RemoveDoubleBrs at beginning");
	}
},

fixMergeAdjacentNodes: function(node) {
	var x=0, c;
	//node.normalize();
	var oldc = node.childNodes[x++];
	while(c = node.childNodes[x++]) {
		var xx=0, a, equal = true;
		if (oldc && c.nodeType == 1 && oldc.nodeName == c.nodeName &&
			c.nodeName.toLowerCase() != "br") { 
			while (c.attributes && (xx < c.attributes.length)) {
				var a = c.attributes.item(xx++);
				if (c.getAttribute(a.name) != a.value) {
					var equal = false;
				}
			}
			if (equal) {
				this.logfix("MergeAdjacentNodes", c);
				wmp.xml.moveContent(c, oldc);
				$(c).dispose();
				c = null;
				x--;
			}
		}
		var oldoldc = oldc;
		var oldc = c;
	}
},
fixSetDefaultContent: function(node, c) {
	var node = node || this.nodes.content || this.node;
	if (wmp.xml.hasVisibleContent(node))
		return;
	var c = c || this.defaultContent;
	if (!c && Browser.Engine.trident)
		c = "\u200B"; // ie is fine withzero-width-space: \u200B;
	else if (!c && Browser.Engine.presto)
		c = "\u00A0"; // opera needs non braking space: \u00A0 &nbsp;
	else if (!c) //firefox wants a <br>
		c = "<br/>";
	this.logfix("SetDefaultContent",node); //node.innerHTML
	node.innerHTML = c;
},
fixNl2br: function(node) {
	if (node.innerHTML.test("\n")) {
		node.innerHTML = node.innerHTML.replace(new RegExp("\r\n", "g"), "<br/>").replace(new RegExp("\n", "g"), "<br/>");
		this.logfix("Nl2br");
	}
},

fixCreateAttibute: function(name, value, node) {
	var a = (node || this.node).getAttribute(name);
	if (!a && a!==(value||""))
		(node || this.node).setAttribute(name, value || "");
},

fixFFDoubleClickBug: function(node) {
	//when you doubleclick on a <p> or <h> (outside the text)
	//the whole paragraph is selected, but we just want the text content...
	if (!Browser.Engine.gecko)
		return; //todo
	var node = node || this.nodes.content || this.node;
	if (!node._dblclick_antibug) {
		node.addEvent("dblclick", this.fixFFDoubleClickBug2.bindWithEvent(this))
		node._dblclick_antibug = true;
	}
},
fixFFDoubleClickBug2: function(event) {
	var node = node || this.nodes.content || this.node;
	if (!node.get('text').test("..\\s..")) {
		this.select();
		event.stop();
	}
},
fixAppendWhitespace: function(node) {
	//this function should ease the editing prozess by addind a whitespace 
	//the whitespace allowes you to type behind <b> etc.
	var node = node || this.nodes.content || this.node;
	var appendBefore = ["ul","ol","br"],x=0, ws="\u00A0";
	var els = node.getChildren(appendBefore.join(",")), el;
	//add whitespace before specific elements
	while (el=els[x++]) {
		lc=el.previousSibling;
		while ($type(lc) == "whitespace" && !lc.nodeValue.test(ws)) //ignores empty whitespaces
			lc = lc.previousSibling;
		if (lc && lc.nodeType==1 && !appendBefore.contains(lc.nodeName.toLowerCase())) {
			this.logfix("AppendWhitespace before Element:", this.node);
			node.insertBefore(this.editor.ct(ws),el);
		}
	}
	//add whitespace at end
	lc=node.lastChild;
	while ($type(lc) == "whitespace" && !lc.nodeValue.test(ws))
		lc = lc.previousSibling;
	if (lc && lc.nodeType==1 && !appendBefore.contains(lc.nodeName.toLowerCase())) {
		this.logfix("AppendWhitespace", this.node);
		node.appendChild(this.editor.ct(ws));
	}
}
});

