//SHARE-IN-MEMORY=true
// Copyright 2000, 2001, 2002, 2003, 2004, 2005 Macromedia, Inc. All rights reserved.

var translatorInfo = new Array();
var lastInStr = "";
var lastOutStr = "";
var lastIndex = "";
var lastDynamicTextFormat = null;
var g_index = "";
var debugMegaLock = false;
var debugTagSpan = false;
var debugTranslationMgr = false;

/////////////////////////////////////////////// TranslationManager Class //////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TranslationManager::TranslationManager
// Purpose  : Instantiate this object and call its translate method within your translators 
//        translateMarkup() function.  This will create a translator that utilizes the
//        translation rules defined in the XML participant files containing a
//        translator section.
// Arguments: translatorClass - the translator class as specified by the translators
//        getTranslatorInfo() function.  This name will be used as the 
//        translatorClass attribute of the MM:BeginLock tags.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TranslationManager(translatorClass, serverModel, serverLanguage)
{
  // Private Members
  this.inStr = "";
  this.translatorClass = translatorClass;
  this.defaultDirectiveTag = translatorClass;  // the default tag name for directives
  if (serverLanguage)
  {
    this.index = serverModel + ":" + serverLanguage;
  }
  else
  {
    this.index = serverModel;
  }
  g_index = this.index;  // for the sorting function
  this.requery = false;
  this.offsetTagBegin = -1;
  this.offsetTagEnd = -1;
  this.currentTagName = null;
  this.whereToSearch = "";
  this.dynamicTextFormat = dw.getDynamicTextFormat();
  this.params = new Array();
  this.dataSources = new Array();
  this.CFOutput = new Stack(); // Cold Fusion specific to ensure dynamic data within cfoutput tags
  this.translator = new translator; // translator class which interfaces to the Translator Support Library (TSL)
  if (translatorInfo[this.index] == null)
  {
    translatorInfo[this.index] = new Array(); // repository for XML translator information
    translatorInfo[this.index].lookInAllAttributes = false;
    translatorInfo[this.index].lookInAllTags = false;
  }

  // Methods to use in identifying dynamic tags and attributes
  this.serverModelAlwaysCheckTag = null;
  this.serverModelAlwaysCheckAttribute = null;

  // Methods for constructing whole tag spans
  this.inTagSpan = false;          // currently inside tag span
  this.tagSpan = null;             // contents of tag span thus far
  this.tagSpanRecover = false;     // on second pass through tag span
  this.tagSpanName = null;         // name of tag
  this.tagSpanCounter = null;      // used to match up nested tags
  this.tagSpanParts = null;        // tag+name and tag+* participants
  this.tagSpanSnapshot = null;
  this.tagSpanLastIndex = -1;

  // Stack of tags that enclose current code (if there are any)
  this.enclosingTags = new Array();

  // Object with methods getTranslation and setTranslation.  If non-null,
  // will be used to cache all tag spans.
  this.tagSpanCache = null;

  this.mode = "preview";
  this.inEditMode = false;
  this.editModeEndOffset = -1;
  this.editModeFullCode = null;
  this.editModeFullTranslation = null;

  // Megalocks aggregate several separate locked regions into one.
  this.inMegaLock = false;
  this.megaLockCode = null;
  this.megaLockTranslation = null;
  this.megaLockPreCodeOffset = 0;
  this.megaLockSnapshot = null;

  // Maintenance for reparsing (when a tag span doesn't match any of the
  // available translations or when parsing in dual-mode)
  this.reparse = false;
  this.reparseSnapshot = null;

  this.directives = "";
}
  // public methods
  TranslationManager.prototype.translate              = TM_translate;
  TranslationManager.prototype.notifyXMLChange        = TM_notifyXMLChange;

  // public static methods
  TranslationManager.findTagLength                    = TM_findTagLength;
  TranslationManager.getAttributeValue                = TM_getAttributeValue;
  TranslationManager.splitBody                        = TM_splitBody;

  // private methods
  TranslationManager.prototype.getTranslatorInfo      = TM_getTranslatorInfo;
  TranslationManager.prototype.getParticipant         = TM_getParticipant;
  TranslationManager.prototype.getDirective           = TM_getDirective;
  TranslationManager.prototype.getAttribute           = TM_getAttribute;
  TranslationManager.prototype.getText                = TM_getText;
  TranslationManager.prototype.getTag                 = TM_getTag;
  TranslationManager.prototype.getData                = TM_getData;
  TranslationManager.prototype.getTokens              = TM_getTokens;
  TranslationManager.prototype.isPattern              = TM_isPattern;
  TranslationManager.prototype.getCurrentTagName      = TM_getCurrentTagName;
  TranslationManager.prototype.getCurrentTag          = TM_getCurrentTag;
  TranslationManager.prototype.translateDirective     = TM_translateDirective;
  TranslationManager.prototype.translateAttribute     = TM_translateAttribute;
  TranslationManager.prototype.translateText          = TM_translateText;
  TranslationManager.prototype.translateTag           = TM_translateTag;
  TranslationManager.prototype.notifyTagBegin         = TM_notifyTagBegin;
  TranslationManager.prototype.notifyTagEnd           = TM_notifyTagEnd;
  TranslationManager.prototype.getCurrentTagOffset    = TM_getCurrentTagOffset;
  TranslationManager.prototype.getParams              = TM_getParams;
  TranslationManager.prototype.searchMatchesCode      = TM_searchMatchesCode;
  TranslationManager.prototype.searchMatchesOpenTag   = TM_searchMatchesOpenTag;
  TranslationManager.prototype.getMatchingParticipant = TM_getMatchingParticipant
  TranslationManager.prototype.startTagSpan           = TM_startTagSpan;
  TranslationManager.prototype.addToTagSpan           = TM_addToTagSpan;
  TranslationManager.prototype.addOpenTagToTagSpan    = TM_addOpenTagToTagSpan;
  TranslationManager.prototype.addCloseTagToTagSpan   = TM_addCloseTagToTagSpan;
  TranslationManager.prototype.finishTagSpan          = TM_finishTagSpan;
  TranslationManager.prototype.correctOutlineIDs      = TM_correctOutlineIDs;
  TranslationManager.prototype.translateTagSpan       = TM_translateTagSpan;
  TranslationManager.prototype.translateTextSpan      = TM_translateTextSpan;
  TranslationManager.prototype.startMegaLock          = TM_startMegaLock;
  TranslationManager.prototype.addToMegaLock          = TM_addToMegaLock;
  TranslationManager.prototype.finishMegaLock         = TM_finishMegaLock;
  TranslationManager.prototype.startEditMode          = TM_startEditMode;
  TranslationManager.prototype.endEditMode            = TM_endEditMode;
  TranslationManager.prototype.setDecoration          = TM_setDecoration;
  TranslationManager.prototype.initialize             = TM_reInitialize;
  TranslationManager.prototype.participantSort        = TM_participantSort;
  TranslationManager.prototype.lookInThisAttribute    = TM_lookInThisAttribute;
  TranslationManager.prototype.alwaysCheckAttribute   = TM_alwaysCheckAttribute;
  TranslationManager.prototype.lookInThisTag          = TM_lookInThisTag;
  TranslationManager.prototype.alwaysCheckTag         = TM_alwaysCheckTag;
  TranslationManager.prototype.lookInTags             = TM_lookInTags;
  TranslationManager.prototype.pushOpenTag            = TM_pushOpenTag;
  TranslationManager.prototype.popCloseTag            = TM_popCloseTag;
  TranslationManager.prototype.prepForReparse         = TM_prepForReparse;
  TranslationManager.prototype.takeSnapshot           = TM_takeSnapshot;
  TranslationManager.prototype.restoreSnapshot        = TM_restoreSnapshot;

///////////////////////////////////////////////
// Function : TM_reInitialize
// Purpose  : Informs the TSL to reinitialize itself
////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////
function TM_reInitialize()
{
    this.translator.initiate(this.document, this.translatorClass);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_translate
// Purpose  : Utilizes dw.scanSourceString to reliably parse through the given HTML string.
// Arguments: the HTML string to translate
// Returns  : the translation string as defined by the rules in the XML participant files
//            containing translator sections.  This string should be returned by the 
//            translators translateMarkup() function.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_translate(inStr)
{
  if (this.index != lastIndex || inStr != lastInStr || this.dynamicTextFormat != lastDynamicTextFormat)
  {
    this.inStr = inStr;
    this.translator.initiate(inStr, this.translatorClass);
    var callback = new TMCallback(this);
    dreamweaver.scanSourceString(inStr, callback);
    while (this.reparse || this.inTagSpan)
    {
	  if (this.inTagSpan)
	  {
		this.prepForReparse("tagspan");
		// Restart the translator from the beginning of the tag span (in "recover" state)
		this.inTagSpan = false;
		this.tagSpanRecover = true;
		callback.inTagSpan = false;
	  }
	  else if (this.inMegaLock)
	  {
		this.prepForReparse("megalock");
	  }

	  this.reparse = false;
	  if (this.reparseSnapshot)
	    this.restoreSnapshot(this.reparseSnapshot);

	  if (debugTranslationMgr)
		  alert("Restarting translator due to " +
			(this.inTagSpan ? "tag span (" : "reparse (") +
			this.reparseSnapshot.offset + ")...\n" + 
			this.inStr);

      dreamweaver.scanSourceString(this.inStr, callback);
    }
	if (this.inMegaLock)
	{
		if (debugMegaLock)
			alert("Open mega lock");
		this.finishMegaLock("");
	}
    var outStr = this.translator.getTranslation();
    this.translator.terminate();
    if (inStr.length != outStr.length)
    {
      lastInStr = inStr;
      lastOutStr = outStr;
      lastIndex = this.index;
    }
    lastDynamicTextFormat = this.dynamicTextFormat;
  }
  else
  {
    outStr = lastOutStr;
  }

  return outStr;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_notifyXMLChanged
// Purpose  : Informs the translator that the translatorInfo needs to be refreshed.
//        Call this when the Participant files translator sections have been modified.
//        Since there is no UI currently provided to define the translation rules,
//        developers will need to restart UltraDev when adding/modifying participant
//        files translator sections.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_notifyXMLChange() 
{ 
  this.requery = true;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_prepForReparse
// Purpose  : Sets up the TM for a reparse of a portion of the document.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_prepForReparse(cause, offset)
{
	this.reparse = true;
	if (cause == "tagspan")
		this.reparseSnapshot = this.tagSpanSnapshot;
	else if (cause == "megalock")
		this.reparseSnapshot = this.megaLockSnapshot;
	else
		this.reparseSnapshot = new Snapshot(this.enclosingTags, offset);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : evalPattern
// Purpose	: The JavaScript interpreter needs to do some extra work (in
//		  js_PutCallObject) whenever it invokes any function containing
//		  an eval statement.  In order to avoid doing that processing
//		  every time that TM_getTranslatorInfo is called, the eval
//		  statement is wrapped inside this function.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function evalPattern(pattern)
{
	return eval(pattern);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_getTranslatorInfo
// Purpose  : Build the global multi-dimensional array (translatorInfo) representing the 
//        translator information from the XML participant files.
//        Each server model is loaded only once unless requery is set.
//        Note: This is the sole interface between the Translation Manager and the
//        Extension Data Manager (EDM).
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_getTranslatorInfo() 
{ 
  if (this.requery || (translatorInfo[this.index].participants == null))
  {
    // TO DO, if this.requery is true, then we need to destroy the objects in
    // translatorInfo so they too will be reloaded on demand.

	// Arrays for partitioning participants by type
    translatorInfo[this.index].participants = 1;
    translatorInfo[this.index].directiveParticipants = new Array();
    translatorInfo[this.index].attributeParticipants = new Array();
    translatorInfo[this.index].textParticipants = new Array();
    translatorInfo[this.index].tagOnlyParticipants = new Array();
    translatorInfo[this.index].outerHTMLParticipants = new Array();

    var partArry = dw.getExtParticipants("", "translator");
    if (partArry)
    {
	  // Sort participants by priority

      var sortValue = 500;
      translatorInfo[this.index].sortOrder = new Array();
      for (var i = 0; i < partArry.length; i++)
      {
        var priority = dw.getExtDataValue(partArry[i], "translator", "priority");
        var type = dw.getExtDataValue(partArry[i], "translator", "type");
		if (type == "renderer")
		{
			continue;
		}

        if (priority.length)
          translatorInfo[this.index].sortOrder[partArry[i]] = priority.valueOf();
        else
          translatorInfo[this.index].sortOrder[partArry[i]] = sortValue++;
      }
      partArry.sort(this.participantSort);

	  // Process each participant in turn (collecting its data)

      for (var i = 0; i < partArry.length; i++)
      {
        var type = dw.getExtDataValue(partArry[i], "translator", "type");
		if (type == "renderer")
		{
			continue;
		}
        if (translatorInfo[this.index][partArry[i]] == null)
        {
          translatorInfo[this.index][partArry[i]] = new Array();
          var transInfo = translatorInfo[this.index][partArry[i]];

		  // === TRANSLATIONS ===

          var transArry = dw.getExtDataArray(partArry[i], "translator", "translations");
          if (transArry && transArry.length)
          {
            transInfo.translations = new Array();

            for (var j = 0; j < transArry.length; j++)
            {
			  // Fill in translatorInfo[this.index].???participants and
			  // translatorInfo[this.index][partArry[i]][whereToSearch]
			  transData.parse(partArry[i], transArry[j], translatorInfo[this.index], this);
            }
          }

		  // === CONTEXT ===

		  var context = dw.getExtDataValue(partArry[i], "translator", "context");
		  if (context && context.length)
			transInfo.context = context.toLowerCase();
		  else
			transInfo.context = null;

		  // === MODE ===
		  var mode = dw.getExtDataValue(partArry[i], "translator", "mode");
		  if (mode && mode.length)
			transInfo.mode = mode.toLowerCase();
		  else
			transInfo.mode = null;

		  // === SEARCH PATTERNS ===

          var patternArry = dw.getExtDataArray(partArry[i], "translator", "searchPatterns");
          if (patternArry && patternArry.length)
          {
            transInfo.patterns = new Array();
            transInfo.isPatternRE = new Array();
            transInfo.isOptional = new Array();
            transInfo.requiredLocation = new Array();
            transInfo.paramNames = new Array();
            for (var j = 0; j < patternArry.length; j++)
            {
              var pattern = dw.getExtDataValue(partArry[i], "translator", "searchPatterns", patternArry[j]);

              if (this.isPattern(pattern))
              {
				// Any participant with a regular expression in the first search pattern is
				// going to have to be checked against each attribute in each page
                if (j == 0 && !translatorInfo[this.index].lookInAllAttributes)
                  if (translatorInfo[this.index].attributeParticipants[partArry[i]])
                    translatorInfo[this.index].lookInAllAttributes = true;
                transInfo.isPatternRE[patternArry[j]] = true;
                transInfo.patterns[patternArry[j]] = evalPattern(pattern);
              }
              else
              {
				// Any participant with a string in the first search pattern is only
				// going to have to be checked against each attribute in each page if
				// that string doesn't match one of our predictable patterns
                if (j == 0 && !translatorInfo[this.index].lookInAllAttributes)
                  if (translatorInfo[this.index].attributeParticipants[partArry[i]])
                    if (!this.alwaysCheckAttribute(pattern))
                      translatorInfo[this.index].lookInAllAttributes = true;
                transInfo.isPatternRE[patternArry[j]] = false;
                transInfo.patterns[patternArry[j]] = pattern;
              }

              var isOptional =
				dw.getExtDataValue(partArry[i], "translator", "searchPatterns", patternArry[j], "isOptional");
			  transInfo.isOptional[patternArry[j]] = (isOptional.toLowerCase() == "true");
								  
			  var requiredLocation =
				dw.getExtDataValue(partArry[i], "translator", "searchPatterns", patternArry[j], "requiredLocation").toLowerCase();
              transInfo.requiredLocation[patternArry[j]] = "";
              if (requiredLocation == "leading" ||
                requiredLocation == "trailing" ||
                requiredLocation == "opentag" ||
                requiredLocation == "tagname")
                transInfo.requiredLocation[patternArry[j]] = requiredLocation;
              var paramNames = dw.getExtDataValue(partArry[i], "translator", "searchPatterns", patternArry[j], "paramNames");
              if (paramNames && paramNames.length)
                transInfo.paramNames[patternArry[j]] = paramNames;
            }
          }
        }
      }
    }
    this.requery = false;
  }
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_lookInThisAttribute
// Purpose  : Used within the TM_callback class to see if it necessary to look
//        within this particular attribute to check for a translation rule.
//        This is an optimization that allows us to ignore looping through
//        all participants with translation rules for most attributes.  It 
//        relies on the fact that our current translation rules for attributes
//        all have quicksearches defined (non-regular expression search pattern)
//        that contain certain known character sequences (ie. '<%', '#', '<cf',
//        and '<jsp:').  If a developer extends these any of our translators in 
//        such a way that their quick search pattern does not exist or is not one
//        of these expressions, then this performance boost will not kick in since
//        we will not be able to quickly reject most attributes.
// Arguments: code : this is the attribute value passed into the attribute callback method.
// Returns  : false if we can quickly reject this attribute value (i.e we dont need to 
//        search within the attribute participants for a pattern that matches this code. 
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_lookInThisAttribute(code)
{
  this.getTranslatorInfo();
  if (translatorInfo[this.index].lookInAllAttributes)
    return true;
  else if (this.alwaysCheckAttribute(code))
    return true;
  return false;
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_alwaysCheckAttribute
// Purpose  : Allows server models to specify their own attribute character
//        sequences for the optimization implemented in TM_lookInThisAttribute.
//        See myAlwaysCheckAttribute in JSP.htm for an example.
// Arguments: code = quicksearch pattern or code from TMCallback attribute function
// Returns  : true = try to translate attribute, false = skip attribute
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_alwaysCheckAttribute(code)
{
  if (this.serverModelAlwaysCheckAttribute != null)
    return this.serverModelAlwaysCheckAttribute(code);
  else
    return false;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_lookInThisTag
// Purpose  : Used within the TM_callback class to see if it necessary to look
//        within this particular tag to check for a translation rule.
//        This is an optimization that allows us to ignore looping through
//        all participants with translation rules for most tags.  It 
//        relies on the fact that our current translation rules for tags
//        apply only to tags with known prefixes (like "jsp:" or "cf")
//        If a developer adds a translation rule for a tag that doesn't
//        start with one of these prefixes, then this performance boost will
//        not kick in since we will not be able to quickly reject most tags.
// Arguments: tagName
// Returns  : false if we can quickly reject this tag (i.e we dont need to search
//        within the tag participants for a pattern that matches this code).
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_lookInThisTag(tagName)
{
  this.getTranslatorInfo();
  if (translatorInfo[this.index].lookInAllTags)
    return true;
  else if (this.alwaysCheckTag(tagName))
    return true;
  return false;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_alwaysCheckTag
// Purpose  : Allows server models to specify their own tag tests for the optimization
//        implemented in TM_lookInThisTag.  See myAlwaysCheckTag in JSP.htm
//        for an example.
// Arguments: tagName
// Returns  : true = try to translate this tag, false = skip this tag
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_alwaysCheckTag(tagName)
{
  if (this.serverModelAlwaysCheckTag != null)
    return this.serverModelAlwaysCheckTag(tagName);
  else
    return false;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_lookInTags
// Purpose  : DEPRECATED
// Arguments: 
// Returns  : true if any participant for the current server model contains a tag
//        search.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_lookInTags()
{
  return translatorInfo[this.index].lookInAllTags;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_participantSort
// Purpose  : Sorts participants based on the priority attribute of the translator tag
// Arguments: ptype - the callback location where dw.scanSourceString found the code fragment
//        code - the code fragment to match against the XML patterns
// Returns  : Name of the participant that matches the code segment and location requirement,
//        null if none found.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_participantSort(part1, part2)
{
  var result = 0;
  var priority1 = translatorInfo[g_index].sortOrder[part1];
  var priority2 = translatorInfo[g_index].sortOrder[part2];
  if (priority1 != priority2)
  {
    if (priority1 < priority2)
    {
      result = -1;
    }
    else
    {
      result = 1;
    }
  }
  return result;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_getParticipant
// Purpose  : Find the participant whose search and location specification matches the
//        specified code fragment.
// Arguments: ptype - the callback location where dw.scanSourceString found the code fragment
//        code - the code fragment to match against the XML patterns
// Returns  : Name of the participant that matches the code segment and location requirement,
//        null if none found.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_getParticipant(pType, code)
{
  var participant = null;
  if (pType == PTYPE_ATTRIBUTE)
  { 
    participant = this.getAttribute(code, TM_getParticipant.arguments[2]);
  }
  else
  {
    if (pType == PTYPE_TAG)
    {
      participant = this.getTag(code);
    }
    else
    {
      if (pType == PTYPE_TEXT)
      {
        participant = this.getText(code);
      }
      else
      {
        if (pType == PTYPE_DIRECTIVE)
        {
          participant = this.getDirective(code);
        }
      }
    }
  }
  return participant;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_getDirective
// Purpose  : Find the participant whose translation name is "directive" and pattern 
//        successfuly identifies the specified code segment.
// Arguments: code - directive from dw.scanSourceString directive callback.
// Returns  : Matching participant, otherwise null.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_getDirective(code)
{
  var participant = null;
  this.getTranslatorInfo();
  var index = "directive";
  var arry = translatorInfo[this.index];

  if (arry.directiveParticipants)
  {
    for (var part in arry.directiveParticipants)
    {
      var translations = arry[part].translations;
      if (translations)
      {
        if (translations[index])
        {
          if (this.searchMatchesCode(part, code))
          {
            this.whereToSearch = index;
            participant = part;
            break;
          }
        }
      }
    }
  }
  return participant;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_getText
// Purpose  : Find the participant whose translation name is "text" and pattern 
//        successfuly identifies the specified code segment.
// Arguments: code - text span from dw.scanSourceString text callback.
// Returns  : Matching participant, otherwise null.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_getText(code)
{
  var participant = null;
  this.getTranslatorInfo();
  var index = "text";
  var arry = translatorInfo[this.index];

  if (arry.textParticipants)
  {
    for (var part in arry.textParticipants)
    {
      var translations = arry[part].translations;
      if (translations)
      {
        if (translations[index])
        {
          if (this.searchMatchesCode(part, code))
          {
            this.whereToSearch = index;
            participant = part;
            break;
          }
        }
      }
    }
  }
  return participant;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_getTag
// Purpose  : Find the participant whose translation name is equivalent to the current tag name
//        or "*", and whose pattern successfuly identifies the specified code segment.
//        Priority is given to participants whose translation name matches the current tag.
//        If no participant is found, then we search for the wild card tag specification "*".
// Arguments: code - tag span collected between the dw.scanSourceString openTagBegin
//        and openTagEnd callbacks and the tag span between the closeTagBegin and closeTagEnd
//        callbacks.
// Returns  : Matching participant, otherwise null.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_getTag(code)
{
  var participant = null;
  this.getTranslatorInfo();
  var tag = this.getCurrentTagName();
  var index = "tag+" + tag + ":tagonly";
  var index2 = "tag+*:tagonly";
  var arry = translatorInfo[this.index];

  if (arry.tagOnlyParticipants)
  {
    for (var part in arry.tagOnlyParticipants)
    {
      var translations = arry[part].translations;
      if (translations)
      {
        if (translations[index])
        {
          if (this.searchMatchesCode(part, code))
          {
            this.whereToSearch = index;
            participant = part;
            break;
          }
        }
        if (!participant)
        {
          if (translations[index2])
          {
            if (this.searchMatchesCode(part, code))
            {
              this.whereToSearch = index2;
              participant = part;
            }
          }
        }
      }
    }
  }
  return participant; 
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_getAttribute
// Purpose  : Find the participant whose translation name is equivalent to the current 
//        tag+attribute pair or "*+*", and whose pattern successfuly identifies the 
//        specified code segment.
//        Priority is given to participants whose translation name matches the current 
//        tag+attribute pair.
//        If no participant is found, then we search for the wild card tag+attribute
//        specification "*+*".
// Arguments: code - attribute value from dw.scanSourceString attribute callback.
//        attrName - attribute name from dw.scanSourceString attribute callback.
// Returns  : Matching participant, otherwise null.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_getAttribute(code, attrName)
{
  var participant = null;
  this.getTranslatorInfo();
  var tag = "tag+" + this.getCurrentTagName();
  var attribute = "attribute+" + attrName.toLowerCase();
  var index = tag + ":" + attribute;
  var index2 = tag + ":attribute+*";
  var index3 = "tag+*:attribute+*";
  var arry = translatorInfo[this.index];

  if (arry.attributeParticipants)
  {
    for (var part in arry.attributeParticipants)
    {
      var translations = arry[part].translations;
      if (translations)
      {
        if (translations[index])
        {
          // Look for specific tag + attribute matches first
          if (this.searchMatchesCode(part, code))
          {
            this.whereToSearch = index;
            participant = part;
            break;
          }
        }
        if (!participant)
        {
          if (translations[index2])
          {
            if (this.searchMatchesCode(part, code))
            {
              this.whereToSearch = index2;
              participant = part;
            }
          }
        }
        if (!participant)
        {
          if (translations[index3])
          {
            if (this.searchMatchesCode(part, code))
            {
              this.whereToSearch = index3;
              participant = part;
            }
          }
        }
      }
    }
  }
  return participant;
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_getParams
// Purpose  : pulls parameters out of regular expression match
// Arguments: params - array of params to add to
//            paramNames - comma delimited list of param names
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_getParams(params, paramNames)
{
  if (paramNames && paramNames.length)
  {
    var paramArry = paramNames.split(",");
    if (paramArry[0] && paramArry[0].length)
    {
      params[paramArry[0]] = RegExp.$1;
    }
    if (paramArry[1] && paramArry[1].length)
    {
      params[paramArry[1]] = RegExp.$2;
    }
    if (paramArry[2] && paramArry[2].length)
    {
      params[paramArry[2]] = RegExp.$3;
    }
    if (paramArry[3] && paramArry[3].length)
    {
      params[paramArry[3]] = RegExp.$4;
    }
    if (paramArry[4] && paramArry[4].length)
    {
      params[paramArry[4]] = RegExp.$5;
    }
    if (paramArry[5] && paramArry[5].length)
    {
      params[paramArry[5]] = RegExp.$6;
    }
    if (paramArry[6] && paramArry[6].length)
    {
      params[paramArry[6]] = RegExp.$7;
    }
    if (paramArry[7] && paramArry[7].length)
    {
      params[paramArry[7]] = RegExp.$8;
    }
    if (paramArry[8] && paramArry[8].length)
    {
      params[paramArry[8]] = RegExp.$9;
    }
  }
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_searchMatchesOpenTag
// Purpose : determines whether the specified code matches the search patterns with
//           requiredLocation="opentag".  This can be used to eliminate some tag spans
//           from consideration before we bother collecting the whole tag span.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_searchMatchesOpenTag(part, code)
{
  var found = true;
  var arry = translatorInfo[this.index][part];

  if (arry.context)
  {
	var top = null;
	if (this.enclosingTags.length > 0)
	  top = this.enclosingTags[this.enclosingTags.length-1];
	if (arry.context != top)
	  return false;
  }

  if (arry.mode)
  {
	if (arry.mode != this.mode)
      return false;
  }

  if (arry.patterns)
  {
    for (var patternName in arry.patterns)
    {
      if (arry.requiredLocation[patternName] == "opentag")
      {
        var isRE = arry.isPatternRE[patternName];
        var pattern = arry.patterns[patternName];
        if (!isRE)
        {
          var offset = code.indexOf(pattern);
        }
        else
        {
          var offset = code.search(pattern);
        }
        if (offset == -1 && !arry.isOptional[patternName])
        {
          found = false;
          break;
        }
      }
	  else if (arry.requiredLocation[patternName] == "tagname")
      {
        var tagName = this.currentTagName;
        var isRE = arry.isPatternRE[patternName];
        var pattern = arry.patterns[patternName];
        if (!isRE)
        {
          var offset = tagName.indexOf(pattern);
        }
        else
        {
          var offset = tagName.search(pattern);
        }
        if (offset == -1 && !arry.isOptional[patternName])
        {
          found = false;
          break;
        }
      }
    }
  }

  return found;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_searchMatchesCode
// Purpose  : determines weather the specified code satisfies the search criteria defined
//        within the XML participant translator, patterns section.
//        Multiple ordered searches supported.
// Arguments: part - participant name
//        code - code fragment to run patterns agains
// Returns  : true if match, false otherwise
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_searchMatchesCode(part, code)
{
  var found = false;
  var params = new Array();
  var arry = translatorInfo[this.index][part];

  if (arry.context)
  {
	var top = null;
	if (this.enclosingTags.length > 0)
	  top = this.enclosingTags[this.enclosingTags.length-1];
	if (arry.context != top)
	  return false;
  }

  if (arry.mode)
  {
	if (arry.mode != this.mode)
      return false;
  }

  if (arry.patterns)
  {
    for (var patternName in arry.patterns)
    {
      var isRE = arry.isPatternRE[patternName];
      var pattern = arry.patterns[patternName];
      if (!isRE)
      {
        if (arry.requiredLocation[patternName] == "")
        {
          var offset = code.indexOf(pattern);
        }
        else
        {
          if (arry.requiredLocation[patternName] == "leading")
          {
            var offset = this.inStr.substr(0, this.getCurrentTagOffset()).indexOf(pattern);
          }
          else
          {
            if (arry.requiredLocation[patternName] == "trailing")
            {
              var offset = this.inStr.substr(this.getCurrentTagOffset() + code.length).indexOf(pattern);
            }
            else
            {
              if (arry.requiredLocation[patternName] == "opentag")
              {
                var tagLength = TranslationManager.findTagLength(code);
                if (tagLength != -1)
                  var offset = code.substr(0, tagLength).indexOf(pattern);
              }
              else
              {
                if (arry.requiredLocation[patternName] == "tagname")
                {
                  var offset = this.currentTagName.indexOf(pattern);
                }
              }
            }
          }
        }
      }
      else
      {
        if (arry.requiredLocation[patternName] == "")
        {
          var offset = code.search(pattern);
          if (offset != -1)
          {
            var paramNames = arry.paramNames[patternName];
            this.getParams(params, paramNames);
          }
        }
        else
        {
          if (arry.requiredLocation[patternName] == "leading")
          {
            var offset = this.inStr.substr(0, this.getCurrentTagOffset()).search(pattern);
          }
          else
          {
            if (arry.requiredLocation[patternName] == "trailing")
            {
              var offset = this.inStr.substr(this.getCurrentTagOffset() + code.length).search(pattern);
            }
            else
            {
              if (arry.requiredLocation[patternName] == "opentag")
              {
                var tagLength = TranslationManager.findTagLength(code);
                if (tagLength != -1)
                {
                  var tagCode = code.substr(0, tagLength);
                  var offset = tagCode.search(pattern);
                  if (offset != -1)
                  {
                    var paramNames = arry.paramNames[patternName];
                    this.getParams(params, paramNames);
                  }
                }
              }
              else
              {
                if (arry.requiredLocation[patternName] == "tagname")
                {
				  var tagName = this.currentTagName;
                  var offset = tagName.search(pattern);
                  if (offset != -1)
                  {
                    var paramNames = arry.paramNames[patternName];
                    this.getParams(params, paramNames);
                  }
                }
              }
            }
          }
        }
      }

      if (offset != -1)
      {
        found = true;       
      }
      else
      {
        if (!arry.isOptional[patternName])
        {
          found = false;
          break;
        }
      }
    }
  }
  this.params = params;

  return found;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_getData
// Purpose  : Retreive the translation information for the given participant.  
//        Token substitution provided using the specified code fragment.
// Arguments: part - participant name
//        code - code for which participant was located
// Returns  : transData structure which contains the openTag, closeTag, display, attributes, etc.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_getData(part, code, attrName)
{
  var data = null;
  if (part)
  {
    var whereToSearch = this.whereToSearch;
    var arry = translatorInfo[this.index][part].translations;
    if (arry)
    {
      if (arry[whereToSearch])
      {
        data = transData.copy(arry[whereToSearch].data);
        var tokens = this.getTokens(part, code, attrName);
        data.replaceTokens(tokens);
      }
    }
  }
  return data;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_getTokens(part)
// Purpose  : retreive the translation token patterns for the specified participant
// Arguments: part - participant
// Returns  : An array indexed by the token names, containing the token patterns
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_getTokens(part, code, attrName)
{
  var tokens = new Array();
  if (attrName && attrName != "")
  {
    tokens["attr"] = attrName;
  }
  for (var paramName in this.params)
  {
    tokens[paramName] = this.params[paramName];
  }
  return tokens;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_isPattern
// Purpose  : determines whether a string is a regular expression
// Arguments: pattern - regular expression or quick search expression (locate using indexOf)
// Returns  : true if regular expression, false otherwise
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_isPattern(pattern)
{
  if (pattern.charAt(0) == "/")
  {
    return true;
  }
  return false;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_getCurrentTagName
// Purpose  : Get the name of the active tag.  Used from openTagEnd, closeTagEnd, and
//        attribute callbacks.
// Arguments: Pass in optional attribute offset, when called from attribute callback.
// Returns  : Active tag name;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_getCurrentTagName()
{
  return this.currentTagName;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_getCurrentTag
// Purpose  : returns active tag - example <cfoutput querry-"">
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_getCurrentTag()
{
  var currTag = "";
  if ((this.offsetTagBegin >= 0) && (this.offsetTagEnd >= 0))
  {
    currTag = this.inStr.substr(this.offsetTagBegin, this.offsetTagEnd - this.offsetTagBegin);
  }
  return currTag;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_getCurrentTagOffset
// Purpose  : returns offset of beginning of active tag
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_getCurrentTagOffset()
{
  return this.offsetTagBegin;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_notifyTagBegin
// Purpose  : returns active tag
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_notifyTagBegin(tag, offset)
{
  this.currentTagName = tag.toLowerCase();
  this.offsetTagBegin = offset;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_notifyTagEnd
// Purpose  : returns active tag
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_notifyTagEnd(offset)
{
  this.offsetTagEnd = offset;
  return this.getCurrentTag();
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_translateDirective
// Purpose  : translate the directive given the translation rules (utilizes the dependent search criteria info)
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_translateDirective(code, offset, trans)
{
  var origPosition = this.translator.getPosition();
  var result = "";
  if (trans)
  {
    result = this.translator.translateDirective(code, offset, trans.participant, trans.type, trans.openTag, trans.closeTag, trans.attributes, trans.display, trans.lockAttributes);
  }
  else if (this.defaultDirectiveTag != this.translatorClass)
  {
    result = this.translator.translateDirective(code, offset, "script", "", this.defaultDirectiveTag, this.defaultDirectiveTag, "", "", "");
  }
  else
  {
    result = this.translator.translateDirective(code, offset, "script", "", "", "", "", "", "");
  }
  if (this.inMegaLock)
  {
	var preCode = this.inStr.substr(origPosition, offset-origPosition);
	this.addToMegaLock(preCode, code, result);
  }
  else if (this.inEditMode)
  {
	this.editModeFullTranslation.add(result);
  }
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_translateText
// Purpose  : translate the text given the translation rules
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_translateText(code, offset, trans)
{
  var origPosition = this.translator.getPosition();
  var result = new Object();
  result.reparse = false;
  result.translation = "";
  if (trans)
  {
    if (trans.insertBefore)
	{
	  result.translation += this.translator.translateText("", offset, trans.participant, "as is", "", "", "", trans.insertBefore, "");
	}

    result.translation += this.translator.translateText(code, offset, trans.participant, trans.type, trans.openTag, trans.closeTag, trans.attributes, trans.display, trans.lockAttributes);

    if (trans.insertAfter)
    {
	  result.translation += this.translator.translateText("", offset+code.length, trans.participant, "as is", "", "", "", trans.insertAfter, "");
    }
	if (trans.reparse)
	{
		result.reparse = true;
		result.useEditMode = trans.useEditMode;
	}
  }
  else
  {
    result.translation = this.translator.translateText(code, offset, "", "", "", "", "", "", "");
  }
  if (this.inMegaLock)
  {
	var preCode = this.inStr.substr(origPosition, offset-origPosition);
	this.addToMegaLock(preCode, code, result.translation);
  }
  else if (this.inEditMode)
  {
	this.editModeFullTranslation.add(result);
  }
  return result;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_translateAttribute
// Purpose  : translate the attribute given the translation rules
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_translateAttribute(code, name, trans)
{
  if (trans)
  {
    if ((this.translatorClass == "MM_CFML") && ((trans.type == "dynamic data") || (trans.type == "dynamic image")) && trans.participant != "DynamicAttribute")
    {
      if (this.CFOutput.IsEmpty())
      {
        // Need to enforce that cfoutput tags surround the dynamic data
        if (code.search(/<cfoutput\s*>/i) == -1 || code.search(/<\/cfoutput\s*>/i) == -1)
        {
          trans = null;
        }
      }
    }
  }
  if (trans)
  {
    this.translator.translateAttribute(code, name, trans.type, trans.attributes);
  }
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : evalExternalFunction
// Purpose	: The JavaScript interpreter needs to do some extra work (in
//		  js_PutCallObject) whenever it invokes any function containing
//		  an eval statement.  In order to avoid doing that processing
//		  every time that TM_translateTag is called, the eval
//		  statement is wrapped inside this function.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function evalExternalFunction(funcName, code, transMgr)
{
  var functionCall = funcName + "(code, transMgr);";
  return eval(functionCall);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_translateTag
// Purpose  : translate the tag given the translation rules
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_translateTag(code, offset, trans)
{
  var origPosition = this.translator.getPosition();
  var translation = "";
  if (trans && trans.type == "external")
  {
    trans = evalExternalFunction(trans.display, code, this);
  }

  if (trans && trans.beginMegaLock)
  {
	this.startMegaLock(offset);
  }

  if (trans)
  {
    if (trans.insertBefore)
	{
	  translation += this.translator.translateText("", offset, trans.participant, "as is", "", "", "", trans.insertBefore, "");
	}

    translation += this.translator.translateTag(code, offset, trans.participant, trans.type, trans.openTag, trans.closeTag, trans.attributes, trans.display, trans.lockAttributes);

    if (trans.insertAfter)
    {
	  translation += this.translator.translateText("", offset+code.length, trans.participant, "as is", "", "", "", trans.insertAfter, "");
    }
  }
  else
  {
    translation = this.translator.translateTag(code, offset, "", "", "", "", "", "", "");
  }

  if (this.inMegaLock)
  {
	var preCode = this.inStr.substr(origPosition, offset-origPosition);
	this.addToMegaLock(preCode, code, translation);
  }
  else if (this.inEditMode)
  {
	this.editModeFullTranslation.add(translation);
  }

  if (trans && trans.endMegaLock)
  {
	translation = this.finishMegaLock(trans.lockAttributes);
  }

  var endOffset = offset + code.length;
  if (this.inEditMode && (endOffset >= this.editModeEndOffset))
  {
	this.endEditMode();
  }
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_findTagLength
// Purpose : look ahead and find the ">" that matches this "<".
// Ignore any script blocks (e.g. <% %>) you find on the way.
// Return the length of the tag.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_findTagLength(searchString)
{
  // Counter to match pairs of < and >.  Starts at 1
  // because we start looking after the first <.
  var counter = 1;

  // Start after the first <.  Stop after the matching >.
  for (var i = 1; i < searchString.length && counter > 0; i++)
  {
    if (searchString[i] == '<')
    counter++;
    else if (searchString[i] == '>')
    counter--;
  }

  if (i == searchString.length && counter > 0)
  return -1;
  else
    return i;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_getAttributeValue
// Purpose  : pull the ID attribute for the current tag
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_getAttributeValue(code, attrName)
{
  var tagLength = TranslationManager.findTagLength(code);
  var tag = code.substr(0, tagLength);

  var value = "";
  var expression;

  // name="value"
  expression = new RegExp(attrName + "\\s*=\\s*\"(((<%.*?%>)|[^<\">])*)\"", "i");
  if (tag.match(expression))
    value = RegExp.$1;

  // name='value'
  expression = new RegExp(attrName + "\\s*=\\s*'(((<%.*?%>)|[^<'>])*)'", "i");
  if (value == "" && tag.match(expression))
    value = RegExp.$1;

  // name=value
  expression = new RegExp(attrName + "\\s*=\\s*(((<%.*?%>)|[^ \t\n<\"'>])*)", "i");
  if (value == "" && tag.match(expression))
    value = RegExp.$1;

  return value;
}

///////////////////////////////////////////////////////////////////////////
// Function : TM_splitBody
// Purpose  : Static function to locate the contents of the <body>
// tag and split the document at its boundaries.  It will only attempt
// this split if the document has a single pair of <body> tags.  Some
// server models can use this to skip over the tags outside the body
// (which don't need to be translated).
///////////////////////////////////////////////////////////////////////////
function TM_splitBody(inStr)
{
	var result = new Object();

	result.preInStr = "";
	result.inStr = inStr;
	result.postInStr = "";
  
  var callback = new Object();
  callback.openTagCount = 0;
  callback.closeTagCount = 0;
  callback.innerStart = -1;
  callback.innerEnd = -1;
  callback.openTagBegin = new Function("tag,offset","if (tag.toUpperCase() == \"BODY\") { this.openTagCount++; }");
  callback.openTagEnd = new Function("offset","if (this.openTagCount == 1 && this.innerStart == -1) { this.innerStart = offset; }");
  callback.closeTagBegin = new Function("tag,offset","if (tag.toUpperCase() == \"BODY\") { this.innerEnd = offset; this.closeTagCount++; }");

  dw.scanSourceString(inStr, callback);

  if (callback.innerStart >= 0 && callback.innerEnd >= 0 && 
      callback.openTagCount == 1 && callback.closeTagCount == 1)
  {
    result.preInStr = inStr.substring(0, callback.innerStart);
    result.postInStr = inStr.substring(callback.innerEnd);
    result.inStr = inStr.substring(callback.innerStart, callback.innerEnd);

    // DEBUG DWfile.write("C:\\TM_splitBody.txt", "preInStr = " + result.preInStr + "\r\n\r\npostInStr = " + result.postInStr + "\r\n\r\ninStr = " + result.inStr);
  }
  else
  {
   // DEBUG DWfile.write("C:\\TM_splitBody.txt", "failed " + callback.openTagCount + ", " + callback.closeTagCount);
  }

	return result;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_translateTagSpan
// Purpose  : translate the tag span given the translation rules 
// Tag span defined to be a span of the document from the open tag to its close tag
// For example <cfoutput>#a.b#</cfoutput> is a tag span
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_translateTagSpan(code, offset, trailingFormat)
{
  if (this.tagSpanRecover)
  {
    this.tagSpanRecover = false;
    return false;
  }

  var tag = this.getCurrentTagName();
  tag = tag.toLowerCase();
  this.getTranslatorInfo();
  
  var index = "tag+" + tag + ":all";
  var index2 = "tag+*:all";
  var arry = translatorInfo[this.index];

  var inTagSpan = false;
  
  var possibleParticipants = null;

  if (arry.outerHTMLParticipants)
  {
    for (var part in arry.outerHTMLParticipants)
    {
	  if (!arry[part].mode || arry[part].mode == this.mode)
	  {
        var translations = arry[part].translations;
        if (translations)
        {
          // Check context, mode, and any search patterns with requiredLocation="openTag" or "tagName"
          if ((translations[index] || translations[index2]) && this.searchMatchesOpenTag(part, code))
          {
            if (possibleParticipants == null)
              possibleParticipants = new Array();
            possibleParticipants.push(part);
          }
		}
      }
    }
  }

  if (possibleParticipants != null)
  {
    this.startTagSpan(tag, offset - code.length, possibleParticipants);
    inTagSpan = true;
  }

  return inTagSpan;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_startTagSpan
// Purpose  : Enter the tag span state.  All new tags and text will go into
// the current tag span
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

function TM_startTagSpan(tagName, offset, participants)
{
  if (debugTagSpan)
    alert("startTagSpan..." + tagName);
  this.tagSpan = new QuickString();
  this.inTagSpan = true;
  this.tagSpanName = tagName;
  this.tagSpanCounter = 0;
  this.tagSpanParts = participants;
  this.tagSpanSnapshot = new Snapshot(this.enclosingTags, offset);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_addToTagSpan
// Purpose  : Add text to the current tag span.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

function isWhiteSpace(x)
{
  return x == '\r' || x == '\n' || x == '\t' || x == ' ';
}

function TM_addToTagSpan(code, offset)
{
  var whitespace = "";
  if (this.tagSpanLastIndex != -1)
  {
    // White space and invalid tags don't generate a call to translateText, so
    // we need to slurp in any text that's occured since the last addToTagSpan
    extraText = this.inStr.substr(this.tagSpanLastIndex, offset-this.tagSpanLastIndex);
    this.tagSpan.add(extraText);
  }
  this.tagSpan.add(code);
  this.tagSpanLastIndex = offset + code.length;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_addOpenTagToTagSpan
// Purpose  : Add open tag to the current tag span.  Keep track of nested
// tags (e.g. <foo><foo></foo></foo>) with the tagSpanCounter.
// Returns  : true = continue parsing, false = stop parsing
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

function TM_addOpenTagToTagSpan(code, offset, trailingFormat)
{
  if (debugTagSpan)
    alert("addOpenTagToTagSpan..." + code);

  var openPattern = new RegExp("^<" + this.tagSpanName + "\\b", "i");

  //if (this.tagSpanCounter == 0) // first tag
  if (this.tagSpanSnapshot.offset == offset-code.length) // first tag
    this.tagSpanLastIndex = -1;
  this.addToTagSpan(code, offset-code.length);

  var isComplete = (trailingFormat.length > 0 &&
            trailingFormat[trailingFormat.length-1] == '/');
  if (!isComplete && (code.search(openPattern) != -1))
    this.tagSpanCounter++;

  if (isComplete && this.tagSpanCounter == 0)
    return this.finishTagSpan();

  var continueTagSpan = new Object();
  continueTagSpan.success = false;
  continueTagSpan.inTagSpan = true;
  continueTagSpan.reparse = false;
  return continueTagSpan;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_addCloseTagToTagSpan
// Purpose  : Add close tag to the current tag span.  Keep track of nested
// tags with the tagSpanCounter.  If this is the end of the tag span, try
// to translate it.  If translation is impossible, stop parsing (we'll have
// to start over).
// Returns  : true = continue parsing, false = stop parsing
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

function TM_addCloseTagToTagSpan(code, offset)
{
  if (debugTagSpan)
    alert("addCloseTagToTagSpan..." + code);

  var closePattern = new RegExp("^</" + this.tagSpanName + "\\b", "i");

  this.addToTagSpan(code, offset-code.length);
  if (code.search(closePattern) != -1)
    this.tagSpanCounter--;

  if (this.tagSpanCounter == 0)
  {
    return this.finishTagSpan();
  }

  var continueTagSpan = new Object();
  continueTagSpan.success = false;
  continueTagSpan.inTagSpan = true;
  continueTagSpan.reparse = false;
  return continueTagSpan;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_finishTagSpan
// Purpose  : Try to translate the tag span.
// Returns  : true = continue parsing, false = stop parsing
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

function TM_finishTagSpan()
{
  var origPosition = this.translator.getPosition();
  if (this.inMegaLock)
  {
	// If we end up reparsing this tag span because there isn't a
	// participant match, we need to have already inserted any leading
	// whitespace into the mega lock before that happens.
	var preCode = this.inStr.substr(origPosition, this.tagSpanSnapshot.offset-origPosition);
	this.addToMegaLock(preCode, "", "");
  }

  var success = new Object();
  success.success = true;
  success.inTagSpan = false;
  success.reparse = false;

  var failure = new Object();
  failure.success = false;
  failure.inTagSpan = true;
  failure.reparse = true;

  var translation;
  var tagSpanString = this.tagSpan.toString();

  if (debugTagSpan)
    alert("finishTagSpan...\n" + tagSpanString);

  if (this.tagSpanCache) // Use cached translation if available
  {
    translation = this.tagSpanCache.getTranslation(tagSpanString);
    if (translation)
    {
	  translation = this.correctOutlineIDs(translation);
//	  alert("CACHED TRANSLATION\n==========\n" + tagSpanString +
//			"\n===========\n" + translation);
      this.translator.insertTranslation(tagSpanString, this.tagSpanSnapshot.offset, translation);
	  this.inTagSpan = false;

      return success;
    }
  }

  var index = "tag+" + this.tagSpanName + ":all";
  var index2 = "tag+*:all";
  var part = this.getMatchingParticipant(index, index2,
                       this.tagSpanParts, tagSpanString);

  if (part != null)
  {
    var trans = this.getData(part, tagSpanString);

	if (trans && trans.type == "external")
	{
	  trans = evalExternalFunction(trans.display, tagSpanString, this);
	  // External functions for tag spans can return null to force the
	  // various components of the tag span to be parsed separately.
	  if (trans == null)
	    return failure;
	}

	if (trans && trans.beginMegaLock)
	{
		this.startMegaLock(this.tagSpanSnapshot.offset);
	}

    // use AppendText to prevent translated attributes being
    // appended to the end tag
	var result = this.translateText(tagSpanString, this.tagSpanSnapshot.offset, trans);

	if (trans && trans.endMegaLock)
	{
		result.translation = this.finishMegaLock(trans.lockAttributes);
	}

	if (result.reparse)
	{
	  if (result.useEditMode)
	  {
		this.startEditMode(this.tagSpanSnapshot.offset + tagSpanString.length,
						   tagSpanString,
						   result.translation);
	  }
	  // We need to leave this.inTagSpan on here, so that the next parse
	  // works on the individual tags instead of the whole span
	  return failure;
	}

    if (this.tagSpanCache) // Store translation in cache if available
    {
      this.tagSpanCache.setTranslation(tagSpanString, result.translation);
	}

    if (this.inMegaLock)
    {
      // We already inserted the precode at the top of this function.
      this.addToMegaLock("", tagSpanString, result.translation);
    }

	this.inTagSpan = false;
    return success;
  }
  else
  {
    return failure;
  }
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_correctOutlineIDs
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_correctOutlineIDs(translation)
{
  var oldBaseId = null;
  var newBaseId = this.translator.getOutlineId();
  var outlineId = null;
  var attr = "mmTranslatedValueOutlineID=\"OUTLINEID=";

  var translationPieces = translation.split(attr);
  for (var i = 1; i < translationPieces.length; i++)
  {
	translationPieces[i] = 
		translationPieces[i].replace(/^([0-9]*)\"/, "\"");
	if (oldBaseId == null)
		oldBaseId = RegExp.$1;
	outlineId = RegExp.$1 - oldBaseId + newBaseId;
	translationPieces[i] = outlineId + translationPieces[i];
  }

  this.translator.setOutlineId(outlineId);
  return translationPieces.join("mmTranslatedValueOutlineID=\"OUTLINEID=");
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_getMatchingParticipant
// Purpose  : Find a participant that matches our tag.
// Arguments: specIndex = specific index (e.g. "tag+foo:all")
//        genIndex = general index (e.g. "tag+*:all")
//        partArray = array of available participants
//        code = stuff to try to match
// Returns  : true = continue parsing, false = stop parsing
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

function TM_getMatchingParticipant(specIndex, genIndex, partArray, code)
{
  var part = null;
  var arry = translatorInfo[this.index];

  for (index in partArray)
  {
    var possiblePart = partArray[index];
    var translations = arry[possiblePart].translations;
    if (translations)
   {
      if (translations[specIndex])
      {
        // translationType="none" skips the translation
        if (translations[specIndex].data.type == "none")
          return null;
        if (this.searchMatchesCode(possiblePart, code))
        {
          this.whereToSearch = specIndex;
          part = possiblePart;
          break;
        }
      }
      if (!part)
      {
        if (translations[genIndex])
        {
          // translationType="none" skips the translation
          if (translations[genIndex].data.type == "none")
            return null;
          if (this.searchMatchesCode(possiblePart, code))
          {
            this.whereToSearch = genIndex;
            part = possiblePart;
          }
        }
      }
    }
  }
  return part;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_translateTextSpan
// Purpose  : translate the text span given the translation rules 
// Text spans may contain multiple pattern matches, we will translate all
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_translateTextSpan(code, offset)
{
  var part = this.getParticipant(PTYPE_TEXT, code);
  if (part && part.length)
  {
    if (translatorInfo[this.index][part].patterns);
    {
      var arry = translatorInfo[this.index][part];
      for (var patternName in arry.patterns)
      {
        var pattern = arry.patterns[patternName];
        var isRE = arry.isPatternRE[patternName];
        if (isRE)
        {
          var paramNames = arry.paramNames[patternName];
          var paramArry;
          if (paramNames)
            paramArry = paramNames.split(",");
          else
            paramArry = Array();
          var re = pattern;
          re.lastIndex = 0;
          //re.compile();
          var matchesArry;
          while ((matchesArry = re.exec(code)) != null)
          {
            var params = new Array();
            for (var i = 1; i < matchesArry.length; i++)
            {
              if (paramArry && paramArry.length >= i && paramArry[i-1].length)
              {
                params[paramArry[i-1]] = matchesArry[i];
              }
            }
            this.params = params;
            var trans = this.getData(part, matchesArry[0]);
            var translate = true;
            if ((this.translatorClass == "MM_CFML") && ((trans.type == "dynamic data") || (trans.type == "dynamic image")))
            {
              translate = !this.CFOutput.IsEmpty();
              if (translate)
              {
                var queryName = this.CFOutput.Peek();
                if (queryName && queryName.length && trans.attributes.indexOf("SOURCE=\"\"") != -1)
                {
                  trans.attributes = trans.attributes.replace(/SOURCE=""/g, "SOURCE=" + queryName);
                }
              }
            }
            if (translate)
            {
              this.translateText(matchesArry[0], offset + matchesArry.index, trans);
            }
            //if we use the exec while loop with a non-global pattern,
            // we will produce an infinite loop.  exit the loop if not global.
            if (!re.global)
            {
              alert("INTERNAL ERROR: non-global search pattern in participant: " + part);
              break;
            }
          }
        }
      }
    }
  }
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_startMegaLock
// Purpose  : Signals the beginning of a mega lock, where several tags, tag spans,
// and so forth should be translated and aggregated into a single locked region.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_startMegaLock(offset)
{
	if (this.inMegaLock)
		return;

	if (debugMegaLock)
		alert("StartMegaLock(" + offset + ")");
	this.translator.previewMode(true);
	this.inMegaLock = true;
	this.megaLockPreCodeOffset = offset;
	this.megaLockCode = new QuickString();
	this.megaLockTranslation = new QuickString();
	this.megaLockSnapshot = new Snapshot(this.enclosingTags, offset);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_addToMegaLock
// Purpose  : Keeps megaLockCode and megaLockTranslation up to date
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_addToMegaLock(preCode, code, translation)
{
	if (!this.inMegaLock)
		return;

	if (debugMegaLock)
		alert("AddToMegaLock\n" +
		"PreCode...\n" + preCode + "\n" +
		"Code...\n" + code + "\n" +
		"Translation...\n" + translation + "\n");

	// If this is the first translated item, we need to drop any leading
	// whitespace characters from the code and from the translation
	if (this.megaLockCode.isEmpty())
	{
		var preCodeLength = preCode.length;
		this.megaLockPreCodeOffset = this.megaLockSnapshot.offset - preCodeLength;
		this.megaLockCode.add(code);
		this.megaLockTranslation.add(translation.substr(preCodeLength));
	}
	// Otherwise, we want to include them.
	else
	{
		this.megaLockCode.add(preCode);
		this.megaLockCode.add(code);
		this.megaLockTranslation.add(translation);
	}

	if (debugMegaLock)
		alert("Code...\n" + this.megaLockCode.toString());
	if (debugMegaLock)
		alert("Translation...\n" + this.megaLockTranslation.toString());
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_finishMegaLock
// Purpose  : Signals the end of a mega lock
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_finishMegaLock(lockAttributes)
{
	if (!this.inMegaLock)
		return;

	if (debugMegaLock)
		alert("FinishMegaLock\n" +
		"PreCodeOffset..." + this.megaLockPreCodeOffset + "\n" +
		"Offset..." + this.megaLockSnapshot.offset + "\n" +
		"Code...\n" + this.megaLockCode.toString() + "\n" +
		"Translation...\n" + this.megaLockTranslation.toString());

	this.translator.previewMode(false);
	this.translator.resetPosition(this.megaLockPreCodeOffset);
	var megaLockCodeString = this.megaLockCode.toString();
	var result = this.translator.translateText(megaLockCodeString, this.megaLockSnapshot.offset, "", "as is", "", "", "", this.megaLockTranslation.toString(), lockAttributes);

	var endOffset = this.megaLockSnapshot.offset + megaLockCodeString.length;
	
	if (this.inEditMode)
	{
		this.editModeFullTranslation.add(result);
		if (endOffset >= this.editModeEndOffset)
		{
			this.endEditMode();
		}
	}

	this.translator.resetPosition(endOffset);
	this.inMegaLock = false;
	this.megaLockCode = null;
	this.megaLockTranslation = null;
	return result;
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_startEditMode
// Purpose  : Sets things up when we've finished parsing a control in normal mode and are ready for edit mode.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_startEditMode(endOffset, code, translation)
{
	this.inEditMode = true;
	this.mode = "edit";
	this.editModeEndOffset = endOffset;
	this.editModeFullCode = code;
	this.editModeFullTranslation = new QuickString(translation);
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_endEditMode
// Purpose  : Wraps things up when we've finished parsing a control in normal mode and in edit mode
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_endEditMode()
{
	var result = this.translator.translateTag("", this.editModeEndOffset, "", "as is", "", "", "", "<mm:toggleVisibility mode=end>", "");
	this.editModeFullTranslation.add(result);

	if (this.tagSpanCache)
	{
	  this.tagSpanCache.setTranslation(this.editModeFullCode, this.editModeFullTranslation.toString());
	}

	this.inEditMode = false;
	this.mode = "preview";
	this.editModeEndOffset = -1;
	this.editModeFullCode = null;
	this.editModeFullTranslation = null;
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_setDecoration
// Purpose  : sets decoration on or off.  Should be off for sections of the document
//        that have been locked by the Live Data Translators.
// Arguments: decoration - true for sections that have been locked by the Live Data translator
//        false otherwise.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_setDecoration(decoration) 
{ 
  this.translator.setDecoration(decoration);
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_restartParse
// Purpose  : restarts Parsing of document this gets called when we encounter a meta tag that is different
//        than the default encoding.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_restartParse() 
{ 
  this.translator.restartParse();
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_pushOpenTag
// Purpose  : Updates this.enclosingTags with a new open tag
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_pushOpenTag(trailingFormat)
{
  var tag = this.getCurrentTagName();
  tag = tag.toLowerCase();

  var isComplete = (trailingFormat.length > 0 &&
            trailingFormat[trailingFormat.length-1] == '/');
  if (!isComplete)
  {
    this.enclosingTags.push(tag);
  }
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : TM_popCloseTag
// Purpose  : Updates this.enclosingTags with a new close tag
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function TM_popCloseTag()
{
  var tag = this.getCurrentTagName();
  tag = tag.toLowerCase();
  var found = false;
  while (this.enclosingTags.length > 0 && !found)
	found = (this.enclosingTags.pop() == tag);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : Snapshot class to maintain information needed to reparse document
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function Snapshot(enclosingTags, offset)
{
	this.enclosingTags = enclosingTags.concat();
	this.offset = offset;
}

function TM_takeSnapshot(offset)
{
	return new Snapshot(this.enclosingTags, offset);
}

function TM_restoreSnapshot(snapshot)
{
	this.translator.offsetAdj += snapshot.offset;
	this.translator.resetPosition(0);
	this.inStr = this.inStr.substr(snapshot.offset);
	this.megaLockPreCodeOffset -= snapshot.offset;
	this.editModeEndOffset -= snapshot.offset;
	this.enclosingTags = snapshot.enclosingTags;
	// Do these last, since snapshot may be the same as megaLockSnapshot or tagSpanSnapshot
	if (this.megaLockSnapshot)
		this.megaLockSnapshot.offset -= snapshot.offset;
	if (this.tagSpanSnapshot)
		this.tagSpanSnapshot.offset -= snapshot.offset;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Function : Stack Class to maintain cfOutput query names
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function Stack()
{
// Members
  this.data = new Array(50);
  this.tos = -1;
  Stack.prototype.Push      = Stack_Push;
  Stack.prototype.Pop       = Stack_Pop;
  Stack.prototype.Peek      = Stack_Peek;
  Stack.prototype.IsEmpty     = Stack_IsEmpty;
}

function Stack_Push(value)
{
  if (this.tos < -1)
  {
    this.tos = -1;
  }
  this.tos += 1;
  this.data[this.tos] = value;
}

function Stack_Pop()
{
  var top = null;
  if (this.tos >= 0)
  {
    top = this.data[this.tos];
    this.tos -= 1;
  }

  return top;
}

function Stack_Peek()
{
  var top = null;
  if (this.tos >= 0)
  {
    top = this.data[this.tos];
  }

  return top;
}

function Stack_IsEmpty()
{
  if (this.tos < 0)
  {
    return true;
  }

  return false;
}
