WindowsXP-SP1/enduser/troubleshoot/tshoot/htmlfrag.cpp

831 lines
23 KiB
C++

//
// MODULE: HTMLFrag.cpp
//
// PURPOSE: implementation of the CHTMLFragmentsTS class, which is how CInfer packages
// up fragments of HTML to be rendered in accord with a template
//
// PROJECT: Generic Troubleshooter DLL for Microsoft AnswerPoint
//
// COMPANY: Saltmine Creative, Inc. (206)-284-7511 support@saltmine.com
//
// AUTHOR: Joe Mabel
//
// ORIGINAL DATE: 8-27-1998
//
// NOTES:
//
// Version Date By Comments
//--------------------------------------------------------------------
// V3.0 7-20-98 JM Original
//
//////////////////////////////////////////////////////////////////////
#pragma warning(disable:4786)
#include "stdafx.h"
#include <algorithm>
#include "HTMLFrag.h"
#include "event.h"
#include "baseexception.h"
#include "CharConv.h"
#include "fileread.h"
#ifdef LOCAL_TROUBLESHOOTER
#include "CHMFileReader.h"
#endif
// V3.2 Additions.
namespace
{
const CString kstrCond_StringCompare= _T("StringCompare");
const CString kstrCond_OperatorGT= _T(".GT.");
const CString kstrCond_OperatorGE= _T(".GE.");
const CString kstrCond_OperatorEQ= _T(".EQ.");
const CString kstrCond_OperatorNE= _T(".NE.");
const CString kstrCond_OperatorLE= _T(".LE.");
const CString kstrCond_OperatorLT= _T(".LT.");
const CString kstrCond_OperatorSubstring= _T(".SubstringOf.");
}
//////////////////////////////////////////////////////////////////////
// CHTMLValue implementation
//////////////////////////////////////////////////////////////////////
bool CHTMLValue::SetValue(const CString& value)
{
CString strOldValue = m_strValValue;
m_strValValue = value;
m_strValValue.TrimLeft();
m_strValValue.TrimRight();
if (IsValid())
return true;
m_strValValue = strOldValue;
return false;
}
bool CHTMLValue::IsNumeric()
{
for (int i = 0; i < m_strValValue.GetLength(); i++)
if(!_ismbcdigit(m_strValValue[i]))
return false;
return true;
}
bool CHTMLValue::IsString()
{
// string should be wrapped by quots
if (m_strValValue.GetLength() >= 2 &&
m_strValValue[0] == _T('"') &&
m_strValValue[m_strValValue.GetLength()-1] == _T('"')
)
return true;
return false;
}
bool CHTMLValue::IsBoolean()
{
return 0 == _tcsicmp(_T("true"), m_strValValue) ||
0 == _tcsicmp(_T("false"), m_strValValue);
}
bool CHTMLValue::GetNumeric(long& out)
{
if (IsNumeric())
{
out = _ttol(m_strValValue);
return true;
}
return false;
}
bool CHTMLValue::GetString(CString& out)
{
if (IsString())
{
out = m_strValValue.Mid(1, m_strValValue.GetLength()-2);
return true;
}
return false;
}
bool CHTMLValue::GetBoolean(bool& out)
{
if (IsBoolean())
{
out = (0 == _tcsicmp(_T("true"), m_strValValue)) ? true : false;
return true;
}
return false;
}
bool CHTMLValue::operator == (const CHTMLValue& sib)
{
return 0 == _tcsicmp(m_strValName, sib.m_strValName); // case insensitive
}
//////////////////////////////////////////////////////////////////////
// CHTMLFragments implementation
//////////////////////////////////////////////////////////////////////
bool CHTMLFragments::SetValue(const CString& str)
{
int index = str.Find(_T('='));
if (index == -1)
return false;
CString name = str.Left(index);
name.TrimLeft();
name.TrimRight();
CString value= str.Right(str.GetLength() - index - 1);
value.TrimLeft();
value.TrimRight();
CHTMLValue HTMLValue(name, value);
HTMLValueVector::iterator found = find(m_HTMLValueVector.begin(), m_HTMLValueVector.end(), HTMLValue);
if (found != m_HTMLValueVector.end())
*found = HTMLValue;
else
m_HTMLValueVector.push_back(HTMLValue);
return true;
}
CHTMLValue* CHTMLFragments::GetValue(const CString& value_name)
{
HTMLValueVector::iterator found = find(m_HTMLValueVector.begin(), m_HTMLValueVector.end(), CHTMLValue(value_name));
if (found != m_HTMLValueVector.end())
return found;
return NULL;
}
//////////////////////////////////////////////////////////////////////
// CHTMLFragmentsTS implementation
//////////////////////////////////////////////////////////////////////
CHTMLFragmentsTS::CHTMLFragmentsTS( const CString & strScriptPath, bool bIncludesHistoryTable ) :
m_bIncludesHistoryTable(bIncludesHistoryTable),
m_bIncludesHiddenHistory(!bIncludesHistoryTable),
m_bSuccess(false),
m_strYes(_T("Yes")),
m_strScriptPath(strScriptPath)
{
}
CHTMLFragmentsTS::~CHTMLFragmentsTS()
{
}
// Obviously, a very ad hoc implementation
int CHTMLFragmentsTS::GetCount(const FragmentIDVector & fidvec) const
{
if (fidvec.empty())
return 0;
if (fidvec.back().Index != -1)
return 0;
const CString & strVariable = fidvec[0].VarName; // ref of convenience
if (fidvec.size() == 1)
{
if (strVariable == VAR_PROBLEM_ASK)
return 1;
if (strVariable == VAR_RECOMMENDATIONS)
return m_vstrVisitedNodes.size();
if (strVariable == VAR_QUESTIONS)
return 1;
if (strVariable == VAR_SUCCESS)
return m_bSuccess ? 1 : 0;
if (strVariable == VAR_STARTFORM)
return 1;
return 0;
}
if (fidvec.size() == 2
&& strVariable == VAR_RECOMMENDATIONS
&& fidvec[0].Index >= 0
&& fidvec[0].Index < m_vvstrStatesOfVisitedNodes.size()
&& fidvec[1].VarName == VAR_STATES)
{
return m_vvstrStatesOfVisitedNodes[fidvec[0].Index].size();
}
return 0;
}
// this function was removed from const to achieve further flexibility:
// we might need to take some active steps in it, as for informational
// statement we might modify current node text. Oleg. 01.05.99
CString CHTMLFragmentsTS::GetText( const FragmentIDVector & fidvec, const FragCommand fragCmd )
{
if (fidvec.empty())
return m_strNil;
const CString & strVariable0 = fidvec[0].VarName; // ref of convenience
int i0 = fidvec[0].Index;
if (fidvec.size() == 1)
{
if (strVariable0 == VAR_PROBLEM_ASK)
return m_strProblem;
if (strVariable0 == VAR_RECOMMENDATIONS
&& i0 >= 0
&& i0 < m_vstrVisitedNodes.size() )
{
return m_vstrVisitedNodes[i0];
}
if (strVariable0 == VAR_QUESTIONS)
return m_strCurrentNode;
if (strVariable0 == VAR_SUCCESS)
return m_bSuccess ? m_strYes : m_strNil;
if (strVariable0 == VAR_STARTFORM)
return m_strStartForm;
if (fragCmd == eResource)
{
// Load a server side include file.
CString strScriptContent;
CString strFullPath = m_strScriptPath + strVariable0;
CFileReader fileReader( CPhysicalFileReader::makeReader( strFullPath ) );
if (fileReader.Read())
{
fileReader.GetContent(strScriptContent);
return strScriptContent;
}
}
// Check for new conditionals added in V3.2.
CString strTemp= strVariable0.Left( kstrCond_NumericCompare.GetLength() );
if (strTemp == kstrCond_NumericCompare)
{
// Evaluate the numeric expression.
if (NumericConditionEvaluatesToTrue( strVariable0.Mid( kstrCond_NumericCompare.GetLength() )))
return( m_strYes );
return( m_strNil );
}
strTemp= strVariable0.Left( kstrCond_StringCompare.GetLength() );
if (strTemp == kstrCond_StringCompare)
{
// Evaluate the string expression.
if (StringConditionEvaluatesToTrue( strVariable0.Mid( kstrCond_StringCompare.GetLength() )))
return( m_strYes );
return( m_strNil );
}
return m_strNil;
}
const CString & strVariable1 = fidvec[1].VarName; // ref of convenience
int i1 = fidvec[1].Index;
if (fidvec.size() == 2
&& strVariable0 == VAR_RECOMMENDATIONS
&& i0 >= 0
&& i0 < m_vvstrStatesOfVisitedNodes.size()
&& strVariable1 == VAR_STATES
&& i1 >= 0
&& i1 < m_vvstrStatesOfVisitedNodes[i0].size() )
return (m_vvstrStatesOfVisitedNodes[i0][i1]);
// V3.2
// The specification for the v3.2 cookies called for permitting underscores
// in cookie names. The HTI reader already used underscores to delimit
// variables. The code below detects a comparision operation that has been
// broken up due to the presence of underscores and reassembles it.
// RAB-19991019.
{
// Check for new conditionals added in V3.2.
int nOpType= 0;
CString strTemp= strVariable0.Left( kstrCond_NumericCompare.GetLength() );
if (strTemp == kstrCond_NumericCompare)
nOpType= 1;
else
{
strTemp= strVariable0.Left( kstrCond_StringCompare.GetLength() );
if (strTemp == kstrCond_StringCompare)
nOpType= 2;
}
if (nOpType)
{
// Reassemble the comparison operation.
CString strCompareOp= fidvec[0].VarName;
for (int nItem= 1; nItem < fidvec.size(); nItem++)
{
strCompareOp+= _T("_"); // Reinsert the delimiter that was removed during the parse.
strCompareOp+= fidvec[ nItem ].VarName;
}
if (nOpType == 1)
{
// Evaluate the numeric expression.
if (NumericConditionEvaluatesToTrue( strCompareOp.Mid( kstrCond_NumericCompare.GetLength() )))
return( m_strYes );
}
else
{
// Evaluate the string expression.
if (StringConditionEvaluatesToTrue( strCompareOp.Mid( kstrCond_StringCompare.GetLength() )))
return( m_strYes );
}
return( m_strNil );
}
}
return m_strNil;
}
bool CHTMLFragmentsTS::IsValidSeqOfVars(const FragmentIDVector & arrParents, const FragmentIDVector & arrChildren) const
{
// we allow only one level of nesting
// that means in "forany" of $Recommendations we can have "forany" array of $States
if (arrParents.size() == 1 && arrChildren.size() == 1)
if (arrParents[0].VarName == VAR_RECOMMENDATIONS && arrChildren[0].VarName == VAR_STATES)
return true;
return false;
}
void CHTMLFragmentsTS::SetStartForm(const CString & str)
{
m_strStartForm = str;
}
void CHTMLFragmentsTS::SetProblemText(const CString & str)
{
if (m_bIncludesHistoryTable)
m_strProblem = str;
}
void CHTMLFragmentsTS::SetCurrentNodeText(const CString & str)
{
m_strCurrentNodeSimple = str;
RebuildCurrentNodeText();
}
void CHTMLFragmentsTS::SetHiddenHistoryText(const CString & str)
{
if (m_bIncludesHiddenHistory)
{
m_strHiddenHistory = str;
RebuildCurrentNodeText();
}
}
// need only be called for bSuccess == true (false is default) but written more generally.
void CHTMLFragmentsTS::SetSuccessBool(bool bSuccess)
{
m_bSuccess = bSuccess;
}
CString CHTMLFragmentsTS::GetCurrentNodeText()
{
return m_strCurrentNodeSimple;
}
// must be called in order nodes were visited. Do not call for problem node.
// return index of added node
int CHTMLFragmentsTS::PushBackVisitedNodeText(const CString & str)
{
if (m_bIncludesHistoryTable)
{
try
{
m_vstrVisitedNodes.push_back(str);
}
catch (exception& x)
{
CString str;
// Note STL exception in event log.
CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ );
CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(),
SrcLoc.GetSrcFileLineStr(),
CCharConversion::ConvertACharToString(x.what(), str),
_T(""),
EV_GTS_STL_EXCEPTION );
}
return m_vstrVisitedNodes.size() - 1;
}
return -1;
}
// For each given iVisitedNode, must be called in order of state number,
// with ST_UNKNOWN last
// return index of added state
int CHTMLFragmentsTS::PushBackStateText(UINT iVisitedNode, const CString & str)
{
if (m_bIncludesHistoryTable)
{
try
{
// Check if we need to add one or more elements to the vector of nodes.
if (m_vvstrStatesOfVisitedNodes.size() <= iVisitedNode)
{
// Check if we need to add more than one element to the vector of nodes.
if (m_vvstrStatesOfVisitedNodes.size() < iVisitedNode)
{
// We need to add more than one element to the vector of nodes.
// This condition should not be occurring, so log it.
CString tmpStrCurCnt, tmpStrReqCnt;
tmpStrCurCnt.Format( _T("%d"), m_vvstrStatesOfVisitedNodes.size() );
tmpStrReqCnt.Format( _T("%d"), iVisitedNode );
CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ );
CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(),
SrcLoc.GetSrcFileLineStr(),
tmpStrCurCnt, tmpStrReqCnt,
EV_GTS_NODE_COUNT_DISCREPANCY );
// Add to the vector of nodes until we have placed a total of
// iVisitedNode elements into the vector. We are inserting empty
// states as the first element of the vector of states for a node.
vector<CString> vecDummy;
vecDummy.push_back( _T("") );
do
{
m_vvstrStatesOfVisitedNodes.push_back( vecDummy );
}
while (m_vvstrStatesOfVisitedNodes.size() < iVisitedNode);
}
// Add this state string as the first element of the vector of states for a node.
vector<CString> tmpVector;
tmpVector.push_back( str );
m_vvstrStatesOfVisitedNodes.push_back( tmpVector );
}
else
{
// Add this state string to the vector of states for a node.
m_vvstrStatesOfVisitedNodes[iVisitedNode].push_back(str);
}
}
catch (exception& x)
{
CString str;
// Note STL exception in event log.
CBuildSrcFileLinenoStr SrcLoc( __FILE__, __LINE__ );
CEvent::ReportWFEvent( SrcLoc.GetSrcFileLineStr(),
SrcLoc.GetSrcFileLineStr(),
CCharConversion::ConvertACharToString(x.what(), str),
_T(""),
EV_GTS_STL_EXCEPTION );
}
return m_vvstrStatesOfVisitedNodes[iVisitedNode].size() - 1;
}
return -1;
}
// call this function to find out if there is any need for a history table.
// If not, calling class can save the effort of constructing one:
// SetProblemText(), AppendVisitedNodeText(), AppendStateText()
// becomes no-ops, so no need to construct strings and call them
bool CHTMLFragmentsTS::IncludesHistoryTable() const
{
return m_bIncludesHistoryTable;
}
// call this function to find out if there is any need for "hidden history"
// If not, calling class can save the effort of constructing one:
// SetHiddenHistoryText() becomes a no-op, so no need to construct a string and call it
bool CHTMLFragmentsTS::IncludesHiddenHistory() const
{
return m_bIncludesHiddenHistory;
}
void CHTMLFragmentsTS::RebuildCurrentNodeText()
{
m_strCurrentNode = m_strHiddenHistory;
m_strCurrentNode += m_strCurrentNodeSimple;
}
// Function which parses and evaluates a numeric condition.
bool CHTMLFragmentsTS::NumericConditionEvaluatesToTrue( const CString & str )
{
bool bRetVal= false;
CString strScratch= RemoveOuterParenthesis( str );
if (strScratch.GetLength())
{
long lLeftOperand, lRightOperand;
// Check for all supported operators.
if (RetNumericOperands( strScratch, kstrCond_OperatorGT, lLeftOperand, lRightOperand ))
{
// .GT. case.
bRetVal= (lLeftOperand > lRightOperand) ? true : false;
}
else if (RetNumericOperands( strScratch, kstrCond_OperatorGE, lLeftOperand, lRightOperand ))
{
// .GE. case.
bRetVal= (lLeftOperand >= lRightOperand) ? true : false;
}
else if (RetNumericOperands( strScratch, kstrCond_OperatorEQ, lLeftOperand, lRightOperand ))
{
// .EQ. case.
bRetVal= (lLeftOperand == lRightOperand) ? true : false;
}
else if (RetNumericOperands( strScratch, kstrCond_OperatorNE, lLeftOperand, lRightOperand ))
{
// .NE. case.
bRetVal= (lLeftOperand != lRightOperand) ? true : false;
}
else if (RetNumericOperands( strScratch, kstrCond_OperatorLE, lLeftOperand, lRightOperand ))
{
// .LE. case.
bRetVal= (lLeftOperand <= lRightOperand) ? true : false;
}
else if (RetNumericOperands( strScratch, kstrCond_OperatorLT, lLeftOperand, lRightOperand ))
{
// .LT. case.
bRetVal= (lLeftOperand < lRightOperand) ? true : false;
}
}
return( bRetVal );
}
// Function which parses and evaluates a string condition.
bool CHTMLFragmentsTS::StringConditionEvaluatesToTrue( const CString & str )
{
bool bRetVal= false;
CString strScratch= RemoveOuterParenthesis( str );
if (strScratch.GetLength())
{
CString strLeftOperand, strRightOperand;
// Check for all supported operators.
if (RetStringOperands( strScratch, kstrCond_OperatorEQ, strLeftOperand, strRightOperand ))
{
if ((strLeftOperand.GetLength() == strRightOperand.GetLength()) &&
(_tcsicmp( strLeftOperand, strRightOperand ) == 0))
bRetVal= true;
}
else if (RetStringOperands( strScratch, kstrCond_OperatorNE, strLeftOperand, strRightOperand ))
{
if ((strLeftOperand.GetLength() != strRightOperand.GetLength()) ||
(_tcsicmp( strLeftOperand, strRightOperand ) != 0))
bRetVal= true;
}
else if (RetStringOperands( strScratch, kstrCond_OperatorSubstring, strLeftOperand, strRightOperand ))
{
int nLeftLen= strLeftOperand.GetLength();
int nRightLen= strRightOperand.GetLength();
if ((nLeftLen) && (nRightLen) && (nLeftLen <= nRightLen))
{
strLeftOperand.MakeLower();
strRightOperand.MakeLower();
if (_tcsstr( strRightOperand, strLeftOperand ) != NULL)
bRetVal= true;
}
}
}
return( bRetVal );
}
// Function to peel off the outer parenthesis of a condition.
CString CHTMLFragmentsTS::RemoveOuterParenthesis( const CString & str )
{
CString strRet;
int nOrigLength= str.GetLength();
if (nOrigLength > 2)
{
TCHAR cFirstChar= str.GetAt( 0 );
TCHAR cLastChar= str.GetAt( nOrigLength - 1 );
if ((cFirstChar == _T('(')) && (cLastChar == _T(')')))
strRet= str.Mid( 1, nOrigLength - 2 );
}
return( strRet );
}
// Breaks out the numeric operands from a string.
bool CHTMLFragmentsTS::RetNumericOperands( const CString & str, const CString & strOperator,
long &lLeftOperand, long &lRightOperand )
{
bool bRetVal= false;
int nOffset= str.Find( strOperator );
if (nOffset != -1)
{
CString strScratch= str.Left( nOffset - 1 );
strScratch.TrimRight();
strScratch.TrimLeft();
if (strScratch.GetLength())
{
lLeftOperand= atol( strScratch );
strScratch= str.Mid( nOffset + strOperator.GetLength() );
strScratch.TrimRight();
strScratch.TrimLeft();
if (strScratch.GetLength())
{
lRightOperand= atol( strScratch );
bRetVal= true;
}
}
}
return( bRetVal );
}
// Breaks out the string operands from a string.
bool CHTMLFragmentsTS::RetStringOperands( const CString & str, const CString & strOperator,
CString & strLeftOperand, CString & strRightOperand )
{
bool bRetVal= false;
int nOffset= str.Find( strOperator );
if (nOffset != -1)
{
strLeftOperand= str.Left( nOffset - 1 );
if (CleanStringOperand( strLeftOperand ))
{
strRightOperand= str.Mid( nOffset + strOperator.GetLength() );
strRightOperand.TrimRight();
strRightOperand.TrimLeft();
if (CleanStringOperand( strRightOperand ))
bRetVal= true;
}
}
return( bRetVal );
}
// Trims an operand string and replaces embedded characters.
int CHTMLFragmentsTS::CleanStringOperand( CString& strOperand )
{
int nRetLength= 0;
if (!strOperand.IsEmpty())
{
strOperand.TrimRight();
strOperand.TrimLeft();
nRetLength= strOperand.GetLength();
if (nRetLength > 2)
{
if ((strOperand[ 0 ] == _T('\"')) && (strOperand[ nRetLength - 1 ] == _T('\"')))
{
// V3.2 Remove the surrounding double quotes.
nRetLength-= 2;
strOperand= strOperand.Mid( 1, nRetLength );
}
// V3.2 Replace embedded quotes or backslashes within the string.
for (int nOp= 0; nOp < 2; nOp++)
{
// Set the search and replacement strings.
CString strSearch, strReplace;
if (nOp)
{
// Replace backslashes.
strSearch= _T("\\\\");
strReplace= _T("\\");
}
else
{
// Replace double quotes.
strSearch= _T("\\\"");
strReplace= _T("\"");
}
// Search and replace.
int nStart= 0, nEnd;
while (CString::FIND_FAILED != (nStart= strOperand.Find( strSearch, nStart )))
{
nEnd= nStart + strSearch.GetLength();
strOperand= strOperand.Left( nStart ) + strReplace + strOperand.Mid( nEnd );
nStart+= strReplace.GetLength(); // Move search past the character that was just replaced.
}
}
}
}
return( nRetLength );
}
// JSM V3.2
// called by HTIReader in parsing stage to convert network property name, given
// in <$GTS property "propname">, to network property (value).
//
CString CHTMLFragmentsTS::GetNetProp(const CString & strNetPropName)
{
map<CString,CString>::iterator it = m_mapstrNetProps.find(strNetPropName);
if (it == m_mapstrNetProps.end())
return _T("\0"); // not found
else
return (*it).second;
}
// JSM V3.2
// add a name to the internal list (map) of Net props which are needed
// by this Fragments object
//
// CAPGTSHTIReader finds the names of the network properties and passes
// them in via AddNetPropName, but it doesn't know how to get the values.
// CInfer will later get the network property names from Fragments object, call the BNTS
// to find out the network property values, and supply the values to Fragments
//
//
void CHTMLFragmentsTS::AddNetPropName(const CString & strNetPropName)
{
// don't insert a NULL key!!!
if (!strNetPropName.IsEmpty())
m_mapstrNetProps[strNetPropName];
}
// JSM V3.2
// SetNetProp()
//
// For a Network Property Name in our internal map, set the
// corresponding network property (ie, fill in the map value
// for that key.) Called by CInfer, which is the object that knows how
// to talk to the BNTS.
//
// returns TRUE if success
// FALSE if we've given a NetPropName which is not in the internal map
//
BOOL CHTMLFragmentsTS::SetNetProp(CString strNetPropName, CString strNetProp)
{
map<CString,CString>::iterator it;
if ((it= m_mapstrNetProps.find(strNetPropName)) == m_mapstrNetProps.end())
return false;
m_mapstrNetProps[strNetPropName] = strNetProp;
return true;
}
// JSM V3.2
// IterateNetProp()
// Called to iterate through the network properties in our internal
// map during the setting process (see above.)
//
// Sets strNameIterator to the name of the next net prop in the map.
//
// calling w/ an empty (NULL) key starts the iteration.
// calling w/ a name that's not in the map returns false.
// calling w/ any other name returns true, unless at end of iteration
//
// strNameIterator is not valid if this function returns false.
//
BOOL CHTMLFragmentsTS::IterateNetProp(CString & strNameIterator)
{
map<CString,CString>::iterator it;
if (strNameIterator.IsEmpty())
{
// request to start iteration, if possible
if (m_mapstrNetProps.empty())
return false; // we're at end already
it = m_mapstrNetProps.begin();
}
else if ((it= m_mapstrNetProps.find(strNameIterator)) != m_mapstrNetProps.end())
{
// iterate:
if (++it == m_mapstrNetProps.end())
return false; // arrived at end
}
else
{
// invalid key
return false;
}
strNameIterator = (*it).first;
return true;
}
// V3.2 enhancement for the Start Over button.
void CHTMLFragmentsTS::SetStartOverLink( const CString & str )
{
m_strStartOverLink = str;
}
// V3.2 enhancement for the Start Over button.
CString CHTMLFragmentsTS::GetStartOverLink()
{
return m_strStartOverLink;
}