|
|
// FTS.CPP : implementation file
//
// by DougO
//
#include "header.h"
#include "stdio.h"
#include "string.h"
#include "TCHAR.h"
#ifdef HHCTRL
#include "parserhh.h"
#else
#include "parser.h"
#endif
#include "collect.h"
#include "hhtypes.h"
#include "toc.h"
#include "highlite.h"
#include "lockout.h"
#include "userwait.h"
#include "fts.h"
#include "hhfinder.h"
#include "csubset.h"
#include "subset.h"
#ifdef _DEBUG
#undef THIS_FILE
static const char THIS_FILE[] = __FILE__; #endif
#include "ftsnames.cpp"
#define CENTAUR_TERMS 1
/*************************************************************************
* * SINGLE TO DOUBLE-WIDTH KATAKANA MAPPING ARRAY * *************************************************************************/
// Single-Width to Double-Width Mapping Array
//
static const int mtable[][2]={ {129,66},{129,117},{129,118},{129,65},{129,69},{131,146},{131,64}, {131,66},{131,68},{131,70},{131,72},{131,131},{131,133},{131,135}, {131,98},{129,91},{131,65},{131,67},{131,69},{131,71},{131,73}, {131,74},{131,76},{131,78},{131,80},{131,82},{131,84},{131,86}, {131,88},{131,90},{131,92},{131,94},{131,96},{131,99},{131,101}, {131,103},{131,105},{131,106},{131,107},{131,108},{131,109}, {131,110},{131,113},{131,116},{131,119},{131,122},{131,125}, {131,126},{131,128},{131,129},{131,130},{131,132},{131,134}, {131,136},{131,137},{131,138},{131,139},{131,140},{131,141}, {131,143},{131,147},{129,74},{129,75} };
// note, cannot put in .text since the pointers themselves are uninitialized
static const char* pJOperatorList[] = {"","?�?�??","?�??","?�?�??","?�???�??","?m?d?`?q","?n?q","?`?m?c","?m?n?s",""}; static const char* pEnglishOperator[] = {"","and " ,"or " ,"not " ,"near " ,"NEAR " ,"OR " ,"AND " ,"NOT " ,""};
int FASTCALL CompareVolumeOrder( const void* p1, const void* p2 ) { int iReturn;
TITLE_ENTRY* pEntry1= (TITLE_ENTRY*) p1; TITLE_ENTRY* pEntry2= (TITLE_ENTRY*) p2;
if( pEntry1->uiVolumeOrder < pEntry2->uiVolumeOrder ) iReturn = -1; else if ( pEntry1->uiVolumeOrder > pEntry2->uiVolumeOrder ) iReturn = 1; else iReturn = 0;
return iReturn; }
/////////////////////////////////////////////////////////////////////////////
// CFullTextSearch Class
//
// This class provides full-text search functionality to HTML Help. This
// class encapsulates multi-title searches and combined indexes.
//
/////////////////////////////////////////////////////////////////////////////
// CFullTextSearch class constructor
//
CFullTextSearch::CFullTextSearch(CExCollection *pTitleCollection) { m_bInit = FALSE; m_bTitleArrayInit = FALSE; m_SearchActive = FALSE; m_InitFailed = FALSE; m_pTitleCollection = pTitleCollection; m_pTitleArray = NULL; m_bMergedChmSetWithCHQ = FALSE; m_SystemLangID = PRIMARYLANGID(GetSystemDefaultLangID()); }
/////////////////////////////////////////////////////////////////////////////
// CFullTextSearch class destructor
//
CFullTextSearch::~CFullTextSearch() { TermListRemoveAll();
INT iTitle, iTitle2;
// Delete the CCombinedFTS objects
//
if(m_pTitleArray) { for(iTitle = 0; iTitle < m_TitleArraySize; ++iTitle) { if(m_pTitleArray[iTitle].pCombinedFTI) { CCombinedFTS *pObject = m_pTitleArray[iTitle].pCombinedFTI;
for(iTitle2 = 0; iTitle2 < m_TitleArraySize; ++iTitle2) { if(m_pTitleArray[iTitle2].pCombinedFTI == pObject) m_pTitleArray[iTitle2].pCombinedFTI = NULL; }
delete pObject; }
// cleanup strings in title array
CHECK_AND_FREE( m_pTitleArray[iTitle].pszQueryName ); CHECK_AND_FREE( m_pTitleArray[iTitle].pszIndexName ); CHECK_AND_FREE( m_pTitleArray[iTitle].pszShortName );
} } if(m_pTitleArray) lcFree(m_pTitleArray); }
/////////////////////////////////////////////////////////////////////////////
// CFullTextSearch initialization
//
BOOL CFullTextSearch::Initialize() { if(m_InitFailed) return FALSE;
if(m_bInit) return TRUE;
m_InitFailed = TRUE; // init here
m_bInit = TRUE; m_InitFailed = FALSE;
ZeroMemory(m_HLTermArray, sizeof(m_HLTermArray)); m_iHLIndex = 0;
m_lMaxRowCount = 500; m_wQueryProximity = 8;
// DOUGO - insert code here to init system language member
return TRUE; }
/////////////////////////////////////////////////////////////////////////////
// CFullTextSearch simple query
//
// pszQuery pointer to null terminated query string.
//
// pcResultCount pointer to variable to receive results count.
//
// ppSearchResults pointer to a SEARCH_RESULT pointer to receive
// results list. Upon return, this pointer will
// point to an array of SEARCH_RESULTS structures
// of size pcResultCount.
//
HRESULT CFullTextSearch::SimpleQuery(WCHAR *pszQuery, int *pcResultCount, SEARCH_RESULT **ppSearchResults) { if(!m_bInit) return FTS_NOT_INITIALIZED;
return ComplexQuery(pszQuery, FTS_ENABLE_STEMMING, pcResultCount, ppSearchResults, NULL); } /////////////////////////////////////////////////////////////////////////////
// CFullTextSearch complex query
//
HRESULT CFullTextSearch::ComplexQuery(WCHAR *pszQuery, DWORD dwFlags, int *pcResultCount, SEARCH_RESULT **ppSearchResults, CSubSet *pSubSet) { int wcb = 0, cb = 0; HRESULT hr;
*ppSearchResults = NULL; *pcResultCount = 0;
if(!m_bInit) return E_FAIL;
if(!pszQuery) return E_FAIL;
CUWait cWaitDlg(GetActiveWindow());
// no chars in string
//
if(!*pszQuery) return S_OK;
// Initialize the title array
//
if(!m_bTitleArrayInit) { InitTitleArray(); }
// Make sure title array exists
//
if(!m_pTitleArray) return E_FAIL;
INT iTitle;
// reset the results flags (this flag is set when a title has results to collect)
//
for(iTitle = 0; iTitle < m_TitleArraySize; ++iTitle) { m_pTitleArray[iTitle].bHasResults = FALSE; m_pTitleArray[iTitle].bAlreadyQueried = FALSE; }
BOOL bDbcsQuery = FALSE; char *pszDbcsQuery = NULL;
WCHAR *pwsBuffer = NULL; pwsBuffer = lcStrDupW(pszQuery); if(!pwsBuffer) return E_FAIL;
TermListRemoveAll();
AddQueryToTermList(pwsBuffer);
// Add field identifier to query (VFLD 0 = full content, VFLD 1 = title only)
//
wcb = lstrlenW(pwsBuffer)*2; WCHAR *pwsField = (WCHAR *) lcMalloc(wcb+22);
if(!pwsField) return E_FAIL;
// check for title only search
//
wcscpy(pwsField,L"("); if(dwFlags & FTS_TITLE_ONLY) wcscat(pwsField,L"VFLD 1 "); else wcscat(pwsField,L"VFLD 0 ");
wcscat(pwsField,pwsBuffer); wcscat(pwsField,L")"); lcFree(pwsBuffer); pwsBuffer = pwsField;
// Append the subset filter query to the user query
//
// Construct the subset query based on the current FTS subset.
//
WCHAR szSubsetFilter[65534]; DWORD dwSubsetTitleCount = 0; CStructuralSubset *pSubset;
// pre-load combined index for merged chm sets (NT5)
//
if(m_bMergedChmSetWithCHQ) LoadCombinedIndex(0);
// copy the user query into buffer larger enough for subset add-on query
//
wcscpy(szSubsetFilter, pwsBuffer);
// Create subset for titles in current merged chm set when running with
// combined index outside of collection (NT5 feature).
//
if(!(dwFlags & FTS_SEARCH_PREVIOUS) && m_bMergedChmSetWithCHQ) { wcscat(szSubsetFilter,L" AND (VFLD 1 "); for(iTitle = 0; iTitle < m_TitleArraySize; ++iTitle) { if(!m_pTitleArray[iTitle].bCombinedIndex) continue;
if(dwSubsetTitleCount != 0) wcscat(szSubsetFilter,L" OR ");
// Convert title name to Unicode
//
char szTempIdentifier[256], szTempLang[20]; WCHAR wszTempIdentifier[256]; // generate the magic title identifier
//
strcpy(szTempIdentifier,"HHTitleID"); strcat(szTempIdentifier,m_pTitleArray[iTitle].pszShortName); szTempLang[0] = 0; Itoa(m_pTitleArray[iTitle].language, szTempLang); strcat(szTempIdentifier,szTempLang); MultiByteToWideChar(CP_ACP, 0, szTempIdentifier, -1, wszTempIdentifier, sizeof(wszTempIdentifier)); // Append the title identifier to the subset query
//
wcscat(szSubsetFilter,wszTempIdentifier); dwSubsetTitleCount++; } // Close the expression
//
wcscat(szSubsetFilter,L")"); } else // Get the current CStructuralSubset and check if "search previous" is active.
// When "search previous" is on, we don't use subsetting (we query within the previous results)
//
if(m_pTitleCollection && m_pTitleCollection->m_pSSList) { if( (pSubset = m_pTitleCollection->m_pSSList->GetFTS()) && !(dwFlags & FTS_SEARCH_PREVIOUS) && !pSubset->IsEntire() ) { wcscat(szSubsetFilter,L" AND (VFLD 1 ");
for(iTitle = 0; iTitle < m_TitleArraySize; ++iTitle) { // Check if this title is part of the current subset
//
if(pSubset->IsTitleInSubset(m_pTitleArray[iTitle].pExTitle)) { if(dwSubsetTitleCount != 0) wcscat(szSubsetFilter,L" OR ");
// Convert title name to Unicode
//
char szTempIdentifier[256], szTempLang[20]; WCHAR wszTempIdentifier[256]; // generate the magic title identifier
//
strcpy(szTempIdentifier,"HHTitleID"); strcat(szTempIdentifier,m_pTitleArray[iTitle].pszShortName); szTempLang[0] = 0; Itoa(m_pTitleArray[iTitle].language, szTempLang); strcat(szTempIdentifier,szTempLang); MultiByteToWideChar(CP_ACP, 0, szTempIdentifier, -1, wszTempIdentifier, sizeof(wszTempIdentifier));
// Append the title identifier to the subset query
//
wcscat(szSubsetFilter,wszTempIdentifier); dwSubsetTitleCount++; } } // Close the expression
//
wcscat(szSubsetFilter,L")"); } }
IITResultSet* pITRS = NULL; LONG CombinedResultCount = 0; SEARCH_RESULT *pNextResult; BOOL bNeverPrompt; int QueryPhase;
// Query Phase #1 - submit queries to any titles currently available (in CD drive, or on HD)
// Query Phase #2 - submit queries to all titles in CD order
//
for(QueryPhase = 0; QueryPhase < 2; QueryPhase++) { for(iTitle = 0; iTitle < m_TitleArraySize; ++iTitle) { if(QueryPhase) bNeverPrompt = FALSE; else bNeverPrompt = TRUE;
if(m_pTitleArray[iTitle].bSearch && !m_pTitleArray[iTitle].bAlreadyQueried) { if(m_pTitleArray[iTitle].bCombinedIndex) { // Query combined index
//
CExTitle* pTitle = m_pTitleArray[iTitle].pExTitle;
// skip title if bad pTitle or we already have results for this query
//
if(!pTitle | m_pTitleArray[iTitle].bHasResults | m_pTitleArray[iTitle].bAlreadyQueried) continue;
// Make sure the storage is available
//
if(!m_bMergedChmSetWithCHQ && m_pTitleCollection && !(m_pTitleCollection->IsSingleTitle())) if( FAILED(hr = EnsureStorageAvailability( pTitle, HHRMS_TYPE_COMBINED_QUERY, FALSE, FALSE, bNeverPrompt ) ) ) continue;
// create combined index object if it doesn't exist
//
if(!m_pTitleArray[iTitle].pCombinedFTI) { // [paulti] must re-init query location before calling LoadCombinedIndex if dirty
if(m_pTitleArray[iTitle].pExTitle->m_pCollection->m_Collection.IsDirty()) { lcFree( m_pTitleArray[iTitle].pszQueryName ); m_pTitleArray[iTitle].pszQueryName = lcStrDup( m_pTitleArray[iTitle].pExTitle->GetUsedLocation()->QueryFileName ); }
if(!m_bMergedChmSetWithCHQ) { if(!LoadCombinedIndex(iTitle)) { // Load failed - not part of specified combined index.
// This title is now disabled for this session.
//
m_pTitleArray[iTitle].bSearch = FALSE; continue; } } }
// All is happy - set options and query the combined index
//
m_pTitleArray[iTitle].pCombinedFTI->UpdateOptions(m_wQueryProximity,m_lMaxRowCount);
if(FAILED(hr = m_pTitleArray[iTitle].pCombinedFTI->Query(szSubsetFilter, dwFlags, &pITRS, this, &cWaitDlg, iTitle))) { if(hr == FTS_INVALID_SYNTAX || hr == FTS_CANCELED) { m_iLastResultCount = 0; return hr; }
// error accessing title
m_pTitleArray[iTitle].bSearch = FALSE; continue; } m_pTitleArray[iTitle].bHasResults = TRUE; m_pTitleArray[iTitle].bAlreadyQueried = TRUE; CombinedResultCount+=ComputeResultCount(pITRS);
// Mark all other titles that use this combined index as having been queried
//
INT iTempTitle; for(iTempTitle = 0; iTempTitle < m_TitleArraySize; ++iTempTitle) if(m_pTitleArray[iTempTitle].pCombinedFTI == m_pTitleArray[iTitle].pCombinedFTI) m_pTitleArray[iTempTitle].bAlreadyQueried = TRUE;
} else { // Query index in individual title
//
CExTitle* pTitle = m_pTitleArray[iTitle].pExTitle;
// skip title if bad pTitle or we already have results for this query
//
if(!pTitle || m_pTitleArray[iTitle].bHasResults || m_pTitleArray[iTitle].bAlreadyQueried ) continue;
CTitleInformation *pTitleInfo = m_pTitleArray[iTitle].pExTitle->GetInfo(); if(!(pTitleInfo && pTitleInfo->IsFullTextSearch())) { // This title turned out to not contain a FTI. We delay this check until now
// for performance reasons.
//
m_pTitleArray[iTitle].bSearch = FALSE; continue; } if(!m_bMergedChmSetWithCHQ && m_pTitleCollection && !(m_pTitleCollection->IsSingleTitle())) if( FAILED(hr = EnsureStorageAvailability( pTitle, HHRMS_TYPE_TITLE, TRUE, FALSE, bNeverPrompt ) ) ) continue;
m_pTitleArray[iTitle].pExTitle->m_pTitleFTS->UpdateOptions(m_wQueryProximity,m_lMaxRowCount);
if(FAILED(hr = m_pTitleArray[iTitle].pExTitle->m_pTitleFTS->Query(pwsBuffer, dwFlags, &pITRS, this, &cWaitDlg, iTitle))) { m_pTitleArray[iTitle].bAlreadyQueried = TRUE; if(hr == FTS_INVALID_SYNTAX || hr == FTS_CANCELED) { m_iLastResultCount = 0; return hr; } continue; }
m_pTitleArray[iTitle].bHasResults = TRUE; m_pTitleArray[iTitle].bAlreadyQueried = TRUE; CombinedResultCount+=ComputeResultCount(pITRS); }
} } } lcFree(pwsBuffer);
// check for zero results
//
if(!CombinedResultCount) { *ppSearchResults = NULL; m_iLastResultCount = 0; return S_OK; }
// compute the max size of the results structure
//
cb = ((CombinedResultCount) * sizeof(SEARCH_RESULT));
// allocate the results structure
//
*ppSearchResults = pNextResult = (SEARCH_RESULT *) lcMalloc(cb);
// Clear the structure
//
memset(pNextResult,0,cb);
int cFilteredResultCount = 0;
long lRowCount;
// Query Phase #3 - collect the query results from each search object
//
for(iTitle = 0; iTitle < m_TitleArraySize; ++iTitle) { if(m_pTitleArray[iTitle].bHasResults) { if(m_pTitleArray[iTitle].bCombinedIndex) pITRS = m_pTitleArray[iTitle].pCombinedFTI->GetResultsSet(); else if( m_pTitleArray[iTitle].pExTitle->m_pTitleFTS ) pITRS = m_pTitleArray[iTitle].pExTitle->m_pTitleFTS->GetResultsSet(); else continue;
if( !pITRS ) continue;
// Get the results row count
//
pITRS->GetRowCount(lRowCount);
// Make sure we have results
//
if(lRowCount) { int i,rank = 1; CProperty Prop; DWORD dwLastTopic = 0xffffffff;
// walk through the results
//
for (i = 0; i < lRowCount; i++) { pITRS->Get(i, 0, Prop);
if(Prop.dwValue != dwLastTopic) { if(m_pTitleArray[iTitle].bCombinedIndex) { pNextResult->dwTopicNumber = TOPIC_NUM(Prop.dwValue); pNextResult->pTitle = LookupTitle(m_pTitleArray[iTitle].pCombinedFTI,CHM_ID(Prop.dwValue)); if(!pNextResult->pTitle) continue; // skip results from title that are out of date or not loaded
if(!pSubSet || pSubSet->m_bIsEntireCollection ) // no subset specified, add the topic
{ pNextResult->dwRank = rank++; pNextResult++; ++cFilteredResultCount; dwLastTopic = Prop.dwValue;
} else if(pNextResult->pTitle->InfoTypeFilter(pSubSet,pNextResult->dwTopicNumber)) { pNextResult->dwRank = rank++; pNextResult++; ++cFilteredResultCount; dwLastTopic = Prop.dwValue; } } else { pNextResult->dwTopicNumber = Prop.dwValue; pNextResult->pTitle = m_pTitleArray[iTitle].pExTitle; if(!pSubSet || pSubSet->m_bIsEntireCollection ) { pNextResult->dwRank = rank++; pNextResult++; ++cFilteredResultCount; dwLastTopic = Prop.dwValue; } else if(pNextResult->pTitle->InfoTypeFilter(pSubSet,pNextResult->dwTopicNumber)) { pNextResult->dwRank = rank++; pNextResult++; ++cFilteredResultCount; dwLastTopic = Prop.dwValue; } }
} } } } }
// Save the result count for client
//
if(cFilteredResultCount > m_lMaxRowCount) *pcResultCount = m_lMaxRowCount; else *pcResultCount = cFilteredResultCount;
m_iLastResultCount = *pcResultCount;
#ifdef _DEBUG
OutputDebugString("FTS: Full-Text Index Query Array\n\n"); OutputDebugString("Entry Title Enabled CHQ Results\n"); OutputDebugString("================================================\n"); for(iTitle = 0; iTitle < m_TitleArraySize; ++iTitle) { char szTemp[500]; wsprintf(szTemp,"%03d %16s %1d %1d %1d\n",iTitle, m_pTitleArray[iTitle].pszShortName,m_pTitleArray[iTitle].bSearch,m_pTitleArray[iTitle].bCombinedIndex,m_pTitleArray[iTitle].bHasResults); OutputDebugString(szTemp); } OutputDebugString("================================================\n\n");
#endif
// Below is some test code that dumps the results list to the debug window
//
// int x;
// char szTemp[100];
// for(x=0;x<cFilteredResultCount;++x)
// {
// wsprintf(szTemp,"Title=%0x Rank=%03d\n",(*ppSearchResults)[x].pTitle,(*ppSearchResults)[x].dwRank);
// OutputDebugString(szTemp);
// }
QSort(*ppSearchResults,cFilteredResultCount,sizeof(SEARCH_RESULT),CompareIntValues);
return S_OK; }
int FASTCALL CompareIntValues(const void *pval1, const void *pval2) { return ((SEARCH_RESULT *)pval1)->dwRank - ((SEARCH_RESULT *)pval2)->dwRank; }
long CFullTextSearch::ComputeResultCount(IITResultSet *pResultSet) { long count = 0, uniqueCount = 0;
if(!pResultSet) return 0;
if(FAILED(pResultSet->GetRowCount(count))) return 0;
if(count) { int i; CProperty Prop; DWORD dwLastTopic = 0xffffffff;
// walk through the results
//
for (i = 0; i < count; i++) { pResultSet->Get(i, 0, Prop); if(Prop.dwValue != dwLastTopic) { ++uniqueCount; dwLastTopic = Prop.dwValue; } } } return uniqueCount; }
/////////////////////////////////////////////////////////////////////////////
// CFullTextSearch lookup title
//
CExTitle *CFullTextSearch::LookupTitle(CCombinedFTS *pCombinedFTS, DWORD dwValue) { static CCombinedFTS *pCacheObject= NULL; static DWORD pCacheValue = NULL; static CExTitle *pPreviousResult;
// see if we are looking up the same title again
//
if(pCacheObject == pCombinedFTS && dwValue == pCacheValue) return pPreviousResult;
INT iTitle;
for(iTitle = 0; iTitle < m_TitleArraySize; ++iTitle) { if(m_pTitleArray[iTitle].pCombinedFTI == pCombinedFTS && m_pTitleArray[iTitle].iTitleIndex == dwValue && m_pTitleArray[iTitle].bCombinedIndex) { pCacheObject = pCombinedFTS; pCacheValue = dwValue; pPreviousResult = m_pTitleArray[iTitle].pExTitle; return m_pTitleArray[iTitle].pExTitle; } }
return NULL; }
/////////////////////////////////////////////////////////////////////////////
// CFullTextSearch Initialize title array
//
void CFullTextSearch::InitTitleArray() { if(m_bTitleArrayInit) return;
m_TitleArraySize = 0;
// Iterate through the titles
//
CExTitle *pTitle = m_pTitleCollection->GetFirstTitle();
// Count the entries in the title list
//
while(pTitle) { pTitle = pTitle->GetNext(); ++m_TitleArraySize; }
int cb = m_TitleArraySize * sizeof(TITLE_ENTRY);
// allocate the results structure
//
m_pTitleArray = (TITLE_ENTRY *) lcMalloc(cb);
if(!m_pTitleArray) return;
// Clear the structure
//
memset(m_pTitleArray,0,cb);
// Iterate through the titles and init members
//
pTitle = m_pTitleCollection->GetFirstTitle();
INT iTitle;
for(iTitle = 0; iTitle < m_TitleArraySize; ++iTitle) { if(pTitle == NULL) { lcFree(m_pTitleArray); m_pTitleArray = NULL; return; } m_pTitleArray[iTitle].pExTitle = pTitle; char *pszQueryName = (char *) pTitle->GetQueryName(); if(pszQueryName && *pszQueryName) { m_pTitleArray[iTitle].pszQueryName = lcStrDup(pszQueryName); m_pTitleArray[iTitle].bCombinedIndex = TRUE; } else m_pTitleArray[iTitle].bCombinedIndex = FALSE;
// Check if combined index for master chm exists
// This feature was added for NT5
//
if(iTitle == 0 && m_pTitleCollection->IsSingleTitle()) { char *pszFilePath = lcStrDup((char *) pTitle->GetIndexFileName()); if(pszFilePath) { _strupr(pszFilePath); char *pszTemp = strstr(pszFilePath,".CHM"); if(pszTemp) { pszTemp[3] = 'Q'; // Check if combined index exists
//
if(IsFile(pszFilePath)) { m_pTitleArray[iTitle].pszQueryName = lcStrDup(pszFilePath); m_pTitleArray[iTitle].bCombinedIndex = TRUE; m_bMergedChmSetWithCHQ = TRUE; // This disables CD swapping
} } if(pszFilePath) lcFree(pszFilePath); } }
if(!m_pTitleCollection->IsSingleTitle()) m_bMergedChmSetWithCHQ = FALSE; // Get version and name info from the index file
//
m_pTitleArray[iTitle].pszIndexName = lcStrDup((char *) pTitle->GetIndexFileName()); CTitleInformation2 Info2(m_pTitleArray[iTitle].pszIndexName); m_pTitleArray[iTitle].pszShortName = lcStrDup(Info2.GetShortName()); m_pTitleArray[iTitle].versioninfo = Info2.GetFileTime(); m_pTitleArray[iTitle].language = Info2.GetLanguage();
// Mark title as having a internal FTI for now. If the title is not part of a combined index
// I'll check for FTI before querying (this prevents opening all the titles during this
// initialization (which saves about 80% on init time).
//
m_pTitleArray[iTitle].bSearch = TRUE;
WORD wTitlePrimaryLang = PRIMARYLANGID(LANGIDFROMLCID(m_pTitleArray[iTitle].language));
if(m_SystemLangID == LANG_JAPANESE || m_SystemLangID == LANG_KOREAN || m_SystemLangID == LANG_CHINESE) { // Disable a DBCS title not running on a corresponding DBCS OS
//
if(m_SystemLangID != wTitlePrimaryLang && (wTitlePrimaryLang == LANG_JAPANESE || wTitlePrimaryLang == LANG_KOREAN || wTitlePrimaryLang == LANG_CHINESE)) m_pTitleArray[iTitle].bSearch = FALSE; }
// get the volume order
pTitle->GetVolumeOrder( &(m_pTitleArray[iTitle].uiVolumeOrder), pszQueryName ? HHRMS_TYPE_COMBINED_QUERY : HHRMS_TYPE_TITLE );
pTitle = pTitle->GetNext(); }
// sort the title list based on volume order
if(!m_bMergedChmSetWithCHQ) QSort( m_pTitleArray, m_TitleArraySize, sizeof(TITLE_ENTRY), CompareVolumeOrder );
m_bTitleArrayInit = TRUE;
}
/////////////////////////////////////////////////////////////////////////////
// CFullTextSearch GetPreviousInstance
//
CCombinedFTS *CFullTextSearch::GetPreviousInstance(char *pszQueryName) { static char *pCacheName = NULL; static CCombinedFTS *pCacheObject = NULL;
if(pCacheName) if(!strcmp(pszQueryName,pCacheName)) return pCacheObject;
INT iTitle;
for(iTitle = 0; iTitle < m_TitleArraySize; ++iTitle) { if(!strcmp(m_pTitleArray[iTitle].pszQueryName,pszQueryName) && m_pTitleArray[iTitle].pCombinedFTI) { pCacheName = pszQueryName; pCacheObject = m_pTitleArray[iTitle].pCombinedFTI; return m_pTitleArray[iTitle].pCombinedFTI; }
} return NULL; }
/////////////////////////////////////////////////////////////////////////////
// CFullTextSearch LoadCombinedIndex
//
BOOL CFullTextSearch::LoadCombinedIndex(DWORD iCurTitle) { HRESULT hr;
CCombinedFTS *pCombinedFTS = NULL;
CFileSystem* pDatabase = new CFileSystem;
char *pszQueryName = m_pTitleArray[iCurTitle].pszQueryName;
if( SUCCEEDED(hr = pDatabase->Init()) && SUCCEEDED(hr = pDatabase->Open(pszQueryName))) { CSubFileSystem* pTitleMap = new CSubFileSystem( pDatabase ); if( SUCCEEDED(hr = pTitleMap->OpenSub( "$TitleMap" ) ) ) { ULONG cbRead = 0; WORD wCount = 0; pTitleMap->ReadSub( (void*) &wCount, sizeof(wCount), &cbRead );
for( int iCount = 0; iCount < (int) wCount; iCount++ ) { CHM_MAP_ENTRY mapEntry;
pTitleMap->ReadSub( (void*) &mapEntry, sizeof(mapEntry), &cbRead );
if(cbRead != sizeof(mapEntry)) return FALSE;
INT iTitle;
// walk through the title array and set title index for matching titles
//
for(iTitle = 0; iTitle < m_TitleArraySize; ++iTitle) { // if a combined index wasn't specified for the title, skip it
//
if(!m_pTitleArray[iTitle].pszQueryName && !m_bMergedChmSetWithCHQ) continue;
FILETIME titleTime = m_pTitleArray[iTitle].versioninfo; char *pszShortName = m_pTitleArray[iTitle].pszShortName; if(!strcmpi(mapEntry.szChmName,pszShortName) && !m_pTitleArray[iTitle].pCombinedFTI && (!strcmpi(pszQueryName,m_pTitleArray[iTitle].pszQueryName) || m_bMergedChmSetWithCHQ) && mapEntry.language == m_pTitleArray[iTitle].language && (mapEntry.versioninfo.dwLowDateTime == titleTime.dwLowDateTime && mapEntry.versioninfo.dwHighDateTime == titleTime.dwHighDateTime )) { if(!pCombinedFTS) { pCombinedFTS = m_pTitleArray[iTitle].pCombinedFTI = new CCombinedFTS(m_pTitleArray[iTitle].pExTitle, m_pTitleArray[iTitle].pExTitle->GetInfo2()->GetLanguage(), this); // This is the master title for this combined index
//
m_pTitleArray[iTitle].bSearch = TRUE; } else { m_pTitleArray[iTitle].pCombinedFTI = pCombinedFTS; m_pTitleArray[iTitle].bSearch = TRUE; } m_pTitleArray[iTitle].bCombinedIndex = TRUE; m_pTitleArray[iTitle].versioninfo = mapEntry.versioninfo; m_pTitleArray[iTitle].iTitleIndex = mapEntry.iIndex; m_pTitleArray[iTitle].dwTopicCount = mapEntry.dwTopicCount; } } } } delete pTitleMap; }
delete pDatabase;
// special case where the master chm is out of date in respects to combined index (NT5)
//
if(m_bMergedChmSetWithCHQ && m_pTitleArray[0].bCombinedIndex && !m_pTitleArray[0].pCombinedFTI) m_pTitleArray[0].bCombinedIndex = 0;
#ifdef _DEBUG
/*
INT iTitle;
OutputDebugString("Full-Text Search Query Map Array:\n"); for(iTitle = 0; iTitle < m_TitleArraySize; ++iTitle) { char szTemp[500]; wsprintf(szTemp," %d bSearch=%d bCombinedIndex=%d iIndex=%02d pExTitle=%d pCombinedFTI=%d\n",iTitle, m_pTitleArray[iTitle].bSearch,m_pTitleArray[iTitle].bCombinedIndex,m_pTitleArray[iTitle].iTitleIndex, m_pTitleArray[iTitle].pExTitle,m_pTitleArray[iTitle].pCombinedFTI); OutputDebugString(szTemp); } */ #endif
// Check to see if the title that initiated the loading of this combined index
// was validated for this index.
if(m_pTitleArray[iCurTitle].pCombinedFTI) return TRUE; else return FALSE; }
/////////////////////////////////////////////////////////////////////////////
// CFullTextSearch abort query function
//
HRESULT CFullTextSearch::AbortQuery() { if(!m_bInit) return E_FAIL;
CExTitle *pTitle = m_pTitleCollection->GetFirstTitle();
while(pTitle) { // BUGBUG: If this is the first title in a collection, it will
// reread the system data.
CTitleInformation *pTitleInfo = pTitle->GetInfo(); if(pTitleInfo && pTitleInfo->IsFullTextSearch()) { pTitle->m_pTitleFTS->AbortQuery(); } pTitle = pTitle->GetNext(); }
return S_OK; }
/////////////////////////////////////////////////////////////////////////////
// CFullTextSearch set query options
//
// dwFlag can be one or more of the following:
//
// IMPLICIT_AND
// Search terms are AND'd if no operator is specified
//
// IMPLICIT_OR
// Search terms are OR'd if no operator is specified
//
// COMPOUNDWORD_PHRASE
// Use PHRASE operator for compound words
//
// QUERYRESULT_RANK
// Results are returned in ranked order
//
// QUERYRESULT_UIDSORT
// Results are returned in UID order
//
// QUERYRESULT_SKIPOCCINFO
// Only topic-level hit information is returned
//
// STEMMED_SEARCH
// The search returns stemmed results
//
HRESULT CFullTextSearch::SetOptions(DWORD dwFlag) { if(!m_bInit) return E_FAIL;
m_dwQueryFlags = dwFlag;
CExTitle *pTitle = m_pTitleCollection->GetFirstTitle();
while(pTitle) { // BUGBUG: If this is the first title in a collection, it will
// reread the system data.
CTitleInformation *pTitleInfo = pTitle->GetInfo(); if(pTitleInfo && pTitleInfo->IsFullTextSearch()) { pTitle->m_pTitleFTS->SetOptions(dwFlag); } pTitle = pTitle->GetNext(); }
return S_OK; }
/////////////////////////////////////////////////////////////////////////////
// CFullTextSearch set proximity
//
HRESULT CFullTextSearch::SetProximity(WORD wNear) { if(!m_bInit) return E_FAIL;
CExTitle *pTitle = m_pTitleCollection->GetFirstTitle();
while(pTitle) { // BUGBUG: If this is the first title in a collection, it will
// reread the system data.
CTitleInformation *pTitleInfo = pTitle->GetInfo(); if(pTitleInfo && pTitleInfo->IsFullTextSearch()) { pTitle->m_pTitleFTS->SetProximity(wNear); } pTitle = pTitle->GetNext(); }
return S_OK; }
/////////////////////////////////////////////////////////////////////////////
// CFullTextSearch set result count
// This sets max number of rows to be returned by query
//
HRESULT CFullTextSearch::SetResultCount(LONG cRows) { if(!m_bInit) return E_FAIL;
CExTitle *pTitle = m_pTitleCollection->GetFirstTitle();
while(pTitle) { // BUGBUG: If this is the first title in a collection, it will
// reread the system data.
CTitleInformation *pTitleInfo = pTitle->GetInfo(); if(pTitleInfo && pTitleInfo->IsFullTextSearch()) { pTitle->m_pTitleFTS->SetResultCount(cRows); } pTitle = pTitle->GetNext(); }
return S_OK; }
/////////////////////////////////////////////////////////////////////////////
// CFullTextSearch free results data structure
//
VOID CFullTextSearch::FreeResults(SEARCH_RESULT *pResults) { if(!m_bInit) return;
if(pResults) lcFree(pResults); } /*
/////////////////////////////////////////////////////////////////////////////
// CFullTextSearch AddHLTerm
// Add a term to the highlight list
//
HRESULT CFullTextSearch::AddHLTerm(WCHAR *pwcTerm) { if(!pwcTerm || !*pwcTerm) return E_FAIL;
if(m_iHLIndex < MAX_HIGHLIGHT_TERMS) return E_FAIL;
int len = (wcslen(pwcTerm) + 1) * sizeof(WCHAR);
m_HLTermArray[m_iHLIndex] = (WCHAR *) lcMalloc(len);
wcscpy(m_HLTermArray[m_iHLIndex],pwcTerm);
m_iHLIndex++;
return S_OK; } */ /////////////////////////////////////////////////////////////////////////////
// CFullTextSearch AddHLTerm
// Add a term to the highlight list
//
HRESULT CFullTextSearch::AddHLTerm(WCHAR *pwcTerm, int cTermLength) { if(!pwcTerm || !*pwcTerm || !cTermLength) return E_FAIL;
if(*pwcTerm == L')' || *pwcTerm == L'(' || *pwcTerm == L'\"') return S_OK;
if(m_iHLIndex >= MAX_HIGHLIGHT_TERMS) return E_FAIL;
if(!wcsnicmp(pwcTerm,L"HHTitleID",9)) return S_OK; int i;
// do not accept duplicates
//
for(i=0;i<m_iHLIndex;++i) { int cLen = wcslen(m_HLTermArray[i]); if(cTermLength == cLen && !wcsnicmp(pwcTerm, m_HLTermArray[i], cLen)) return S_OK; }
int len = (cTermLength + 1) * sizeof(WCHAR);
m_HLTermArray[m_iHLIndex] = (WCHAR *) lcMalloc(len);
CopyMemory(m_HLTermArray[m_iHLIndex],pwcTerm,len-sizeof(WCHAR));
*(m_HLTermArray[m_iHLIndex]+cTermLength) = NULL;
m_iHLIndex++;
return S_OK; }
/////////////////////////////////////////////////////////////////////////////
// CFullTextSearch AddHLTerm
// Add a term to the highlight list
//
HRESULT CFullTextSearch::AddQueryToTermList(WCHAR *pwsBuffer) { WCHAR *pwsStart, *pwsTemp = pwsBuffer;
if(!pwsBuffer || !*pwsBuffer) return E_FAIL;
while(*pwsTemp) { // Remove white space
//
while(*pwsTemp && (*pwsTemp == L' ' || *pwsTemp == L'(' || *pwsTemp == L')')) ++pwsTemp;
if(!*pwsTemp) continue;
pwsStart = pwsTemp;
if(*pwsTemp == L'"') { pwsStart++; pwsTemp++;
while(*pwsTemp && *pwsTemp != L'"') ++pwsTemp; } else { while(*pwsTemp && !(*pwsTemp == L' ' || *pwsTemp == L'(' || *pwsTemp == L')')) ++pwsTemp; }
if(!*pwsTemp) continue;
if(pwsStart != pwsTemp) { if(wcsnicmp(pwsStart,L"and",(int)(pwsTemp-pwsStart)) && wcsnicmp(pwsStart,L"or",(int)(pwsTemp-pwsStart)) && wcsnicmp(pwsStart,L"near",(int)(pwsTemp-pwsStart)) && wcsnicmp(pwsStart,L"not",(int)(pwsTemp-pwsStart)) && wcsnicmp(pwsStart,L"VFLD 0",(int)(pwsTemp-pwsStart)) && wcsnicmp(pwsStart,L"VFLD 1",(int)(pwsTemp-pwsStart)) && wcsnicmp(pwsStart,L"HHTitleID",9)) AddHLTerm(pwsStart,(int)(pwsTemp-pwsStart)); }
if(!wcsnicmp(pwsStart,L"HHTitleID",(int)(pwsTemp-pwsStart))) { while(*pwsTemp && !(*pwsTemp == L' ' || *pwsTemp == L'(' || *pwsTemp == L')')) ++pwsTemp; }
if(!wcsnicmp(pwsStart,L"VFLD 0",(int)(pwsTemp-pwsStart)) && !wcsnicmp(pwsStart,L"VFLD 1",(int)(pwsTemp-pwsStart))) pwsTemp+=2;
if(*pwsTemp == L'"') ++pwsTemp; } return S_OK; }
/////////////////////////////////////////////////////////////////////////////
// CFullTextSearch TermListRemoveAll
// Delete all entries in the highlight term list
//
HRESULT CFullTextSearch::TermListRemoveAll(void) { int i;
for(i=0;i<m_iHLIndex;++i) { if(m_HLTermArray[i]) { lcFree(m_HLTermArray[i]); m_HLTermArray[i] = NULL; } }
m_iHLIndex = 0;
return S_OK; }
/////////////////////////////////////////////////////////////////////////////
// CFullTextSearch GetHLTerm
// Retrieve a term from the highlight term list
//
WCHAR * CFullTextSearch::GetHLTermAt(int index) { if(index >= m_iHLIndex) return NULL;
return m_HLTermArray[index]; }
/////////////////////////////////////////////////////////////////////////////
// CFullTextSearch GetHLTermCount
// Get the count of entries in the highlight term list
//
INT CFullTextSearch::GetHLTermCount(void) { return m_iHLIndex; }
/////////////////////////////////////////////////////////////////////////////
// CTitleFTS Class
//
// This class provides full-text search functionality for a single open
// title. In a multi-title environment, there will be an instance of this
// class for each open title.
//
// This class is intended only for use by the CFullTextSearch class which
// provides full-text search services for both single and multi-title
// configurations.
/////////////////////////////////////////////////////////////////////////////
// CTitleFTS class constructor
//
CTitleFTS::CTitleFTS( PCSTR pszTitlePath, LCID lcid, CExTitle *pTitle) { MultiByteToWideChar(CP_ACP, 0, pszTitlePath, -1, m_tcTitlePath, sizeof(m_tcTitlePath) ); m_lcid = lcid; m_langid = LANGIDFROMLCID(lcid); m_codepage = CodePageFromLCID(lcid); m_bInit = FALSE; m_SearchActive = FALSE; m_pIndex = NULL; m_pQuery = NULL; m_pITResultSet = NULL; m_pITDB = NULL; m_InitFailed = FALSE; m_InitError = E_FAIL; m_pTitle = pTitle; m_pPrevQuery = NULL;
m_SystemLangID = PRIMARYLANGID(GetSystemDefaultLangID()); m_fDBCS = FALSE;
m_lMaxRowCount = 500; m_wQueryProximity = 8; m_iLastResultCount = 0; }
/////////////////////////////////////////////////////////////////////////////
// CTitleFTS class destructor
//
CTitleFTS::~CTitleFTS() { ReleaseObjects(); if(m_pPrevQuery) lcFree(m_pPrevQuery); }
/////////////////////////////////////////////////////////////////////////////
// CTitleFTS release objects
//
void CTitleFTS::ReleaseObjects() { if(!m_bInit) return;
if (m_pITResultSet) { m_pITResultSet->Clear(); m_pITResultSet->Release(); }
if(m_pQuery) m_pQuery->Release();
if(m_pIndex) { m_pIndex->Close(); m_pIndex->Release(); }
if(m_pITDB) { m_pITDB->Close(); m_pITDB->Release(); }
m_pITResultSet = NULL; m_pQuery = NULL; m_pIndex = NULL; m_pITDB = NULL; }
/////////////////////////////////////////////////////////////////////////////
// CTitleFTS class initialization
//
HRESULT CTitleFTS::Initialize() { if(m_InitFailed) return E_FAIL;
if(m_bInit) return S_OK;
m_InitFailed = TRUE;
WORD PrimaryLang = PRIMARYLANGID(m_langid); if(PrimaryLang == LANG_JAPANESE || PrimaryLang == LANG_CHINESE || PrimaryLang == LANG_KOREAN) m_fDBCS = TRUE; else m_fDBCS = FALSE;
if(m_lcid == 1033) m_dwQueryFlags = IMPLICIT_AND | QUERY_GETTERMS | STEMMED_SEARCH; // QUERYRESULT_RANK
else m_dwQueryFlags = IMPLICIT_AND | QUERY_GETTERMS;
// char szLangID[20];
//GetLocaleInfo(m_lcid,LOCALE_ILANGUAGE,szLangID,sizeof(szLangID));
// Make sure we have a path
//
if(!*m_tcTitlePath) { m_InitError = FTS_NOT_INITIALIZED; return E_FAIL; }
// DOUGO - insert code here to initialize language information
// Get IITIndex pointer
//
HRESULT hr = CoCreateInstance(CLSID_IITIndexLocal, NULL, CLSCTX_INPROC_SERVER, IID_IITIndex, (VOID**)&m_pIndex); if (FAILED(hr)) { m_InitError = FTS_NOT_INITIALIZED; return E_FAIL; }
// Get IITDatabase pointer
//
hr = CoCreateInstance(CLSID_IITDatabaseLocal, NULL, CLSCTX_INPROC_SERVER, IID_IITDatabase, (VOID**)&m_pITDB); if (FAILED(hr)) { m_InitError = FTS_NOT_INITIALIZED; return E_FAIL; }
// Open the storage system
//
hr = m_pITDB->Open(NULL, m_tcTitlePath, NULL); if (FAILED(hr)) { m_InitError = FTS_NO_INDEX; return E_FAIL; }
// open the index.
//
hr = m_pIndex->Open(m_pITDB, txtwFtiMain, TRUE); if (FAILED(hr)) { m_InitError = FTS_NO_INDEX; return E_FAIL; }
// Create query instance
//
hr = m_pIndex->CreateQueryInstance(&m_pQuery); if (FAILED(hr)) { m_InitError = FTS_NOT_INITIALIZED; return E_FAIL; }
// set search options
//
hr = m_pQuery->SetOptions(m_dwQueryFlags); if (FAILED(hr)) { m_InitError = FTS_NOT_INITIALIZED; return E_FAIL; }
// Create Result Set object
//
hr = CoCreateInstance(CLSID_IITResultSet, NULL, CLSCTX_INPROC_SERVER, IID_IITResultSet, (VOID**) &m_pITResultSet); if (FAILED(hr)) { m_InitError = FTS_NOT_INITIALIZED; return E_FAIL; }
m_bInit = TRUE; m_InitFailed = FALSE; return S_OK; }
/////////////////////////////////////////////////////////////////////////////
// CTitleFTS query function
//
HRESULT CTitleFTS::Query(WCHAR *pwcQuery, DWORD dwFlags, IITResultSet **ppITRS, CFullTextSearch *pFullTextSearch, CUWait *pWaitDlg, int iTitle) { HRESULT hr;
WCHAR *pNewQuery = NULL; if(!Init()) return m_InitError;
// Reinit the object if the dirty bit is set
//
if(m_pTitle->m_pCollection->m_Collection.IsDirty()) { ReleaseObjects(); MultiByteToWideChar(CP_ACP, 0, m_pTitle->GetContentFileName(), -1, m_tcTitlePath, sizeof(m_tcTitlePath) ); m_InitFailed = FALSE; m_bInit = FALSE; m_InitError = E_FAIL; if(!Init()) return m_InitError; }
WCHAR *pszQuery = pwcQuery; pszQuery = PreProcessQuery(pwcQuery, m_codepage);
if(!pszQuery) return E_FAIL;
*ppITRS = NULL;
// return if search previous set, but no previous query
//
if((dwFlags & FTS_SEARCH_PREVIOUS) && !m_pPrevQuery) return S_OK;
// If this title resulted in no hits last query, but the global result count was non-zero, then
// return no hits. The only time we want to query this title using the last known good query is
// when the global query count (combined results) was also zero. In that case, we want to
// revert all titles and combined indexes back to the last known good query. Otherwise, we skip
// this title because the last query (which resulted in some results globally) is still valid.
//
if((dwFlags & FTS_SEARCH_PREVIOUS) && pFullTextSearch->m_iLastResultCount && !m_iLastResultCount) { lcFree(m_pPrevQuery); m_pPrevQuery = NULL; return S_OK; }
WCHAR *pPrevQuerySaved = NULL; if(m_pPrevQuery) { pPrevQuerySaved = (WCHAR *) lcMalloc((wcslen(m_pPrevQuery)+ 2) * sizeof(WCHAR)); wcscpy(pPrevQuerySaved,m_pPrevQuery); }
CStructuralSubset *pSubset;
// Check if this title is part of the current subset
//
if(m_pTitle->m_pCollection && m_pTitle->m_pCollection->m_pSSList) if( (pSubset = m_pTitle->m_pCollection->m_pSSList->GetFTS()) && !pSubset->IsEntire() ) if(!pSubset->IsTitleInSubset(m_pTitle) && !(dwFlags & FTS_SEARCH_PREVIOUS) ) { // clear the previous results
//
hr = m_pITResultSet->Clear(); m_iLastResultCount = 0; if(m_pPrevQuery) lcFree(m_pPrevQuery); m_pPrevQuery = NULL; if(pPrevQuerySaved) lcFree(pPrevQuerySaved); return S_OK; } if(dwFlags & FTS_ENABLE_STEMMING) m_dwQueryFlags |= STEMMED_SEARCH; else m_dwQueryFlags &= (~STEMMED_SEARCH);
// Set previous options
//
m_pQuery->ReInit(); m_pQuery->SetOptions(m_dwQueryFlags); m_pQuery->SetResultCount(m_lMaxRowCount); m_pQuery->SetProximity(m_wQueryProximity);
FCALLBACK_MSG fCallBackMsg;
fCallBackMsg.MessageFunc = SearchMessageFunc; fCallBackMsg.pUserData = (PVOID)pWaitDlg; fCallBackMsg.dwFlags = 0; // not recommended for use
m_pQuery->SetResultCallback(&fCallBackMsg);
// Search Previous
//
if(dwFlags & FTS_SEARCH_PREVIOUS) { // append new query onto old query for "search previous"
//
int cQuery = wcslen(pszQuery); int cPrevQuery = wcslen(m_pPrevQuery); pNewQuery = (WCHAR *) lcMalloc((cQuery+cPrevQuery+20) * sizeof(WCHAR)); if(!pNewQuery) return E_FAIL;
*pNewQuery = 0; wcscat(pNewQuery,m_pPrevQuery); wcscat(pNewQuery,L" and "); wcscat(pNewQuery,pszQuery); wcscat(pNewQuery,L" "); }
// free the previous prev query
//
if(m_pPrevQuery) { lcFree(m_pPrevQuery); m_pPrevQuery = NULL; } // Save the new query for next time
//
if(pNewQuery) m_pPrevQuery = (WCHAR *) lcMalloc((wcslen(pNewQuery)+ 2) * sizeof(WCHAR)); else m_pPrevQuery = (WCHAR *) lcMalloc((wcslen(pszQuery)+ 2) * sizeof(WCHAR));
if(pNewQuery) wcscpy(m_pPrevQuery,pNewQuery); else wcscpy(m_pPrevQuery,pszQuery);
// clear the previous results
//
hr = m_pITResultSet->Clear(); if (FAILED(hr)) { // Error clearing results set
if(pPrevQuerySaved) lcFree(pPrevQuerySaved); return E_FAIL; }
// we want topic numbers back
//
hr = m_pITResultSet->Add(STDPROP_UID, (DWORD) 0, PRIORITY_NORMAL); if (FAILED(hr)) { // Error adding result property
m_InitError = FTS_NOT_INITIALIZED; if(pPrevQuerySaved) lcFree(pPrevQuerySaved); return E_FAIL; }
hr = m_pITResultSet->Add(STDPROP_TERM_UNICODE_ST, (DWORD)NULL, PRIORITY_NORMAL); if (FAILED(hr)) { // Error adding result property
m_InitError = FTS_NOT_INITIALIZED; if(pPrevQuerySaved) lcFree(pPrevQuerySaved); return E_FAIL;
}
hr = m_pITResultSet->Add(STDPROP_COUNT, (DWORD)NULL, PRIORITY_NORMAL); if (FAILED(hr)) { // Error adding result property
m_InitError = FTS_NOT_INITIALIZED; if(pPrevQuerySaved) lcFree(pPrevQuerySaved); return E_FAIL; }
// Set the query
//
if(pNewQuery) { pFullTextSearch->AddQueryToTermList(pNewQuery); hr = m_pQuery->SetCommand(pNewQuery); // MessageBoxW(NULL,pNewQuery,L"Query",MB_OK);
} else { pFullTextSearch->AddQueryToTermList(pszQuery); hr = m_pQuery->SetCommand(pszQuery); // MessageBoxW(NULL,pwcQuery,L"Query",MB_OK);
}
if (FAILED(hr)) { // Error setting query
if(pPrevQuerySaved) lcFree(pPrevQuerySaved); return E_FAIL; }
if(pNewQuery) lcFree(pNewQuery);
// Execute the query
//
hr = m_pIndex->Search(m_pQuery, m_pITResultSet);
// if we receive a no stemmer error, re-query with stemming turned off
//
if(hr == E_NOSTEMMER) { m_dwQueryFlags &= (~STEMMED_SEARCH); m_pQuery->SetOptions(m_dwQueryFlags); m_pQuery->SetCommand(pszQuery); hr = m_pIndex->Search(m_pQuery, m_pITResultSet); }
long lRowCount;
m_pITResultSet->GetRowCount(lRowCount);
m_iLastResultCount = lRowCount;
// If query failed, then restore the previous query (for next search previous)
//
if((FAILED(hr) || !lRowCount) && pPrevQuerySaved) { if(m_pPrevQuery) lcFree(m_pPrevQuery); m_pPrevQuery = (WCHAR *) lcMalloc((wcslen(pPrevQuerySaved)+ 2) * sizeof(WCHAR)); wcscpy(m_pPrevQuery,pPrevQuerySaved); }
if(pPrevQuerySaved) lcFree(pPrevQuerySaved);
if (hr ==E_NULLQUERY || hr == E_MISSQUOTE || hr == E_EXPECTEDTERM || hr == E_MISSLPAREN || hr == E_MISSRPAREN || hr == E_ALL_WILD) { // Error invalid syntax
//
return FTS_INVALID_SYNTAX; }
if(hr == FTS_CANCELED) return FTS_CANCELED;
// Generic error
//
if (FAILED(hr)) return E_FAIL;
// Add search terms to term list
//
// Make sure we have results
//
if(lRowCount) { WCHAR wcBuffer[2048];
int i; CProperty Prop, Prop2, Prop3; DWORD dwLastTopic = 0xffffffff, dwNextWordCount;
WCHAR *pwcTemp = wcBuffer;
*pwcTemp = 0;
// prime the topic number
//
m_pITResultSet->Get(0,0, Prop3); dwLastTopic = Prop3.dwValue;
// walk through the results
//
for (i = 0; i < lRowCount; i++) { m_pITResultSet->Get(i,0, Prop3); if(i<lRowCount) { m_pITResultSet->Get(i+1,2, Prop2); dwNextWordCount = Prop2.dwValue; } else dwNextWordCount = 0;
m_pITResultSet->Get(i,1, Prop); m_pITResultSet->Get(i,2, Prop2);
if(!Prop.dwValue) continue;
WORD wFlags[2]; wFlags[0]=0;
// Convert term from Unicode to ANSI because GetStringTypeW is not supported on Win95.
//
WORD dwStrLen = (WORD)(wcslen(&Prop.lpszwData[1]) * sizeof(WCHAR)); if(dwStrLen) { char *pAnsiString = (char *) lcMalloc(dwStrLen); DWORD dwTermLen = 1;
//UNICODE WORK: will have to use title cp when doing this conversion to ANSI (Damn Win98 and no Unicode support!!).
WideCharToMultiByte(m_codepage, 0, Prop.lpszwData + 1, -1, pAnsiString, dwStrLen, NULL, NULL); if(IsDBCSLeadByteEx(m_codepage, *pAnsiString)) dwTermLen=2;
GetStringTypeA(m_lcid, CT_CTYPE3, pAnsiString, dwTermLen, wFlags);
lcFree(pAnsiString); } // skip DB chars (DB words were already added)
//
if(wFlags[0] & C3_FULLWIDTH || wFlags[0] & C3_KATAKANA || wFlags[0] & C3_HIRAGANA) { if(*pwcTemp) { pFullTextSearch->AddHLTerm(wcBuffer,wcslen(wcBuffer)); pwcTemp = wcBuffer; *pwcTemp = 0; } dwLastTopic = Prop3.dwValue; continue; }
CopyMemory(pwcTemp, Prop.lpszwData + 1, (*((WORD *)Prop.lpszwData) * sizeof(WCHAR))); pwcTemp+=*((WORD *)Prop.lpszwData); *pwcTemp = 0;
if(dwNextWordCount != (Prop2.dwValue+1) || (wcslen(wcBuffer) > 500)) { pFullTextSearch->AddHLTerm(wcBuffer,wcslen(wcBuffer)); pwcTemp = wcBuffer; *pwcTemp = 0; } else { *pwcTemp++ = L' '; *pwcTemp = 0; } dwLastTopic = Prop3.dwValue; } }
// Send the results set back
//
*ppITRS = m_pITResultSet;
return S_OK; }
/////////////////////////////////////////////////////////////////////////////
// CTitleFTS abort current query
//
HRESULT CTitleFTS::AbortQuery() { if(!Init()) return E_FAIL;
// DOUGO - insert abort code here when Centaur support is available
return S_OK; }
/////////////////////////////////////////////////////////////////////////////
// CTitleFTS update FTS options without calling into Centaur
//
HRESULT CTitleFTS::UpdateOptions(WORD wNear, LONG cRows) { m_wQueryProximity = wNear; m_lMaxRowCount = cRows; return S_OK; }
/////////////////////////////////////////////////////////////////////////////
// CTitleFTS set title FTS options
//
HRESULT CTitleFTS::SetOptions(DWORD dwFlag) { HRESULT hr;
if(!Init()) return E_FAIL;
m_dwQueryFlags = dwFlag;
hr = m_pQuery->SetOptions(dwFlag);
if (FAILED(hr)) return E_FAIL; else return S_OK; }
/////////////////////////////////////////////////////////////////////////////
// CTitleFTS set proximity for title
//
HRESULT CTitleFTS::SetProximity(WORD wNear) { if(!Init()) return E_FAIL;
m_wQueryProximity = wNear; return m_pQuery->SetProximity(wNear); }
/////////////////////////////////////////////////////////////////////////////
// CTitleFTS set max result count for title query
//
HRESULT CTitleFTS::SetResultCount(LONG cRows) { if(!Init()) return E_FAIL;
m_lMaxRowCount = cRows;
return m_pQuery->SetResultCount(cRows); }
// Han2Zen
//
// This function converts half-width katakana character to their
// full-width equivalents while taking into account the nigori
// and maru marks.
//
DWORD Han2Zen(unsigned char *lpInBuffer, unsigned char *lpOutBuffer, UINT codepage ) { // Note: The basic algorithm (including the mapping table) used here to
// convert half-width Katakana characters to full-width Katakana appears
// in the book "Understanding Japanese Information Systems" by
// O'Reily & Associates.
while(*lpInBuffer) { if(*lpInBuffer >= 161 && *lpInBuffer <= 223) { // We have a half-width Katakana character. Now compute the equivalent
// full-width character via the mapping table.
//
*lpOutBuffer = (unsigned char)mtable[*lpInBuffer-161][0]; *(lpOutBuffer+1) = (unsigned char)mtable[*lpInBuffer-161][1];
lpInBuffer++;
// check if the second character is nigori mark.
//
if(*lpInBuffer == 222) { // see if we have a half-width katakana that can be modified by nigori.
//
if((*(lpInBuffer-1) >= 182 && *(lpInBuffer-1) <= 196) || (*(lpInBuffer-1) >= 202 && *(lpInBuffer-1) <= 206) || (*(lpInBuffer-1) == 179)) { // transform kana into kana with maru
//
if((*(lpOutBuffer+1) >= 74 && *(lpOutBuffer+1) <= 103) || (*(lpOutBuffer+1) >= 110 && *(lpOutBuffer+1) <= 122)) { (*(lpOutBuffer+1))++; ++lpInBuffer; } else if(*lpOutBuffer == 131 && *(lpOutBuffer+1) == 69) { *(lpOutBuffer+1) = 148; ++lpInBuffer; } } } else if(*lpInBuffer==223) // check if following character is maru mark
{ // see if we have a half-width katakana that can be modified by maru.
//
if((*(lpInBuffer-1) >= 202 && *(lpInBuffer-1) <= 206)) { // transform kana into kana with nigori
//
if(*(lpOutBuffer+1) >= 110 && *(lpOutBuffer+1) <= 122) { *(lpOutBuffer+1)+=2; ++lpInBuffer; } } }
lpOutBuffer+=2; } else { if(IsDBCSLeadByteEx(codepage, *lpInBuffer)) { *lpOutBuffer++ = *lpInBuffer++; if(*lpInBuffer) *lpOutBuffer++ = *lpInBuffer++; } else *lpOutBuffer++ = *lpInBuffer++; } }
*lpOutBuffer = 0; return TRUE; }
WCHAR* PreProcessQuery(WCHAR *pwcQuery, UINT codepage) { if(!pwcQuery) return NULL; char *pszQuery = NULL;
// compute max length for ANSI/DBCS conversion buffer
//
DWORD dwTempLen = ((wcslen(pwcQuery)*2)+4); // allocate buffer for ANSI/DBCS version of query string
//
char *pszTempQuery1 = (char *) lcMalloc(dwTempLen);
// return on fail
//
if(!pszTempQuery1) return NULL;
// Convert our Unicode query to ANSI/DBCS
//
int ret = WideCharToMultiByte(codepage, 0, pwcQuery, -1, pszTempQuery1, dwTempLen, "%", NULL);
// return on fail
//
if(!ret || !pszTempQuery1) return NULL;
int cUnmappedChars = 0; char *pszTempQuery5 = pszTempQuery1;
// Count the number of unmappable characters
//
while(*pszTempQuery5) { if(*pszTempQuery5 == '%') ++cUnmappedChars; if(IsDBCSLeadByteEx(codepage, *pszTempQuery5)) { pszTempQuery5++; if(*pszTempQuery5) pszTempQuery5++; } else ++pszTempQuery5; }
// allocate a new buffer large enough for unmapped character place holders plus original query
//
DWORD dwTranslatedLen = (DWORD)strlen(pszTempQuery1) + (cUnmappedChars * 4) + 16;
char *pszTempQuery6 = (char *)lcMalloc(dwTranslatedLen); char *pszTempQuery7 = pszTempQuery6; if(!pszTempQuery6) return NULL; pszTempQuery5 = pszTempQuery1; // construct the new query string (inserting unmappable character place holders)
//
while(*pszTempQuery5) { if(*pszTempQuery5 == '%') { ++pszTempQuery5; *pszTempQuery7++='D'; *pszTempQuery7++='X'; *pszTempQuery7++='O'; continue; }
if(IsDBCSLeadByteEx(codepage, *pszTempQuery5)) { *pszTempQuery7++ = *pszTempQuery5++; if(*pszTempQuery5) *pszTempQuery7++ = *pszTempQuery5++; } else *pszTempQuery7++ = *pszTempQuery5++; }
*pszTempQuery7 = 0;
lcFree(pszTempQuery1); char *pszTempQuery2 = pszTempQuery6; // If we are running a Japanese title then we nomalize Katakana characters
// by converting half-width Katakana characters to full-width Katakana.
// This allows the user to receive hits for both the full and half-width
// versions of the character regardless of which version they type in the
// query string.
//
if(codepage == 932) { int cb = (int)strlen(pszTempQuery2)+1;
// allocate new buffer for converted query
//
char *pszTempQuery3 = (char *) lcMalloc(cb*2);
// convert half-width katakana to full-width
//
Han2Zen((unsigned char *)pszTempQuery2,(unsigned char *)pszTempQuery3, codepage); if(pszTempQuery2) lcFree(pszTempQuery2); pszTempQuery2 = pszTempQuery3; } // done half-width normalization
// For Japanese queries, convert all double-byte quotes into single byte quotes
//
if(codepage == 932) { char *pszTemp = pszTempQuery2;
while(*pszTemp) { if(*pszTemp == '�' && (*(pszTemp+1) == 'h' || *(pszTemp+1) == 'g' || *(pszTemp+1) == 'J') ) { *pszTemp = ' '; *(pszTemp+1) = '\"'; } pszTemp = CharNext(pszTemp); } } // done convert quotes
// This section converts contigious blocks of DBCS characters into phrases (enclosed in double quotes).
// Converting DBCS words into phrases is required with the character based DBCS indexer we use.
//
int i, cb = (int)strlen(pszTempQuery2);
// allocate new buffer for processed query
//
char *pszDest, *pszTemp;
char *pszTempQuery4 = (char *) lcMalloc(cb*8);
if(!pszTempQuery4) return NULL;
pszTemp = pszTempQuery2; pszDest = pszTempQuery4;
while(*pszTemp) { // check for quoted string - if found, copy it
if(*pszTemp == '"') { *pszDest++=*pszTemp++; while(*pszTemp && *pszTemp != '"') { if(IsDBCSLeadByteEx(codepage, *pszTemp)) { *pszDest++=*pszTemp++; *pszDest++=*pszTemp++; } else *pszDest++=*pszTemp++; } if(*pszTemp == '"') *pszDest++=*pszTemp++; continue; }
// Convert Japanese operators to English operators
//
if(IsDBCSLeadByteEx(codepage, *pszTemp)) { // check for full-width operator, if found, convert to ANSI
if(i = IsJOperator(pszTemp)) { strcpy(pszDest,pEnglishOperator[i]); pszDest+=strlen(pEnglishOperator[i]); pszTemp+=strlen(pJOperatorList[i]); continue; }
*pszDest++=' '; *pszDest++='"'; while(*pszTemp && *pszTemp !='"' && IsDBCSLeadByteEx(codepage, *pszTemp)) { *pszDest++=*pszTemp++; *pszDest++=*pszTemp++; } *pszDest++='"'; *pszDest++=' '; continue; }
*pszDest++=*pszTemp++; } *pszDest = 0;
if(pszTempQuery2) lcFree(pszTempQuery2);
// compute size of Unicode buffer;
int cbUnicodeSize = ((MultiByteToWideChar(codepage, 0, pszTempQuery4, -1, NULL, 0) + 2) *2);
WCHAR *pszUnicodeBuffer = (WCHAR *) lcMalloc(cbUnicodeSize);
ret = MultiByteToWideChar(codepage, 0, pszTempQuery4, -1, pszUnicodeBuffer, cbUnicodeSize);
if(!ret) return NULL;
if(pszTempQuery4) lcFree(pszTempQuery4);
return (WCHAR *) pszUnicodeBuffer; }
// This function computes if pszQuery is a FTS operator in full-width alphanumeric.
//
// return value
//
// 0 = not operator
// n = index into pEnglishOperator array of translated English operator
//
int IsJOperator(char *pszQuery) { if((PRIMARYLANGID(GetSystemDefaultLangID())) != LANG_JAPANESE) return FALSE;
if(!pszQuery) return 0;
int i = 1; char *pTerm = (char*)pJOperatorList[i];
while(*pTerm) { if(compareOperator(pszQuery,pTerm)) return i;
pTerm = (char*)pJOperatorList[++i]; }
return 0; }
// Compare operator to query. This is similar to a stricmp.
//
BOOL compareOperator(char *pszQuery, char *pszTerm) { if(!*pszQuery || !*pszTerm) return FALSE;
while(*pszQuery && *pszTerm) { if(*pszQuery != *pszTerm) return FALSE;
++pszQuery; ++pszTerm; }
if(*pszTerm) return FALSE;
return TRUE; }
/////////////////////////////////////////////////////////////////////////////
// CCombinedFTS Class
//
// This class provides full-text search functionality for multiple titles.
//
// This class is intended only for use by the CFullTextSearch class which
// provides full-text search services for both single and multi-title
// configurations.
/////////////////////////////////////////////////////////////////////////////
// CCombinedFTS class constructor
//
CCombinedFTS::CCombinedFTS(CExTitle *pExTitle, LCID lcid, CFullTextSearch *pFTS) { m_lcid = lcid; m_langid = LANGIDFROMLCID(lcid); m_codepage = CodePageFromLCID(lcid); m_pTitle = pExTitle; m_SearchActive = FALSE; m_pIndex = NULL; m_pQuery = NULL; m_pITResultSet = NULL; m_pITDB = NULL; m_pFullTextSearch = pFTS; m_pPrevQuery = NULL; m_SystemLangID = PRIMARYLANGID(GetSystemDefaultLangID()); m_fDBCS = FALSE; m_lMaxRowCount = 500; m_wQueryProximity = 8; m_iLastResultCount = 0; }
/////////////////////////////////////////////////////////////////////////////
// CCombinedFTS class destructor
//
CCombinedFTS::~CCombinedFTS() { if (m_pITResultSet) { m_pITResultSet->Clear(); m_pITResultSet->Release(); } if(m_pPrevQuery) lcFree(m_pPrevQuery);
}
/////////////////////////////////////////////////////////////////////////////
// CCombinedFTS class destructor
//
void CCombinedFTS::ReleaseObjects() {
if(m_pQuery) { m_pQuery->Release(); m_pQuery = NULL; }
if(m_pIndex) { m_pIndex->Close(); m_pIndex->Release(); m_pIndex = NULL; }
if(m_pITDB) { m_pITDB->Close(); m_pITDB->Release(); m_pITDB = NULL; } }
/////////////////////////////////////////////////////////////////////////////
// CCombinedFTS class initialization
//
HRESULT CCombinedFTS::Initialize() { const char *pszQueryName;
if(m_pFullTextSearch && m_pFullTextSearch->m_bMergedChmSetWithCHQ) pszQueryName = m_pFullTextSearch->m_pTitleArray[0].pszQueryName; else pszQueryName = m_pTitle->GetQueryName();
if(!pszQueryName) return E_FAIL;
MultiByteToWideChar(CP_ACP, 0, pszQueryName, -1, m_tcTitlePath, sizeof(m_tcTitlePath) );
WORD PrimaryLang = PRIMARYLANGID(m_langid); if(PrimaryLang == LANG_JAPANESE || PrimaryLang == LANG_CHINESE || PrimaryLang == LANG_KOREAN) m_fDBCS = TRUE; else m_fDBCS = FALSE;
if(m_lcid == 1033) m_dwQueryFlags = IMPLICIT_AND | QUERY_GETTERMS | STEMMED_SEARCH; else m_dwQueryFlags = IMPLICIT_AND | QUERY_GETTERMS;
// char szLangID[20];
//GetLocaleInfo(m_lcid,LOCALE_ILANGUAGE,szLangID,sizeof(szLangID));
// Make sure we have a path
//
if(!*m_tcTitlePath) { return E_FAIL; }
// Get IITIndex pointer
//
HRESULT hr = CoCreateInstance(CLSID_IITIndexLocal, NULL, CLSCTX_INPROC_SERVER, IID_IITIndex, (VOID**)&m_pIndex); if (FAILED(hr)) { return E_FAIL; }
// Get IITDatabase pointer
//
hr = CoCreateInstance(CLSID_IITDatabaseLocal, NULL, CLSCTX_INPROC_SERVER, IID_IITDatabase, (VOID**)&m_pITDB); if (FAILED(hr)) { return E_FAIL; }
// Open the storage system
//
hr = m_pITDB->Open(NULL, m_tcTitlePath, NULL); if (FAILED(hr)) { return E_FAIL; }
// open the index.
//
hr = m_pIndex->Open(m_pITDB, txtwFtiMain, TRUE); if (FAILED(hr)) { return E_FAIL; }
// Create query instance
//
hr = m_pIndex->CreateQueryInstance(&m_pQuery); if (FAILED(hr)) { return E_FAIL; }
// set search options
//
hr = m_pQuery->SetOptions(m_dwQueryFlags); if (FAILED(hr)) { return E_FAIL; }
// Create Result Set object
//
if(!m_pITResultSet) { hr = CoCreateInstance(CLSID_IITResultSet, NULL, CLSCTX_INPROC_SERVER, IID_IITResultSet, (VOID**) &m_pITResultSet); if (FAILED(hr)) { return E_FAIL; } }
m_pITResultSet->Clear();
// we want topic numbers back
//
hr = m_pITResultSet->Add(STDPROP_UID, (DWORD) 0, PRIORITY_NORMAL); if (FAILED(hr)) { // Error adding result property
return E_FAIL; }
hr = m_pITResultSet->Add(STDPROP_TERM_UNICODE_ST, (DWORD)NULL, PRIORITY_NORMAL); if (FAILED(hr)) { // Error adding result property
return E_FAIL;
}
hr = m_pITResultSet->Add(STDPROP_COUNT, (DWORD)NULL, PRIORITY_NORMAL); if (FAILED(hr)) { // Error adding result property
return E_FAIL; }
return S_OK; }
/////////////////////////////////////////////////////////////////////////////
// CCombinedFTS query function
//
HRESULT CCombinedFTS::Query(WCHAR *pwcQuery, DWORD dwFlags, IITResultSet **ppITRS, CFullTextSearch *pFullTextSearch, CUWait *pWaitDlg, int iTitle) { HRESULT hr;
WCHAR *pNewQuery = NULL;
if(FAILED(hr = Initialize())) return hr;
*ppITRS = NULL;
// return if search previous set, but no previous query
//
if((dwFlags & FTS_SEARCH_PREVIOUS) && !m_pPrevQuery) { ReleaseObjects(); return S_OK; }
WCHAR *pszQuery = pwcQuery; pszQuery = PreProcessQuery(pwcQuery, m_codepage);
if(!pszQuery) return E_FAIL;
// If this combined index resulted in no hits last query, but the global result count was non-zero, then
// return no hits. The only time we want to query this combined index using the last known good query is
// when the global query count (combined results) was also zero. In that case, we want to
// revert all titles and combined indexes back to the last known good query. Otherwise, we skip
// this combined index because the last query (which resulted in some results globally) is still valid.
//
if((dwFlags & FTS_SEARCH_PREVIOUS) && pFullTextSearch->m_iLastResultCount && !m_iLastResultCount) { lcFree(m_pPrevQuery); m_pPrevQuery = NULL; return S_OK; }
WCHAR *pPrevQuerySaved = NULL; if(m_pPrevQuery) { pPrevQuerySaved = (WCHAR *) lcMalloc((wcslen(m_pPrevQuery)+ 2) * sizeof(WCHAR)); wcscpy(pPrevQuerySaved,m_pPrevQuery); } if(dwFlags & FTS_ENABLE_STEMMING) m_dwQueryFlags |= STEMMED_SEARCH; else m_dwQueryFlags &= (~STEMMED_SEARCH);
// Set previous options
//
m_pQuery->ReInit(); m_pQuery->SetOptions(m_dwQueryFlags); m_pQuery->SetResultCount(m_lMaxRowCount); m_pQuery->SetProximity(m_wQueryProximity);
FCALLBACK_MSG fCallBackMsg;
fCallBackMsg.MessageFunc = SearchMessageFunc; fCallBackMsg.pUserData = (PVOID)pWaitDlg; // used to pass back userdata
fCallBackMsg.dwFlags = 0; // not recommended for use
m_pQuery->SetResultCallback(&fCallBackMsg);
// create group if doing "search previous results"
//
if(dwFlags & FTS_SEARCH_PREVIOUS) { // append new query onto old query for "search previous"
//
int cQuery = wcslen(pszQuery); int cPrevQuery = wcslen(m_pPrevQuery); pNewQuery = (WCHAR *) lcMalloc((cQuery+cPrevQuery+20) * sizeof(WCHAR)); if(!pNewQuery) { ReleaseObjects(); return E_FAIL; } *pNewQuery = 0; wcscat(pNewQuery,m_pPrevQuery); wcscat(pNewQuery,L" and "); wcscat(pNewQuery,pszQuery); wcscat(pNewQuery,L" "); }
// free the previous prev query
//
if(m_pPrevQuery) lcFree(m_pPrevQuery);
// Save the new query for next time
//
if(pNewQuery) m_pPrevQuery = (WCHAR *) lcMalloc((wcslen(pNewQuery)+ 2) * sizeof(WCHAR)); else m_pPrevQuery = (WCHAR *) lcMalloc((wcslen(pszQuery)+ 2) * sizeof(WCHAR));
if(!m_pPrevQuery) { ReleaseObjects(); return E_FAIL; }
if(pNewQuery) wcscpy(m_pPrevQuery,pNewQuery); else wcscpy(m_pPrevQuery,pszQuery);
// clear the previous results
//
hr = m_pITResultSet->ClearRows(); if (FAILED(hr)) { if(pPrevQuerySaved) lcFree(pPrevQuerySaved); ReleaseObjects(); return E_FAIL; }
// Set the query
//
if(pNewQuery) { pFullTextSearch->AddQueryToTermList(pNewQuery); hr = m_pQuery->SetCommand(pNewQuery); } else { pFullTextSearch->AddQueryToTermList(pszQuery); hr = m_pQuery->SetCommand(pszQuery); }
if (FAILED(hr)) { if(pPrevQuerySaved) lcFree(pPrevQuerySaved); ReleaseObjects(); return E_FAIL; }
if(pNewQuery) lcFree(pNewQuery);
// Execute the query
//
hr = m_pIndex->Search(m_pQuery, m_pITResultSet);
if(hr == E_NOSTEMMER) { m_dwQueryFlags &= (~STEMMED_SEARCH); m_pQuery->SetOptions(m_dwQueryFlags); m_pQuery->SetCommand(pszQuery); hr = m_pIndex->Search(m_pQuery, m_pITResultSet); }
long lRowCount;
m_pITResultSet->GetRowCount(lRowCount);
m_iLastResultCount = lRowCount;
// If query failed, then restore the previous query (for next search previous)
//
if((FAILED(hr) || !lRowCount) && pPrevQuerySaved) { if(m_pPrevQuery) lcFree(m_pPrevQuery); m_pPrevQuery = (WCHAR *) lcMalloc((wcslen(pPrevQuerySaved)+ 2) * sizeof(WCHAR)); wcscpy(m_pPrevQuery,pPrevQuerySaved); }
if(pPrevQuerySaved) lcFree(pPrevQuerySaved);
if (hr ==E_NULLQUERY || hr == E_MISSQUOTE || hr == E_EXPECTEDTERM || hr == E_MISSLPAREN || hr == E_MISSRPAREN || hr == E_ALL_WILD) { // Error invalid syntax
//
ReleaseObjects(); return FTS_INVALID_SYNTAX; }
if(hr == FTS_CANCELED) { ReleaseObjects(); return FTS_CANCELED; }
// Generic error
//
if (FAILED(hr)) { ReleaseObjects(); return E_FAIL; }
// Add search terms to term list
//
// Make sure we have results
//
if(lRowCount) { WCHAR wcBuffer[2048];
int i; CProperty Prop, Prop2, Prop3; DWORD dwLastTopic = 0xffffffff, dwNextWordCount;
WCHAR *pwcTemp = wcBuffer;
*pwcTemp = 0;
// prime the topic number
//
m_pITResultSet->Get(0,0, Prop3); dwLastTopic = Prop3.dwValue;
// walk through the results
//
for (i = 0; i < lRowCount; i++) { m_pITResultSet->Get(i,0, Prop3); if(i<lRowCount) { m_pITResultSet->Get(i+1,2, Prop2); dwNextWordCount = Prop2.dwValue; } else dwNextWordCount = 0;
m_pITResultSet->Get(i,1, Prop); m_pITResultSet->Get(i,2, Prop2);
if(!Prop.dwValue) continue;
WORD wFlags[2]; wFlags[0]=0;
// Convert term from Unicode to ANSI because GetStringTypeW is not supported on Win95.
//
WORD dwStrLen = (WORD)(wcslen(Prop.lpszwData + 1) * sizeof(WCHAR)); if(dwStrLen) { char *pAnsiString = (char *) lcMalloc(dwStrLen); DWORD dwTermLen = 1;
WideCharToMultiByte(m_codepage, 0, Prop.lpszwData + 1, -1, pAnsiString, dwStrLen, NULL, NULL); if(IsDBCSLeadByteEx(m_codepage, *pAnsiString)) dwTermLen=2;
GetStringTypeA(m_lcid, CT_CTYPE3, pAnsiString, dwTermLen, wFlags);
lcFree(pAnsiString); }
// skip DB chars (DB words were already added)
//
if(wFlags[0] & C3_FULLWIDTH || wFlags[0] & C3_KATAKANA || wFlags[0] & C3_HIRAGANA) { if(*pwcTemp) { pFullTextSearch->AddHLTerm(wcBuffer,wcslen(wcBuffer)); pwcTemp = wcBuffer; *pwcTemp = 0; } dwLastTopic = Prop3.dwValue; continue; }
CopyMemory(pwcTemp, Prop.lpszwData + 1, (*((WORD *)Prop.lpszwData) * sizeof(WCHAR))); pwcTemp+=*((WORD *)Prop.lpszwData); *pwcTemp = 0;
if(dwNextWordCount != (Prop2.dwValue+1) || (wcslen(wcBuffer) > 500)) { pFullTextSearch->AddHLTerm(wcBuffer,wcslen(wcBuffer)); pwcTemp = wcBuffer; } else { *pwcTemp++ = L' '; *pwcTemp = 0; } dwLastTopic = Prop3.dwValue; } }
// Send the results set back
//
*ppITRS = m_pITResultSet;
ReleaseObjects();
return S_OK; }
/////////////////////////////////////////////////////////////////////////////
// CCombinedFTS abort current query
//
HRESULT CCombinedFTS::AbortQuery() { // DOUGO - insert abort code here when Centaur support is available
return S_OK; }
/////////////////////////////////////////////////////////////////////////////
// CCombinedFTS update FTS options without calling into Centaur
//
HRESULT CCombinedFTS::UpdateOptions(WORD wNear, LONG cRows) { m_wQueryProximity = wNear; m_lMaxRowCount = cRows; return S_OK; }
/////////////////////////////////////////////////////////////////////////////
// CCombinedFTS set title FTS options
//
HRESULT CCombinedFTS::SetOptions(DWORD dwFlag) { HRESULT hr = E_FAIL;
m_dwQueryFlags = dwFlag;
if(m_pQuery) hr = m_pQuery->SetOptions(dwFlag);
if (FAILED(hr)) return E_FAIL; else return S_OK; }
/////////////////////////////////////////////////////////////////////////////
// CCombinedFTS set proximity for title
//
HRESULT CCombinedFTS::SetProximity(WORD wNear) { HRESULT hr = E_FAIL;
m_wQueryProximity = wNear;
if(m_pQuery) hr = m_pQuery->SetProximity(wNear);
return hr; }
/////////////////////////////////////////////////////////////////////////////
// CCombinedFTS set max result count for title query
//
HRESULT CCombinedFTS::SetResultCount(LONG cRows) { HRESULT hr = E_FAIL;
m_lMaxRowCount = cRows;
if(m_pQuery) hr = m_pQuery->SetResultCount(cRows);
return hr; }
ERR SearchMessageFunc(DWORD dwFlag, LPVOID pUserData, LPVOID pMessage) { MSG msg; CUWait *pUW = (CUWait *)pUserData;
if(!pUW->m_bVisable) { ShowWindow(pUW->m_hwndUWait, SW_SHOW); pUW->m_bVisable = TRUE; }
while (PeekMessage(&msg, pUW->m_hwndUWait, 0, 0, PM_REMOVE)) { if(!IsDialogMessage(pUW->m_hwndUWait, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } }
if (pUW->m_bUserCancel == TRUE) return FTS_CANCELED;
return S_OK; // return something else to abort
}
|