// Ver .50 Feb 28 1998
//////////////////////////////////////////////////////////////
//
//	Copyright 1998 Jeremie
//	Free for public non-commercial use and modification
//	as long as this header is kept intact and unmodified.
//	Please see http://www.jeremie.com for more information
//	or email jer@jeremie.com with questions/suggestions.
//
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
////////// Simple XSL Processing Library //////////////////////
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
///////   to do items (I know it's a lot, but the core works)
// > Make the code more understandable
// > Rethink complex parts of the code
// > Patterns:
// >   Any Wildcards
// >   Element Positioning/Uniqueness
// >   ID/Class
// >   Precedence
// > Actions:
// >   Macros
// >   Selection:
// >      Children/Descendants
// >      Filters
// >      Multiple Selects
// > Scripting(eval, values, functions)
// > Modes
// > Default Rule
// > Styles:
// >   style-rule
// >   Named
// >   Inline


/////////////////////////


//// HTML tags with no closing tag
var _tags = new Array();
_tags["AREA"] = true;
_tags["BASE"] = true;
_tags["BASEFONT"] = true;
_tags["BGSOUND"] = true;
_tags["BR"] = true;
_tags["COL"] = true;
_tags["COLGROUP"] = true;
_tags["FRAME"] = true;
_tags["HR"] = true;
_tags["IMG"] = true;
_tags["INPUT"] = true;
_tags["ISINDEX"] = true;
_tags["LINK"] = true;
_tags["META"] = true;
_tags["PARAM"] = true;
_tags["WBR"] = true;

//// Common HTML attributes with no value
var _attr = new Array();
_attr["NOHREF"] = true;
_attr["NOTAB"] = true;
_attr["COMPACT"] = true;
_attr["NOWRAP"] = true;
_attr["NORESIZE"] = true;
_attr["NOSHADE"] = true;
_attr["CONTROLS"] = true;
_attr["CHECKED"] = true;
_attr["DECLARE"] = true;
_attr["SHAPES"] = true;
_attr["SELECTED"] = true;

//// Internal Global Variables
var _xmlGrove = new Array();
var _xslGrove = new Array();

//// Main public function to return HTML string from XML based on stylesheet
function Sparse(xml,xsl)
{
	_xmlGrove = Xparse(xml);
	_xslGrove = Xparse(xsl);
	_rules = govern();
	return doElement(_xmlGrove);
}

//// Main recursive function to process XML element and return HTML
function doElement(e)
{
	if(e.type == "chardata")
	{
		return e.value;
	}
	else
	{
		if(e.type == "element")
		{

//alert("E:" + e.name);
//alert("FLOW:" + e.flow.length);

			return doFlow(e,e.flow);
		}
	}
}

//// recursive function to process the element and an array of flow objects
function doFlow(e,flow)
{
	var ret = new String();
	for(var i = 0; i < flow.length; i++)
	{
		if(flow[i].type == "chardata")
		{
			ret += flow[i].value;
		}
		if(flow[i].type == "element")
		{
			if(flow[i].name == "children")
			{
				for(var j = 0; j < e.contents.length; j++)
				{
					ret += doElement(e.contents[j]);
				}
			}
			else
			{
				ret += printFlow(e,flow[i]);
			}
		}
	}
	return ret;
}

//// actually print the right HTML for the flow object
function printFlow(e,flowobj)
{
	var ret = new String();
	var style = new Array();
	ret += "<" + flowobj.name;
	for(attrib in flowobj.attributes)
	{
		// lowercase attributes are assumed to be css
		if(attrib.toLowerCase() == attrib)
		{
			style[attrib] = flowobj.attributes[attrib];
			var isStyle = true;
		}
		else
		{
			if(_attr[attrib])
			{
				ret += " " + attrib;
			}
			else
			{
				ret += " " + attrib + "=\"" + flowobj.attributes[attrib] + "\"";
			}
		}
	}
	if(isStyle)
	{
		ret += " STYLE=\"";
		for(prop in style)
		{
			ret += prop + ":" + style[prop] + ";";
		}
		ret += "\"";
	}
	ret += ">";
	ret += doFlow(e,flowobj.contents);
	if(!_tags[flowobj.name]){
		ret += "</" + flowobj.name + ">";
	}

	return ret;
}


//// pre-processor of the XSL object tree to associate the elements with
//// their flow objects
function govern()
{
	var subGrove = new Array();
	var allGrove = new Array();
	// collect an array of all of the xml elements
	for(e in _xmlGrove.index)
	{
		_xmlGrove.index[e].flow = new Array();
		allGrove[allGrove.length] = _xmlGrove.index[e];
	}
	for(var i = 0; i < _xslGrove.contents.length; i++)
	{
		if(_xslGrove.contents[i].type == "element" && _xslGrove.contents[i].name == "rule")
		{
//alert("previous subgrove length:" + subGrove.length);
			subGrove = new Array();
			for(var j = 0; j < _xslGrove.contents[i].contents.length; j++)
			{
				var ispattern = false;
				if(_xslGrove.contents[i].contents[j].type == "element" && (_xslGrove.contents[i].contents[j].name == "target-element" || _xslGrove.contents[i].contents[j].name == "element"))
				{
					subGrove = match(_xslGrove.contents[i].contents[j],allGrove);
					ispattern = true;
				}
				if(_xslGrove.contents[i].contents[j].type == "element" && _xslGrove.contents[i].contents[j].name == "root")
				{
					subGrove = new Array();
					subGrove[0] = _xmlGrove;
					ispattern = true;
				}
				for(var k = 0; k < subGrove.length && !ispattern; k++)
				{
					if(!subGrove[k].flowed)
					{
						subGrove[k].flow[subGrove[k].flow.length] = _xslGrove.contents[i].contents[j];
					}
				}
			}
			for(var k = 0; k < subGrove.length; k++)
			{
				subGrove[k].flowed = true;
			}
		}
	}
	return "";
}


//pass it the current parsed "<element/>" or "<target-element/>"
//and pass it an array of available XML elements
function match(e,subGrove)
{
//alert("Matching " + e.attributes["type"] + " with " + subGrove.length + " available elements");
	var newGrove = new Array();
	var returnGrove = new Array();
	var childGrove = new Array();
	//check all available and only match the right ones
	for(var i = 0; i < subGrove.length; i++)
	{
//alert("Matched " + newGrove.length + " Checking " + subGrove[i].name);
//alert(e.attributes["type"] == subGrove[i].name);
		//if this XSL rule matches the XML element, add it to thearray
		if(String(e.attributes["type"]) == subGrove[i].name || !Boolean(e.attributes["type"]))
		{
			var attribsmatch = true;
			for(var j = 0; j < e.contents.length; j++)
			{
//alert("doing attribute check");
				if(e.contents[j].type == "element" && e.contents[j].name == "attribute")
				{
					if(subGrove[i].attributes[e.contents[j].attributes["name"]] != e.contents[j].attributes["value"])
					{
						attribsmatch = false;
					}
				}
			}
			if(attribsmatch)
			{
//alert("saving element");
				newGrove[newGrove.length] = subGrove[i];
			}
		}
	}
//alert("Total Matched " + newGrove.length);

	//build an array of the pattern element's sub elements
	var esube = new Array();
	for(var i = 0; i < e.contents.length; i++)
	{
		if(e.contents[i].type == "element" && (e.contents[i].name == "element" || e.contents[i].name == "target-element"))
		{
			esube[esube.length] = e.contents[i];
		}
	}

	if(esube.length > 0)
	{
		// process each possible matched element seperately
		for(var i = 0; i < newGrove.length; i++)
		{
			var egood = true;
			// build an array of it's children elements
			var ekids = new Array();
			for(var j = 0; j < newGrove[i].contents.length; j++)
			{
				if(newGrove[i].contents[j].type == "element")
				{
					ekids[ekids.length] = newGrove[i].contents[j];
				}
			}
			// check it against each sub element
			for(var j = 0; j < esube.length; j++)
			{
				var tempGrove = match(esube[j],ekids);
				if(tempGrove.isTarget)
				{
					returnGrove = tempGrove;
				}
				else
				{
					if(tempGrove.length == 0)
					{
						egood = false;
					}
				}
			}
			if(!tempGrove.isTarget && egood)
			{
				returnGrove[returnGrove.length] = newGrove[i];
			}
		}
	}
	else
	{
		returnGrove = newGrove;
	}

	if(e.name == "target-element")
	{
		//signal that we are returning the authortative array
		returnGrove.isTarget = true;
	}

	return returnGrove;
}

