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

1390 lines
43 KiB

  1. /*************************************************************************
  2. * *
  3. * PARSER.C *
  4. * *
  5. * Copyright (C) Microsoft Corporation 1990-1994 *
  6. * All Rights reserved. *
  7. * *
  8. **************************************************************************
  9. * *
  10. * Module Intent *
  11. * Boolean & Flat parser *
  12. * *
  13. **************************************************************************
  14. * *
  15. * Current Owner: BinhN *
  16. * *
  17. **************************************************************************/
  18. #include <mvopsys.h>
  19. #include <mem.h>
  20. #include <memory.h>
  21. #ifdef DOS_ONLY // {
  22. #ifdef _DEBUG
  23. #include <stdio.h>
  24. #include <assert.h>
  25. #endif
  26. #endif // } _DEBUG && DOS_ONLY
  27. #include <mvsearch.h>
  28. #include "common.h"
  29. #include "search.h"
  30. #ifdef _DEBUG
  31. static BYTE NEAR s_aszModule[] = __FILE__; /* Used by error return functions.*/
  32. #endif
  33. #ifdef _DEBUG
  34. int Debug;
  35. #endif
  36. #define NEWLINE '\n'
  37. #define CRETURN '\r'
  38. typedef struct
  39. {
  40. LPB OpName;
  41. DWORD dwOffset;
  42. USHORT OpVal;
  43. } STACK_NODE;
  44. typedef struct OPSTACK
  45. {
  46. BYTE DefaultOpVal;
  47. int Top;
  48. char cParentheses;
  49. char cQuotes;
  50. char fRequiredOp;
  51. char fRequiredTerm;
  52. STACK_NODE Stack[STACK_SIZE];
  53. } OPSTACK,
  54. FAR * _LPSTACK;
  55. /*************************************************************************
  56. * EXTERNAL VARIABLES
  57. * All those variables must be read only
  58. *************************************************************************/
  59. extern OPSYM OperatorSymbolTable[];
  60. extern BYTE LigatureTable[];
  61. /*************************************************************************
  62. *
  63. * API FUNCTIONS
  64. * Those functions should be exported in a .DEF file
  65. *************************************************************************/
  66. PUBLIC LPQT EXPORT_API FAR PASCAL MVQueryParse(LPPARSE_PARMS, PHRESULT);
  67. /*************************************************************************
  68. *
  69. * INTERNAL PRIVATE FUNCTIONS
  70. * All of them should be declared near
  71. *************************************************************************/
  72. PRIVATE VOID PASCAL NEAR LowLevelTransformation (LPQI, CB, LPOPSYM, int, LPSIPB);
  73. static LSZ PASCAL NEAR StringToLong (LSZ, LPDW);
  74. PRIVATE char NEAR is_special_char (BYTE, LPQI, BYTE);
  75. PRIVATE HRESULT PASCAL NEAR StackAddToken (LPQI, int, LST, DWORD, BOOL);
  76. PRIVATE HRESULT PASCAL NEAR PushOperator (LPQI, int, DWORD);
  77. PRIVATE int PASCAL NEAR GetType(LPQI, char FAR *);
  78. PRIVATE HRESULT PASCAL NEAR StackFlush (LPQI);
  79. PRIVATE BOOL PASCAL NEAR ChangeToOpSym (LPOPSYM, int, LPB, int);
  80. PRIVATE HRESULT PASCAL NEAR CopyNode (_LPQT lpQt, _LPQTNODE FAR *plpQtNode,
  81. _LPQTNODE lpSrcNode);
  82. __inline BOOL IsCharWhitespace (BYTE cVal);
  83. /*************************************************************************
  84. *
  85. * INTERNAL GLOBAL FUNCTIONS
  86. * All of them should be declared far, unless they are known to be called
  87. * in the same segment
  88. *************************************************************************/
  89. PUBLIC HRESULT PASCAL FAR EXPORT_API FCallBack (LST lstRawWord, LST lstNormWord,
  90. LFO lfoWordOffset, LPQI lpQueryInfo);
  91. /*************************************************************************
  92. * @doc API RETRIEVAL
  93. *
  94. * @func LPQT FAR PASCAL | MVQueryParse |
  95. * Given a query, this function will parse and build a query expression
  96. * according to the parameters specifications
  97. *
  98. * @parm LPPARSE_PARMS | lpParms |
  99. * Pointer to a structure containing all parameters necessary
  100. * for the parsing.
  101. *
  102. * @parm PHRESULT | phr |
  103. * Error buffer
  104. *
  105. * @rdesc NULL if failed, else pointer to a query expression if succeeded.
  106. * This pointer will be used in subsequent calls to IndexSearch()
  107. *************************************************************************/
  108. PUBLIC LPQT EXPORT_API FAR PASCAL MVQueryParse (LPPARSE_PARMS lpParms,
  109. PHRESULT phr)
  110. {
  111. HRESULT fRet; // Return value.
  112. HANDLE hqi; // Handle to "lpqi".
  113. HANDLE hibi; // Handle to internal breaker info
  114. HANDLE hQuery; // Handle to secondary query buffer
  115. GHANDLE hStack; // Handle to stack buffer
  116. LPQI lpQueryInfo; // Query information.
  117. LPB lpbQueryBuf; // Copy of query's buffer
  118. _LPSTACK lpStack; // Postfix stack pointer
  119. char Operator; // Operator
  120. WORD i,j; // Scratch variables
  121. _LPQT lpQueryTree; // Query tree pointer
  122. _LPOPTAB lpOpTab; // Operator table structure
  123. NODE_PARM parms; // Parameter of unary operator
  124. LPB lpbTemp; // Temporary pointer
  125. DWORD dwOffset; // Offset from the beginning of the query
  126. BOOL fFlatQuery; // Is it QuickKey search?
  127. /* LPPARSE_PARMS structure break-out variables */
  128. LPCSTR lpbQuery; // Query buffer
  129. DWORD cbQuery; // Query length
  130. WORD cProxDist; // Proximity distance
  131. WORD cDefOp; // Default operator
  132. PEXBRKPM pexbrkpm; // External breaker param struct.
  133. LPGROUP lpGroup; // Group
  134. lpbQuery = lpParms->lpbQuery;
  135. cbQuery = lpParms->cbQuery;
  136. cProxDist = lpParms->cProxDist;
  137. cDefOp = lpParms->cDefOp;
  138. pexbrkpm = lpParms->pexbrkpm;
  139. lpGroup = lpParms->lpGroup;
  140. /* The bit is used to tell search that QuickKeys is used. In
  141. * QuickKeys, operators are treated like regular words
  142. */
  143. cDefOp = lpParms->cDefOp & ~TL_QKEY;
  144. fFlatQuery = lpParms->cDefOp & TL_QKEY;
  145. if (pexbrkpm == NULL)
  146. {
  147. SetErrCode(phr, E_BADBREAKER);
  148. return NULL;
  149. }
  150. if (cbQuery == 0 || lpbQuery == NULL) {
  151. SetErrCode(phr, E_NULLQUERY);
  152. return NULL;
  153. }
  154. if (cDefOp > MAX_DEFAULT_OP)
  155. {
  156. SetErrCode(phr, E_INVALIDARG);
  157. return NULL;
  158. }
  159. lpQueryTree = NULL;
  160. hqi = hibi = hQuery = hStack = NULL;
  161. /* Allocate query info. */
  162. if ((hqi = (GHANDLE)_GLOBALALLOC(GMEM_MOVEABLE | GMEM_ZEROINIT,
  163. (LCB)sizeof(QUERY_INFO))) == NULL)
  164. {
  165. fRet = SetErrCode(phr, E_OUTOFMEMORY);
  166. goto ErrFreeAll;
  167. }
  168. lpQueryInfo = (LPQI)_GLOBALLOCK(hqi);
  169. lpQueryInfo->lperrb = phr;
  170. lpQueryInfo->fFlag |= (DWORD)(lpParms->wCompoundWord & CW_PHRASE);
  171. /* Set the flat query operator symbol table */
  172. if (fFlatQuery)
  173. {
  174. lpQueryInfo->lpOpSymTab = FlatOpSymbolTable;
  175. cDefOp = OR_OP;
  176. }
  177. else if ((lpOpTab = (_LPOPTAB)lpParms->lpOpTab) == NULL ||
  178. lpOpTab->lpOpsymTab == NULL)
  179. {
  180. /* Use default operator table */
  181. lpQueryInfo->lpOpSymTab = OperatorSymbolTable;
  182. lpQueryInfo->cOpEntry = OPERATOR_ENTRY_COUNT;
  183. }
  184. else
  185. {
  186. /* Get the operators' count */
  187. lpQueryInfo->lpOpSymTab = lpOpTab->lpOpsymTab;
  188. lpQueryInfo->cOpEntry = lpOpTab->cEntry;
  189. }
  190. // Initialize breaker param to break words as text.
  191. pexbrkpm->dwBreakWordType = 0;
  192. /* Allocate a stack for postfix operators handler */
  193. if ((hStack = (GHANDLE)_GLOBALALLOC(GMEM_MOVEABLE | GMEM_ZEROINIT,
  194. (LCB)sizeof(OPSTACK))) == NULL)
  195. {
  196. fRet = SetErrCode(phr, E_OUTOFMEMORY);
  197. goto ErrFreeAll;
  198. }
  199. lpStack = (_LPSTACK)_GLOBALLOCK(hStack);
  200. lpStack->DefaultOpVal = (BYTE)cDefOp;
  201. lpStack->fRequiredOp = lpStack->fRequiredTerm = 0;
  202. lpStack->cParentheses = lpStack->cQuotes = 0;
  203. lpStack->Top = -1;
  204. lpQueryInfo->lpStack = (LPV)lpStack;
  205. /* Allocate a query tree */
  206. if ((lpQueryTree = (_LPQT)QueryTreeAlloc()) == NULL)
  207. {
  208. fRet = SetErrCode(phr, E_OUTOFMEMORY);
  209. goto ErrFreeAll;
  210. }
  211. /* Associate the query tree with the query. In the future, this will
  212. * ensure the capability to have several queries and query trees
  213. * at once
  214. */
  215. lpQueryInfo->lpQueryTree = (LPQT)lpQueryTree;
  216. /* Default arguments */
  217. lpQueryTree->iDefaultOp = (BYTE)cDefOp;
  218. lpQueryTree->lpGroup = lpGroup; // Use default Group
  219. lpQueryTree->dwFieldId = DW_NIL_FIELD; // No fieldid search
  220. lpQueryTree->cStruct.dwKey = CALLBACKKEY;
  221. /* Suppose that we have all OR or all AND query */
  222. lpQueryTree->fFlag |= (ALL_OR | ALL_AND);
  223. lpQueryTree->fFlag |= ALL_ANDORNOT;
  224. if (cProxDist == 0)
  225. lpQueryTree->wProxDist = DEF_PROX_DIST;
  226. else
  227. lpQueryTree->wProxDist = cProxDist;
  228. /* Copy the query into a temporary buffer since we are going to make
  229. change to it
  230. */
  231. if ((hQuery = _GLOBALALLOC(DLLGMEM_ZEROINIT, (LCB)cbQuery + 2)) == NULL)
  232. {
  233. SetErrCode(phr, E_OUTOFMEMORY);
  234. FreeHandle(hqi);
  235. return NULL;
  236. }
  237. lpbQueryBuf = lpQueryInfo->lpbQuery = (LPB)_GLOBALLOCK(hQuery);
  238. lpbQueryBuf[cbQuery] = ' '; // Add a space to help LowLeveltransformation
  239. lpbQueryBuf[cbQuery + 1] = 0; // Zero-terminated string (safety bytes)
  240. MEMCPY(lpbQueryBuf, lpbQuery, cbQuery);
  241. /* Change to low level query */
  242. LowLevelTransformation (lpQueryInfo, (CB)(cbQuery + sizeof(SHORT)),
  243. (LPOPSYM)lpQueryInfo->lpOpSymTab, lpQueryInfo->cOpEntry, pexbrkpm);
  244. /* Do the parsing */
  245. for (i = 0; i < cbQuery; )
  246. {
  247. if ((Operator = is_special_char(lpbQueryBuf[i],
  248. lpQueryInfo, (BYTE)(lpQueryInfo->fFlag & IN_PHRASE))) != -1)
  249. {
  250. dwOffset = i;
  251. i++;
  252. switch (Operator)
  253. {
  254. // A stopword. We want to add it
  255. // to tree, but not shove it on to operator stack
  256. case STOP_OP:
  257. if ((fRet = StackAddToken(lpQueryInfo, STOP_OP,
  258. NULL, dwOffset, 0)) != S_OK)
  259. {
  260. fRet = VSetUserErr(phr, fRet, (WORD)(i-1));
  261. goto ErrFreeAll;
  262. }
  263. continue;
  264. case QUOTE:
  265. /* Mark the beginning and end of phrase, so that words
  266. that looks like operators inside phrase will not be changed
  267. to operator
  268. */
  269. if (fFlatQuery == FALSE)
  270. lpQueryInfo->fFlag ^= IN_PHRASE;
  271. break;
  272. case GROUP_OP:
  273. /* The argument is a FAR pointer */
  274. lpQueryTree->lpGroup =
  275. (LPGROUP) (*((UNALIGNED void **)(&lpbQueryBuf[i])));
  276. i += sizeof(void *);
  277. continue;
  278. case FIELD_OP:
  279. /* The argument is an ASCII string number */
  280. if ((lpbTemp = StringToLong(&lpbQueryBuf[i],
  281. &lpQueryTree->dwFieldId)) == NULL)
  282. {
  283. /* Missing argument */
  284. fRet = VSetUserErr(phr, E_BADVALUE, i);
  285. goto ErrFreeAll;
  286. };
  287. i += (WORD)(lpbTemp - (LPB)&lpbQueryBuf[i]); // Move pointer
  288. lpQueryTree->fFlag &= ~(ALL_OR | ALL_AND | ALL_ANDORNOT);
  289. continue;
  290. case BRKR_OP :
  291. /* The argument is an ASCII string number */
  292. if ((lpbTemp = StringToLong(&lpbQueryBuf[i],
  293. &parms.dwValue)) == NULL)
  294. {
  295. /* Missing argument */
  296. fRet = VSetUserErr(phr, E_BADVALUE, i);
  297. goto ErrFreeAll;
  298. };
  299. /* Make sure that we got a breaker function */
  300. if ((WORD)parms.dwValue > (WORD)MAXNUMBRKRS)
  301. {
  302. fRet = VSetUserErr(phr, E_BADVALUE, i);
  303. goto ErrFreeAll;
  304. }
  305. else
  306. pexbrkpm->dwBreakWordType = parms.dwValue;
  307. i += (WORD)(lpbTemp - (LPB)&lpbQueryBuf[i]); // Move pointer
  308. /* Save the breaker type info */
  309. lpQueryTree->wBrkDtype = (WORD)parms.dwValue;
  310. continue;
  311. }
  312. if ((fRet = StackAddToken(lpQueryInfo, Operator,
  313. (LST)&parms.lpStruct, dwOffset, 0)) != S_OK)
  314. {
  315. fRet = VSetUserErr(phr, fRet, (WORD)(i-1));
  316. goto ErrFreeAll;
  317. }
  318. }
  319. else
  320. {
  321. /* Find the next special character */
  322. for (j = i + 1; j < cbQuery; j++)
  323. {
  324. if (fFlatQuery == FALSE &&
  325. is_special_char((BYTE)lpbQueryBuf[j], lpQueryInfo,
  326. (BYTE)(lpQueryInfo->fFlag & IN_PHRASE)) != -1)
  327. break;
  328. }
  329. //
  330. // Word-break between here and there.
  331. //
  332. pexbrkpm->lpbBuf = lpbQueryBuf + i;
  333. pexbrkpm->cbBufCount = j - i;
  334. pexbrkpm->lpvUser = lpQueryInfo;
  335. pexbrkpm->lpfnOutWord = (FWORDCB)FCallBack;
  336. pexbrkpm->fFlags = ACCEPT_WILDCARD;
  337. // Set this for compound word-breaking. If this is set
  338. // we can use implicit phrase when we get the first term
  339. lpQueryInfo->fFlag &= ~FORCED_PHRASE;
  340. lpQueryInfo->pWhitespace = NULL;
  341. lpQueryInfo->dwOffset = i;
  342. if ((fRet = ExtBreakText(pexbrkpm)) != S_OK)
  343. {
  344. fRet = SetErrCode(phr, fRet);
  345. goto ErrFreeAll;
  346. }
  347. // Turn off the Forced Phrase if it is set
  348. if (lpQueryInfo->fFlag & FORCED_PHRASE)
  349. {
  350. lpQueryInfo->fFlag &= ~FORCED_PHRASE;
  351. if (S_OK != (fRet = StackAddToken
  352. (lpQueryInfo, QUOTE, NULL, cbQuery, FALSE)))
  353. {
  354. fRet = SetErrCode(phr, fRet);
  355. goto ErrFreeAll;
  356. }
  357. }
  358. i = j;
  359. }
  360. }
  361. /* Set the position of pointer to report missing term at
  362. the end of the query. -1 since the offset starts at 0
  363. */
  364. lpQueryInfo->dwOffset = cbQuery - 1;
  365. fRet = E_FAIL;
  366. /* Flush all operators on the stack into the query tree, and
  367. * build the binary query tree
  368. */
  369. if ((fRet = StackFlush(lpQueryInfo)) == S_OK)
  370. {
  371. LPQT lpTempQT;
  372. if ((lpTempQT = QueryTreeBuild(lpQueryInfo)) != NULL)
  373. {
  374. lpQueryTree = lpTempQT;
  375. fRet = S_OK;
  376. }
  377. else
  378. {
  379. fRet = *phr;
  380. }
  381. }
  382. else
  383. {
  384. VSetUserErr(phr, fRet, (WORD)lpQueryInfo->dwOffset);
  385. }
  386. ErrFreeAll:
  387. /* Free query info */
  388. if (hqi)
  389. {
  390. FreeHandle(hqi);
  391. };
  392. /* Free internal query buffer info */
  393. if (hQuery)
  394. {
  395. FreeHandle(hQuery);
  396. };
  397. /* Free internal stack buffer info */
  398. if (hStack)
  399. {
  400. FreeHandle(hStack);
  401. };
  402. if (fRet == S_OK)
  403. return lpQueryTree;
  404. else
  405. {
  406. if (lpQueryTree)
  407. {
  408. BlockFree(lpQueryTree->lpStringBlock);
  409. #ifndef SIMILARITY
  410. BlockFree(lpQueryTree->lpWordInfoBlock);
  411. #endif
  412. BlockFree(lpQueryTree->lpOccMemBlock);
  413. BlockFree(lpQueryTree->lpTopicMemBlock);
  414. BlockFree(lpQueryTree->lpNodeBlock);
  415. /* Free Query tree block */
  416. FreeHandle ((HANDLE)lpQueryTree->cStruct.dwReserved);
  417. }
  418. return NULL;
  419. }
  420. }
  421. /*************************************************************************
  422. * @doc INTERNAL
  423. *
  424. * @func HRESULT PASCAL FAR | FCallBack |
  425. * This call back function is called by various breakers after
  426. * fetching a token. The token is checked for wild char presence
  427. *
  428. * @parm LST | lstRawWord |
  429. * Pointer to unnormalized string
  430. *
  431. * @parm LST | lstNormWord |
  432. * Pointer to normalized string. This pascal string's size should be
  433. * at least *lstNormWord+2
  434. *
  435. * @parm LFO | lfoWordOffset |
  436. * Offset into the query buffer. It is used to mark the location
  437. * where an parsing error has occurred
  438. *
  439. * @parm LPQI | lpqi |
  440. * Pointer to query info structure. This has all "global" variables
  441. *
  442. * @rdesc S_OK if succeeded, else various errors.
  443. *************************************************************************/
  444. PUBLIC HRESULT PASCAL FAR EXPORT_API FCallBack (LST lstRawWord, LST lstNormWord,
  445. LFO lfoWordOffset, LPQI lpqi)
  446. {
  447. BYTE WordType; // Type of the token
  448. BOOL fWildChar; // Flag for wild char presence
  449. HRESULT fRet; // Return value
  450. register int i;
  451. #ifdef _DEBUG
  452. char szWord[1024] = {0};
  453. // erinfox: GETWORD byte-swaps on Mac and causes crash
  454. MEMCPY (szWord, lstNormWord + sizeof (WORD), *(LPW)(lstNormWord));
  455. OutputDebugString ("|");
  456. OutputDebugString (szWord);
  457. OutputDebugString ("|\n");
  458. #endif /* _DEBUG */
  459. /*
  460. * A token is a TERM_TOKEN if
  461. * - If doesn't match any description for operators
  462. * - Or it is inside a phrase
  463. * For term, we have to check for wild chars
  464. */
  465. if ((lpqi->fFlag & IN_PHRASE) ||
  466. (WordType = (BYTE) GetType(lpqi, lstNormWord)) == TERM_TOKEN)
  467. {
  468. WordType = TERM_TOKEN;
  469. /* Add extra 0 to make sure that AllocWord() gets the needed 0
  470. * for WildCardCompare()
  471. */
  472. lstNormWord[*(LPW)(lstNormWord) + 2] = 0;
  473. /* Regular expression search accepted */
  474. fWildChar = FALSE;
  475. if ((lpqi->fFlag & IN_PHRASE) == FALSE)
  476. {
  477. BYTE fGetStar = FALSE;
  478. BYTE fAllWild = TRUE; // Assume all wildcard chars
  479. for (i = *(LPW)(lstNormWord) + 1; i > 1; i--)
  480. {
  481. if (lstNormWord[i] == WILDCARD_STAR)
  482. {
  483. fWildChar = TRUE;
  484. fGetStar = TRUE;
  485. }
  486. else if (lstNormWord[i] == WILDCARD_CHAR)
  487. {
  488. fWildChar = TRUE;
  489. }
  490. else
  491. fAllWild = FALSE;
  492. }
  493. if (fGetStar && fAllWild)
  494. {
  495. return VSetUserErr(lpqi->lperrb, E_ALL_WILD,
  496. (WORD)lfoWordOffset);
  497. }
  498. }
  499. }
  500. /*
  501. Okay, here's the implicit phrase scheme.
  502. We keep a pointer the the next whitespace. There are 3 conditions
  503. that we must handle. All of this conditions must also check to make
  504. sure that explicit phrasing is not on.
  505. 1) Phrasing is not on and term does not contain wildcard(s):
  506. TURN ON
  507. 2) Term that comes after the whitespace pointer:
  508. TURN OFF, THEN ON
  509. 3) Term contains wildcard(s):
  510. TURN OFF
  511. Note: We need to make sure that phrasing is turned off before we enter
  512. this callback and turned back off once we are done.
  513. */
  514. // Make sure we are not in a normal (explicit) phrase
  515. // And that the user asked for implicit phrasing
  516. if (!(lpqi->fFlag & IN_PHRASE) && (lpqi->fFlag & CW_PHRASE))
  517. {
  518. LPB pWhitespace = lpqi->pWhitespace;
  519. LPB pTerm = lpqi->lpbQuery + lfoWordOffset + lpqi->dwOffset;
  520. // Condition 1. We have a term and phrase is off
  521. if (!(lpqi->fFlag & FORCED_PHRASE) && FALSE == fWildChar)
  522. {
  523. lpqi->fFlag |= FORCED_PHRASE;
  524. if ((fRet = StackAddToken(lpqi, QUOTE, NULL,
  525. lfoWordOffset ? lfoWordOffset - 1 : 0, FALSE)) != S_OK)
  526. {
  527. return VSetUserErr(lpqi->lperrb, fRet, (WORD)lfoWordOffset);
  528. }
  529. }
  530. // Condition 2 or 3
  531. else if (lpqi->fFlag & FORCED_PHRASE)
  532. {
  533. // Condition 3. Wildcard and phrase is already on
  534. if (TRUE == fWildChar)
  535. {
  536. lpqi->fFlag &= ~FORCED_PHRASE;
  537. if ((fRet = StackAddToken(lpqi, QUOTE, NULL, lfoWordOffset
  538. + *(LPW)(lstRawWord), fWildChar)) != S_OK)
  539. {
  540. return VSetUserErr(lpqi->lperrb, fRet, (WORD)lfoWordOffset);
  541. }
  542. }
  543. // Condition 2. Term comes after the whitespace pointer
  544. else if (pWhitespace < pTerm)
  545. {
  546. // Turn phrase off
  547. if ((fRet = StackAddToken(lpqi, QUOTE, NULL,
  548. (DWORD)(pWhitespace - lpqi->lpbQuery), fWildChar)) != S_OK)
  549. {
  550. return VSetUserErr(lpqi->lperrb, fRet, (WORD)lfoWordOffset);
  551. }
  552. // Turn phrase on
  553. if ((fRet = StackAddToken(lpqi, QUOTE, NULL,
  554. (DWORD)(pWhitespace - lpqi->lpbQuery), fWildChar)) != S_OK)
  555. {
  556. return VSetUserErr(lpqi->lperrb, fRet, (WORD)lfoWordOffset);
  557. }
  558. }
  559. }
  560. // Find the next whitespace
  561. if (pWhitespace < pTerm + *(LPW)(lstRawWord))
  562. {
  563. pWhitespace = pTerm + *(LPW)(lstRawWord);
  564. // There is always a space at the end of the query, so the
  565. // *pWhitespace check is just a extra safety margin.
  566. while (*pWhitespace &&
  567. !(IsCharWhitespace (*pWhitespace)))
  568. {
  569. pWhitespace++;
  570. }
  571. // Save variable
  572. lpqi->pWhitespace = pWhitespace;
  573. }
  574. }
  575. /* Add the token */
  576. if ((fRet = StackAddToken(lpqi, WordType, lstNormWord,
  577. lfoWordOffset, fWildChar)) != S_OK)
  578. {
  579. return VSetUserErr(lpqi->lperrb, fRet, (WORD)lfoWordOffset);
  580. }
  581. return S_OK;
  582. }
  583. /*************************************************************************
  584. * @doc INTERNAL
  585. *
  586. * @func HRESULT PASCAL NEAR | PushOperator |
  587. * Push an operator onto the stack. This is done in two steps:
  588. * - Pop all operators on the stack into the query tree
  589. * - Then push the new operator onto the stack
  590. *
  591. * @parm LPQI | lpQueryInfo |
  592. * Pointer to query tree. This has all global informations
  593. *
  594. * @parm int | OpValue |
  595. * Operator to be pushed onto the stack
  596. *
  597. * @parm DWORD | dwOffset |
  598. * Offset from the beginning of the query
  599. *
  600. * @rdesc S_OK if succeeded, errors otherwise
  601. *************************************************************************/
  602. PRIVATE HRESULT PASCAL NEAR PushOperator (LPQI lpQueryInfo, int OpValue,
  603. DWORD dwOffset)
  604. {
  605. HRESULT fRet;
  606. _LPSTACK lpStack = (_LPSTACK)lpQueryInfo->lpStack;
  607. _LPQT lpQueryTree = lpQueryInfo->lpQueryTree;
  608. WORD opVal;
  609. if (OpValue < MAX_OPERATOR)
  610. {
  611. while (lpStack->Top >= 0 &&
  612. (opVal = lpStack->Stack[lpStack->Top].OpVal) <= MAX_OPERATOR) {
  613. if ((fRet = QueryTreeAddToken(lpQueryTree, opVal,
  614. NULL, lpStack->Stack[lpStack->Top].dwOffset, 0)) != S_OK)
  615. return fRet;
  616. lpStack->Top--;
  617. }
  618. }
  619. if (++lpStack->Top == STACK_SIZE)
  620. return E_TOODEEP;
  621. lpStack->Stack[lpStack->Top].OpVal = (BYTE)OpValue;
  622. lpStack->Stack[lpStack->Top].OpName = (OpValue < MAX_OPERATOR ?
  623. OperatorSymbolTable[OpValue].OpName: NULL);
  624. lpStack->Stack[lpStack->Top].dwOffset = dwOffset;
  625. return (S_OK);
  626. }
  627. /*************************************************************************
  628. * @doc INTERNAL
  629. *
  630. * @func HRESULT NEAR PASCAL | StackAddToken |
  631. * This function will:
  632. * - Add a term into the query tree. It also creates and add the
  633. * default operator into the stack if necessary to compensate for
  634. * missing operators between two terms
  635. *
  636. * @parm LPQI | lpQueryInfo |
  637. * Pointer to query info
  638. *
  639. * @parm int | OpValue |
  640. * Operator/term to be added onto the stack/query
  641. *
  642. * @parm LST | p |
  643. * Name of the token. For term, this is the word itself
  644. *
  645. * @parm HRESULT | fWildChar |
  646. * Wild char flag for terms
  647. *
  648. * @rdesc S_OK if succeeded, errors otherwise
  649. *************************************************************************/
  650. PRIVATE HRESULT NEAR PASCAL StackAddToken (LPQI lpQueryInfo, int OpValue,
  651. LST p, DWORD dwOffset, BOOL fWildChar)
  652. {
  653. HRESULT fRet;
  654. _LPQT lpQueryTree = lpQueryInfo->lpQueryTree;
  655. _LPSTACK lpStack = (_LPSTACK)lpQueryInfo->lpStack;
  656. WORD opVal;
  657. // erinfox: add code to handle adding a NULL token
  658. switch (OpValue)
  659. {
  660. case TERM_TOKEN:
  661. case STOP_OP: // Null term/token, like a stopword
  662. /* If an operator is needed between this term and the previous
  663. * one, then insert an operator
  664. */
  665. if (lpStack->fRequiredOp)
  666. {
  667. if ((fRet = PushOperator(lpQueryInfo,
  668. lpStack->DefaultOpVal, dwOffset)) != S_OK)
  669. return fRet;
  670. }
  671. /* Add the term into the query tree */
  672. lpStack->fRequiredOp = TRUE;
  673. if ((fRet = QueryTreeAddToken(lpQueryTree, OpValue,
  674. p, dwOffset, fWildChar)) != S_OK)
  675. return fRet;
  676. lpStack->fRequiredTerm = FALSE;
  677. break;
  678. case QUOTE:
  679. if (lpStack->cQuotes == 0)
  680. {
  681. /* This is the first quote */
  682. if (lpStack->fRequiredOp)
  683. {
  684. if ((fRet = PushOperator(lpQueryInfo,
  685. lpStack->DefaultOpVal, dwOffset)) != S_OK)
  686. return fRet;
  687. lpStack->fRequiredOp = FALSE;
  688. }
  689. if ((fRet = PushOperator(lpQueryInfo, OpValue, dwOffset)) != S_OK)
  690. return fRet;
  691. /* Change the default operator */
  692. lpStack->DefaultOpVal = PHRASE_OP;
  693. lpQueryTree->fFlag &= ~(ALL_OR | ALL_AND | ALL_ANDORNOT);
  694. lpStack->cQuotes++;
  695. lpStack->fRequiredTerm = TRUE;
  696. }
  697. else
  698. {
  699. /* Check for term inside quotes */
  700. if (lpStack->fRequiredTerm)
  701. return E_EXPECTEDTERM;
  702. lpStack->cQuotes--;
  703. while ((opVal = lpStack->Stack[lpStack->Top].OpVal) != QUOTE)
  704. {
  705. if ((fRet = QueryTreeAddToken(lpQueryTree,
  706. opVal, NULL, lpStack->Stack[lpStack->Top].dwOffset,
  707. fWildChar)) != S_OK)
  708. return fRet;
  709. lpStack->Top--;
  710. }
  711. lpStack->Top --; // Pop the quote
  712. /* Change the default operator */
  713. lpStack->DefaultOpVal= (BYTE)lpQueryTree->iDefaultOp;
  714. lpStack->fRequiredOp = TRUE;
  715. }
  716. break;
  717. case LEFT_PAREN:
  718. if (lpStack->fRequiredOp)
  719. {
  720. if ((fRet = PushOperator(lpQueryInfo,
  721. lpStack->DefaultOpVal, dwOffset)) != S_OK)
  722. return fRet;
  723. lpStack->fRequiredOp = FALSE;
  724. }
  725. lpStack->cParentheses ++;
  726. lpStack->fRequiredTerm = TRUE;
  727. if ((fRet = PushOperator(lpQueryInfo, OpValue, dwOffset)) != S_OK)
  728. return fRet;
  729. break;
  730. case RIGHT_PAREN:
  731. if (lpStack->cParentheses == 0)
  732. {
  733. return (E_MISSLPAREN);
  734. }
  735. /* Check for term inside parentheses */
  736. if (lpStack->fRequiredTerm)
  737. return E_EXPECTEDTERM;
  738. lpStack->cParentheses --;
  739. while ((opVal = lpStack->Stack[lpStack->Top].OpVal) != LEFT_PAREN)
  740. {
  741. if ((fRet = QueryTreeAddToken(lpQueryTree, opVal,
  742. NULL, lpStack->Stack[lpStack->Top].dwOffset,
  743. 0)) != S_OK)
  744. return fRet;
  745. lpStack->Top--;
  746. }
  747. lpStack->Top --;
  748. lpStack->fRequiredOp = TRUE;
  749. break;
  750. default: /* Operator */
  751. /* Check for Unary operator */
  752. if (OperatorAttributeTable[OpValue] & UNARY_OP)
  753. {
  754. lpQueryTree->fFlag &= ~(ALL_OR | ALL_AND | ALL_ANDORNOT);
  755. /* Add the unary operator into the query tree */
  756. return QueryTreeAddToken(lpQueryTree, OpValue, p, dwOffset, 0);
  757. }
  758. if (OpValue != AND_OP)
  759. lpQueryTree->fFlag &= ~ ALL_AND;
  760. if (OpValue != OR_OP)
  761. lpQueryTree->fFlag &= ~ ALL_OR;
  762. if (OpValue != AND_OP && OpValue != OR_OP && OpValue != NOT_OP)
  763. lpQueryTree->fFlag &= ~ ALL_ANDORNOT;
  764. if (lpStack->fRequiredOp == FALSE)
  765. {
  766. return E_EXPECTEDTERM;
  767. }
  768. lpStack->fRequiredOp = FALSE;
  769. lpStack->fRequiredTerm = TRUE;
  770. if ((fRet = PushOperator(lpQueryInfo, OpValue,
  771. dwOffset)) != S_OK)
  772. return fRet;
  773. }
  774. return S_OK;
  775. }
  776. /*************************************************************************
  777. * @doc INTERNAL
  778. *
  779. * @func HRESULT PASCAL NEAR | StackFlush |
  780. * Flush all the operators on the stacks into the query expression
  781. *
  782. * @parm LPQI | lpQueryInfo |
  783. * Pointer to query info structure, which contains all "global"
  784. * variables
  785. *
  786. * @rdesc S_OK if succeeded, errors otherwise
  787. *************************************************************************/
  788. PRIVATE HRESULT PASCAL NEAR StackFlush (LPQI lpQueryInfo)
  789. {
  790. _LPSTACK lpStack = lpQueryInfo->lpStack;
  791. HRESULT fRet;
  792. /* Check for parentheses */
  793. if (lpStack->cParentheses)
  794. {
  795. return (E_MISSRPAREN);
  796. }
  797. /* Check for mismatch quotes */
  798. if (lpStack->cQuotes)
  799. {
  800. return (E_MISSQUOTE);
  801. }
  802. /* Check for missing term */
  803. if (lpStack->fRequiredTerm)
  804. return (E_EXPECTEDTERM);
  805. while (lpStack->Top >= 0)
  806. {
  807. if ((fRet = QueryTreeAddToken (lpQueryInfo->lpQueryTree,
  808. lpStack->Stack[lpStack->Top].OpVal, NULL,
  809. lpStack->Stack[lpStack->Top].dwOffset, 0)) != S_OK)
  810. return fRet;
  811. lpStack->Top --;
  812. }
  813. #if defined(_DEBUG) && DOS_ONLY
  814. PrintList(lpQueryInfo->lpQueryTree);
  815. #endif
  816. return (S_OK);
  817. }
  818. /*************************************************************************
  819. * @doc INTERNAL
  820. *
  821. * @func char NEAR | is_special_char |
  822. * Check to see if a character is the low level symbol of an
  823. * operator. If it is , then return its internal representation
  824. *
  825. * @parm BYTE | c |
  826. * Character to be checked
  827. *
  828. * @parm LPQI | lpQueryInfo |
  829. * Pointer to query info structure, where all "global variables"
  830. * reside
  831. *
  832. * @parm BYTE | fInPhrase |
  833. * Flag to determine that we are inside a phrase or not
  834. *
  835. * @rdesc Return the internal representation of the operator, or -1
  836. *************************************************************************/
  837. PRIVATE char NEAR is_special_char (BYTE c, LPQI lpQueryInfo, BYTE fInPhrase)
  838. {
  839. LPOPSYM lpOpSymTab;
  840. WORD cOpEntry;
  841. #if 0
  842. LPCMAP lpMapTab;
  843. lpMapTab = (LPCMAP)lpQueryInfo->lpCharTab->lpCMapTab;
  844. #endif
  845. lpOpSymTab = lpQueryInfo->lpOpSymTab;
  846. cOpEntry = lpQueryInfo->cOpEntry;
  847. if (!(fInPhrase & IN_PHRASE))
  848. {
  849. if (c == '(')
  850. return LEFT_PAREN;
  851. if (c == ')')
  852. return RIGHT_PAREN;
  853. #if 0
  854. // REVIEW (billa): I disabled this switch test because it doesn't
  855. // seem to be necessary. All the operators have values <= 14,
  856. // which should never conflict with the three char classes tested.
  857. /* Check for one-byte operator */
  858. switch ((lpMapTab+c)->Class)
  859. {
  860. case CLASS_CHAR:
  861. case CLASS_NORM:
  862. case CLASS_DIGIT:
  863. return -1;
  864. break;
  865. }
  866. #endif
  867. #if 0
  868. for (; cOpEntry > 0; cOpEntry--, lpOpSymTab++)
  869. {
  870. if (lpOpSymTab->OpName[0] == 1 &&
  871. lpOpSymTab->OpName[1] == c)
  872. {
  873. c = (BYTE)lpOpSymTab->OpVal;
  874. break;
  875. }
  876. }
  877. #endif
  878. switch (c)
  879. {
  880. case UO_AND_OP:
  881. return AND_OP;
  882. case UO_OR_OP:
  883. return OR_OP;
  884. case UO_NOT_OP:
  885. return NOT_OP;
  886. case UO_PHRASE_OP:
  887. return PHRASE_OP;
  888. case UO_NEAR_OP:
  889. return NEAR_OP;
  890. case UO_RANGE_OP:
  891. return RANGE_OP;
  892. case UO_GROUP_OP:
  893. return GROUP_OP;
  894. case UO_FIELD_OP:
  895. return FIELD_OP;
  896. case UO_FBRK_OP:
  897. return BRKR_OP;
  898. case STOP_OP:
  899. return STOP_OP;
  900. }
  901. }
  902. if (c == '\"')
  903. return QUOTE;
  904. return -1;
  905. }
  906. PRIVATE int PASCAL NEAR GetType(LPQI lpQueryInfo, char FAR *p)
  907. {
  908. LPOPSYM lpOpSymTab;
  909. WORD cOpEntry;
  910. cOpEntry = lpQueryInfo->cOpEntry;
  911. lpOpSymTab = lpQueryInfo->lpOpSymTab;
  912. for (; cOpEntry > 0; lpOpSymTab++, cOpEntry--)
  913. {
  914. if (!StringDiff2(p, lpOpSymTab->OpName))
  915. {
  916. switch (lpOpSymTab->OpVal)
  917. {
  918. case UO_AND_OP:
  919. return AND_OP;
  920. case UO_OR_OP:
  921. return OR_OP;
  922. case UO_NOT_OP:
  923. return NOT_OP;
  924. case UO_PHRASE_OP:
  925. return PHRASE_OP;
  926. case UO_NEAR_OP:
  927. return NEAR_OP;
  928. case UO_RANGE_OP:
  929. return RANGE_OP;
  930. case UO_GROUP_OP:
  931. return GROUP_OP;
  932. case UO_FBRK_OP:
  933. return BRKR_OP;
  934. case UO_FIELD_OP:
  935. return FIELD_OP;
  936. default:
  937. RET_ASSERT(UNREACHED);
  938. }
  939. }
  940. }
  941. return (TERM_TOKEN);
  942. }
  943. /*************************************************************************
  944. * @doc INTERNAL
  945. *
  946. * @func VOID PASCAL NEAR | LowLevelTransformation |
  947. * This function will transform a high level query to a low level
  948. * one. The change consists changing the operator's names to token
  949. *
  950. * @parm LPQI | lpQueryInfo |
  951. * The query info structure where all variables can be found
  952. *
  953. * @parm CB | cb |
  954. * How many bytes are there in the query
  955. *
  956. * @parm LPOPSYM | lpOpSymTab |
  957. * Operator symbol table
  958. *
  959. * @parm int | cOpEntry |
  960. * Number of operators' entry
  961. *
  962. * @parm LPSIPB | lpsipb |
  963. * Pointer to stoplist info block
  964. *************************************************************************/
  965. // erinfox: I added lpsipb here so we can transform stop words
  966. PRIVATE VOID PASCAL NEAR LowLevelTransformation (LPQI lpQueryInfo,
  967. register CB cb, LPOPSYM lpOpSymTab, int cOpEntry, PEXBRKPM pexbrkpm)
  968. {
  969. BYTE fPhrase = FALSE; // Are we in a PHRASE
  970. BYTE fNeedOp = FALSE; // Flag to denote an operator is needed
  971. BYTE fSkipArg = FALSE; // Flag to skip an argument
  972. LPB lpLastOp = NULL; // Location to insert the operator
  973. LPB lpLastWord = NULL; // Beginning of the last word
  974. LPB lpbQuery = lpQueryInfo->lpbQuery;
  975. int wLen = 0; // Last word's length
  976. BYTE WordToCheck[CB_MAX_WORD_LEN]; // Buffer containing word to check as stopword
  977. for (; cb > 1; cb--, lpbQuery++)
  978. {
  979. switch (*lpbQuery)
  980. {
  981. case 0:
  982. return;
  983. case '"': // Check for phrase
  984. /* Set (or unset) the phrase flag */
  985. if ((fPhrase = (BYTE)!fPhrase) == FALSE)
  986. {
  987. /* We just got out of a phrase, don't convert the
  988. * last word in the phrase, which may be an operator
  989. * format, by resetting the word length to 0, and the
  990. * pointer to NULL
  991. */
  992. wLen = 0;
  993. lpLastWord = NULL;
  994. }
  995. /* Fall through */
  996. case ' ':
  997. case '\t':
  998. case NEWLINE:
  999. case CRETURN:
  1000. break;
  1001. default: /* Regular character */
  1002. /* Check for special characters */
  1003. if (is_special_char(*lpbQuery, lpQueryInfo,
  1004. (BYTE)(lpQueryInfo->fFlag & IN_PHRASE)) != -1)
  1005. {
  1006. break;
  1007. }
  1008. #if 0
  1009. /* Check for single character operator */
  1010. if (fPhrase == FALSE)
  1011. {
  1012. /* Scan for operator, and change it to symbol
  1013. if only we are not in a phrase */
  1014. if (ChangeToOpSym(lpOpSymTab, cOpEntry,
  1015. lpbQuery, 1) == TRUE)
  1016. break;
  1017. }
  1018. #endif
  1019. if (lpLastWord == NULL)
  1020. lpLastWord = lpbQuery;
  1021. wLen++;
  1022. /* This continue is important. This is the only way
  1023. * to accumulate a word */
  1024. continue;
  1025. } /* End switch */
  1026. if (wLen == 0)
  1027. continue;
  1028. if (fSkipArg == TRUE)
  1029. {
  1030. /* Reset the flag, and skip this argument */
  1031. fSkipArg = FALSE;
  1032. lpLastWord = NULL;
  1033. wLen = 0;
  1034. continue;
  1035. }
  1036. if (fPhrase == FALSE)
  1037. {
  1038. /* Scan for operator, and change it to symbol
  1039. if only we are not in a phrase */
  1040. if (ChangeToOpSym(lpOpSymTab, cOpEntry, lpLastWord,
  1041. wLen) == TRUE)
  1042. {
  1043. /* We got an operator, but don't change the
  1044. value of fNeedOp for FBRK or FIELD, since
  1045. they have no effect on the boolean grammar
  1046. */
  1047. if (*lpLastWord == UO_FBRK_OP ||
  1048. *lpLastWord == UO_FIELD_OP)
  1049. {
  1050. fSkipArg = TRUE;
  1051. }
  1052. }
  1053. // Check to see if it's a stopword
  1054. else if (pexbrkpm)
  1055. {
  1056. MEMCPY(WordToCheck+2, lpLastWord, wLen);
  1057. *(LPW) WordToCheck = (WORD) wLen;
  1058. // yes, so replace it with STOP_OP
  1059. if (S_OK == ExtLookupStopWord(pexbrkpm->lpvIndexObjBridge,
  1060. WordToCheck))
  1061. {
  1062. *lpLastWord++ = (BYTE) STOP_OP;
  1063. for (--wLen; wLen > 0; wLen--)
  1064. *lpLastWord++ = ' ';
  1065. }
  1066. }
  1067. }
  1068. /* Reset pointer */
  1069. lpLastWord = NULL;
  1070. wLen = 0;
  1071. }
  1072. }
  1073. /*************************************************************************
  1074. * @doc INTERNAL
  1075. *
  1076. * @func BOOL PASCAL NEAR | ChangeToOpSym |
  1077. * This function will look for operators, then rewrite them into
  1078. * their abbreviated form
  1079. *
  1080. * @parm LPOPSYM | lpOpSymTab |
  1081. * Pointer to operator table
  1082. *
  1083. * @parm int | cOpEntry |
  1084. * Number of operators' entries
  1085. *
  1086. * @parm LPB | pWord |
  1087. * Word to be checked and modified
  1088. *
  1089. * @parm int | wLen |
  1090. * Length of the word
  1091. *
  1092. * @rdesc
  1093. * TRUE if the word is an operator, and is rewritten, FALSE otherwise
  1094. *************************************************************************/
  1095. PRIVATE BOOL PASCAL NEAR ChangeToOpSym (LPOPSYM pOptable, int cOpEntry,
  1096. LPB pWord, int wLen)
  1097. {
  1098. for (; cOpEntry > 0; pOptable++, cOpEntry--)
  1099. {
  1100. if ((WORD)wLen == pOptable->OpName[0])
  1101. {
  1102. if (StrNoCaseCmp (pWord, pOptable->OpName + 1, (WORD)wLen) == 0)
  1103. {
  1104. /* Match! Change to operator symbol and pad with blank */
  1105. *pWord++ = (BYTE)pOptable->OpVal;
  1106. for (--wLen; wLen > 0; wLen--)
  1107. *pWord++ = ' ';
  1108. return TRUE;
  1109. }
  1110. }
  1111. }
  1112. return FALSE;
  1113. }
  1114. /*************************************************************************
  1115. * @doc INTERNAL
  1116. *
  1117. * @func LSZ PASCAL NEAR | StringToLong |
  1118. * The function reads in a string of digits and convert them into
  1119. * a DWORD. The function will move the input pointer correspondingly
  1120. *
  1121. * @parm LSZ | lszBuf |
  1122. * Input buffer containing the string of digit with no sign
  1123. *
  1124. * @parm LPDW | lpValue |
  1125. * Pointer to a DWORD that receives the result
  1126. *
  1127. * @rdesc NULL, if there is no digit, else the new position of the input
  1128. * buffer pointer
  1129. *************************************************************************/
  1130. static LSZ PASCAL NEAR StringToLong (LSZ lszBuf, LPDW lpValue)
  1131. {
  1132. register DWORD Result; // Returned result
  1133. register int i; // Scratch variable
  1134. char fGetDigit; // Flag to mark we do get a digit
  1135. /* Skip all blanks, tabs */
  1136. while (*lszBuf == ' ' || *lszBuf == '\t')
  1137. lszBuf++;
  1138. Result = fGetDigit = 0;
  1139. if (*lszBuf >= '0' && *lszBuf <= '9')
  1140. {
  1141. fGetDigit = TRUE;
  1142. /* The credit of this piece of code goes to Leon */
  1143. while (i = *lszBuf - '0', i >= 0 && i <= 9)
  1144. {
  1145. Result = Result * 10 + i;
  1146. lszBuf++;
  1147. }
  1148. }
  1149. *lpValue = Result;
  1150. return (fGetDigit ? lszBuf : NULL);
  1151. }
  1152. int PASCAL FAR QueryTreeCopyBufPointer (_LPQT lpSrcQT, _LPQT lpDestQT)
  1153. {
  1154. if (lpSrcQT == NULL || lpDestQT == NULL)
  1155. return(E_INVALIDARG);
  1156. lpDestQT->lpTopicStartSearch = NULL;
  1157. lpDestQT->lpTopicFreeList = lpSrcQT->lpTopicFreeList;
  1158. /* Occ list related global variables */
  1159. lpDestQT->lpOccStartSearch = NULL; /* Starting occurrence for searching */
  1160. lpDestQT->lpOccFreeList = lpSrcQT->lpOccFreeList;
  1161. return(S_OK);
  1162. }
  1163. LPQT QueryTreeDuplicate (_LPQT lpSrcQT, PHRESULT phr)
  1164. {
  1165. _LPQT lpDupQT;
  1166. HRESULT fRet;
  1167. HANDLE hndSaved;
  1168. // Allocate the new query tree
  1169. if ((lpDupQT = (_LPQT)GLOBALLOCKEDSTRUCTMEMALLOC(sizeof(QTREE))) == NULL)
  1170. {
  1171. SetErrCode(phr, E_OUTOFMEMORY);
  1172. exit0:
  1173. return lpDupQT;
  1174. }
  1175. hndSaved = *(HANDLE FAR *)lpDupQT;
  1176. // Copy all the info from the old QT
  1177. *lpDupQT = *lpSrcQT;
  1178. *(HANDLE FAR *)lpDupQT = hndSaved;
  1179. lpDupQT->lpTopNode = NULL;
  1180. // Copy the tree
  1181. if ((fRet = CopyNode (lpSrcQT, &lpDupQT->lpTopNode,
  1182. lpSrcQT->lpTopNode)) != S_OK)
  1183. {
  1184. SetErrCode (phr, fRet);
  1185. GlobalLockedStructMemFree((LPV)lpDupQT);
  1186. lpDupQT = NULL;
  1187. }
  1188. goto exit0;
  1189. }
  1190. PRIVATE HRESULT PASCAL NEAR CopyNode (_LPQT lpQt, _LPQTNODE FAR *plpQtNode,
  1191. _LPQTNODE lpSrcNode)
  1192. {
  1193. int fRet;
  1194. _LPQTNODE lpQtNode;
  1195. if (lpSrcNode == NULL)
  1196. {
  1197. *plpQtNode = NULL;
  1198. return(S_OK);
  1199. }
  1200. /* Allocate the node */
  1201. if ((lpQtNode = BlockGetElement(lpQt->lpNodeBlock)) == NULL)
  1202. return E_OUTOFMEMORY;
  1203. // Copy the node
  1204. *lpQtNode = *lpSrcNode;
  1205. *plpQtNode = lpQtNode;
  1206. // Copy the left child
  1207. if ((fRet = CopyNode (lpQt, &QTN_LEFT(lpQtNode), QTN_LEFT(lpSrcNode))) !=
  1208. S_OK)
  1209. return(fRet);
  1210. return CopyNode (lpQt, &QTN_RIGHT(lpQtNode), QTN_RIGHT(lpSrcNode));
  1211. }
  1212. VOID PASCAL FAR DuplicateQueryTreeFree(_LPQT lpQT)
  1213. {
  1214. if (lpQT)
  1215. GlobalLockedStructMemFree((LPV)lpQT);
  1216. }
  1217. __inline BOOL IsCharWhitespace (BYTE cVal)
  1218. {
  1219. return
  1220. (' ' == cVal || '\t' == cVal || NEWLINE == cVal || CRETURN == cVal);
  1221. }