// --------------------------------------------------------------------------
/// IWF - Interactive Website Framework.  Javascript library for creating
/// responsive thin client interfaces.
///
/// Copyright (C) 2005 Brock Weaver brockweaver@users.sourceforge.net
///
///     This library is free software; you can redistribute it and/or modify
/// it under the terms of the GNU Lesser General Public License as published
/// by the Free Software Foundation; either version 2.1 of the License, or
/// (at your option) any later version.
///
///     This library is distributed in the hope that it will be useful, but
/// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
/// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
/// License for more details.
///
///    You should have received a copy of the GNU Lesser General Public License
/// along with this library; if not, write to the Free Software Foundation,
/// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
///
/// Brock Weaver
/// brockweaver@users.sourceforge.net
/// 8772 NE 94th Ave
/// Bondurant, IA 50035
///
//! http://iwf.sourceforge.net/
// --------------------------------------------------------------------------
//! NOTE: To minimize file size, strip all fluffy comments (except the LGPL, of course!)
//! using the following regex (global flag and multiline on):
//!            ^\t*//([^/!].*|$)
//!  To rip out all comments (LGPL or otherwise):
//!            ^\t*//(.*|$)
//!  To rip out only logging statements (commented or uncommented):
//!            ^/{0,2}iwfLog.*
// --------------------------------------------------------------------------

// --------------------------------------------------------------------------
//! iwfxml.js
//
// Javascript-based xml parser
//
//! Dependencies:
//! iwfcore.js
//
//! Valentin Crettaz - valentin@crettaz.ch
//! v 0.7 - 2006-08-23
//! Added the setText() function in iwfXmlNode to be able to set the text
//! content of any given node.
//! PIs are not eaten up anymore but they are stored as child nodes instead.
//! All carriage returns are replaced by spaces before parsing attributes in
//! order to accomodate element whose attributes are declared on separate lines.
//! The processing instruction are now stored at the document level in the pis array.
//! The textual content of an element is trimmed before being added to its parent
//! in order to prevent unnecessary whitespaces to be prepended/appended to the
//! text content.
//! The comment nodes are serialized on a single line instead of on multiple lines.
//! When serializing a document, the first attribute is always written on the
//! first line.
// --------------------------------------------------------------------------
//! Valentin Crettaz - valentin@crettaz.ch
//! v 0.6 - 2006-08-19
//! Added support for nodes (elements + attributes) with namespace moniker (:)
//! Note the string '__' replaces the ':' to make valid javascript.
//! So to reference a namespaced element or attribute node such as
//! SOAP:Envelope or xmlns:tns
//!     var se = doc.SOAP__Envelope.xmlns__tns;
//! The only previous alternative was:
//!     var se = doc.["SOAP:Envelope"]["xmlns:tns"];
//! Added support for retrieving the prefix and the local name of
//! namespace-qualified elements, i.e. the node representing SOAP:Envelope
//! now contains two attributes called prefix ("SOAP") and localName ("Envelope")
//! All the declared namespaces are now stored as a property map in the
//! declaring element in which the namespace prefix is the key.
// --------------------------------------------------------------------------
//! Brock Weaver - brockweaver@users.sourceforge.net
//! v 0.4 - 2006-08-15
//! Fixed xml parser so namespaced node names can be retrieved (a colon in
//! a javascript property identifier is invalid javascript, so xml nodes with
//! a name of "SOAP:Envelope" could only be referenced via doc["SOAP:Envelope"]
//! and not doc.SOAP:Envelope.  Namespaced elements now have their colon
//! silently converted to
// --------------------------------------------------------------------------
//! Brock Weaver - brockweaver@users.sourceforge.net
//! v 0.5 - 2006-08-15
//! Added support for nodes with namespace moniker (:)
//! Note the string '_-' replaces the ':' to make valid javascript.
//! So to reference a namespaced node such as SOAP:Envelope
//!     var se = doc.SOAP_-Envelope;
//! The only previous alternative was:
//!     var se = doc.["SOAP:Envelope"];
//! Thanks Val
// --------------------------------------------------------------------------
//! Brock Weaver - brockweaver@users.sourceforge.net
//! v 0.2 - 2005-11-14
//! core bug patch
// --------------------------------------------------------------------------
//! Brock Weaver - brockweaver@users.sourceforge.net
//! v 0.1 - 2005-06-05
//! Initial release.
// --------------------------------------------------------------------------
// This class is meant to ease the burden of parsing xml.
// Xml is parsed into sets of arrays, in a hierarchical format.
// Each node is an array (of length 1, if applicable)
// The root node is an exception, as it is simply a node and not an array,
// as there is always only a single root node.
// Each attribute is a property of the current element in the node array.
// Useful for loading xml from server into a nice, easy-to-use format
// --------------------------------------------------------------------------

if (!__iwfCoreIncluded){
	iwfLogFatal("IWF Dependency Error: iwfxml.js is dependent upon iwfcore.js, so you *must* reference that file first.\n\nExample:\n\n<script type='text/javascript' src='iwfcore.js'></script>\n<script type='text/javascript' src='iwfxml.js'></script>", true);
}

var __iwfXmlIncluded = true;

// -----------------------------------
// Begin: Xml Document Object
// -----------------------------------

function iwfXmlDoc(xml){
	if (!xml || xml.length == 0){
		return;
	}
	this.loadXml(xml);
}

iwfXmlDoc.prototype.loadXml = function(xml){

	// store document-level PIs
	this.pis = new Array();
	this._parsePIs(xml);

	// note this root node is not an array, as there will always be exactly 1 root.
	this.childNodes = new Array();
	var node = new iwfXmlNode(xml);
	this.childNodes.push(node);
	this[node.nodeName] = node;
}

iwfXmlDoc.prototype.outerXml = function(pretty, asHtml){
	// outputs as raw xml, optionally with pretty formatting or optionally as html (escaped <>&"' for displaying in browsers properly)
	var writer = new iwfWriter(true, pretty, asHtml);
	var buf = '';
	if (this.pis){
		for (var i = 0; i < this.pis.length; i++){
			buf += this.pis[i];
			buf += (i < this.pis.length - 1) ? "\n" : "";
		}
	}
	if (this.childNodes.length > 0){
		buf += this.childNodes[0].outerXml(writer);
	}
	return buf;
}

iwfXmlDoc.prototype.toString = function(pretty, asHtml){
	return this.outerXml(pretty, asHtml);
}

iwfXmlDoc.prototype._parsePIs = function(xml){
	if (!xml || xml.length == 0) { return; }

	var BEGIN_MONIKER = "<?";
	var END_MONIKER = "?>";
	var begin = xml.indexOf(BEGIN_MONIKER);
	var end = xml.indexOf(END_MONIKER);
	while (begin > -1 && end > begin) {
		var pi = xml.substr(begin, end + END_MONIKER.length);
		this.pis.push(pi);
		xml = xml.substr(end + END_MONIKER.length);
		begin = xml.indexOf(BEGIN_MONIKER);
		end = xml.indexOf(END_MONIKER);
	}
}

// -----------------------------------
// End: Xml Document Object
// -----------------------------------


// -----------------------------------
// Begin: Xml Node Object
// -----------------------------------


// --------------------------------------------------------------------------
//! iwfXmlNode
//
//! Brock Weaver - brockweaver@users.sourceforge.net
//! v 0.1 - 2005-06-05
//! Initial release.
// --------------------------------------------------------------------------
// This class is used to represent a single xml node.
// All xml is kept except processing instructions.
// There are issues with "<" and ">" chars embedded within
// the xml as text.  This is technically not valid xml, so
// it is technically not a problem I guess.

function iwfXmlNode(xml, parent){

	this.childNodes = new Array();
	this._text = '';
	this.attributes = new Array();
	this.attributeNames = new Array();
	this.attributeQuotes = new Array();
	this.namespaces = new Array();
	this.nodeName = 'UNKNOWN';
	this._parent = parent;
	this._isEmpty = false;


	if (!xml || xml.length == 0){
		return;
	} else {
		this._parseXml(xml);
	}

	this._htmlMode = false;


}

iwfXmlNode.prototype.toString = function(innerOnly){
	if (innerOnly){
		return this.innerXml();
	} else {
		return this.outerXml();
	}
}

iwfXmlNode.prototype.cloneNode = function(addToParent){
	var copy = new iwfXmlNode(this.outerXml());
	if (addToParent) {
		copy._parent = this._parent;
		// push it onto the parent's object's array
		copy._parent[copy.nodeName].push(copy);
		// push it onto the parent's childNodes array
		var originalIndex = this._parent.indexOfNode(this);
		if (originalIndex < 0) {
			//should never happen, but still append the node at the end
			originalIndex = this._parent.childNodes.length - 1;
		}
		var deleteCount = this._parent.childNodes.length - originalIndex - 1;
		var endArray = this._parent.childNodes.splice(originalIndex + 1, deleteCount);
		this._parent.childNodes = this._parent.childNodes.concat(copy, endArray);
	}
	return copy;
}

iwfXmlNode.prototype.outerXml = function(writer){
	return this._serialize(writer, false, false, false);
}

iwfXmlNode.prototype.innerXml = function(writer){
	return this._serialize(writer, true, false, true);
}

iwfXmlNode.prototype.outerHtml = function(writer) {
	return this._serialize(writer, false, true, false);
}

iwfXmlNode.prototype.innerHtml = function(writer) {
	return this._serialize(writer, true, true, true);
}

iwfXmlNode.prototype._serialize = function(writer, pretty, asHtml, innerOnly){
	var pretty = false;
	if (iwfIsBoolean(writer)){
		pretty = writer;
		writer = false;
	}
	if (!writer){
		writer = new iwfWriter(true, pretty, asHtml);
	}

	if (!innerOnly){
		writer.writeNodeOpen(this.nodeName);
		for(var i=0;i<this.attributes.length;i++){
			writer.writeAttribute(i == 0, this.attributeNames[i], this.attributes[i], this.attributeQuotes[i]);
		}
	}

	writer.writeText(this._text, false);
	for(var i=0;i<this.childNodes.length;i++){
		this.childNodes[i].outerXml(writer);
	}

	if (!innerOnly){
		writer.writeNodeClose();
	}


	return writer.toString();
}

iwfXmlNode.prototype.indexOfNode = function(node){
	for (var i = 0; i < this.childNodes.length; i++) {
		if (this.childNodes[i] == node) {
			return i;
		}
	}
	return -1;
}

iwfXmlNode.prototype.removeAttribute = function(name){
	for(var i=0;i<this.attributeNames.length;i++){
		if (this.attributeNames[i] == name){

			// found the attribute. splice it out of all the arrays.
			this.attributeQuotes.splice(i, 1);
			this.attributeNames.splice(i, 1);
			this.attributes.splice(i, 1);
			// remove from our object and jump out of the loop
			delete this[name];
			break;
		}
	}
}

iwfXmlNode.prototype.removeChildNode = function(node){
	for(var i=0;i<this.childNodes.length;i++){
		if (node == this.childNodes[i]){
			// remove from childNodes array
			this.childNodes.splice(k, 1);
			var arr = this[node.nodeName];
			if (arr){
				if (arr.length > 1){
					var j = 0;
					for(j=0;i<arr.length;i++){
						if (arr[j] == node){
							arr.splice(j, 1);
							break;
						}
					}
				} else {
					delete arr;
				}
			}
			break;
		}
	}
}

iwfXmlNode.prototype.addAttribute = function(name, val, quotesToUse){
	if (!quotesToUse){
		// assume apostrophes
		quotesToUse = "'";
	}
	if (!this[name]){
		this.attributeQuotes.push(quotesToUse);
		this.attributeNames.push(name);
		this.attributes.push(val);
	}
	this[name] = val;

	//the attribute is a namespace declaration, so store it once.
	if (name.indexOf("xmlns__") == 0 && name.length > 7) {
		var prefix = name.split("__")[1];
		if (!this.namespaces[prefix]) {
			this.namespaces[prefix] = val;
		}
	}
}

iwfXmlNode.prototype.addChildNode = function(node, nodeName){
	if (nodeName && nodeName.length > 0 && nodeName.indexOf("#") == 0){
		var txt = this.trim(node);
		node = new iwfXmlNode('', this);

		// 2006-08-15 thanks Val for reporting the namespace bug...
		node.origNodeName = nodeName;
		// iwfLog('addChildNode before: ' + this.origNodeName + ' - ' + this.nodeName);
		node.nodeName = this.normalize(nodeName);
		// iwfLog('addChildNode after: ' + this.origNodeName + ' - ' + this.nodeName);

		node._text = txt;


		// push it onto the childNodes array
		this.childNodes.push(node);

	} else {

		// make a node object out of it if they gave us a string...
		if (typeof(node) == 'string'){
			node = new iwfXmlNode(node, this);
		}

		if (node.nodeName.indexOf("#") == 0){
			// is a special node -- duplicate names may exist...
			node._text = txt;

			// push it onto the childNodes array
			this.childNodes.push(node);

		} else {

			if (!this[node.nodeName]){
				// no other child node with this name exists yet.

				// make a new array off of our object
				this[node.nodeName] = new Array();
			}
			// push it onto our object's array
			this[node.nodeName].push(node);

			// push it onto the childNodes array
			this.childNodes.push(node);
		}
	}
}

iwfXmlNode.prototype.getText = function(){
	var txt = '';

	if (this.nodeName.indexOf('#') == 0){
		txt = this._text;
	}
	for(var i=0;i<this.childNodes.length;i++){
		txt += this.childNodes[i].getText();
	}
	return txt;
}

iwfXmlNode.prototype.setText = function(newText){
	//Node is already a text node
	if (this.nodeName.indexOf('#') == 0){
		this._text = newText;
	}

	//Node is an element, either create the new text node or update it
	else if (this.childNodes.length == 0) {
		this.addChildNode(newText, "#text");
	} else {
		this.childNodes[0].setText(newText);
	}
}

iwfXmlNode.prototype.findNSByPrefix = function(prefix){
	if (this.namespaces && this.namespaces[prefix]) {
		return this.namespaces[prefix];
	} else if (this._parent) {
		return this._parent.findNSByPrefix(prefix);
	} else {
		return null;
	}
}

iwfXmlNode.prototype._parseXml = function(xml){

	var remainingXml = xml;

	while(remainingXml.length > 0){
		var type = this._determineNodeType(remainingXml);
		switch(type){
			case 'open':
				remainingXml = this._parseName(remainingXml);
				remainingXml = this._parseAtts(remainingXml);
				if (this._parent){
					this._parent.addChildNode(this, null);
				}

				if (!this._isEmpty){
					remainingXml = this._parseChildren(remainingXml);
					// we still need to parse out the close node, so don't jump out yet!
				} else {
					return remainingXml;
				}
				break;

			case 'text':
				return this._parseText(remainingXml);

			case 'close':
				return this._parseClosing(remainingXml);

			case 'end':
				return '';

			case 'pi':
				// return this._parsePI(remainingXml);
				remainingXml = this._parsePI(remainingXml);
				break;

			case 'comment':
				return this._parseComment(remainingXml);

			case 'cdata':
				return this._parseCDATA(remainingXml);

			case 'directive':
				return this._parseDirective(remainingXml);

			case 'whitespace':
				remainingXml = this._parseWhitespace(remainingXml);
				if (this._parent){
					return remainingXml;
				}
				break;

			default:
				iwfLogFatal('IWF Xml Parsing Error: undefined type of ' + type + ' returned for xml starting with "' + remainingXml + '"', true);
				break;
		}

	}

	return '';

}

iwfXmlNode.prototype._determineNodeType = function(xml){


	if (!xml || xml.length == 0){
		return 'end';
	}

	var trimmed = this.ltrim(xml);

	var firstTrimmedLt = trimmed.indexOf("<");

	switch(firstTrimmedLt){
		case -1:
			// this is either insignificant whitespace or text
			if (trimmed.length == 0){
				return 'whitespace';
			} else {
				return 'text';
			}
		case 0:
			// this is either an open node or insignificant whitespace.
			var firstLt = xml.indexOf("<");
			if (firstLt > 0){
				return 'whitespace';
			} else {
				switch(trimmed.charAt(1)){
					case '?':
						return 'pi';
					case '!':
						if (trimmed.substr(0,4) == '<!--'){
							return 'comment';
						} else if (trimmed.substr(0,9) == '<![CDATA[') {
							return 'cdata';
						} else if (trimmed.toUpperCase().substr(0,9) == '<!DOCTYPE') {
							return 'directive';
						} else {
							return 'unknown: ' + trimmed.substr(0,10);
						}
					case '/':
						return 'close';
					default:
						return 'open';
				}
			}

		default:
			// this is a text node
			return 'text';
	}
}

iwfXmlNode.prototype._parseName = function(xml){
	// we know xml starts with <.

	// replace all carriage return and line feeds by a space
	xml = xml.replace("\r\n", " ");
	// replace all line feeds by a space
	xml = xml.replace("\n", " ");

	var firstSpace = xml.indexOf(" ");
	var firstApos = xml.indexOf("'");
	var firstQuote = xml.indexOf('"');
	var firstGt = xml.indexOf(">");

	if (firstGt == -1){
		iwfLogFatal("IWF Xml Parsing Error: Bad xml; no > found for an open node.", true);
		return '';
	}

	// see if it's an empty node...
	if (xml.charAt(firstGt-1) == '/'){
		this._isEmpty = true;
	}

	// see if there is a possibility that atts exist
	if (firstSpace > firstGt || firstSpace == -1){
		if (this._isEmpty){
// <h1/>
			this.nodeName = this.trim(xml.substr(1,firstGt-2));
		} else {
// <h1>
			this.nodeName = this.trim(xml.substr(1,firstGt-1));
		}
		this.origNodeName = this.nodeName;
		if (this.nodeName && this.nodeName.length > 0){
			this.nodeName = this.normalize(this.nodeName);
		}

		//store the prefix and localName separately if the element is namespaced
		this._parseQName(this.nodeName);

		// iwfLog('_parseName: ' + this.origNodeName + ' - ' + this.nodeName);

		// return starting at > so parseAtts knows there are no atts.
		return xml.substr(firstGt);
	} else {
// <h1 >
// <h1 att='val'>
// <h1 att='val'/>
		this.nodeName = this.trim(xml.substr(1,firstSpace-1));
		this.origNodeName = this.nodeName;
		if (this.nodeName && this.nodeName.length > 0){
			this.nodeName = this.normalize(this.nodeName);
		}

		//store the prefix and localName separately if the element is namespaced
		this._parseQName(this.nodeName);

		// iwfLog('_parseName (space): ' + this.origNodeName + ' - ' + this.nodeName);

		// eat everything up to the space, return the rest
		return xml.substr(firstSpace);
	}

}

iwfXmlNode.prototype._parseQName = function(qname){
	if (qname && qname.indexOf("__") > 0) {
		var parts = qname.split("__");
		this.prefix = parts[0];
		this.localName = parts[1];
	} else {
		this.localName = qname;
	}
}

iwfXmlNode.prototype._parseAtts = function(xml){


	xml = this.ltrim(xml);
	var firstGt = xml.indexOf(">");
	if (firstGt == -1){
		iwfLogFatal("IWF Xml Parsing Error: Bad xml; no > found when parsing atts for " + this.nodeName, true);
		return '';
	} else if (firstGt == 0){
		// no atts.
		return xml.substr(firstGt+1);
	} else {
		// at least one att exists.
		var attxml = xml.substr(0, firstGt);
		var re = /\s*?([^=]*?)=(['"])(.*?)(\2)/;
		var matches= re.exec(attxml);
		while(matches){
			attxml = this.ltrim(attxml.substr(matches[0].length));
			var attname = this.normalize(this.ltrim(matches[1]));
			var attval = matches[3];
			var quotesToUse = matches[2];
			this.addAttribute(attname, attval, quotesToUse);

			re = /\s*?([^=]*?)=(['"])(.*?)(\2)/;
			matches = re.exec(attxml);
		}


		// return everything after the end of the att list and the > which closes the start of the node.
		return xml.substr(firstGt+1);
	}
}

iwfXmlNode.prototype._parseChildren = function(xml){
	// we are at the > which closes the open node.

	if (xml && xml.length > 0){
		var endnode = "</" + this.origNodeName + ">";
		while (xml && xml.length > 0 && xml.indexOf(endnode) > 0){

			// do not pass the xml to the constructor, as that may cause an infinite loop.
			var childNode = new iwfXmlNode('', this);
			xml = childNode._parseXml(xml);
		}

		// note we don't cut off the close node here, as the _parseXml function will do this for us.

	}

	return xml;
}



iwfXmlNode.prototype._parseClosing = function(xml){
	var firstGt = xml.indexOf("</");
	if (firstGt == -1){
		iwfLogFatal('IWF Xml Parsing Error: Bad xml; no </' + this.origNodeName + ' found', true);
		return '';
	} else {
		var firstLt = xml.indexOf(">",firstGt);
		if (firstLt == -1){
			iwfLogFatal('IWF Xml Parsing Error: Bad xml; no > found after </' + this.origNodeName, true);
			return '';
		} else {
			var result = xml.substr(firstLt+1);
			return result;
		}
	}
}

iwfXmlNode.prototype._parseText = function(xml){
	return this._parsePoundNode(xml, "#text", "", "<", false);
}

iwfXmlNode.prototype._parsePI = function(xml){
	var result = this._eatXml(xml, "?>", true);
	return result;
}

iwfXmlNode.prototype._parseComment = function(xml){
	return this._parsePoundNode(xml, "#comment", "<!--", "-->", true);
}

iwfXmlNode.prototype._parseCDATA = function(xml){
	return this._parsePoundNode(xml, "#cdata", "<![CDATA[", "]]>", true);
}

iwfXmlNode.prototype._parseDirective = function(xml){
	return this._parsePoundNode(xml, '#directive', '<!DOCTYPE', '>', true);
}

iwfXmlNode.prototype._parseWhitespace = function(xml){
	var result = '';
	if (this._parent && this._parent.nodeName.toLowerCase() == 'pre'){
		// hack for supporting HTML's <pre> node. ugly, I know :)
		result = this._parsePoundNode(xml, "#text", "", "<", false);
	} else {
		// whitespace is insignificant otherwise
		result = this._eatXml(xml, "<", false);
	}
	return result;
}

iwfXmlNode.prototype._parsePoundNode = function(xml, nodeName, beginMoniker, endMoniker, eatMoniker){
	// simply slurp everything up until the first endMoniker, starting after the beginMoniker
	var end = xml.indexOf(endMoniker);
	if (end == -1){
		iwfLogFatal("IWF Xml Parsing Error: Bad xml: " + nodeName + " does not have ending " + endMoniker, true);
		return '';
	} else {
		var len = beginMoniker.length;
		var s = xml.substr(len, end - len);
		if (this._parent){
			this._parent.addChildNode(s, nodeName);
		}
		var result = xml.substr(end + (eatMoniker ? endMoniker.length : 0));
		return result;
	}
}

iwfXmlNode.prototype._eatXml = function(xml, moniker, eatMoniker){
	var pos = xml.indexOf(moniker);

	if (eatMoniker){
		pos += moniker.length;
	}

	return xml.substr(pos);

}

iwfXmlNode.prototype.trim = function(s){
	return s.replace(/^\s*|\s*$/g,"");
}

iwfXmlNode.prototype.ltrim = function(s){
	return s.replace(/^\s*/g,"");
}

iwfXmlNode.prototype.rtrim = function(s){
	return s.replace(/\s*$/g,"");
}

iwfXmlNode.prototype.normalize = function(nm){
//	return nm;
	return nm.replace(/:/gi, '__');
}

// -----------------------------------
// End: Xml Node Object
// -----------------------------------







// -----------------------------------
// Begin: Xml Writer Object
// -----------------------------------


// --------------------------------------------------------------------------
//! iwfWriter
//
//! Brock Weaver - brockweaver@users.sourceforge.net
//
//! v 0.1 - 2005-06-05
//! Initial release.
// --------------------------------------------------------------------------
// This class is meant to ease the creation of xml strings.
// Note it is not a fully-compliant xml generator.
// --------------------------------------------------------------------------


function iwfWriter(suppressProcessingInstruction, prettyPrint, htmlMode) {
	this._buffer = new String();
	if (!suppressProcessingInstruction){
		this.writeRaw("<?xml version='1.0' ?>");
	}
	this._nodeStack = new Array();
	this._nodeOpened = false;
	this._prettyPrint = prettyPrint;
	this._htmlMode = htmlMode;
}

iwfWriter.prototype.writePretty = function(writeLineFeed, writeTabs){
	if (this._prettyPrint){
		if (writeLineFeed){
			this.writeRaw('\n');
		}

		// assumption is most xml won't have a maximum node depth exceeding 30...
		if (writeTabs){
			var tabs = '\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t';
			while (tabs.length < this._nodeStack.length){
				// but some xml might exceed node depth of 30, so keep tacking on another 30 tabs until we have enough...
				// I know this is an awkward way of doing it, but I wanted to avoid looping and concatenating for "most" xml...
				tabs += tabs;
			}
			var depth = this._nodeStack.length;
			if (depth < 0) { depth = 0; }
			this.writeRaw(tabs.substr(0,depth));
		}
	}
}

iwfWriter.prototype.writeNode = function(name, txt){
	this.writeNodeOpen(name);
	this.writeText(txt, true);
	this.writeNodeClose();
}

iwfWriter.prototype.writeNodeOpen = function(name){
	if (this._nodeOpened){
		this.writeRaw(">");
	}
	this._nodeOpened = false;

	switch(name){
		case '#pi':
			this.writePretty(true, true);
			this.writeRaw('<?');
			break;
		case '#text':
			// do nothing
			break;
		case '#cdata':
			this.writePretty(true, false);
			this.writeRaw('<![CDATA[');
			break;
		case '#comment':
			this.writePretty(true, true);
			this.writeRaw('<!--');
			break;
		default:
			this.writePretty(true, true);
			this.writeRaw("<" + this.normalize(name));
			this._nodeOpened = true;
			break;
	}
	this._nodeStack.push(name);
}

iwfWriter.prototype.writeAttribute = function(first, name, val, quotes){
	if (!this._nodeOpened){
		iwfLogFatal("IWF Xml Parsing Error: Appending attribute '" + this.normalize(name) + "' when no open node exists!", true);
	}
	var attval = val;


	if (!quotes){
		quotes = "'";
	}

	// browsers handle html attributes in a special way:
	// only the character that matches the html delimiter for the attribute should be xml-escaped.
	// the OTHER quote character (' or ", whichever) needs to be javascript-escaped,
	//
	// the iwfXmlNode class also needs the > char escaped to be functional -- I need to fix that!
	// But for right now, this hack gets things working at least.
	//
	// Like I said, I'm getting lazy :)
	//

		if (this._htmlMode){
			// we're kicking out html, so xml escape only the quote character that matches the delimiter and the > sign.
			// We also need to javascript-escape the quote char that DOES NOT match the delimiter but is xml-escaped.
			if (quotes == "'"){
				// we're using apostrophes to delimit html.
				attval = attval.replace(/&quot;/gi, '\\"').replace(/'/gi, '&apos;').replace(/>/gi, '&gt;');
			} else {
				// we're using quotes to delimit html
				attval = attval.replace(/&apos;/gi, "\\'").replace(/"/gi, '&quot;').replace(/>/gi, '&gt;');
			}
		} else {
			attval = iwfXmlEncode(attval);
		}

	if (!first) { this.writePretty(true, true); }
	this.writeRaw(" " + this.normalize(name) + "=" + quotes + attval + quotes);
}

iwfWriter.prototype.writeAtt = function(first, name, val, quotes){
	this.writeAttribute(first, name, val, quotes);
}

iwfWriter.prototype.writeText = function(txt, encodeAsXml){
	if (!txt || txt.length == 0){
		// no text to write. do nothing.
		return;
	} else {
		if (this._nodeOpened){
			this.writeRaw(">");
			this._nodeOpened = false;
		}
//		this.writeRaw('[');
		this.writePretty(true, true);
		if (encodeAsXml){
			this.writeRaw(iwfXmlEncode(txt));
		} else {
			this.writeRaw(txt);
		}
//		this.writeRaw(']');
	}
}

iwfWriter.prototype.writeNodeClose = function(){
	if (this._nodeStack.length > 0){
		var name = this._nodeStack.pop();

		switch(name){
			case '#pi':
				this.writePretty(true, true);
				this.writeRaw("?>");
				break;
			case '#text':
				// do nothing
				break;
			case '#cdata':
				this.writePretty(true, true);
				this.writeRaw("]]>");
				break;
			case '#comment':
				this.writePretty(true, true);
				this.writeRaw("-->");
				break;
			default:
				if (this._nodeOpened){
					//! hack for <script /> and <div /> needing to be <script></script> or <div></div>
					switch(name){
						case 'script':
						case 'div':
							this.writeRaw("></" + this.normalize(name) + ">");
							break;
						default:
							this.writeRaw("/>");
							break;
					}
				} else {
					this.writePretty(true, true);
					this.writeRaw("</" + this.normalize(name) + ">");
				}
				break;
		}
		this._nodeOpened = false;
	}
}

iwfWriter.prototype.writeRaw = function(xml){
	this._buffer += xml;
}

iwfWriter.prototype.toString = function(){
	return this._buffer;
}

iwfWriter.prototype.clear = function(){
	this.buffer = new String();
}

iwfWriter.prototype.normalize = function(name){
	return name.replace(/__/gi, ':');
}
// -----------------------------------
// End: Xml Writer Object
// -----------------------------------


