Windows NT 4.0 source code leak
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.

730 lines
19 KiB

4 years ago
  1. /*****************************************************************************
  2. * *
  3. * CTX.C *
  4. * *
  5. * Copyright (C) Microsoft Corporation 1990. *
  6. * All Rights reserved. *
  7. * *
  8. ******************************************************************************
  9. * *
  10. * Module Intent *
  11. * This module handles context strings, including footnote processing *
  12. * and cross reference error checking. *
  13. *
  14. *****************************************************************************/
  15. #include "stdafx.h"
  16. #include "..\common\coutput.h"
  17. #ifdef _DEBUG
  18. #undef THIS_FILE
  19. static char THIS_FILE[] = __FILE__;
  20. #endif
  21. const char CTX_DEFINED = '0';
  22. const char CTX_REFERENCED = '1';
  23. const char CTX_SEPARATOR = '@';
  24. const char txtSameCtx[] = ">";
  25. extern COutput* pLogFile;
  26. INLINE static BOOL STDCALL IsCtxPrefix(PCSTR pszContext);
  27. /*
  28. * Module Algorithms
  29. *
  30. * This module has three entry points: one when context strings
  31. * are defined, one when they are referenced, and then one for error
  32. * checking all the links at the end. When context strings are
  33. * defined, the hash value and address pair is added to the context
  34. * btree. When context strings are defined or referenced, the
  35. * hash value, string, filename, and page number are all added to
  36. * a file, as well as whether it is a definition or a reference. When
  37. * error checking occurs, this file is sorted, and then entries are
  38. * read in. An error is reported whenever a context string is
  39. * referenced without being defined.
  40. *
  41. * Revision History
  42. * This functionality is completely different from what this
  43. * source file used to do.
  44. *
  45. */
  46. /*****************************************************************************
  47. * *
  48. * Defines *
  49. * *
  50. *****************************************************************************/
  51. static char const txtCTXBtree[] = "|CONTEXT";
  52. /*****************************************************************************
  53. * *
  54. * Prototypes *
  55. * *
  56. *****************************************************************************/
  57. BOOL STDCALL FCreateContextFiles(void)
  58. {
  59. BTREE_PARAMS bp;
  60. // Create hash btree file
  61. InitBtreeStruct(&bp, "L4", CB_CTX_BLOCK); // KT_LONG
  62. fmsg.qbthrCtx = HbtCreateBtreeSz(txtCTXBtree, &bp);
  63. // Create context info file. This may already have been done.
  64. if (!ptblCtx)
  65. ptblCtx = new CTable;
  66. return TRUE;
  67. }
  68. void STDCALL CloseContextBtree(void)
  69. {
  70. if (fmsg.qbthrCtx != NULL) {
  71. if (RcCloseBtreeHbt(fmsg.qbthrCtx) != RC_Success) {
  72. fmsg.qbthrCtx = NULL;
  73. }
  74. else
  75. fmsg.qbthrCtx = NULL;
  76. }
  77. }
  78. /***************************************************************************
  79. *
  80. - Name FProcContextSz
  81. -
  82. * Purpose
  83. * This function processes a context footnote. If it is given a
  84. * valid context string, it puts the address into the context btree,
  85. * indexed by a hash of the context string. It also enters a
  86. * definition entry into the context string cross reference file.
  87. *
  88. * Arguments
  89. * PSTR szContext: String found in context footnote
  90. * ADDR addr: Address for context string
  91. * PERR perr: Pointer to error information.
  92. *
  93. * Returns
  94. * TRUE if context string was valid.
  95. *
  96. * +++
  97. *
  98. * Notes
  99. * Currently uses global fmsg.
  100. *
  101. ***************************************************************************/
  102. // BUGBUG: nobody uses this
  103. // HASH hPrev; // REVIEW: who uses this?
  104. BOOL STDCALL FProcContextSz(PSTR pszContext, IDFCP idfcp, UINT wObjrg,
  105. PERR perr)
  106. {
  107. HASH hash;
  108. PSTR szMaster;
  109. SzTrimSz(pszContext);
  110. if (*pszContext == '\0') {
  111. VReportError(HCERR_MISSING_CTX, &errHpj);
  112. return FALSE;
  113. }
  114. else if (!FValidContextSz(pszContext)) {
  115. VReportError(HCERR_INVALID_CTX, &errHpj, pszContext);
  116. return FALSE;
  117. }
  118. hash = HashFromSz(pszContext);
  119. // Remap if the hash value is in the [ALIAS] section. Note that
  120. // apparently, viewer 2.0 doesn't do this anymore. 14-Oct-1993 [ralphw]
  121. szMaster = SzTranslateHash(&hash);
  122. // Add hash value into btree
  123. ASSERT(fmsg.qbthrCtx != NULL);
  124. FDelayExecutionContext(hash, idfcp, wObjrg);
  125. // Add context info to context info file
  126. FRecordContext(hash, pszContext, szMaster, TRUE, perr);
  127. curHash = hash; // save for possible use in FTS processing
  128. fContextSeen = TRUE;
  129. doGrind(); // update grinder bitmap
  130. return TRUE;
  131. }
  132. /***************************************************************************
  133. *
  134. - Name: FRecordContext
  135. -
  136. * Purpose:
  137. * Records a context string definition or reference, so that we
  138. * may later check to see if it was undefined.
  139. *
  140. * Strings are written out as:
  141. * hash szMaster fDefine pszContext pchFile iTopic
  142. * If szMaster is nil, pszContext is used instead.
  143. *
  144. * Arguments:
  145. * HASH hash: Translated hash value of context string.
  146. * PSTR pszContext: Context string as it appears in the text.
  147. * PSTR szMaster: Context string corresponding to given hash value,
  148. * if different from pszContext.
  149. * BOOL fDefine: TRUE for definition, FALSE for reference.
  150. * PERR perr: Pointer to error information.
  151. *
  152. * Returns:
  153. *
  154. * Globals Used:
  155. * Writes out info to fmsg.
  156. *
  157. ***************************************************************************/
  158. BOOL STDCALL FRecordContext(HASH hash, PCSTR pszContext, PSTR pszMaster,
  159. BOOL fDefine, PERR perr)
  160. {
  161. if (!ptblCtx)
  162. ptblCtx = new CTable;
  163. ASSERT(pszContext != NULL && pszContext[0] != '\0');
  164. if (pszMaster == NULL)
  165. pszMaster = (PSTR) pszContext;
  166. ASSERT(pszMaster[0] != '\0');
  167. ASSERT(perr->lpszFile != NULL && perr->lpszFile[0] != '\0');
  168. char szBuf[1024];
  169. ASSERT(strlen(pszMaster) < MAX_PATH);
  170. // The CTX_SEPARATOR value must be used to separate strings. You can't
  171. // use space because that would prevent using space for a Topic ID.
  172. int iFile = ptblRtfFiles->IsStringInTable(perr->lpszFile);
  173. if (iFile)
  174. wsprintf(szBuf, "%8lX@%s@%c@%s@%u@%u",
  175. hash, pszMaster, (char) (fDefine ? CTX_DEFINED : CTX_REFERENCED),
  176. strcmp(pszMaster, pszContext) == 0 ? txtSameCtx : pszContext,
  177. iFile, perr->iTopic);
  178. else
  179. wsprintf(szBuf, "%8lX@%s@%c@%s@%s@%u",
  180. hash, pszMaster, (char) (fDefine ? CTX_DEFINED : CTX_REFERENCED),
  181. strcmp(pszMaster, pszContext) == 0 ? txtSameCtx : pszContext,
  182. perr->lpszFile, perr->iTopic);
  183. ASSERT(strchr(szBuf, CTX_SEPARATOR)); // Make sure you used CTX_SEPARATOR!
  184. if (!ptblCtx->AddString(hash, szBuf))
  185. OOM();
  186. if (fDefine)
  187. hlpStats.cTopics++;
  188. else
  189. hlpStats.cJumps++;
  190. return TRUE;
  191. }
  192. /***************************************************************************
  193. *
  194. - Name: FResolveContextErrors
  195. -
  196. * Purpose:
  197. * This function goes through the list of context string definitions
  198. * and references, looking for multiple definitions, hash value conflicts,
  199. * and unresolved references.
  200. *
  201. * Arguments:
  202. *
  203. * Returns:
  204. *
  205. * Globals Used:
  206. *
  207. * +++
  208. *
  209. * Notes:
  210. *
  211. ***************************************************************************/
  212. const char txtSpaceEol[] = " \n";
  213. BOOL STDCALL FResolveContextErrors(void)
  214. {
  215. PSTR pszMasterLast;
  216. PSTR pszHash, pszMaster, pszReference, pszContext, pszFile, pszTopic;
  217. CTable* ptblErrors = NULL;
  218. int pos;
  219. ASSERT(ptblCtx != NULL);
  220. if (!iflags.fTrusted) {
  221. CMem mem(CB_SCRATCH); // create a scratch buffer
  222. CMem master(MAX_FOOTNOTE);
  223. ptblCtx->SetTableSortColumn(sizeof(HASH) + 1);
  224. ptblCtx->SortTablei();
  225. ptblCtx->RemoveDuplicateHashStrings();
  226. #if 0 // Debugging code
  227. {
  228. COutput out("ctx.log");
  229. int i = 1;
  230. while (i < ptblCtx->CountStrings())
  231. out.outstring_eol(ptblCtx->GetPointer(i++) + sizeof(HASH) + 1);
  232. ptblCtx->SetPosition();
  233. }
  234. #endif
  235. pszMasterLast = master.psz;
  236. *pszMasterLast = '\0';
  237. /*
  238. * Note that we don't check for overflow of pszHash. This shouldn't
  239. * happen. If it does, it will corrupt the heap which will be caught
  240. * when the function terminates.
  241. */
  242. // REVIEW: we now save the has number in the table...
  243. HASH hash, hashLast = 0;
  244. while(ptblCtx->GetHashAndString(&hash, mem.psz)) {
  245. /*
  246. * REVIEW: We still save the hash string in order to sort the
  247. * hash strings. To avoid this we'd have to have a special sort
  248. * table that sorted both the DWORD hash value and the ensuing
  249. * string.
  250. */
  251. pszHash = StrToken(mem.psz, CTX_SEPARATOR);
  252. pszMaster = StrToken(NULL, CTX_SEPARATOR);
  253. pszReference = StrToken(NULL, CTX_SEPARATOR);
  254. pszContext = StrToken(NULL, CTX_SEPARATOR);
  255. if (*pszContext == txtSameCtx[0])
  256. pszContext = pszMaster;
  257. pszFile = StrToken(NULL, CTX_SEPARATOR);
  258. pszTopic = StrToken(NULL, CTX_SEPARATOR);
  259. ASSERT(pszTopic != NULL);
  260. errHpj.lpszFile = isdigit(*pszFile) ?
  261. ptblRtfFiles->GetPointer(atoi(pszFile)) : pszFile;
  262. errHpj.iTopic = atoi(pszTopic);
  263. if (hash == hashLast) {
  264. // Case insensitive comparison of aliased context strings:
  265. CStr cszMaster(pszMaster);
  266. CStr cszLast(pszMasterLast);
  267. StrUpper(cszMaster);
  268. StrUpper(cszLast);
  269. if (strcmp(cszMaster, cszLast) != 0) {
  270. VReportError(HCERR_HASH_CONFLICT, &errHpj,
  271. pszMaster, pszMasterLast);
  272. if (*pszReference == CTX_REFERENCED)
  273. VReportError(HCERR_BAD_JUMP, &errHpj, pszContext);
  274. }
  275. else if (*pszReference == CTX_DEFINED) {
  276. UINT curpos = ptblCtx->GetPosition();
  277. pos = curpos - 2;
  278. CMem memTmp(CB_SCRATCH);
  279. // Find and whine about every duplicate
  280. while (pos > 0) {
  281. HASH hashTmp;
  282. ptblCtx->GetHashAndString(&hashTmp, memTmp.psz, pos--);
  283. PSTR pszTmpHash = StrToken(memTmp.psz, CTX_SEPARATOR);
  284. // We're done when we hit a different hash number
  285. if (hash != hashTmp)
  286. break;
  287. PSTR pszTmpMaster = StrToken(NULL, CTX_SEPARATOR);
  288. PSTR pszTmpReference = StrToken(NULL, CTX_SEPARATOR);
  289. // Only complain if its a definition, not if its a jump
  290. if (*pszTmpReference == CTX_DEFINED &&
  291. hash == hashTmp &&
  292. strcmp(pszMaster, pszTmpMaster) == 0) {
  293. PSTR pszTmpContext = StrToken(NULL, CTX_SEPARATOR);
  294. PSTR pszTmpFile = StrToken(NULL, CTX_SEPARATOR);
  295. PSTR pszTmpTopic = StrToken(NULL, CTX_SEPARATOR);
  296. VReportError(HCERR_DUPLICATE_CTX, &errHpj, pszContext,
  297. atoi(pszTmpTopic), pszTmpFile);
  298. break;
  299. }
  300. }
  301. ptblCtx->SetPosition(curpos);
  302. }
  303. }
  304. else {
  305. if (*pszReference == CTX_REFERENCED)
  306. VReportError(HCERR_BAD_JUMP, &errHpj, pszContext);
  307. else if (ptblMap && *pszContext == 'I' &&
  308. *pszReference == CTX_DEFINED &&
  309. strcmp(pszContext, pszMaster) == 0 &&
  310. IsCtxPrefix(pszContext)) {
  311. if (!ptblMap->IsHashInTable(HashFromSz(pszContext))) {
  312. int ialias;
  313. QALIAS qalias;
  314. HASH hashMap = HashFromSz(pszContext);
  315. if (pdrgAlias && pdrgAlias->Count() > 0) {
  316. for (ialias = 0, qalias = (QALIAS) pdrgAlias->GetBasePtr();
  317. ialias < pdrgAlias->Count();
  318. ialias++, qalias++) {
  319. // Look up address for alias in context btree
  320. if (qalias->hashCtx == hashMap)
  321. goto AliasedCtx;
  322. }
  323. if (ialias < pdrgAlias->Count())
  324. continue; // aliased, so not an error
  325. }
  326. if (!ptblErrors)
  327. ptblErrors = new CTable();
  328. wsprintf(szParentString, "\t%s\tTopic %s of %s\r\n",
  329. pszContext, pszTopic, ptblRtfFiles->GetPointer(atoi(pszFile)));
  330. ptblErrors->AddString(szParentString);
  331. }
  332. }
  333. }
  334. AliasedCtx:
  335. hashLast = hash;
  336. strcpy(pszMasterLast, pszMaster);
  337. }
  338. }
  339. delete ptblCtx;
  340. ptblCtx = NULL;
  341. if (ptblErrors) {
  342. errHpj.ep = epNoFile;
  343. VReportError(HCERR_NOT_IN_MAP, &errHpj);
  344. ptblErrors->SortTable();
  345. // Remove warning count from VReportError() and add real count
  346. errcount.cWarnings--;
  347. errcount.cWarnings += ptblErrors->CountStrings();
  348. for (pos = 1; pos <= ptblErrors->CountStrings(); pos++) {
  349. ptblErrors->GetString(szParentString, pos);
  350. SendStringToParent(szParentString);
  351. if (pLogFile)
  352. pLogFile->outstring(szParentString);
  353. if (pos % 10 == 0)
  354. doGrind();
  355. }
  356. delete ptblErrors;
  357. }
  358. return TRUE;
  359. }
  360. /***************************************************************************
  361. *
  362. - Name AddrGetContents
  363. -
  364. * Purpose
  365. * Gets the address of the contents topic.
  366. *
  367. * Arguments
  368. * HASH hash: Hash value of index topic.
  369. *
  370. * Returns
  371. * Address of contents topic.
  372. *
  373. * Globals Used:
  374. * This function uses the fmsg.qbthrCtx global to look up the
  375. * contents topic.
  376. *
  377. ***************************************************************************/
  378. ADDR STDCALL AddrGetContents(PSTR pszContents)
  379. {
  380. ADDR addr = 0; // Actually, default is sizeof(MBHD)
  381. // Get address of contents
  382. if (pszContents) {
  383. ASSERT(fmsg.qbthrCtx != NULL);
  384. HASH hash = HashFromSz(pszContents);
  385. RC_TYPE rc = RcLookupByKey(fmsg.qbthrCtx,
  386. (KEY) &hash, NULL, &addr);
  387. if (rc == RC_NoExists) {
  388. VReportError(HCERR_CONTENTS_CTX_MISSING, &errHpj,
  389. pszContents);
  390. addr = 0;
  391. }
  392. #ifdef _DEBUG
  393. else
  394. ASSERT(rc == RC_Success); // REVIEW: Is this true?
  395. #endif
  396. }
  397. return addr;
  398. }
  399. /***************************************************************************
  400. *
  401. - Name: FOutAliasToCtxBtree
  402. -
  403. * Purpose:
  404. * This function takes each entry in the alias table, looks up the
  405. * address of the aliased topic, and enters this into the context
  406. * btree.
  407. *
  408. * Returns:
  409. * TRUE if successful, FALSE otherwise.
  410. *
  411. * Globals:
  412. * This function reads and writes values to fmsg.qbthrCtx.
  413. *
  414. ***************************************************************************/
  415. BOOL STDCALL FOutAliasToCtxBtree(void)
  416. {
  417. RC_TYPE rc;
  418. ADDR addr;
  419. int ialias;
  420. QALIAS qalias;
  421. ASSERT(fmsg.qbthrCtx != NULL);
  422. if (pdrgAlias && pdrgAlias->Count() > 0) {
  423. for (ialias = 0, qalias = (QALIAS) pdrgAlias->GetBasePtr();
  424. ialias < pdrgAlias->Count();
  425. ialias++, qalias++) {
  426. // Look up address for alias in context btree
  427. rc = RcLookupByKey(fmsg.qbthrCtx, (KEY) &qalias->hashCtx,
  428. NULL, &addr);
  429. switch (rc) {
  430. case RC_Success:
  431. // Put context string and address of alias into context btree
  432. // REVIEW: Error check?
  433. ASSERT(fmsg.qbthrCtx != NULL);
  434. RcInsertHbt(fmsg.qbthrCtx, (KEY) &qalias->hashAlias, &addr);
  435. break;
  436. case RC_NoExists:
  437. // Context string was not defined -- not an error here?
  438. break;
  439. default:
  440. // REVIEW: Error message?
  441. ASSERT(FALSE);
  442. }
  443. }
  444. }
  445. return TRUE;
  446. }
  447. /*-----------------------------------------------------------------------------
  448. * VOID VOutCtxOffsetTable()
  449. *
  450. * Description:
  451. * This function outputs the Context-Offset table into the FS. It goes
  452. * through every context string defined in the map section of the project
  453. * file and finds the offset looking into the Topic-Offset Table. It
  454. * outputs the offset against the hashed context string.
  455. *
  456. * Table layout:
  457. * integer Count of Context strings present
  458. * Context ID -- Address
  459. * .....................
  460. * .....................
  461. * Context ID -- Address
  462. *
  463. * Returns;
  464. * NOTHING
  465. *-----------------------------------------------------------------------------*/
  466. const char txtCTXOMAP[] = "|CTXOMAP"; // context map file name
  467. void STDCALL VOutCtxOffsetTable(void)
  468. {
  469. QMAP qmap;
  470. ADDR addr;
  471. RC_TYPE rc;
  472. BOOL fNagged = FALSE;
  473. int count;
  474. #ifdef _DEBUG
  475. int actual = 0;
  476. int cActualMap;
  477. #endif
  478. // create the context map file
  479. fmsg.hfCtxOMap = HfCreateFileHfs(hfsOut, txtCTXOMAP, 0);
  480. // Write out size of map table.
  481. int cmap = pdrgMap ? pdrgMap->Count() : 0;
  482. int imap;
  483. /*
  484. * If we're compressing, then we run the map entries first to get a
  485. * count of valid entries. It sometimes happens that people will drop
  486. * in .H files that contain not only map entries but a bunch of other
  487. * #defines for their code -- such as dropping in ApStudio's resource.h
  488. * file. Rather then put all of the non-valid entries into the help file,
  489. * we make a pass here to calculate the number of real entries. Then when
  490. * we're in the for() loop that writes the entry into the help file, we
  491. * simply ignore non-valid entries.
  492. */
  493. if (cmap && (options.fsCompress &
  494. (COMPRESS_TEXT_PHRASE | COMPRESS_TEXT_HALL | COMPRESS_TEXT_ZECK))) {
  495. int cRealMap = cmap;
  496. #ifdef _DEBUG
  497. cActualMap = cmap;
  498. #endif
  499. for (imap = 0, qmap = (QMAP) pdrgMap->GetBasePtr();
  500. imap < cmap;
  501. imap++, qmap++) {
  502. // Get address of given hash value
  503. rc = RcLookupByKey(fmsg.qbthrCtx, (KEY) &qmap->hash, NULL,
  504. (LPVOID) &addr);
  505. if (rc == RC_NoExists)
  506. cRealMap--;
  507. }
  508. LcbWriteIntAsShort(fmsg.hfCtxOMap, cRealMap);
  509. }
  510. else {
  511. LcbWriteIntAsShort(fmsg.hfCtxOMap, cmap);
  512. #ifdef _DEBUG
  513. cActualMap = cmap;
  514. #endif
  515. }
  516. ASSERT(fmsg.qbthrCtx != NULL);
  517. if (cmap > 0) {
  518. ASSERT(ptblMap);
  519. for (imap = 0, qmap = (QMAP) pdrgMap->GetBasePtr();
  520. imap < cmap;
  521. imap++, qmap++) {
  522. // Get address of given hash value
  523. rc = RcLookupByKey(fmsg.qbthrCtx, (KEY) &qmap->hash, NULL, (LPVOID) &addr);
  524. if (rc == RC_NoExists) {
  525. int ialias;
  526. QALIAS qalias;
  527. if (pdrgAlias && pdrgAlias->Count() > 0) {
  528. for (ialias = 0, qalias = (QALIAS) pdrgAlias->GetBasePtr();
  529. ialias < pdrgAlias->Count();
  530. ialias++, qalias++) {
  531. // Look up address for alias in context btree
  532. if (qalias->hashAlias == qmap->hash)
  533. break;
  534. }
  535. if (ialias < pdrgAlias->Count()) {
  536. if (options.fsCompress &
  537. (COMPRESS_TEXT_PHRASE | COMPRESS_TEXT_HALL |
  538. COMPRESS_TEXT_ZECK))
  539. continue; // aliased, so not an error
  540. else
  541. goto BadCtx; // non-compressed, have to add it anyway
  542. }
  543. }
  544. if (!fNagged) {
  545. errHpj.ep = epNoFile;
  546. VReportError(HCERR_MAP_UNUSED, &errHpj);
  547. fNagged = TRUE;
  548. count = 0;
  549. }
  550. ASSERT(HCERR_MAP_UNUSED < HCERR_WARNINGS);
  551. if (!options.fSupressNotes) {
  552. strcpy(szParentString, "\t");
  553. strcat(szParentString, ptblMap->GetPointer(qmap->pos) +
  554. sizeof(HASH));
  555. strcat(szParentString, txtEol);
  556. SendStringToParent(szParentString);
  557. if (pLogFile)
  558. pLogFile->outstring(szParentString);
  559. if (++count == 10) {
  560. count = 0;
  561. doGrind();
  562. }
  563. }
  564. BadCtx:
  565. addr = addrNil;
  566. /*
  567. * When we are compressing the help file, we already reduced
  568. * the total map count to the number of entries actually
  569. * used, so we can just ignore this not-found entry.
  570. */
  571. if (options.fsCompress &
  572. (COMPRESS_TEXT_PHRASE | COMPRESS_TEXT_HALL |
  573. COMPRESS_TEXT_ZECK))
  574. continue;
  575. }
  576. // Write out CTX
  577. LcbWriteHf(fmsg.hfCtxOMap, &qmap->ctx, sizeof(CTX));
  578. // Write out address
  579. LcbWriteHf(fmsg.hfCtxOMap, &addr, sizeof(ADDR));
  580. #ifdef _DEBUG
  581. actual += sizeof(CTX) + sizeof(ADDR);
  582. #endif
  583. }
  584. }
  585. #ifdef _DEBUG
  586. {
  587. int expected = cActualMap * (sizeof(CTX) + sizeof(ADDR));
  588. ASSERT(expected == actual);
  589. }
  590. #endif
  591. }
  592. INLINE static BOOL STDCALL IsCtxPrefix(PCSTR pszContext)
  593. {
  594. // BUGBUG: strnicmp won't be correct for DBCS
  595. if (ptblCtxPrefixes) {
  596. for (int pos = 1; pos <= ptblCtxPrefixes->CountStrings(); pos++) {
  597. if (strnicmp(pszContext, ptblCtxPrefixes->GetPointer(pos),
  598. strlen(ptblCtxPrefixes->GetPointer(pos))) == 0)
  599. return TRUE;
  600. }
  601. }
  602. else if (strnicmp(pszContext, "IDH_", 4) == 0)
  603. return TRUE;
  604. return FALSE;
  605. }