initXML.XMLDoc=initXML.XSLDoc=null;

// single-line functions:
function empty(){;}
var reTestNum = /^\s*\-?((\d+)|\d*(\.\d+))\s*$/;
function fixnum(s) { return reTestNum.test(s)?Number(s):s; }
function copyAttrToObj(o,no,na){o[na]=fixnum(no.getAttribute(na));}

/////////////////////////////
// genXSL object methods:
// "virtual" prototype functions for genXSL object:
function genXSL(url,cb) {}	// url can be a string or a DOMDocument
genXSL.prototype.init = function(){};
genXSL.prototype.findNode = function(xpath){};
genXSL.prototype.transformFill = function(eTarget,node){};

/////////////////////////////
// forward-declared functions
//
function initXML(){}		// MUST FIRST CALL this function to setup XML tools
function getXMLDocument(){}	// Gets XML DOM document that provides data for current page
function getXSLDocument(){}	// Gets XSL document that transformed current page
function attrToObj(node){}	// convert to JSON object (attributes only)

// http://cpjj.dyndns.org/Cross-browser/elementHasNoStyle.htm
function addEl(tag, parent, before){return addElNS(tag,"http://www.w3.org/1999/xhtml",parent,before,document);}
function addElNS(tag, namespace, parent, before, doc){}

//"virtual" functions, depends on browser
function getNewDoc(url, cb, ns){}
function getReq(){}			// XMLHttpRequest, uses appropriate IE or Firefox form
function getXMLFromNode(n){return n.xml;}
function getDocFromXML(str,ns){}
function prepns(doc){}

// If not IE, init as Firefox/W3C, updating virtual functions:
// Adds member functions selectSingleNode(), and selectNode().
function initFFXML() {}

function initXML()
{
   if (!initXML.done)
   {
	   if (document.implementation && document.implementation.createDocument && window.XPathEvaluator)
		   initFFXML();

	   if (window.ActiveXObject)
	   {
		   var x;
		   // http://cpjj.dyndns.org/Cross-browser/IeXmlDom.htm
		   if (!(x=getXMLDocument()))
		   {
		      try{x=new ActiveXObject("MSXML2.DOMDocument.4.0");}
		      catch(e){try{x=new ActiveXObject("MSXML.DOMDocument");} catch(e) {;}}
		   }
		   if (x)
		   {
		      x.setProperty("SelectionLanguage", "XPath");
		      getNewDoc.xbase = x;
		   }
	   }
	   initXML.done = true;
   }
}

// cross-browser methods to get what IE calls XMLDocument and XSLDocument:
function getXMLDocument()
{
   if (initXML.XMLDoc)
	   return initXML.XMLDoc;

   var o;
   if ((o=document.XMLDocument))
   {
	   o.setProperty("SelectionLanguage", "XPath");
	   prepns(o);
   }
   else if (initXML.done) // don't try getNewDoc() until initXML is done
   {
	   try {o=getNewDoc(window.location.href);}
	   catch(e){alert("caught exception: " + e.message);}
   }

   if (o)
	   initXML.XMLDoc=o;

   return o;
}

function getXSLDocument()
{
  if (initXML.XSLDoc)
	 return initXML.XSLDoc;

  var o, sError;

  if (!(o=document.XSLDocument))
  {
	 var xml=getXMLDocument();
	 if (xml)
	 {
		var ss = xml.selectSingleNode("/processing-instruction('xml-stylesheet')");
		if (ss)
		{
		  var sUrl;
		  var reXSL=/type\s*=\s*[\"\']text\/xsl[\"\']/;
		  var reHREF=/href\s*=\s*[\"\']([^\"\']+)[\'\"]/;
		  var t=ss.data;
		  if (reXSL.test(t))
		  {
			 var a=reHREF.exec(t);
			 if (a && a.length>1)
				sUrl=a[1];
		  }

		  if (sUrl)
		  {
			 try{o=getNewDoc(sUrl);}
			 catch(e){sError="Unable to load implied stylesheet: " + e.message;}
		  }
		  else
			 sError="Unable to parse stylesheet URL.";
		}
	 }
  }

  if (o)
  {
	 initXML.XSLDoc=o;
	 prepns(o);
  }

  if (sError)
	 alert(sError);

  return o;
}

function attrToObj(node)
{
  var o={};
  var attr=node.attributes;
  for (var i=0;i<attr.length;i++)
	 copyAttrToObj(o,node,attr[i].name);
  return o;
}

// If source document is XML, adding HTML elements will
// require specifying the namespace.  This function helps.
function addElNS(tag, namespace, parent, before, doc)
{
  var n, d=doc||document;

  if (d.createElementNS)
	 n=d.createElementNS(namespace,tag);
  else if (d.createNode)
  n=d.createNode(1,tag,namespace);
  else
	 n=d.createElement(tag);

  if (parent && before)
	 parent.insertBefore(n,before);
  else if (parent)
  parent.appendChild(n);
  return n;
}

function getNewDoc(url, cb, ns)
{
  var d = getNewDoc.xbase.cloneNode(false);
  if (ns)
	 d.setProperty("SelectionNamespaces", ns);

  if (url)
  {
	 if (cb)
	 {
		d.async=true;
		d.onreadystatechange=function()
		{
		  if (d.readyState==4)
		  {
			 d.onreadystatechange=empty;
			 if (d.parseError.errorCode==0)
			 {
				if (!ns)
				  prepns(d);
				cb(d);
			 }
			 else
				throw new Error("Error loading " + url + " " + d.parseError.reason);
		  }
		};
		d.load(url);
	 }
	 else
	 {
		d.async=false;
      d.validateOnParse=false; // fix IE failure to validate xsi:nil="true"
		if (d.load(url))
		{
		  if (!ns)
			 prepns(d);
		}
		else
		  throw new Error("Error loading " + url + " " + d.parseError.reason);
	 }
  }

  return d;
}

function getReqFF() { return new XMLHttpRequest(); }
function getReqIE()
{
  var arr = ["MSXML2.XMLHTTP", "Microsoft.XMLHTTP"];
  var i=getReqIE.ndx, r=null;
  if (i>=0)
	 r=new ActiveXObject(arr[i]);
  else
	 for (var i=0; !r && i<arr.length; i++) { try {r=new ActiveXObject(arr[i]); getReqIE.ndx=i; } catch(e){;} }
  return r;
}

function getReq()
{
  var r;
  if (window.XMLHttpRequest)
  {
	 r=getReqFF();
	 getReq=getReqFF;
  }
  else
  {
	 r=getReqIE();
	 if(r)
		getReq=getReqIE;
  }

  if (!r)
	 throw new Error("no XMLHttpRequest object available");

  return r;
}

function getDocFromXML(str,ns)
{
  var pe, d=getNewDoc.xbase.cloneNode(false);
  d.validateOnParse=false;
  d.loadXML(str);
  if ((pe=d.parseError).errorCode!=0){alert("xml parse error: " + pe.reason);}
  if (ns) d.setProperty("SelectionNamespaces", ns);
  return d;
}

function prepns(doc)
{
  var ns="";
  var de = doc.documentElement;
  var a = de.attributes;
  for (var i=0; i<a.length; i++)
  {
	 var at = a[i];
	 if (at.name.substring(0,5)=="xmlns")
		ns += (ns.length?" ":"") + at.name+"=\"" + at.value + "\"";
  }
  doc.setProperty("SelectionNamespaces", ns);
}




// genXSL object:
genXSL.prototype.nsXSL = "xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\"";
function genXSL(url,cb)
{
   var t=this;
   if (typeof(url)=="object" && ("ownerDocument" in url || "documentElement" in url))
   {
	   if (cb)
	   {
		   cb=null;
		   alert("callback ignored if passing document");
	   }
	   f(url);
   }
   else if (typeof(url)=="string")
   {
	   var ns=t.nsXSL;
	   if (cb)
		   getNewDoc(url,f,ns);
	   else
		   f(getNewDoc(url,null,ns));
   }
   else
      alert("unknown type of url");

   function f(d)
   {
	   t.doc=d;
	   t.init();
	   if (cb)
		   cb(d);
   }
}
genXSL.prototype.init=function(){ this.doc.setProperty("SelectionNamespaces",this.nsXSL);};
genXSL.prototype.findNode=function(xpath) { return this.doc.selectSingleNode(xpath); };
genXSL.prototype.transformFill=function(eTarget, node)
{
  try
  {
	 eTarget.innerHTML=node.transformNode(this.doc);
  }
  catch(e){alert("Error setting innerHTML: "+ e.message);}
};

// Opera won't recognize changes in stylesheet without creating a new processor.
// Detect problem by changing stylesheet, transforming, then looking for change.
// Some browsers won't show the change, other browsers will.  Return true if
// browser has the problem.
function testForOperaError(oxsl)
{
   var ns = "http://www.w3.org/1999/XSL/Transform";
   var docXSL = oxsl.doc;
   var docEl = docXSL.documentElement;

   // Add main template at bottom (overriding original) with simple text output
   var tmpMain = docEl.appendChild(docXSL.createElementNS(ns,"template"));
   tmpMain.setAttribute("match","/");
   var xtext = tmpMain.appendChild(docXSL.createElementNS(ns,"text"));
   xtext.appendChild(docXSL.createTextNode(ns));

   // Do transformation,
   // NOTE: We're matching /, so the first argument should be the docXSL,
   //       do NOT use the document element (or any other element):
   var ddf = oxsl.processor.transformToFragment(docXSL, docXSL);
   // Remove testing template,
   docEl.removeChild(tmpMain);
   // Compare output with string, "opera" problem if strings differ:
   var xmls=new XMLSerializer();
   return xmls.serializeToString(ddf)!=ns;
}

function initFFXML()
{
   prepns = empty;	// disable unnecessary function
   var isWebKit = false;
   var hasOperaError = false;

	var d=document.implementation.createDocument("","",null);
   if (d.load)
      getNewDoc = getNewDocFF;
   else
   {
      getNewDoc = getNewDocWebKit;
      isWebKit = true;
   }

   function fixWebKitStylesheet(doc)
   {
      // ensure output method = html:
      var node = doc.selectSingleNode("/xsl:stylesheet/xsl:output");
      if (node && node.getAttribute("method")=="xml")
         node.setAttribute("method", "html");

      // gut out document node template to deal with Webkit bug
      // Google: webkit bug 28744 "root one"
      if ((node=doc.selectSingleNode("/xsl:stylesheet/xsl:template[@match='/']")))
      {
         var child;
         while ((child=node.lastChild))
            node.removeChild(child);
         child = node.appendChild(doc.createElement("xsl:apply-templates"));
         child.setAttribute("select", "*");
      }
   }

   function getNewDocFF(url,cb,ns)
   {
      var d=document.implementation.createDocument("","",null);
	   if (url)
	   {
		   if (cb)
		      d.onload=function(){d.onload=empty; cb(d);};
		   else
		      d.async=false;
		   if (!d.load(url))
		   {
		      throw new Error("Error loading " + url);
		      d=null;
		   }
	   }
	   return d;
   };

   function getNewDocWebKit(url,cb,ns)
   {
      var r = getReq();
      var usecb = cb?true:false;

      function prepNCall()
      {
         if (r.readyState==4)
         {
            var doc = r.responseXML;
            if (usecb)
               cb(doc);
            return doc;
         }
         return null;
      }

      r.open("GET", url, usecb);

      if (usecb)
         r.onreadystatechange = prepNCall;

      r.send();

      if (usecb==false)
         return prepNCall();
      else
         return null;
   }

   getXMLFromNode=function(n)
   {
	   var xmls=new XMLSerializer();
	   return xmls.serializeToString(n);
   };

   getDocFromXML=function(str,ns)
   {
	   var d=null;
	   try{d=xparse.parseFromString(str, "text/xml");}
	   catch(e){alert("error attempting to parse xml string: "+e.message);}
	   return d;
   };

   genXSL.prototype.init=function()
   {
	   var p=this.processor=new XSLTProcessor();
      if (isWebKit)
         fixWebKitStylesheet(this.doc);

	   try{p.importStylesheet(this.doc);}catch(e){alert("importStylesheet failed: "+e.message);}

      hasOperaError = testForOperaError(this);
   };

   genXSL.prototype.transformFill=function(eTarget,node)
   {
	   var ddf;
      eTarget.innerHTML = "";

	   try
	   {
         if (hasOperaError)
            this.init();

		   if ((ddf=this.processor.transformToFragment(node, eTarget.ownerDocument)))
		      eTarget.appendChild(ddf);
         else
            alert("transformToFragment unexpectedly returned null");
	   }
	   catch(e){alert("transformToFragment failed: " + e.message);}
   };

   var reDoc=/\[(object\s)?XMLDocument\]/;
   var xeval=new XPathEvaluator();
   var xparse=new DOMParser();

   function xsel(t,sXPath,bSingle)
   {
	   var n,od;
	   var it=(bSingle?XPathResult.FIRST_ORDERED_NODE_TYPE:XPathResult.ORDERED_NODE_ITERATOR_TYPE);

      if ("documentElement" in t)
         n=t.documentElement, od=t;
      else if ("ownerDocument" in t)
         od=t.ownerDocument, n=t;
      else
         alert("Unable to find XML document");

	   var de = od.documentElement;
	   try
	   {
		   var nsr=od.createNSResolver(de);
		   var r=xeval.evaluate(sXPath,n,nsr,it,null);
		   if (r)
		   {
		      if (bSingle)
			      return r.singleNodeValue;
		      else
		      {
			      var nl=[];
			      var n=r.iterateNext();
			      while (n)
			      {
				      nl.push(n);
				      n=r.iterateNext();
			      }
			      return nl;
		      }
		   }
	   }
	   catch(e){alert("xpath search error: " + e.message);}
	   return null;
   }

   XMLDocument.prototype.selectSingleNode = Element.prototype.selectSingleNode = function(xpath){return xsel(this,xpath,true);};
   XMLDocument.prototype.selectNodes = Element.prototype.selectNodes = function(xpath){return xsel(this,xpath,false);};
}


