function StringBuilder(sString) {
	
	// public
	this.length = 0;
	
	this.append = function (sString) {
		// append argument
		this.length += (this._parts[this._current++] = String(sString)).length;
		
		// reset cache
		this._string = null;
		return this;
	};
	
	this.toString = function () {
		if (this._string != null)
			return this._string;
		
		var s = this._parts.join("");
		this._parts = [s];
		this._current = 1;
		this.length = s.length;
		
		return this._string = s;
	};

	// private
	this._current	= 0;
	this._parts		= [];
	this._string	= null;	// used to cache the string
	
	// init
	if (sString != null)
		this.append(sString);
}

function getXhtml(oNode) {
	var sb = new StringBuilder;
	var cs = oNode.childNodes;
	var l = cs.length;
	for (var i = 0; i < l; i++)
		_appendNodeXHTML(cs[i], sb);

	return sb.toString();
}

function _fixAttribute(s) {
	return String(s).replace(/\&/g, "&amp;").replace(/</g, "&lt;").replace(/\"/g, "&quot;");
}

function _fixText(s) {
	return String(s).replace(/\&/g, "&amp;").replace(/</g, "&lt;");
}

function _getAttributeValue(oAttrNode, oElementNode, sb) {
	if (!oAttrNode.specified)
		return;
		
	var name = oAttrNode.nodeName;
	var value = oAttrNode.nodeValue;
	if (name != "style" ) {
		if (!isNaN(value))	// IE5.x bugs for number values
			value = oElementNode.getAttribute(name);
		sb.append(" " + (oAttrNode.expando ? name : name.toLowerCase()) +
					"=\"" + _fixAttribute(value) + "\"");
	}
	else
		sb.append(" style=\"" + _fixAttribute(oElementNode.style.cssText) + "\"");
}

function _appendNodeXHTML(node, sb) {

	switch (node.nodeType) {
		case 1:	// ELEMENT
		
			if (node.nodeName == "!") {	// IE5.0 and IE5.5 are weird
				sb.append(node.text);
				break;
			}
		
			var name = node.nodeName;
			if (node.scopeName == "HTML")
				name = name.toLowerCase();
		
			sb.append("<" + name);
			
			// attributes
			var attrs = node.attributes;
			var l = attrs.length;
			for (var i = 0; i < l; i++)
				_getAttributeValue(attrs[i], node, sb);
				
			if (node.canHaveChildren || node.hasChildNodes()) {
				sb.append(">");
				
				// childNodes
				var cs = node.childNodes;
				l = cs.length;
				for (var i = 0; i < l; i++)
					_appendNodeXHTML(cs[i], sb);
				
				sb.append("</" + name + ">");
			}
			else if (name == "script")
				sb.append(">" + node.text + "</" + name + ">");
			else if (name == "title" || name == "style" || name == "comment")
				sb.append(">" + node.innerHTML + "</" + name + ">");
			else 
				sb.append(" />");
				
			break;
			
		case 3:	// TEXT
			sb.append( _fixText(node.nodeValue) );
			break;
				
		case 4:
			sb.append("<![CDA" + "TA[\n" + node.nodeValue + "\n]" + "]>");
			break;
				
		case 8:
			//sb.append("<!--" + node.nodeValue + "-->");
			sb.append(node.text);
			if (/(^<\?xml)|(^<\!DOCTYPE)/.test(node.text) )
				sb.append("\n");
			break;
			
		case 9:	// DOCUMENT
			// childNodes
			var cs = node.childNodes;
			l = cs.length;
			for (var i = 0; i < l; i++)
				_appendNodeXHTML(cs[i], sb);
			break;
			
		default:
			sb.append("<!--\nNot Supported:\n\n" + "nodeType: " + node.nodeType + "\nnodeName: " + node.nodeName + "\n-->");
	}
}
