3677 lines
98 KiB
C++
3677 lines
98 KiB
C++
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// 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;
|
|
}
|