// // MODULE: APGTSINF.CPP // // PURPOSE: Inference Engine Interface // Completely implement class CInfer. VERY IMPORTANT STUFF! // One of these is created for each user request // Some utility functions at end of file. // // PROJECT: Generic Troubleshooter DLL for Microsoft AnswerPoint // // COMPANY: Saltmine Creative, Inc. (206)-284-7511 support@saltmine.com // // AUTHOR: Roman Mach, Joe Mabel // // ORIGINAL DATE: 8-2-96 // // NOTES: // 1. Many methods in this class could be const if BNTS had more appropriate use of const // 2. Several places in this file you will see a space after %s in the format passed to // CInfer::AppendMultilineNetProp() or CInfer::AppendMultilineNodeProp(). This is the // upshot of some 12/98 correspondence between Microsoft and Saltmine. Many older DSC files // were built with a tool that could not handle more than 255 characters in a string. // The DSC feil format's "Array of string" was used to build up longer strings. Newer // DSC files (and all Argon-produced DSC files) should use only the first element of this // array. // The older DSC files assumed that the separate strings would effectively be separated // by white space, so we must maintain that situation. // 3. >>> $MAINT - exception-handling strategy for push_back and other memory allocation // functions is really overkill. If we run out of memory, we're screwed anyway. Really // would suffice to handle try/catch just at the main function of the thread. // // Version Date By Comments //-------------------------------------------------------------------- // V0.1 - RM Original // V3.0 7-21-98 JM Major revision, deprecate IDH. // 8-27-98 JM Totally new method of communicating with template // #pragma warning(disable:4786) #include "stdafx.h" #include "event.h" #include "apgts.h" #include "apgtsinf.h" #include "apgtsmfc.h" #include "apgtsassert.h" #include "CharConv.h" #include "maxbuf.h" #include #include #include #include "Sniff.h" #include "SniffController.h" #ifdef LOCAL_TROUBLESHOOTER #include "SniffLocal.h" #endif // ------------------------------------------------------------------- // Constructor/Destructor, other initialization // ------------------------------------------------------------------- // // INPUT *pCtxt is a buffer for building the string to pass back over the net. CInfer::CInfer(CSniffConnector* pSniffConnector) : #ifdef LOCAL_TROUBLESHOOTER m_pSniff(new CSniffLocal(pSniffConnector, NULL)), #else m_pSniff(NULL), #endif m_nidProblem(nidNil), m_bDone(false), m_bRecOK (false), m_SniffedRecommendation(nidNil, SNIFF_FAILURE_RESULT), m_bUseBackEndRedirection(false), m_bRecycleSkippedNode(false), m_nidRecycled(0), m_bRecyclingInitialized(false), m_nidSelected(nidNil), m_bLastSniffedManually(false) { } // // CInfer::~CInfer() { delete m_pSniff; } // The intention is that this be called only once. // It would be ideal if this were part of the constructor, but the CTopic * is not // yet available at time of construction. // The expectation is that this should be called before calling any other function. (Some // are technically OK to call, but it's smartest not to rely on that.) void CInfer::SetTopic(CTopic *pTopic) { m_pTopic = pTopic; if (m_pSniff) m_pSniff->SetTopic(pTopic); } // This fn exists so APGTSContext can access *m_pSniff to tell it what the sniffing // policies are. CSniff* CInfer::GetSniff() { return m_pSniff; } // ------------------------------------------------------------------- // First, we set the states of nodes, based on the query string we got from the HTML form // ------------------------------------------------------------------- // Convert IDH to NID. Needed on some old query string formats // "Almost vestigial", still supported in v3.2, but will be dropped in v4.0. NID CInfer::NIDFromIDH(IDH idh) const { if (idh == m_pTopic->CNode() + idhFirst) return nidProblemPage; if (idh == nidService + idhFirst) return nidService; if (idh == IDH_FAIL) return nidFailNode; if (idh == IDH_BYE) return nidByeNode; ASSERT (idh >= idhFirst); return idh - idhFirst; } // Associate a state with a node. // INPUT nid // INPUT ist - Normally, index of a state for that node. // If nid == nidProblemPage, then ist is actually NID of selected problem void CInfer::SetNodeState(NID nid, IST ist) { if (nid == nidNil) return; CString strTemp; CString strTxt; if (ist == ST_WORKED) { if (nid == nidFailNode || nid == nidSniffedAllCausesNormalNode || nid == nidService || nid == nidImpossibleNode) { if (m_pTopic->HasBES()) { m_bUseBackEndRedirection = true; CString strThrowaway; // we don't really care about this string; // we call OutputBackend strictly for the side // effect of setting m_strEncodedForm. OutputBackend(strThrowaway); return; } } m_bDone = true; AddToBasisForInference(nid, ist); // this node still needs to be present // in m_arrBasisForInference, as it is // present in m_SniffedStates. // Add to the visited array to be displayed in the visible history page. RAB-20000628. try { m_arrnidVisited.push_back( nid ); } 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; } if (ist == ST_ANY) { // We rely on the fact that only a service node offers ST_ANY // ("Is there anything else I can try?") m_bRecycleSkippedNode = true; return; } // We should never have service node go past this point (always ST_WORKED or ST_ANY). if (nid == nidByeNode || nid == nidFailNode || nid == nidSniffedAllCausesNormalNode) return; if (ist == ST_UNKNOWN) { // Add it to the list of skipped nodes & visited nodes try { m_arrnidSkipped.push_back(nid); m_arrnidVisited.push_back(nid); } 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; } if (nid == nidProblemPage) { if (!IsProblemNode(ist)) { // Totally bogus query. Arbitrary course of action. m_bRecycleSkippedNode = true; return; } // Change this around to the way we would express it for any other node. nid = ist; ist = 1; // Set this problem node to a state value of 1 (in fact, we never // explicitly set problem nodes to state value of 0) m_nidProblem = nid; // special case: here instead of in m_arrnidVisited AddToBasisForInference(nid, ist); return; } AddToBasisForInference(nid, ist); // Store into our list of nodes obtained from the user try { m_arrnidVisited.push_back(nid); } 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 ); } } void CInfer::AddToBasisForInference(NID nid, IST ist) { try { m_BasisForInference.push_back(CNodeStatePair(nid, ist)); } 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 ); } } // Add to the list of (previously) sniffed nodes. void CInfer::AddToSniffed(NID nid, IST ist) { try { if (ist == ST_WORKED && m_pTopic->IsCauseNode(nid)) { // in case of cause node in abnormal state (which is ST_WORKED) // we need to set state to "1" as if it was sniffed. // This situation happens during manual sniffing of cause node that worked. ist = 1; } m_SniffedStates.push_back(CNodeStatePair(nid, ist)); } 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 ); } } // Be careful not to call this redundantly: its call to CTopic::GetRecommendations() // is expensive. void CInfer::GetRecommendations() { // if we haven't previously sought a recommendation... if ( m_SniffedRecommendation.nid() != nidNil ) { // The one and only relevant recommendation is already forced, so don't bother // getting recommendations. // m_SniffedRecommendation.nid() is a Cause node in its abnormal state m_Recommendations.empty(); try { m_Recommendations.push_back(m_SniffedRecommendation.nid()); m_bRecOK = true; } 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 ); } } else { // Pass data into m_pTopic // Get back recomendations. int status = m_pTopic->GetRecommendations(m_BasisForInference, m_Recommendations); m_bRecOK = (status == CTopic::RS_OK); } } // returns true if nid is a problem node of this network bool CInfer::IsProblemNode(NID nid) const { // get data array of problem nodes vector* parrnid = NULL; m_pTopic->GetProblemArray(parrnid); vector::const_iterator itnidBegin = parrnid->begin(); vector::const_iterator itnidEnd = parrnid->end(); vector::const_iterator itnidProblem = find(itnidBegin, itnidEnd, nid); if (itnidProblem == itnidEnd) return false; else return true; } bool CInfer::IsInSniffedArray(NID nid) const { UINT nSniffedNodes = m_SniffedStates.size(); for (UINT i = 0; i < nSniffedNodes; i++) { if (m_SniffedStates[i].nid() == nid) { // Do not have to check for state, as m_SniffedStates will // have only valid states (states, which are accepted by BNTS), // no 102 or -1 states return true; } } return false; } // ------------------------------------------------------------------- // For writing the new page after inference: the following texts are // invariant for a given topic (aka network). // ------------------------------------------------------------------- // CreateUnknownButtonText: Reads the network property for the // unknown-state radio button from the network dsc file. // Puts value in strUnknown // This is specific to the radio button for "unknown" in the history table, // that is, for a node which has previously been visited. This should not be // used for the radio button for the "unknown" state of the present node. void CInfer::CreateUnknownButtonText(CString & strUnknown) const { strUnknown = m_pTopic->GetNetPropItemStr(HTK_UNKNOWN_RBTN); if (strUnknown.IsEmpty()) strUnknown = SZ_UNKNOWN; return; } // AppendNextButtonText: Reads the network property for the // NEXT button from the network dsc file and append it to str. void CInfer::AppendNextButtonText(CString & str) const { CString strTemp = m_pTopic->GetNetPropItemStr(HTK_NEXT_BTN); if (strTemp.IsEmpty()) strTemp = SZ_NEXT_BTN; str += strTemp; return; } // AppendNextButtonText: Reads the network property for the // NEXT button from the network dsc file and append it to str. void CInfer::AppendStartOverButtonText(CString & str) const { CString strTemp = m_pTopic->GetNetPropItemStr(HTK_START_BTN); if (strTemp.IsEmpty()) strTemp = SZ_START_BTN; str += strTemp; return; } // AppendBackButtonText: Reads the network property for the // BACK button from the network dsc file and append it to str. void CInfer::AppendBackButtonText(CString & str) const { CString strTemp = m_pTopic->GetNetPropItemStr(HTK_BACK_BTN); if (strTemp.IsEmpty()) strTemp = SZ_BACK_BTN; str += strTemp; return; } // AppendPPSnifferButtonText: Reads the network property for the // sniffer button from the network dsc file. // NOTE that this button is related to "expensive" sniffing only. // Appends to str. void CInfer::AppendPPSnifferButtonText(CString & str) const { CString strTemp = m_pTopic->GetNetPropItemStr(HTK_SNIF_BTN); if (strTemp.IsEmpty()) strTemp = SZ_PP_SNIF_BTN; str += strTemp; } // AppendManualSniffButtonText: Reads the network property for the // manual sniff button from the network dsc file. // Appends to str. void CInfer::AppendManualSniffButtonText(CString & str) const { CString strTemp = m_pTopic->GetNetPropItemStr(H_NET_TEXT_SNIFF_ONE_NODE); if (strTemp.IsEmpty()) strTemp = SZ_SNIFF_ONE_NODE; str += strTemp; } // AppendHistTableSniffedText: Reads the network property for the // indication in history table that a node was sniffed. // Appends to str. void CInfer::AppendHistTableSniffedText(CString & str) const { CString strTemp = m_pTopic->GetNetPropItemStr(H_NET_HIST_TABLE_SNIFFED_TEXT); if (strTemp.IsEmpty()) strTemp = SZ_HIST_TABLE_SNIFFED_TEXT; str+= _T("
\n"); str += strTemp; } // AppendAllowSniffingText: Reads the network property for the // label of the AllowSniffing checkbox from the network dsc file. // Appends to str. void CInfer::AppendAllowSniffingText(CString & str) const { CString strTemp = m_pTopic->GetNetPropItemStr(H_NET_ALLOW_SNIFFING_TEXT); if (strTemp.IsEmpty()) strTemp = SZ_ALLOW_SNIFFING_TEXT; str += strTemp; } // AppendSniffFailedText: Reads the network property for the // alert box to be used when manual sniffing fails from the network dsc file. // Appends to str. void CInfer::AppendSniffFailedText(CString & str) const { CString strTemp = m_pTopic->GetNetPropItemStr(H_NET_TEXT_SNIFF_ALERT_BOX); if (strTemp.IsEmpty()) strTemp = SZ_SNIFF_FAILED; str += strTemp; } // Appends an HTML link but makes it look like an HTML Form Button // useful for Start Over in Online TS, because with no idea what browser user will have, // we can't usefully use an onClick method (not supported in older browsers). // Online TS runs in a "no scripting" environment. // Pure HTML doesn't provide a means to put both a "Next" and a "Start Over" button // in the same HTML form. Conversely, if Start Over btn was outside the form, pure HTML // doesn't provide a means to align it with a button in the form. // Note that x.gif does not exist: its absence creates a 1-pixel placeholder. // >>>$MAINT We may want to change some of the rowspans to better emulate the exact size // of a button; try to make it look perfect under IE void CInfer::AppendLinkAsButton( CString & str, const CString & strTarget, const CString & strLabel) const { str += _T("" "\n" "\n" " \n" " \n" "\n" "\n" " \n" " \n" " \n" "\n" "\n" " \n" "\n" "\n" " \n" "\n" "\n" " \n" "\n" "\n" " \n" "\n" "
\n" " \n" "
\n" " \n" " \n" "
\n"); // >>> $MAINT might want to change the font/style in the following str += _T("   \n" " \n" " "); str += strLabel; str += _T("\n" "    
\n" "
\n" "
\n" "
\n" "\n"); } // ------------------------------------------------------------------- // Writing to the new HTML page. Miscellaneous low-level pieces. // ------------------------------------------------------------------- // If the state name is missing or is simply "", return true. // This indicates a state that should never be overtly presented to the user as a choice. // Typically used in an informational node, this may describe a state that can be deduced with // 100% certainty from certain other node/state combinations. /* static */ bool CInfer::HideState(LPCTSTR szStateName) { if (szStateName && *szStateName && _tcscmp(szStateName, _T("") ) ) return false; return true; } // write a symbolic name (based on NID) to a string sz // INPUT nid - node ID // OUTPUT str - the string to which we write. // RETURNS true if successful // NOTE that this restores the "current" node when it is finished. // Alternative would be side effect of setting current node (by omitting nidOld), but that // would work strangely on "special" nodes (e.g. Service, Fail), which aren't in BNTS. bool CInfer::SymbolicFromNID(CString & str, NID nid) const { if (nid == nidProblemPage) { str= NODE_PROBLEM_ASK; return true; } if (nid == nidService) { str= NODE_SERVICE; return true; } if (nid == nidFailNode) { str= NODE_FAIL; return true; } if (nid == nidSniffedAllCausesNormalNode) { str= NODE_FAILALLCAUSESNORMAL; return true; } if (nid == nidImpossibleNode) { str= NODE_IMPOSSIBLE; return true; } if (nid == nidByeNode) { str= NODE_BYE; return true; } // if it's a "normal" node, this will fill in the name str= m_pTopic->GetNodeSymName(nid); return (!str.IsEmpty() ); } // append an HTML radio button to str // INPUT/OUTPUT str - the string to which we append // INPUT szName, szValue - For // INPUT szLabel - text to appear after the radio button but before a line break /*static*/ void CInfer::AppendRadioButtonCurrentNode( CString &str, LPCTSTR szName, LPCTSTR szValue, LPCTSTR szLabel, bool bChecked/*= false*/) { CString strTxt; if ( ! HideState(szLabel)) { if (RUNNING_LOCAL_TS()) str += "\n\n\n"; strTxt.Format(_T(" %s"), szName, szValue, bChecked ? _T("CHECKED") : _T(""), szLabel); str += strTxt; if (RUNNING_LOCAL_TS()) str += "\n\n\n"; else str += "\n
\n"; } } // This is different than other radio buttons because it // - has a different format for label szLabel. // - vanishes if bShowHistory is false and this button isn't CHECKED // - turns into a hidden field if bShowHistory is false and this button is CHECKED // - writes SNIFFED_ values as applicable...although that's not in this function: it's // handled in a separate call to AppendHiddenFieldSniffed() // JM 11/12/99 previously, we special-cased hidden states here. However, per 11/11/99 email // from John Locke, the only state we ever hide in the History Table (for v3.2) is the // Unknown/skipped state, and that is handled elsewhere. // INPUT/OUTPUT str - string to which we append the HTML for this button. // INPUT nid - NID of node // INPUT value - state // INPUT bSet - true ==> button is CHECKED // INPUT szctype - short name of the state // INPUT bShowHistory - see explanation a few lines above void CInfer::AppendRadioButtonVisited( CString &str, NID nid, UINT value, bool bSet, LPCTSTR szLabel, bool bShowHistory) const { CString strTxt; CString strSymbolic; SymbolicFromNID(strSymbolic, nid); if (bShowHistory) strTxt.Format(_T("%-16s \n"), strSymbolic, value, bSet ? _T(" CHECKED") : _T(""), szLabel); else if (bSet) strTxt.Format(_T("\n"), strSymbolic, value); str += strTxt; } // If this nid is an already sniffed node, then we append this fact as a // "hidden" value in the HTML in str. // For example, if a node with symbolic name FUBAR has been sniffed in state 1, // we will append "\n" // INPUT: string to have appended; node ID // OUTPUT: string with appended hidden field if node was sniffed // RETURN: true id string is appended void CInfer::AppendHiddenFieldSniffed(CString &str, NID nid) const { CString strSymbolic; UINT nSniffedNodes = m_SniffedStates.size(); SymbolicFromNID(strSymbolic, nid); for (UINT i = 0; i < nSniffedNodes; i++) { if (m_SniffedStates[i].nid() == nid) { // Do not have to check for state, as m_SniffedStates will // have only valid states (states, which are accepted by BNTS), // no 102 or -1 states // In case that this is manually sniffed cause node in abnormal state // (and we just re-submit previous page), we need not mention // this node as sniffed. if (!(IsManuallySniffedNode(nid) && m_SniffedStates[i].state() == 1 && m_pTopic->IsCauseNode(nid)) ) { CString strTxt; strTxt.Format(_T("\n"), C_SNIFFTAG, strSymbolic, m_SniffedStates[i].state()); str += strTxt; return; } } } } // Appends (to str) info conveying whether Automatic Sniffing is allowed. void CInfer::AddAllowAutomaticSniffingHiddenField(CString &str) const { CString strTxt; strTxt.Format(_T("\n"), C_ALLOW_AUTOMATIC_SNIFFING_NAME, C_ALLOW_AUTOMATIC_SNIFFING_CHECKED); str += strTxt; } // Radio buttons for currently recommended node // Each button will appear only if appropriate string property is defined // Accounts for multi-state or simple binary node. // INPUT nid - identifies a node of an appropriate type // INPUT/OUTPUT str - string to which we are appending to build HTML page we send back. // The detailed behavior of this function was changed at John Locke's request 11/30/98 for V3.0. // Then for v3.1, handling of H_ST_AB_TXT_STR, H_ST_NORM_TXT_STR removed 8/19/99 per request // from John Locke & Alex Sloley void CInfer::AppendCurrentRadioButtons(NID nid, CString & str) { CString strSymbolic; SymbolicFromNID(strSymbolic, nid); CString strPropLongName; // long name of property int nStates = m_pTopic->GetCountOfStates(nid); if (RUNNING_LOCAL_TS()) str += "\n"; for (IST state=0; state < nStates; state ++) { TCHAR szStateNumber[MAXBUF]; // buffer for _itot() CString strDisplayState = _itot( state, szStateNumber, 10 ); if (state == 1 && m_pTopic->IsCauseNode( nid )) strDisplayState = SZ_ST_WORKED; strPropLongName = _T(""); if (strPropLongName.IsEmpty()) // account for multistate node strPropLongName = m_pTopic->GetNodePropItemStr(nid, MUL_ST_LONG_NAME_STR, state); // if we're not past the end of states, append a button if (!strPropLongName.IsEmpty()) AppendRadioButtonCurrentNode(str, strSymbolic, strDisplayState, strPropLongName, // check state button if this state was sniffed m_SniffedRecommendation.state() == state ? true : false); }; // "unknown" state (e.g. "I want to skip this") strPropLongName = m_pTopic->GetNodePropItemStr(nid, H_ST_UKN_TXT_STR); if (!strPropLongName.IsEmpty()) AppendRadioButtonCurrentNode(str, strSymbolic, SZ_ST_UNKNOWN, strPropLongName); if (RUNNING_LOCAL_TS()) str += "
\n"; return; } // If we are showing the history table, place a localizable Full Name // (e.g."Printouts appear garbled") of problem and a hidden-data // field corresponding to this problem into str. // Otherwise, just the hidden data field void CInfer::CreateProblemVisitedText(CString & str, NID nidProblem, bool bShowHistory) { // This code is structured in pieces as sending all of these strings to a single // CString::Format() results in a program exception. Did some research into this // behavior but did not discover anything. RAB-981014. CString tmpStr; tmpStr.Format( _T("%s"), bShowHistory ? m_pTopic->GetNodeFullName(nidProblem) : _T("") ); str= tmpStr; tmpStr.Format( _T(""), m_pTopic->GetNodeSymName(nidProblem) ); str+= tmpStr; tmpStr.Format( _T("%s"), bShowHistory ? _T("") : _T("\n") ); str+= tmpStr; str+= _T("\n"); } // Append a NET property (for Belief Network as a whole, not for one // particular node) to str. // INPUT/OUTPUT str - string to append to // INPUT item - Property name // INPUT szFormat - string to format each successive line. Should contain one %s, otherwise // constant text. void CInfer::AppendMultilineNetProp(CString & str, LPCTSTR szPropName, LPCTSTR szFormat) { str += m_pTopic->GetMultilineNetProp(szPropName, szFormat); } // Like AppendMultilineNetProp, but for a NODE property item, for one particular node. // INPUT/OUTPUT str - string to append to // INPUT item - Property name // INPUT szFormat - string to format each successive line. Should contain one %s, otherwise // constant text. void CInfer::AppendMultilineNodeProp(CString & str, NID nid, LPCTSTR szPropName, LPCTSTR szFormat) { str += m_pTopic->GetMultilineNodeProp(nid, szPropName, szFormat); } // JSM V3.2 Wrapper for AppendMultilineNetProp to make it easier // to fill in the Net properties in HTMLFragments CString CInfer::ConvertNetProp(const CString &strNetPropName) { CString strNetPropVal; AppendMultilineNetProp(strNetPropVal,strNetPropName,"%s"); return strNetPropVal; } // If there is a pre-sniffed recommendation, remove it from the list & set m_SniffedRecommendation. void CInfer::IdentifyPresumptiveCause() { vector arrnidNoSequence; multimap mapSeqToNID; // Find all presumptive causes for (int i = 0; i < m_SniffedStates.size(); i++) { if (m_pTopic->IsCauseNode(m_SniffedStates[i].nid()) // cause node ... && m_SniffedStates[i].state() == 1) // ... that is sniffed in abnormal (1) state { if (IsManuallySniffedNode(m_SniffedStates[i].nid())) { // now we have manually sniffed cause node in abnormal state. // It means that we are re-submitting the page. We will set m_SniffedRecommendation // to this node, and return. m_SniffedRecommendation = CNodeStatePair(m_SniffedStates[i].nid(), 1 /*cause node abnormal state*/); return; } NID nid = m_SniffedStates[i].nid(); CString str = m_pTopic->GetNodePropItemStr(nid, H_NODE_CAUSE_SEQUENCE); try { if (str.IsEmpty()) arrnidNoSequence.push_back(nid); else { mapSeqToNID.insert(pair(_ttoi(str), nid)); } } 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 ); } } } // We want the first in sequence according to H_NODE_CAUSE_SEQUENCE numbering. // If nothing has a number, we settle for the (arbitrary) first in the array of // unnumbered Cause nodes. if (mapSeqToNID.size() > 0) m_SniffedRecommendation = CNodeStatePair( (mapSeqToNID.begin()->second), 1 /*cause node abnormal state*/); else if (arrnidNoSequence.size() > 0) m_SniffedRecommendation = CNodeStatePair( *(arrnidNoSequence.begin()), 1 /*cause node abnormal state*/); // now remove the matching nid from the incoming arrays if (m_SniffedRecommendation.nid() != nidNil) { for (i = 0; i < m_BasisForInference.size(); i++) { if (m_BasisForInference[i].nid() == m_SniffedRecommendation.nid()) { m_BasisForInference.erase(m_BasisForInference.begin() + i); break; } } for (i = 0; i < m_SniffedStates.size(); i++) { if (m_SniffedStates[i].nid() == m_SniffedRecommendation.nid()) { m_SniffedStates.erase(m_SniffedStates.begin() + i); break; } } for (i = 0; i < m_arrnidVisited.size(); i++) { if (m_arrnidVisited[i] == m_SniffedRecommendation.nid()) { m_arrnidVisited.erase(m_arrnidVisited.begin() + i); break; } } } } // return true if every Cause node in the topic is determined to be normal; // this would imply that there is nothing useful this topic can do for us. bool CInfer::AllCauseNodesNormal() { // for every node in this Belief Network (but taking action only on "cause" nodes) // see if each of these is known to be Normal for(int nid = 0; nid < m_pTopic->CNode(); nid++) { if (m_pTopic->IsCauseNode(nid)) { bool bFound=false; for (CBasisForInference::iterator p= m_SniffedStates.begin(); p != m_SniffedStates.end(); ++p) { if (p->nid() == nid) { if (p->state() != 0) // found a Cause node in an abnormal state (or skipped) return false; bFound = true; break; } } if (!bFound) // found a Cause node for which no state is set return false; } } return true; } // ------------------------------------------------------------- // Writing pieces of the new HTML page. This builds a structure to be used under HTI // control to represent the recommended node and the (visible or invisible) history table. // ------------------------------------------------------------- void CInfer::FillInHTMLFragments(CHTMLFragmentsTS &frag) { vectorarrnidPresumptiveCause; // First, a side effect: get the URL for the Online TS Start Over link / pseudo-button m_strStartOverLink = frag.GetStartOverLink(); // Then on to the main business at hand. In practice (at least as of 11/99) // bIncludesHistoryTable and bIncludesHiddenHistory are mutually exclusive, // but this class doesn't need that knowledge. const bool bIncludesHistoryTable = frag.IncludesHistoryTable(); const bool bIncludesHiddenHistory = frag.IncludesHiddenHistory(); { // JSM V3.2: convert the net properties in the HTML fragment // The HTI template may indicate that certain net properties are to be written // directly into the resulting page. We get a list of these properties and // fill in a structrue in frag to contain their values. CString strNetPropName; for(;frag.IterateNetProp(strNetPropName);) frag.SetNetProp(strNetPropName,ConvertNetProp(strNetPropName)); } { // JM V3.2 to handle sniffing correctly, must do this before history table: sniffing // on the fly (which happens in AppendCurrentNodeText()) could add to the history. CString strCurrentNode; AppendCurrentNodeText(strCurrentNode); frag.SetCurrentNodeText(strCurrentNode); } CString strHiddenHistory; if (m_nidProblem != nidNil) { CString strProblem; CreateProblemVisitedText(strProblem, m_nidProblem, frag.IncludesHistoryTable()); // OK V3.2 We use hidden field to save the value returned by the "AllowSniffing" // checkbox (on the problem page) and pass it to each subsequent page. // We effectively place this before the history table. if (m_pSniff) if (m_pSniff->GetAllowAutomaticSniffingPolicy()) AddAllowAutomaticSniffingHiddenField(strProblem); // Added for V3.2 sniffing // Not lovely, but this is where we insert sniffed presumptive causes (as hidden // fields). // >>> $MAINT Once we integrate with a launcher, this may require // further thought: what if we sniff presumptive causes before we have an // identified problem? Where do we put those hidden fields? for (UINT i=0; iIsCauseNode(nid) && stateSet == 1) { // This is a cause node sniffed as abnormal, to be presented eventually // as a "presumptive" cause. All we put in the History table is hidden AppendStateText(strProblem, nid, 1, true, false, false, stateSet); try { arrnidPresumptiveCause.push_back(nid); } 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 ); } } } if (bIncludesHistoryTable) frag.SetProblemText(strProblem); if (bIncludesHiddenHistory) strHiddenHistory = strProblem; } UINT nVisitedNodes = m_arrnidVisited.size(); // iVisited incremented for every visited node, iHistory only for a subset: // if we have a visible History table, iHistory provides an index of nodes visible // to the end user. If not, iHistory is a harmless irrelevance for (UINT iVisited=0, iHistory=0; iVisitedGetCountOfStates(nid); int stateSet = -1; if (IsSkipped(nid)) { // "skipped" node. // instead of ST_UNKNOWN (==102), stateSet uses the number immediately // past the last valid state of this node. Most nodes have only states // 0, 1, and 102 so typically stateSet is set = 2, but a multistate // node can use a different number stateSet = nStates; } else { UINT nSetNodes = m_BasisForInference.size(); for (UINT ii = 0; ii < nSetNodes; ii++) if (m_BasisForInference[ii].nid() == nid) { stateSet = m_BasisForInference[ii].state(); break; } } // The following test added for V3.2 sniffing // Weed out cause node sniffed as abnormal, to be presented eventually // as a "presumptive" cause. Handled above as a hidden field. if (find(arrnidPresumptiveCause.begin(), arrnidPresumptiveCause.end(), nid) != arrnidPresumptiveCause.end()) { // cause node sniffed as abnormal } else { if (bIncludesHistoryTable) { CString strVisitedNode; AppendVisitedNodeText(strVisitedNode, nid, true); frag.PushBackVisitedNodeText(strVisitedNode); } for (UINT iState=0; iState <= nStates; iState++) { if (bIncludesHistoryTable) { CString strState; AppendStateText(strState, nid, iState, iState == stateSet, iState == nStates, true, stateSet); // If we are processing last state, and we need to attach // hidden field for this node as sniffed one // (if it ts really sniffed) if (iState == nStates) AppendHiddenFieldSniffed(strState, nid); // We need not have empty entry in CHTMLFragment's array, // describing history table, so by applying "numPresumptiveCauseNodesEncounered" // we make this array continuous frag.PushBackStateText(iHistory, strState); } if (bIncludesHiddenHistory) { AppendStateText(strHiddenHistory, nid, iState, iState == stateSet, iState == nStates, false, stateSet); // same as in case of visible history table applies. if (iState == nStates) AppendHiddenFieldSniffed(strHiddenHistory, nid); } } if (bIncludesHistoryTable) { // Check if we need to mark this as visibly sniffed. UINT nSniffedNodes = m_SniffedStates.size(); for (UINT i = 0; i < nSniffedNodes; i++) { if (m_SniffedStates[i].nid() == nid) { // mark it visibly as sniffed CString strState; AppendHistTableSniffedText( strState ); frag.PushBackStateText(iHistory, strState); break; } } } iHistory++; } } if (frag.IncludesHiddenHistory()) frag.SetHiddenHistoryText(strHiddenHistory); frag.SetSuccessBool(m_bDone); } // Append the text for the current (recommended) node to str void CInfer::AppendCurrentNodeText(CString & str) { CString strSave = str; if (m_nidProblem == nidNil) // show first page (radio-button list of possible problems) AppendProblemPage(str); else if (m_bDone && !ManuallySniffedNodeExists()) AppendNIDPage(nidByeNode, str); else if ( m_SniffedRecommendation.nid() != nidNil ) // we already have a recommendation, presumably from a sniffer AppendNIDPage(m_SniffedRecommendation.nid(), str); else { // sniff/resniff all, as needed if (RUNNING_LOCAL_TS()) { // Before we mess with m_BasisForInference, determine if the only node with a // state is the problem node // [BC - 20010301] - Added check for size of skipped node count when setting // bHaveOnlyProblem here. This catches case where user selects to skip first // node presented, when that node is sniffed in abnormal state. bool bHaveOnlyProblem = (m_BasisForInference.size() == 1) && (m_arrnidSkipped.size() == 0); if (m_pSniff) { long nExplicitlySetByUser = 0; CBasisForInference arrManuallySniffed; // can contain max 1 element; // used to prevent resniffing // of already sniffed node. // We need arrayOrderRestorer in order to make sure that when sniffed // nodes are first removed from the array of visited nodes, then restored, // we maintain the same sequence in which nodes were visited in the first // place. This order is important in our caching strategy and also provides // a sense of consistency for the end user. CArrayOrderRestorer arrayOrderRestorer(m_arrnidVisited); if (ManuallySniffedNodeExists()) { arrManuallySniffed.push_back(m_SniffedStates[m_SniffedStates.size()-1]); } // Remove all sniffed nodes from m_BasisForInference m_BasisForInference -= m_SniffedStates; // remove m_SniffedStates from m_arrnidVisited m_arrnidVisited -= m_SniffedStates; nExplicitlySetByUser = m_arrnidVisited.size(); if (bHaveOnlyProblem) { // sniff all since we're in problem page m_pSniff->SniffAll(m_SniffedStates); } else { CBasisForInference arrSniffed; // resniff all except recently sniffed manually (if any) arrSniffed = m_SniffedStates; arrSniffed -= arrManuallySniffed; m_pSniff->Resniff(arrSniffed); arrSniffed += arrManuallySniffed; m_SniffedStates = arrSniffed; } // add updated m_SniffedStates to m_arrnidVisited m_arrnidVisited += m_SniffedStates; arrayOrderRestorer.Restore(nExplicitlySetByUser, m_arrnidVisited); // Add all sniffed nodes into m_BasisForInference m_BasisForInference += m_SniffedStates; if (bHaveOnlyProblem && AllCauseNodesNormal()) { // We just sniffed at startup & we already know all Cause nodes // are in their normal states. There is absolutely nothing this // troubleshooting topic can do to help this user. AppendSniffAllCausesNormalPage(str); return; } } // in case that we do not have sniffed recommendation from manual sniffing if (m_SniffedRecommendation.nid() == nidNil) { // Did we get a presumptive cause out of that? IdentifyPresumptiveCause(); } if ( m_SniffedRecommendation.nid() != nidNil ) { AppendNIDPage(m_SniffedRecommendation.nid(), str); return; } } bool bSniffSucceeded = true; while (bSniffSucceeded) { IST state = -1; NID nidNew = nidNil; GetRecommendations(); if (!m_bRecOK) { str = strSave; AppendImpossiblePage(str); return; } else if (m_Recommendations.empty()) { str = strSave; AppendNIDPage(nidFailNode, str); return; } else // Have Recommendations { // Find a recommendation from list of recommendations that is // not in the skip list. This is normally the first node in the // list. int n = m_Recommendations.size(); for (UINT i=0; iSniffNode(nidNew, &state); if (bSniffSucceeded) { // if it's a cause node and was sniffed as abnormal if (m_pTopic->IsCauseNode(nidNew) && state == 1) { // Display this page as a presumptive cause. m_SniffedRecommendation = CNodeStatePair( nidNew, state ); str = strSave; AppendNIDPage(nidNew, str); return; } CNodeStatePair nodestateNew(nidNew, state); try { m_SniffedStates.push_back(nodestateNew); m_BasisForInference.push_back(nodestateNew); m_arrnidVisited.push_back(nidNew); } 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 ); } } } } } // Write radio buttons describing what was decided by user in a previous node. Part // of "the table" (aka "the visited node table" or "table of previous responses"). // // Note that Cause nodes are specially handled. On a cause node: // state 0 = "no, this didn't fix it" // state 1 = This wasn't OK, so we have a diagnosis. In that case, we // wouldn't be displaying these radio buttons. // We don't want the user selecting that value from the history table. // We append this only if it's been sniffed and must be presented as a presumptive // cause, and even then we always append it "hidden" // state 2 = "skipped" // // In other words, on a cause node, the only possibilities we offer to the user through // a visible history table are state 0 () and "skip". // // In the case where a Cause node has been sniffed abnormal, THE CALLING ROUTINE is // responsible to call this only for the abnormal state. Otherwise, call for all states. // // OUTPUT str - string to which we append // INPUT nid node of which this is a state // INPUT state state number; for ST_UNKNOWN, this is the count of states, not 102 // INPUT bSet true = this is the current state of this node // INPUT bSkipped true = this is the "skipped" state, not a normal node state known to BNTS // INPUT bShowHistory true = we are showing a history table, false = history is stored // invisibly in the HTML. void CInfer::AppendStateText(CString & str, NID nid, UINT state, bool bSet, bool bSkipped, bool bShowHistory, int nStateSet) { // Check if this selection worked. // If so only display the "it worked" text in the history table. if (m_pTopic->IsCauseNode(nid) && nStateSet == ST_WORKED) { if (state == 1) // it is presumptive cause ... AppendRadioButtonVisited( str, nid, state, true, m_pTopic->GetStateName(nid, state), bShowHistory); return; } if (bSkipped) { CString strUnknownLongName = m_pTopic->GetNodePropItemStr(nid, H_ST_UKN_TXT_STR); // The following test is per 11/11/99 email from John Locke if (HideState(strUnknownLongName)) return; // totally omit Unknown from history table: Unknown cannot be // selected for this node. // Previous calls to AppendStateText have looped through the states known to BNTS; // now we handle "skipped", which is a concept BNTS lacks. CString strUnknown; CreateUnknownButtonText(strUnknown); AppendRadioButtonVisited(str, nid, ST_UNKNOWN, bSet, strUnknown, bShowHistory); return; } if (m_pTopic->IsCauseNode(nid) && state == 1) // it is presumptive cause ... { if (IsInSniffedArray(nid)) //... taken from sniffed array, but NOT current node. { // We are about to add entry for presumptive cause node. // Actually, since this is sniffed node, we need to have two entries: // one hidden fiels with node name and one hidden field with node name // prefixed by "SNIFFED" prefix. if (bSet) { // "bSet" will always set to true, as sniffed presumptive cause will never // be visible. AppendRadioButtonVisited(str, nid, state, bSet, m_pTopic->GetStateName(nid, state), false); AppendHiddenFieldSniffed(str, nid); } } return; } AppendRadioButtonVisited(str, nid, state, bSet, m_pTopic->GetStateName(nid, state), bShowHistory); return; } // This is used to get the name of a node that has already been visited (for the // history table). // INPUT nid - node ID of desired node // OUTPUT str - The "full name" of the node is appended to this, something like // "Disable IBM AntiVirus" or "Make all paths less than 66 characters" // If its value was sniffed, we append the appropriate string to mark it visibly // as sniffed (typically, just "SNIFFED"). // INPUT bShowHistory // If !bShowHistory, no appending: no need to show full name in a hidden table. // Symbolic name will be written in a hidden field. // Note that our CString, unlike MFC's, won't throw an exception on += out of memory // RETURNS true if node number exists bool CInfer::AppendVisitedNodeText(CString & str, NID nid, bool bShowHistory) const { if (!bShowHistory) return true; CString strTemp = m_pTopic->GetNodeFullName(nid); if ( !strTemp.IsEmpty() ) { str += strTemp; return true; } else return false; } // ------------------------------------------------------------------- // Writing to the new HTML page. Representing the recommended node. // This is what is often called the page, although it is really only part of // the body of the HTML page, along with history. // ------------------------------------------------------------------- // AppendImpossiblePage: Gets the body of text that is // displayed when the network is in an unreliable state. void CInfer::AppendImpossiblePage(CString & str) { CString strHeader, strText; strHeader = m_pTopic->GetMultilineNetProp(HTK_IMPOSSIBLE_HEADER, _T("

%s

\n")); strText = m_pTopic->GetMultilineNetProp(HTK_IMPOSSIBLE_TEXT , _T("%s ")); if (!strHeader.IsEmpty() && !strText.IsEmpty()) { str = strHeader + strText + _T("
\n
\n"); } else { strHeader = m_pTopic->GetMultilineNetProp(HX_FAIL_HD_STR , _T("

%s

\n")); strText = m_pTopic->GetMultilineNetProp(HX_FAIL_TXT_STR , _T("%s ")); if (!strHeader.IsEmpty() && !strText.IsEmpty()) { str = strHeader + strText + _T("
\n
\n"); } else { str = SZ_I_NO_RESULT_PAGE; } } // Make a radio button with name = NODE_IMPOSSIBLE & value = SZ_ST_WORKED CString strTemp = m_pTopic->GetNetPropItemStr(HX_IMPOSSIBLE_NORM_STR); if (strTemp.IsEmpty()) // fall back on Fail node's property strTemp = m_pTopic->GetNetPropItemStr(HX_FAIL_NORM_STR); if (!strTemp.IsEmpty()) { if (RUNNING_LOCAL_TS()) str += "\n"; AppendRadioButtonCurrentNode(str, NODE_IMPOSSIBLE, SZ_ST_WORKED, strTemp); if (RUNNING_LOCAL_TS()) str += "
\n"; } str += _T("

"); AppendActionButtons (str, k_BtnNext|k_BtnBack|k_BtnStartOver); } // AppendSniffAllCausesNormalPage: Gets the body of text that is displayed when sniffing // on startup detects that all Cause nodes are in their Normal states. void CInfer::AppendSniffAllCausesNormalPage(CString & str) { CString strHeader, strText; strHeader = m_pTopic->GetMultilineNetProp(HTK_SNIFF_FAIL_HEADER, _T("

%s

\n")); strText = m_pTopic->GetMultilineNetProp(HTK_SNIFF_FAIL_TEXT , _T("%s ")); if (!strHeader.IsEmpty() && !strText.IsEmpty()) { str = strHeader + strText + _T("
\n
\n"); } else { strHeader = m_pTopic->GetMultilineNetProp(HX_FAIL_HD_STR , _T("

%s

\n")); strText = m_pTopic->GetMultilineNetProp(HX_FAIL_TXT_STR , _T("%s ")); if (!strHeader.IsEmpty() && !strText.IsEmpty()) { str = strHeader + strText + _T("
\n
\n"); } else { str = SZ_I_NO_RESULT_PAGE; } } // Make a radio button with name = NODE_FAILALLCAUSESNORMAL & value = SZ_ST_WORKED CString strTemp = m_pTopic->GetNetPropItemStr(HX_SNIFF_FAIL_NORM); if (strTemp.IsEmpty()) // fall back on Fail node's property strTemp = m_pTopic->GetNetPropItemStr(HX_FAIL_NORM_STR); if (!strTemp.IsEmpty()) { if (RUNNING_LOCAL_TS()) str += "\n"; AppendRadioButtonCurrentNode(str, NODE_FAILALLCAUSESNORMAL, SZ_ST_WORKED, strTemp); if (RUNNING_LOCAL_TS()) str += "
\n"; } str += _T("

"); AppendActionButtons (str, k_BtnNext|k_BtnBack|k_BtnStartOver); } // OUTPUT str - string to which we are appending to build HTML page we send back. // Append (to str) a group of radio buttons, one for each "problem" node in the Belief Network void CInfer::AppendProblemPage(CString & str) { CString strTemp; m_nidSelected = nidProblemPage; // text to precede list of problems. Introduced 8/98 for version 3.0. // space after %s in next line: see note at head of file str += m_pTopic->GetMultilineNetProp(H_PROB_PAGE_TXT_STR, _T("%s ")); // write problem header. This is text written as HTML

. strTemp.Format(_T("

%s

\n\n"), m_pTopic->GetNetPropItemStr(H_PROB_HD_STR)); str += strTemp; // Write a comment in the HTML in service of automated test program str += _T("\n"); //str += "
"; if (RUNNING_LOCAL_TS()) str += "\n"; AppendProblemNodes(str); if (RUNNING_LOCAL_TS()) str += "\n
\n"; if (m_pTopic->UsesSniffer()) { AppendActionButtons (str, k_BtnNext|k_BtnPPSniffing); } else { AppendActionButtons (str, k_BtnNext); } return; } // Helper routine for AppendProblemPage void CInfer::AppendProblemNodes(CString & str) { vector arrnidNoSequence; multimap mapSeqToNID; // for every node in this Belief Network (but taking action only on "problem" nodes) // put this nid in arrnidNoSequence if it has no sequence number or mapSeqToNID if it // has one. for(int nid = 0; nid < m_pTopic->CNode(); nid++) { if (m_pTopic->IsProblemNode(nid)) { CString strSpecial = m_pTopic->GetNodePropItemStr(nid, H_PROB_SPECIAL); // if it's not marked as a "hidden" problem, we'll want it in the problem page if (strSpecial.CompareNoCase(_T("hide")) != 0) { CString str = m_pTopic->GetNodePropItemStr(nid, H_NODE_PROB_SEQUENCE); try { if (str.IsEmpty()) arrnidNoSequence.push_back(nid); else mapSeqToNID.insert(pair(_ttoi(str), nid)); } 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 ); } } } } for (multimap::const_iterator ppair=mapSeqToNID.begin(); ppair != mapSeqToNID.end(); ppair++) { // Create a radio button with "ProblemAsk" as its name & this problem // as its value AppendRadioButtonCurrentNode( str, NODE_PROBLEM_ASK, m_pTopic->GetNodeSymName(ppair->second), m_pTopic->GetNodePropItemStr(ppair->second, H_PROB_TXT_STR)); } for (vector::const_iterator pnid=arrnidNoSequence.begin(); pnid != arrnidNoSequence.end(); pnid++) { // Create a radio button with "ProblemAsk" as its name & this problem // as its value AppendRadioButtonCurrentNode( str, NODE_PROBLEM_ASK, m_pTopic->GetNodeSymName(*pnid), m_pTopic->GetNodePropItemStr(*pnid, H_PROB_TXT_STR)); } } // Append this network's "BYE" page to str // OUTPUT str - string to append to void CInfer::AppendByeMsg(CString & str) { str += _T("\n"); // Write a comment in the HTML in service of automated test program str += _T("\n"); // Write this troubleshooter's "Bye" header and text // space after %s in next 2 lines: see note at head of file AppendMultilineNetProp(str, HX_BYE_HD_STR, _T("

%s

\n")); AppendMultilineNetProp(str, HX_BYE_TXT_STR, _T("%s ")); str += _T("

\n"); AppendActionButtons (str, k_BtnBack|k_BtnStartOver); return; } // Append this network's "FAIL" page to str // OUTPUT str - string to append to void CInfer::AppendFailMsg(CString & str) { str += _T("\n"); // Write a comment in the HTML in service of automated test program str += _T("\n"); // Write this topic's "Fail" header and text // space after %s in next 2 lines: see note at head of file AppendMultilineNetProp(str, HX_FAIL_HD_STR, _T("

%s

\n")); AppendMultilineNetProp(str, HX_FAIL_TXT_STR, _T("%s ")); str += _T("
\n
\n"); // Make a radio button with name = NODE_FAIL & value = SZ_ST_WORKED CString strTemp = m_pTopic->GetNetPropItemStr(HX_FAIL_NORM_STR); if (!strTemp.IsEmpty()) { if (RUNNING_LOCAL_TS()) str += "\n"; AppendRadioButtonCurrentNode(str, NODE_FAIL, SZ_ST_WORKED, strTemp); if (RUNNING_LOCAL_TS()) str += "
\n"; } AppendActionButtons (str, k_BtnNext|k_BtnBack|k_BtnStartOver); return; } // Append content of the "service" page to str (Offers 2 possibilities: seek help elsewhere // or go back and try something you skipped) // OUTPUT str - string to append to void CInfer::AppendServiceMsg(CString & str) { CString strTemp; str += _T("\n"); str += _T("\n"); // Write a comment in the HTML in service of automated test program str += _T("\n"); // Write this troubleshooter's "Service" header and text // space after %s in next 2 lines: see note at head of file AppendMultilineNetProp(str, HX_SER_HD_STR, _T("

%s

\n")); AppendMultilineNetProp(str, HX_SER_TXT_STR, _T("%s ")); str += _T("
\n
\n"); if (RUNNING_LOCAL_TS()) str += "\n"; // Make a radio button with name = Service & value = SZ_ST_WORKED // Typical text is "I will try to get help elsewhere."; strTemp = m_pTopic->GetNetPropItemStr(HX_SER_NORM_STR); if (!strTemp.IsEmpty()) AppendRadioButtonCurrentNode(str, NODE_SERVICE, SZ_ST_WORKED, strTemp); // Make a radio button with name = Service & value = SZ_ST_ANY // Typical text is "Retry any steps that I have skipped." strTemp = m_pTopic->GetNetPropItemStr(HX_SER_AB_STR); if (!strTemp.IsEmpty()) AppendRadioButtonCurrentNode(str, NODE_SERVICE, SZ_ST_ANY, strTemp); if (RUNNING_LOCAL_TS()) str += "
\n"; str += _T("

"); AppendActionButtons (str, k_BtnNext|k_BtnBack|k_BtnStartOver); return; } // Depending on the value of nid, this fn can build // - a BYE page // - a FAIL page // - a SERVICE page // - a page for a normal node (fixable/observable, fixable/unobservable, unfixable, or // informational). // If none of these cases apply, returns with no action taken // INPUT nid - ID of a node // OUTPUT str - string to append to void CInfer::AppendNIDPage(NID nid, CString & str) { CString strTxt; m_nidSelected = nid; if (nid == nidByeNode) AppendByeMsg(str); else if (nid == nidFailNode) AppendFailMsg(str); else if (nid == nidSniffedAllCausesNormalNode) AppendSniffAllCausesNormalPage(str); else if (nid == nidService) AppendServiceMsg(str); else if (m_pTopic->IsValidNID(nid)) { bool bShowManualSniffingButton = false; if (m_pSniff) if (nid != m_SniffedRecommendation.nid()) // we're NOT showing sniffed node. bShowManualSniffingButton = m_pSniff->GetSniffController()->AllowManualSniffing(nid); // Write a comment in the HTML in service of automated test program str += _T("\n"); // Write this node's header & text // space after %s in next several lines: see note at head of file AppendMultilineNodeProp(str, nid, H_NODE_HD_STR, _T("

%s

\n")); if (bShowManualSniffingButton) AppendMultilineNodeProp(str, nid, H_NODE_MANUAL_SNIFF_TEXT, _T("%s ")); if (m_SniffedRecommendation.nid() == nid) { CString tmp; AppendMultilineNodeProp(tmp, nid, H_NODE_DCT_STR, _T("%s ")); if (tmp.IsEmpty()) AppendMultilineNodeProp(str, nid, H_NODE_TXT_STR, _T("%s ")); else str += tmp; } else { AppendMultilineNodeProp(str, nid, H_NODE_TXT_STR, _T("%s ")); } str += _T("\n
\n
\n"); // Write appropriate radio buttons depending on what kind of node it is. if (m_pTopic->IsCauseNode(nid) || m_pTopic->IsInformationalNode(nid)) AppendCurrentRadioButtons(nid, str); AppendActionButtons ( str, k_BtnNext|k_BtnBack|k_BtnStartOver|(bShowManualSniffingButton ? k_BtnManualSniffing : 0), nid); } // else nothing we can do with this return; } // ------------------------------------------------------------------- // BES // ------------------------------------------------------------------- // Historically: // Returns true if we are supposed to show the full BES page (& let the user edit the // search string) vs. extracting the search string & starting the search without // any possible user intervention // However, we no longer offer that option as of 981021. bool CInfer::ShowFullBES() { return false; } // returns true in the circumstances where we wish to show a Back End Search bool CInfer::TimeForBES() { return (m_pTopic->HasBES() && m_bUseBackEndRedirection); } // If it is time to do a Back End Search redirection, append the "redirection" string // to str and return true // Otherwise, return false // str should represent the header of an HTML page. // For browsers which support redirection, this is how we overide service node (or fail node) // when BES is present bool CInfer::AppendBESRedirection(CString & str) { if (m_pTopic->HasBES() && TimeForBES() && !ShowFullBES() && !m_strEncodedForm.IsEmpty()) { str += _T("Location: "); str += m_strEncodedForm; str += _T("\r\n"); return( true ); } return false; } // Append HTML representing BES to OUTPUT str and build m_strEncodedForm, // This is a distinct new algorithm in Ver 3.0, replacing the old "word list" approach. void CInfer::OutputBackend(CString & str) { vectorarrstrSearch; int nNodesInBasis = m_BasisForInference.size(); for (int i = 0; iGetNodePropItemStr(nid, H_NODE_NORM_SRCH_STR); else if (state == 1) strSearchState = m_pTopic->GetNodePropItemStr(nid, H_NODE_AB_SRCH_STR); else strSearchState = _T(""); if (strSearchState.IsEmpty()) // multistate node strSearchState = m_pTopic->GetNodePropItemStr(nid, MUL_ST_SRCH_STR, state); if (! strSearchState.IsEmpty()) { try { arrstrSearch.push_back(strSearchState); } 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 ); } } } // Build the full BES page CString strRaw; m_pTopic->GenerateBES(arrstrSearch, m_strEncodedForm, strRaw); str += strRaw; } // ------------------------------------------------------------- // Logging // ------------------------------------------------------------- // Return NID of page ultimately selected. If no such page, returns nidNil. NID CInfer::NIDSelected() const { return m_nidSelected; } // ------------------------------------------------------------- // Effectively, a method on m_arrnidSkipped // ------------------------------------------------------------- // INPUT nid // RETURNS true if nid is node in the "skip list" (ST_UNKNOWN, "Try something else"). bool CInfer::IsSkipped(NID nid) const { vector::const_iterator itBegin= m_arrnidSkipped.begin(); vector::const_iterator itEnd= m_arrnidSkipped.end(); return (find(itBegin, itEnd, nid) != itEnd); } // ------------------------------------------------------------- // Buttons // ------------------------------------------------------------- // appends only clause void CInfer::AppendNextButton(CString & str) const { str += SZ_INPUT_TAG_NEXT; // _T(""); } // For local TS, appends only clause // For Online TS, must build a pseudo button. void CInfer::AppendStartOverButton(CString & str) const { if (RUNNING_LOCAL_TS()) { str += SZ_INPUT_TAG_STARTOVER; // _T(""); } else { // Added for V3.2 CString strLabel; // visible label for pseudo button AppendStartOverButtonText(strLabel); AppendLinkAsButton(str, m_strStartOverLink, strLabel); } } // appends only clause void CInfer::AppendBackButton(CString & str) const { if (RUNNING_LOCAL_TS()) { str += SZ_INPUT_TAG_BACK; // _T(""); } } // AppendManualSniffButton will generate script something like this, but this // comment is not being carefully maintained, so see actual code for details. ///////////////////////////////////////////////////////////////////////////// // function sniffManually() { // // var stateSniffed = parent.t3.PerformSniffingJS("NodeName", "", "");// // // // if(stateSniffed == -1) { // // stateSniffed = parent.t3.PerformSniffingVB("NodeName", "", "");// // } // // // // if(stateSniffed == -1) { // // alert("Could not sniff this node"); // // } else { // // if(stateSniffed > NumOfStates) { // // alert("Could not sniff this node"); // // } else { // // /////////////////////////////////////////////////////// // // IF IS CAUSE NODE: // // if (stateSniffed == 1) // // document.all.Sniffed_NodeName.value = 101; // // else // // document.all.Sniffed_NodeName.value = stateSniffed; // // /////////////////////////////////////////////////////// // // IF IS NOT CAUSE NODE: // // document.all.Sniffed_NodeName.value = stateSniffed; // // /////////////////////////////////////////////////////// // // document.all.NodeState[stateSniffed].checked = true; // // document.ButtonForm.onsubmit(); // // } // // } // // } // ///////////////////////////////////////////////////////////////////////////// void CInfer::AppendManualSniffButton(CString & str, NID nid) const { if (RUNNING_LOCAL_TS()) { CString strNodeName; CString strTmp; SymbolicFromNID(strNodeName, nid); bool bIsCause = m_pTopic->IsCauseNode(nid); str += _T( "\n\n\n\n"); str += _T( "\n"); str += _T( "\n"); str += _T( "\n"); } } // appends only clause void CInfer::AppendPPSnifferButton(CString & str) const { str += SZ_INPUT_TAG_SNIFFER; // _T(""); } void CInfer::AppendActionButtons(CString & str, ActionButtonSet btns, NID nid /*=-1*/) const { // Online TS's Start Over "button" is actually a link, and will implicitly // start a new line unless we do something about it. bool bGenerateTable = (!RUNNING_LOCAL_TS() && (btns & k_BtnStartOver)); if (bGenerateTable) str += _T("
"); if (btns & k_BtnNext) { AppendNextButton(str); str += _T("\n"); } if (btns & k_BtnBack) { AppendBackButton(str); str += _T("\n"); } if (bGenerateTable) str += _T(""); if (btns & k_BtnStartOver) { AppendStartOverButton(str); str += _T("\n"); } if (bGenerateTable) str += _T(""); if (btns & k_BtnPPSniffing) { AppendPPSnifferButton(str); str += _T("\n"); } if ((btns & k_BtnManualSniffing) && nid != -1) { AppendManualSniffButton(str, nid); str += _T("\n"); } if (bGenerateTable) str += _T("
"); str += _T("

"); } // ------------------------------------------------------------- // MISCELLANY // ------------------------------------------------------------- // RETURN true for cause (vs. informational or problem) node. // Note that a cause may be either a fixable node or an "unfixable" node which // "can be fixed with infinite effort" /* static */ bool CInfer::IsCause (ESTDLBL lbl) { return (lbl == ESTDLBL_fixobs || lbl == ESTDLBL_fixunobs || lbl == ESTDLBL_unfix); } // This code can take a previously skipped node and bring it back again as a recommendation. // It is relevant only if the user received the service node in the previous call // to the DLL and now wants to see if there is "Anything Else I Can Try". // // This code will remove the first node from the skip list so that it may be delivered to // the user again. // // Of course, m_arrnidSkipped, m_arrnidVisited must be filled in before this is called. // void CInfer::RecycleSkippedNode() { // Only should take effect once per instance of this object, because peels the first // entry off of m_arrnidSkipped. We guarantee that with the following: if (m_bRecyclingInitialized) return; m_bRecyclingInitialized = true; // Only relevant if the query asks for a previously skipped node brought back again // as a recommendation. if (!m_bRecycleSkippedNode) return; // This is a safety check to bail out if there are no skipped nodes. // This would be a bogus query, because the Service Node should only have been // offered if there were skipped recommendations to try. if (m_arrnidSkipped.empty()) { m_bRecycleSkippedNode = false; return; } // OK,now down to business. // Get a value for m_nidRecycled from the first item skipped m_nidRecycled = m_arrnidSkipped.front(); // Remove skipped item from skip table m_arrnidSkipped.erase(m_arrnidSkipped.begin()); // Fix table of nodes that will be placed into the output table // to not include the first node skipped vector::const_iterator itnidBegin = m_arrnidVisited.begin(); vector::const_iterator itnidEnd = m_arrnidVisited.end(); vector::const_iterator itnidAnythingElse = find(itnidBegin, itnidEnd, m_nidRecycled); if (itnidAnythingElse != itnidEnd) m_arrnidVisited.erase( const_cast::iterator>(itnidAnythingElse) ); } bool CInfer::ManuallySniffedNodeExists() const { // If last element in m_BasisForInference is sniffed, // it means, that this element was set by manual sniffing // function. if (m_BasisForInference.size() && m_SniffedStates.size()) return m_bLastSniffedManually; return false; } bool CInfer::IsManuallySniffedNode(NID nid) const { if (ManuallySniffedNodeExists()) return nid == m_SniffedStates[m_SniffedStates.size()-1].nid(); return false; } void CInfer::SetLastSniffedManually(bool set) { m_bLastSniffedManually = set; } // ------------------------------------------------------------------- // CInfer::CArrayOrderRestorer implementation // ------------------------------------------------------------------- // // CInfer::CArrayOrderRestorer exists so that after re-sniffing, we can restore an array // of visited nodes to its original order, as saved in m_arrInitial. // // INPUT: nBaseLength = number of elements in fixed locations at head of array, which will // never be moved (typically nodes explicitly set by user rather than sniffed). // INPUT/OUTPUT: arrToRestore = array to restore: input dictates content of output, but // (beyond nBaseLength) does not dictate the order. Order comes from m_arrInitial. // OUTPUT: arrToRestore = array with restored order // RETURN: true if successful bool CInfer::CArrayOrderRestorer::Restore(long nBaseLength, vector& arrToRestore) { if (nBaseLength > arrToRestore.size()) return false; long i; vector::iterator i_base; vector::iterator i_additional; vector arrBase; vector arrAdditional; try { for (i = 0; i < nBaseLength; i++) arrBase.push_back(arrToRestore[i]); for (i = nBaseLength; i < arrToRestore.size(); i++) arrAdditional.push_back(arrToRestore[i]); } 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 ); } arrToRestore.clear(); for (i = 0, i_base = arrBase.begin(); i < m_arrInitial.size(); i++) { if (arrBase.end() != find(arrBase.begin(), arrBase.end(), m_arrInitial[i])) { if (i_base != arrBase.end()) i_base++; } else if (arrAdditional.end() != (i_additional = find(arrAdditional.begin(), arrAdditional.end(), m_arrInitial[i]))) { i_base = arrBase.insert(i_base, m_arrInitial[i]); i_base++; arrAdditional.erase(i_additional); } } arrToRestore = arrBase; try { for (i = 0; i < arrAdditional.size(); i++) arrToRestore.push_back(arrAdditional[i]); } 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 true; }