﻿//-----------------------------------------------------------------------------
// XDoc
//
// Copyright 2005-2010 - Xcential Group LLC.
//
//-----------------------------------------------------------------------------

XDoc.prototype = new XNode;
XDoc.prototype.constructor = XDoc;

//=============================================================================
// Constructor

function XDoc(
   refOrItem,
   resolveIncludes,
   cacheMode,
   create
)
{
   refOrItem = (refOrItem == null) ? null : refOrItem;
   resolveIncludes = (resolveIncludes == null) ? false : resolveIncludes;
   cacheMode = (cacheMode == null) ? XDoc.CACHE_SESSION : cacheMode;

   create = (create == null) ? true : create;

   if (create)
      return new XDoc(refOrItem, resolveIncludes, cacheMode, false);

   //--------------------------------------------------------------------------
   // Private Interface

   //--------------------------------------------------------------------------
   // Privileged Interface

   this.valueOf = function()
   {

      return oDoc;
   }

   //--------------------------------------------------------------------------

   this.setObjectValue = function(
      refOrItem,
      resolveIncludes,
      cacheMode
   )
   {
      refOrItem = (refOrItem == null) ? null : refOrItem;
      resolveIncludes = (resolveIncludes == null) ? false : resolveIncludes;
      cacheMode = (cacheMode == null) ? XDoc.CACHE_SESSION : cacheMode;

      oDoc = XDoc.getDocFrom(refOrItem, resolveIncludes, cacheMode);

      return oDoc;
   }

   //--------------------------------------------------------------------------
   // Initialization

   var oDoc = this.setObjectValue(refOrItem, resolveIncludes, cacheMode);

   this.ref = (refOrItem && typeof(refOrItem) == "string") ? refOrItem : null;

}

XDoc.prototype.objectClass = "XDoc";

//=============================================================================
// Static Interface

 XDoc.RESOLVE_INCLUDES = true;

 XDoc.NO_CACHE = true;

 XDoc.CACHE_NONE    = "cache_none";
 XDoc.CACHE_SESSION = "cache_session";
 XDoc.CACHE_CLIENT  = "cache_client";

 XDoc.REFRESH_CACHE = true;

//-----------------------------------------------------------------------------

XDoc.getDocFrom = function(
   refOrItem,
   resolveIncludes,
   cacheMode
)
{
   refOrItem = (refOrItem == null) ? null : refOrItem;
   resolveIncludes = (resolveIncludes == null) ? false : resolveIncludes;
   cacheMode = (cacheMode == null) ? XDoc.CACHE_SESSION : cacheMode;

   //--------------------------------------------------------------------------

   function read(
      ref,
      resolveIncludes,
      refreshCache
   )
   {
      resolveIncludes = (resolveIncludes == null) ? false : resolveIncludes;
      refreshCache = (refreshCache == null) ? false : refreshCache;

      var xRef = XRef(ref);
      var uri = xRef.getURI();
      var xDoc = null;

      // Get the file
      var retrieveURL = null;
      var retrievePath = null;
      var retrieveCmd = null;
      if ((/^urn:/).test(uri))
      {
         var model = xRef.getModel();
         if (XApp.getMode() == XApp.MODE_CLIENT)
         {
            var retrieveURL = XApp.getDomainURL() + XApp.URL_MODEL + xRef.getModel() + "/Retrieve.asp" +
               "?ref=" + encodeURIComponent(xRef.valueOf()) +
               "&resolveIncludes=" + ((resolveIncludes) ? "yes" : "no") +
               "&refreshCache=" + ((refreshCache) ? "yes" : "no") +
               "&displayMode=app";
         }
         else
         {
            retrieveCmd = model.toUpperCase() +
               ".retrieve(" +
               "'" + XString(xRef.valueOf()).encode(XString.ENCODE_BACKSLASH) + "', " +
               ((resolveIncludes) ? "true" : "false") + ", " +
               ((refreshCache) ? "true" : "false") +
            ")";
         }
      }
      else if ((/^https?:/).test(uri) || (/^\//).test(uri))
      {
         var retrieveURL = xRef.getURL();
         switch (XApp.getMode())
         {
            case XApp.MODE_STANDALONE:
               if (!(/\.asp\?/).test(retrieveURL) && !(/\.asp$/).test(retrieveURL) && !(/^https:/).test(retrieveURL))
               {
                  var retrievePath = retrieveURL.replace(/^https?:\/\/[^\/]+/,"").replace(/\//g, "\\");
                  if (!(/^[A-Z]\:/i).test(retrievePath))
                  {
                     retrievePath = XApp.getSitePath() + "\\" + retrievePath.replace(/^\.?\\/,"");
                     retrieveURL = null;
                  }
               }
               break;
            case XApp.MODE_SERVER:
               if ((new RegExp("^" + XApp.getDomainURL())).test(retrieveURL) &&
                   (!(/\.asp\?/).test(retrieveURL) && !(/\.asp$/).test(retrieveURL)))
               {
                  if (!(/\?/).test(retrieveURL)) // Can't simply retrieve a file if calling a program
                  {
                     var localURL = retrieveURL;
                     if (XMatch(localURL, /https?:\/\/[^\/]*/))
                        localURL = XMatch.rightContext;
                     var localPath = XRef.mapPath(localURL);
                     if (XString(localPath).isSomething())
                     {
                        retrievePath = localPath;
                        retrieveURL = null;
                     }
                  }
               }
               break;
         }
      }
      else
         throw XMsg("Cannot retrieve local file from a client computer.");

      if (retrieveCmd)
      {
         try
         {
            eval("docXML = " + retrieveCmd);
         }
         catch (error)
         {
            XApp.logEvent(XApp.EVENT_ERROR, error);
            throw XMsg("File for " + xRef.toString(XRef.STYLE_FRIENDLY) + " cannot be retrieved.", error);
         }
      }
      else if (retrievePath)
      {
         var noCache = (cacheMode == XDoc.CACHE_CLIENT) ? false : true;
         var docXML = XTextDoc().read(retrievePath, null, noCache);
      }
      else
      {
         var noCache = (cacheMode == XDoc.CACHE_CLIENT) ? false : true;
         var docXML = XTextDoc().read(retrieveURL, null, noCache);
      }

      if (docXML.length == 0)
         throw XMsg("File at '" + retrieveURL + "' returned empty.");
      if ((/^<Error[\s>]/).test(docXML))
         throw XMsg(docXML);

      xDoc = XDoc(docXML);

      if (xDoc == null)
         throw XMsg("Cannot retrieve XML at '" + xRef.toString() + "'.");

      return xDoc;
   }

   //--------------------------------------------------------------------------

   if (refOrItem == null)
      return null;

   if (XDoc.docs == null)
      XDoc.docs = XCache();

   var doc = null;
   try
   {
      switch (typeof(refOrItem))
      {
         case "string":
            if (XRef.isRef(refOrItem))
            {
               var ref = refOrItem;
               try
               {
                  var doc = (cacheMode == XDoc.CACHE_SESSION) ? XDoc.docs.getItem(ref) : null;
                  if (doc == null)
                  {
                     var xDoc = read(ref, resolveIncludes);
                     doc = xDoc.valueOf();
                     if (cacheMode == XDoc.CACHE_SESSION)
                        XDoc.docs.addItem(ref, doc);
                  }
               }
               catch (error)
               {
                  XApp.logEvent(XApp.EVENT_ERROR, error);
                  var msgText = XMsg.getMsgText(error);
                  if (XString(msgText).isSomething())
                     throw error;
                  else
                     throw XMsg("Cannot retrieve and parse '" + XRef(ref).toString(XRef.STYLE_FRIENDLY) + "' to a document.", error);
               }
            }
            else if (XNode.isXML(refOrItem))
            {
               try
               {
                  var xDoc = XDoc.createXDoc();
                  xDoc.setXML(refOrItem);
                  doc = xDoc.valueOf();
               }
               catch (error)
               {
                  XApp.logEvent(XApp.EVENT_ERROR, error);
                  throw XMsg("Cannot parse XML item into a node.", error);
               }
            }
            else
            {
               throw XMsg("String is not a valid XML document.");
            }
            break;
         case "number":
            throw XMsg("Cannot create item. Invalid reference '" + String(refOrItem) + "'.");
            break;
         case "boolean":
            throw XMsg("Cannot create item. Invalid reference '" + String(refOrItem) + "'.");
            break;
         case "function":
            try
            {
               return XDoc.getDocFrom(refOrItem(), resolveIncludes);
            }
            catch (error)
            {
               XApp.logEvent(XApp.EVENT_ERROR, error);
               throw XMsg("Cannot create item from a function.", error);
            }
            break;
         case "object":
            if (refOrItem.objectClass != null)
            {
               doc = XDoc.getDocFrom(refOrItem.valueOf(), resolveIncludes);
            }
            else
            {
               try
               {
                  var node = refOrItem;
                  var nodeType = node.nodeType;
                  if (nodeType == XNode.NODE_DOCUMENT)
                  {
                     doc = node;
                  }
                  else if (node.ownerDocument != null)
                  {
                     doc = node.ownerDocument;
                  }
                  else
                  {
                     XApp.logEvent(XApp.EVENT_ERROR, error);
                     throw XMsg("Cannot create item from XML Node.", error);
                  }
               }
               catch (error)
               {
                  XApp.logEvent(XApp.EVENT_ERROR, error);
                  throw XMsg("Cannot create item from object.", error);
               }
            }
            break;
      }
   }
   catch (error)
   {
      XApp.logEvent(XApp.EVENT_ERROR, error);
      var msgText = XMsg.getMsgText(error);
      if (XString(msgText).isSomething())
         throw error;
      else if (typeof(refOrItem) == "string" && XRef.isRef(refOrItem))
         throw XMsg("Cannot find " + XRef(refOrItem).toString(XRef.STYLE_FRIENDLY) + ".", error);
      else
         throw XMsg("Cannot find or create item.", error);
   }

   if (doc == null)
   {
      throw XMsg("Cannot create item.");
   }

   var rootNode = doc.documentElement;
   if (rootNode && doc.implementation.hasFeature("XML", "1.0"))
   {
      try
      {
         var selectionNamespaces = doc.getProperty("SelectionNamespaces") ;
         if (!selectionNamespaces || selectionNamespaces.length == 0)
         {
            var model = doc.documentElement.getAttribute("model");
            if (XString(model).isNothing())
               model = doc.documentElement.prefix;
            if (XString(model).isNothing() && doc.documentElement.namespaceURI)
               model = doc.documentElement.namespaceURI.replace(/.*\//, "").replace(/[^a-z0-9]/ig, "").toLowerCase();
            if (model)
            {
               try
               {
                  var modelConfig = (model != "xcfg") ? XConfig(model) : null;
                  if (modelConfig != null)
                     selectionNamespaces = modelConfig.getProperty("selectionNamespaces");
               }
               catch (error)
               {
                  XApp.logEvent(XApp.EVENT_ERROR, error);
               }
            }
            if (XString(rootNode.prefix).isSomething())
               var rootPrefix = rootNode.prefix;
            else if (rootNode.namespaceURI)
               var rootPrefix = rootNode.namespaceURI.replace(/.*\//, "").replace(/[^a-z0-9]/ig, "").toLowerCase();
            else
               var rootPrefix = null;
            if (!selectionNamespaces)
               selectionNamespaces = "";
            if (rootPrefix && !(new RegExp("xmlns:" + rootPrefix + "=")).test(selectionNamespaces))
               selectionNamespaces += " xmlns:" + rootPrefix + "='" + rootNode.namespaceURI + "'";
            doc.setProperty("SelectionNamespaces", selectionNamespaces);
         }
      }
      catch (error)
      {
         XApp.logEvent(XApp.EVENT_ERROR, error);
      }
   }

   return doc;
}

//-----------------------------------------------------------------------------

XDoc.createXDoc = function()
{

   var doc = XDOM.createDocument();

   doc.async = false;
   doc.resolveExternals = false;
   doc.validateOnParse = false;
   if (XApp.getMode() == XApp.MODE_SERVER)
      doc.setProperty("ServerHTTPRequest", true);
   doc.setProperty("SelectionLanguage", "XPath");

   return XDoc(doc);
}

//=============================================================================
// Public Interface

XDoc.prototype.load = function(
   ref
)
{
   ref = (ref == null) ? null : ref;

   var doc = this.valueOf();

   var xRef = XRef(ref);
   var url = xRef.getURL();

   var xml = XTextDoc.readHttpFile(url, XTextDoc.GET, XTextDoc.NO_CACHE);
   doc.loadXML(xml);

   this.ref = url;

   XApp.logEvent(XApp.EVENT_RETRIEVE, url);

   return true;
}

//-----------------------------------------------------------------------------

XDoc.prototype.refresh = function()
{

   var doc = this.valueOf();

   var url = this.getURL();

   var xml = XTextDoc.readHttpFile(url, XTextDoc.GET, XTextDoc.NO_CACHE);
   doc.loadXML(xml);

   this.ref = url;

   XApp.logEvent(XApp.EVENT_RETRIEVE, url);

   return true;
}

//-----------------------------------------------------------------------------

XDoc.prototype.getXDoc = function(
   castAs
)
{
   castAs = (castAs == null) ? XDoc : castAs;

   return castAs(this);
}

//-----------------------------------------------------------------------------

XDoc.prototype.getURL = function(
   castAs
)
{
   castAs = (castAs == null) ? null : castAs;

   var doc = this.valueOf();

   var url = (XString(this.ref).isSomething()) ? XRef(this.ref).getURL() : doc.url;

   return (castAs == null) ? url : castAs(url);
}

//-----------------------------------------------------------------------------

XDoc.prototype.toXML = function(
   castAs
)
{
   castAs = (castAs == null) ? null : castAs;

   var doc = this.valueOf();

   return (castAs == null) ? doc.xml : castAs(doc.xml);
}

XDoc.prototype.getXML = XNode.prototype.toXML; // Synonym

//-----------------------------------------------------------------------------

XDoc.prototype.setXML = function(
   xml
)
{

   var doc = this.valueOf();

   if (!(/^<\?xml/).test(xml))
      xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" + xml;

   doc.loadXML(xml);

   // If the load failed, try removing any non-utf-8 encoding declaration
   if (doc.xml == "" && !(/encoding=[\'\"]utf-8[\'\"]/i).test(xml))
   {
      xml = xml.replace(/<\?xml\s[^>]*?>/, "");
      var xmlNoUTF = "<?xml version=\"1.0\" ?>\n" + xml;
      doc.loadXML(xmlNoUTF);
   }

   // If the load still failed, try getting rid of any uncommon character.
   if (doc.xml == "")
   {
      var xmlNoSpecialChars = xml.replace(/[^a-zA-Z0-9_\s\r\n\u00A0\<\>\,\.\;\'\"\:\?\/\\\~\`\!\@\#\$\%\^\&\*\(\)\-\+\|\=\[\]\{\}]/g, "?");
      doc.loadXML(xmlNoSpecialChars);
   }

   // Try getting rid of a doctype
   if (doc.xml == "" && (/DOCTYPE/).test(xml))
   {
      var xmlNoDocType = xml.replace(/<!DOCTYPE[^>]*>/, "");
      doc.loadXML(xmlNoDocType);
   }

}

//-----------------------------------------------------------------------------

XDoc.prototype.getRoot = function(
   castAs
)
{
   castAs = (castAs == null) ? XNode : castAs;

   var doc = this.valueOf();

   return castAs(doc.documentElement);
}

//-----------------------------------------------------------------------------

XDoc.prototype.isLoaded = function()
{

   var doc = this.valueOf();

   return (doc == null) ? false : (doc.documentElement == null) ? false : (doc.documentElement.xml == "") ? false : true;
}

//-----------------------------------------------------------------------------

XDoc.prototype.getModel = function(
   castAs
)
{
   castAs = (castAs == null) ? null : castAs;

   var doc = this.valueOf();

   var model = doc.documentElement.getAttribute("model");
   if (XString(model).isNothing())
      model = doc.documentElement.prefix;
   if (XString(model).isNothing() && doc.implementation.hasFeature("XML", "1.0") && doc.documentElement.namespaceURI)
      model = doc.documentElement.namespaceURI.replace(/^.*\/([^\/]+)$/, "$1").toLowerCase();

   return (XString(model).isNothing()) ? null : (castAs == null) ? model : castAs(model);
}

//-----------------------------------------------------------------------------

XDoc.prototype.setModel = function(
   model
)
{
   model = (model == null) ? null : model;

   var doc = this.valueOf();

   if (model)
      doc.documentElement.setAttribute("model", model);

   return model;
}

//-----------------------------------------------------------------------------

XDoc.prototype.getChildren = function(
   castAs
)
{
   castAs = (castAs == null) ? XNodeList : castAs;

   var doc = this.valueOf();

   return castAs(doc.childNodes);
}

//-----------------------------------------------------------------------------

XDoc.prototype.getProperty = function(
   propertyName
)
{
   propertyName = (propertyName == null) ? null : propertyName.toString();

   if (propertyName == null)
      return null;

   var doc = this.valueOf();

   return doc.getProperty(propertyName);
}

//-----------------------------------------------------------------------------

XDoc.prototype.setProperty = function(
   propertyName,
   propertyValue
)
{
   propertyName = (propertyName == null) ? null : propertyName.toString();
   propertyValue = (propertyValue == null) ? "" : propertyValue;

   if (propertyName == null)
      return false;

   var doc = this.valueOf();

   doc.setProperty(propertyName, propertyValue);

   return true;
}

//-----------------------------------------------------------------------------

XDoc.prototype.createNode = function(
   nodeType,
   name,
   namespaceURI,
   castAs
)
{
   nodeType = (nodeType == null) ? null : nodeType.toString();
   name = (name == null) ? null : name.toString();
   namespaceURI = (namespaceURI == null) ? null : namespaceURI.toString();
   castAs = (castAs == null) ? XNode : castAs;

   if (nodeType == null)
      return null;

   var doc = this.valueOf();
   var node = doc.createNode(nodeType, name, namespaceURI);

   return castAs(node);
}

//-----------------------------------------------------------------------------

XDoc.prototype.createProcessingInstruction = function(
   target,
   data
)
{
   target = (target == null) ? null : target.toString();
   data = (data == null) ? null : data.toString();

   if (target == null)
      return null;

   var doc = this.valueOf();
   var piNode = doc.createProcessingInstruction(target, data);

   return X$(piNode);
}

//-----------------------------------------------------------------------------

XDoc.prototype.createTextNode = function(
   text
)
{
   text = (text == null) ? "" : text.toString();

   var doc = this.valueOf();
   var textNode = doc.createTextNode(text);

   return X$(textNode);
}

//-----------------------------------------------------------------------------

XDoc.prototype.createComment = function(
   comment
)
{
   comment = (comment == null) ? "" : comment.toString();

   var doc = this.valueOf();
   var commentNode = doc.createComment(comment);

   return X$(commentNode);
}

//-----------------------------------------------------------------------------

XDoc.prototype.transformTo = function(
   format,
   params,
   postProcessFunc
)
{
   format = (format == null) ? null : format.toString();
   params = (params == null) ? [] : params;
   postProcessFunc = (postProcessFunc == null) ? null : postProcessFunc;

   var doc = this.valueOf();

   if (!doc.documentElement)
      throw XMsg("Cannot transform document" + ((format) ? " to a '" + format + "' format" : "") + " as no document is loaded.");

   if (format == null || format == doc.documentElement.prefix)
      return doc.xml;

   var model = this.getModel();
   var xTransforms = XTransforms(model);

   if (XApp.getMode() == XApp.MODE_CLIENT && (XApp.isBrowser("Safari") || XApp.isBrowser("Chrome")))
   {
      try
      {

         var message = "xml=" + encodeURIComponent(doc.xml);
         message += "&format=" + encodeURIComponent(format);
         message += "&params=" + encodeURIComponent(XArray(params).toString());

         var httpRequest = XApp.getXmlHttpRequest();

         httpRequest.open("POST", XApp.getDomainURL() + XApp.URL_MODEL + "xfw/core/Transform.asp", false);
         httpRequest.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
         //httpRequest.setRequestHeader("Content-Length",message.length);
         //httpRequest.setRequestHeader("Connection","close");
         httpRequest.send(message);

         if (httpRequest.readyState != 4)
            throw "Transform to '" + format + "' could not be completed.";
         if (httpRequest.status == 404)
            throw "Transform to '" + format + "' could not be completed as the transformer is missing.";
         if (httpRequest.status != 200)
         {
            var errorMsg = "Transform to '" + format + "' could not be completed.";
            var errorText = httpRequest.statusText;
            if (errorText && errorText.length > 0)
               errorMsg += " (" + errorText + ")";
            throw errorMsg;
         }
         var transformXML = XApp.getResponseText(httpRequest);

      }
      catch (error)
      {
         var transformXML = "<div class=\"Error\"><br/><b>Error:</b> " + error.toString() + "<br/></div>";
      }
   }
   else
   {

      var transformStylesheet = xTransforms.getStylesheet(doc.documentElement, format);

      // Setup the processor
      var transformTemplate = XDOM.createTemplate();
      transformTemplate.stylesheet = transformStylesheet;
      var transformProcessor = transformTemplate.createProcessor();
      transformProcessor.input = doc;
      transformProcessor.addParameter("applicationURL", XApp.URL_APP);
      transformProcessor.addParameter("transformURL", transformStylesheet.url);
      var url = this.getURL();
      if (url != null)
         transformProcessor.addParameter("docURL", url);
      for (var i=0; i<params.length; i++)
      {
         if (params[i][0] != null && params[i][1] != null)
            transformProcessor.addParameter(params[i][0], (params[i][1] != null) ? params[i][1] : "");
      }

      // Perform the tranform and return the result
      transformProcessor.transform();
      var transformXML = transformProcessor.output;
   }

   // Cleanup
   transformStylesheet = null;
   transformTemplate = null;
   transformProcessor = null;

   // Note: MSXML tends to work in UTF-16 internally and generally
   //       ignores any encoding directives. As we work in UTF-8
   //       exclusively, we force the issue here. However, be aware
   //       that within the server side applications, all strings
   //       remain encoded in UTF-16. The actual encoding only changes
   //       when the document is written to the response stream or
   //       to the file system.

   transformXML = transformXML.replace(/UTF-16/ig, "UTF-8");

   // Note: Firefox refuses to implement disable-output-escaping. This
   //       is a rather lame workaround.
   if (XApp.isBrowser("Firefox"))
   {
      transformXML = transformXML.replace(/\r?\n\r?/g, " ");
      while (XMatch(transformXML, /<doey>(.*?)<\/doey>/))
      {
         var leftContext = XMatch.leftContext;
         var rightContext = XMatch.rightContext;
         var doeyText = XMatch.matches[1];
         if ((/&/).test(doeyText))
         {
            doeyText = doeyText.replace(/&lt;/g, "<");
            doeyText = doeyText.replace(/&gt;/g, ">");
            doeyText = doeyText.replace(/&quot;/g, "\"");
            doeyText = doeyText.replace(/&apos;/g, "'");
            doeyText = doeyText.replace(/&amp;/g, "&");
         }
         transformXML = leftContext + doeyText + rightContext;
      }
   }
   else
      transformXML = transformXML.replace(/<\/?doey>/g,"");

   if (postProcessFunc)
   {
      transformXML = postProcessFunc(transformXML, this);
   }

   return transformXML;

}

//=============================================================================

