// File             : utils.js
// Programmer       : John Wong
// Copyright (c) Q-Surf Computing Solutions, 2003-07. All rights reserved.
// http://www.qwebeditor.com/

// create the main name space
var JSLIB = window.JSLIB || {};

// this function is used to pre-populate namespace
JSLIB.namespace = function(name)  {
    var names = name.split(".");
    if (names[0] != "JSLIB") {
        return;
    }
    var o = JSLIB;
    for (var i = 1; i < names.length; i ++) {
        if (typeof o[names[i]] == "undefined") {
            o[names[i]] = {};
        }
        o = o[names[i]];
    }
}

// http://phrogz.net/JS/Classes/OOPinJS.html
// http://www.coolpage.com/developer/javascript/Correct%20OOP%20for%20Javascript.html

// call parent constructor
JSLIB.callParentConstructor = function(parent, obj) {
	// Apply parent's constructor to this object
	if( arguments.length > 2 ) {
		// Note: 'arguments' is an Object, not an Array
		parent.apply( obj, Array.prototype.slice.call( arguments, 2 ) );
	}
	else {
		parent.call( obj );
	}
}

// child copies parent hierarchy
JSLIB.inherits = function(parent, child) {
	child.prototype = new parent();
	child.prototype.constructor = child;
}

// PSLIB.simpleObject class. base class for inheritance
JSLIB.namespace("PSLIB.simpleObject");
JSLIB.simpleObj = function () {
}
JSLIB.simpleObj.prototype.constructor = JSLIB.simpleObj;

// PSLIB.object class. base class for JSLIB classes
JSLIB.object = function () {
	JSLIB.callParentConstructor(JSLIB.simpleObj, this);
    this.properties = {};
}
JSLIB.inherits(JSLIB.simpleObj, JSLIB.object);

JSLIB.object.prototype.getProperty = function(name) {
    return this.properties[name];
}
JSLIB.object.prototype.setProperty = function(name, value) {
    this.properties[name] = value;
}

function LTrim(s){var str=new String(s); return str.replace(/^\s*/,""); }
function RTrim(s){var str=new String(s); return str.replace(/\s*$/,""); }
function Trim(str){return LTrim(RTrim(str))}

JSLIB.namespace("JSLIB.htmlSourceCleaner");
JSLIB.htmlSourceCleaner = function () {
	this._singleTags = {img:true, input:true, br:true, hr:true, meta:true, link:true};
	this._tableTags = {table:true, tr:true, th:true, td: true};
	this._tableAttrs = {width:true, height:true, bgcolor:true, background:true, border:true, bordercolor:true};
	this._formattingTags = {
		span:true, font:true, b:true, strong:true,
		i:true, em:true, u:true, strike:true,
		big:true, small:true, sup:true, sub:true,
		col:true, colgroup:true};
	this._formattingAttrs = {
		style:true, "class":true
		};

	this._cleanStyleValue = function (str) {
		var cssStyles = str.split(";");
		var output = [];
		for (var i = 0; i < cssStyles.length; i ++) {
			var parts = cssStyles[i].split(":");
			if (parts.length == 2) {
				output[output.length] = Trim(parts[0].toLowerCase()) + ": " + Trim(parts[1]);
			}
			else {
				var dummy = Trim(cssStyles[i]);
				if (dummy.length > 0) {
					output[output.length] = cssStyles[i];
				}
			}
		}
		return output.join("; ");
	};
}

JSLIB.htmlSourceCleaner.prototype.process = function (str, options) {
	var pos = 0, output = "";
	var reTag = /^<([A-Za-z1-9\-\_\:\/]+)([^>]*)>$/;
	var reAttr = /^([ \r\n\t]*([A-Za-z0-9\-\_\:]+)(?:(\=)("([^"]*)(")|'([^']*)(')|[^ ]*))?)/;
	var T = true, F = false, UD = "undefined";
	
	var safeHtml = (typeof options["safeHtml"] == UD) ? F : options["safeHtml"];
	var cleanForWord = (typeof options["cleanForWord"] == UD) ? F : options["cleanForWord"];
	var cleanTableFormatting = (typeof options["cleanTableFormatting"] == UD) ? F : options["cleanTableFormatting"];
	var cleanExcelAttributes = (typeof options["cleanExcelAttributes"] == UD) ? F : options["cleanExcelAttributes"];
	var cleanFormatting = (typeof options["cleanFormatting"] == UD) ? F : options["cleanFormatting"];
	var baseHref = (typeof options["baseHref"] == UD) ? "" : options["baseHref"];
	var absUrl = (typeof options["absUrl"] == UD) ? false : options["absUrl"];

	baseHref = DirName(baseHref);
	if (baseHref.length > 0) {
		baseHref += "/";
	}
	
	if (safeHtml) {
		str = str.replace(/<\/SCRIPT/gi, "</script");
	}
	
	do {
		// look for start delimiter of the tag
		var nextPos = str.indexOf("<", pos);

		// output everything as-is before the tag
		if (nextPos < 0) {
			if (str.length > pos) {
				output += str.substring(pos);
			}
			break;
		}
		else if (nextPos > pos) {
			output += str.substring(pos, nextPos);
		}
		
		// look for end delimiter of the tag
		pos = nextPos;
		nextPos = str.indexOf(">", pos);
		
		if (nextPos < 0) {
			return output + "[[ ERROR: template compilation failed - missing tag closing delimiter ]]";
		}
		
		var 
			tag = str.substring(pos, nextPos + 1),
			matches = tag.match(reTag),
			tagname, attrs,
			updatedTag = "",
			attrName, attrValue;
		
		if (matches) {
			tagname = matches[1];
			attrs = matches[2];
			
			if (tagname.indexOf(":") < 0) {
				tagname = tagname.toLowerCase();
				if (cleanForWord && tagname == "p") {
					tagname = "div";
				}
			}
			updatedTag += tagname;
			
			if (tagname.substring(0, 1) == "/") {
				if ((typeof this._singleTags[tagname.substring(1)] != "undefined") ||
					(cleanFormatting && typeof this._formattingTags[tagname.substring(1)] != "undefined")) {
					// do nothing
				}
				else {
					output += "<" + tagname + ">";
				}
			}
			else if (safeHtml && (tagname == "script")) {
				nextPos = str.indexOf("</script", nextPos);
				if (nextPos >= 0) {
					nextPos = str.indexOf(">", nextPos);
				}
				if (nextPos < 0) {
					nextPos = str.length;
				}
			}
			else if (cleanFormatting && typeof this._formattingTags[tagname] != "undefined") {
				// do nothing
			}
			else {
				while (attrs.length > 0) {
					if (matches = attrs.match(reAttr)) {
						attrName = matches[2].toLowerCase();
						if (matches[3] == "=") {
							if (matches[6] == "\"") {
								attrValue = matches[5];
							}
							else if (matches[8] == "'") {
								attrValue = matches[7];
							}
							else {
								attrValue = matches[4];
							}
						}
						else {
							attrValue = attrName;
						}
					}
					else {
						// error
						break;
					}
					attrs = attrs.substring(matches[0].length);
					if ((safeHtml && attrName.substring(0, 2) == "on") ||
						(cleanTableFormatting && 
						(typeof this._tableTags[tagname] != "undefined") && 
						(typeof this._tableAttrs[attrName] != "undefined")) ||
						(cleanExcelAttributes && attrName.substring(0,2) == "x:") ||
						(cleanFormatting && typeof this._formattingAttrs[attrName] != "undefined")
						) {
						// do nothing
					}
					else {
						// clean style attribute
						if (attrName == "style") {
							attrValue = this._cleanStyleValue(attrValue);
						}
						// clean URL based on baseHref
						else if ((attrName == "src" || attrName == "href") && baseHref.length > 0) {
							attrValue = AbsUrl(attrValue, baseHref);
							if (!absUrl && attrValue.indexOf(baseHref) == 0) {
								attrValue = attrValue.substring(baseHref.length);
							}
						}
						if (safeHtml && attrName == "href") {
							var dummy = Trim(attrValue);
							if (dummy.substring(0,11) == "javascript:") {
								attrValue = "";
							}
						}
						updatedTag += " " + attrName + "=\"" + attrValue + "\"";
					}
				}
				output += "<" + updatedTag + (typeof this._singleTags[tagname] == "undefined" ? ">" : " />");
			}
		}
		else {
			output += tag;
		}
		
		pos = nextPos + 1;
	} while (pos < str.length);

	return output;		
};

function IsValidInteger(str){
	var n1 = new String(str)
	for (i = 0; i < n1.length; i ++){
	    if(n1.charAt(i) < "0" || n1.charAt(i) > "9")
	        return false
	}    
	return true
}

function AbsUrl(str, baseHref) {
	var str=new String(str),re,arr,arr2;
	if(/^(http|https|email|file|ftp|about):/.test(str)){
		return str;
	}
	else {
		if (typeof baseHref == "undefined") {
			baseHref = DirName(location.href) + "/";
		}
		if (str.charAt(0) == "/") {
			arr=baseHref.match(/(^(http|https|file):\/\/[^\/]*\/)/);
			if (arr != null) {
				return arr[1] + str.substring(1);
			}
			arr=baseHref.match(/(^file:\/\/)/);
			if (arr != null) {
				return "file://" + str;
			}
			// everything seems invalid. just return asis.
			return str;
		}
		else {
			re = /^((\.|\.\.)\/)/;
			while (true) { 
				arr = str.match(re);
				if (arr == null) {
					break;
				}
				if (arr[1] == "../") {
					arr2 = baseHref.match(/^((http|https|ftp):\/\/[^\/]*\/([^\/]*\/)*)([^\/]*\/)$/);
					if (arr2 != null) {
						baseHref = arr2[1];
					}
					else {
						arr2 = baseHref.match(/^(file:\/\/(.*\/)\/)([^\/]*\/)$/);
						if (arr2 != null) {
							baseHref = arr2[1];
						}
					}
				}
				str = str.substring(arr[1].length);
			}
			return baseHref + str;
		}
	}
}

function DirName(str) {
    var str=new String(str),re=/(.*)\/([^\/]*)$/,arr=str.match(re);
    return (arr==null||typeof(arr)=="undefined"||typeof(arr[1])=="undefined")?".":arr[1] 
}

 
function BaseName(str) {
    var str=new String(str)
    var re=/(.*)\/([^\/]*)$/;
    var arr = str.match(re)
    return (arr==null||typeof(arr)=="undefined"||typeof(arr[2])=="undefined")?"":arr[2]
}

function utilsInArray(value, arr) {
	for (var i = 0; i < arr.length; i ++) {
		if (arr[i] == value) {
			return true;
		}
	}
	return false;
}

// flag - undefined or 0 - escape double quote
//        1 - do not escape double quote
//        2 - quote single quote too
function HtmlSpecialChars(str, flag){
	var mystr = new String(str)
	var re;
	
	if (typeof(flag) == "undefined") {
		flag = 0;
	}
	re=/&/g;
	mystr=mystr.replace(re,"&amp;");
	if (flag != 1) {
		re=/\"/g;
		mystr=mystr.replace(re,"&quot;");
	}
	if (flag == 2) {
		re=/\'/g;
		mystr=mystr.replace(re,"&#039;");
	}
	re=/</g;
	mystr=mystr.replace(re,"&lt;");
	re=/>/g;
	mystr=mystr.replace(re,"&gt;");
	re=new RegExp(String.fromCharCode(160), "g")
	mystr=mystr.replace(re,"&nbsp;");
	return mystr
}

function HtmlEditAddSlashes(str){
	var mystr = new String(str), re;
	mystr=mystr.replace(/\"/g,"\\\"");
    mystr=mystr.replace(/\'/g,"\\\'");
    mystr=mystr.replace(/\t/g,"\\t");
    mystr=mystr.replace(/\r/g,"\\r");
    mystr=mystr.replace(/\n/g,"\\n");
    return mystr;
}

function IsHtmlText(str){
	var mystr = new String(str);
	var re=/<(p|h1|h2|h3|h4|h5|h6|table|td|tr|ul|ol|li|b|i|u|strong|em|strike|super|sup|big|small|body|html|br|hr|font|blockquote|pre|tt|script|object|embed)/i;
	if(re.test(mystr)) return true;
	re=/(&[a-zA-Z]{2,5};|&#[0-9]{1,5};)/i;
	if(re.test(mystr)) return true;
	return false;
}

function PlainTextToHtml(str){
	var mystr = new String(str), re;
	mystr=mystr.replace(/\r\n/g,"<br />");
	mystr=mystr.replace(/\n/g,"<br />");
	mystr=mystr.replace(/\r/g,"<br />");
	mystr=mystr.replace(/  /g," &nbsp;");
	mystr=mystr.replace(/\t/g," &nbsp; &nbsp;");
	return mystr;
}

function HtmlToPlainText(str, strImage){
	var mystr = new String(str)
	var re;
	re=/>\r\n/g;
	mystr=mystr.replace(re,">");
	re=/>\r/g;
	mystr=mystr.replace(re,">");
	re=/>\n/g;
	mystr=mystr.replace(re,">");
	re=/\r\n/g;
	mystr=mystr.replace(re," ");
	re=/\r/g;
	mystr=mystr.replace(re," ");
	re=/\n/g;
	mystr=mystr.replace(re," ");
    if(strImage){
		re=/<img[^>]*>|<object[^>]*>|<embed[^>]*>/ig;
		mystr=mystr.replace(re,strImage);
    }
	re=/<\/p[^>]*>|<\/h1[^>]*>|<\/h2[^>]*>|<\/h3[^>]*>|<\/h4[^>]*>|<\/h5[^>]*>|<\/h6[^>]*>|<\/blockquote[^>]*>|<\/ul[^>]*>|<\/ol[^>]*>/gi;
	mystr=mystr.replace(re,"\n\n");
	re=/<br[^>]*>/gi;
	mystr=mystr.replace(re,"\n");
	re=/<\/div[^>]*>/gi;
	mystr=mystr.replace(re,"\n");
	re=/<li[^>]*>/gi;
	mystr=mystr.replace(re,"\n  * ");
	re=/<\/td[^>]*>/gi;
	mystr=mystr.replace(re,"  ");
	re=/<hr[^>]*>/gi;
	mystr=mystr.replace(re,"\n-------------------------------\n");
	re=/<[^>]*>/gi;
	mystr=mystr.replace(re,"");
	// need to convert html entities back to original chars.
	// but not necessary for qwebeditor
	return mystr;
}

function CleanWindowsCharset(strValue) {
	var re = new RegExp("(["+String.fromCharCode(
		183,
		8211, 8212, 8213, 8214, 8215, 
		8216, 8217, 8218, 8219, 8220, 
		8221, 8222, 8223, 8224, 8225,
		8226, 8227, 8228, 8229, 8230)+"])","g") 
	strValue = strValue.replace(re, function($1){
			var str=new String($1)
			switch(str.charCodeAt(0)){
			case 183: return "&middot;"
			case 8211: return "-"
			case 8212: return "--"
			case 8213: return "--"
			case 8214: return "|"
			case 8215: return "-"
			case 8216: return "'"
			case 8217: return "'"
			case 8218: return "'"
			case 8219: return "'"
			case 8220: return '"'
			case 8221: return '"'
			case 8222: return '"'
			case 8228: return "."
			case 8229: return ". ."
			case 8230: return "..."
			}
		});
	return strValue;
}

function GetInnerTextById(id){
    if(!document.getElementById) return "";
    var node=document.getElementById(id)
    if(node){
		if(is_ie)return node.innerText;
		else{
			var html=document.createRange();
			html.selectNodeContents(node);
			return html.toString();
		}
    }
    return "";
}

function GetInnerHtmlFromNode(node,baseHref,bSafe){
	var absUrl = absUrl=(typeof(arguments[3])!="undefined"?arguments[3]:false);
	var o = new JSLIB.htmlSourceCleaner();
	return o.process(node.innerHTML, {safeHtml:bSafe, baseHref:baseHref});
}

function GetCleanCode(node){
	// bWord - change p tag to div
	var 
		bWord=(typeof(arguments[1])!="undefined"?arguments[1]:false),
		bRemoveTableFormatting=(typeof(arguments[2])!="undefined"?arguments[2]:false),
		bRemoveExcelAttributes=(typeof(arguments[3])!="undefined"?arguments[3]:false),
		baseHref=(typeof(arguments[4])!="undefined"?arguments[4]:""),
		absUrl=(typeof(arguments[5])!="undefined"?arguments[5]:false)
		;

	var o = new JSLIB.htmlSourceCleaner();
	return o.process(node.innerHTML, {
		safeHtml:true, 
		cleanFormatting:true,
		cleanForWord:bWord,
		cleanTableFormatting:bRemoveTableFormatting,
		cleanExcelAttributes:bRemoveExcelAttributes,
		baseHref:baseHref,
		absUrl:absUrl
		});
}

function HeUtilsPopupCreate(bForceDiv){
    if(window.createPopup&&!bForceDiv){
    	// IE way
        return{
        	objPopup:window.createPopup(),
        	bDiv:false};
    }
    else if(document.createElement){
    	// using DOM to create an abs pos div
        var div=document.createElement("div"),ds=div.style;
        ds.position='absolute';
        ds.left='-1000px';
        ds.top='-1000px';
        ds.visibility='hidden';
        ds.zIndex=10;
        document.body.appendChild(div);
        return{
        	objPopup:div,
        	bDiv:true
			};
    }
    return null;
}

function HeUtilsPopupGetContent(obj){
    if(!obj||!obj.objPopup)return;
    return (!obj.bDiv)?obj.objPopup.document.body.innerHTML : obj.objPopup.innerHTML;
}

function HeUtilsPopupSetContent(obj,content){
    if(!obj||!obj.objPopup)return;
    if(!obj.bDiv)
        obj.objPopup.document.body.innerHTML=content;
    else
        obj.objPopup.innerHTML=content;
}

function HeUtilsPopupShow(obj,left,top,width,height,element,bCenter){
	if(!obj||!obj.objPopup)return;
	var newLeft,newTop,width,height;
	if(!obj.bDiv){
		// has to declare display the popup first before able to get the dimension
	    obj.objPopup.show(left,top,1,1,element);
		if (width) {
			obj.objPopup.document.body.firstChild.style.width="100%";
		}
		else {
			obj.objPopup.document.body.firstChild.style.width="";
		}
		width=(width?width:obj.objPopup.document.body.firstChild.offsetWidth);
		height=(height?height+2:obj.objPopup.document.body.firstChild.offsetHeight);
		newLeft=(bCenter)?((element.offsetWidth-width)/2):left;
		newTop=(bCenter)?((element.offsetHeight-height)/2):top;
	    obj.objPopup.show(newLeft,newTop,width,height,element);
	}
	else{
		if (width) {
			obj.objPopup.firstChild.style.width="100%";
		}
		else {
			obj.objPopup.firstChild.style.width="";
		}
		width=width?width:obj.objPopup.firstChild.offsetWidth;
		height=height?height:obj.objPopup.firstChild.offsetHeight;
		newLeft=(bCenter)?((element.offsetWidth-width)/2):left;
		newTop=(bCenter)?((element.offsetHeight-height)/2):top;
		newLeft+=HeUtilsGetOffsetLeft(element);
		newTop+=HeUtilsGetOffsetTop(element);
		if(isNaN(newLeft)) newLeft=0;
		if(isNaN(newTop)) newTop=0;
		if(newLeft+width>window.innerWidth-20+window.scrollX)
		    newLeft=window.innerWidth-width-20+window.scrollX;
		if(newTop+height>window.innerHeight-20+window.scrollY)
		    newTop=window.innerHeight-height-20 +window.scrollY;
		var os=obj.objPopup.style;
		os.left=newLeft+"px";
		os.top=newTop+"px";
		os.width=width+"px";
		os.height=height+"px";
		os.visibility="visible";
	}
}

function HeUtilsPopupHide(obj){
	if(!obj || !obj.objPopup) return;
	if(!obj.bDiv)obj.objPopup.hide();
	else{
		var os=obj.objPopup.style;
		os.visibility="hidden";
		os.left="-1000px";
		os.top="-1000px";
	}
}

function HeUtilsGetOffsetTop(elm){
	var mOT=elm.offsetTop;
	var mOP=elm.offsetParent;
	while(mOP){
		mOT+=mOP.offsetTop;
		mOP=mOP.offsetParent;
	}
	return mOT;
}

function HeUtilsGetOffsetLeft(elm){
    var mOL=elm.offsetLeft;
    var mOP=elm.offsetParent;
    while(mOP){
		mOL+=mOP.offsetLeft;
		mOP=mOP.offsetParent;
    }
    return mOL;
}

function StyleSheetCreate(doc){
	if(!doc)return;
	if(doc.createStyleSheet){
		var e;
		try{return doc.createStyleSheet()}catch(e){}
	}
	else if(is_gecko||
        (is_safari&&g_heBrowser.version>419)||
        (g_heBrowser.browser=="opera"&&g_heBrowser.version>=9)){
		var index=doc.styleSheets.length
		var head=doc.getElementsByTagName('head').item(0);
		var mystyle = doc.createElement('style');
		head.appendChild(mystyle);
		// mystyle is a style element object. need the cssstylesheet object
		return doc.styleSheets[index];
	}
    return null;
}

// url must be a abs url with protocol and domainname
function StyleSheetCreateFromUrl(doc, url){
	if(is_ie)return doc.createStyleSheet(url);
	else if(is_gecko||
        (is_safari&&g_heBrowser.version>412)||
        (g_heBrowser.browser=="opera"&&g_heBrowser.version>=9)){
		var index=doc.styleSheets.length;
		var head=doc.getElementsByTagName('head').item(0);
		var em = doc.createElement('link');
		em.setAttribute('rel','stylesheet');
		em.setAttribute('type','text/css');
		em.setAttribute('href',url);
		head.appendChild(em);
		// mystyle is a style element object. need the cssstylesheet object
		return doc.styleSheets[index];
	}
	return null;
}

function StyleSheetAddRule(ss,name,rule){
    if(is_ie){
        ss.addRule(name,rule);
    }
    else{
		var e;
		try{ss.insertRule(name+"{"+rule+"}",ss.cssRules.length)}catch(e){}
    }
}

function StyleSheetRemoveRule(ss,index){
    if (is_ie){
        ss.removeRule(index);
    }
    else {
		var e;
        try{ss.deleteRule(index);}catch(e){}
    }
}

// ss - stylesheet
function StyleSheetRemoveAllRules(ss){
    var e;
    if (is_ie) {
		try{
        while(ss.rules.length)StyleSheetRemoveRule(ss, 0);
        }catch(e){}
    }
    else{
        // dont know why gecko wont allow accessing cssRules for created stylesheet
        // just trying to delete rules until exception is caught
		try{
			var num=ss.cssRules.length;
			for(var i=0;i<num;i++)StyleSheetRemoveRule(ss,0);
		}catch(e){}
    }
}

function StyleSheetGetRulesArray(ss){
	if(!ss)return null;
	return is_ie?ss.rules:ss.cssRules;
}

function ObjGetCssText(o){
	if(!o)return;
	return (is_ie||is_safari)?o.style.cssText:o.getAttribute("style");
}
 
function ObjSetCssText(o,cssText){
	if(!o)return;
	if(is_ie||is_safari){
		o.style.cssText=cssText;
	}
	else {
		SetRemoveAttr(o,"style",cssText);
	}
}

function AttachEventListener(o,eventName,func){
	if(!o)return false;
	if(o.attachEvent){ // IE
		o.attachEvent("on"+eventName,func);
	}
	else if(o.addEventListener){ // Mozilla
		o.addEventListener(eventName,func,false);
	}
	return true;
}

function SetRemoveAttr(e,attr,value){
	if(typeof(e)=="undefined"||!e.removeAttribute||!e.setAttribute)return;
	if(typeof(value)=="undefined"||value==null||value.length==0){
		e.removeAttribute(attr);
	}
	else{
		e.setAttribute(attr,value);
	}
}

function utilsConvertSpanToHtmlTag(doc) {
	// get all span tag
	var arrSpans = doc.getElementsByTagName("span");
	for (var i = arrSpans.length - 1; i >= 0; i --) {
		var 
			node = arrSpans[i],
			parentNode = node.parentNode,
			strStyle = node.getAttribute("style"),
			newParent = null,topParent = null,
			elem,bIE = false,
			className = node.className;

		// safari tweak
		if (className == "Apple-style-span") {
			className = null;
		}

		if (strStyle) {
			// IE returns a style object instead. we need the string
			if (!strStyle.indexOf) {
				strStyle = new String(strStyle.cssText);
				bIE = true;
			}
			// IE returns CSS attributes in uppercase
			strStyle = Trim(strStyle.toLowerCase());
			// IE does not return semicolon for last css attribute
			if (strStyle.length > 0 && strStyle.substr(strStyle.length - 1, 1) != ";") {
				strStyle = strStyle + ";";
			}
		}

		if (strStyle) {
			// look for the following CSS attributes
			
			// b
			if (strStyle.indexOf("font-weight: bold;") >= 0) {
				elem = doc.createElement("b");
				if (!topParent) topParent = elem;
				if (newParent) newParent.appendChild(elem);
				newParent = elem;
				strStyle = Trim(strStyle.replace("font-weight: bold;", ""));
			}
			// bold under Opera 9
			if (strStyle.indexOf("font-weight: 700;") >= 0) {
				elem = doc.createElement("b");
				if (!topParent) topParent = elem;
				if (newParent) newParent.appendChild(elem);
				newParent = elem;
				strStyle = Trim(strStyle.replace("font-weight: 700;", ""));
			}
			
			// i
			if (strStyle.indexOf("font-style: italic;") >= 0) {
				elem = doc.createElement("i");
				if (!topParent) topParent = elem;
				if (newParent) newParent.appendChild(elem);
				newParent = elem;
				strStyle = Trim(strStyle.replace("font-style: italic;", ""));
			}
			
			// u
			if (strStyle.indexOf("text-decoration: underline;") >= 0) {
				elem = doc.createElement("u");
				if (!topParent) topParent = elem;
				if (newParent) newParent.appendChild(elem);
				newParent = elem;
				strStyle = Trim(strStyle.replace("text-decoration: underline;", ""));
			}

			// strike
			if (strStyle.indexOf("text-decoration: line-through;") >= 0) {
				elem = doc.createElement("strike");
				if (!topParent) topParent = elem;
				if (newParent) newParent.appendChild(elem);
				newParent = elem;
				strStyle = Trim(strStyle.replace("text-decoration: line-through;", ""));
			}

			// super
			if (strStyle.indexOf("vertical-align: super;") >= 0) {
				elem = doc.createElement("sup");
				if (!topParent) topParent = elem;
				if (newParent) newParent.appendChild(elem);
				newParent = elem;
				strStyle = Trim(strStyle.replace("vertical-align: super;", ""));
			}

			// sub
			if (strStyle.indexOf("vertical-align: sub;") >= 0) {
				elem = doc.createElement("sub");
				if (!topParent) topParent = elem;
				if (newParent) newParent.appendChild(elem);
				newParent = elem;
				strStyle = Trim(strStyle.replace("vertical-align: sub;", ""));
			}

			// font face
			var strMatch = strStyle.match(/font\-family: ([^\;]*)\;/);
			if (strMatch) {
				elem = doc.createElement("font");
				elem.setAttribute('face', strMatch[1]);
				if (!topParent) topParent = elem;
				if (newParent) newParent.appendChild(elem);
				newParent = elem;
				strStyle = Trim(strStyle.replace(strMatch[0], ""));
			}

			// font size
			var strMatch = strStyle.match(/font\-size: ([^\;]*)\;/);
			if (strMatch) {
				var value = "";
				switch (strMatch[1]) {
				case "xx-large": 
				case "-webkit-xxx-large":
					value = 7; break;
				case "x-large": value = 6; break;
				case "large": value = 5; break;
				case "medium": value = 4; break;
				case "small": value = 3; break;
				case "x-small": value = 2; break;
				case "xx-small": value = 1; break;
				}
				if (value) {
					elem = doc.createElement("font");
					elem.setAttribute('size', value);
					if (!topParent) topParent = elem;
					if (newParent) newParent.appendChild(elem);
					newParent = elem;
					strStyle = Trim(strStyle.replace(strMatch[0], ""));
				}
			}

			// font color
			var strMatch = strStyle.match(/^color:[ ]*([^\;]*)\;/);
			if (!strMatch) {
				strMatch = strStyle.match(/[ ]+color:[ ]*([^\;]*)\;/);
			}
			if (!strMatch) {
				strMatch = strStyle.match(/\;color:[ ]*([^\;]*)\;/);
			}
			if (strMatch) {
				elem = doc.createElement("font");
				elem.setAttribute('color', strMatch[1]);
				if (!topParent) topParent = elem;
				if (newParent) newParent.appendChild(elem);
				newParent = elem;
				strStyle = Trim(strStyle.replace(strMatch[0], ""));
			}

			// found replacement. restructure the DOM tree
			if (topParent) {
				// move all children to new branch
				while (node.childNodes.length > 0) {
					var oldChild = node.removeChild(node.firstChild);
					newParent.appendChild(oldChild);
				}

				// span tag is cleaned. take it out now.
				if (strStyle.length == 0 && !className) {
					if (parentNode) {
						parentNode.replaceChild(topParent, node);
					}
				}
				// span tag probably still has some css style and class name.
				// just cleaned whatever which is not needed.
				else {
					node.appendChild(topParent);
					if (bIE) {
						node.className = className;
						node.style.cssText = strStyle;
					}
					else {
						SetRemoveAttr(node,"style",strStyle);
						SetRemoveAttr(node,"class",className);
					}
				}
			}
			// no replacement and still have style or class attributes. update span tag
			else if (strStyle || className) {
				if (bIE) {
					node.className = className;
					node.style.cssText = strStyle;
				}
				else {
					SetRemoveAttr(node,"style",strStyle);
					SetRemoveAttr(node,"class",className);
				}
			}
			// no style and no class attribute. takes out the span tag
			else {
				while (node.childNodes.length > 0) {
					var oldChild = node.removeChild(node.firstChild);
					parentNode.insertBefore(oldChild, node);
				}
				parentNode.removeChild(node);
			}			
		}
		// no style and no class attribute. takes out the span tag
		else if (!className) {
			while (node.childNodes.length > 0) {
				var oldChild = node.removeChild(node.firstChild);
				parentNode.insertBefore(oldChild, node);
			}
			parentNode.removeChild(node);
		}
	}	

	// clean font tag
	var arrFonts = doc.getElementsByTagName("font");
	for (var i = arrFonts.length - 1; i >= 0; i --) {
		var node = arrFonts[i];
		var className = node.className;
		if (className == "Apple-style-span") {
			node.removeAttribute("class");
		}
	}
	
	// clean br tag
	var arrBrs = doc.getElementsByTagName("br");
	for (var i = arrBrs.length - 1; i >= 0; i --) {
		var node = arrBrs[i];
		var className = node.className;
		if (className == "khtml-block-placeholder" ||
			className == "webkit-block-placeholder") {
			node.removeAttribute("class");
		}
	}
	
}

function utilsMoveChildrenToNode(oldN, newN) {
	if (!oldN || !newN) return;
	while (oldN.firstChild) {
		var n = oldN.firstChild;
		oldN.removeChild(n);
		newN.appendChild(n);
	}
}

function utilsMoveNodesToNode(startNode, endNode, newNode) {
	var node = startNode;
	var nextNode = startNode.nextSibling;
	do {
		node.parentNode.removeChild(node);
		newNode.appendChild(node);
		if (node == endNode) {
			break;
		}
		node = nextNode;
		nextNode = node.nextSibling;
	} while (node != null);
}

function utilsMoveNodesBeforeNode(startNode, endNode, newNode) {
	var node = startNode;
	var nextNode = startNode.nextSibling;
	var doc = startNode.ownerDocument;
	if (!doc) {
		doc = newNode.ownerDocument;
	}
	do {
		node.parentNode.removeChild(node);
		newNode.parentNode.insertBefore(node, newNode);
		if (node == endNode) {
			break;
		}
		node = nextNode;
		nextNode = node.nextSibling;
	} while (node != null);
}

function utilsGetFirstTextNode(n) {
	if (!n) return;
	while (n) {
		if (n.nodeType == 3) break;
		n = n.firstChild;
	}
	return n;
}

function utilsGetLastTextNode(n) {
	if (!n)return;
	while (n) {
		if (n.nodeType == 3) break;
		n = n.lastChild;
	}
	return n;
}
