/**
 *  type
 *      class
 *
 *  name
 *      SweetButty
 *
 *  version
 *      1.0
 *
 *  author
 *      Julian Turner, Ripley, Derbyshire, United Kingdom
 *
 *  copyright
 *      ŠJulian Turner - July 2007
 *
 *  description
 *      Provides simple parsing of SweetXML
 *      {@link http://innig.net/software/sweetxml/index.html}
 *  
 *  licence
 *      Licensed under the terms stated at
 *      {@link http://www.baconbutty.com/blog-entry.php?id=9} 
 *      SweetButty 1.0 is used by you at your sole risk.  
 *      SweetButty 1.0 is provided as is, without any promise, 
 *      warranty, condition, or guarantee of any kind relating to 
 *      its condition, qualtiy, compliance with description, 
 *      fitness for purpose, or freedom from bugs or errors. 
 *
 *  class-name
 *      classSweetButty()
 * 
 *  methods
 *      setIndentUnit(indentUnit : String) : void
 *          Usually 4 spaces or a tab 
 *
 *      parse(sweetXMLMarkup : String) : classSweetXMLNode
 *          Parses the markup, and returns a root node
 *          which contains each of the parsed nodes.
 *
 *  sub-classes 
 *      classSweetXMLNode
 *          nodeName : String
 *          nodeType : Number
 *          nodeValue : String
 *          parentNode : classSweetXMLNode
 *          [0]...[N] : classSweetXMLNode
 *              Child nodes
 *          <child-node-name> : classSweetXMLNode | String
 *              Short cut access to child nodes by name
 *          <attr-name> : String
 *
 **/var classSweetButty /*: Function*/ = (function()
{function classSweetButty()
{
	this.indentUnit /*: String*/;

	this.initialise();
}

var constr /*: Function*/ = classSweetButty;function initialise () /*: void*/
{
	this.indentUnit = "\t";
}

AddMethod(constr, initialise);function setIndentUnit(
	indentUnit /*: String*/
) /*: void*/
{
	this.indentUnit = indentUnit;
}

AddMethod(constr, setIndentUnit);function parse(
	markup /*: String*/
) /*: Node*/
{	

	var nodes /*: Array.<Node>*/ = this.splitIntoNodes(markup);

	var rootNode /*: Node*/ = this.nodesToTree(nodes);

	this.createTreeShortcuts(rootNode);

	return rootNode;
}

AddMethod(constr, parse);var NODE_INDENT = "(<nodeIndentPart>\\t+| +)*";
var NODE_NAME = "(<nodeNamePart>[^ \\t\\r\\n\"'=\:\!\#]*)";
var NODE_ATTR_DIVIDER = "([ \\t]*[\\r\\n]+[ \\t]*\\|[ \\t]*|[ \\t]+)";
var NODE_ATTR = "[^ \\t\\r\\n\"'=]+=(\"[^\"]*\"|'[^']*'|[\\w\\-\\.]+)";
var NODE_ATTRS = "(<nodeAttributesPart>(" + NODE_ATTR_DIVIDER + NODE_ATTR + ")*)";
var NODE_INLINE = "[ \\t]*(<nodeInlinePart>\\:)?";
var NODE_TEXT = "[ \\t]*(<nodeTextPart>!?\"[^\"]*\"|!?'[^']*'|!?[\\w\\-\\.]+)?";
var NODE_COMMENT = "(<nodeCommentPart>[ \\t]*\\#[^\\r\\n]+)?";
var NODE_PATTERN = 
	NODE_INDENT + 
	NODE_NAME + 
	NODE_ATTRS + 
	NODE_INLINE + 
	NODE_TEXT + 
	NODE_COMMENT + 
	"[ \\t]*(\\r\\n|\\n\\r|\\r|\\n|$)";

var NODE_REGEXP = new XRegExp(NODE_PATTERN, "kgi");var ATTR_PATTERN = "(<attrNamePart>[^ \\t\\r\\n\"'=]+)=(\"(<attrQuotePart>[^\"]*)\"|'(<attrAposPart>[^']*)'|(<attrSimplePart>[\\w\\-\\.]+))";
var ATTR_REGEXP = new XRegExp(ATTR_PATTERN, "kgi");var TEXT_PATTERN = "[ \\t]*(!?\"(<textQuotePart>[^\"]*)\"|!?'(<textAposPart>[^']*)'|!?(<textSimplePart>[\\w\\-\\.]+))";
var TEXT_REGEXP = new XRegExp(TEXT_PATTERN, "k");var COMMENT_PATTERN = "[ \\t]*\\#(<commentTextPart>[^\\r\\n]+)";
var COMMENT_REGEXP = new XRegExp(COMMENT_PATTERN, "k");function Node()
{
	this.isNode /*: Boolean*/ = true;

	this.depth /*: int*/ = 0;
	this.length /*: int*/ = 0;

	this.nodeType /*: int*/ = 0;
	this.nodeName /*: String*/ = "";
	this.nodeValue /*: String*/ = "";


	this.parentNode /*: Node*/ = null;
}function splitIntoNodes(
	markup /*: String*/
) /*: Array.<Node>*/
{	
	/* Reset RegExps */
	NODE_REGEXP.lastIndex = 0;	
	ATTR_REGEXP.lastIndex = 0;	
	TEXT_REGEXP.lastIndex = 0;	
	COMMENT_REGEXP.lastIndex = 0;	

	var execResult /*: Array*/;

	var nodes /*: Array.<Node>*/ = [];
	var node /*: Node*/;

	var nodeIndentPart /*: String*/ = "";
	var nodeNamePart /*: String*/ = "";
	var nodeAttributesPart /*: String*/ = ""; 
	var nodeInlinePart /*: String*/ = "";
	var nodeTextPart /*: String*/ = "";
	var nodeCommentPart /*: String*/ = "";

	var indentUnitLength /*: String*/ = this.indentUnit.length;

	var attrExecResult /*: Array*/ = [];
	var attrName /*: String*/ = "";
	var attrValue /*: String*/ = "";

	var textExecResult /*: Array*/ = [];
	var text /*: String*/ = "";
	var textNode /*: Object*/;

	var commentExecResult /*: Array*/ = [];
	var commentNode /*: Object*/;
	
	var overflow /*: int*/ = 100000;

	while(execResult = this.exec(NODE_REGEXP, markup))
	{
		/* Clear*/
		node = null;
		textNode = null;
		commentNode = null;

		/* At end */
		if (execResult.index == markup.length)
		{
			break;
		}

		if (!overflow--)
		{
			break;
		}

		/* Empty */
		if (!this.trim(execResult[0]))
		{
			continue;
		}

		/* At End */
		if (execResult.lastIndex == markup.length)
		{
			break;
		}


		//alert(this.execResultToString(execResult));

		/* Get Node Parts*/
		nodeIndentPart = execResult.nodeIndentPart;
		nodeNamePart = this.trim(execResult.nodeNamePart);
		nodeAttributesPart = this.trim(execResult.nodeAttributesPart); 
		nodeInlinePart = this.trim(execResult.nodeInlinePart);
		nodeTextPart = this.trim(execResult.nodeTextPart);
		nodeCommentPart = this.trim(execResult.nodeCommentPart);

		/* Node*/
		node = new Node();

		/* Calculate Depth*/
		node.depth = Math.ceil(nodeIndentPart.length / indentUnitLength) + 1;

		//alert(nodeIndentPart.length);
		
		/* Node Type*/
		if (nodeNamePart)
		{
			node.nodeType = 1;
			node.nodeName = nodeNamePart;
		}
		else if (nodeTextPart)
		{
			if (/^!/.test(nodeTextPart))
			{
				node.nodeType = 4;
				node.nodeName = "#cdata-section";
			}
			else
			{
				node.nodeType = 3;
				node.nodeName = "#text";
			}
		}
		else if (nodeCommentPart)
		{
			node.nodeType = 8;
			node.nodeName = "#comment";
		}
		else
		{
			continue;
		}

		/* Node Attributes*/
		if (node.nodeType == 1 && nodeAttributesPart)
		{
			while(attrExecResult = this.exec(ATTR_REGEXP, nodeAttributesPart))
			{
				attrName = attrExecResult.attrNamePart; 
				attrValue = attrExecResult.attrQuotePart || attrExecResult.attrAposPart || attrExecResult.attrSimplePart;
				attrValue = this.unescapeAttrHTML(attrValue);
	
				node[attrName] = attrValue;
			}			
		}

		/* Inline Extras */
		if (node.nodeType == 1 && !nodeInlinePart)
		{
			nodes[nodes.length] = node;
			continue;
		}

		/* Look for inline parts */
		nodes[nodes.length] = node;

		/* Text */
		if (nodeTextPart)
		{
			textExecResult = this.exec(TEXT_REGEXP, nodeTextPart)
			
			if ((node.nodeType == 3 || node.nodeType == 4) && !textExecResult)
			{
				continue;
			}

			text = textExecResult.textQuotePart || textExecResult.textCommentPart || textExecResult.textSimplePart;
			text = this.unescapeAttrHTML(text);

			if (node.nodeType == 1)
			{
				textNode = new Node();
				textNode.depth = node.depth + 1; 

				if (/^!/.test(nodeTextPart))
				{
					textNode.nodeType = 4;
					textNode.nodeName = "#cdata-section";
				}
				else
				{
					textNode.nodeType = 3;
					textNode.nodeName = "#text";
				}

				textNode.nodeValue = text;

				nodes[nodes.length] = textNode;
			}
			else if (node.nodeType == 3 || node.nodeType == 4)
			{
				node.nodeValue = text;
			}
		}

		/* Comment */
		if (nodeCommentPart)
		{
			commentExecResult = this.exec(COMMENT_REGEXP, nodeCommentPart);
	
			text = this.trim(commentExecResult.commentTextPart);
			text = this.unescapeAttrHTML(text);

			if (node.nodeType == 1)
			{
				commentNode = new Node();
				commentNode.depth = node.depth + 1; 
				commentNode.nodeType = 8;
				commentNode.nodeName = "#comment";
				commentNode.nodeValue = text;

				nodes[nodes.length] = commentNode;
			}
			else if (node.nodeType == 3 || node.nodeType == 4)
			{
				commentNode = new Node();
				commentNode.depth = node.depth; 
				commentNode.nodeType = 8;
				commentNode.nodeName = "#comment";
				commentNode.nodeValue = text;

				nodes[nodes.length] = commentNode;
			}
			else if (node.nodeType == 8)
			{
				node.nodeValue = text;
			}

		}	
	}

	return nodes;
}

AddMethod(constr, splitIntoNodes);function nodesToTree(
	nodes /*: Array.<Node>*/
) /*: Node*/
{	
	var root /*: Node*/ = new Node();
	root.depth = 0;
	root.nodeType = 1;
	root.nodeName = "root";

	var node /*: Node*/;
	var parents /*: Array*/ = [root];
	var parent /*: Object*/;
	var currentParent /*: Object*/ = root;
	var depth /*: int*/ = 0;
	var lastDepth /*: int*/ = 1;
	var lastElement /*: Object*/ = root;
	var depthDifference /*: int*/ = 0;
	var nodeName /*: String*/;

	for (var i = 0; i < nodes.length; i++)
	{
		node = nodes[i];
		
		if (node.depth == lastDepth)
		{
			currentParent[currentParent.length++] = node;
			node.parentNode = currentParent;
		}
		else if (node.depth > lastDepth)
		{
			node.depth = lastDepth + 1;
			lastDepth = node.depth;

			if (currentParent != lastElement)
			{
				currentParent = lastElement;
				parents.push(lastElement);
			}

			currentParent[currentParent.length++] = node;
			node.parentNode = currentParent;
		} 
		else
		{
			depthDifference = lastDepth - node.depth;
			
			while (depthDifference--)
			{
				parents.pop();
			}
	
			currentParent = parents[parents.length - 1];
			lastDepth = node.depth;

			currentParent[currentParent.length++] = node;
			node.parentNode = currentParent;
		}

		if (node.nodeType == 1)
		{
			lastElement = node;
		}

		//if (node.nodeType == 3 || node.nodeType == 4)
		//{
		//	for (var j = 0; j < parents.length; j++)
		//	{
		//		parents[j].nodeValue += node.nodeValue;
		//	}
		//}
	}

	nodes.unshift(root);
	return root;

	//if (root.length == 0)
	//{
	//	return null;
	//}
	//else if (root.length == 1)
	//{
	//	root[0].parentNode = null;
	//	return root[0];
	//}
	//else
	//{
	//	return root;
	//}
}

AddMethod(constr, nodesToTree);function createTreeShortcuts (
	node /*: Node*/
) /*: Node*/
{
	//alert(node.nodeName + " " + node.length);

	var child /*: Node*/ = node[0];
	var parent /*: Node*/ = node.parentNode;
	var nodeName /*: String*/ = node.nodeName;

	if (parent && node.length == 1 && child && (child.nodeType == 3 || child.nodeType == 4))
	{
		if (typeof parent[nodeName] == "undefined")
		{
			parent[nodeName] = child.nodeValue;
		}
		else if (typeof parent[nodeName] == "string")
		{
			var a /*: Array*/ = [parent[nodeName]];
			a.push(child.nodeValue);
			parent[nodeName] = a;
		}
		else if (typeof parent[nodeName] == "object")
		{
			var parentProperty = parent[nodeName];

			if (parentProperty.isNode)
			{
				var a /*: Array*/ = [parent[nodeName]];
				a.push(node);
				parent[nodeName] = a;
			}
			else
			{
				if (typeof (parentProperty[0]) == "string")
				{
					parentProperty.push(child.nodeValue);
				}
				else
				{
					parentProperty.push(node);
				}
			}
		}	

		return;
	}
	else if (parent)
	{
		if (typeof parent[nodeName] == "undefined")
		{
			parent[nodeName] = node;
		}
		else if (typeof parent[nodeName] == "string")
		{
			var a /*: Array*/ = [parent[nodeName]];
			a.push(node);
			parent[nodeName] = a;
		}
		else if (typeof parent[nodeName] == "object")
		{
			var parentProperty = parent[nodeName];

			if (parentProperty.isNode)
			{
				var a /*: Array*/ = [parent[nodeName]];
				a.push(node);
				parent[nodeName] = a;
			}
			else
			{
				parentProperty.push(node);
			}
		}
	}

	if (node.length == 0)
	{
		return;
	}

	for (var i = 0; i < node.length; i++)
	{
		child = node[i];

		if (child.nodeType == 1)
		{
			this.createTreeShortcuts(child);
		}
	}
}

AddMethod(constr, createTreeShortcuts);function trim(
	s /*: String*/
) /*: String*/
{
	return s.replace(/^\s\s*/, "").replace(/\s*\s$/,"");
}

AddMethod(constr, trim);function escapeAttrHTML(
	string /*: String*/
) /*: String*/
{
	return string.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
}

AddMethod(constr, escapeAttrHTML);function exec(
	re /*: XRegExp*/,
	input /*: String*/
) /*: Array*/
{
	var execResult /*: Array*/ = re.exec(input);

	if (execResult === null)
	{
		return execResult;
	}

	for (var k /*: String*/ in execResult)
	{
		if (typeof execResult[k] == "undefined" || execResult[k] == "undefined")
		{
			execResult[k] = "";
		}
	}

	return execResult;
}
AddMethod(constr, exec);function unescapeAttrHTML(
	string /*: String*/
) /*: String*/
{
	return string.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, "\"").replace(/&apos;/g, "'").replace(/&amp;/g, "&");
}

AddMethod(constr, unescapeAttrHTML);function escapeHTML(
	string /*: String*/
) /*: String*/
{
	return string.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}

AddMethod(constr, escapeHTML);function unescapeHTML(
	string /*: String*/
) /*: String*/
{
	return string.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
}

AddMethod(constr, unescapeHTML);function execResultToString(
	execResult /*: Array*/
) /*: String*/
{
	if (!execResult)
	{
		return "";
	}
	var a = ["Result","======="];

	for (var key in execResult)
	{
		a.push(key + " : |" + execResult[key] + "|");
	}

	a.push("Substr : |" + execResult.input.substring(execResult.index, execResult.lastIndex) + "|");

	return a.join("\r\n");
}

AddMethod(constr, execResultToString);function nodeListToString(
	nodes /*: Array.<Nodes>*/
) /*: String*/
{
	var a /*: Array.<String>*/ = [];
	var node;

	for (var i /*: String*/ = 0; i < nodes.length; i++)
	{
		node = nodes[i];
		var nodeString /*: String*/ = [];
		
		for (var k in node)
		{
			nodeString.push(k + "            ".substring(0,12 - k.length) + "\t: " + "\"" + node[k] + "\"");
		}

		a.push(nodeString.join("\r\n"));
	}

	return a.join("\r\n\r\n==============================\r\n");
}

AddMethod(constr, nodeListToString);function ExtendClass(child, parent)
{
	child.prototype = new parent();
}

function AddMethod(constr, func)
{
	var re = /^\s*function\s+(\w+)/;
	var name = re.exec(func.toString())[1];
	constr.prototype[name] = func;
}return constr;

})();

//makeModulePublicClass(classSweetXML);
