// *********************************************************************
// *                   XmlDom2 CONSTRUCTOR			       *
// *********************************************************************

function XmlDom2(obj, group)
{
   this.id = UniqID();
   this.base = (obj) ? ((typeof(obj) == 'object') ? obj : document.getElementById(obj)) : null;
   this.group = group;
   this.customs = {};
   InstanciateSupervisor();
   supervisor.RegisterGarbage(this, this.id);
}

XmlDom2.prototype =
{
   id: null,
   base: null,
   xPathParser: null,
   group: null,
   customs: null
}

// *********************************************************************
// *                   PUBLIC					       *
// *********************************************************************

XmlDom2.prototype.DocumentElement = function(root)
{
   var base = (root) ? this.BaseElement() : this.base;
   if(this.base)
   { while(base.nodeType != 1) { base = (base.nextSibling) ? base.nextSibling : base.firstChild; } }
   return new XmlDom2(base);
}

XmlDom2.prototype.RootElement = function()
{
   return this.DocumentElement(true);
}

XmlDom2.prototype.BaseElement = function()
{ 
   if(this.base) { return (this.base.ownerDocument) ? this.base.ownerDocument : this.base; }
   return null;
}

XmlDom2.prototype.Load = function(url)
{
   var ajax = new Ajax('XmlDom2', false);
   ajax.customs['XmlDom2'] = this;
   ajax.Get(url);
}

XmlDom2.prototype.LoadXML = function(s)
{ 
   try
   {
      this.base = this.InstantiateDocument();
      this.base.loadXML(s);
   }
   catch(e)
   { throw new Exception(0, 'LoadXml failed: ' + e.message); }
}

XmlDom2.prototype.SelectNodes = function(s, caseSensitive)
{
   try
   {
      this.xPathParser = new XPathParser(s);
      var results = this.Filter(caseSensitive);
      if(results.length > 0) { return this.InstantiateResults(results); }
      return new Array();
   }
   catch(e)
   { throw new Exception(0, 'SelectNodes failed: ' + e.message); }
}

XmlDom2.prototype.SelectSingleNode = function(s, caseSensitive)
{
   try
   {
      this.xPathParser = new XPathParser(s);
      var results = this.Filter(caseSensitive);
      if(results.length > 0) { return this.InstantiateResults(results)[0]; }
      return null;
   }
   catch(e)
   { throw new Exception(0, 'SelectSingleNode failed: ' + e.message); }
}

// *********************************************************************
// *                   PROPERTIES				       *
// *********************************************************************

XmlDom2.prototype.NodeName = function()
{ return this.DocumentElement().base.nodeName; }

XmlDom2.prototype.NodeValue = function()
{ 
   var node = this.DocumentElement().base;
   return (node.firstChild && node.firstChild.nodeType == 3) ? node.firstChild.nodeValue : node.nodeValue;
}

XmlDom2.prototype.ParentNode = function()
{ return new XmlDom2(this.DocumentElement().base.parentNode); }

XmlDom2.prototype.FirstChild = function()
{ return new XmlDom2(this.DocumentElement().base.firstChild); }

XmlDom2.prototype.LastChild = function()
{ return new XmlDom2(this.DocumentElement().base.lastChild); }

XmlDom2.prototype.NextSibling = function()
{
   var node = this.DocumentElement().base.nextSibling;
   while(node && node.nodeType != 1)
   { node = node.nextSibling; }
   return (node && node.nodeType == 1) ? new XmlDom2(node) : new XmlDom2();
}

XmlDom2.prototype.PreviousSibling = function()
{
   var node = this.DocumentElement().base.previousSibling;
   while(node && node.nodeType != 1)
   { node = node.previousSibling; }
   return (node && node.nodeType == 1) ? new XmlDom2(node) : new XmlDom2();
}

XmlDom2.prototype.ChildNodes = function()
{ return this.InstantiateResults(this.DocumentElement().base.childNodes); }

XmlDom2.prototype.OuterXml = function()
{ return (this.DocumentElement().base.xml) ? this.DocumentElement().base.xml : this.DocumentElement().base.outerHTML; }

XmlDom2.prototype.InnerXml = function()
{
   var childs = this.ChildNodes();
   var s = '';
   for(i = 0; i < childs.length; i++)
   { s += childs[i].OuterXml() + '\n'; }
   return s;
}

// *********************************************************************
// *                   METHODS					       *
// *********************************************************************

XmlDom2.prototype.GetAttribute = function(name)
{ return this.DocumentElement().base.getAttribute(name); }

XmlDom2.prototype.SetAttribute = function(name, value)
{
   if(this.DocumentElement().base.style.setAttribute)
   {
      switch(name.toLowerCase())
      {
	 case 'class':	this.DocumentElement().base.setAttribute('className', value);
			break;

	 case 'style':	this.DocumentElement().base.style.setAttribute('cssText', value);
			break;

	 default: 	if(name.toLowerCase().startWith('on'))
			{
			   var mySelf = this.DocumentElement().base;
			   var action = 'mySelf.' + name + ' = function() { eval("' + value + '"); }';
			   eval(action);
			}
			else { this.DocumentElement().base.setAttribute(name, value); }
			break;
      }
   }
   else { this.DocumentElement().base.setAttribute(name, value); }
}

XmlDom2.prototype.RemoveAttribute = function(name)
{ this.DocumentElement().base.removeAttribute(name); }

XmlDom2.prototype.CreateElement = function(name, value)
{
   var node = this.BaseElement().createElement(name);
   if(value){ node.appendChild(this.BaseElement().createTextNode(value)); }
   return new XmlDom2(node);
}

XmlDom2.prototype.AppendChild = function(node)
{ this.DocumentElement().base.appendChild(node.DocumentElement().base); }

XmlDom2.prototype.RemoveChild = function(node)
{ this.DocumentElement().base.removeChild(node.DocumentElement().base); }

XmlDom2.prototype.InsertBefore = function(node)
{ this.ParentNode().base.insertBefore(node.DocumentElement().base, this.DocumentElement().base); }

XmlDom2.prototype.InsertAfter = function(node)
{
   var nSibling = this.NextSibling();
   if(nSibling.base) { nSibling.ParentNode().base.insertBefore(node.DocumentElement().base, nSibling.DocumentElement().base); }
   else { this.ParentNode().AppendChild(node); }
}

XmlDom2.prototype.CloneNode = function(recursive)
{ return new XmlDom2(this.base.cloneNode(recursive)); }

XmlDom2.prototype.CloneNodeFor = function(forNode)
{ return new XmlDom2(this.ForCopy(forNode.BaseElement())); }

XmlDom2.prototype.Remove = function()
{ this.ParentNode().RemoveChild(this); }

XmlDom2.prototype.MoveUp = function()
{
  var pSibling = this.PreviousSibling();
  if(pSibling.base) { pSibling.InsertBefore(this); }
}

XmlDom2.prototype.MoveDown = function()
{
  var nSibling = this.NextSibling();
  if(nSibling.base) { nSibling.InsertAfter(this); }
}

XmlDom2.prototype.Replace = function(node)
{ this.ParentNode().base.replaceChild(node.DocumentElement().base, this.DocumentElement().base); }

// *********************************************************************
// *                   PRIVATE					       *
// *********************************************************************

XmlDom2.prototype.Filter = function(caseSensitive)
{
   caseSensitive = (caseSensitive != undefined) ? caseSensitive : false;
   if(this.base)
   {
      var base = (this.xPathParser.root) ? this.RootElement().base : this.base;
      while(base.nodeType != 1) { base = base.firstChild; }
      var selectedNodes = new Array(base);
      
      for(i = 0; i < this.xPathParser.nodes.length; i++)
      {
         var jQuery = '';
         var groups = {};
         var nodeFilter = this.xPathParser.nodes[i];
         for(x = 0; x < nodeFilter.attributes.length; x++)
	 {
	       var group = nodeFilter.attributes[x].group;
	       if(!groups[group]) { groups[group] = new Array(); }
	       groups[group][groups[group].length] = nodeFilter.attributes[x];
   	 }
   	 
   	 for(key in groups)
	 {
	    if(key != 'xml')
	    {
	       for(x = 0; x < groups[key].length; x++)
	       {
	          var attribute = groups[key][x];
	          if(jQuery != '') { jQuery += (attribute.or) ? ' || ' : ' && '; }
	          if(x == 0 && groups[key].length > 1) { jQuery += '('; }
	          if(attribute.negation) { jQuery += '!'; }
	          jQuery += '('
	          + ((attribute.type == 1) ? 'parseFloat(' : '')
	          + 'child.getAttribute(\'' + attribute.name + '\')' 
	          + ((attribute.type == 1) ? ')' : '')
	          + ' ' + attribute.pattern 
	          + ((attribute.type == 0) ? ' \'' : ' ') 
	          + attribute.value.replaceAll('\'', '\\\'') 
	          + ((attribute.type == 0) ? '\')' : ')');
	          if((x + 1) == groups[key].length && groups[key].length > 1) { jQuery += ')'; }
	       }
	    }
   	}
   	
   	selectedNodes = this.FilterNodes(selectedNodes, nodeFilter, jQuery, caseSensitive);
      }
      return selectedNodes;
   }
   throw new Exception(0, 'Object not set to an instance of an object.');
}

XmlDom2.prototype.FilterNodes = function(nodes, nodeFilter, jQuery, caseSensitive)
{
   var filteredNodes = new Array();
   for(c = 0; c < nodes.length; c++)
   {
      var node = nodes[c];
      for(x = 0; x < node.childNodes.length; x++)
      {
         var child = node.childNodes[x];
         if(child.nodeType == 1 && (child.nodeName == nodeFilter.name
         	|| (!caseSensitive && child.nodeName.toLowerCase() == nodeFilter.name.toLowerCase())))
         {
            if(nodeFilter.attributes.length > 0)
      	    { if(eval(jQuery)) { filteredNodes[filteredNodes.length] = child; } }
   	    else { filteredNodes[filteredNodes.length] = child; }
         }
      }
   }
   return filteredNodes;
}

XmlDom2.prototype.InstantiateResults = function(nodes)
{
   var results = new Array();
   for(i = 0; i < nodes.length; i++)
   { if(nodes[i].nodeType == 1) { results[results.length] = new XmlDom2(nodes[i]); } }
   return results;
}

XmlDom2.prototype.InstantiateDocument = function()
{
   if (document.implementation && document.implementation.createDocument)
   {
      var doc = document.implementation.createDocument('', '', null);
      if (doc.readyState == null)
      {
	 doc.readyState = 1;
	 doc.addEventListener('load', 
	 function ()
	 {
	    doc.readyState = 4;
	    if (typeof doc.onreadystatechange == "function") { doc.onreadystatechange(); }
	 }
	 , false);
      }
      if(supervisor.browser != EBrowser.Safari)
      {
         Document.prototype.loadXML = function (s)
         {
            var doc = (new DOMParser()).parseFromString(s, "text/xml");
            while (this.childNodes.length != 0) { this.removeChild(this.lastChild); }
       
            for (var i = 0; i < doc.childNodes.length; i++)
            { this.appendChild(this.importNode(doc.childNodes[i], true)); }
         };
      }
      else
      {
      	doc.loadXML = function(s)
      	{
      	   if (!String.prototype.toDocumentFragment)
      	   {
	   	String.prototype.toDocumentFragment = function toDocumentFragment(context) {
	   		var aRange = document.createRange();
	   		context = context || document.getElementsByTagName("body")[0];
	   		aRange.selectNodeContents(context);
	   		return aRange.createContextualFragment(this);
	   	}
	   }
	   doc.appendChild(s.toDocumentFragment());
      	};
      }

      if(Object.prototype.__defineGetter__)
      {
         Object.prototype.__defineGetter__("xml", function () {
            if(this.nodeType) { return (new XMLSerializer()).serializeToString(this); }
            else { throw new Exception(0, '\'xml\' property is only available for xml objects.'); }
         });
      }

      return doc;
   }
   if (window.ActiveXObject) { return this.InstantiateByPrefix(); }
}

XmlDom2.prototype.InstantiateByPrefix = function()
{
   var prefixes = ["MSXML2", "Microsoft", "MSXML", "MSXML3"];
   for (var i = 0; i < prefixes.length; i++)
   {
      try { return new ActiveXObject(prefixes[i] + '.XmlDom'); }
      catch (ex) {};
   }
   throw new Exception(0, 'Could not find an installed XML parser.');
}

XmlDom2.prototype.ForCopy = function(owner, node, parent)
{ 
   node = (node) ? node : this.DocumentElement().base;
   var copy = null;
   switch(node.nodeType)
   {   
      case 1 :  if(node.nodeName != 'style' && node.nodeName != 'script')
      		{
      		   copy = owner.createElement(node.nodeName);
            	   for(var i = 0; i < node.attributes.length; i++)
            	   {
            	      if(node.attributes[i].nodeValue != '' && node.attributes[i].nodeValue != null)
            	      {
		         if(copy.style.setAttribute)
		         {
		            switch(node.attributes[i].nodeName.toLowerCase())
		            {
		        	case 'class':	copy.setAttribute('className', node.attributes[i].nodeValue);
						break;

				case 'style':	if(copy.style.setAttribute) { copy.style.setAttribute('cssText', node.attributes[i].nodeValue); }
						break;

				default: 	if(node.attributes[i].nodeName.toLowerCase().startWith('on'))
						{
						   var action = 'copy.' + node.attributes[i].nodeName + ' = function() { eval("' + node.attributes[i].nodeValue + '"); }';
						   eval(action);
						}
						else { copy.setAttribute(node.attributes[i].nodeName, node.attributes[i].nodeValue); }
						break;
			   }
			}
			else { copy.setAttribute(node.attributes[i].nodeName, node.attributes[i].nodeValue); }
            	   }
            	}

                for(var i = 0; i < node.childNodes.length; i++)
                {
                   var cCopy = this.ForCopy(owner, node.childNodes[i], copy);
                   if(cCopy) { copy.appendChild(cCopy); }
                }
                }
                break;
                
      case 3 :  if(parent) { parent.appendChild(parent.ownerDocument.createTextNode(node.data)); }
      		break;
      		
      default: throw new Exception(0, 'nodeType ' + node.nodeType + ' not managed.');
   }
   return copy;
}

// *********************************************************************
// *                   EVENTS					       *
// *********************************************************************

XmlDom2.prototype.LoadEvent = function(name, object)
{
  try
  {
     name = (this.group) ?  name + '_' + this.group : name;
     eval(name + '(this' + ((object == null) ? ')' : ', object)'));
  }
  catch(e) { }
}

// *********************************************************************
// *                   	AJAX EVENTS				       *
// *********************************************************************

function Ajax_OnGetFailed_XmlDom2(sender, object)
{ throw new Exception(0, e.message); }

function Ajax_OnCompleted_XmlDom2(sender, status)
{ sender.customs['XmlDom2'].LoadXML(sender.Text()); }

// *********************************************************************
// *                   XPathParser CONSTRUCTOR			       *
// *********************************************************************

function XPathParser(s)
{
   try
   {
      this.nodes = new Array();
      this.Parse(s);
   }
   catch(e)
   { throw new Exception(0, 'The system was unable to parse the XPath query: ' + e.message); }
}

XPathParser.prototype =
{
   nodes: null,
   root: false
}

XPathParser.prototype.Parse = function(s)
{
   s = s.trim();
   this.root = s.startWith('//');
   s = (this.root) ? s.substring(2) : s;
   s = s.split('/');
   for(i = 0; i < s.length; i++)
   { if(s[i].trim() != '') { this.AddNode(new Node(s[i].trim())); } }
}

XPathParser.prototype.AddNode = function(node)
{
   this.nodes[this.nodes.length] = node;
}

// *********************************************************************
// *                   Node CONSTRUCTOR			      	       *
// *********************************************************************

function Node(n)
{
   this.attributes = new Array();
   this.Parse(n);
}

Node.prototype =
{
   name: null,
   attributes: null
}

Node.prototype.Parse = function(n)
{
   if(n.indexOf('[') != -1)
   {
      n = n.split('[');
      this.name = n[0];
      for(x = 1; x < n.length; x++)
      {
         var rest = n[x].substring(0, n[x].length - 1).trim();
         while(rest != null)
         {
            var att = new Attribute(x);
            rest =  att.Parse(rest);
            this.AddAttribute(att);
         }
      }
   }
   else { this.name = n; }
}

Node.prototype.AddAttribute = function(attribute)
{
   this.attributes[this.attributes.length] = attribute;
}

// *********************************************************************
// *                   Attribute CONSTRUCTOR			       *
// *********************************************************************

function Attribute(group)
{
   this.group = group;
}

Attribute.prototype =
{
   name: null,
   value: '',
   negation: false,
   or: false,
   pattern: '',
   type: -1,
   group: 0
}

Attribute.prototype.Parse = function(a)
{ 
   a = a.replaceAll('\\\'', '[QUOTE]');
   if(a.startWith('and')) { a = a.substring(3).trim(); }
   this.or = (a.startWith('or'));
   if(this.or) { a = a.substring(2).trim(); }
   this.negation = a.startWith('not(');
   if(this.negation){ a = a.substring(4).trim(); var notNegation = true; }
   if(a.startWith('!'))
   {
      this.negation = !this.negation;
      a = a.substring(1).trim();
   }

   if(!a.startWith('@')) { throw new Exception(0, 'missing \'@\'.'); }
   else { a = a.substring(0).trim(); }

   var chars = a.split('');
   var isIn = false;

   for(cpt = 0; cpt < chars.length; cpt++)
   {
      if(chars[cpt].match('([!=<>])')) { this.pattern +=  chars[cpt]; isIn = true; }
      else if(isIn) { break; }
   }

   var z = a.indexOf(this.pattern);
   chars = a.substring(z).split('');
   var charIndex = -1;
   
   for(cpt = 0; cpt < chars.length; cpt++)
   {
      if(chars[cpt] == '\'') { this.type = 0; charIndex = z + cpt; break; }
      else if(chars[cpt].match('([0-9.])')) { this.type = 1; charIndex = z + cpt; break; }
   }

   this.pattern = a.substring(z, charIndex).replaceAll(' ', null);
   this.pattern = (this.pattern == '=') ? '==' : this.pattern;
   
   this.name = a.substring(1, z).trim();
   a = a.substring(charIndex).trim();
   switch(this.type)
   {
   	case 0:	a = a.substring(a.indexOf('\'') + 1);
   		this.value = a.substring(0, a.indexOf('\'')).replaceAll('[QUOTE]', '\'');
   		a = a.substring(a.indexOf('\'') + 1).trim();
   		a = a.substring(((notNegation) ? 1 : 0) + ((this.or) ? 1 : 0 )).trim();
   		break;
   		
   	case 1: chars = a.split('');
   		for(cpt = 0; cpt < chars.length; cpt++)
   		{
     		   if(chars[cpt].match('([0-9.])')) { this.value += chars[cpt]; }
     		   else { break; }
   		}
   		a = a.substring(a.indexOf(this.value) + this.value.length -1
     		   + ((notNegation) ? 2 : 1) + ((this.or) ? 1 : 0 )).trim();
   		break;
   		
   	default: throw new Exception(0, 'Unknown data type.');
   		 break;   		
   }
   
   return (a != '') ? a : null;
}