﻿//-----------------------------------------------------------------------------
// XRef
//
// Copyright 2005-2010 - Xcential Group LLC.
//
//-----------------------------------------------------------------------------

// Terminology:
//
//    urn:{model}:{setId}:{docId}:{aspect}:{format}?{query}#{fraction}
//    |-- ref -------------------------------------------------------|
//    |-- uri --------------------------------------------|
//    |-- urn ------------------------------------|
//    |-- identifier --------------------|
//
//    http://xxx/{name}.{format}?{query}#fraction
//    |-- ref ----------------------------------|
//    |-- uri -------------------------|
//    |-- url -----------------|
//    |-- identifier-----------|
//
// URN Pattern:
//
//    urn:{model}:{setId}:{docId}[-v({versionNum})|-senate|-assembly|-d({date})]:{aspect}:{[variant-]format}
//
//       where:
//          versionNum = ### (i.e. 001)
//          date = yyyymmdd
//          aspect = doc|level|vote|history|tosa
//          variant = search|raw|edit|web
//          format = slim|calm|html

XRef.prototype = new XObject;
XRef.prototype.constructor = XRef;

//=============================================================================
// Constructor

function XRef(
   ref,
   create
)
{
   ref = (ref == null) ? null : ref;
   create = (create == null) ? true : false;

   if (create)
      return new XRef(ref, false);

   //--------------------------------------------------------------------------
   // Private Interface

   //--------------------------------------------------------------------------
   // Privileged Interface

   this.valueOf = function()
   {

      return oRef;
   }

   //--------------------------------------------------------------------------

   this.setObjectValue = function(
      ref
   )
   {
      ref = (ref == null) ? null : ref;

      oRef = XRef.toRef(ref);

      return oRef;
   }

   //--------------------------------------------------------------------------
   // Initialization

   var oRef = this.setObjectValue(ref);

}

XRef.prototype.objectClass = "XRef";

//=============================================================================
// Static Interface

XRef.KEEP_MODIFIERS = true;

XRef.STYLE_URI      = "style_uri";
XRef.STYLE_FRIENDLY = "style_friendly";

XRef.REMOVE_QUERY = true;

XRef.DEFAULT_URN_ASPECT = "doc";
XRef.DEFAULT_URL_ASPECT = null;

XRef.DEFAULT_URN_FORMAT = "html";
XRef.DEFAULT_URL_FORMAT = "html";

XRef.NO_ERROR = true;

//-----------------------------------------------------------------------------

XRef.readRequest = function(
   server,
   noError
)
{
   noError = (noError == null) ? false : noError;

   var model = null;
   var setId = null;
   var docId = null;
   var aspect = XRef.DEFAULT_URN_ASPECT;
   var format = XRef.DEFAULT_URN_FORMAT;
   var query = null;
   var id = null;
   var xPath = null;

   var ref = server.getTextArg("ref");
   if (ref)
   {
      var xRef = XRef(ref);
      model = xRef.getModel();
      setId = xRef.getSetId();
      docId = xRef.getDocId(XRef.KEEP_MODIFIERS);
      aspect = xRef.getAspect();
      format = xRef.getFormat();
      query = xRef.getQuery();
      id = xRef.getId();
      xPath = xRef.getXPath();
   }

   model = server.getTextArg("model", model);
   setId = server.getTextArg("setId", setId);
   aspect = server.getTextArg("aspect", aspect).toLowerCase();
   docId = server.getTextArg("docId", (aspect == "index") ? "*" : docId);
   format = server.getTextArg("format", format).toLowerCase();
   query = server.getTextArg("query", query);
   id = server.getTextArg("id", id);
   xPath = server.getTextArg("xPath", xPath);

   if (setId == null || docId == null)
   {
      if (noError)
         return null;
      else
         throw XMsg("No set or document specified");
   }

   var urn = "urn";
   urn += ":" + model;
   urn += ":" + setId.toLowerCase();
   urn += ":" + docId.toLowerCase();
   urn += ":" + aspect;
   urn += ":" + format;

   ref = urn;
   ref += (query != null) ? "?" + query : "";
   ref += ((XString(id).isSomething()) ? "#id(" + id + ")" : ((XString(xPath).isSomething()) ? XRef.createXPointer(xPath) : ""));

   return XRef(ref);
}

XRef.toRef = function(
   ref
)
{
   ref = (ref == null) ? null : ref;

   if (ref == null)
      return null;

   switch (typeof(ref))
   {
      case "string":
         break;
      case "number":
         throw XMsg("Cannot convert a number to a ref.");
         break;
      case "boolean":
         throw XMsg("Cannot convert a boolean to a ref.");
         break;
      case "function":
         try
         {
            return XRef.toRef(ref());
         }
         catch (error)
         {
            XApp.logEvent(XApp.EVENT_ERROR, error);
            throw XMsg("Cannot convert a function to a ref.", error);
         }
         break;
      case "object":
         try
         {
            ref = ref.valueOf();
         }
         catch (error)
         {
            XApp.logEvent(XApp.EVENT_ERROR, error);
            throw XMsg("Cannot convert an object to a ref.", error);
         }
        break;
   }

   if (!XRef.isRef(ref))
      return null;

   // Canonicalize any URN
   if (XMatch(ref, /^urn:[^\#]*/i))
   {
      var urn = XMatch.lastMatch;
      var fraction = XMatch.rightContext;

      var query = "";
      if (XMatch(urn, /^urn:[^\?]*/))
      {
         urn = XMatch.lastMatch;
         query = XMatch.rightContext;
      }
      urn = urn.toLowerCase();
      if (XMatch(urn, /(^urn:[^:]*:[^:]*:[^:]*:)(:.*)/))
         urn = XMatch.matches[1] + XRef.DEFAULT_URN_ASPECT + XMatch.matches[2];
      else if (XMatch(urn, /(^urn:[^:]*:[^:]*:[^:]*:)$/))
         urn = XMatch.matches[1] + XRef.DEFAULT_URN_ASPECT;
      else if (XMatch(urn, /(^urn:[^:]*:[^:]*:[^:]*)$/))
         urn = XMatch.matches[1] + ":" + XRef.DEFAULT_URN_ASPECT;

      else if (XMatch(urn, /(^urn:[^:]*:[^:]*:[^:]*:[^:]*:)$/))
         urn = XMatch.matches[1] + XRef.DEFAULT_URN_FORMAT;
      else if (XMatch(urn, /(^urn:[^:]*:[^:]*:[^:]*:[^:]*)$/))
         urn = XMatch.matches[1] + ":" + XRef.DEFAULT_URN_FORMAT;

      ref = urn + query + fraction;
   }

   return ref;
}

//-----------------------------------------------------------------------------

XRef.isRef = function(
   item
)
{
   item = (item == null) ? null : item;

   if (item == null)
      return false;

   switch (typeof(item))
   {
      case "string":
         if ((/^<\?xml/).test(item))
            return false;
         if ((/^\s*</).test(item))
            return false;
         if (item.length == 0)
            return false;
         if ((/\s/).test(item.replace(/\[.*?\]/g,"")))
            return false;
         if ((/[\n\r]/).test(item))
            return false;
         if ((/\\/).test(item))
            return false;
         if (!(/\//).test(item) && !(/\:/).test(item))
            return false;
         return true;
         break;
      case "number":
         return false;
         break;
      case "boolean":
         return false;
         break;
      case "function":
         try
         {
            return XRef.isRef(item());
         }
         catch (error)
         {
            return false;
         }
         break;
      case "object":
         try
         {
            return XRef.isRef(item.valueOf());
         }
         catch (error)
         {
            return false;
         }
         break;
   }

   return false;
}

//-----------------------------------------------------------------------------

XRef.isRelative = function(
   url
)
{

   if ((/:/).test(url))
      return false;

   return true;
}

//-----------------------------------------------------------------------------

XRef.getRelative = function(
   url
)
{

   if (XRef.isRelative(url))
      return url;

   if (XMatch(url, new RegExp("^" + XApp.getDomainURL(), "i")))
      return XMatch.rightContext;

   return null;
}

//-----------------------------------------------------------------------------

XRef.getAbsolute = function(
   url
)
{

   if (!XRef.isRelative(url))
      return url;

   return XApp.getDomainURL() + "/" + url.replace(/^\//,"");
}

//-----------------------------------------------------------------------------

XRef.isLocal = function(
   url
)
{

   if (XRef.isRelative(url))
      return true;

   if ((new RegExp("^" + XApp.getDomainURL(), "i")).test(url))
      return true;

   return false;
}

//-----------------------------------------------------------------------------

XRef.applyAspect = function(
   ref,
   aspect
)
{
   ref = (ref == null) ? null : ref.toString();
   aspect = (aspect == null) ? null : aspect.toString();

   if (ref == null)
      return null;

   if (aspect == null)
      return ref;

   var fraction = null;
   if (XMatch(ref, /^([^\#]*)\#(.*)$/))
   {
      ref = XMatch.matches[1];
      fraction = XMatch.matches[2];
   }

   var query = null;
   if (XMatch(ref, /^([^\?]*)\?(.*)$/))
   {
      ref = XMatch.matches[1];
      query = XMatch.matches[2];
   }

   if (query != null)
   {
      query = query.replace(/^aspect=[^\&]*/, "");
      query = query.replace(/\&aspect=[^\&]*/g, "");
      query = ((query.length > 0) ? "&" : "") + "aspect=" + aspect;
   }
   else if (XMatch(ref, /^urn:(.*)/))
   {
      ref = XMatch.matches[1];
      ref = ((XMatch(ref, /(.*):[^:]*$/)) ? XMatch.matches[1] : ref) + ":" + aspect;
      ref = "urn:" + ref;
   }
   else
   {
      ref = ((XMatch(ref, /(.*)\.[^\.]*$/)) ? XMatch.matches[1] : ref) + "." + aspect;
   }

   ref += (query != null && query.length > 0) ? "?" + query : "";
   ref += (fraction != null && fraction.length > 0) ? "#" + fraction : "";

   return ref;
}

//-----------------------------------------------------------------------------

XRef.completeURN = function(
   urn,
   defaultAspect,
   defaultFormat
)
{
   defaultAspect = (defaultAspect == null) ? XRef.DEFAULT_URN_ASPECT : defaultAspect;
   defaultFormat = (defaultFormat == null) ? XRef.DEFAULT_URN_FORMAT : defaultFormat;

   if (!(/urn:[^:]+:[^:]+:[^:]+/).test(urn))
      return urn;

   if ((/urn:[^:]+:[^:]+:[^:]+:$/).test(urn))
      urn += defaultAspect;
   if ((/urn:[^:]+:[^:]+:[^:]+$/).test(urn))
      urn += ":" + defaultAspect;

   if ((/urn:[^:]+:[^:]+:[^:]+:[^:]+:$/).test(urn))
      urn += defaultFormat;
   if ((/urn:[^:]+:[^:]+:[^:]+[^:]+$/).test(urn))
      urn += ":" + defaultFormat;

   return urn;
}

//-----------------------------------------------------------------------------

XRef.createXPointer = function(
   xPath
)
{

   return "#xpointer(" + encodeURI(xPath) + ")";
}

//-----------------------------------------------------------------------------

XRef.normalizeURL = function(
   url
)
{

   if ((/^https?:/i).test(url))
      return url;

   if ((/^\//).test(url))
      return url;

   if ((/^\.\.?\//).test(url))
      return url;

   if ((/[^\/]+\.(?:com|net|edu|gov|org|biz|info|co)[\.\/]/i).test(url))
      return "http://" + url;

   if ((/[^\/]+\.(?:com|net|edu|gov|org|biz|info|co)$/i).test(url))
      return "http://" + url;

}

//-----------------------------------------------------------------------------

XRef.mapPath = function(
   url
)
{

   if (XRef.isLocal(url) && !XRef.isRelative(url))
      url = XRef.getRelative(url);

   var modifier = "";
   if (XMatch(url, /^([^#\?]*)([#\?].*)/))
   {
      url = XMatch.matches[1];
      modifier = XMatch.matches[2];
   }

   switch (XApp.getMode())
   {
      case XApp.MODE_SERVER:
         return Server.MapPath(url) + modifier;
         break;
      case XApp.MODE_STANDALONE:
         return XApp.getSitePath() + "\\" + url.replace(/\//g, "\\").replace(/^\.?\\/,"");
         break;
   }

   return null;
}

//=============================================================================
// Public Interface

//-----------------------------------------------------------------------------

XRef.prototype.toString = function(
   style
)
{
   style = (style == null) ? XRef.STYLE_URI : style;

   var ref = this.valueOf();
   //ref = decodeURI(ref);

   var refText = null;
   if (style == XRef.STYLE_FRIENDLY)
   {
      try
      {
         if (!(/^urn:/).test(this.getURI()))
            return ref;

         var model = this.getModel();
         var retrieveCmd = model.toUpperCase() + ".composeTitle('" + ref + "')";
         eval("refText = " + retrieveCmd);
      }
      catch (error)
      {
         refText = ref;
      }
   }

   return (XString(refText).isNothing()) ? ref : refText;
}

//-----------------------------------------------------------------------------

XRef.prototype.getURI = function(
   removeQuery
)
{
   removeQuery = (removeQuery == null) ? false : removeQuery;

   var ref = this.valueOf();

   var uri = ref;
   if (XMatch(ref, /^([^\#]*)\#/))
   {
      uri = XMatch.matches[1];
      uri = (uri.length > 0) ? uri : null;
   }

   if (removeQuery && uri)
      uri = uri.replace(/\?.*$/, "");

   return uri;
}

//-----------------------------------------------------------------------------

XRef.prototype.getURN = function(
   removeQuery
)
{
   removeQuery= (removeQuery == null) ? false : removeQuery;

   var uri = this.getURI(removeQuery);

   var urn = null;
   if (uri == null)
      return urn;

   if ((/^urn:/).test(uri))
   {
      urn = uri;
   }

   return urn;
}

//-----------------------------------------------------------------------------

XRef.prototype.setURN = function(
   urn
)
{

   var ref = this.valueOf();

   if (XString(ref).isSomething() && XMatch(ref, /^[^\?\#]+/)) // URN excludes the query
      ref = urn + XMatch.rightContext;
   else
      ref = urn;

   this.setObjectValue(ref);

   return urn;
}

//-----------------------------------------------------------------------------

XRef.prototype.isLocalRef = function()
{

   return XRef.isLocal(this.getURL());
}

//-----------------------------------------------------------------------------

XRef.prototype.getURL = function(
   removeQuery
)
{
   removeQuery = (removeQuery == null) ? null : removeQuery;

   var uri = this.getURI(removeQuery);

   var url = null;
   if (uri == null)
      return url;

   if (!(/^urn:/).test(uri))
   {
      url = uri;
   }
   else if (XMatch(uri, /^urn:([^:]+)/))
   {
      var urn = XRef.completeURN(uri);
      var model = XMatch.matches[1];
      var query = null;
      if (XMatch(urn, /^([^\?]+)\?(.*)$/))
      {
         urn = XMatch.matches[1];
         query = XMatch.matches[2];
      }
      if (XMatch("&" + query, /\&url=([^\&]+)/i))
         url = decodeURIComponent(XMatch.matches[1]);
      else
         url = XLocationMap().mapURN(urn);
      if (url != null && XString(query).isSomething())
         url += (((/\?/).test(url)) ? "&" : "?") + query;
   }

   if (url && !(/^https?:/).test(url) && !(/^ftp:/).test(url) && !(/^\//).test(url))
      url = XApp.getDomainURL() + XApp.URL_APP + url;

   return url;
}

//-----------------------------------------------------------------------------

XRef.prototype.setURL = function(
   url
)
{

   var ref = this.valueOf();

   if (XString(ref).isSomething() && XMatch(ref, /^[^\#]+/)) // URL includes the query
      ref = url + XMatch.rightContext;
   else
      ref = url;

   this.setObjectValue(ref);

   return url;
}

//-----------------------------------------------------------------------------

XRef.prototype.getIdentifier = function()
{

   var uri = this.getURI(XRef.REMOVE_QUERY);

   var identifier = uri;

   if (XMatch(identifier, /^(urn:[^:]*:[^:]*:[^:]*:[^:]*):.*/))
      identifier = XMatch.matches[1];

   return identifier;
}

//-----------------------------------------------------------------------------

XRef.prototype.getModel = function()
{

   var urn = this.getURN();
   if (urn != null)
   {
      if (XMatch(urn, /^urn:([^:]+)/))
      {
         var model = XMatch.matches[1];
         return model;
      }
   }

   var url = this.getURL();
   if (url != null)
   {
      if (XMatch(url, /[?&]model=([^&]+)/i))
      {
         var model = XMatch.matches[1];
         return model;
      }
   }

   return null;
}

//-----------------------------------------------------------------------------

XRef.prototype.getSetId = function()
{

   var urn = this.getURN();
   if (urn != null)
   {
      if (XMatch(urn, /^urn:[^:]*:([^:]+)/))
      {
         var setId = XMatch.matches[1];
         return setId;
      }
   }

   var url = this.getURL();
   if (url != null)
   {
      if (XMatch(url, /[?&]setId=([^&]+)/i))
      {
         var setId = XMatch.matches[1];
         return setId;
      }
   }

   return null;
}

//-----------------------------------------------------------------------------

XRef.prototype.getDocId = function(
   keepModifiers,
   castAs
)
{
   keepModifiers = (keepModifiers == null) ? false : keepModifiers;
   castAs = (castAs == null) ? null : castAs;

   var urn = this.getURN();
   if (urn != null)
   {
      if (XMatch(urn, /^urn:[^:]*:[^:]*:([^:]+)/))
      {
         var docId = XMatch.matches[1];
         docId = (keepModifiers) ? docId : docId.replace(/-.*/, "");
         return (castAs == null) ? docId : castAs(docId);
      }
   }

   var url = this.getURL();
   if (url != null)
   {
      if (XMatch(url, /[?&]docId=([^&]+)/i))
      {
         var docId = XMatch.matches[1];
         docId = (keepModifiers) ? docId : docId.replace(/-.*/, "");
         return (castAs == null) ? docId : castAs(docId);
      }
   }

   return null;
}

//-----------------------------------------------------------------------------

XRef.prototype.setDocId = function(
   docId
)
{

   var urn = this.getURN();
   if (urn != null)
   {
      if (XMatch(urn, /^(urn:[^:]*:[^:]*:)([^:]+)(.*)/))
         urn = XMatch.matches[1] + docId + XMatch.matches[3];
      else
         urn = urn + ":" + docId;
      this.setURN(urn);
      return docId;
   }

   return null;
}

//-----------------------------------------------------------------------------

XRef.prototype.getShortName = function(
   castAs
)
{
   castAs = (castAs == null) ? null : castAs;

   var docId = this.getDocId();
   var shortName = null;
   if (docId)
   {
      shortName = docId.toUpperCase();
      shortName = shortName.replace(/^0+/, "");
      shortName = shortName.replace(/([^0-9])0+/g, "$1 ");
   }

   return shortName;
}

//-----------------------------------------------------------------------------

XRef.prototype.getModifier = function(
   numOrPrefix,
   castAs
)
{
   castAs = (castAs == null) ? null : castAs;

   //--------------------------------------------------------------------------

   function extractModifier(
      text,
      numOrPrefix
   )
   {
      var modifier = null;
      var count = 0;

      if (typeof(numOrPrefix) == "number")
      {
         var num = numOrPrefix;
         while (text && XMatch(text, /-([^-]*)/))
         {
            modifier = XMatch.matches[1];
            text = XMatch.rightContext;
            count++;
            if (count == num)
               return modifier;
         }
      }
      else
      {
         var prefix = numOrPrefix;
         while (text && XMatch(text, /-([^-]*)/))
         {
            modifier = XMatch.matches[1];
            text = XMatch.rightContext;
            if (XMatch(modifier, "^" + prefix, "i"))
               return XMatch.rightContext;
         }
      }

      return null;
   }

   //--------------------------------------------------------------------------

   var urn = this.getURN();
   if (urn != null)
   {
      if (XMatch(urn, /^urn:[^:]*:[^:]*:([^:]+)/))
      {
         var docId = XMatch.matches[1];
         var modifier = extractModifier(docId, numOrPrefix);
         return (modifier == null) ? null : (castAs == null) ? modifier : castAs(modifier);
      }
   }

   var url = this.getURL();
   if (url != null)
   {
      if (XMatch(url, /[?&]docId=([^&]+)/i))
      {
         var docId = XMatch.matches[1].replace(/-.*/, "");
         var modifier = extractModifier(docId, numOrPrefix);
         return (modifier == null) ? null : (castAs == null) ? modifier : castAs(modifier);
      }
   }

   return null;
}

//-----------------------------------------------------------------------------

XRef.prototype.removeModifier = function(
   numOrPrefix
)
{
   var modifier = null;
   var count = 0;

   var docId = null;

   var urn = this.getURN();
   if (urn != null && XMatch(urn, /^urn:[^:]*:[^:]*:([^:]+)/))
      docId = XMatch.matches[1];

   if (docId == null)
   {
      var url = this.getURL();
      if (url != null && XMatch(url, /[?&]docId=([^&]+)/i))
         var docId = XMatch.matches[1];
   }

   if (docId != null)
   {
      if (typeof(numOrPrefix) == "number")
      {
         var num = numOrPrefix;
         var leftContext = "";
         while (docId && XMatch(docId, /(-[^-]*)/))
         {
            count++;
            if (count == num)
               break;
            leftContext += XMatch.leftContext + XMatch.matches[1];
            docId = XMatch.rightContext;
         }
         docId = leftContext + XMatch.rightContext;
      }
      else
      {
         var prefix = numOrPrefix;
         docId = docId.replace(new RegExp("-" + prefix + "[^-]*", "i"), "");
      }
   }

   this.setDocId(docId);

}

//-----------------------------------------------------------------------------

XRef.prototype.getVersionId = function(
   castAs
)
{
   castAs = (castAs == null) ? null : castAs;

   var versionId = "vl";

   var urn = this.getURN();
   if (urn != null)
   {
      if (XMatch(urn, /^urn:[^:]*:[^:]*:[^:]*-(v[^-:]+)/))
      {
         versionId = XMatch.matches[1];
         return (castAs == null) ? versionId : castAs(versionId);
      }
   }

   var url = this.getURL();
   if (url != null)
   {
      if (XMatch(url, /[?&]docId=[^&]*-(v[^-&]+)/i))
      {
         var versionId = XMatch.matches[1];
         return (castAs == null) ? versionId : castAs(versionId);
      }
      if (XMatch(url, /[?&]versionId=([^&]+)/i))
      {
         versionId = XMatch.matches[1];
         return (castAs == null) ? versionId : castAs(versionId);
      }
      if (XMatch(url, /[?&]versionNum=([^&]+)/i))
      {
         var versionNum = XMatch.matches[1];
         versionId = "v" + versionNum;
         return (castAs == null) ? versionId : castAs(versionId);
      }
   }

   return (castAs == null) ? versionId : castAs(versionId);
}

//-----------------------------------------------------------------------------

XRef.prototype.getVersionNum = function(
   castAs
)
{
   castAs = (castAs == null) ? null : castAs;

   var versionNum = null;

   var urn = this.getURN();
   if (urn != null)
   {
      if (XMatch(urn, /^urn:[^:]*:[^:]*:[^:]*-v([0-9]+)/))
      {
         versionNum = XMatch.matches[1];
         return (castAs == null) ? versionNum : castAs(versionNum);
      }
   }

   var url = this.getURL();
   if (url != null)
   {
      if (XMatch(url, /[?&]docId=[^&]*-v([0-9]+)/i))
      {
         versionNum = XMatch.matches[1];
         return (castAs == null) ? versionNum : castAs(versionNum);
      }
      if (XMatch(url, /[?&]versionNum=([0-9]+)/i))
      {
         versionNum = XMatch.matches[1];
         return (castAs == null) ? versionNum : castAs(versionNum);
      }
   }

   return (castAs == null) ? versionNum : castAs(versionNum);
}

//-----------------------------------------------------------------------------

XRef.prototype.getDate = function(
   castAs
)
{
   castAs = (castAs == null) ? null : castAs;

   var urn = this.getURN();
   if (urn != null)
   {
      if (XMatch(urn, /^urn:[^:]*:[^:]*:[^:]*-d([^-:]+)/))
      {
         var dateText = XMatch.matches[1];
         return (castAs == null) ? dateText : castAs(dateText);
      }
   }

   var url = this.getURL();
   if (url != null)
   {
      if (XMatch(url, /[?&]docId=[^&]*-d([^&]+)/i))
      {
         var dateText = XMatch.matches[1];
         return (castAs == null) ? dateText : castAs(dateText);
      }
      if (XMatch(url, /[?&]dateText=([^&]+)/i))
      {
         var dateText = XMatch.matches[1];
         return (castAs == null) ? dateText : castAs(dateText);
      }
   }

   return null;
}

//-----------------------------------------------------------------------------

XRef.prototype.getAspect = function(
   castAs
)
{
   castAs = (castAs == null) ? null : castAs;

   var aspect = null;

   var uri = this.getURI(XRef.REMOVE_QUERY);
   if (uri != null)
   {
      if (XMatch(uri, /^urn:[^:]*:[^:]*:[^:]*:([^:]+)/))
      {
         aspect = XMatch.matches[1];
         return (castAs == null) ? aspect : castAs(aspect);
      }

      // Look to see if the URI is a URL with a query set (do not convert the URI to a URL)
      if (XMatch(uri, /[?&]aspect=([^&]+)/i))
      {
         aspect = XMatch.matches[1];
         return (castAs == null) ? aspect : castAs(aspect);
      }

      aspect = ((/^urn:/).test(uri)) ? XRef.DEFAULT_URN_ASPECT : XRef.DEFAULT_URL_ASPECT;
   }

   return (castAs == null) ? aspect : castAs(aspect);
}

//-----------------------------------------------------------------------------

XRef.prototype.setAspect = function(
   aspect
)
{
   aspect = (aspect == null) ? null : aspect;

   if (!aspect)
      return null;

   // If the config for an object, remove the modifiers (ie. version)
   if (aspect == "config")
      this.setDocId(this.getDocId());

   var urn = this.getURN();
   if (urn != null)
   {
      if (XMatch(urn, /^(urn:[^:]*:[^:]*:[^:]*:)([^:]+)(.*)/))
         urn = XMatch.matches[1] + aspect + XMatch.matches[3];
      else if (XMatch(urn, /^(urn:[^:]*:[^:]*:[^:]*)/))
         urn = XMatch.matches[1] + ":" + aspect;
      else
         urn = urn + ":" + aspect;
      this.setURN(urn);
      return this.getURN();
   }

   var url = this.getURL();
   if (url != null)
   {
      this.setArg("aspect", aspect);
      return this.getURL();
   }

   return null;
}

//-----------------------------------------------------------------------------

XRef.prototype.getName = function(
   castAs
)
{

   var name = null;

   var url = this.getURL();
   if (url)
   {
      url = url.replace(/\?.*/,"");
      if (XMatch(url, /.*\/([^\/]+)$/))
      {
         name = XMatch.matches[1];
         name = name.replace(/\.[^\.]*$/,"");
      }
   }

   return name;
}

//-----------------------------------------------------------------------------

XRef.prototype.getFormat = function(
   keepModifiers,
   castAs
)
{
   keepModifiers = (keepModifiers == null) ? false : keepModifiers;
   castAs = (castAs == null) ? null : castAs;

   //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

   function removeModifiers(
      format
   )
   {

      return format.replace(/^.*\-([^\-]*)$/,"$1");
   }

   //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

   var format = null;

   var urn = this.getURN(XRef.REMOVE_QUERY);
   if (urn != null)
   {
      if (XMatch(urn, /^urn:[^:]*:[^:]*:[^:]*:[^:]*:([^:]+)/))
      {
         format = XMatch.matches[1];

         format = (keepModifiers) ? format : removeModifiers(format);
         return (castAs == null) ? format : castAs(format);
      }
      else
         format = XRef.DEFAULT_URN_FORMAT;
   }

   var url = this.getURL();
   if (url != null)
   {
      if (XMatch(url, /[?&]format=([^&]+)/i))
      {
         format = XMatch.matches[1];
      }
      else if (XMatch(url, /\/rss\//i))
      {
         format = "rss";
      }
      else if (XMatch(url, /\=rss/i))
      {
         format = "rss";
      }
      else if (XMatch(url, /\/atom\//i))
      {
         format = "atom";
      }
      else if (XMatch(url, /\=atom/i))
      {
         format = "atom";
      }
      else
      {
         url = url.replace(/\?.*/, "");
         if (XMatch(url, /\.([^\/\.]+)$/))
         {
            format = XMatch.matches[1].toLowerCase();
         }
         else
            format = XRef.DEFAULT_URL_FORMAT;
      }
   }

   format = (keepModifiers) ? format : removeModifiers(format);
   return (castAs == null) ? format : castAs(format);
}

//-----------------------------------------------------------------------------

XRef.prototype.setVariant = function(
   variant
)
{
   variant = (variant == null) ? null : variant;

   var format = this.getFormat(XRef.KEEP_MODIFIERS);
   format = (format) ? format.replace(/^.*\-([^\-]*)$/,"$1") : "";
   format = ((variant) ? variant + "-" : "") + format;
   this.setFormat(format);

   return variant;
}

//-----------------------------------------------------------------------------

XRef.prototype.getVariant = function()
{

   var variant = null;
   var format = this.getFormat(XRef.KEEP_MODIFIERS);

   if (XMatch(format, /^(.*)\-[^\-]*$/))
      variant = XMatch.matches[1];

   return variant;
}

//-----------------------------------------------------------------------------

XRef.prototype.setFormat = function(
   format
)
{
   format = (format == null) ? null : format;

   if (!format)
      return null;

   var fraction = this.getFraction();
   fraction = (XString(fraction).isSomething()) ? "#" + fraction : "";

   var urn = this.getURN();
   if (urn != null)
   {
      if (XMatch(urn, /^(urn:[^:]*:[^:]*:[^:]*:[^:]*:)([^:]+)(.*)/))
         urn = XMatch.matches[1] + format + XMatch.matches[3];
      if (XMatch(urn, /^(urn:[^:]*:[^:]*:[^:]*:[^:]*)/))
         urn = XMatch.matches[1] + ":" + format;
      else if (XMatch(urn, /^(urn:[^:]*:[^:]*:[^:]*)/))
         urn = XMatch.matches[1] + ":" + XRef.DEFAULT_URN_ASPECT + ":" + format;
      else
         urn = urn + ":" + format;
      this.setURN(urn);
      return this.getURN() + fraction;
   }

   var url = this.getURL();
   if (url != null)
   {
      this.setArg("format", format);
      return this.getURL() + fraction;
   }

   return null;
}

//-----------------------------------------------------------------------------

XRef.prototype.getQuery = function(
   castAs
)
{
   castAs = (castAs == null) ? null : castAs;

   var uri = this.getURI();

   var query = null;
   if (uri == null)
      return null;

   if (XMatch(uri, /\?(.*)$/))
   {
      query = XMatch.matches[1].replace(/\#.*$/, "");
   }

   return (query == null) ? null : (castAs == null) ? query : castAs(query);
}

//-----------------------------------------------------------------------------

XRef.prototype.getArg = function(
   argName,
   castAs
)
{
   argName = (argName == null) ? null : argName;
   castAs = (castAs == null) ? null : castAs;

   if (argName == null)
      return null;

   var query = this.getQuery();
   if (query == null)
      return null;

   var argValue = null;
   if (XMatch("&" + query + "&", "&" + argName + "=([^\&]*)", "i"))
      var argValue = decodeURIComponent(XMatch.matches[1]);

   return (argValue == null) ? null : (castAs == null) ? argValue : castAs(argValue);
}

//-----------------------------------------------------------------------------

XRef.prototype.setArg = function(
   argName,
   argValue
)
{
   argName = (argName == null) ? null : argName;
   argValue = (argValue == null) ? null : encodeURIComponent(argValue);

   var uri = this.getURI();

   var leftContext = "";
   var rightContext = "";

   var query = null;
   if (XMatch(uri, /\?(.*)$/))
   {
      leftContext = XMatch.leftContext;
      query = XMatch.matches[1];
      if (XMatch(query, /(\#.*$)/))
      {
         query = XMatch.leftContext;
         rightContext = XMatch.matches[1];
      }
      if (XMatch("&" + query + "&", "&" + argName + "=([^\&]*)" + "&", "i"))
      {
         var leftQuery = XMatch.leftContext;
         var rightQuery = XMatch.rightContext;
         leftContext = leftContext + ((leftQuery.length > 0) ? "?" + leftQuery.replace(/^&/, "") + "&" : "?");
         rightContext = ((rightQuery.length > 0) ? "&" + rightQuery.replace(/&$/, "") : "") + rightContext;
      }
      else
         leftContext = leftContext + "?" + query + "&";
   }
   else if (XMatch(uri, /(\#.*$)/))
   {
      leftContext = XMatch.leftContext + "?";
      rightContext = XMatch.matches[1];
   }
   else
      leftContext = uri + "?";

   uri = leftContext + argName + "=" + argValue + rightContext;

   this.setObjectValue(uri);

   return argValue;
}

//-----------------------------------------------------------------------------

XRef.prototype.delArg = function(
   argName
)
{

   var uri = this.getURI();

   uri = uri.replace(RegExp("([\\?\\&])" + argName + "=" + "[^\\&]*\\&?","gi"),"$1").replace(/[\?\&]$/,"");

   this.setObjectValue(uri);

   return uri;
}

//-----------------------------------------------------------------------------

XRef.prototype.addArgs = function(
   args
)
{
   args = (args == null) ? "" : args;

   var url = this.getURL();
   url = url.replace(/[\?\&]+$/, "");

   args = args.replace(/^[\?\&]+/,"");
   args = args.replace(/\&+/g, "&");

   if (args.length > 0)
      url += (((/\?/).test(url)) ? "&" : "?") + args;

   this.setObjectValue(url);

   return url;
}

//-----------------------------------------------------------------------------

XRef.prototype.getFraction = function(
   castAs
)
{
   castAs = (castAs == null) ? null : castAs;

   var ref = this.valueOf();

   var fraction = null;
   if (XMatch(ref, /\#(.*)/))
   {
      fraction = XMatch.matches[1];
      fraction = (fraction.length > 0) ? fraction : null;
   }

   return (fraction == null) ? null : (castAs == null) ? fraction : castAs(fraction);
}

//-----------------------------------------------------------------------------

XRef.prototype.setFraction = function(
   fraction
)
{
   fraction = (fraction == null) ? null : fraction;

   var ref = this.valueOf();

   ref = ref.replace(/\#.*$/, "") + ((fraction) ? "#" + fraction : "");

   this.setObjectValue(ref);

   return fraction;
}

//-----------------------------------------------------------------------------

XRef.prototype.hasFraction = function()
{

   var ref = this.valueOf();

   var fraction = null;
   if (XMatch(ref, /\#(.*)/))
   {
      fraction = XMatch.matches[1];
      fraction = (fraction.length > 0) ? fraction : null;
   }

   return (fraction == null) ? false : true;
}

//-----------------------------------------------------------------------------

XRef.prototype.getId = function(
   castAs
)
{
   castAs = (castAs == null) ? null : castAs;

   var fraction = this.getFraction();
   if (fraction == null)
      return null;

   var id = null;

   if (XMatch(fraction, /^id\(([^\)]+)\)$/))
   {
      id = XMatch.matches[1];

      if (XMatch(id, /\[.*\]$/))
         id = XMatch.leftContext;
   }

   return (id == null) ? null : (castAs == null) ? id : castAs(id);
}

//-----------------------------------------------------------------------------

XRef.prototype.getIdLabel = function(
   castAs
)
{
   castAs = (castAs == null) ? null : castAs;

   var fraction = this.getFraction();
   if (fraction == null)
      return null;

   var id = null;
   var label = null;

   if (XMatch(fraction, /^id\(([^\)]+)\)$/))
   {
      id = XMatch.matches[1];

      if (XMatch(id, /\[(.*)\]$/))
         label = XMatch.matches[1];
   }

   return (label == null) ? null : (castAs == null) ? label : castAs(label);
}

//-----------------------------------------------------------------------------

XRef.prototype.getXPath = function(
   castAs
)
{
   castAs = (castAs == null) ? null : castAs;

   var fraction = this.getFraction();
   if (fraction == null)
      return null;

   var xPath = null;

   if (XMatch(fraction, /^xpointer\((.*)\)$/))
   {
      xPath = XMatch.matches[1];
      xPath = decodeURI(xPath);
   }

   return (xPath == null) ? null : (castAs == null) ? xPath : castAs(xPath);
}

//-----------------------------------------------------------------------------

XRef.prototype.retrieve = function(
   castAs
)
{
   castAs = (castAs == null) ? ((castAs.getId() == null && castAs.getXPath() == null) ? XDoc : XNode) : castAs;

   return castAs(this.toString());
}

//-----------------------------------------------------------------------------
