mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2890 lines
80 KiB
2890 lines
80 KiB
// 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
|
|
}
|