﻿//-----------------------------------------------------------------------------
// XNode
//
// Copyright 2005-2010 - Xcential Group LLC.
//
//-----------------------------------------------------------------------------

XNode.prototype = new XObject;
XNode.prototype.constructor = XNode;

//=============================================================================
// Constructor

function XNode(
   refOrItem,
   create
)
{
   refOrItem = (refOrItem == null) ? null : refOrItem;
   create = (create == null) ? true : create;

   if (create)
      return new XNode(refOrItem, false);

   //--------------------------------------------------------------------------
   // Private Interface

   //--------------------------------------------------------------------------
   // Privileged Interface

   this.valueOf = function()
   {

      return oNode;
   }

   //--------------------------------------------------------------------------

   this.setObjectValue = function(
      refOrItem
   )
   {
      refOrItem = (refOrItem == null) ? null : refOrItem;

      oNode = XNode.getNodeFrom(refOrItem);

      return oNode;
   }

   //--------------------------------------------------------------------------
   // Initialization

   var oNode = this.setObjectValue(refOrItem);

}

X$ = XNode;

XNode.prototype.objectClass = "XNode";

//=============================================================================
// Static Interface

XNode.NODE_ELEMENT                = 1;
XNode.NODE_ATTRIBUTE              = 2;
XNode.NODE_TEXT                   = 3;
XNode.NODE_CDATA_SECTION          = 4;
XNode.NODE_ENTITY_REFERENCE       = 5;
XNode.NODE_ENTITY                 = 6;
XNode.NODE_PROCESSING_INSTRUCTION = 7;
XNode.NODE_COMMENT                = 8;
XNode.NODE_DOCUMENT               = 9;
XNode.NODE_DOCUMENT_TYPE          = 10;
XNode.NODE_DOCUMENT_FRAGMENT      = 11;
XNode.NODE_NOTATION               = 12;

XNode.INNER_ONLY = true;
XNode.CLONE_DEEP = true;
XNode.SKIP_WHITESPACE = true;
XNode.HTML_MODE = true;

XNode.SPACE_DEFAULT               = "default";
XNode.SPACE_PRESERVE              = "preserve";

//=============================================================================

XNode.getNodeFrom = function(
   refOrItem
)
{
   refOrItem = (refOrItem == null) ? null : refOrItem;

   if (refOrItem == null)
      return null;

   var node = null;
   switch (typeof(refOrItem))
   {
      case "string":
         if (XRef.isRef(refOrItem))
         {
            try
            {
               var xRef = XRef(refOrItem);
               var url = xRef.getURL();
               var xDoc = XDoc(url);
               var fraction = xRef.getFraction();
               if (fraction != null)
               {
                  var xPath = xRef.getXPath();
                  if (xPath == null)
                  {
                     var id = xRef.getId();
                     if (id != null)
                        xPath = "//*[@id='" + id + "']";
                  }
                  if (xPath == null)
                     throw XMsg("Cannot retrieve fraction '" + fraction + "'.");
                  xNode = xDoc.X$(xPath);
               }
               else
                  xNode = xDoc.getRoot();
               node = xNode.valueOf();
            }
            catch (error)
            {
               XApp.logEvent(XApp.EVENT_ERROR, error);
               throw XMsg("Cannot retrieve and parse item at '" + refOrItem + "' to a node.", error);
            }
         }
         else if (XNode.isXML(refOrItem))
         {
            try
            {
               var xml = refOrItem;
               var doc = XDoc.getDocFrom(xml);
               node = doc.documentElement;
            }
            catch (error)
            {
               XApp.logEvent(XApp.EVENT_ERROR, error);
               throw XMsg("Cannot parse item into a node.", error);
            }
         }
         break;
      case "number":
         throw XMsg("Cannot create a node from a number.");
         break;
      case "boolean":
         throw XMsg("Cannot create a node from a boolean.");
         break;
      case "function":
         try
         {
            XNode.getNodeFrom(refOrItem());
         }
         catch (error)
         {
            XApp.logEvent(XApp.EVENT_ERROR, error);
            throw XMsg("Cannot create a node from a function.", error);
         }
         break;
      case "object":
         if (refOrItem.objectClass != null)
         {
            node = XNode.getNodeFrom(refOrItem.valueOf());
         }
         else
         {
            node = refOrItem;
            try
            {
               var nodeType = node.nodeType;
            }
            catch (error)
            {
               nodeType = null;
            }
            if (nodeType == null || nodeType == undefined)
               throw XMsg("Cannot convert object to a node.");
         }
         break;
   }

   return node;
}

//-----------------------------------------------------------------------------

XNode.isXML = function(
   text
)
{
   text = (text == null) ? null : text

   if (text == null)
      return false;

   if (typeof(text) != "string")
      return false;

   // Take out the doctype, it confuses things
   text = text.replace(/^<!DOCTYPE[^>]*>\s*\r?\n?\r?/, "");

   if ((/^<\?xml/).test(text))
      return true;

   if (XMatch(text, /^<([^\s>]+)/))
   {
      var rootTag = XMatch.matches[1];
      if ((new RegExp("</" + rootTag + ">\\s*$")).test(text.replace(/\r?\n\r?/gm, "")))
         return true;
   }

   // Single element XML
   if (XMatch(text, /^<[^>]+\/>$/))
      return true;

   return false;
}

//-----------------------------------------------------------------------------

XNode.doEvent = function(
   event
)
{

   var srcElement = (event.srcElement != null) ? event.srcElement : event.target;

   var eventName = "on" + event.type;
   var eventText = null;
   var contextElement = srcElement;
   while (!eventText && contextElement)
   {
      eventText = contextElement.getAttribute(eventName);
      contextElement = contextElement.parentNode;
   }
   if (eventText)
      eval(eventText);

}

//=============================================================================
// Public Interface

XNode.prototype.getImplementation = function()
{

   var node = XNode.getNodeFrom(this.valueOf());

   return node.ownerDocument.implementation;
}

//-----------------------------------------------------------------------------

XNode.prototype.toString = function()
{

   var node = XNode.getNodeFrom(this.valueOf());

   return node.nodeName;
}

//-----------------------------------------------------------------------------

XNode.prototype.getNodeType = function()
{

   var node = XNode.getNodeFrom(this.valueOf());

   return node.nodeType;
}

//-----------------------------------------------------------------------------

XNode.prototype.getNodeName = function()
{

   var node = XNode.getNodeFrom(this.valueOf());

   return node.nodeName;
}

//-----------------------------------------------------------------------------

XNode.prototype.getNamespaceURI = function()
{

   var node = XNode.getNodeFrom(this.valueOf());

   try
   {
      return node.namespaceURI;
   }
   catch (error)
   {
      XApp.logEvent(XApp.EVENT_ERROR, error);
   }

   return null;
}

//-----------------------------------------------------------------------------

XNode.prototype.getPrefix = function()
{

   var node = XNode.getNodeFrom(this.valueOf());

   return (node == null) ? null : node.prefix;
}

//-----------------------------------------------------------------------------

XNode.prototype.getLocalName = function()
{

   var node = XNode.getNodeFrom(this.valueOf());

   return node.nodeName.replace(/^[^\:]*\:/,"");
}

//-----------------------------------------------------------------------------

XNode.prototype.getNodeValue = function()
{

   var node = XNode.getNodeFrom(this.valueOf());

   return node.nodeValue;
}

//-----------------------------------------------------------------------------

XNode.prototype.isNode = function(
   localNames,
   namespaceURI
)
{
   localNames = (localNames == null) ? [] : localNames;
   namespaceURI = (namespaceURI == null) ? null : namespaceURI.toString();

   localNames = (typeof(localNames) == "string") ? [localNames] : localNames;

   for (var i=0; i<localNames.length; i++)
   {
      if (this.getLocalName() == localNames[i] && this.getNamespaceURI() == namespaceURI)
         return true;
   }

   return false;
}

//-----------------------------------------------------------------------------

XNode.prototype.isEqual = function(
   otherXNode
)
{
   otherXNode = (otherXNode == null) ? null : otherXNode;

   return (otherXNode && otherXNode.valueOf() == this.valueOf()) ? true : false;
}

//-----------------------------------------------------------------------------

XNode.prototype.getId = function()
{

   var node = XNode.getNodeFrom(this.valueOf());

   if (node.nodeType != XNode.NODE_ELEMENT)
      return null;

   return node.getAttribute("id");
}

//-----------------------------------------------------------------------------

XNode.prototype.getAttribute = function(
   attributeName,
   defaultValue,
   castAs
)
{
   defaultValue = (defaultValue == null) ? null : defaultValue;
   castAs = (castAs == null) ? null : castAs

   var node = XNode.getNodeFrom(this.valueOf());

   if (node.nodeType != XNode.NODE_ELEMENT)
      return null;

   var attributeValue = node.getAttribute(attributeName);
   if (attributeValue == null || attributeValue.length == 0)
      attributeValue = defaultValue;

   if (castAs != null)
      attributeValue = castAs(attributeValue);

   return attributeValue;
}

//-----------------------------------------------------------------------------

XNode.prototype.setAttribute = function(
   attributeName,
   attributeValue
)
{
   attributeValue = (attributeValue == null) ? null : attributeValue.toString();

   var node = XNode.getNodeFrom(this.valueOf());

   if (node.nodeType != XNode.NODE_ELEMENT)
      throw XMsg("Cannot add attribute '" + attributeName + "' with value '" + attributeValue + "' to a non-element DOM node.");

   if (attributeValue != null)
      return node.setAttribute(attributeName, attributeValue);
   else
      return node.removeAttribute(attributeName);

   return attributeValue;
}

//-----------------------------------------------------------------------------

XNode.prototype.removeAttribute = function(
   attributeName
)
{
   attributeName = (attributeName == null) ? null : attributeName.toString();

   var node = XNode.getNodeFrom(this.valueOf());

   if (attributeName == null)
      return false;

   return node.removeAttribute(attributeName);
}

//-----------------------------------------------------------------------------

XNode.prototype.getSpace = function(
   castAs
)
{
   castAs = (castAs == null) ? null : castAs;

   var space = this.getAttribute("xml:space");

   return (space == null) ? null : (castAs == null) ? space : castAs(space);
}

//-----------------------------------------------------------------------------

XNode.prototype.setSpace = function(
   space
)
{
   space = (space == null) ? null : space.toString();

   return this.setAttribute("xml:space", space);
}

//-----------------------------------------------------------------------------

XNode.prototype.getParent = function(
   castAs
)
{
   castAs = (castAs == null) ? XNode : castAs;

   var node = XNode.getNodeFrom(this.valueOf());

   try
   {
      return (node.parentNode == null) ? null : castAs(node.parentNode);
   }
   catch (error)
   {
      XApp.logEvent(XApp.EVENT_ERROR, error);
   }

   return null;
}

//-----------------------------------------------------------------------------

XNode.prototype.getXDoc = function(
   castAs
)
{
   castAs = (castAs == null) ? XDoc : castAs;

   var node = XNode.getNodeFrom(this.valueOf());

   try
   {

      return (node.ownerDocument == null) ? null : castAs(node.ownerDocument);

   }
   catch (error)
   {
      XApp.logEvent(XApp.EVENT_ERROR, error);
   }

   return null;
}

//-----------------------------------------------------------------------------
// Siblings

XNode.prototype.getPreviousSibling = function(
   skipWhitespace,
   castAs
)
{
   skipWhitespace = (skipWhitespace == null) ? false : skipWhitespace;
   castAs = (castAs == null) ? XNode : castAs;

   var node = XNode.getNodeFrom(this.valueOf());

   try
   {
      var previousSibling = node.previousSibling;
      if (previousSibling == null)
         return null;
      else if (skipWhitespace && previousSibling.nodeType == XNode.NODE_TEXT && XString(previousSibling.nodeValue).trim().length == 0)
         return X$(previousSibling).getPreviousSibling(skipWhitespace, castAs);
      else
         return castAs(previousSibling);
   }
   catch (error)
   {
      XApp.logEvent(XApp.EVENT_ERROR, error);
   }

   return null;
}

//-----------------------------------------------------------------------------

XNode.prototype.getNextSibling = function(
   skipWhitespace,
   castAs
)
{
   skipWhitespace = (skipWhitespace == null) ? false : skipWhitespace;
   castAs = (castAs == null) ? XNode : castAs;

   var node = XNode.getNodeFrom(this.valueOf());

   try
   {
      var nextSibling = node.nextSibling;
      if (nextSibling == null)
         return null;
      else if (skipWhitespace && nextSibling.nodeType == XNode.NODE_TEXT && XString(nextSibling.nodeValue).trim().length == 0)
         return X$(nextSibling).getNextSibling(skipWhitespace, castAs);
      else
         return castAs(nextSibling);
   }
   catch (error)
   {
      XApp.logEvent(XApp.EVENT_ERROR, error);
   }

   return null;
}

//-----------------------------------------------------------------------------
// Children

XNode.prototype.hasChildren = function()
{

   var node = XNode.getNodeFrom(this.valueOf());

   return (node.childNodes.length > 0) ? true : false;
}

//-----------------------------------------------------------------------------

XNode.prototype.getChildren = function(
   castAs
)
{
   castAs = (castAs == null) ? XNodeList : castAs;

   var node = XNode.getNodeFrom(this.valueOf());

   return castAs(node.childNodes);
}

//-----------------------------------------------------------------------------

XNode.prototype.getFirstChild = function(
   castAs
)
{
   castAs = (castAs == null) ? XNode : castAs;

   var node = XNode.getNodeFrom(this.valueOf());

   try
   {
      return (node.firstChild == null) ? null : castAs(node.firstChild);
   }
   catch (error)
   {
      XApp.logEvent(XApp.EVENT_ERROR, error);
   }

   return null;
}

//-----------------------------------------------------------------------------

XNode.prototype.getFirstTextChild = function()
{

   var node = XNode.getNodeFrom(this.valueOf());

   try
   {
      var searchNode = node;
      while (searchNode && searchNode.nodeType != XNode.NODE_TEXT)
         searchNode = searchNode.nextSibling;
   }
   catch (error)
   {
      XApp.logEvent(XApp.EVENT_ERROR, error);
   }

   return (searchNode == null) ? null : X$(searchNode);
}

//-----------------------------------------------------------------------------

XNode.prototype.childIndex = null;

XNode.prototype.indexChildren = function(
   indexNodeNames
)
{
   indexNodeNames = (indexNodeNames == null) ? false : indexNodeNames;

   this.childIndex = new Array();
   var childXNode = null;
   var children = this.getChildren();
   for (var i=0; i<children.length; i++)
   {
      var listChildXNode = children.X$(i);
      if (listChildXNode.getNodeType() != XNode.NODE_ELEMENT)
         continue;

      var id = listChildXNode.getAttribute("id");
      if (id)
         this.childIndex[id] = listChildXNode;

      var name = listChildXNode.getAttribute("name");
      if (name)
         this.childIndex[name] = listChildXNode;

      if (indexNodeNames)
      {
         var nodeName = listChildXNode.getNodeName();
         if (nodeName)
            this.childIndex[nodeName] = listChildXNode;
      }
   }

   return true;
}

//-----------------------------------------------------------------------------

XNode.prototype.getChild = function(
   name,
   castAs
)
{
   name = (name == null) ? null : name;
   castAs = (castAs == null) ? XNode : castAs;

   if (name == null)
      return this.getFirstChild(castAs);

   if ((/\[/).test(name))
      childXNode = this.X$(name);
   else
   {
      if (XMatch(name,/^([^\/]+)\/(.*)/))
      {
         var name1 = XMatch.matches[1];
         var name2 = XMatch.matches[2];
         if (name1 == ".")
         {
            return this.getChild(name2);
         }
         else
         {
            var child = this.getChild(name1);
            if (!child)
               return null;
            else
               return child.getChild(name2);
         }
      }

      if (this.childIndex && this.childIndex[name] != null)
         return this.childIndex[name];

      var childXNode = null;
      var children = this.getChildren();
      for (var i=0; i<children.length; i++)
      {
         var listChildXNode = children.X$(i);
         if (listChildXNode.getNodeType() != XNode.NODE_ELEMENT)
            continue;
         if (listChildXNode.getAttribute("id") == name)
         {
            childXNode = listChildXNode;
            break;
         }
         else if (listChildXNode.getAttribute("name") == name)
         {
            childXNode = listChildXNode;
            break;
         }
         else if (listChildXNode.getNodeName() == name)
         {
            childXNode = listChildXNode;
            break;
         }
      }
   }

   try
   {
      return (childXNode == null) ? null : castAs(childXNode);
   }
   catch (error)
   {
      return null;
   }
}

//-----------------------------------------------------------------------------

XNode.prototype.getChildValue = function(
   name,
   defaultValue,
   castAs
)
{
   name = (name == null) ? null : name;
   defaultValue = (defaultValue == null) ? null : defaultValue;
   castAs = (castAs == null) ? null : castAs;

   if (XMatch(name,/^@([^\/]+)$/))
   {
      var attributeName = XMatch.matches[1];
      var value = this.getAttribute(attributeName, defaultValue);
   }
   else if (XMatch(name,/^(.*)\/@([^\/]+)$/))
   {
      var childPath =  XMatch.matches[1];
      var attributeName = XMatch.matches[2];
      var xNode = this.getChild(childPath);
      var value = (xNode) ? xNode.getAttribute(attributeName, defaultValue) : defaultValue;
   }
   else
   {
      var xNode = this.getChild(name);
      var value = (xNode) ? xNode.toText() : defaultValue;
   }

   return (value == null) ? null : (castAs == null) ? value : castAs(value);
}

//-----------------------------------------------------------------------------

XNode.prototype.getLastChild = function(
   castAs
)
{
   castAs = (castAs == null) ? XNode : castAs;

   var node = XNode.getNodeFrom(this.valueOf());

   try
   {
      return (node.lastChild == null) ? null : castAs(node.lastChild);
   }
   catch (error)
   {
      XApp.logEvent(XApp.EVENT_ERROR, error);
   }

   return null;
}

//-----------------------------------------------------------------------------

XNode.prototype.addChild = function(
   child,
   before
)
{
   child = (child == null) ? null : child;
   before = (before == null) ? null : before;

   if (child == null)
      return null;

   var node = XNode.getNodeFrom(this.valueOf());

   var childNode = XNode.getNodeFrom(child);

   var beforeNode = (before == null) ? null : XNode.getNodeFrom(before);
   if (beforeNode == null)
      node.appendChild(childNode);
   else
      node.insertBefore(childNode, beforeNode);

   return child;
}

//-----------------------------------------------------------------------------

XNode.prototype.removeChild = function(
   child
)
{
   child = (child == null) ? null : child;

   if (child == null)
      return null;

   var node = XNode.getNodeFrom(this.valueOf());

   var childNode = null;
   switch (typeof(child))
   {
      case "string":
         var xPath = "./node()[name()='" + child + "' or @id='" + child + "' or @name='" + child + "']";
         childNode = node.selectSingleNode(xPath);
         break;
      case "number":
         childNode = node.childNodes[i];
         break;
      case "boolean":
         throw XMsg("Cannot remove a boolean child.");
         break;
      case "function":
         try
         {
            childNode = XNode.getNodeFrom(child());
         }
         catch (error)
         {
            XApp.logEvent(XApp.EVENT_ERROR, error);
            throw XMsg("Cannot remove a function child.", error);
         }
         break;
      case "object":
         childNode = XNode.getNodeFrom(child);
         break;
   }

   if (childNode != null)
      node.removeChild(childNode);

   return (childNode == null) ? null :child;
}

//-----------------------------------------------------------------------------

XNode.prototype.addAfter = function(
   newXNode
)
{
   newXNode = (newXNode == null) ? null : newXNode;

   if (newXNode == null)
      return null;

   var node = XNode.getNodeFrom(this.valueOf());

   if (typeof(newXNode) == "string")
   {
      var text = newXNode;
      newXNode = node.ownerDocument.createTextNode(text);
   }

   var newNode = XNode.getNodeFrom(newXNode);

   var beforeNode = node.nextSibling;
   if (beforeNode == null)
      node.parentNode.appendChild(newNode);
   else
      node.parentNode.insertBefore(newNode, beforeNode);

   return newXNode;
}

//-----------------------------------------------------------------------------

XNode.prototype.moveContents = function(
   source
)
{

   var node = XNode.getNodeFrom(this.valueOf());

   var sourceNode = XNode.getNodeFrom(source.valueOf());

   var moveNodes = sourceNode.selectNodes("./node()");
   for (var i=0; i<moveNodes.length; i++)
   {
      var moveNode = moveNodes[i];
      if (moveNode != node)
      {
         sourceNode.removeChild(moveNode);
         node.appendChild(moveNode);
      }
   }

   return true;
}

//-----------------------------------------------------------------------------

XNode.prototype.clearContents = function()
{

   var node = XNode.getNodeFrom(this.valueOf());

   while (node.firstChild != null)
      node.removeChild(node.firstChild);

   return true;
}

//-----------------------------------------------------------------------------

XNode.prototype.toText = function()
{

   var node = XNode.getNodeFrom(this.valueOf());

   var text = "";
   if (node.nodeType == XNode.NODE_ELEMENT)
   {
      var childNodes = node.childNodes;
      for (var i=0; i<childNodes.length; i++)
      {
         var childNode = childNodes[i];
         switch (childNode.nodeType)
         {
            case XNode.NODE_ELEMENT:
               text += X$(childNode).toText();
               break;
            case XNode.NODE_TEXT:
            case XNode.NODE_CDATA_SECTION:
               text += childNode.nodeValue;
               break;
         }
      }
   }
   else
      text = node.nodeValue;

   // Get rid of non-normal spaces (to allow better regular expression handling)
   text = text.replace(/[\u00A0\u2002\u2003]/g, " ");

   return text;
}

//-----------------------------------------------------------------------------

XNode.prototype.addText = function(
   text,
   before
)
{
   text = (text == null) ? "" : text;
   before = (before == null) ? null : before;

   var node = XNode.getNodeFrom(this.valueOf());

   if (node.nodeType == XNode.NODE_ELEMENT)
   {
      // Add a new text node
      var textNode = node.ownerDocument.createTextNode(text);

      var beforeNode = (before == null) ? null : XNode.getNodeFrom(before);
      if (beforeNode == null)
         node.appendChild(textNode);
      else
         node.insertBefore(textNode, beforeNode);
   }
   else
      node.nodeValue = text;

   return X$(textNode);
}

//-----------------------------------------------------------------------------

XNode.prototype.setText = function(
   text
)
{
   text = (text == null) ? null : text.toString();

   var node = XNode.getNodeFrom(this.valueOf());

   if (node.nodeType == XNode.NODE_ELEMENT)
   {
      // Remove existing content;
      while (node.firstChild)
         node.removeChild(node.firstChild);

      // Add a new text node
      if (text != null)
      {
         var textNode = node.ownerDocument.createTextNode(text);
         node.appendChild(textNode);
      }
   }
   else
      node.nodeValue = text;

   return text;
}

//-----------------------------------------------------------------------------

XNode.prototype.selectByFraction = function(
   fraction,
   castAs
)
{
   fraction = (fraction == null) ? null : fraction.toString();
   castAs = (castAs == null) ? XNode : castAs;

   if (fraction == null || fraction.length == 0)
      return null;

   var xPath = fraction;
   if (XMatch(fraction, /^id\((.*)\)$/))
   {
      var id = XMatch.matches[1];
      var label = null;
      if (XMatch(id, /^([^\[]*)\[(.*)*\]$/))
      {
         id = XMatch.matches[1];
         label = XMatch.matches[2];
      }
      xPath = ".//*[@id=\"" + id + "\"" + ((label != null) ? " label=\"" + label + "\"" : "") + "]";
   }
   else if (XMatch(fraction, /^xpointer\((.*)\)$/))
   {
      var xPath = XMatch.matches[1];
      xPath = decodeURI(xPath);
   }

   return this.selectByXPath(xPath, castAs);
}

XNode.prototype.getByFraction = XNode.prototype.selectByFraction;

//-----------------------------------------------------------------------------

XNode.prototype.selectByXPath = function(
   xPath,
   castAs
)
{
   xPath = (xPath == null) ? null : xPath;
   castAs = (castAs == null) ? XNode : castAs;

   if (xPath == null)
      xPath = "./node()[1]";
   else if (typeof(xPath) == "number")
      xPath = "./node()[" + xPath + "]";
   else if (xPath.length == 0)
      return null;
   else if (typeof(xPath) != "string")
   {
      for (var i=0; i<xPath.length; i++)
      {
         var xPathItem = xPath[i];
         var resultXNode = null;
         try
         {
            resultXNode = this.selectByXPath(xPathItem, castAs);
         } catch (error) { resultXNode == null; }
         if (resultXNode != null)
            return resultXNode;
      }
   }
   else if (!(/[\/]/).test(xPath) && !(/\:\:/).test(xPath)) // No path or axis
   {
      if (!(/[\:]/).test(xPath) || (/^urn\:/).test(xPath))
         xPath = ".//*[@id=\"" + xPath + "\"]"; // Select by Id
      else
         xPath = "./" + xPath; // Select by tag name
   }

   var node = XNode.getNodeFrom(this.valueOf());

   var resultNode = node.selectSingleNode(xPath);

   return (resultNode == null) ? null : castAs(resultNode);
}

XNode.prototype.selectFirst = XNode.prototype.selectByXPath;
XNode.prototype.X$ = XNode.prototype.selectByXPath;

//-----------------------------------------------------------------------------

XNode.prototype.getTextByXPath = function(
   xPath
)
{
   xPath = (xPath == null) ? null : xPath;

   if (xPath == null || xPath.length == 0)
      return null;

   var resultXNode = (xPath == null) ? null : this.selectByXPath(xPath);

   return (resultXNode == null) ? null : resultXNode.toText();
}

//-----------------------------------------------------------------------------

XNode.prototype.select = function(
   xPath,
   castAs
)
{
   xPath = (xPath == null) ? null : xPath;
   castAs = (castAs == null) ? XNodeList : castAs;

   var node = XNode.getNodeFrom(this.valueOf());

   if (xPath == null)
      xPath = "./node()";
   else if (typeof(xPath) == "number")
      xPath = "./node()[" + xPath + "]";
   else if (xPath.length == 0)
      return null;
   else if (typeof(xPath) != "string")
   {
      for (var i=0; i<xPath.length; i++)
      {
         var xPathItem = xPath[i];
         var resultXNodes = null;
         try
         {
            resultXNodes = this.select(xPathItem, castAs);
         } catch (error) { resultXNodes == null; }
         if (resultXNodes != null)
            return resultXNodes;
      }
   }
   else if (!(/[\/]/).test(xPath) && !(/\:\:/).test(xPath)) // No path or axis
   {
      if (!(/[\:]/).test(xPath) || (/^urn\:/).test(xPath))
         xPath = ".//*[@id=\"" + xPath + "\"]"; // Select by Id
      else
         xPath = "./" + xPath; // Select by tag name
   }

   return castAs(node.selectNodes(xPath));
}

XNode.prototype.X$$ = XNode.prototype.select;

//-----------------------------------------------------------------------------

XNode.prototype.toXML = function(
   innerOnly
)
{
   innerOnly = (innerOnly == null) ? false : innerOnly;

   var node = XNode.getNodeFrom(this.valueOf());

   var xml = node.xml;

   if (innerOnly && this.getNodeType() == XNode.NODE_ELEMENT)
   {
      xml = xml.replace(/^\s*<[^>]+>/, "");
      xml = xml.replace(/<\/[^>]+>\s*$/, "");
   }

   return xml;
}

XNode.prototype.getXML = XNode.prototype.toXML; // Synonym

//-----------------------------------------------------------------------------

XNode.prototype.setXML = function(
   xml,
   innerOnly
)
{
   innerOnly = (innerOnly == null) ? false : innerOnly;

   var node = XNode.getNodeFrom(this.valueOf());

   xml = (innerOnly) ? "<wrap xml:space=\"preserve\">" + xml + "</wrap>" : xml;

   // Create an XML document of the xml string
   var xDoc = XDoc(xml);

   // Clone the document for this document
   var cloneXNode = xDoc.getRoot().clone(XNode.CLONE_DEEP, this.getXDoc());
   var cloneNode = XNode.getNodeFrom(cloneXNode);

   if (innerOnly)
   {
      while (node.childNodes.length > 0)
         node.removeChild(node.childNodes.item(0));
      while (cloneNode.childNodes.length > 0)
      {
         var childNode = cloneNode.childNodes.item(0);
         cloneNode.removeChild(childNode);
         node.appendChild(childNode);
      }
   }
   else
   {
      // Replace this node with the clone replacement
      node.parentNode.insertBefore(cloneNode, node);
      node.parentNode.removeChild(node);
      this.setObjectValue(cloneNode);
   }

   return this.valueOf();
}

//-----------------------------------------------------------------------------

XNode.prototype.clone = function(
   deep,
   forDoc,
   htmlMode
)
{
   deep = (deep == null) ? false : deep;
   forDoc = (forDoc == null) ? this.getXDoc() : forDoc;
   htmlMode = (htmlMode == null) ? false : htmlMode;

   var node = XNode.getNodeFrom(this.valueOf());

   var forDoc = XDoc.getDocFrom(forDoc);

   if (node.ownerDocument == forDoc)
   {
      var cloneNode = node.cloneNode(deep);
   }
   else
   {
      switch (node.nodeType)
      {
         case XNode.NODE_ELEMENT:
            var nodeName = (htmlMode) ? node.nodeName.replace(/^[^:]*:/, "") : node.nodeName;
            var namespaceURI = node.namespaceURI;
            try
            {
               if (XString(namespaceURI).isSomething())
                  var cloneNode = forDoc.createNode(XNode.NODE_ELEMENT, nodeName, namespaceURI);
               else
                  var cloneNode = forDoc.createElement(nodeName);
            }
            catch (error)
            {
               if (!(/\:/).test(nodeName))
                  var cloneNode = forDoc.createElement(nodeName);
            }
            for (var i=0; i<node.attributes.length; i++)
            {
               var attributeName = node.attributes.item(i).nodeName;
               var attributeValue = node.attributes.item(i).nodeValue;
               if ((/^xmlns/).test(attributeName))
                  continue;
               // Note: These are IE6 limitations
               if (htmlMode && attributeName.toLowerCase() == "class")
                  cloneNode.className = attributeValue;
               else if (htmlMode && attributeName.toLowerCase() == "colspan")
                  cloneNode.colSpan = attributeValue;
               else if (htmlMode && attributeName.toLowerCase() == "rowspan")
                  cloneNode.rowSpan = attributeValue;
               else if (htmlMode && attributeName.toLowerCase() == "style")
                  cloneNode.style.cssText = attributeValue;
               else if (htmlMode && attributeName.toLowerCase().substr(0,2) == "on")
               {

                  // Kludge: IE6 events don't get correctly attached
                  //         when using setAttribute. This algorithm
                  //         attaches them, except that arguments can't
                  //         be attached and the object context is
                  //         wrong. Hence, some around about processing
                  //         occurs in the event handler to correctly
                  //         call the event handler again in these
                  //         situations. Note that the event is both
                  //         attached as an event pointer and as an
                  //         attribute. The call via the pointer in
                  //         turn calls the attribute value.
                  if (XApp.isBrowser("MSIE",6.0,7.0))
                     cloneNode.attachEvent(attributeName, XNode.doEvent);
                  cloneNode.setAttribute(attributeName, attributeValue);
               }
               else
                  cloneNode.setAttribute(attributeName, attributeValue);
            }
            if (deep)
            {
               for (var i=0; i<node.childNodes.length; i++)
               {
                  var childNode = node.childNodes.item(i);
                  var clonedChildXNode = X$(childNode).clone(deep, forDoc, htmlMode);
                  cloneNode.appendChild(clonedChildXNode.valueOf());
               }
            }
            break;
         case XNode.NODE_TEXT:
            var cloneNode = forDoc.createTextNode(node.nodeValue);
            break;
         case XNode.NODE_ENTITY_REFERENCE:
            var cloneNode = forDoc.createEntityReference(node.nodeName);
            break;
         case XNode.NODE_PROCESSING_INSTRUCTION:
            var cloneNode = forDoc.createProcessingInstruction(node.nodeName, node.nodeValue);
            break;
         case XNode.NODE_COMMENT:
            var cloneNode = forDoc.createComment(node.nodeValue);
            break;
      }
   }

   return X$(cloneNode);
}

//-----------------------------------------------------------------------------

XNode.prototype.normalize = function()
{

   var node = XNode.getNodeFrom(this.valueOf());

   return node.normalize();
}

//-----------------------------------------------------------------------------

XNode.prototype.toString = function()
{

   return this.toXML();
}

//=============================================================================
