// // 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 #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 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 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::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::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::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; }