|
|
//
// 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 [email protected]
//
// 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; }
|