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.

686 lines
18 KiB

  1. // Copyright (C) 1997 Microsoft Corporation. All rights reserved.
  2. #include "header.h"
  3. #ifndef HHCTRL
  4. #include "hha_strtable.h"
  5. #endif
  6. #ifdef HHCTRL
  7. #include "strtable.h"
  8. #include "secwin.h"
  9. #endif
  10. #include "system.h"
  11. #include "csubset.h"
  12. #include "ctable.h"
  13. const char g_szUDSKey[] = "ssv2\\UDS"; // User Defined Subsets
  14. // Shared init.
  15. //
  16. void CSubSets::_CSubSets(void)
  17. {
  18. m_max_subsets = MAX_SUBSETS;
  19. m_aSubSets = (CSubSet**)lcCalloc( m_max_subsets * sizeof( CSubSet* ) );
  20. m_cur_Set = NULL;
  21. m_Toc = NULL;
  22. m_Index = NULL;
  23. m_FTS = NULL;
  24. m_cSets = 0;
  25. m_pszFilename = NULL;
  26. }
  27. #ifdef HHCTRL
  28. CSubSets::CSubSets(int ITSize, CHmData* const phmData, BOOL fPredefined=FALSE ) : CSubSet(ITSize)
  29. {
  30. _CSubSets();
  31. m_fPredefined = fPredefined;
  32. m_bPredefined = fPredefined;
  33. m_pIT = new CInfoType();
  34. m_pIT->CopyTo(phmData);
  35. }
  36. #endif
  37. CSubSets::CSubSets( int ITSize = 4, CInfoType *pIT=NULL, BOOL fPredefined=FALSE ) : CSubSet( ITSize)
  38. {
  39. _CSubSets();
  40. m_fPredefined = fPredefined;
  41. m_bPredefined = fPredefined;
  42. m_pIT = new CInfoType();
  43. if ( pIT != NULL )
  44. *m_pIT = *pIT; // BUGBUG: This will fault if you ever use the defult for the parameter.
  45. }
  46. CSubSets::CSubSets( int ITSize, CTable *ptblSubSets, CSiteMap *pSiteMap, BOOL fPredefined=FALSE ) : CSubSet(ITSize)
  47. {
  48. _CSubSets();
  49. m_fPredefined = fPredefined;
  50. m_bPredefined = fPredefined;
  51. m_pIT = new CInfoType();
  52. if ( pSiteMap )
  53. *m_pIT = *pSiteMap;
  54. // populate subsets with declarations in ptblSubSets
  55. CStr cszSSDecl; // one item of a subset
  56. CStr cszSubSetName;
  57. CStr cszITName;
  58. int cDeclarations = ptblSubSets->CountStrings();
  59. for (int i=1; i<cDeclarations; i++ )
  60. {
  61. cszSSDecl = ptblSubSets->GetPointer( i );
  62. int type ;
  63. if ( isSameString(cszSSDecl.psz, txtSSInclusive) )
  64. type = SS_INCLUSIVE;
  65. else if ( isSameString(cszSSDecl.psz, txtSSExclusive) )
  66. type = SS_EXCLUSIVE;
  67. cszSubSetName = StrChr(cszSSDecl.psz, ':')+1;
  68. PSTR psz = StrChr(cszSubSetName, ':');
  69. *psz = '\0';
  70. cszITName = StrChr( cszSSDecl.psz, ':')+1;
  71. if ( GetSubSetIndex( cszSubSetName ) < 0 )
  72. {
  73. AddSubSet(); // create a new subset
  74. m_cur_Set->m_cszSubSetName = cszSubSetName.psz;
  75. m_cur_Set->m_bPredefined = TRUE;
  76. }
  77. int pos = m_pIT->m_itTables.m_ptblInfoTypes->IsStringInTable( cszITName.psz );
  78. if ( pos <= 0 )
  79. continue;
  80. if ( type == SS_INCLUSIVE )
  81. m_cur_Set->AddIncType( pos ); // Add this "include" bit.
  82. else
  83. m_cur_Set->AddExcType( pos ); // Add this "exclude" bit.
  84. }
  85. }
  86. CSubSets::~CSubSets()
  87. {
  88. if (m_aSubSets) //leak fix
  89. {
  90. for(int i=0; i<m_max_subsets; i++)
  91. {
  92. if ( m_aSubSets[i] )
  93. delete m_aSubSets[i];
  94. }
  95. lcFree(m_aSubSets);
  96. }
  97. if ( m_pszFilename )
  98. lcFree(m_pszFilename );
  99. if ( m_pIT )
  100. delete( m_pIT );
  101. }
  102. const CSubSets& CSubSets::operator=( const CSubSets& SetsSrc ) // Copy Constructor
  103. {
  104. if ( this == NULL )
  105. return *this;
  106. if (m_max_subsets < SetsSrc.m_max_subsets )
  107. m_aSubSets = (CSubSet **) lcReAlloc(m_aSubSets, SetsSrc.m_max_subsets*sizeof(CSubSet*) );
  108. m_cSets = SetsSrc.m_cSets;
  109. m_max_subsets = SetsSrc.m_max_subsets;
  110. if ( m_pszFilename )
  111. lcFree( m_pszFilename );
  112. m_pszFilename = lcStrDup( SetsSrc.m_pszFilename);
  113. m_fPredefined = SetsSrc.m_fPredefined;
  114. for(int i=0; i< m_cSets; i++)
  115. {
  116. if ( m_aSubSets[i] )
  117. delete m_aSubSets[i];
  118. *m_aSubSets[i] = *SetsSrc.m_aSubSets[i];
  119. if ( SetsSrc.m_Toc == SetsSrc.m_aSubSets[i] )
  120. m_Toc = m_aSubSets[i];
  121. if ( SetsSrc.m_Index == SetsSrc.m_aSubSets[i] )
  122. m_Index = m_aSubSets[i];
  123. if ( SetsSrc.m_FTS == SetsSrc.m_aSubSets[i] )
  124. m_FTS = m_aSubSets[i];
  125. if ( SetsSrc.m_cur_Set == SetsSrc.m_aSubSets[i] )
  126. m_cur_Set = m_aSubSets[i];
  127. }
  128. m_pIT = new CInfoType();
  129. *m_pIT = *SetsSrc.m_pIT;
  130. return *this;
  131. }
  132. #ifdef HHCTRL
  133. void CSubSets::CopyTo( CHmData * const phmData )
  134. {
  135. CStr cszSubSetName;
  136. CStr cszITName;
  137. if ( !phmData->m_pdSubSets )
  138. return;
  139. for(int i=0; (phmData->m_pdSubSets[i].type>=SS_INCLUSIVE) && (phmData->m_pdSubSets[i].type<=SS_EXCLUSIVE); i++)
  140. {
  141. cszSubSetName = phmData->GetString( phmData->m_pdSubSets[i].dwStringSubSetName );
  142. if ( GetSubSetIndex( cszSubSetName ) < 0 )
  143. {
  144. AddSubSet(); // create a new subset
  145. m_cur_Set->m_cszSubSetName = cszSubSetName.psz;
  146. m_cur_Set->m_bPredefined = TRUE;
  147. }
  148. cszITName = phmData->GetString( phmData->m_pdSubSets[i].dwStringITName );
  149. int pos = m_pIT->m_itTables.m_ptblInfoTypes->IsStringInTable( cszITName.psz );
  150. if ( pos <= 0 )
  151. continue;
  152. if ( phmData->m_pdSubSets[i].type == SS_INCLUSIVE )
  153. m_cur_Set->AddIncType( pos ); // Add this "include" bit.
  154. else
  155. m_cur_Set->AddExcType( pos ); // Add this "exclude" bit.
  156. }
  157. // Read in all the user defined Subsets.
  158. CState* pstate = phmData->m_pTitleCollection->GetState();
  159. static const int MAX_PARAM = 4096;
  160. CMem memParam( MAX_PARAM );
  161. PSTR pszParam = (PSTR)memParam; // for notational convenience
  162. DWORD cb;
  163. int nKey=1;
  164. char buf[5];
  165. CStr cszKey = g_szUDSKey;
  166. wsprintf(buf,"%d",nKey++);
  167. cszKey += buf;
  168. while( SUCCEEDED(pstate->Open(cszKey.psz,STGM_READ)) )
  169. {
  170. int type;
  171. cszKey = g_szUDSKey;
  172. wsprintf(buf, "%d", nKey++);
  173. cszKey += buf;
  174. if ( SUCCEEDED(pstate->Read(pszParam,MAX_PATH,&cb)) )
  175. {
  176. if ( isSameString( (PCSTR)pszParam, txtSSInclusive) )
  177. type = SS_INCLUSIVE;
  178. else
  179. if ( isSameString((PCSTR)pszParam, txtSSExclusive) )
  180. type = SS_EXCLUSIVE;
  181. else
  182. break;
  183. }
  184. else
  185. break;
  186. // format INCLUSIVE|EXCLUSIVE:SubSetName:ITName
  187. PSTR psz = StrChr((PCSTR)pszParam, ':');
  188. if (psz)
  189. psz++;
  190. else
  191. psz = "";
  192. CStr cszTemp = psz;
  193. PSTR pszTemp = StrChr(cszTemp.psz, ':');
  194. *pszTemp = '\0';
  195. if ( GetSubSetIndex(cszTemp) < 0)
  196. {
  197. AddSubSet(); // create a new subset
  198. m_cur_Set->m_cszSubSetName = cszTemp.psz;
  199. m_cur_Set->m_bPredefined = FALSE; // The subset is user defined.
  200. }
  201. // Get the name of the type
  202. psz = StrChr(psz, ':');
  203. if ( psz )
  204. psz++;
  205. else
  206. psz ="";
  207. if (IsNonEmptyString(psz))
  208. {
  209. int pos = m_pIT->m_itTables.m_ptblInfoTypes->IsStringInTable( psz );
  210. if ( pos <= 0 )
  211. continue;
  212. if ( type == SS_INCLUSIVE )
  213. m_cur_Set->AddIncType( pos );
  214. else if ( type == SS_EXCLUSIVE)
  215. m_cur_Set->AddExcType( pos );
  216. }
  217. pstate->Close();
  218. pstate->Delete();
  219. } // while reading user defined subsets
  220. for ( int n = 0; n < m_cSets; n++ )
  221. m_aSubSets[n]->BuildMask();
  222. }
  223. #endif
  224. // Import a subset
  225. // ******************
  226. CSubSet *CSubSets::AddSubSet(CSubSet *pSubSet )
  227. {
  228. if ( !pSubSet || pSubSet->m_cszSubSetName.IsEmpty() )
  229. return NULL;
  230. if ( GetSubSetIndex( pSubSet->m_cszSubSetName.psz ) >= 0)
  231. return NULL; // the SS is already defined
  232. if ( m_cSets >= m_max_subsets )
  233. ReSizeSubSet(); // allocate space for more subsets
  234. m_aSubSets[m_cSets] = new CSubSet( m_ITSize );
  235. m_cur_Set = m_aSubSets[m_cSets];
  236. m_cSets++;
  237. *m_cur_Set = *pSubSet; // populate the new SubSet
  238. return m_cur_Set;
  239. }
  240. // Create a new (empty) subset
  241. // **********************
  242. CSubSet *CSubSets::AddSubSet( )
  243. {
  244. if ( (m_cSets >= m_max_subsets) )
  245. ReSizeSubSet();
  246. m_aSubSets[m_cSets] = new CSubSet( m_ITSize);
  247. m_cur_Set = m_aSubSets[m_cSets];
  248. m_cSets++;
  249. m_cur_Set->m_bPredefined = m_bPredefined; // from the base class
  250. m_cur_Set->m_pIT = m_pIT;
  251. return m_cur_Set;
  252. }
  253. // Removes a subset from m_aSubSets.
  254. // ***********************************
  255. void CSubSets::RemoveSubSet( PCSTR pszName )
  256. {
  257. int pos;
  258. if ( IsEmptyString(pszName) )
  259. return;
  260. pos = GetSubSetIndex( pszName );
  261. if ( (pos < 0) || (pos>m_max_subsets) )
  262. return; // subset name is not defined
  263. delete m_aSubSets[pos];
  264. m_aSubSets[pos] = NULL;
  265. m_cur_Set = NULL;
  266. }
  267. // NOTE:
  268. // Only user defined Subsets are saved to file. Author defined subsets (ie predefined
  269. // subsets live in the master .chm file. And are saved in the .hhp file and in the future
  270. // in the .hhc and .hhk file.
  271. // First try to save in the same directory as the .chm is in. next put it in the windows help directory.
  272. //
  273. // Format:
  274. // [SUBSETS]
  275. // SetName:Inclusive:TypeName
  276. // SetName:Exclusive:TypeName
  277. BOOL CSubSets::SaveToFile( PCSTR filename )
  278. {
  279. if ( m_fPredefined )
  280. return FALSE;
  281. // finish this
  282. return TRUE;
  283. }
  284. BOOL CSubSets::ReadFromFile( PCSTR filename )
  285. {
  286. // finish this
  287. if ( m_fPredefined )
  288. return FALSE;
  289. // Read the user defined subsets from the .sub file and add them to this subset.
  290. return TRUE;
  291. }
  292. int CSubSets::GetSubSetIndex( PCSTR pszName ) const
  293. {
  294. for (int i=0; i<m_cSets; i++ )
  295. {
  296. if (m_aSubSets[i] && m_aSubSets[i]->IsSameSet( pszName ) )
  297. return i;
  298. }
  299. return -1;
  300. }
  301. // Adds 5 more subsets.
  302. void CSubSets::ReSizeSubSet()
  303. {
  304. ASSERT(m_aSubSets);
  305. m_max_subsets += 5;
  306. m_aSubSets = (CSubSet**)lcReAlloc( m_aSubSets, m_max_subsets * sizeof(CSubSet*) );
  307. }
  308. #ifdef HHCTRL
  309. void CSubSets::SetTocMask( PCSTR psz, CHHWinType* phh)
  310. {
  311. CSubSet* pSS;
  312. int i = GetSubSetIndex(psz);
  313. if ( i >= 0 )
  314. pSS = m_aSubSets[i];
  315. else
  316. pSS = NULL;
  317. if ( pSS != m_Toc )
  318. {
  319. m_Toc = pSS;
  320. // Need to re-init the TOC!
  321. if (phh)
  322. phh->UpdateInformationTypes();
  323. }
  324. }
  325. #endif
  326. ////////////////////////////////////////////////////////////////////////////////////////////////
  327. //
  328. // CSubSet
  329. //
  330. CSubSet::CSubSet(int const ITSize)
  331. {
  332. // Round up to nearest DWORD boundary and allocate the bit fields. We have to do this becuase
  333. // m_pInclusive and m_pExclusive are DWORD pointers rather than byte pointers. As an optimization, if
  334. // ITSize is <= 4 We'll just use CSubSet member DWORDS as the bit fields. <mc>
  335. //
  336. int iRem = ITSize % 4;
  337. if ( iRem )
  338. m_ITSize = (ITSize + (4 - iRem));
  339. else
  340. m_ITSize = ITSize;
  341. if ( ITSize > 4 )
  342. {
  343. m_pInclusive = (INFOTYPE*) lcCalloc(m_ITSize);
  344. m_pExclusive = (INFOTYPE*) lcCalloc(m_ITSize);
  345. }
  346. else
  347. {
  348. dwI = dwE = 0;
  349. m_pInclusive = &dwI;
  350. m_pExclusive = &dwE;
  351. }
  352. m_aCatMasks = NULL;
  353. m_bIsEntireCollection = FALSE;
  354. }
  355. CSubSet::~CSubSet()
  356. {
  357. if ( m_ITSize > 4 )
  358. {
  359. if ( m_pInclusive )
  360. lcFree(m_pInclusive);
  361. if ( m_pExclusive )
  362. lcFree(m_pExclusive);
  363. }
  364. if ( m_aCatMasks )
  365. {
  366. for(int i=0; i<m_pIT->HowManyCategories(); i++)
  367. lcFree(m_aCatMasks[i]);
  368. lcFree(m_aCatMasks);
  369. }
  370. }
  371. // Copy constructor
  372. // *********************
  373. const CSubSet& CSubSet::operator=(const CSubSet& SetSrc)
  374. {
  375. if ( this == NULL )
  376. return *this;
  377. if ( this == &SetSrc )
  378. return *this;
  379. m_cszSubSetName = SetSrc.m_cszSubSetName.psz; // copy the set name
  380. memcpy(m_pInclusive, SetSrc.m_pInclusive, SetSrc.m_ITSize);
  381. memcpy(m_pExclusive, SetSrc.m_pExclusive, SetSrc.m_ITSize);
  382. m_ITSize = SetSrc.m_ITSize;
  383. m_bPredefined = SetSrc.m_bPredefined;
  384. m_pIT = SetSrc.m_pIT;
  385. return *this;
  386. }
  387. void CSubSet::BuildMask(void)
  388. {
  389. if ( m_bIsEntireCollection )
  390. return;
  391. int cCategories = m_pIT->HowManyCategories();
  392. m_aCatMasks = (INFOTYPE **) lcCalloc(cCategories * sizeof(INFOTYPE *) );
  393. for(int i =0; i<cCategories; i++)
  394. {
  395. INFOTYPE *pIT, *pCatMask;
  396. m_aCatMasks[i] = (INFOTYPE*)lcCalloc( m_ITSize);
  397. pIT = m_aCatMasks[i];
  398. INFOTYPE *pSSMask = m_pInclusive;
  399. pCatMask = m_pIT->m_itTables.m_aCategories[i].pInfoType;
  400. for(int j=0; j<m_ITSize; j+=4)
  401. {
  402. *pIT = (*pSSMask & *pCatMask);
  403. pIT++;
  404. pCatMask++;
  405. pSSMask++;
  406. }
  407. }
  408. }
  409. BOOL CSubSet::IsSameSet (PCSTR pszName) const
  410. {
  411. if ( pszName && strcmp(m_cszSubSetName.psz, pszName) == 0)
  412. return TRUE;
  413. else
  414. return FALSE;
  415. }
  416. static int ExclusiveOffset=-1;
  417. static INFOTYPE curExclusiveBit=1;
  418. int CSubSet::GetFirstExcITinSubSet(void) const
  419. {
  420. ExclusiveOffset = -1;
  421. curExclusiveBit = 1;
  422. int pos = GetITBitfromIT(m_pExclusive,
  423. &ExclusiveOffset,
  424. &curExclusiveBit,
  425. m_ITSize*8 );
  426. while ( (pos != -1) && IsDeleted(pos) )
  427. pos = GetNextExcITinSubSet();
  428. return pos;
  429. }
  430. int CSubSet::GetNextExcITinSubSet(void) const
  431. {
  432. if ( ExclusiveOffset <=-1 || curExclusiveBit <=1 )
  433. return -1;
  434. return GetITBitfromIT(m_pExclusive,
  435. &ExclusiveOffset,
  436. &curExclusiveBit,
  437. m_ITSize*8 );
  438. }
  439. static int InclusiveOffset=-1;
  440. static INFOTYPE curInclusiveBit=1;
  441. int CSubSet::GetFirstIncITinSubSet( void) const
  442. {
  443. InclusiveOffset = -1;
  444. curInclusiveBit = 1;
  445. int pos = GetITBitfromIT(m_pInclusive,
  446. &InclusiveOffset,
  447. &curInclusiveBit,
  448. m_ITSize*8 );
  449. while ( (pos != -1) && IsDeleted( pos ) )
  450. pos = GetNextIncITinSubSet();
  451. return pos;
  452. }
  453. int CSubSet::GetNextIncITinSubSet( void) const
  454. {
  455. if ( InclusiveOffset <= -1 || curInclusiveBit <= 1 )
  456. return -1;
  457. return GetITBitfromIT(m_pInclusive,
  458. &InclusiveOffset,
  459. &curInclusiveBit,
  460. m_ITSize*8 );
  461. }
  462. BOOL CSubSet::Filter( INFOTYPE const *pTopicIT ) const
  463. {
  464. INFOTYPE const *pTopicOffset = pTopicIT;
  465. INFOTYPE MaskOffset;
  466. //INFOTYPE *pExcITOffset, ExcITMask, ExcITBits;
  467. int cCategories = m_pIT->HowManyCategories();
  468. BOOL NoMask;
  469. if ( m_bIsEntireCollection )
  470. return TRUE;
  471. // Filter on the Subset's Exclusive Bit Mask
  472. #if 0
  473. pTopicOffset = pTopicIT;
  474. memcpy(&MaskOffset,m_pInclusive, sizeof(INFOTYPE) );
  475. memcpy(&ExcITBits, m_pExclusive, sizeof(INFOTYPE) );
  476. pExcITOffset = m_pIT->m_itTables.m_pExclusive;
  477. ExcITMask = MaskOffset & *pExcITOffset; // ExcITMask has the EX ITs that are set in the filter.
  478. ExcITMask = (~ExcITMask | ExcITBits); // flip the exclusive bits in the filter mask
  479. ExcITMask = ExcITMask ^ ~*pExcITOffset; // To get rid of those extra 1's from the previous ~.
  480. #endif
  481. NoMask = FALSE;
  482. for ( int i=0; i<m_ITSize; i+=4)
  483. {
  484. #if 0
  485. if ( (ExcITMask & *pTopicOffset) )
  486. return FALSE;
  487. memcpy(&MaskOffset,m_pInclusive+i, sizeof(INFOTYPE) );
  488. memcpy(&ExcITBits, m_pExclusive+i, sizeof(INFOTYPE) );
  489. pTopicOffset++;
  490. pExcITOffset++;
  491. ExcITMask = MaskOffset & *pExcITOffset; // ExcITMask has the EX ITs that are set in the filter.
  492. ExcITMask = (~ExcITMask | ExcITBits); // flip the exclusive bits in the filter mask
  493. ExcITMask = ExcITMask ^ ~*pExcITOffset; // To get rid of those extra 1's from the previous ~.
  494. #else
  495. if ( *m_pExclusive & *pTopicOffset )
  496. return FALSE;
  497. #endif
  498. }
  499. // Filter on the Subset's Inclusive Bit Mask
  500. #if 0
  501. if ( cCategories > 0)
  502. {
  503. for (int i=0; i<cCategories; i++)
  504. {
  505. pTopicOffset = pTopicIT;
  506. pMaskOffset = m_aCatMasks[i];
  507. #if 0
  508. NoMask = TRUE;
  509. for( int j=0; j<m_ITSize; j+=4)
  510. {
  511. if ( NoMask && *pTopicOffset == 0L )
  512. NoMask = TRUE;
  513. else
  514. NoMask = FALSE;
  515. if (! *pMaskOffset )
  516. {
  517. pMaskOffset++;
  518. pTopicOffset++;
  519. continue;
  520. }
  521. if (!NoMask && !(*pMaskOffset & *pTopicOffset) )
  522. return FALSE;
  523. pMaskOffset++;
  524. pTopicOffset++;
  525. }
  526. #endif
  527. BOOL bOk = TRUE;
  528. for(int j=0; j<m_ITSize; j+=4, pMaskOffset++, pTopicOffset++ )
  529. {
  530. if ( *pTopicOffset == 0L || *pMaskOffset == 0L )
  531. continue;
  532. if ( (bOk = (BOOL)(*pMaskOffset & *pTopicOffset)) )
  533. break;
  534. }
  535. if (! bOk )
  536. {
  537. //
  538. // Before we filter the topic out of existance we need to figure out if the topic is void of
  539. // any IT's from the current catagory. If it is we will NOT filter this topic based upon subset
  540. // bits for this catagory.
  541. //
  542. for(int n=0; n < (m_ITSize / 4); n++)
  543. {
  544. if ( m_pIT->m_itTables.m_aCategories[i].pInfoType[n] & pTopicIT[n] )
  545. return FALSE; // Yep, topic has an IT from the catagory.
  546. }
  547. }
  548. if (NoMask && *pTopicIT&1L ) // bit zero ==1 Inclusive logic
  549. return FALSE;
  550. }
  551. }
  552. else
  553. #endif
  554. { // No categories,
  555. pTopicOffset = pTopicIT;
  556. MaskOffset = *m_pInclusive;
  557. BOOL NoMask = TRUE;
  558. for ( int i=0; i<m_ITSize/4; i++)
  559. {
  560. if ( NoMask && *pTopicOffset == 0L )
  561. NoMask = TRUE;
  562. else
  563. NoMask = FALSE;
  564. // MaskOffset &= ~(*(m_pIT->m_itTables.m_pExclusive+i));
  565. if (!NoMask && !(MaskOffset & *pTopicOffset) )
  566. return FALSE;
  567. MaskOffset = (*m_pInclusive)+i;
  568. pTopicOffset++;
  569. }
  570. if (NoMask && *pTopicIT&1L ) // bit zero ==1 Inclusive logic
  571. return FALSE;
  572. }
  573. return TRUE;
  574. }