//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // // File: toc.cpp // Author: Donald Drake // Purpose: Implements classes to support the table of contents #include "header.h" #include "stdio.h" #include "string.h" #ifdef HHCTRL #include "parserhh.h" #else #include "windows.h" #include "parser.h" #endif #include "collect.h" #include "hhtypes.h" #include "wwheel.h" #include "toc.h" #include "fts.h" #include "subfile.h" #include "fs.h" #include "sysnames.h" #include "highlite.h" #include "hhfinder.h" #include "csubset.h" #include "hherror.h" // NormalizeUrlInPlace #include "util.h" #include "subset.h" // typed -- just use ANSI for now #undef _tcsicmp #undef _tcstok #define _tcsicmp strcmpi #define _tcstok StrToken #ifdef _DEBUG #undef THIS_FILE static const char THIS_FILE[] = __FILE__; #endif // Persist keys. No need for these to be localized so I place them here. // const char g_szFTSKey[] = "ssv1\\FTS"; const char g_szIndexKey[] = "ssv1\\Index"; const char g_szTOCKey[] = "ssv1\\TOC"; const char g_szUDSKey[] = "ssv2\\UDS"; // User Defined Subsets //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // global helper functions CExCollection* g_pCurrentCollection = NULL; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // CExCollection implementation CExCollection::CExCollection(CHmData* phmData, const CHAR* pszFile, BOOL bSingleTitle) { m_pstate = new CState(pszFile); m_phmData = phmData; m_bSingleTitle = bSingleTitle; m_pHeadTitles = NULL; m_csFile = pszFile; m_pFullTextSearch = NULL; m_pSearchHighlight = NULL; m_pDatabase = NULL; m_szWordWheelPathname = NULL; m_dwCurrSlot = 0; m_pCurrTitle = NULL; m_pSubSets = NULL; m_pMasterTitle = NULL ; // HH BUG 2428: Always initialize your variables!!! m_dwLastSlot = 0; m_pSSList = NULL; for (int i = 0; i < MAX_OPEN_TITLES; i++) m_MaxOpenTitles[i] = NULL; if (! bSingleTitle ) { // // If this is ever not the case then we have a situation where we are trying to init more than a single // collection. This is a very bad thing and must be avoided. // ASSERT(g_pCurrentCollection == NULL); g_pCurrentCollection = NULL; } m_pCSlt = NULL; } CExCollection::~CExCollection() { if ( m_phmData->m_sysflags.fDoSS && m_pSSList ) { m_pSSList->PersistSubsets(this); delete m_pSSList; } if( m_pDatabase ) delete m_pDatabase; if (m_pFullTextSearch) delete m_pFullTextSearch; if ( m_pSearchHighlight ) delete m_pSearchHighlight; CExTitle *p, *pNext; p = m_pHeadTitles; while (p) { pNext = p->GetNext(); delete p; p = pNext; } if (m_Collection.IsDirty()) m_Collection.Save(); m_Collection.Close(); if( m_szWordWheelPathname ) { delete [] (CHAR*) m_szWordWheelPathname; m_szWordWheelPathname = NULL; } // Persist subset selections. // #if 0 CSubSet* pSS; #endif if ( m_pSubSets ) { #if 0 if ( (pSS = m_pSubSets->GetFTSSubset()) && SUCCEEDED(m_pstate->Open(g_szFTSKey,STGM_WRITE)) ) { m_pstate->Write(pSS->m_cszSubSetName, strlen(pSS->m_cszSubSetName)+1); m_pstate->Close(); } if ( (pSS = m_pSubSets->GetIndexSubset()) && SUCCEEDED(m_pstate->Open(g_szIndexKey,STGM_WRITE)) ) { m_pstate->Write(pSS->m_cszSubSetName, strlen(pSS->m_cszSubSetName)+1); m_pstate->Close(); } if ( (pSS = m_pSubSets->GetTocSubset()) && SUCCEEDED(m_pstate->Open(g_szTOCKey,STGM_WRITE)) ) { m_pstate->Write(pSS->m_cszSubSetName, strlen(pSS->m_cszSubSetName)+1); m_pstate->Close(); } #ifdef _DEBUG /* Output all the user defined subsets to the state store. *******************/ int nKey; char buf[5]; CStr cszKey; extern const char txtSSInclusive[]; // "Inclusive"; extern const char txtSSExclusive[]; // "Exclusive"; static const int MAX_PARAM = 4096; static const char txtSSConvString[] = "%s:%s:%s"; // Exclusive|Inclusive:SetName:TypeName CMem memParam( MAX_PARAM ); CHAR* pszParam = (CHAR*)memParam; // for notational convenience for(int i=0; iHowManySubSets(); i++ ) { pSS = m_pSubSets->GetSubSet(i); if ( pSS->m_bPredefined ) continue; int type = pSS->GetFirstExcITinSubSet(); nKey=1; while (type != -1 && pSS->m_pIT->GetInfoTypeName(type) && (type <= pSS->m_pIT->HowManyInfoTypes())) { // even though we don't define exclusive filters, for user defined, it will be // here when we do. wsprintf(pszParam, txtSSConvString, txtSSExclusive, pSS->m_cszSubSetName.psz, pSS->m_pIT->GetInfoTypeName(type) ); cszKey = g_szUDSKey; wsprintf(buf,"%d",nKey++); cszKey += buf; if ( SUCCEEDED( m_pstate->Open(cszKey, STGM_WRITE)) ) m_pstate->Write(pszParam, strlen(pszParam)+1); m_pstate->Close(); type = pSS->GetNextExcITinSubSet(); } type = pSS->GetFirstIncITinSubSet(); nKey=1; while (type != -1 && pSS->m_pIT->GetInfoTypeName(type) && (type <= pSS->m_pIT->HowManyInfoTypes())) { wsprintf(pszParam, txtSSConvString, txtSSInclusive, pSS->m_cszSubSetName.psz, pSS->m_pIT->GetInfoTypeName(type) ); cszKey = g_szUDSKey; wsprintf(buf,"%d",nKey++); cszKey += buf; if ( SUCCEEDED( m_pstate->Open(cszKey, STGM_WRITE)) ) m_pstate->Write(pszParam, strlen(pszParam)+1); m_pstate->Close(); type = pSS->GetNextIncITinSubSet(); } } #endif #endif delete m_pSubSets; } if (m_pCSlt) delete m_pCSlt; // leak fix if ( m_pstate ) delete m_pstate; if (! m_bSingleTitle ) g_pCurrentCollection = NULL; } /* The FullPath parameter is the name of a directory. The directory is suppose to contain files to add to the collection. */ #ifdef CHIINDEX #include BOOL CExCollection::InitCollection( const TCHAR * FullPath, const TCHAR * szMasterChmFn ) { char szExt[2][5] = {".chm", ".chi"}; BOOL ret = FALSE; CStr filespec; long hSrch; struct _finddata_t fd_t; CStr szAdd; HRESULT hr; m_pMasterTitle = NULL; for(int i=0; i<2; i++) { filespec = FullPath; filespec += "\\*"; filespec += szExt[i]; if ( (hSrch = _findfirst( filespec, &fd_t ) ) == -1 ) continue; else ret = TRUE; do { CExTitle *pExTitle = NULL; szAdd = FullPath; szAdd += "\\"; szAdd += fd_t.name; // only check for the existance of a index for chm files if (0==i) { CFileSystem* pFileSystem = new CFileSystem; pFileSystem->Init(); if (!(SUCCEEDED(pFileSystem->Open( szAdd )))) { delete pFileSystem; continue; } CSubFileSystem* pSubFileSystem = new CSubFileSystem(pFileSystem); hr = pSubFileSystem->OpenSub("$WWKeywordLinks\\btree"); if (FAILED(hr)) { hr = pSubFileSystem->OpenSub("$WWAssociativeLinks\\btree"); if (FAILED(hr)) { pFileSystem->Close(); delete pFileSystem; delete pSubFileSystem; continue; } } delete pSubFileSystem; pFileSystem->Close(); delete pFileSystem; } pExTitle = new CExTitle( szAdd , this ); pExTitle->SetNext(m_pHeadTitles); m_pHeadTitles = pExTitle; if ( (m_pMasterTitle == NULL) && (strnicmp( szMasterChmFn, fd_t.name, strlen(szMasterChmFn)) == 0) ) { m_pMasterTitle = m_pHeadTitles; m_phmData->SetCompiledFile(szAdd); } m_Collection.IncrementRefTitleCount(); } while( _findnext( hSrch, &fd_t ) == 0 ); } if ( (ret == TRUE) && (m_pMasterTitle == NULL) ) { m_pMasterTitle = m_pHeadTitles; m_phmData->SetCompiledFile(szAdd); } return ret; } #endif BOOL CExCollection::InitCollection() { if (m_bSingleTitle) { m_pHeadTitles = new CExTitle(m_csFile, this); m_pMasterTitle = m_pHeadTitles; m_Collection.IncrementRefTitleCount(); GetMergedTitles(m_pHeadTitles); CStr cszCompiledFile; const CHAR* pszFilePortion = GetCompiledName(m_csFile, &cszCompiledFile); m_phmData->SetCompiledFile(cszCompiledFile); #ifdef DUMPTOC m_fh = fopen("c:\\toc_dump.txt", "w"); m_bRoot = TRUE; m_dwLevel = 0; CTreeNode *pNode = GetRootNode(); DumpNode(&pNode); fclose(m_fh); #endif } else { m_Collection.ConfirmTitles(); m_Collection.m_bFailNoFile = TRUE; if (m_Collection.Open(m_csFile) != F_OK) return FALSE; // Create an CExTitle for each referanced title LANGID LangId; CHAR* pszTitle; CTitle *pTitle; CFolder *p; LISTITEM *pItem; CExTitle *pExTitle; m_pCSlt = new CSlotLookupTable(); // start a new slot lookup table. pItem = m_Collection.m_RefTitles.First(); while (pItem) { p = (CFolder *)pItem->pItem; pszTitle = p->GetTitle() + 1; LangId = p->GetLanguage(); // check if extitle already exist, title referanced twice in the collection if ( (pExTitle = FindTitle(pszTitle, LangId)) ) { m_Collection.DecrementRefTitleCount(); pItem = m_Collection.m_RefTitles.Next(pItem); p->SetExTitlePtr(pExTitle); continue; } // find the title pTitle = m_Collection.FindTitle(pszTitle, LangId); if (pTitle == NULL) { m_Collection.DecrementRefTitleCount(); pItem = m_Collection.m_RefTitles.Next(pItem); continue; } //create CExTitle pExTitle = new CExTitle(pTitle, m_Collection.GetColNo(), this); if (ValidateTitle(pExTitle) == FALSE) { pItem = m_Collection.m_RefTitles.Next(pItem); continue; } // add the title pExTitle->SetNext(m_pHeadTitles); m_pHeadTitles = pExTitle; // Wire up the CFolder to the CExTitle, also, generate the Title Hash identifier. // p->SetExTitlePtr(pExTitle); char szBuf[20]; char szID[MAX_PATH + 20]; Itoa(p->GetLanguage(), szBuf); strcpy(szID, p->GetTitle()+1); // Don't hash the '=' strcat(szID, szBuf); pExTitle->m_dwHash = HashFromSz(szID); m_pCSlt->AddValue(p); if (pExTitle->GetUsedLocation()->bSupportsMerge) { GetMergedTitles(pExTitle); } pItem = m_Collection.m_RefTitles.Next(pItem); } // // check for a master chm // CHAR* pszName; LANGID LangId2; if (m_Collection.GetMasterCHM(&pszName, &LangId2)) { m_pMasterTitle = FindTitle(pszName, LangId2); } if (!m_pMasterTitle) { // default to first title m_pMasterTitle = GetFirstTitle(); } if (!m_pMasterTitle) return FALSE; g_pCurrentCollection = this; m_pCSlt->SortAndAssignSlots(); // Complete construction of the slot lookup table. while (TRUE) { if (m_pMasterTitle->OpenTitle() == FALSE) { m_pMasterTitle = m_pMasterTitle->GetNext(); if (m_pMasterTitle == NULL) return FALSE; continue; } CStr cszCompiledFile; GetCompiledName(m_pMasterTitle->GetPathName(), &cszCompiledFile); m_phmData->SetCompiledFile(cszCompiledFile); return TRUE; } } return TRUE; } void CExCollection::InitStructuralSubsets(void) { if ( m_bSingleTitle ) { m_phmData->m_sysflags.fDoSS = 0; return; } if ( m_phmData->m_sysflags.fDoSS ) { // Initilize the structural subset list and the "new" and "entire contents" subsets. // CStructuralSubset* pSS; CHAR szBuf[50]; m_pSSList = new CSSList; strncpy(szBuf,GetStringResource(IDS_ADVSEARCH_SEARCHIN_ENTIRE), sizeof(szBuf)); szBuf[49] = 0; pSS = new CStructuralSubset(szBuf); pSS->SetEntire(); pSS->SetReadOnly(); m_pSSList->AddSubset(pSS); m_pSSList->SetEC(pSS); strncpy(szBuf,GetStringResource(IDS_NEW), sizeof(szBuf)); szBuf[49] = 0; pSS = new CStructuralSubset(szBuf); pSS->SetEmpty(); pSS->SetReadOnly(); m_pSSList->AddSubset(pSS); m_pSSList->SetNew(pSS); m_pSSList->RestoreSubsets(this, szBuf); m_pSSList->ReadPreDefinedSubsets(this, szBuf); // // If a subset has been selected via SetGlobalProperties() via HH_GPROPID_CURRENT_SUBSET then use that as an override... // if ( _Module.szCurSS[0] ) { pSS = NULL; while ( (pSS = m_pSSList->GetNextSubset(pSS)) ) { if (! strcmpi(_Module.szCurSS, pSS->GetID()) ) { m_pSSList->SetFTS(pSS); m_pSSList->SetF1(pSS); m_pSSList->SetTOC(pSS); break; } } } } } void CExCollection::GetOpenSlot(CExTitle *p) { if (m_MaxOpenTitles[m_dwLastSlot]) m_MaxOpenTitles[m_dwLastSlot]->CloseTitle(); m_MaxOpenTitles[m_dwLastSlot] = p; m_dwLastSlot++; if (m_dwLastSlot == MAX_OPEN_TITLES) m_dwLastSlot = 0; } BOOL CExCollection::ValidateTitle(CExTitle *pExTitle, BOOL bDupCheckOnly) { // make sure the collection does not already contain a chm with same location MMC CExTitle *pEnumTitle; pEnumTitle = GetFirstTitle(); while (pEnumTitle) { if (lstrcmp(pEnumTitle->GetContentFileName(), pExTitle->GetContentFileName()) == 0) { delete pExTitle; m_Collection.DecrementRefTitleCount(); return FALSE; } pEnumTitle = pEnumTitle->GetNext(); } if (bDupCheckOnly == TRUE) return TRUE; // before adding to title list confirm that files exist pExTitle->FindUsedLocation(); if (pExTitle->GetUsedLocation() == NULL) { delete pExTitle; m_Collection.DecrementRefTitleCount(); return FALSE; } if (pExTitle->GetUsedLocation()->IndexFileName && pExTitle->GetUsedLocation()->IndexFileName[0]) { if (GetFileAttributes(pExTitle->GetUsedLocation()->IndexFileName) == HFILE_ERROR) { delete pExTitle; m_Collection.DecrementRefTitleCount(); return FALSE; } } return TRUE; } BOOL CExCollection::UpdateLocation( const CHAR* pszLocId, const CHAR* pszNewPath, const CHAR* pszNewVolume, const CHAR* pszNewTitle ) { // find the location itself CLocation *pLocation = FindLocation( (CHAR*)pszLocId); if (pLocation == NULL) return FALSE; // make sure new path has trailing backslash CStr cStrPath = pszNewPath; if (pszNewPath[strlen(pszNewPath) - 1] != '\\') cStrPath += "\\"; // get the current (old) location path CStr cStrOldPath = pLocation->GetPath(); if (cStrOldPath.psz[strlen(cStrOldPath.psz) - 1] != '\\') cStrOldPath += "\\"; // if new and old paths are the some just bail out if( lstrcmpi( cStrPath.psz, cStrOldPath.psz ) == 0 ) return FALSE; // determine what has changed in this path (was it just the drive letter?) // note we must compare from the tail end and thus we have to be careful when // dealing with DBCS strings CHAR* pszOldPathHead = cStrOldPath.psz; CHAR* pszOldPathTail = CharPrev(pszOldPathHead, pszOldPathHead + strlen(pszOldPathHead)); CHAR* pszPathHead = cStrPath.psz; CHAR* pszPathTail = CharPrev(pszPathHead, pszPathHead + strlen(pszPathHead)); while( (pszOldPathTail >= pszOldPathHead) && (pszPathTail >= pszPathHead) ) { BOOL bOldLB = IsDBCSLeadByte(*pszOldPathTail); BOOL bLB = IsDBCSLeadByte(*pszPathTail); if( bOldLB && bLB ) { if( !((*pszOldPathTail == *pszPathTail) && (*(pszOldPathTail+1) == *(pszPathTail+1))) ) break; } else if( bOldLB || bLB ) { break; } else if( ToLower(*pszOldPathTail) != ToLower(*pszPathTail) ) { break; } // bail if we compared all chars if( (pszOldPathTail == pszOldPathHead) || (pszPathTail == pszPathHead) ) break; // advance to previous char pszOldPathTail = CharPrev(pszOldPathHead, pszOldPathTail); pszPathTail = CharPrev(pszPathHead, pszPathTail); } if( IsDBCSLeadByte(*pszOldPathTail) ) pszOldPathTail++; if( IsDBCSLeadByte(*pszPathTail) ) pszPathTail++; char szOldPathPrefix[MAX_PATH]; int iLen = (int)(((DWORD_PTR)pszOldPathTail)-((DWORD_PTR)pszOldPathHead)+1); // always at least one lstrcpyn( szOldPathPrefix, pszOldPathHead, iLen+1 ); char szPathPrefix[MAX_PATH]; iLen = (int)(((DWORD_PTR)pszPathTail)-((DWORD_PTR)pszPathHead)+1); // always at least one lstrcpyn( szPathPrefix, pszPathHead, iLen+1 ); // update title if specified if (pszNewTitle) pLocation->SetTitle(pszNewTitle); // get the volume that will be updated CStr cStrVolume = pLocation->GetVolume(); // first, update each pathname in the titles that have the same volume label CExTitle *pExTitle = GetFirstTitle(); LOCATIONHISTORY *pLH; CHAR* pszFilePortion; CStr cFileName; while (pExTitle) { // if the used location for this title is the new location update it pLH = pExTitle->GetUsedLocation(); CLocation *pLoc = FindLocation( (CHAR*)pLH->LocationId ); CStr cStrVol = pLoc->GetVolume(); // get the old location path CStr cStrOldPath = pLoc->GetPath(); if (cStrOldPath.psz[strlen(cStrOldPath.psz) - 1] != '\\') cStrOldPath += "\\"; if (pExTitle->GetContentFileName().psz && pLH) { if (strcmpi( cStrVol, cStrVolume ) == 0) { // make sure that it did point to the old location pszFilePortion = (CHAR*)FindFilePortion(pExTitle->GetContentFileName()); cFileName = cStrOldPath.psz; cFileName += pszFilePortion; pszFilePortion = (CHAR*)FindFilePortion(cFileName); if (strcmpi(cFileName, pExTitle->GetContentFileName()) == 0) { // find the ending location of the old prefix CHAR* pszOldPathNameEnd = pExTitle->GetContentFileName().psz + strlen(szOldPathPrefix); // create the new pathname using the prefix of the new location and the // ending string of the old pathname CStr cStrPathName = szPathPrefix; cStrPathName += pszOldPathNameEnd; // update the pathname pExTitle->GetContentFileName() = cStrPathName.psz; } } } // check each location for this title pLH = pExTitle->m_pTitle->m_pHead; while (pLH) { // chm file location information CLocation *pLoc = FindLocation( (CHAR*)pLH->LocationId ); // if the location is NULL, this indications that we are looking at a title // that belongs to a different collection and thus we should skip it if( pLoc ) { CStr cStrVol = pLoc->GetVolume(); // get the old location path CStr cStrOldPath = pLoc->GetPath(); if (cStrOldPath.psz[strlen(cStrOldPath.psz) - 1] != '\\') cStrOldPath += "\\"; // chm file if (strcmpi( cStrVol, cStrVolume ) == 0) { pszFilePortion = (CHAR*)FindFilePortion(pLH->FileName); cFileName = cStrOldPath.psz; cFileName += pszFilePortion; if (strcmpi(cFileName, pLH->FileName) == 0) { // find the ending location of the old prefix CHAR* pszOldPathNameEnd = pLH->FileName + strlen(szOldPathPrefix); // create the new pathname using the prefix of the new location and the // ending string of the old pathname CStr cStrPathName = szPathPrefix; cStrPathName += pszOldPathNameEnd; // update the pathname AllocSetValue(cStrPathName.psz, &pLH->FileName); } } // chq file if( pLH->QueryFileName && *pLH->QueryFileName ) { // chq file location information // (use the same as the chm file if QueryLocation is not set) if( pLH->QueryLocation && *(pLH->QueryLocation) ) { pLoc = FindLocation( (CHAR*) pLH->QueryLocation ); cStrVol = pLoc->GetVolume(); // get the old location path cStrOldPath = pLoc->GetPath(); if (cStrOldPath.psz[strlen(cStrOldPath.psz) - 1] != '\\') cStrOldPath += "\\"; } // chq file if( strcmpi( cStrVol, cStrVolume ) == 0 ) { pszFilePortion = (CHAR*)FindFilePortion(pLH->QueryFileName); cFileName = cStrOldPath.psz; cFileName += pszFilePortion; if (strcmpi(cFileName, pLH->QueryFileName) == 0) { // find the ending location of the old prefix CHAR* pszOldPathNameEnd = pLH->QueryFileName + strlen(szOldPathPrefix); // create the new pathname using the prefix of the new location and the // ending string of the old pathname CStr cStrPathName = szPathPrefix; cStrPathName += pszOldPathNameEnd; // update the pathname AllocSetValue(cStrPathName.psz, &pLH->QueryFileName); } } } } pLH = pLH->pNext; } pExTitle = pExTitle->GetNext(); } // and finally, update any location identifier that has the same volume label // // note: we must do this list since the individual title updating relies on the fact // that we can fetch the "old" location information to validate the file pathing before // we update it CLocation *pLoc = m_Collection.FirstLocation(); while( pLoc ) { CStr cStrVol = pLoc->GetVolume(); if( strcmpi( cStrVol, cStrVolume ) == 0 ) { // get the old location path CStr cStrOldPath = pLoc->GetPath(); if (cStrOldPath.psz[strlen(cStrOldPath.psz) - 1] != '\\') cStrOldPath += "\\"; // find the ending location of the old prefix CHAR* pszOldPathEnd = cStrOldPath.psz + strlen(szOldPathPrefix); // create the new path using the prefix of the new location and the // ending string of the old path CStr cStrPath = szPathPrefix; cStrPath += pszOldPathEnd; // make sure the path aways ends with a backslash if (cStrPath.psz[strlen(cStrPath.psz) - 1] != '\\') cStrPath += "\\"; // update the path pLoc->SetPath(cStrPath); // update the volume label if specified if( pszNewVolume ) pLoc->SetVolume( pszNewVolume ); } pLoc = pLoc->GetNextLocation(); } // set the collection dirty bit so the hhcolreg.dat will get updated on shutdown m_Collection.Dirty(); return FALSE; } void CExCollection::GetChildURLS(CTreeNode *pNode, CTable *pTable) { CTreeNode *pParents[50]; CTreeNode *pCur, *pNext; CHAR* pszFind; DWORD dwCurLevel = 0; CHAR szURL[MAX_URL]; for (int i = 0; i < 50; i++) pParents[i] = NULL; // add the URL for this node if (pNode->GetURL(szURL, sizeof(szURL), TRUE)) { // Truncate the strings at the # character if there is one. pszFind = StrChr((const CHAR*)szURL, '#'); if (pszFind != NULL) *pszFind = '\0'; if (pszFind = StrChr((const CHAR*)szURL, '\\')) { while (*pszFind != '\0') { if (*pszFind == '\\') *pszFind = '/'; pszFind = CharNext(pszFind); } } if (pTable->IsStringInTable(szURL) == 0) pTable->AddString(szURL); } if (pNode->HasChildren()) { pParents[dwCurLevel] = pNode; dwCurLevel++; pCur = pNode->GetFirstChild(); while (pCur) { if (pCur->GetURL(szURL, sizeof(szURL), TRUE)) { // Truncate the strings at the # character if there is one. pszFind = StrChr((const CHAR*)szURL, '#'); if (pszFind != NULL) *pszFind = '\0'; if (pszFind = StrChr((const CHAR*)szURL, '\\')) { while (*pszFind != '\0') { if (*pszFind == '\\') *pszFind = '/'; pszFind = CharNext(pszFind); } } if (pTable->IsStringInTable(szURL) == 0) pTable->AddString(szURL); } if (pNext = pCur->GetFirstChild()) { if (pParents[dwCurLevel]) delete pParents[dwCurLevel]; pParents[dwCurLevel] = pCur; dwCurLevel++; pCur = pNext; } else if (pNext = pCur->GetNextSibling()) { delete pCur; pCur = pNext; } else { delete pCur; while (TRUE) { dwCurLevel--; if (dwCurLevel == 0) { if (pParents[dwCurLevel+1]) delete pParents[dwCurLevel+1]; pCur = NULL; break; } pCur = pParents[dwCurLevel]; if (pNext = pCur->GetNextSibling()) { pCur = pNext; break; } delete pCur; pParents[dwCurLevel] = NULL; } } } } return; } BOOL CExCollection::InitFTSKeyword() { // BUGBUG: we shouldn't do this until we know we have full-text search // in the file (which will usually NOT be the case). // Create full-text search object // if (m_phmData->m_sysflags.fFTI) { m_pFullTextSearch = new CFullTextSearch(this); m_pFullTextSearch->Initialize(); // Create search highlight object // m_pSearchHighlight = new CSearchHighlight(this); } // create word wheels (they self initialize themselves upon use) // // TODO: move full text search shared code to CTitleDatabase // m_pDatabase = new CTitleDatabase( this ); return TRUE; } #ifdef DUMPTOC void CExCollection::DumpNode(CTreeNode **p) { char sz[256]; if (m_bRoot == TRUE) m_bRoot = FALSE; else { for (DWORD i = 1; i < m_dwLevel; i++) fprintf(m_fh, " "); (*p)->GetTopicName(sz, sizeof(sz)); fprintf(m_fh, "%s\n", sz); } CTreeNode *pNode; if (pNode = (*p)->GetFirstChild()) { m_dwLevel++; DumpNode(&pNode); m_dwLevel--; } pNode = (*p)->GetNextSibling(); delete (*p); *p = NULL; do { if (pNode) DumpNode(&pNode); } while (pNode && (pNode = pNode->GetNextSibling())); } #endif void CExCollection::GetMergedTitles(CExTitle *pTitle) { CStr cStrFile; CStr cStrFullPath; CExTitle *pNewTitle = NULL; CExTitle *pPreviousNewTitle = NULL; CExTitle *pParent = pTitle; char szMasterPath[MAX_PATH]; char szTmp[MAX_PATH]; BOOL bOk2Add = FALSE; LCID TitleLocale = NULL; if( !(pTitle->Init()) ) return; //[ZERO IDXHDR] if (!pTitle->IsIdxHeaderValid()) { return ; } if (pTitle->GetInfo()) { TitleLocale = pTitle->GetInfo()->GetLanguage(); } DWORD dwCount = pTitle->GetIdxHeaderStruct()->dwCntMergedTitles; for (DWORD i = 0; i < dwCount; i++) { // read string table for this string if (FAILED(pTitle->GetString((((DWORD*)(&(pTitle->GetIdxHeaderStruct()->pad)))[i]), &cStrFile))) continue; // if this is not a single title (.col file) search collection registry if (m_bSingleTitle == FALSE) { // get base name SplitPath((CHAR*)cStrFile, NULL, NULL, szTmp, NULL); // search CTitle *pCTitle = m_Collection.FindTitle(szTmp, (LANGID)TitleLocale); if (pCTitle == NULL && TitleLocale != ENGLANGID) // for localized merged chms if we can't find a child with the same lang look for englist look for english titles in hhcolreg { pCTitle = m_Collection.FindTitle(szTmp, (LANGID)ENGLANGID); } if (pCTitle) { // create CExTitle pNewTitle = new CExTitle(pCTitle, m_Collection.GetColNo(), this); m_Collection.IncrementRefTitleCount(); if (ValidateTitle(pNewTitle) == TRUE) { CExTitle* pTitle = m_pHeadTitles; while(pTitle->GetNext()) pTitle = pTitle->GetNext(); pTitle->SetNext(pNewTitle); // // Sync/Next/Prev and subsetting spupport for "merged" chms... // pNewTitle->m_pParent = pParent; // Set parent pointer. if (! pParent->m_pKid ) // Set parent titles kid pointer to first kid. pParent->m_pKid = pNewTitle; if ( pPreviousNewTitle ) pPreviousNewTitle->m_pNextKid = pNewTitle; pPreviousNewTitle = pNewTitle; // // Create a hash for these... // char szBuf[20]; char szID[MAX_PATH + 20]; Itoa(pCTitle->GetLanguage(), szBuf); strcpy(szID, pCTitle->GetId()); strcat(szID, szBuf); pNewTitle->m_dwHash = HashFromSz(szID); continue; } } else if (m_Collection.GetFindMergedCHMS()) { if ( FindThisFile(NULL, cStrFile, &cStrFullPath) ) { // if found add to title list (add to the end of the list) pNewTitle = new CExTitle(cStrFullPath, this); m_Collection.IncrementRefTitleCount(); if (ValidateTitle(pNewTitle, TRUE) == TRUE) { CExTitle* pTitle = m_pHeadTitles; while(pTitle->GetNext()) pTitle = pTitle->GetNext(); pTitle->SetNext(pNewTitle); // // Sync/Next/Prev and subsetting spupport for "merged" chms... // pNewTitle->m_pParent = pParent; // Set parent pointer. if (! pParent->m_pKid ) // Set parent titles kid pointer to first kid. pParent->m_pKid = pNewTitle; if ( pPreviousNewTitle ) pPreviousNewTitle->m_pNextKid = pNewTitle; pPreviousNewTitle = pNewTitle; // // Create a hash for these... // char szBuf[20]; char szID[MAX_PATH + 20]; Itoa(0, szBuf); strcpy(szID, szTmp); strcat(szID, szBuf); pNewTitle->m_dwHash = HashFromSz(szID); continue; } } } } else { // // First check location of this file // if ((CHAR*)m_csFile ) { SplitPath((CHAR*)m_csFile, szMasterPath, szTmp, NULL, NULL); CatPath(szMasterPath, szTmp); CatPath(szMasterPath, (CHAR*)cStrFile); bOk2Add = ( GetFileAttributes(szMasterPath) != HFILE_ERROR ); } // search for file // if (! bOk2Add ) bOk2Add = FindThisFile(NULL, cStrFile, &cStrFullPath); else cStrFullPath = (const CHAR*)szMasterPath; if ( bOk2Add ) { // if found add to title list (add to the end of the list) pNewTitle = new CExTitle(cStrFullPath, this); pNewTitle->m_pParent = pParent; // Set parent pointer. CExTitle* pTitle = m_pHeadTitles; while(pTitle->GetNext()) pTitle = pTitle->GetNext(); pTitle->SetNext(pNewTitle); m_Collection.IncrementRefTitleCount(); } } } } // BUGBUG: dondr review, could this go away in-lu of checking the f_IsOrphan bit ? CTreeNode * CExCollection::CheckForTitleNode(CFolder *p) { if (p == NULL) return NULL; CHAR* pszTitle = p->GetTitle(); if (pszTitle && pszTitle[0] == '=') { CExTitle *pt = FindTitle(pszTitle+1, p->GetLanguage()); if (pt == NULL) return NULL; CExTitleNode *pext = new CExTitleNode(pt, p); return pext; } else { // Check if this folder has a title below it somewhere BOOL bFound = FALSE; CFolder *pFolder; if (pFolder = p->GetFirstChildFolder()) { CheckForTitleChild(pFolder, &bFound); } if (bFound == FALSE) { return NULL; } CExFolderNode *pf = new CExFolderNode(p, this); return pf; } return NULL; } // BUGBUG: dondr review, could this go away in-lu of checking the f_IsOrphan bit ? void CExCollection::CheckForTitleChild(CFolder *p, BOOL *pbFound) { if (*pbFound == TRUE) return; CHAR* pszTitle = p->GetTitle(); CFolder *pF; if (pszTitle && pszTitle[0] == '=') { *pbFound = TRUE; return; } if (pF = p->GetFirstChildFolder()) { CheckForTitleChild(pF, pbFound); if (*pbFound == TRUE) return; } pF = p->GetNextFolder(); while (pF) { CheckForTitleChild(pF, pbFound); if (*pbFound == TRUE) return; pF = pF->GetNextFolder(); } } DWORD CExCollection::GetRefedTitleCount() { return m_Collection.GetRefTitleCount(); } BOOL CExCollection::IsBinaryTOC(const CHAR* pszToc) { if (! m_phmData || !pszToc) return FALSE; return (HashFromSz(FindFilePortion(pszToc)) == m_phmData->m_hashBinaryTocName); } CTreeNode * CExCollection::GetRootNode() { CStructuralSubset* pSS = NULL; if (m_bSingleTitle) { if (!m_pHeadTitles) m_pHeadTitles = new CExTitle(GetPathName(), this); TOC_FOLDERNODE Node; if ( !SUCCEEDED(m_pHeadTitles->GetRootNode(&Node)) ) return NULL; CExTitleNode *pext = new CExTitleNode(m_pHeadTitles, NULL); return pext; } else { CFolder *p = m_Collection.GetRootFolder(); CTreeNode *pN; // implement structural subset filtering for TOC here. if( m_pSSList ) pSS = m_pSSList->GetTOC(); while (p) { if ((pN = CheckForTitleNode(p))) { if ( !pSS || pSS->IsEntire() || p->bIsVisable() ) return pN; else delete pN; // leak fix } p = p->GetNextFolder(); } return NULL; } } ////////////////////////////////////////////////////////////////////////// // // Ignore LangId if its Zero. // CExTitle * CExCollection::FindTitle(const CHAR* pszId, LANGID LangId) { // look in list of titles CExTitle *p = m_pHeadTitles; while (p) { if( p->GetCTitle() ) if( _tcsicmp(p->GetCTitle()->GetId(), pszId) == 0 ) if( (LangId == 0 || p->GetCTitle()->GetLanguage() == LangId) ) // If LangId == 0, we ignore the lang id. { return p; } p = p->GetNext(); } return NULL; } // Try multiple LangIds before failing CExTitle * CExCollection::FindTitleNonExact(const CHAR* pszId, LANGID DesiredLangId) { CExTitle* pTitle = NULL ; CLanguageEnum* pEnum = _Module.m_Language.GetEnumerator(DesiredLangId) ; ASSERT(pEnum) ; LANGID LangId = pEnum->start() ; while (LangId != c_LANGID_ENUM_EOF) { pTitle = FindTitle(pszId, LangId); if (pTitle) { break ; // Found it! } LangId = pEnum->next() ; } // Cleanup. if (pEnum) { delete pEnum ; } return pTitle; } // CExTitle * CExCollection::TitleFromChmName(const CHAR* pszChmName) { CExTitle *p = m_pHeadTitles; CHAR szFN[MAX_PATH]; CHAR szExt[MAX_PATH]; while (p) { SplitPath(p->GetContentFileName(), NULL, NULL, szFN, szExt); strcat(szFN, szExt); if (! _tcsicmp(szFN, pszChmName) ) return p; p = p->GetNext(); } return NULL; } CLocation * CExCollection::FindLocation(CHAR* pszId) { return m_Collection.FindLocation(pszId); } // GetNext() // // Returns the next physical TOC node irregaurdless of its type. If a node is a container, it's // next is considered to be it's child. // // Note that the caller will be responsible for deleting the returned CTreeNode object. // CTreeNode * CExCollection::GetNext(CTreeNode* pTreeNode, DWORD* pdwSlot) { CTreeNode *pTreeNext = NULL, *pTreeParent = NULL, *pSaveNode = NULL; DWORD dwObjType; dwObjType = pTreeNode->GetType(); if ( ((dwObjType == FOLDER) || (dwObjType == CONTAINER)) && (pTreeNext = pTreeNode->GetFirstChild(pdwSlot)) ) return pTreeNext; else { if ( (pTreeNext = pTreeNode->GetNextSibling(NULL, pdwSlot)) ) return pTreeNext; else { pSaveNode = pTreeNode; do { pTreeParent = pTreeNode->GetParent(pdwSlot, TRUE); if ( pSaveNode != pTreeNode ) delete pTreeNode; if (! (pTreeNode = pTreeParent) ) return NULL; } while (!(pTreeNext = pTreeNode->GetNextSibling(NULL, pdwSlot))); } return pTreeNext; } } // GetNext() // // Returns the next TOC node that represents a displayable topic. // // Note that the caller will be responsible for deleting the returned CTreeNode object. // CTreeNode * CExCollection::GetNextTopicNode(CTreeNode* pTreeNode, DWORD* pdwSlot) { CTreeNode *pTocNext = NULL, *pTocKid = NULL; DWORD dwObjType; try_again: if ( (pTocNext = GetNext(pTreeNode, pdwSlot)) ) { dwObjType = pTocNext->GetType(); if ( (dwObjType != TOPIC) && (dwObjType != CONTAINER) ) { // We need to drill down! // do { if ( (pTocKid = pTocNext->GetFirstChild(pdwSlot)) ) { dwObjType = pTocKid->GetType(); delete pTocNext; pTocNext = pTocKid; } else { // This is the case of the book that contains no kids! For this case, we'll skip this dirty node! // pTreeNode = pTocNext; goto try_again; } } while ( (dwObjType != TOPIC) && (dwObjType != CONTAINER) ); } return pTocNext; } return NULL; } // GetPrev() // // Returns the previous physical TOC node irregardless of its type. // // Note that the caller will be responsible for deleting the returned CTreeNode object. // CTreeNode * CExCollection::GetPrev(CTreeNode* pTreeNode, DWORD* pdwSlot) { CTreeNode *pTreeNext = NULL, *pTreeParent = NULL , *pSaveNode = NULL, *pTmpNode = NULL; DWORD dwObjType; DWORD dwTmpSlot; pSaveNode = pTreeNode; if (! (pTreeParent = pTreeNode->GetParent(pdwSlot, TRUE)) ) { // Could this be a single title with multiple roots ? // if ( pTreeNode->GetObjType() == EXNODE ) { CExTitle* pTitle; pTitle = ((CExNode*)pTreeNode)->GetTitle(); if ( pTitle->m_pCollection->IsSingleTitle() ) { TOC_FOLDERNODE Node; pTitle->GetRootNode(&Node); pTreeNext = new CExNode(&Node, pTitle); if ( pTreeNext->Compare(pSaveNode) ) { delete pTreeNext; // leak fix return NULL; // No prev to be found! } if ( pdwSlot ) *pdwSlot = pTitle->GetRootSlot(); goto find_it; } } else { CFolder *p = m_Collection.GetRootFolder(); if( !p ) return NULL; p = p->GetFirstChildFolder(); // // Is it visable ? // CStructuralSubset* pSS = NULL; if( m_pSSList ) pSS = m_pSSList->GetTOC(); if ( pSS && !pSS->IsEntire() && !p->bIsVisable() ) return NULL; pTreeNext = new CExFolderNode(p, this); goto find_it; } return NULL; } if (! (pTreeNext = pTreeParent->GetFirstChild(&dwTmpSlot)) ) { delete pTreeParent; // leak fix return NULL; } // ---LEAK!: At this point we have a pTreeNext and pTreeParent --- if ( pTreeNext->Compare(pSaveNode) ) { dwObjType = pTreeParent->GetType(); if ( dwObjType == CONTAINER ) return pTreeParent; else { pTmpNode = GetPrev(pTreeParent, pdwSlot); delete pTreeParent; if (! pTmpNode ) { delete pTreeNext ; // Fix Leak. return NULL; } if ( pTmpNode->GetType() == CONTAINER ) return pTmpNode; else { pTreeParent = GetLastChild(pTmpNode, pdwSlot); if ( pTreeParent != pTmpNode ) delete pTmpNode; return pTreeParent; } } } delete pTreeParent; if ( pdwSlot ) *pdwSlot = dwTmpSlot; find_it: pTmpNode = pTreeNext->GetNextSibling(NULL, &dwTmpSlot); while ( pTmpNode && !pTmpNode->Compare(pSaveNode) ) { delete pTreeNext; pTreeNext = NULL; // leak fix pTreeNext = pTmpNode; if ( pdwSlot ) *pdwSlot = dwTmpSlot; pTmpNode = pTreeNext->GetNextSibling(NULL, &dwTmpSlot); } if (pTmpNode && pTmpNode->Compare(pSaveNode) ) { delete pTmpNode; if ( pTreeNext->GetType() == BOGUS_FOLDER ) { pTmpNode = GetPrev(pTreeNext, pdwSlot); if ( pTmpNode != pTreeNext ) { delete pTreeNext; pTreeNext = NULL; // leak fix pTreeNext = pTmpNode; } } pTmpNode = GetLastChild(pTreeNext, pdwSlot); if ( pTmpNode != pTreeNext ) delete pTreeNext; return pTmpNode; } else if( pTmpNode ) delete pTmpNode; // leak fix if( pTreeNext && (pTreeNext != pTmpNode) ) delete pTreeNext; // leak fix return NULL; } CTreeNode * CExCollection::GetLastChild(CTreeNode* pTreeNode, DWORD* pdwSlot) { CTreeNode *pTreeTmp = NULL, *pTreeKid = NULL, *pSiblingNode = NULL; DWORD dwObjType; if (! pTreeNode ) return NULL; dwObjType = pTreeNode->GetType(); if ( dwObjType != TOPIC ) { // We need to drill down. // if ( (pTreeKid = pTreeNode->GetFirstChild(pdwSlot)) ) { while ( (pSiblingNode = pTreeKid->GetNextSibling(NULL, pdwSlot)) ) { while ( pSiblingNode->GetType() == BOGUS_FOLDER ) { if ( ! (pTreeTmp = pSiblingNode->GetNextSibling(NULL, pdwSlot)) ) break; else { delete pSiblingNode; pSiblingNode = pTreeTmp; } } delete pTreeKid; pTreeKid = pSiblingNode; } if ( pTreeKid->GetType() != TOPIC ) { pTreeTmp = GetLastChild(pTreeKid, pdwSlot); if (pTreeTmp == pTreeKid) { delete pTreeKid; return NULL; } delete pTreeKid; pTreeKid = pTreeTmp; } return pTreeKid; } } return pTreeNode; } HRESULT CExCollection::URL2ExTitle(const CHAR* pszURL, CExTitle **ppTitle) { // get the title from the URL CStr cStr; const CHAR* pszThisFileName; GetCompiledName(pszURL, &cStr); if( !cStr.psz ) return E_FAIL; const CHAR* pszFileName = FindFilePortion( cStr.psz ); CExTitle *pTitle = GetFirstTitle(); while (pTitle) { pszThisFileName = pTitle->GetFileName(); if (pszThisFileName == NULL) { pTitle = pTitle->GetNext(); continue; } if( StrCmpIA(pszThisFileName, pszFileName) == 0 ) { *ppTitle = pTitle; return S_OK; } pTitle = pTitle->GetNext(); } return E_FAIL; } // // The following bit of code attempts to detect if we have arrived at a topic that is referenced in // more than one place in the TOC. This is done by comparing the TOC location we lookup for the URL with // the "m_dwCurrSlot" which is updated here and when any navigation call is made. If these TOC locations // are different yet they reference the same topic, we utilize the "m_dwCurrSlot" TOC location for syncing // needs. // // UpdateTopicSlot() // // Called ONLY from BeforeNavigate() before a navigation in allowed to proceed. // void CExCollection::UpdateTopicSlot(DWORD dwSlot, DWORD dwTN, CExTitle* pTitle) { if ( m_dwCurrSlot && dwSlot != m_dwCurrSlot ) { // if ( (dwTN != m_dwCurrTN) && dwSlot ) if ( (dwTN != m_dwCurrTN) ) m_dwCurrSlot = dwSlot; else return; } else m_dwCurrSlot = dwSlot; m_dwCurrTN = dwTN; m_pCurrTitle = pTitle; } HRESULT CExCollection::Sync(CPointerList *pHier, const CHAR* pszURL) { CTreeNode* pThis = NULL; CExTitle *pTitle = NULL; CTreeNode *pParent = NULL; if ( m_dwCurrSlot ) { if( IsBadReadPtr(m_pCurrTitle, sizeof(CExTitle)) ) return E_FAIL; if (! SUCCEEDED(m_pCurrTitle->Slot2TreeNode(m_dwCurrSlot, &pThis)) ) return E_FAIL; } else if (pszURL) { if (! SUCCEEDED(URL2ExTitle(pszURL, &pTitle)) ) return E_FAIL; // MAJOR HACK to fix a show stopper for nt5. This code assumes nt's superchm authoring of URLS's as follows // MS-ITS:dkconcepts.chm::/defrag_overview_01.htm. This code assumes ansi URL strings if (pTitle->m_pParent) { char szBuf[MAX_URL]; strcpy(szBuf, pszURL); char *pszLastWak, *pszCurChar; pszLastWak = NULL; pszCurChar = szBuf; while (*pszCurChar) { // if we get to the :: we are at the end of the pathing information if (*pszCurChar == ':' && *(pszCurChar+1) == ':') break; if (*pszCurChar == '\\' || *pszCurChar == '/') pszLastWak = pszCurChar; pszCurChar++; } if (pszLastWak) { char szBuf2[MAX_URL]; pszLastWak ++; strcpy(szBuf2, pszLastWak); strcpy(szBuf, txtMsItsMoniker); pszCurChar = szBuf2; strcat(szBuf, szBuf2); if (! SUCCEEDED(pTitle->m_pParent->GetURLTreeNode(szBuf, &pThis, FALSE)) ) return E_FAIL; } } else if (! SUCCEEDED(pTitle->GetURLTreeNode(pszURL, &pThis)) ) { return E_FAIL; } } else { return E_FAIL; } // Make sure we have a valid CTreeNode pointer (Whistler bug #8112 & 8101). // The above code below "MAJOR HACK" doesn't appear to work because: // 1) we fall out of the parsing loop before pszLastWak is set, and // 2) even if pszLastWak was set, GetURLTreeNode() fails on the // generated URL. // // In the case of bug #8112 & 8101, we get to this point and pThis has not // been set to a valid CTreeNode (thus the crash). The safest thing to do // is simply call GetURLTreeNode here and get the CTreeNode pointer. // if(!pThis) { if (!SUCCEEDED(pTitle->GetURLTreeNode(pszURL, &pThis))) return E_FAIL; } pHier->Add(pThis); // now get all of the parents for (pParent = pThis->GetParent(); pParent; pParent = pParent->GetParent()) pHier->Add(pParent); return S_OK; } CFolder *CExCollection::FindTitleFolder(CExTitle *pTitle) { CFolder *p; LISTITEM *pItem; pItem = m_Collection.m_RefTitles.First(); while (pItem) { p = (CFolder *)pItem->pItem; if (pTitle->GetCTitle() && _tcsicmp(pTitle->GetCTitle()->GetId(), p->GetTitle() + 1) == 0 && pTitle->GetCTitle()->GetLanguage() == p->GetLanguage()) return p; pItem = m_Collection.m_RefTitles.Next(pItem); } return NULL; } // // I've modified this function be be more generic. It returns a local storage path according to // the same rules as always: // // 1.) location of local .COL // 2.) "windir"\profiles\"user"\application data\microsoft\htmlhelp directory. // // The function mow takes an extension name which will be used to qualify the requested storage // pathname. // // **** WARNING **** // We return a pointer from this function. Callers should make a copy of the string immeadiatly // after they call this function rather than placing any reliance of the integrity of the pointer returned. // // const CHAR* CExCollection::GetLocalStoragePathname(const CHAR* pszExt) { // TODO: read this in from the XML file // (for now base it on the collection pathname) // // TODO: check write permission of destination, if not allowed // the set the path to the system directory or some // other default writable location // // TODO: check for sufficient disk space. static CHAR szPathname[MAX_PATH]; CHAR szFileName[MAX_PATH]; CHAR* pszExtension; UINT uiDt = DRIVE_UNKNOWN; if( !pszExt && *pszExt ) return NULL; // if we want the chw and it is already set then return it and bail out if( !strcmp( pszExt, ".chw" ) ) { if( m_szWordWheelPathname ) return m_szWordWheelPathname; } // If we're operating a collection, use the location of the collection file and the collection // file root name as the .chm name. // if (! m_bSingleTitle ) strcpy(szPathname, m_Collection.GetCollectionFileName()); else // else, in single title mode, use master .chm name and location. strcpy(szPathname ,GetPathName()); CHAR* pszFilePart = NULL; GetFullPathName( szPathname, sizeof(szPathname), szPathname, &pszFilePart ); // get the drive of the path CHAR szDriveRoot[4]; strncpy( szDriveRoot, szPathname, 4 ); szDriveRoot[3] = '\0'; // make sure to add a backslash if the second char is a colon // sometimes we will get just "d:" instead of "d:\" and thus // GetDriveType will fail under this circumstance if( szDriveRoot[1] == ':' ) { szDriveRoot[2] = '\\'; szDriveRoot[3] = 0; } // Get media type if( szDriveRoot[1] == ':' && szDriveRoot[2] == '\\' ) { uiDt = GetDriveType(szDriveRoot); } else if( szDriveRoot[0] == '\\' && szDriveRoot[1] == '\\' ) { uiDt = DRIVE_REMOTE; } // If removable media or not write access then write to the %windir%\profiles... directory if( !( ((uiDt == DRIVE_FIXED) || (uiDt == DRIVE_REMOTE) || (uiDt == DRIVE_RAMDISK)) ) ) { strcpy(szFileName, FindFilePortion(szPathname)); HHGetUserDataPath( szPathname ); CatPath(szPathname, szFileName); } // for chw files this location must be writeable if( !strcmp( pszExt, ".chw" ) ) { if( (pszExtension = StrRChr( szPathname, '.' )) ) *pszExtension = '\0'; strcat( szPathname, ".foo" ); HANDLE hFile = CreateFile(szPathname, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS , FILE_ATTRIBUTE_NORMAL, 0); if (INVALID_HANDLE_VALUE == hFile) { strcpy(szFileName, FindFilePortion(szPathname)); HHGetUserDataPath( szPathname ); CatPath(szPathname, szFileName); } else { CloseHandle(hFile); DeleteFile(szPathname); } } // now update the extension if( (pszExtension = StrRChr( szPathname, '.' )) ) *pszExtension = '\0'; strcat( szPathname, pszExt ); // for the chw file, save it if( !strcmp( pszExt, ".chw" ) ) SetWordWheelPathname( szPathname ); return( szPathname ); } const CHAR* CExCollection::GetUserCHSLocalStoragePathnameByLanguage() { // TODO: check for sufficient disk space. static CHAR szPathname[MAX_PATH]; CHAR* pszExtension; // If we're operating a collection, use the location of the collection file and the collection // file root name as the .chm name. // if (! m_bSingleTitle ) strcpy(szPathname, m_Collection.GetCollectionFileName()); else // else, in single title mode, use master .chm name and location. strcpy(szPathname ,GetPathName()); CHAR* pszFilePart = NULL; GetFullPathName( szPathname, sizeof(szPathname), szPathname, &pszFilePart ); // now get the users path name char szUserPath[MAX_PATH]; if (HHGetCurUserDataPath( szUserPath ) == S_OK) { // append language id CHAR szTemp[5]; CHAR* pszName; LANGID LangId; m_Collection.GetMasterCHM(&pszName, &LangId); wsprintf(szTemp, "%d", LangId); CatPath(szUserPath, szTemp); if( !IsDirectory(szUserPath) ) CreateDirectory( szUserPath, NULL ); CatPath(szUserPath, pszFilePart); strcpy(szPathname, szUserPath); } // now update the extension if( (pszExtension = StrRChr( szPathname, '.' )) ) *pszExtension = '\0'; strcat( szPathname, ".chs" ); return( szPathname ); } const CHAR* CExCollection::GetUserCHSLocalStoragePathname() { // TODO: check for sufficient disk space. static CHAR szPathname[MAX_PATH]; CHAR* pszExtension; // If we're operating a collection, use the location of the collection file and the collection // file root name as the .chm name. // if (! m_bSingleTitle ) strcpy(szPathname, m_Collection.GetCollectionFileName()); else // else, in single title mode, use master .chm name and location. strcpy(szPathname ,GetPathName()); CHAR* pszFilePart = NULL; GetFullPathName( szPathname, sizeof(szPathname), szPathname, &pszFilePart ); // now get the users path name char szUserPath[MAX_PATH]; if (HHGetCurUserDataPath( szUserPath ) == S_OK) { CatPath(szUserPath, pszFilePart); strcpy(szPathname, szUserPath); } // now update the extension if( (pszExtension = StrRChr( szPathname, '.' )) ) *pszExtension = '\0'; strcat( szPathname, ".chs" ); return( szPathname ); } const CHAR* CExCollection::SetWordWheelPathname( const CHAR* pszWordWheelPathname ) { // if we already have one, free it if( m_szWordWheelPathname ) { delete [] (CHAR*) m_szWordWheelPathname; m_szWordWheelPathname = NULL; } // allocate a new once based on the size of the input buffer int iLen = (int)strlen( pszWordWheelPathname ); m_szWordWheelPathname = new char[iLen+10]; // Add 10 for future extension additions. strcpy( (CHAR*) m_szWordWheelPathname, pszWordWheelPathname ); return m_szWordWheelPathname; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // CExTitle implementation void CExTitle::_CExTitle() // <--- put all shared initialization here { m_pUsedLocation = NULL; m_bOpen = FALSE; m_dwNodeOffsetInParentTitle = 0; m_pTitleFTS = NULL; m_pCFileSystem = NULL; m_pTocNodes = m_pTopics = m_pStrTbl = m_pUrlTbl = m_pUrlStrings = m_pITBits = NULL; m_pNext = NULL; m_pHeader = NULL; m_pInfo = NULL; m_pInfo2 = NULL; m_fIsChiFile = FALSE; m_uiVolumeOrder = (UINT) -1; m_bChiChmChecked = FALSE; m_bIsValidTitle = FALSE; m_cMapIds = 0; m_pMapIds = NULL; //[ZERO IDXHDR] Mark the m_IdxHeader as being uninitialized. m_pIdxHeader = NULL; m_pKid = m_pNextKid = m_pParent = NULL; m_szAttachmentPathName[0] = 0; } CExTitle::CExTitle(const CHAR* pszFileName, CExCollection *pCollection) { _CExTitle(); m_ContentFileName = pszFileName; m_dwColNo = 0; m_pTitle = NULL; m_pCollection = pCollection; m_pInfo2 = new CTitleInformation2( GetIndexFileName() ); } CExTitle::CExTitle(CTitle *p, DWORD ColNo, CExCollection *pCollection) { _CExTitle(); m_dwColNo = ColNo; m_pTitle = p; m_pCollection = pCollection; const CHAR* pFileName = NULL ; ASSERT(p) ; FindUsedLocation(); if (GetUsedLocation() == NULL) return; if (GetUsedLocation()->IndexFileName) pFileName = GetUsedLocation()->IndexFileName ; else pFileName = GetUsedLocation()->FileName ; m_pInfo2 = new CTitleInformation2(pFileName); } CExTitle::~CExTitle() { CloseTitle(); if ( m_pTitleFTS ) delete m_pTitleFTS; m_pTitleFTS = NULL; if ( m_pInfo2 ) delete m_pInfo2; m_pInfo2 = NULL; if ( m_pInfo ) delete m_pInfo; m_pInfo = NULL; } void CExTitle::CloseTitle() { if ( m_pTocNodes ) delete m_pTocNodes; m_pTocNodes = NULL; if ( m_pTopics ) delete m_pTopics; m_pTopics = NULL; if ( m_pStrTbl ) delete m_pStrTbl; m_pStrTbl = NULL; if ( m_pUrlTbl ) delete m_pUrlTbl; m_pUrlTbl = NULL; if ( m_pUrlStrings ) delete m_pUrlStrings; m_pUrlStrings = NULL; if ( m_pITBits ) delete m_pITBits; m_pITBits = NULL; if ( m_pHeader ) lcFree(m_pHeader); m_pHeader = NULL; if ( m_pCFileSystem ) { m_pCFileSystem->Close(); delete m_pCFileSystem; } m_pCFileSystem = NULL; m_bOpen = FALSE; } static const char txtChiFile[] = ".chi"; BOOL CExTitle::OpenTitle() { if (m_bOpen) return TRUE; // get our title locations const CHAR* pszContentFilename = GetPathName(); const CHAR* pszIndexFilename = GetIndexFileName(); // bail out if neither file specified if( !pszContentFilename && !pszIndexFilename ) return FALSE; // for the single title case, the command line is simply // the chm file so we will have to see if the chi files lives in the // same location. If it does not, then we have a monolithic title // condition. char szIndexFilename[_MAX_PATH]; if( m_pCollection && m_pCollection->IsSingleTitle() && pszContentFilename ) { strcpy( szIndexFilename, pszContentFilename ); CHAR* psz; if( (psz = StrRChr(szIndexFilename, '.')) ) strcpy(psz, txtChiFile); else strcat(szIndexFilename, txtChiFile); if (GetFileAttributes(szIndexFilename) != HFILE_ERROR) pszIndexFilename = szIndexFilename; } // monolithic title? BOOL bMonolithic = FALSE; if( !pszIndexFilename || !pszContentFilename || (strcmpi( pszContentFilename, pszIndexFilename ) == 0) ) { bMonolithic = TRUE; } // bail out if no content file if( !pszContentFilename ) return FALSE; // now open the title files return exOpenFile( pszContentFilename, (bMonolithic || pszIndexFilename[0] == NULL) ? NULL : pszIndexFilename ); } BOOL CExTitle::FindUsedLocation() { if (m_pUsedLocation) return TRUE; char drive[_MAX_DRIVE]; char dir[_MAX_DIR]; char fname[_MAX_FNAME]; char ext[_MAX_EXT]; // find the correct location to use // first look for the newest local and entry for this collection m_pUsedLocation = m_pTitle->m_pHead; DWORD dwNewestLocal = 0; DWORD dwNewest = 0; LOCATIONHISTORY *pNewestLocal = NULL; LOCATIONHISTORY *pNewest = NULL; LOCATIONHISTORY *pThisCol = NULL; while (m_pUsedLocation) { _splitpath( m_pUsedLocation->FileName, drive, dir, fname, ext ); strcpy(dir, drive); if (dir[0] != NULL) { dir[1] = ':'; dir[2] = '\\'; dir[3] = NULL; } if (GetDriveType(dir) == DRIVE_FIXED) { if (m_pUsedLocation->Version > dwNewestLocal) { dwNewestLocal = m_pUsedLocation->Version; pNewestLocal = m_pUsedLocation; } } if (m_pUsedLocation->Version >= dwNewest) { dwNewest = m_pUsedLocation->Version; pNewest = m_pUsedLocation; } if (m_pUsedLocation->CollectionNumber == m_dwColNo) { if (pThisCol) { if (pThisCol->Version <= m_pUsedLocation->Version) pThisCol = m_pUsedLocation; } else pThisCol = m_pUsedLocation; } m_pUsedLocation = m_pUsedLocation->pNext; } m_pUsedLocation = pThisCol; if (m_pUsedLocation == NULL) return FALSE; m_ContentFileName = m_pUsedLocation->FileName; m_IndexFileName = m_pUsedLocation->IndexFileName; return TRUE; } BOOL CExTitle::exOpenFile(const CHAR* pszContent, const CHAR* pszIndex) { HRESULT hr; if (m_bOpen) return TRUE; ASSERT_COMMENT(!m_pCFileSystem, "exOpenFile already called once"); m_pCFileSystem = new CFileSystem(); if ( m_pCFileSystem->Init() != S_OK ) goto failure; if ( pszIndex ) { hr = m_pCFileSystem->Open(pszIndex); m_IndexFileName = pszIndex; m_fIsChiFile = TRUE; } else hr = m_pCFileSystem->Open(pszContent); if (FAILED(hr)) goto failure; // title information pointer if (! m_pInfo ) { m_pInfo = new CTitleInformation( m_pCFileSystem ); if (! m_pInfo->GetIdxHeader(&m_pIdxHeader) ) { // // Open and read the idxheader subifle. // CPagedSubfile* pHdrSubFile; BYTE* pb; pHdrSubFile = new CPagedSubfile; if ( SUCCEEDED(hr = pHdrSubFile->Open(this, txtIdxHdrFile)) ) { if ( (pb = (BYTE*)pHdrSubFile->Offset(0)) ) { CopyMemory((PVOID)m_pIdxHeader, (LPCVOID)pb, sizeof(IDXHEADER)); // m_pIdxHeader->dwOffsMergedTitles = (DWORD*)&m_pIdxHeader->pad; // 64bit overwrite } } if( pHdrSubFile ) delete pHdrSubFile; } } m_bOpen = TRUE; // // Init full-text for title // if(!m_pTitleFTS && GetInfo()->IsFullTextSearch()) { m_pTitleFTS = new CTitleFTS( pszContent, GetInfo()->GetLanguage(), this ); } m_ITCnt = GetInfo()->GetInfoTypeCount(); if( m_pCollection ) { if ( m_pCollection->GetMasterTitle() != this ) // ALWAYS cache the CExTitle data for the master title. i.e. m_pCollection->GetOpenSlot(this); } // BUGBUG - return a value based on hr when these subfiles exist return(TRUE); // TRUE on success. failure: delete m_pCFileSystem; m_pCFileSystem = NULL; return FALSE; } BOOL CExTitle::EnsureChmChiMatch( CHAR* pszChm ) { if (! m_fIsChiFile ) return TRUE; if ( !pszChm && m_bChiChmChecked ) return m_bIsValidTitle; if( pszChm ) { m_bChiChmChecked = FALSE; m_bIsValidTitle = FALSE; } else m_bChiChmChecked = TRUE; FILETIME ftChi, ftChm; CTitleInformation* pChmInfo = NULL; HRESULT hr; ftChi = GetInfo()->GetFileTime(); CFileSystem* pCFileSysChm = new CFileSystem(); if ( pCFileSysChm->Init() == S_OK ) { const CHAR* pszChmTry; if( pszChm ) pszChmTry = pszChm; else pszChmTry = GetPathName(); if ( SUCCEEDED((hr = pCFileSysChm->Open(pszChmTry))) ) { pChmInfo = new CTitleInformation(pCFileSysChm); ftChm = pChmInfo->GetFileTime(); if ( (ftChm.dwHighDateTime != ftChi.dwHighDateTime) || (ftChm.dwLowDateTime != ftChi.dwLowDateTime) ) { if( pszChm ) m_bIsValidTitle = FALSE; else if ( MsgBox(IDS_CHM_CHI_MISMATCH, GetPathName(), MB_OKCANCEL | MB_ICONWARNING | MB_TASKMODAL) == IDOK ) m_bIsValidTitle = TRUE; else m_bIsValidTitle = FALSE; } else m_bIsValidTitle = TRUE; } } delete pCFileSysChm; if ( pChmInfo ) delete pChmInfo; return m_bIsValidTitle; } const CHAR* CExTitle::GetFileName() { if (m_pTitle && GetUsedLocation() == NULL) return NULL; if (GetUsedLocation()) return FindFilePortion(GetUsedLocation()->FileName); else return FindFilePortion(m_ContentFileName); } const CHAR* CExTitle::GetPathName() { if (m_pTitle && GetUsedLocation() == NULL) return NULL; if (GetUsedLocation()) return GetUsedLocation()->FileName; else return m_ContentFileName; } const CHAR* CExTitle::GetQueryName() { if (m_pTitle && GetUsedLocation() == NULL) return NULL; if (GetUsedLocation()) { char *pszQueryName = GetUsedLocation()->QueryFileName; if( pszQueryName && *pszQueryName) // don't want a null string return pszQueryName; else return NULL; } else { // DON: Is this right? // return NULL; } } const CHAR* CExTitle::GetIndexFileName() { if (m_pTitle && GetUsedLocation() == NULL) return NULL; if (GetUsedLocation()) { return GetUsedLocation()->IndexFileName; } else { if ( (CHAR*)m_IndexFileName ) return m_IndexFileName; else return GetPathName(); } } const CHAR* CExTitle::GetCurrentAttachmentName() { return (const CHAR*) &m_szAttachmentPathName; } const CHAR* CExTitle::SetCurrentAttachmentName( const CHAR* pszAttachmentPathName ) { if( pszAttachmentPathName && pszAttachmentPathName[0] ) strcpy( m_szAttachmentPathName, pszAttachmentPathName ); return (const CHAR*) &m_szAttachmentPathName; } DWORD CExTitle::GetRootSlot(void) { BYTE* pb; if (m_bOpen == FALSE) { if (OpenTitle() == FALSE) return 0; } if (!m_pTocNodes) { m_pTocNodes = new CPagedSubfile; if (FAILED(m_pTocNodes->Open(this, txtTocIdxFile))) { delete m_pTocNodes; m_pTocNodes = NULL; return 0; } } if (! m_pHeader ) { m_pHeader = (TOCIDX_HDR*)lcCalloc(sizeof(TOCIDX_HDR)); if (! (pb = (BYTE*)m_pTocNodes->Offset(0)) ) return 0; // m_pHeader will be freeed in destructor. CopyMemory((PVOID)m_pHeader, (LPCVOID)pb, sizeof(TOCIDX_HDR)); } return(m_pHeader->dwOffsRootNode); } HRESULT CExTitle::GetRootNode(TOC_FOLDERNODE *pNode) { HRESULT hr = E_FAIL; DWORD dwNode; unsigned int uiWidth; const unsigned int *pdwITBits; CSubSet* pSS; dwNode = GetRootSlot(); do { if ( !SUCCEEDED(GetNode(dwNode, pNode)) ) return hr; if ( pNode->dwFlags & TOC_HAS_UNTYPED ) break; if ( (pSS = m_pCollection->m_pSubSets->GetTocSubset()) && pSS->m_bIsEntireCollection ) break; if ( pNode->dwFlags & TOC_FOLDER ) { if ( m_ITCnt > 31 ) { uiWidth = (((m_ITCnt / 32) + 1) * 4); pdwITBits = GetITBits(pNode->dwIT_Idx * uiWidth); } else pdwITBits = (const unsigned int *)&pNode->dwIT_Idx; } else pdwITBits = GetTopicITBits(pNode->dwOffsTopic); } while ( !m_pCollection->m_pSubSets->fTOCFilter(pdwITBits) && (dwNode = pNode->dwOffsNext) ); if ( dwNode ) hr = S_OK; return hr; } HRESULT CExTitle::GetNode(DWORD iNode, TOC_FOLDERNODE *pNode) { BYTE* pb; TOC_FOLDERNODE* pLocalNode; HRESULT hr = E_FAIL; if ( !iNode ) return hr; if (m_bOpen == FALSE) { if (OpenTitle() == FALSE) return hr; } if (!m_pTocNodes) { m_pTocNodes = new CPagedSubfile; if (FAILED(hr = m_pTocNodes->Open(this, txtTocIdxFile))) { delete m_pTocNodes; m_pTocNodes = NULL; return hr; } } if ( (pb = (BYTE*)m_pTocNodes->Offset(iNode)) ) { pLocalNode = (TOC_FOLDERNODE*)pb; if ( pLocalNode->dwFlags & TOC_FOLDER ) CopyMemory((PVOID)pNode, (LPCVOID)pb, sizeof(TOC_FOLDERNODE)); else { CopyMemory((PVOID)pNode, (LPCVOID)pb, sizeof(TOC_LEAFNODE)); pNode->dwOffsChild = 0; } hr = S_OK; } return hr; } // CExTitle::GetString() // const CHAR* CExTitle::GetString( DWORD dwOffset ) { HRESULT hr; const CHAR* pStr; if( !m_bOpen ) if( !OpenTitle() ) return NULL; if( !m_pStrTbl ) { m_pStrTbl = new CPagedSubfile; if( FAILED(hr = m_pStrTbl->Open(this,txtStringsFile)) ) { delete m_pStrTbl; m_pStrTbl = NULL; return NULL; } } pStr = (const CHAR*) m_pStrTbl->Offset( dwOffset ); return pStr; } // CExTitle::GetString() // HRESULT CExTitle::GetString( DWORD dwOffset, CHAR* psz, int cb ) { const CHAR* pStr = GetString( dwOffset ); if( pStr ) { strncpy( psz, pStr, cb ); psz[cb-1] = 0; return S_OK; } else return E_FAIL; } // CExTitle::GetString() // HRESULT CExTitle::GetString( DWORD dwOffset, CStr* pcsz ) { const CHAR* pStr = GetString( dwOffset ); if( pStr ) { *pcsz = pStr; return S_OK; } else return E_FAIL; } // CExTitle::GetString() // HRESULT CExTitle::GetString( DWORD dwOffset, WCHAR* pwsz, int cch ) { const CHAR* pStr = GetString( dwOffset ); if( pStr ) { MultiByteToWideChar( GetInfo()->GetCodePage(), 0, pStr, -1, pwsz, cch ); return S_OK; } else return E_FAIL; } // CExTitle::GetString() // HRESULT CExTitle::GetString( DWORD dwOffset, CWStr* pcwsz ) { const CHAR* pStr = GetString( dwOffset ); if( pStr ) { MultiByteToWideChar( GetInfo()->GetCodePage(), 0, pStr, -1, *pcwsz, -1 ); return S_OK; } else return E_FAIL; } // // CExTitle::InfoTypeFilter() // // Function determines if the given topic id (topic number) is part of the currently // selected InfoType profile. // // ENTRY: // dwTopic - Topic number. // // EXIT: // BOOL - TRUE if topic is part of currently selected infotype profile. FALSE if not. // BOOL CExTitle::InfoTypeFilter(CSubSet* pSubSet, DWORD dwTopic) { const unsigned int *pdwITBits; if (m_bOpen == FALSE) { if (OpenTitle() == FALSE) return FALSE; } pdwITBits = GetTopicITBits(dwTopic); return pSubSet->Filter(pdwITBits); } // // Given a topic number, fetch and return a pointer to the itbits // in the form of a DWORD* // const unsigned int * CExTitle::GetTopicITBits(DWORD dwTN) { TOC_TOPIC TopicData; unsigned int uiWidth; static unsigned int dwITBits; GetTopicData(dwTN, &TopicData); if ( m_ITCnt > 15 ) { uiWidth = (((m_ITCnt / 32) + 1) * 4); return (GetITBits(TopicData.wIT_Idx * uiWidth)); } else { dwITBits = 0; dwITBits = TopicData.wIT_Idx; return &dwITBits; } } // // Given an offset into the ITBITS subfile, fetch and return a pointer to the itbits // in the form of a DWORD* // const unsigned int * CExTitle::GetITBits(DWORD dwOffsBits) { if (! m_pITBits ) { m_pITBits = new CPagedSubfile; if (FAILED(m_pITBits->Open(this, txtITBits))) { delete m_pITBits; m_pITBits = NULL; return NULL; } } // // Compute the width in DWORDS and get the bits. // return (const unsigned int *)m_pITBits->Offset(dwOffsBits); } // CExTitle::GetTopicData() // HRESULT CExTitle::GetTopicData(DWORD dwTopic, TOC_TOPIC * pTopicData) { HRESULT hr; BYTE * pb; if (m_bOpen == FALSE) { if (OpenTitle() == FALSE) return E_FAIL; } if (!m_pTopics) { m_pTopics = new CPagedSubfile; if (FAILED(hr = m_pTopics->Open(this, txtTopicsFile))) { delete m_pTopics; m_pTopics = NULL; return hr; } } pb = (BYTE*)m_pTopics->Offset(dwTopic * sizeof(TOC_TOPIC)); if (pb) { memcpy(pTopicData, pb, sizeof(TOC_TOPIC)); return S_OK; } else return E_FAIL; } HRESULT CExTitle::GetTopicName(DWORD dwTopic, CHAR* pszTitle, int cb) { TOC_TOPIC topic; HRESULT hr; if (SUCCEEDED(hr = GetTopicData(dwTopic, &topic))) return GetString(topic.dwOffsTitle, pszTitle, cb); else return hr; } HRESULT CExTitle::GetTopicName(DWORD dwTopic, WCHAR* pwszTitle, int cch) { TOC_TOPIC topic; HRESULT hr; if (SUCCEEDED(hr = GetTopicData(dwTopic, &topic))) return GetString(topic.dwOffsTitle, pwszTitle, cch); else return hr; } HRESULT CExTitle::GetTopicLocation(DWORD dwTopic, CHAR* pszLocation, int cb) { CTitleInformation* pInfo = GetInfo(); if( pInfo ) { const CHAR* psz = NULL; psz = pInfo->GetDefaultCaption(); if( !psz || !*psz ) psz = pInfo->GetShortName(); if( psz && *psz ) { strncpy( pszLocation, psz, cb ); pszLocation[cb-1] = 0; return S_OK; } } return E_FAIL; } HRESULT CExTitle::GetTopicLocation(DWORD dwTopic, WCHAR* pwszLocation, int cch) { CTitleInformation* pInfo = GetInfo(); if( pInfo ) { const CHAR* psz = NULL; psz = pInfo->GetDefaultCaption(); if( !psz || !*psz ) psz = pInfo->GetShortName(); if( psz && *psz ) { MultiByteToWideChar( GetInfo()->GetCodePage(), 0, psz, -1, pwszLocation, cch ); return S_OK; } } return E_FAIL; } // // GetUrlTocSlot(); // // Translates a URL from this title into it's corisponding offset into the TOC. // Function will also return the Topic number if desired in reference pdwTopicNumber. // HRESULT CExTitle::GetUrlTocSlot(const CHAR* pszURL, DWORD* pdwSlot, DWORD* pdwTopicNumber) { char szURL[MAX_URL]; HRESULT hr = E_FAIL; strncpy( szURL, pszURL, MAX_URL ); szURL[MAX_URL-1] = 0; // Remove all of the stuff from the url. NormalizeUrlInPlace(szURL) ; TOC_TOPIC Topic; if (SUCCEEDED(URL2Topic(szURL, &Topic, pdwTopicNumber))) { *pdwSlot = Topic.dwOffsTOC_Node; return S_OK; } CHAR* pszInterTopic = NULL; if (pszInterTopic = StrChr(szURL, '#')) { *pszInterTopic = NULL; if (SUCCEEDED(URL2Topic(szURL, &Topic, pdwTopicNumber))) { *pdwSlot = Topic.dwOffsTOC_Node; return S_OK; } } return E_FAIL; } // // GetURLTreeNode() // // Translates a URL from this title into it's corisponding node in the TOC. // HRESULT CExTitle::GetURLTreeNode(const CHAR* pszURL, CTreeNode** ppTreeNode, BOOL bNormalize /* = TRUE */) { char szURL[MAX_URL]; HRESULT hr = E_FAIL; strncpy( szURL, pszURL, MAX_URL ); szURL[MAX_URL-1] = 0; // Remove all of the stuff from the url. if (bNormalize) NormalizeUrlInPlace(szURL) ; TOC_TOPIC Topic; if (SUCCEEDED(URL2Topic(szURL, &Topic))) { TOC_FOLDERNODE Node; if (SUCCEEDED(GetNode(Topic.dwOffsTOC_Node, &Node))) { *ppTreeNode = new CExNode(&Node, this); hr = S_OK; } } return hr; } // // Slot2TreeNode(DWORD dwSlot) // // Returns a tree node given a TOC "slot" number. A slot number is just the // offset into the #TOCIDX subfile. // HRESULT CExTitle::Slot2TreeNode(DWORD dwSlot, CTreeNode** ppTreeNode) { TOC_FOLDERNODE Node; if ( !IsBadReadPtr(this, sizeof(CExTitle)) && SUCCEEDED(GetNode(dwSlot, &Node))) { // *ppTreeNode = new CExNode(&Node, this, dwSlot); *ppTreeNode = new CExNode(&Node, this); return S_OK; } return E_FAIL; } HRESULT CExTitle::GetTopicURL(DWORD dwTopic, CHAR* pszURL, int cb, BOOL bFull ) { TOC_TOPIC topic; HRESULT hr; CExTitle* pTitle; CHAR* psz; if (m_bOpen == FALSE) { if (OpenTitle() == FALSE) return E_FAIL; } if (!m_pUrlTbl) { m_pUrlTbl = new CPagedSubfile; if (FAILED(hr = m_pUrlTbl->Open(this, txtUrlTblFile))) { delete m_pUrlTbl; m_pUrlTbl = NULL; return hr; } } if (!m_pUrlStrings) { m_pUrlStrings = new CPagedSubfile; if (FAILED(hr = m_pUrlStrings->Open(this, txtUrlStrFile))) { delete m_pUrlStrings; m_pUrlStrings = NULL; return hr; } } if ( (hr = GetTopicData(dwTopic, &topic)) == S_OK ) { PCURL pUrlTbl; if ( (pUrlTbl = (PCURL)m_pUrlTbl->Offset(topic.dwOffsURL)) ) { PURLSTR purl = (PURLSTR) m_pUrlStrings->Offset(pUrlTbl->dwOffsURL); if (purl) { // If not an interfile jump, the create the full URL // if (! StrChr(purl->szURL, ':')) { /* * 22-Oct-1997 [ralphw] ASSERT on this rather then * checking in retail, because if the caller is using * MAX_URL or INTERNET_MAX_URL_LENGTH then an overflow * will never happen. */ pTitle = this; psz = purl->szURL; qualify: ASSERT((strlen(psz) + strlen((g_bMsItsMonikerSupport ? txtMsItsMoniker : txtMkStore)) + strlen(pTitle->GetPathName()) + 7) < (size_t) cb); if ((int) (strlen(psz) + strlen((g_bMsItsMonikerSupport ? txtMsItsMoniker : txtMkStore)) + strlen(pTitle->GetPathName()) + 7) > cb ) return E_OUTOFMEMORY; strncpy(pszURL, (g_bMsItsMonikerSupport ? txtMsItsMoniker : txtMkStore), cb); pszURL[cb-1] = 0; if( bFull ) strcat(pszURL, pTitle->GetPathName()); else strcat(pszURL, pTitle->GetFileName()); if (*psz != '/') strcat(pszURL, txtSepBack); else strcat(pszURL, txtDoubleColonSep); strcat(pszURL, psz); } else { // Check for a URL of this type: vb98.chm::\foo\bar\cat.htm // if ( psz = StrStr(purl->szURL, txtChmColon) ) { psz += 4; *psz = '\0'; pTitle = m_pCollection->TitleFromChmName(purl->szURL); *psz = ':'; if ( pTitle ) { psz += 2; goto qualify; } } // BUGBUG: we do the check here to see if the CHM exists, // and if not, switch to the alternate URL (if there is one). strncpy(pszURL, purl->szURL, cb); pszURL[cb-1] = 0; } hr = S_OK; } } } return hr; } // // // // we now have to support a new URL format for compiled files. The format is: // // mk:@MSITStore:mytitle.chm::/dir/mytopic.htm // // where "mk:@MSITStore:" can take any one of the many forms of our URL // prefix and it may be optional. The "mytitle.chm" substring may or // may not be a full pathname to the title. The remaining part is simply // the pathname inside of the compiled title. // // When the URL is in the format, we need to change it to the fully // qualified URL format which is: // // mk:@MSITStore:c:\titles\mytitle.chm::/dir/mytopic.htm // HRESULT CExTitle::ConvertURL( const CHAR* pszURLIn, CHAR* pszURLOut ) { HRESULT hr = S_OK; // prefix if( IsSamePrefix(pszURLIn, txtMkStore, (int)strlen(txtMkStore) - 1) ) strcpy( pszURLOut, txtMkStore ); else if( IsSamePrefix(pszURLIn, txtMsItsMoniker, (int)strlen(txtMsItsMoniker) - 1) ) strcpy( pszURLOut, txtMsItsMoniker ); else if( IsSamePrefix(pszURLIn, txtItsMoniker, (int)strlen(txtItsMoniker) - 1) ) strcpy( pszURLOut, txtItsMoniker ); else { strcpy( pszURLOut, pszURLIn ); return hr; } // title full pathname CStr szPathName; ConvertSpacesToEscapes( m_ContentFileName.psz, &szPathName ); strcat( pszURLOut, szPathName.psz ); // subfile pathname char* pszTail = strstr( pszURLIn, "::" ); if( pszTail ) strcat( pszURLOut, pszTail ); else strcpy( pszURLOut, pszURLIn ); return hr; } // CExTitle::URL2TopicNumber // // Translate a URL into a topic number. // HRESULT CExTitle::URL2Topic(const CHAR* pszURL, TOC_TOPIC* pTopic, DWORD* pdwTN) { static int iPageCnt = (PAGE_SIZE / sizeof(CURL)); DWORD dwOffs; HRESULT hr = E_FAIL; HASH dwHash; PCURL pCurl; int mid,low = 0; if (m_bOpen == FALSE) { if (OpenTitle() == FALSE) return E_FAIL; } //[ZERO IDXHDR] if (!IsIdxHeaderValid()) { return E_FAIL; } int high = m_pIdxHeader->cTopics - 1; if (!m_pUrlTbl) { m_pUrlTbl = new CPagedSubfile; if (FAILED(hr = m_pUrlTbl->Open(this,txtUrlTblFile))) { delete m_pUrlTbl; m_pUrlTbl = NULL; return hr; } } // // Hash it and find it! dwHash = HashFromSz(pszURL); while ( low <= high ) { mid = ((low + high) / 2); dwOffs = (((mid / iPageCnt) * PAGE_SIZE) + ((mid % iPageCnt) * sizeof(CURL))); if (! (pCurl = (PCURL)m_pUrlTbl->Offset(dwOffs)) ) // Read the data in. return hr; if ( pCurl->dwHash == dwHash ) { if ( pdwTN ) { *pdwTN = pCurl->dwTopicNumber; hr = S_OK ; // This part succeeded. we may fail the GetTopicData below. } if ( pTopic ) // Found it! hr = GetTopicData(pCurl->dwTopicNumber, pTopic); break; } else if ( pCurl->dwHash > dwHash ) high = mid - 1; else low = mid + 1; } return hr; } //////////////////////////////////////////////////////// // GetVolumeOrder // // returned value in puiVolumeOrder is: // 0 = local drive // 1-N = Removable media order (CD1, CD2, etc.) // -1 = error HRESULT CExTitle::GetVolumeOrder( UINT* puiVolumeOrder, UINT uiFileType ) { HRESULT hr = S_OK; if( m_uiVolumeOrder == (UINT) -1 ) { if( m_pCollection->IsSingleTitle() ) return E_FAIL; // Get the location information LOCATIONHISTORY* pLocationHistory = GetUsedLocation(); // Get the pathname const CHAR* pszPathname = NULL; if( uiFileType == HHRMS_TYPE_TITLE ) pszPathname = GetPathName(); else if( uiFileType == HHRMS_TYPE_COMBINED_QUERY ) pszPathname = GetQueryName(); else return E_FAIL; // Get the location identifier const CHAR* pszLocation = NULL; CLocation* pLocation = NULL; if( pLocationHistory ) { pszLocation = pLocationHistory->LocationId; pLocation = m_pCollection->m_Collection.FindLocation( pszLocation, puiVolumeOrder ); m_uiVolumeOrder = *puiVolumeOrder; } // Get the location path const CHAR* pszPath = NULL; CHAR szPath[MAX_PATH]; szPath[0]= 0; if( pLocation ) { pszPath = pLocation->GetPath(); strcpy( szPath, pszPath ); } // get the drive of the path CHAR szDriveRoot[4]; strncpy( szDriveRoot, szPath, 4 ); szDriveRoot[3] = '\0'; // make sure to add a backslash if the second char is a colon // sometimes we will get just "d:" instead of "d:\" and thus // GetDriveType will fail under this circumstance if( szDriveRoot[1] == ':' ) { szDriveRoot[2] = '\\'; szDriveRoot[3] = 0; } // Get media type UINT uiDriveType = DRIVE_UNKNOWN; if( szDriveRoot[1] == ':' && szDriveRoot[2] == '\\' ) { uiDriveType = GetDriveType(szDriveRoot); } else if( szDriveRoot[0] == '\\' && szDriveRoot[1] == '\\' ) { uiDriveType = DRIVE_REMOTE; } // handle the drive types switch( uiDriveType ) { case DRIVE_REMOTE: case DRIVE_FIXED: case DRIVE_RAMDISK: m_uiVolumeOrder = 0; break; case DRIVE_REMOVABLE: case DRIVE_CDROM: //m_uiVolumeOrder = 1; // set to one for now break; case DRIVE_UNKNOWN: case DRIVE_NO_ROOT_DIR: return E_FAIL; break; default: return E_FAIL; } } *puiVolumeOrder = m_uiVolumeOrder; if( m_uiVolumeOrder == (UINT) -1 ) hr = E_FAIL; return hr; } ////////////////////////////////////////////////////////////////////////// // // Translate a ContextID into a URL. // // HRESULT CExTitle::ResolveContextId(DWORD id, CHAR** ppszURL) { CSubFileSystem* pCSubFS; int cbMapIds, cbRead; if (!m_bOpen) { if (OpenTitle() == FALSE) return E_FAIL; } if ( !m_pMapIds ) { pCSubFS = new CSubFileSystem(m_pCFileSystem); if(FAILED(pCSubFS->OpenSub(txtMAP))) { delete pCSubFS; return HH_E_NOCONTEXTIDS; } if ( (pCSubFS->ReadSub(&cbMapIds, sizeof(DWORD), (ULONG*)&cbRead) != S_OK) || (cbRead != sizeof(DWORD)) ) { delete pCSubFS; return HH_E_NOCONTEXTIDS; } if (! (m_pMapIds = (MAPPED_ID*)lcMalloc(cbMapIds)) ) { delete pCSubFS; return E_FAIL; } if ( (pCSubFS->ReadSub(m_pMapIds, cbMapIds, (ULONG*)&cbRead) != S_OK) || (cbRead != cbMapIds) ) { delete pCSubFS; lcFree(m_pMapIds); m_pMapIds = NULL; return HH_E_NOCONTEXTIDS; } m_cMapIds = cbMapIds / sizeof(MAPPED_ID); } // // Translate the id into a URL... // for (int i = 0; i < m_cMapIds; i++) { if ( id == m_pMapIds[i].idTopic ) { *ppszURL = (CHAR*)GetString(m_pMapIds[i].offUrl); return S_OK; } } return HH_E_CONTEXTIDDOESNTEXIT; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // CTreeNode implementation CTreeNode::CTreeNode() { m_Expanded = FALSE; SetError(0); } CTreeNode::~CTreeNode() { } BOOL CTreeNode::Compare(CTreeNode *pOtherNode) { if (m_ObjType != pOtherNode->GetObjType()) return FALSE; switch (m_ObjType) { case EXFOLDERNODE: if (((CExFolderNode *)this)->GetFolder() == ((CExFolderNode *)pOtherNode)->GetFolder()) return TRUE; return FALSE; case EXTITLENODE: if (((CExTitleNode *)this)->GetFolder() == ((CExTitleNode *)pOtherNode)->GetFolder() && ((CExTitleNode *)this)->GetTitle() == ((CExTitleNode *)pOtherNode)->GetTitle()) return TRUE; return FALSE; case EXNODE: case EXMERGEDNODE: CExTitle *p1, *p2; p1 = ((CExNode *)this)->GetTitle(); p2 =((CExNode *)pOtherNode)->GetTitle(); if (p1 == p2 && ((CExNode *)this)->m_Node.dwFlags == ((CExNode *)pOtherNode)->m_Node.dwFlags && ((CExNode *)this)->m_Node.dwOffsTopic == ((CExNode *)pOtherNode)->m_Node.dwOffsTopic && ((CExNode *)this)->m_Node.dwOffsParent == ((CExNode *)pOtherNode)->m_Node.dwOffsParent && ((CExNode *)this)->m_Node.dwOffsNext == ((CExNode *)pOtherNode)->m_Node.dwOffsNext && ((CExNode *)this)->m_Node.dwOffsChild == ((CExNode *)pOtherNode)->m_Node.dwOffsChild) return TRUE; return FALSE; } return FALSE; } CTreeNode *CTreeNode::GetExNode(TOC_FOLDERNODE *pNode, CExTitle *pTitle) { if (pNode->dwFlags & TOC_MERGED_REF) { // read chm file from string file CStr cStr; if (SUCCEEDED(pTitle->GetString(pNode->dwOffsChild, &cStr))) { // search title list CExTitle *pCur = pTitle->m_pCollection->GetFirstTitle(); while (pCur) { if (strcmp(cStr, FindFilePortion(pCur->GetPathName())) == 0) { CExMergedTitleNode *pT; pT = new CExMergedTitleNode(pNode, pCur); if (pT != NULL) return pT; } pCur = pCur->GetNext(); } } return GetNextSibling(pNode); } else { CExNode *p = new CExNode(pNode, pTitle); if (p == NULL) return GetNextSibling(pNode); return p; } } void CTreeNode::SetError(DWORD dw) { m_Error = dw; } DWORD CTreeNode::GetLastError() { return m_Error; } BOOL CTreeNode::GetURL(CHAR* pszURL, unsigned cb, BOOL bFull) { return FALSE; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // CExFolderNode implementation CExFolderNode::CExFolderNode(CFolder *p, CExCollection *pCollection) { SetObjType(EXFOLDERNODE); m_pCollection = pCollection; m_pFolder = p; } CExFolderNode::~CExFolderNode() { } CTreeNode *CExFolderNode::GetFirstChild(DWORD* pdwSlot) { CStructuralSubset* pSS = NULL; CFolder *p = m_pFolder->GetFirstChildFolder(); CTreeNode *pN; // implement structural subsetting here. if( m_pCollection && m_pCollection->m_pSSList ) pSS = m_pCollection->m_pSSList->GetTOC(); while (p) { if ((pN = m_pCollection->CheckForTitleNode(p))) { if ( !pSS || pSS->IsEntire() || p->bIsVisable() ) return pN; else delete pN; // leak fix } p = p->GetNextFolder(); } return NULL; } CTreeNode *CExFolderNode::GetNextSibling(TOC_FOLDERNODE *pAltNode, DWORD* pdwSlot) { CStructuralSubset* pSS = NULL; CFolder *p = m_pFolder->GetNextFolder(); CTreeNode *pN; // implement structural subsetting here. if( m_pCollection && m_pCollection->m_pSSList ) pSS = m_pCollection->m_pSSList->GetTOC(); while (p) { if ((pN = m_pCollection->CheckForTitleNode(p))) { if ( !pSS || pSS->IsEntire() || p->bIsVisable() ) return pN; else delete pN; // leak fix } p = p->GetNextFolder(); } return NULL; } CTreeNode *CExFolderNode::GetParent(DWORD* pdwSlot, BOOL bDirectParent) { CFolder *p = m_pFolder->GetParent(); if (p->GetTitle() == NULL) return NULL; if (p) { return m_pCollection->CheckForTitleNode(p); } return NULL; } HRESULT CExFolderNode::GetTopicName(CHAR* pszTitle, int cb) { CHAR* psz = m_pFolder->GetTitle(); if (strlen(psz) + 1 > (size_t) cb) return E_FAIL; else strcpy(pszTitle, psz); return S_OK; } HRESULT CExFolderNode::GetTopicName( WCHAR* pwszTitle, int cch ) { CHAR* psz = m_pFolder->GetTitle(); if( (2*(strlen(psz) + 1)) > (size_t) cch ) return E_FAIL; else { UINT CodePage = m_pCollection->GetMasterTitle()->GetInfo()->GetCodePage(); MultiByteToWideChar( CodePage, 0, psz, -1, pwszTitle, cch ); } return S_OK; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // CExTitleNode implementation CExTitleNode::CExTitleNode(CExTitle *pTitle, CFolder *p) { SetObjType(EXTITLENODE); m_pTitle = pTitle; m_pFolder = p; } #if 0 CExTitleNode::~CExTitleNode() { } #endif CTreeNode * CExTitleNode::GetFirstChild(DWORD* pdwSlot) { TOC_FOLDERNODE Node; if ( !SUCCEEDED(m_pTitle->GetRootNode(&Node)) ) return NULL; if ( pdwSlot ) *pdwSlot = m_pTitle->GetRootSlot(); return GetExNode(&Node, m_pTitle); } CTreeNode * CExTitleNode::GetNextSibling(TOC_FOLDERNODE *pAltNode, DWORD* pdwSlot) { CTreeNode *pN; CStructuralSubset* pSS = NULL; if (m_pFolder == NULL) return NULL; CFolder *p = m_pFolder->GetNextFolder(); // implement structural subsetting here. if( m_pTitle && m_pTitle->m_pCollection && m_pTitle->m_pCollection->m_pSSList ) pSS = m_pTitle->m_pCollection->m_pSSList->GetTOC(); while (p) { if ((pN = m_pTitle->m_pCollection->CheckForTitleNode(p))) { if ( !pSS || pSS->IsEntire() || p->bIsVisable() ) return pN; else delete pN; // leak fix } p = p->GetNextFolder(); } return NULL; } CTreeNode * CExTitleNode::GetParent(DWORD* pdwSlot, BOOL bDirectParent) { CFolder *p = m_pFolder->GetParent(); if (p->GetTitle() == NULL) return NULL; if (p) { return m_pTitle->m_pCollection->CheckForTitleNode(p); } return NULL; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // CExMergedTitleNode implementation CExMergedTitleNode::CExMergedTitleNode(TOC_FOLDERNODE *p, CExTitle *pTitle) : CExNode(p, pTitle) { SetObjType(EXMERGEDNODE); } CExMergedTitleNode::~CExMergedTitleNode() { } CTreeNode * CExMergedTitleNode::GetFirstChild(DWORD* pdwSlot) { TOC_FOLDERNODE Node; if (FAILED(GetTitle()->GetRootNode(&Node))) return NULL; return GetExNode(&Node, GetTitle()); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // CExNode implementation CExNode::CExNode(TOC_FOLDERNODE *p, CExTitle *pTitle) { SetObjType(EXNODE); if (p) memcpy(&m_Node, p, sizeof(TOC_FOLDERNODE)); m_pTitle = pTitle; } #if 0 CExNode::~CExNode() { } #endif DWORD CExNode::GetType() { if ( (m_Node.dwFlags & TOC_FOLDER) ) { if ( (m_Node.dwFlags & TOC_TOPIC_NODE) ) return CONTAINER; // Topic Folder. else { if ( m_Node.dwOffsChild ) return FOLDER; else return BOGUS_FOLDER; } } // Folder Only. return TOPIC; // Topic Only. } HRESULT CExNode::GetTopicName(CHAR* pszTitle, int cb) { HRESULT hr = S_OK; if (! (m_Node.dwFlags & TOC_TOPIC_NODE) ) hr |= m_pTitle->GetString(m_Node.dwOffsTopic, pszTitle, cb); else hr |= m_pTitle->GetTopicName(m_Node.dwOffsTopic, pszTitle, cb); return hr; } HRESULT CExNode::GetTopicName( WCHAR* pwszTitle, int cch ) { HRESULT hr = S_OK; if (! (m_Node.dwFlags & TOC_TOPIC_NODE) ) hr |= m_pTitle->GetString(m_Node.dwOffsTopic, pwszTitle, cch); else hr |= m_pTitle->GetTopicName(m_Node.dwOffsTopic, pwszTitle, cch); return hr; } CTreeNode *CExNode::GetFirstChild(DWORD* pdwSlot) { unsigned int uiWidth; const unsigned int *pdwITBits; DWORD dwOffsChild; CSubSet* pSS; if (m_Node.dwFlags & TOC_HAS_CHILDREN) { TOC_FOLDERNODE Node; dwOffsChild = m_Node.dwOffsChild; do { if ( !SUCCEEDED(m_pTitle->GetNode(dwOffsChild, &Node)) ) return NULL; if ( Node.dwFlags & TOC_HAS_UNTYPED ) break; if ( (pSS = m_pTitle->m_pCollection->m_pSubSets->GetTocSubset()) && pSS->m_bIsEntireCollection ) break; if ( Node.dwFlags & TOC_FOLDER ) { if ( m_pTitle->m_ITCnt > 31 ) { uiWidth = (((m_pTitle->m_ITCnt / 32) + 1) * 4); pdwITBits = m_pTitle->GetITBits(Node.dwIT_Idx * uiWidth); } else pdwITBits = (const unsigned int *)&Node.dwIT_Idx; } else pdwITBits = m_pTitle->GetTopicITBits(Node.dwOffsTopic); } while ( !m_pTitle->m_pCollection->m_pSubSets->fTOCFilter(pdwITBits) && (dwOffsChild = Node.dwOffsNext) ); if ( dwOffsChild ) { if ( pdwSlot ) *pdwSlot = dwOffsChild; return GetExNode(&Node, m_pTitle); } } return NULL; } CTreeNode *CExNode::GetNextSibling(TOC_FOLDERNODE *pAltNode, DWORD* pdwSlot) { TOC_FOLDERNODE Node; DWORD dwNext = (pAltNode ? pAltNode->dwOffsNext : m_Node.dwOffsNext); unsigned int uiWidth; const unsigned int *pdwITBits; CSubSet* pSS; do { if ( !SUCCEEDED(m_pTitle->GetNode(dwNext, &Node)) ) return NULL; if ( Node.dwFlags & TOC_HAS_UNTYPED ) break; if ( (pSS = m_pTitle->m_pCollection->m_pSubSets->GetTocSubset()) && pSS->m_bIsEntireCollection ) break; if ( Node.dwFlags & TOC_FOLDER ) { if ( m_pTitle->m_ITCnt > 31 ) { uiWidth = (((m_pTitle->m_ITCnt / 32) + 1) * 4); pdwITBits = m_pTitle->GetITBits(Node.dwIT_Idx * uiWidth); } else pdwITBits = (const unsigned int *)&Node.dwIT_Idx; } else pdwITBits = m_pTitle->GetTopicITBits(Node.dwOffsTopic); } while ( !m_pTitle->m_pCollection->m_pSubSets->fTOCFilter(pdwITBits) && (dwNext = Node.dwOffsNext) ); if ( dwNext ) { if ( pdwSlot ) *pdwSlot = dwNext; return GetExNode(&Node, m_pTitle); } return NULL; } CTreeNode *CExNode::GetParent(DWORD* pdwSlot, BOOL bDirectParent) { TOC_FOLDERNODE Node; if (m_Node.dwOffsParent) { if ( !SUCCEEDED(m_pTitle->GetNode(m_Node.dwOffsParent, &Node)) ) return NULL; if ( pdwSlot ) *pdwSlot = m_Node.dwOffsParent; return GetExNode(&Node, m_pTitle); } else { // check if this is a merged title if (m_pTitle->GetNodeOffsetInParentTitle()) { // assume that the first title in the collection is the master title CExTitle * pParent; pParent = m_pTitle->m_pCollection->GetFirstTitle(); if (!pParent) return NULL; if ( !SUCCEEDED(pParent->GetNode(m_pTitle->GetNodeOffsetInParentTitle(), &Node)) ) return NULL; if ( pdwSlot ) *pdwSlot = m_pTitle->GetNodeOffsetInParentTitle(); return GetExNode(&Node, pParent); } else { // is this a collection if (m_pTitle->m_pCollection->IsSingleTitle() == FALSE) { CFolder *p = m_pTitle->m_pCollection->FindTitleFolder(m_pTitle); if (!p) return NULL; CTreeNode *pTitle; if (pTitle = m_pTitle->m_pCollection->CheckForTitleNode(p)) { if (bDirectParent == TRUE) return pTitle; CTreeNode *p = pTitle->GetParent(pdwSlot); delete pTitle; return p; } } } } return NULL; } BOOL CExNode::GetURL(CHAR* pszURL, unsigned cb, BOOL bFull) { if ( (m_Node.dwFlags & TOC_TOPIC_NODE) ) if ( SUCCEEDED(m_pTitle->GetTopicURL(m_Node.dwOffsTopic, pszURL, cb, bFull)) ) return TRUE; return FALSE; }