Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

3676 lines
98 KiB

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// 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; i<m_pSubSets->HowManySubSets(); 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 <io.h>
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: <mikecole> 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: <mikecole> 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;
}
// <mc>
// 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.
//
// </mc>
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;
}