﻿//-----------------------------------------------------------------------------
// SLIM
//
// Copyright 2005-2010 - Xcential Group LLC.
//
//-----------------------------------------------------------------------------

//=============================================================================
// Constructor

function SLIM()
{

   //--------------------------------------------------------------------------
   // Private Interface

   //--------------------------------------------------------------------------
   // Privileged Interface

   //--------------------------------------------------------------------------
   // Initialization

}

SLIM.objectClass = "SLIM";

//=============================================================================
// Static Interface

SLIM.NAMESPACE_URI               = "http://www.xcential.com/schemas/2005/slim";
SLIM.PREFERRED_PREFIX            = "slim";

SLIM.NUM_STYLE_VALUE             = "value";
SLIM.NUM_STYLE_TEXT              = "text";
SLIM.NUM_STYLE_NORMALIZED        = "normalized";

SLIM.CHANGE_NORMAL               = "normal";
SLIM.CHANGE_INSERTION            = "insertion";
SLIM.CHANGE_DELETION             = "deletion";

SLIM.LINKING_LINKED              = "linked"
SLIM.LINKING_EMBEDDED            = "embedded";
SLIM.LINKING_CACHED              = "cached";

SLIM.ACTION_ADDS                 = "adds";
SLIM.ACTION_ADDS_BY_RENUM        = "adds_by_renum";
SLIM.ACTION_ADDS_AND_REPEALS     = "adds_and_repeals";
SLIM.ACTION_INSERTS              = "inserts";
SLIM.ACTION_AMENDS               = "amends";
SLIM.ACTION_AMENDS_AND_RENUMS    = "amends_and_renums";
SLIM.ACTION_AMENDS_AND_REPEALS   = "amends_and_repeals";
SLIM.ACTION_RENUMS               = "renums";
SLIM.ACTION_REPEALS              = "repeals";
SLIM.ACTION_DELETES              = "deletes";

SLIM.STATUS_PROPOSED             = "proposed";
SLIM.STATUS_NEW                  = "new";
SLIM.STATUS_ADDED                = "added";
SLIM.STATUS_INSERTED             = "inserted";
SLIM.STATUS_REVISED              = "revised";
SLIM.STATUS_UPDATED              = "updated";
SLIM.STATUS_DELETED              = "deleted";
SLIM.STATUS_REPEALED             = "repealed";
SLIM.STATUS_VIRTUAL              = "virtual";
SLIM.STATUS_RENUMBERED           = "renumbered";

SLIM.VERSIONED                   = true;
SLIM.UNVERSIONED                 = false;

SLIM.REJECT_CHANGES              = true;

SLIM.SINGLE_PI_DELETION          = true;
SLIM.DUAL_PI_DELETION            = false;

SLIM.NEW_LINE_AFTER_NUM          = true;
SLIM.EN_SPACE_AFTER_NUM          = false;

SLIM.deletionStyle = SLIM.SINGLE_PI_DELETION;

//-----------------------------------------------------------------------------

SLIM.valueOf = function()
{

   return "SLIM";
}

//-----------------------------------------------------------------------------
// Content Management
//-----------------------------------------------------------------------------

SLIM.cast = function(
   item
)
{
   item = (item == null) ? null : item;

   if (XObject(item).isNothing())
      return null;

   //--------------------------------------------------------------------------

   function castTo(
      item,
      role
   )
   {
      role = (role == null) ? null : role.toString();

      if (role == null)
         return null;

      var slimItem = null;
      switch (role)
      {
         case "document":
            slimItem = SLIM_Document(item);
            break;
         case "propertyset":
            slimItem = SLIM_PropertySet(item);
            break;
         case "about":
            slimItem = SLIM_About(item);
            break;
         case "container":
            slimItem = SLIM_Container(item);
            break;
         case "property":
            slimItem = SLIM_Property(item);
            break;
         case "outline":
            slimItem = SLIM_Outline(item);
            break;
         case "folder":
            slimItem = SLIM_Folder(item)
            break;
         case "item":
            slimItem = SLIM_Item(item);
            break;
         case "catalog":
            slimItem = SLIM_Catalog(item);
            break;
         case "body":
            slimItem = SLIM_Body(item);
            break;
         case "level":
            slimItem = SLIM_Level(item);
            break;
         case "heading":
            slimItem = SLIM_Heading(item);
            break;
         case "section":
            slimItem = SLIM_Section(item);
            break;
         case "version":
            slimItem = SLIM_Version(item);
            break;
         case "num":
            slimItem = SLIM_Num(item);
            break;
         case "fragment":
            slimItem = SLIM_Fragment(item);
            break;
         case "include":
            slimItem = SLIM_Include(item);
            break;
         case "fef":
            slimItem = SLIM_Ref(item);
            break;
      }

      return slimItem;
   }

   //--------------------------------------------------------------------------

   var slimItem = null;
   switch (typeof(item))
   {
      case "string":
         var xNode = X$(item);
         slimItem = SLIM.Cast(xNode);
         break;
      case "number":
         throw XMsg("Cannot cast a number to be a SLIM object.");
         break;
      case "boolean":
         throw XMsg("Cannot cast a boolean to be a SLIM object.");
         break;
      case "function":
         try
         {
            return SLIM.cast(item());
         }
         catch (error)
         {
            XApp.logEvent(XApp.EVENT_ERROR, error);
            throw XMsg("Cannot cast a function to be a SLIM object.", error);
         }
      case "object":
         try
         {
            if (item.getNamespaceURI() == SLIM.NAMESPACE_URI)
            {
               var role = XString(item.getLocalName()).toLowerCase();
               slimItem = castTo(item, role);
               if (slimItem == null)
               {
                  var base = XString(item.getAttribute("base")).toLowerCase();
                  var role = base.replace(/^slim\:/, "");
                  slimItem = castTo(item, role);
                  if (slimItem == null)
                  {
                     role = XString(item.getAttribute("role")).toLowerCase();
                     slimItem = castTo(item, role);
                  }
               }
            }
         }
         catch (error)
         {
            slimItem = null;
         }
         break;
   }

   return slimItem;
}

//-----------------------------------------------------------------------------

SLIM.isA = function(
   xNode,
   slimName,
   className
)
{
   xNode = (xNode == null) ? null : xNode;
   slimName = (slimName == null) ? null : slimName.toString();
   className = (className == null) ? null : className.toString();

   if (xNode == null || slimName == null)
      return false;

   if (xNode.isNothing())
      return false;

   if (xNode.getNodeType() != XNode.NODE_ELEMENT)
      return false;

   if (className != null && xNode.getAttribute("class") != className)
      return false;

   if (slimName == "*" && xNode.getNamespaceURI() == SLIM.NAMESPACE_URI)
      return true;

   if (xNode.getAttribute("role") == slimName.toLowerCase())
      return true;

   if (xNode.getAttribute("base") == "slim:" + slimName)
      return true;

   if (xNode.getLocalName() == slimName && xNode.getNamespaceURI() == SLIM.NAMESPACE_URI)
      return true;

   return false;
}

//-----------------------------------------------------------------------------

SLIM.isContent = function(
   item,
   ignoreWhitespace
)
{
   item = (item == null) ? "" : item.toString();
   ignoreWhitespace = (ignoreWhitespace == null) ? false : ignoreWhitespace;

   if ((/\s*<<[^>]*>>\s*$/).test(item))
      return false;
   if (item.length > 0)
   {
      if ((/^\s+$/).test(item) && ignoreWhitespace)
         return false;
      else
         return true;
   }

   return false;
}

//-----------------------------------------------------------------------------

SLIM.isPI = function(
   item,
   name
)
{
   item = (item == null) ? "" : item.toString();

   if ((new RegExp("^\\s*<<" + name + "\\s+[^>]*>>\\s*$")).test(item))
      return true;
   if ((new RegExp("^\\s*<<" + name + "\\s*>>\\s*$")).test(item))
      return true;

   return false;
}

//-----------------------------------------------------------------------------

SLIM.getStartingPI = function(
   items,
   name
)
{
   items = (items == null) ? [] : items;

   for (var i=0; i<items.length; i++)
   {
      var item = items[i];
      if (SLIM.isContent(item, true))
         return -1;

      if (SLIM.isPI(item, name))
         return i;
   }

   return -1;
}

//-----------------------------------------------------------------------------

SLIM.getEndingPI = function(
   items,
   name
)
{
   items = (items == null) ? [] : items;

   for (var i=items.length-1; i>=0; i--)
   {
      var item = items[i];
      if (SLIM.isContent(item, true))
         return -1;

      if (SLIM.isPI(item, name))
         return i;
   }

   return -1;
}

//-----------------------------------------------------------------------------

SLIM.hasMiddlePI = function(
   items,
   name
)
{
   items = (items == null) ? [] : items;
   name = (name == null) ? null : name.toString();

   if (name == null)
      return false;

   var startContentFound = false;
   var piFound = false;
   var endContentFound = false;
   for (var i=0; i<items.length; i++)
   {
      var item = items[i];
      if (SLIM.isContent(item, true))
      {
         if (!piFound)
            startContentFound = true;
         else
            endContentFound = true;
      }
      if (startContentFound && SLIM.isPI(item, name))
         piFound = true;
   }

   return (startContentFound && piFound && endContentFound) ? true : false;
}

//-----------------------------------------------------------------------------
// State Management
//-----------------------------------------------------------------------------

SLIM.getState = function(
   xinput
)
{
   var xoutput = xinput;

   var state = SLIM.CHANGE_NORMAL;
   if ((/^\|\-\-/).test(xoutput))
      state = SLIM.CHANGE_DELETION;
   else if ((/^\|\+\+/).test(xoutput))
      state = SLIM.CHANGE_INSERTION;

   return state;
}

//-----------------------------------------------------------------------------

SLIM.removeState = function(
   xinput
)
{
   var xoutput = xinput;

   xoutput = xoutput.replace(/\|\-\-/g, "");
   xoutput = xoutput.replace(/\|\+\+/g, "");

   return xoutput;
}

//-----------------------------------------------------------------------------

SLIM.protectPIs = function(
   xinput
)
{
   xinput = (xinput == null) ? "" : xinput.toString();

   var xoutput = xinput;
   xoutput = xoutput.replace(/<\?/g, "<<");
   xoutput = xoutput.replace(/\?>/g, ">>");

   return xoutput;
}

//-----------------------------------------------------------------------------

SLIM.unprotectPIs = function(
   xinput
)
{
   xinput = (xinput == null) ? "" : xinput.toString();

   var xoutput = xinput;
   xoutput = xoutput.replace(/<</g, "<?");
   xoutput = xoutput.replace(/>>/g, "?>");

   return xoutput;
}


//-----------------------------------------------------------------------------

SLIM.createInsertionText = function(
   xinput
)
{

   var xoutput = SLIM.acceptChanges(xinput);
   xoutput = "<?insertion_start?>" + xoutput + "<?insertion_end?>";

   return xoutput;
}

//-----------------------------------------------------------------------------

SLIM.createDeletionText = function(
   xinput
)
{

   var xoutput = SLIM.acceptChanges(xinput);

   xoutput = "<\?deletion data=\"" + XString(xoutput).encode(XString.ENCODE_ENTITY) + "\"\?>";

   return xoutput;
}

//-----------------------------------------------------------------------------

SLIM.acceptChanges = function(
   xinput
)
{
   xinput = (xinput == null) ? "" : xinput.toString();

   if (!(/<\?deletion/).test(xinput) && !(/<\?insertion/).test(xinput) && !(/\|[\-\+]{2}/).test(xinput))
      return xinput;

   var xoutput = SLIM_Converter.protectPIs(xinput);

   xoutput = xoutput.replace(/\|\-\-[^\s]+/g, "");
   xoutput = xoutput.replace(/\|\+\+/g, "");
   xoutput = xoutput.replace(/<<deletion\s+[^>]*>>/g, "");
   xoutput = xoutput.replace(/<<deletion_start[^>]*>>.*?<<deletion_end[^>]*>>/g, "");
   xoutput = xoutput.replace(/<<insertion_start[^>]*>>/g, "");
   xoutput = xoutput.replace(/<<insertion_end[^>]*>>/g, "");

   // Remove any blank paragraphs that might be left behind
   xoutput = xoutput.replace(/<(xhtml:|)p\s*\/>/g, "");
   xoutput = xoutput.replace(/<(xhtml:|)p\s+[^>\/]*\/>/g, "");
   xoutput = xoutput.replace(/<(xhtml:|)p\s*>\s*<\/(xhtml:|)p>/g, "");
   xoutput = xoutput.replace(/<(xhtml:|)p\s+[^>]*>\s*<\/(xhtml:|)p>/g, "");

   xoutput = xoutput.replace(/[\n\r]/g, " ");

   // Clean up the spaces
   xoutput = xoutput.replace(/<([^\/\<][^>]*?[^\/\>])>\s+/g, "<$1>");
   xoutput = xoutput.replace(/\s+<\/([^>]*?)>/g, "</$1>");

   return SLIM_Converter.unprotectPIs(xoutput);
}

//-----------------------------------------------------------------------------

SLIM.rejectChanges = function(
   xinput
)
{
   xinput = (xinput == null) ? "" : xinput.toString();

   if (!(/<\?deletion/).test(xinput) && !(/<\?insertion/).test(xinput) && !(/\|[\-\+]{2}/).test(xinput))
      return xinput;

   var xoutput = SLIM_Converter.protectPIs(xinput);

   xoutput = xoutput.replace(/\|\+\+[^\s]+/g, "");
   xoutput = xoutput.replace(/\|\-\-/g, "");
   xoutput = xoutput.replace(/<<insertion_start[^>]*>>.*?<<insertion_end[^>]*>>/g, "");
   xoutput = xoutput.replace(/<<deletion_start[^>]*>>/g, "");
   xoutput = xoutput.replace(/<<deletion_end[^>]*>>/g, "");
   var xoutput1 = "";
   while (XMatch(xoutput, /<<deletion\s+data=\"([^\"]*)\"\s*>>/))
   {
      xoutput1 += XMatch.leftContext;
      xoutput = XMatch.rightContext;
      var deletionText = XMatch.matches[1];
      var deletionXML = XString(deletionText).decode(XString.ENCODE_ENTITY);
      xoutput1 += deletionXML;
   }
   xoutput = xoutput1 + xoutput;

   // Remove any blank paragraphs that might be left behind
   xoutput = xoutput.replace(/<(xhtml:|)p\s*\/>/g, "");
   xoutput = xoutput.replace(/<(xhtml:|)p\s+[^>\/]*\/>/g, "");
   xoutput = xoutput.replace(/<(xhtml:|)p\s*>\s*<\/(xhtml:|)p>/g, "");
   xoutput = xoutput.replace(/<(xhtml:|)p\s+[^>]*>\s*<\/(xhtml:|)p>/g, "");

   xoutput = xoutput.replace(/[\n\r]/g, " ");

   // Clean up the spaces
   xoutput = xoutput.replace(/<([^\/\<][^>]*?[^\/\>])>\s+/g, "<$1>");
   xoutput = xoutput.replace(/\s+<\/([^>]*?)>/g, "</$1>");

   return SLIM_Converter.unprotectPIs(xoutput);
}

//-----------------------------------------------------------------------------

SLIM.normalizeNumText = function(
   numText
)
{

   numText = XString(numText).normalize();
   numText = numText.replace(/\.$/, "");

   return numText;
}

//-----------------------------------------------------------------------------

SLIM.getNum = function(
   numText,
   rejectChanges
)
{
   rejectChanges = (rejectChanges == null) ? false : rejectChanges;

   var numText = (rejectChanges) ? SLIM.rejectChanges(numText) : SLIM.acceptChanges(numText);
   numText = numText.toLowerCase();
   numText = numText.replace(/\s+/g, "");
   if ((/^[a-z]+$/).test(numText))
      num = XNumber(numText).toNumber();
   else
   {
      numText = numText.replace(/[a-z]\.?/gi, "");
      numText = numText.replace(/\.$/, "");
      if ((/[^0-9\.]/).test(numText))
         return 0;
      while (XMatch(numText, /(\.[0-9]*)\./))
         numText = XMatch.leftContext + XMatch.matches[1] + XMatch.rightContext;
      num = Number(numText);
   }

   return num;
}

//-----------------------------------------------------------------------------

SLIM.getNumId = function(
   numText,
   rejectChanges
)
{
   rejectChanges = (rejectChanges == null) ? false : rejectChanges;

   var numText = (rejectChanges) ? SLIM.rejectChanges(numText) : SLIM.acceptChanges(numText);

   var numId = numText.replace(/[\s]/g, "");
   numId = numId.replace(/^[a-z\.]+/i, "");
   numId = numId.replace(/\.$/, "");

   return numId;
}

//-----------------------------------------------------------------------------

SLIM.makeTokens = function(
   xinput
)
{
   xinput = (xinput == null) ? "" : xinput.toString();

   var xoutput = xinput;

   xoutput = SLIM.acceptChanges(xoutput);
   xoutput = XString(xoutput).trim(xoutput);
   xoutput = xoutput.replace(/[\,\.\;]$/, "");

   return xoutput;
}

//=============================================================================


