	/********************************************************************************
	 *
	 */
	 
	function Tree_NodeReferenceStep(relation, id, index)
	{
		this.relation = relation ;
		this.id = id ;
		this.index = index ;
		
		this.getRelation = function() { return this.relation; }
		this.getId = function() { return this.id ; };
		this.getIndex = function() { return this.index ; };
		this.toString = function() { return this.relation + "::" + this.id + ":" + this.index; };
	}
	
	/********************************************************************************
	 *
	 */
	 
	function Tree_NodePathStep(relation, id)
	{
		this.relation = relation ;
		this.id = id ;
		
		this.getRelation = function() { return this.relation; }
		this.getId = function() { return this.id ; };
		this.toString = function() { return this.relation + "::" + this.id; };
	}
	
	/********************************************************************************
	 *
	 */
	 
	function Tree_NodeReference(steps)
	{
		this.steps = steps ;
		if (this.steps == null)
			this.steps = new Array();
	
		this.getDeep = function() { return this.steps.length; };	
		this.getSteps = function() { return this.steps; };
		this.getStep = function(deep) { return this.steps[deep]; };
		
		/*
		 *
		 */
		this.toString = function()
		{
			var i, str;
			
			str = "" ;
			if (this.steps.length > 0)
				for (i=0; i<this.steps.length ; i++)
					str += this.steps[i].toString() + ".";
			
			return str ;
		};
	}

	/********************************************************************************
	 *
	 */
	 
	function Tree_NodePath(steps)
	{
		this.steps = steps ;

		this.getSteps = function() { return steps; }
		this.getDeep = function() { return steps.length; }
		this.getStep = function(deep) { return steps[deep]; } 
		
		this.getSubPath = function(deep) 
		{ 
			var i, steps = new Array();
			for (i=deep; i<this.steps.length; i++)
				steps[steps.length] = this.steps[i];
			return new Tree_NodePath(steps);
		}
		
		this.toString = function()
		{    
			var i, str;
			str = "";
			for (i=0; i<this.steps.length; i++) 
			  	str += this.steps[i].toString();    
			return str;
		}
	}
		
	/********************************************************************************
	 *
	 */
	 
	function Tree_AttributeReference(nodeReference, attribute)
	{
		this.nodeReference = nodeReference ;
		this.attribute = attribute ;
		
		this.getNodeReference = function() { return this.nodeReference; }
		this.getAttribute = function() { return this.attribute; }
		this.toString = function() 
		{ 
			var str = "";
			if (this.nodeReference != null)
				str += this.nodeReference.toString();
			return str + "." + this.attribute; 
		}
	}

	/********************************************************************************
	 *
	 */
	
	var NODE_CHILD_SEPARATOR = "child" ;
	var NODE_RELATED_SEPARATOR = "related";
		 
	//-----------------
	
	function Tree_NodePathSintax(childNodeSeparator, relatedNodeSeparator, indexPrefix, indexSufix)
	{		
		this.childNodeSeparator = childNodeSeparator ;
		this.relatedNodeSeparator = relatedNodeSeparator ;
		this.indexPrefix = indexPrefix ;
		this.indexSufix = indexSufix ;
		
		this.bCheckFirstChild = (childNodeSeparator.length > relatedNodeSeparator.length);

		//----------------------------------------------------------------------------
		
		this.toNodePath = function(sPath)
		{
			if ((sPath == null) || (sPath.length <= 0))
				return null ;
				
			var i1, i2, iChild, iRelated, step, relation, bPosibleChild, bPosibleRelated ;
			
			var steps = new Array();
			
			iSize = sPath.length;
			i1 = 0;
			while (i1 < iSize)
			{
				// CHECK THE RELATION       
				
				bPosibleChild = String_startsWith(sPath, this.childNodeSeparator, i1);
				bPosibleRelated = String_startsWith(sPath, this.relatedNodeSeparator, i1);
				
				relation = null;
				
				if (this.bCheckFirstChild)
				{
					if (bPosibleChild) relation = NODE_CHILD_SEPARATOR;
					else if (bPosibleRelated) relation = NODE_RELATED_SEPARATOR;
				}
				else 
				{
					if (bPosibleRelated) relation = NODE_RELATED_SEPARATOR;
					else if (bPosibleChild) relation = NODE_CHILD_SEPARATOR;
				}

				if (relation == null)
					return null;
					
				if (relation == NODE_CHILD_SEPARATOR)
					i1 += this.childNodeSeparator.length;
				else
					i1 += this.relatedNodeSeparator.length;  

				// JUMP TO THE NEXT RELATION INDICATOR (-> NODE SEPARATOR), AND ANOTATE THE NODE

				iChild = sPath.indexOf(this.childNodeSeparator, i1);
				if (iChild < 0) iChild = iSize;
				iRelated = sPath.indexOf(this.relatedNodeSeparator, i1);
				if (iRelated < 0) iRelated = iSize; 
								
				i2 = Math.min(iChild,iRelated);
				id = sPath.substring(i1, i2);
				i1 = i2;
				
				// PROCESS THE NODE
				
				steps[steps.length] = new Tree_NodePathStep(relation, id);
			}
			
  			if ((steps == null) || (steps.length <= 0))
  				return null ;

			return new Tree_NodePath(steps);
		}
				
		//----------------------------------------------------------------------------
		
		this.toNodeReference = function(sRef)
		{
			if ((sRef == null) || (sRef.length <= 0))
				return null ;
				
			var i1, i2, k, iChild, iRelated, stepID, stepIdAndIndex, index, relation, bPosibleChild, bPosibleRelated ;
			
			var steps = new Array();
			
			iSize = sRef.length;
			i1 = 0;
			
			while (i1 < iSize)
			{
				// CHECK THE RELATION
				
				bPosibleChild = String_startsWith(sRef, this.childNodeSeparator, i1);
				bPosibleRelated = String_startsWith(sRef, this.relatedNodeSeparator, i1);
				
				relation = null;
				
				if (this.bCheckFirstChild)
				{
					if (bPosibleChild) relation = NODE_CHILD_SEPARATOR;
					else if (bPosibleRelated) relation = NODE_RELATED_SEPARATOR;
				}
				else 
				{
					if (bPosibleRelated) relation = NODE_RELATED_SEPARATOR;
					else if (bPosibleChild) relation = NODE_CHILD_SEPARATOR;
				}

				if (relation == null)
					return null;
					
				if (relation == NODE_CHILD_SEPARATOR)
					i1 += this.childNodeSeparator.length;
				else
					i1 += this.relatedNodeSeparator.length;

				// JUMP TO THE NEXT RELATION INDICATOR (-> NODE SEPARATOR), AND ANOTATE THE NODE

				iChild = sRef.indexOf(this.childNodeSeparator, i1);
				if (iChild < 0) iChild = iSize;
				iRelated = sRef.indexOf(this.relatedNodeSeparator, i1);
				if (iRelated < 0) iRelated = iSize;
				
				i2 = Math.min(iChild,iRelated);
				stepIdAndIndex = sRef.substring(i1, i2);
				i1 = i2;
				
				// PROCESS THE NODE
				
				k = stepIdAndIndex.lastIndexOf(this.indexPrefix);
  				if (k <= 0)
  					return null ;
  					
				stepId = stepIdAndIndex.substring(0,k);
  				stepIdAndIndex = stepIdAndIndex.substring(k+this.indexPrefix.length);
  				
  				k = stepIdAndIndex.lastIndexOf(this.indexSufix, k);
  				if (k <= 0)
  					return null ;
  					
				index = parseInt(stepIdAndIndex.substring(0,k));
  				if (index == NaN)
  					return null ;

				steps[steps.length] = new Tree_NodeReferenceStep(relation, stepId, index);
			}
			
  			if ((steps == null) || (steps.length <= 0))
  				return null ;			

  			
  			return new Tree_NodeReference(steps) ;
		}
		
		//----------------------------------------------------------------------------

		this.toAttributeReference = function(sRef)
		{
			if ((sRef == null) || (sRef.length <= 0))
				return null ;
	
			var iChild = sRef.lastIndexOf(this.childNodeSeparator);
			var iRelated = sRef.lastIndexOf(this.relatedNodeSeparator);
			
			if ((iChild < 0) && (iRelated < 0))
			{
				if (sRef.charAt(0) == "@")
					sRef = sRef.substring(1);
				return new Tree_AttributeReference(null, sRef);
			}
				
			var bChild, attribute ;
			
			if (iChild > iRelated)
				bChild = true ;
			else if (iRelated > iChild)
				bChild = false ;
			else 
				bChild = bCheckFirstChild;
			
			if (bChild)
			{
				i = iChild ;
				attribute = sRef.substring(iChild+this.childNodeSeparator.length);
			}
			else if (iRelated > iChild)
			{
				i = iRelated ;
				attribute = sRef.substring(iRelated+this.relatedNodeSeparator.length);
			}
				
			// END
			
			if (attribute.charAt(0) == "@")
				attribute = attribute.substring(1);
  			
  			return new Tree_AttributeReference(this.toNodeReference(sRef.substring(0,i)), attribute) ;
		}
	}
  
	var NODEPATHSINTAX_HTML = new Tree_NodePathSintax(".", "..", ":", "");
	var NODEPATHSINTAX_XML = new Tree_NodePathSintax("/", "//", "[", "]");
  
   /************************************************************************************************
    *
    */
   function AttributeTree(id)
   {
   	this.id = id ;
   	this.attributes = new Array();
   	this.childs = new Array();
   	this.childsByID = new Array();	// CONVENIENCE REDUNDANT ARRAY FOR KEEP THE SUBTREES GROUPED BY ID
   	
   	this.getID = function() { return this.id; };
   	this.setAttribute = function(name, value) { if (value==null) value=""; this.attributes[name] = value ; };
   	this.getAttribute = function(name) { return this.attributes[name]; }
   	this.getChilds = function() { return this.childs; }
   	this.getChildsById = function(sId) { return this.childsByID[sId]; }
   	this.addChild = AttributeTree_addChild ;
   	this.getChild = AttributeTree_getChild ;
   	this.getAttributeByReference = AttributeTree_getAttributeByReference;
   	this.setAttributeByReference = AttributeTree_setAttributeByReference;
   	this.getDescendantsByPath = AttributeTree_getDescendantsByPath;
   	this.getDescendantByReference = AttributeTree_getDescendantByReference;
   	this.toXML = AttributeTree_toXML ;
   	this.toString = AttributeTree_toString ;
   }
   
   /*
    *
    */
   function AttributeTree_addChild(child) 
   { 
   	this.childs[this.childs.length] = child ;
   	
   	// ADD THE SUBTREE TO THE ID-GROUPED SET
   	var sId = child.getID();
   	var childs = this.childsByID[sId] ;
   	if (typeof(childs) != "object") 
   		childs = new Array();
   	childs[childs.length] = child ;
   	this.childsByID[sId] = childs ;
   }
   
   /*
    *
    */
    /*
   function AttributeTree_setChild(child, index) 
   { 
   	var i, childs, id, oldChild, bAl ;
   	
   	id = child.getID(); 	
   	childs = this.childsByID[id];
   	
   	if (childs == null)
   		childs = new Array();
 	   	 		
   	if (index < childs.length)
   	{
			oldChild = childs[index] ;
	   }
		else
		{
	   	// IF NECESARY, ADD TREES WITH THE SAME ID UNTIL REACH THE DESIRED INDEX
	   	for (i=childs.length; i<index ; i++)
	   	{
	   		childs[i] = new AttributeTree(id);
	   		this.childs[this.childs.length] = childs[i];
	   	}
	   	oldSubtree = null ;
	   }
   	
   	childs[index] = child ;
     	this.childsByID[id] = childs;
     	
   	this.childs[this.childs.length] = child;
   	
   	return oldChild;
   }
   */
   
   /*
    *
    */
   function AttributeTree_getChild(id, index) 
   { 
   	var childs = this.childsByID[id];
   	if (childs == null)
   		return null ;
   		
   	if (index >= childs.length)
   		return null ;
   		
   	return childs[index] ;
   }

   
   /*
    *
    */
   
   function AttributeTree_getDescendantsByPath(nodePath)
   {
   	if ((nodePath == null) || (nodePath.getDeep() <= 0))
   		return this.childs ;     		
     	
   	var step, stepId, rest, i, j ;
   	
   	var descendantsSet;
   	var requestedDescendants = new Array();

   	step = nodePath.getStep(0);
   	stepId = step.getId();
  
   	for (i=0; i<this.childs.length ; i++)
   	{	
   		if (this.childs[i].getID().toLowerCase() == stepId.toLowerCase())
   		{
   			if (nodePath.getDeep() == 1)
   				requestedDescendants[requestedDescendants.length] = this.childs[i];
   			else
   			{
   				descendantsSet = this.childs[i].getDescendantsByPath(nodePath.getSubPath(1));
   				for (j=0; j<descendantsSet.length ; j++)
   					requestedDescendants[requestedDescendants.length] = descendantsSet[j];
   			}
   		}
   	}
   
   	return requestedDescendants;
   }
   
   /* 
    *
    */
   function AttributeTree_getDescendantByReference(nodeRef)
   {
   	var descendant, i, step ;
   	
   	descendant = this ;
   	
   	if (nodeRef != null)
   	{
	   	for (i=0; i<nodeRef.getDeep(); i++)
	   	{
	   		step = nodeRef.getStep(i);
	   		descendant = descendant.getChild(step.getId(), step.getIndex());
	   		if (descendant == null)
	   			return null ;
	   	}
   	}
   
   	return descendant ;
   }
   
   /*
    *
    */
    
   function AttributeTree_getAttributeByReference(attribRef)
   {  	
   	if (attribRef == null)
   		return null ;
   		
   	var subtree = this.getDescendantByReference(attribRef.getNodeReference());
	  	if (subtree == null)
   		return null ;
   		
   	return subtree.getAttribute(attribRef.getAttribute());
   }
   
   /*
    *
    */
    
   function AttributeTree_setAttributeByReference(attribRef, value)
   {  	
   	if (attribRef == null)
   		return null ;
   		
   	var subtree = this.getDescendantByReference(attribRef.getNodeReference());
	  	if (subtree == null)
   		return null ;
   		
   	return subtree.setAttribute(attribRef.getAttribute(), value);
   }
	
  /****************************************************************************
   *
   */
   
  function AttributeTree_toXML(sIndent)
  {
  		var sTree, attribute, i, value, nodes, sValue, sConex ;
  		
  		if (!sIndent)
  			sIndent = "";
  	
  		sTree = sIndent + "<" + this.getID() ;
 
  		nodes = "" ;
  		sValue = "" ;
  		
		for (attribute in this.attributes)
		{
			value = this.attributes[attribute];
			if (value != null)
			{
				if ((attribute == "#text") || (attribute == "#value"))
				{
					sValue = "<![CDATA[" + value + "]]>";
				}
				else if (attribute == "_node_relation_")
				{
					sTree += " " + attribute + "='" + String_replaceSubstring(value,"'","&#039;") + "'";
				}
				else
				{
					// VALUE WITH MULTIPLE LINES OR BIG LENGTH, USE A NODE
					nodes += "<" + attribute + "><![CDATA[" + value + "]]></" + attribute + ">\n" ;
				}
				/*
				else if ((value.indexOf("\n") > 0) || (value.length > 20))
				{
					// VALUE WITH MULTIPLE LINES OR BIG LENGTH, USE A NODE
					nodes += "<" + attribute + "><![CDATA[" + value + "]]></" + attribute + ">\n" ;
				}
				else
				{
					// VALUE WITHOUT MULTIPLE LINES AND SHORT LENGTH, USE AN ATTRIBUTE
					sTree += " " + attribute + "='" + String_replaceSubstring(value,"'","&#039;") + "'";
				}
				*/
			}
		}
			
		sTree += ">" + sValue;
		if (nodes.length > 0)
			sTree += "\n" + nodes ;	

		if (this.childs.length > 0)
		{
			sTree += "\n" ;
			for (i=0 ; i<this.childs.length ; i++)
				sTree += this.childs[i].toXML(" " + sIndent);
		}
		
		sTree += sIndent + "</" + this.getID() + ">\n" ;
  		
  		return sTree ;
  }
  
  /****************************************************************************
   *
   */
  function AttributeTree_toString(sIndent)
  {
  		var sTree, attribute, i, value, nodes, sValue, sConex ;
  		
  		if (!sIndent)
  			sIndent = "";
  	
  		sTree = sIndent + "<Node::" + this.getID() ;
 
  		nodes = "" ;
  		sValue = "" ;
  		
		for (attribute in this.attributes)
		{
			value = this.attributes[attribute];
			if (value != null)
			{
				if ((attribute == "#text") || (attribute == "#value"))
				{
					sValue = "<![CDATA[" + XML_encode(value) + "]]>";
				}
				else if ((value.indexOf("\n") > 0) || (value.length > 20))
				{
					// VALUE WITH MULTIPLE LINES OR BIG LENGTH, USE A NODE
					nodes += "<Attr::" + attribute + "><![CDATA[" + XML_encode(value) + "]]></Attr:" + attribute + ">\n" ;
				}
				else
				{
					// VALUE WITHOUT MULTIPLE LINES AND SHORT LENGTH, USE AN ATTRIBUTE
					sTree += " " + attribute + "='" + String_replaceSubstring(XML_encode(value), "'", "&#039;") + "'";
				}
			}
		}
			
		sTree += ">" + sValue;
		if (nodes.length > 0)
			sTree += "\n" + nodes ;	

		if (this.childs.length > 0)
		{
			sTree += "\n" ;
			for (i=0 ; i<this.childs.length ; i++)
				sTree += this.childs[i].toString(" " + sIndent);
		}
		
		sTree += sIndent + "</Node::" + this.getID() + ">\n" ;
  		
  		return sTree ;
  }

  /****************************************************************************************
   *
   */
  function AttributeTree_buildFromHTMLForm(forms, sintax, rootTreeID)
  {
  		var i, j, k, iForm, form, elem, parentTree, childTree, attribRef, nodeRef, step, 
  			attributes, attribute, currentValues, values, attribRefAndValue, childs ;
  		
  		var bAnyElement = false;
  		
  		// LOOP FOR ALL THE ELEMENTS OF THE FORMS
  		
  		attributes = new Array();	// ASOCIATIVE ARRAY TO STORE THE ATTRIBUTE REFS AND ITS VALUES. USED TO ACUMULATE VALUES IF MORE THAN ONE
  		
  		if (forms != null)
  		{
	  		for (iForm=0; iForm<forms.length; iForm++)
	  		{	
	  			form = forms[iForm];
	  			if ((form) && (form != null) && (form.elements))
	  			{
	  				for (i=0; i<form.elements.length; i++)
	  				{
			  			elem = form.elements[i];

			  			// THE NAME OF THE ELEMENT IS THE COMPLETE PATH AND NAME OF THE ATTRIBUTE
			
			  			if ((elem) && (elem.name) && (elem.name.length > 0))
			  			{
			  				name = AttributeTree_FormField_removePrefix(elem.name);	// SOME FIELDS HAVE A PREFIX FOR OPERATIVE REASONS (DISTINGUISH OLD AND NEW, ETC...)
			  				
			  				attribRef = sintax.toAttributeReference(name);
			  				if (attribRef != null)
			  				{
			  					attribRefAndValue = attributes[attribRef.toString()] ;
			  					if (attribRefAndValue == null)
			  						currentValues = new Array() ;
			  					else
			  						currentValues = attribRefAndValue[1] ;
			  							
			  					values = HTML_getFormElementValue(elem);
			  					if ((values != null) && (values.length > 0))
			  					{
			  						for (j=0; j<values.length; j++)
			  						{
			  							if ((values[j] != null) && (values[j].length > 0))
			  							{
					  						bAnyElement = true;
					  						
					  						currentValues[currentValues.length] = values[j];
					  					}
					  				}
			  					}
			  					attributes[attribRef.toString()] = [attribRef, currentValues];
			  				}
			  			}
			  		}
	  			}
		  	}
	  	}
	  	
	  	//-----------------------------------------------
	  		
	  	if (!bAnyElement)
	  		return null;
		  		
	   var tree = new AttributeTree(rootTreeID);
	   		   
  		var size, index, values ;
  		
  		for (attribute in attributes)
		{
			attribRefAndValue = attributes[attribute];
			
			attribRef = attribRefAndValue[0];
			
			nodeRef = attribRef.getNodeReference();
			parentTree = tree ;
			
			if (nodeRef != null)
			{
				for (j=0; j<nodeRef.getDeep(); j++)
				{
					step = nodeRef.getStep(j);
					index = step.getIndex();
					
					childs = parentTree.getChildsById(step.getId());
					if (childs == null)
						size = 0 ;
					else
						size = childs.length ;
						
					if (size > index)
					{
						childTree = childs[index];
					}
					else
					{							
  						for (k=size; k<=index; k++)
  						{
  							childTree = new AttributeTree(step.getId());
  							childTree.setAttribute("_node_relation_", step.getRelation());
  							parentTree.addChild(childTree);
  						}
					}
					parentTree = childTree ;
				}
			}
			
			values = attribRefAndValue[1];
			if (values != null)
			  	parentTree.setAttribute(attribRef.getAttribute(), values[0]);
	  	}
  		
  		return tree ;
  }
  
  /****************************************************************************
   *
   */
  function HTML_getFormElementValue(elem)
  {
  		var type, values, tmp, i;
  		
  		type = elem.type;
  		if ((type != null) && (type.length > 0))
  			type = type.toLowerCase();
  			
  		
  		if ((type == "text") || (type == "hidden") || (type == "textarea"))
  			return [elem.value] ;
  		
  		if ((type == "checkbox") && (elem.checked))
  			return [elem.value] ;
		
		if ((type == "radio") && (elem.checked))
  			return [elem.value] ;
  			
  		if ((type == "select") || (type == "select-one"))
  		{
  			values = new Array();
  			tmp = "" ;
  			for (i=0; i<elem.options.length ;i++)
  				if (elem.options[i].selected)
  					values[values.length] = elem.options[i].value ;
  				
  			return values ;
  		}
		return null ;
  }
  
  /***************************************************************************
   *
   */
   function AttributeTree_FormField_removePrefix(str)
   {
   	var i = str.indexOf(".");	// THE REFERENCES ALWAYS MUST START WITH '.'. IF NOT, PERHAPS HAVE A PREFIX
   	if (i<=0)
   		return str;
   	else if (i == str.length-1)
   		return str;
   	return str.substring(i);
	}

   