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.

1332 lines
39 KiB

  1. /****************************************************************************\
  2. *
  3. * PARSERAT.C -- Code to parse .RAT files
  4. *
  5. * Created: Greg Jones
  6. *
  7. \****************************************************************************/
  8. /*Includes------------------------------------------------------------------*/
  9. #include "msrating.h"
  10. #include "mslubase.h"
  11. #include "parselbl.h" /* we use a couple of this guy's subroutines */
  12. #include "msluglob.h"
  13. #include "debug.h"
  14. // Sundown: pointer to boolean conversion
  15. #pragma warning (disable: 4800)
  16. /****************************************************************************
  17. Some design notes on how this parser works:
  18. A ParenThing is:
  19. '(' identifier [stuff] ')'
  20. where [stuff] could be:
  21. a quoted string
  22. a number
  23. a boolean
  24. a series of ParenThings
  25. in the case of extensions:
  26. a quoted string, followed by
  27. one or more quoted strings and/or ParenThings
  28. The entire .RAT file is a ParenThing, except that it has no identifier, just
  29. a list of ParenThings inside it.
  30. **********************************************************************
  31. We pass the parser a schema for what things it expects -- we have
  32. a big array listing identifiers for each different possible keyword, and
  33. each parser call receives a smaller array containing only those indices
  34. that are valid to occur within that object.
  35. We make PicsRatingSystem, PicsCategory, and PicsEnum derive from a common
  36. base class which supports a virtual function AddItem(ID,data). So at the
  37. top level, we construct an (empty) PicsRatingSystem. We call the parser,
  38. giving it a pointer to that guy, and a structure describing what to parse --
  39. the ParenObject's token is a null string (since the global structure is the
  40. one that doesn't start with a token before its first embedded ParenThing),
  41. and we give a list saying the allowable things in a PicsRatingSystem are
  42. PICS-version, rating-system, rating-service, default, description, extension,
  43. icon, name, category. There is a global table indicating a handler function
  44. for every type of ParenThing, which knows how to create a data structure
  45. completely describing that ParenThing. (That data structure could be as
  46. simple as a number or as complex as allocating and parsing a complete
  47. PicsCategory object.)
  48. The parser walks along, and for each ParenThing he finds, he identifies it
  49. by looking up its token in the list provided by the caller. Each entry in
  50. that list should include a field which indicates whether multiple things
  51. of that identity are allowed (e.g., 'category') or not (e.g., rating-system).
  52. If only one is allowed, then when the parser finds one he marks it as having
  53. been found.
  54. When the parser identifies the ParenThing, he calls its handler function to
  55. completely parse the data in the ParenThing and return that object into an
  56. LPVOID provided by the parser. If that is successful, the parser then calls
  57. its object's AddItem(ID,data) virtual function to add the specified item to
  58. the object, relying on the object itself to know what type "data" points to --
  59. a number, a pointer to a heap string which can be given to ETS::SetTo, a
  60. pointer to a PicsCategory object which can be appended to an array, etc.
  61. The RatFileParser class exists solely to provide a line number shared by
  62. all the parsing routines. This line number is updated as the parser walks
  63. through the file, and is frozen as soon as an error is found. This line
  64. number can later be reported to the user to help localize errors in RAT files.
  65. *****************************************************************************/
  66. class RatFileParser
  67. {
  68. public:
  69. UINT m_nLine;
  70. RatFileParser() { m_nLine = 1; }
  71. LPSTR EatQuotedString(LPSTR pIn);
  72. HRESULT ParseToOpening(LPSTR *ppIn, AllowableOption *paoExpected,
  73. AllowableOption **ppFound);
  74. HRESULT ParseParenthesizedObject(
  75. LPSTR *ppIn, /* where we are in the text stream */
  76. AllowableOption aao[], /* allowable things inside this object */
  77. PicsObjectBase *pObject /* object to set parameters into */
  78. );
  79. char* FindNonWhite(char *pc);
  80. };
  81. /* White returns a pointer to the first whitespace character starting at pc.
  82. */
  83. char* White(char *pc)
  84. {
  85. ASSERT(pc);
  86. while (1)
  87. {
  88. if (*pc == '\0' ||
  89. *pc ==' ' ||
  90. *pc == '\t' ||
  91. *pc == '\r' ||
  92. *pc == '\n')
  93. {
  94. return pc;
  95. }
  96. pc++;
  97. }
  98. }
  99. /* NonWhite returns a pointer to the first non-whitespace character starting
  100. * at pc.
  101. */
  102. char* NonWhite(char *pc)
  103. {
  104. ASSERT(pc);
  105. while (1)
  106. {
  107. if (*pc != ' ' &&
  108. *pc != '\t' &&
  109. *pc != '\r' &&
  110. *pc != '\n') /* includes null terminator */
  111. {
  112. return pc;
  113. }
  114. pc++;
  115. }
  116. }
  117. /* FindNonWhite returns a pointer to the first non-whitespace character starting
  118. * at pc.
  119. */
  120. char* RatFileParser::FindNonWhite(char *pc)
  121. {
  122. ASSERT(pc);
  123. while (1)
  124. {
  125. if (*pc != ' ' &&
  126. *pc != '\t' &&
  127. *pc != '\r' &&
  128. *pc != '\n') /* includes null terminator */
  129. {
  130. return pc;
  131. }
  132. if (*pc == '\n')
  133. m_nLine++;
  134. pc++;
  135. }
  136. }
  137. /* Returns a pointer to the closing doublequote of a quoted string, counting
  138. * linefeeds as we go. Returns NULL if no closing doublequote found.
  139. */
  140. LPSTR RatFileParser::EatQuotedString(LPSTR pIn)
  141. {
  142. LPSTR pszQuote = strchrf(pIn, '\"');
  143. if (pszQuote == NULL)
  144. {
  145. TraceMsg( TF_WARNING, "RatFileParser::EatQuotedString() - No closing doublequote found!" );
  146. return NULL;
  147. }
  148. pIn = strchrf(pIn, '\n');
  149. while (pIn != NULL && pIn < pszQuote)
  150. {
  151. m_nLine++;
  152. pIn = strchrf(pIn+1, '\n');
  153. }
  154. return pszQuote;
  155. }
  156. /***************************************************************************
  157. Worker functions for inheriting category properties and other
  158. miscellaneous category stuff.
  159. ***************************************************************************/
  160. HRESULT PicsCategory::InitializeMyDefaults(PicsCategory *pCategory)
  161. {
  162. if (!pCategory->etnMin.fIsInit() && etnMin.fIsInit())
  163. pCategory->etnMin.Set(etnMin.Get());
  164. if (!pCategory->etnMax.fIsInit() && etnMax.fIsInit())
  165. pCategory->etnMax.Set(etnMax.Get());
  166. if (!pCategory->etfMulti.fIsInit() && etfMulti.fIsInit())
  167. pCategory->etfMulti.Set(etfMulti.Get());
  168. if (!pCategory->etfInteger.fIsInit() && etfInteger.fIsInit())
  169. pCategory->etfInteger.Set(etfInteger.Get());
  170. if (!pCategory->etfLabelled.fIsInit() && etfLabelled.fIsInit())
  171. pCategory->etfLabelled.Set(etfLabelled.Get());
  172. if (!pCategory->etfUnordered.fIsInit() && etfUnordered.fIsInit())
  173. pCategory->etfUnordered.Set(etfUnordered.Get());
  174. return NOERROR;
  175. }
  176. HRESULT PicsRatingSystem::InitializeMyDefaults(PicsCategory *pCategory)
  177. {
  178. if (m_pDefaultOptions != NULL)
  179. return m_pDefaultOptions->InitializeMyDefaults(pCategory);
  180. return NOERROR; /* no defaults to initialize */
  181. }
  182. HRESULT PicsDefault::InitializeMyDefaults(PicsCategory *pCategory)
  183. {
  184. if (!pCategory->etnMin.fIsInit() && etnMin.fIsInit())
  185. pCategory->etnMin.Set(etnMin.Get());
  186. if (!pCategory->etnMax.fIsInit() && etnMax.fIsInit())
  187. pCategory->etnMax.Set(etnMax.Get());
  188. if (!pCategory->etfMulti.fIsInit() && etfMulti.fIsInit())
  189. pCategory->etfMulti.Set(etfMulti.Get());
  190. if (!pCategory->etfInteger.fIsInit() && etfInteger.fIsInit())
  191. pCategory->etfInteger.Set(etfInteger.Get());
  192. if (!pCategory->etfLabelled.fIsInit() && etfLabelled.fIsInit())
  193. pCategory->etfLabelled.Set(etfLabelled.Get());
  194. if (!pCategory->etfUnordered.fIsInit() && etfUnordered.fIsInit())
  195. pCategory->etfUnordered.Set(etfUnordered.Get());
  196. return NOERROR;
  197. }
  198. HRESULT PicsEnum::InitializeMyDefaults(PicsCategory *pCategory)
  199. {
  200. return E_NOTIMPL; /* should never have a category inherit from an enum */
  201. }
  202. PicsExtension::PicsExtension()
  203. : m_pszRatingBureau(NULL)
  204. {
  205. /* nothing else */
  206. }
  207. PicsExtension::~PicsExtension()
  208. {
  209. delete m_pszRatingBureau;
  210. m_pszRatingBureau = NULL;
  211. }
  212. HRESULT PicsExtension::InitializeMyDefaults(PicsCategory *pCategory)
  213. {
  214. return E_NOTIMPL; /* should never have a category inherit from an extension */
  215. }
  216. void PicsCategory::FixupLimits()
  217. {
  218. BOOL fLabelled = (etfLabelled.fIsInit() && etfLabelled.Get());
  219. /*fix up max and min values*/
  220. if (fLabelled ||
  221. (arrpPE.Length()>0 && (!etnMax.fIsInit() || !etnMax.fIsInit())))
  222. {
  223. if (arrpPE.Length() > 0)
  224. {
  225. if (!etnMax.fIsInit())
  226. etnMax.Set(N_INFINITY);
  227. if (!etnMin.fIsInit())
  228. etnMin.Set(P_INFINITY);
  229. for (int z=0;z<arrpPE.Length();++z)
  230. {
  231. if (arrpPE[z]->etnValue.Get() > etnMax.Get()) etnMax.Set(arrpPE[z]->etnValue.Get());
  232. if (arrpPE[z]->etnValue.Get() < etnMin.Get()) etnMin.Set(arrpPE[z]->etnValue.Get());
  233. }
  234. }
  235. else
  236. {
  237. etfLabelled.Set(FALSE); /* no enum labels? better not have labelled flag then */
  238. fLabelled = FALSE;
  239. }
  240. }
  241. /*sort labels by value*/
  242. if (fLabelled)
  243. {
  244. int x,y;
  245. PicsEnum *pPE;
  246. for (x=0;x<arrpPE.Length()-1;++x)
  247. {
  248. for (y=x+1;y<arrpPE.Length();++y)
  249. {
  250. if (arrpPE[y]->etnValue.Get() < arrpPE[x]->etnValue.Get())
  251. {
  252. pPE = arrpPE[x];
  253. arrpPE[x] = arrpPE[y];
  254. arrpPE[y] = pPE;
  255. }
  256. }
  257. }
  258. }
  259. }
  260. void PicsCategory::SetParents(PicsRatingSystem *pOwner)
  261. {
  262. pPRS = pOwner;
  263. UINT cSubCategories = arrpPC.Length();
  264. for (UINT i = 0; i < cSubCategories; i++)
  265. {
  266. InitializeMyDefaults(arrpPC[i]); /* subcategory inherits our defaults */
  267. arrpPC[i]->SetParents(pOwner); /* process all subcategories */
  268. }
  269. FixupLimits(); /* inheritance is done, make sure limits make sense */
  270. }
  271. /***************************************************************************
  272. Handler functions which know how to parse the various kinds of content
  273. which can occur within a parenthesized object.
  274. ***************************************************************************/
  275. HRESULT RatParseString(LPSTR *ppszIn, LPVOID *ppOut, RatFileParser *pParser)
  276. {
  277. *ppOut = NULL;
  278. LPSTR pszCurrent = *ppszIn;
  279. if (*pszCurrent != '\"')
  280. {
  281. TraceMsg( TF_WARNING, "RatParseString() - Start string expected!" );
  282. return RAT_E_EXPECTEDSTRING;
  283. }
  284. pszCurrent++;
  285. LPSTR pszEnd = pParser->EatQuotedString(pszCurrent);
  286. if (pszEnd == NULL)
  287. {
  288. TraceMsg( TF_WARNING, "RatParseString() - End string expected!" );
  289. return RAT_E_EXPECTEDSTRING;
  290. }
  291. UINT cbString = (unsigned int) (pszEnd-pszCurrent);
  292. LPSTR pszNew = new char[cbString + 1];
  293. if (pszNew == NULL)
  294. {
  295. TraceMsg( TF_WARNING, "RatParseString() - pszNew is NULL!" );
  296. return E_OUTOFMEMORY;
  297. }
  298. memcpyf(pszNew, pszCurrent, cbString);
  299. pszNew[cbString] = '\0';
  300. *ppOut = (LPVOID)pszNew;
  301. *ppszIn = pParser->FindNonWhite(pszEnd + 1);
  302. return NOERROR;
  303. }
  304. HRESULT RatParseNumber(LPSTR *ppszIn, LPVOID *ppOut, RatFileParser *pParser)
  305. {
  306. int n;
  307. LPSTR pszCurrent = *ppszIn;
  308. HRESULT hres = ::ParseNumber(&pszCurrent, &n);
  309. if (FAILED(hres))
  310. {
  311. TraceMsg( TF_WARNING, "RatParseNumber() - Number Expected!" );
  312. return RAT_E_EXPECTEDNUMBER;
  313. }
  314. *(int *)ppOut = n;
  315. LPSTR pszNewline = strchrf(*ppszIn, '\n');
  316. while (pszNewline != NULL && pszNewline < pszCurrent)
  317. {
  318. pParser->m_nLine++;
  319. pszNewline = strchrf(pszNewline+1, '\n');
  320. }
  321. *ppszIn = pszCurrent;
  322. return NOERROR;
  323. }
  324. HRESULT RatParseBool(LPSTR *ppszIn, LPVOID *ppOut, RatFileParser *pParser)
  325. {
  326. BOOL b;
  327. /* PICS spec allows a terse way of specifying a TRUE boolean -- leaving
  328. * out the value entirely. In a .RAT file, the result looks like
  329. *
  330. * (unordered)
  331. * (multivalue)
  332. *
  333. * and so on. Called has pointed us at non-whitespace, so if we see
  334. * a closing paren, we know the .RAT file author used this syntax.
  335. */
  336. if (**ppszIn == ')')
  337. {
  338. b = TRUE;
  339. }
  340. else
  341. {
  342. LPSTR pszCurrent = *ppszIn;
  343. HRESULT hres = ::GetBool(&pszCurrent, &b);
  344. if (FAILED(hres))
  345. {
  346. TraceMsg( TF_WARNING, "RatParseBool() - Boolean Expected!" );
  347. return RAT_E_EXPECTEDBOOL;
  348. }
  349. LPSTR pszNewline = strchrf(*ppszIn, '\n');
  350. while (pszNewline != NULL && pszNewline < pszCurrent)
  351. {
  352. pParser->m_nLine++;
  353. pszNewline = strchrf(pszNewline+1, '\n');
  354. }
  355. *ppszIn = pszCurrent;
  356. }
  357. *(LPBOOL)ppOut = b;
  358. return NOERROR;
  359. }
  360. AllowableOption aaoPicsCategory[] = {
  361. { ROID_TRANSMITAS, AO_SINGLE | AO_MANDATORY },
  362. { ROID_NAME, AO_SINGLE },
  363. { ROID_DESCRIPTION, AO_SINGLE },
  364. { ROID_ICON, AO_SINGLE },
  365. { ROID_EXTENSION, 0 },
  366. { ROID_INTEGER, AO_SINGLE },
  367. { ROID_LABELONLY, AO_SINGLE },
  368. { ROID_MIN, AO_SINGLE },
  369. { ROID_MAX, AO_SINGLE },
  370. { ROID_MULTIVALUE, AO_SINGLE },
  371. { ROID_UNORDERED, AO_SINGLE },
  372. { ROID_LABEL, 0 },
  373. { ROID_CATEGORY, 0 },
  374. { ROID_INVALID, 0 }
  375. };
  376. const UINT caoPicsCategory = sizeof(aaoPicsCategory) / sizeof(aaoPicsCategory[0]);
  377. HRESULT RatParseCategory(LPSTR *ppszIn, LPVOID *ppOut, RatFileParser *pParser)
  378. {
  379. /* We must make a copy of the allowable options array because the
  380. * parser will fiddle with the flags in the entries -- specifically,
  381. * setting AO_SEEN. It wouldn't be thread-safe to do this to a
  382. * static array.
  383. */
  384. AllowableOption aao[caoPicsCategory];
  385. ::memcpyf(aao, ::aaoPicsCategory, sizeof(aao));
  386. PicsCategory *pCategory = new PicsCategory;
  387. if (pCategory == NULL)
  388. {
  389. TraceMsg( TF_WARNING, "RatParseCategory() - pCategory is NULL!" );
  390. return E_OUTOFMEMORY;
  391. }
  392. HRESULT hres = pParser->ParseParenthesizedObject(
  393. ppszIn, /* var containing current ptr */
  394. aao, /* what's legal in this object */
  395. pCategory); /* object to add items back to */
  396. if (FAILED(hres))
  397. {
  398. TraceMsg( TF_WARNING, "RatParseCategory() - ParseParenthesizedObject() Failed with hres=0x%x!", hres );
  399. delete pCategory;
  400. pCategory = NULL;
  401. return hres;
  402. }
  403. *ppOut = (LPVOID)pCategory;
  404. return NOERROR;
  405. }
  406. AllowableOption aaoPicsEnum[] = {
  407. { ROID_NAME, AO_SINGLE },
  408. { ROID_DESCRIPTION, AO_SINGLE },
  409. { ROID_VALUE, AO_SINGLE | AO_MANDATORY },
  410. { ROID_ICON, AO_SINGLE },
  411. { ROID_INVALID, 0 }
  412. };
  413. const UINT caoPicsEnum = sizeof(aaoPicsEnum) / sizeof(aaoPicsEnum[0]);
  414. HRESULT RatParseLabel(LPSTR *ppszIn, LPVOID *ppOut, RatFileParser *pParser)
  415. {
  416. /* We must make a copy of the allowable options array because the
  417. * parser will fiddle with the flags in the entries -- specifically,
  418. * setting AO_SEEN. It wouldn't be thread-safe to do this to a
  419. * static array.
  420. */
  421. AllowableOption aao[caoPicsEnum];
  422. ::memcpyf(aao, ::aaoPicsEnum, sizeof(aao));
  423. PicsEnum *pEnum = new PicsEnum;
  424. if (pEnum == NULL)
  425. {
  426. TraceMsg( TF_WARNING, "RatParseCategory() - pEnum is NULL!" );
  427. return E_OUTOFMEMORY;
  428. }
  429. HRESULT hres = pParser->ParseParenthesizedObject(
  430. ppszIn, /* var containing current ptr */
  431. aao, /* what's legal in this object */
  432. pEnum); /* object to add items back to */
  433. if (FAILED(hres))
  434. {
  435. TraceMsg( TF_WARNING, "RatParseLabel() - ParseParenthesizedObject() Failed with hres=0x%x!", hres );
  436. delete pEnum;
  437. pEnum = NULL;
  438. return hres;
  439. }
  440. *ppOut = (LPVOID)pEnum;
  441. return NOERROR;
  442. }
  443. AllowableOption aaoPicsDefault[] = {
  444. { ROID_EXTENSION, 0 },
  445. { ROID_INTEGER, AO_SINGLE },
  446. { ROID_LABELONLY, AO_SINGLE },
  447. { ROID_MAX, AO_SINGLE },
  448. { ROID_MIN, AO_SINGLE },
  449. { ROID_MULTIVALUE, AO_SINGLE },
  450. { ROID_UNORDERED, AO_SINGLE },
  451. { ROID_INVALID, 0 }
  452. };
  453. const UINT caoPicsDefault = sizeof(aaoPicsDefault) / sizeof(aaoPicsDefault[0]);
  454. HRESULT RatParseDefault(LPSTR *ppszIn, LPVOID *ppOut, RatFileParser *pParser)
  455. {
  456. /* We must make a copy of the allowable options array because the
  457. * parser will fiddle with the flags in the entries -- specifically,
  458. * setting AO_SEEN. It wouldn't be thread-safe to do this to a
  459. * static array.
  460. */
  461. AllowableOption aao[caoPicsDefault];
  462. ::memcpyf(aao, ::aaoPicsDefault, sizeof(aao));
  463. PicsDefault *pDefault = new PicsDefault;
  464. if (pDefault == NULL)
  465. {
  466. TraceMsg( TF_WARNING, "RatParseDefault() - pDefault is NULL!" );
  467. return E_OUTOFMEMORY;
  468. }
  469. HRESULT hres = pParser->ParseParenthesizedObject(
  470. ppszIn, /* var containing current ptr */
  471. aao, /* what's legal in this object */
  472. pDefault); /* object to add items back to */
  473. if (FAILED(hres))
  474. {
  475. TraceMsg( TF_WARNING, "RatParseDefault() - ParseParenthesizedObject() Failed with hres=0x%x!", hres );
  476. delete pDefault;
  477. pDefault = NULL;
  478. return hres;
  479. }
  480. *ppOut = (LPVOID)pDefault;
  481. return NOERROR;
  482. }
  483. AllowableOption aaoPicsExtension[] = {
  484. { ROID_MANDATORY, AO_SINGLE },
  485. { ROID_OPTIONAL, AO_SINGLE },
  486. { ROID_INVALID, 0 }
  487. };
  488. const UINT caoPicsExtension = sizeof(aaoPicsExtension) / sizeof(aaoPicsExtension[0]);
  489. HRESULT RatParseExtension(LPSTR *ppszIn, LPVOID *ppOut, RatFileParser *pParser)
  490. {
  491. /* We must make a copy of the allowable options array because the
  492. * parser will fiddle with the flags in the entries -- specifically,
  493. * setting AO_SEEN. It wouldn't be thread-safe to do this to a
  494. * static array.
  495. */
  496. AllowableOption aao[caoPicsExtension];
  497. ::memcpyf(aao, ::aaoPicsExtension, sizeof(aao));
  498. PicsExtension *pExtension = new PicsExtension;
  499. if (pExtension == NULL)
  500. {
  501. TraceMsg( TF_WARNING, "RatParseExtension() - pExtension is NULL!" );
  502. return E_OUTOFMEMORY;
  503. }
  504. HRESULT hres = pParser->ParseParenthesizedObject(
  505. ppszIn, /* var containing current ptr */
  506. aao, /* what's legal in this object */
  507. pExtension); /* object to add items back to */
  508. if (FAILED(hres))
  509. {
  510. TraceMsg( TF_WARNING, "RatParseExtension() - ParseParenthesizedObject() Failed with hres=0x%x!", hres );
  511. delete pExtension;
  512. pExtension = NULL;
  513. return hres;
  514. }
  515. *ppOut = (LPVOID)pExtension;
  516. return NOERROR;
  517. }
  518. /* Since the only extension we support right now is the one for a label
  519. * bureau, we just return the first quoted string we find if the caller
  520. * wants it. If ppOut is NULL, then it's some other extension and the
  521. * caller doesn't care about the data, he just wants it eaten.
  522. */
  523. HRESULT ParseRatExtensionData(LPSTR *ppszIn, RatFileParser *pParser, LPSTR *ppOut)
  524. {
  525. HRESULT hres = NOERROR;
  526. LPSTR pszCurrent = *ppszIn;
  527. /* Must look for closing ')' ourselves to terminate */
  528. while (*pszCurrent != ')')
  529. {
  530. if (*pszCurrent == '(')
  531. {
  532. pszCurrent = pParser->FindNonWhite(pszCurrent+1); /* skip paren and whitespace */
  533. hres = ParseRatExtensionData(&pszCurrent, pParser, ppOut); /* parentheses contain data */
  534. if (FAILED(hres))
  535. {
  536. TraceMsg( TF_WARNING, "ParseRatExtensionData() - ParseRatExtensionData() Failed with hres=0x%x!", hres );
  537. return hres;
  538. }
  539. if (*pszCurrent != ')')
  540. {
  541. TraceMsg( TF_WARNING, "ParseRatExtensionData() - Right Parenthesis Expected!" );
  542. return RAT_E_EXPECTEDRIGHT;
  543. }
  544. pszCurrent = pParser->FindNonWhite(pszCurrent+1); /* skip close ) and whitespace */
  545. }
  546. else if (*pszCurrent == '\"')
  547. { /* should be just a quoted string */
  548. if (ppOut != NULL && *ppOut == NULL)
  549. {
  550. hres = RatParseString(&pszCurrent, (LPVOID *)ppOut, pParser);
  551. // $REVIEW - Should we return for FAILED(hres)?
  552. }
  553. else
  554. {
  555. ++pszCurrent;
  556. LPSTR pszEndQuote = pParser->EatQuotedString(pszCurrent);
  557. if (pszEndQuote == NULL)
  558. {
  559. TraceMsg( TF_WARNING, "ParseRatExtensionData() - String Expected!" );
  560. return RAT_E_EXPECTEDSTRING;
  561. }
  562. pszCurrent = pParser->FindNonWhite(pszEndQuote+1); /* skip close " and whitespace */
  563. }
  564. }
  565. else
  566. {
  567. TraceMsg( TF_WARNING, "ParseRatExtensionData() - General Bad Syntax!" );
  568. return RAT_E_UNKNOWNITEM; /* general bad syntax */
  569. }
  570. }
  571. /* Caller will skip over final ')' for us. */
  572. *ppszIn = pszCurrent;
  573. return NOERROR;
  574. }
  575. HRESULT RatParseMandatory(LPSTR *ppszIn, LPVOID *ppOut, RatFileParser *pParser)
  576. {
  577. LPSTR pszCurrent = *ppszIn;
  578. /* First thing better be a quoted URL identifying the extension. */
  579. if (*pszCurrent != '\"')
  580. {
  581. TraceMsg( TF_WARNING, "RatParseMandatory() - Start String Expected!" );
  582. return RAT_E_EXPECTEDSTRING;
  583. }
  584. pszCurrent++;
  585. LPSTR pszEnd = pParser->EatQuotedString(pszCurrent);
  586. if (pszCurrent == NULL)
  587. {
  588. TraceMsg( TF_WARNING, "RatParseMandatory() - End String Expected!" );
  589. return RAT_E_EXPECTEDSTRING; /* missing closing " */
  590. }
  591. /* See if it's the extension for a label bureau. */
  592. LPSTR pszBureau = NULL;
  593. LPSTR *ppData = NULL;
  594. if (IsEqualToken(pszCurrent, pszEnd, ::szRatingBureauExtension))
  595. {
  596. ppData = &pszBureau;
  597. }
  598. pszCurrent = pParser->FindNonWhite(pszEnd+1); /* skip closing " and whitespace */
  599. HRESULT hres = ParseRatExtensionData(&pszCurrent, pParser, ppData);
  600. if (FAILED(hres))
  601. {
  602. TraceMsg( TF_WARNING, "RatParseMandatory() - ParseRatExtensionData() Failed with hres=0x%x!", hres );
  603. return hres;
  604. }
  605. *ppOut = pszBureau; /* return label bureau string if that's what we found */
  606. *ppszIn = pszCurrent;
  607. if (ppData == NULL)
  608. return RAT_E_UNKNOWNMANDATORY; /* we didn't recognize it */
  609. else
  610. return NOERROR;
  611. }
  612. /* RatParseOptional uses the code in RatParseMandatory to parse the extension
  613. * data, in case an extension that should be optional comes in as mandatory.
  614. * We then detect RatParseMandatory rejecting the thing as unrecognized and
  615. * allow it through, since here it's optional.
  616. */
  617. HRESULT RatParseOptional(LPSTR *ppszIn, LPVOID *ppOut, RatFileParser *pParser)
  618. {
  619. HRESULT hres = RatParseMandatory(ppszIn, ppOut, pParser);
  620. if (hres == RAT_E_UNKNOWNMANDATORY)
  621. hres = S_OK;
  622. return hres;
  623. }
  624. /***************************************************************************
  625. Code to identify the opening keyword of a parenthesized object and
  626. associate it with content.
  627. ***************************************************************************/
  628. /* The following array is indexed by RatObjectID values. */
  629. struct {
  630. LPCSTR pszToken; /* token by which we identify it */
  631. RatObjectHandler pHandler; /* function which parses the object's contents */
  632. } aObjectDescriptions[] = {
  633. { szNULL, NULL },
  634. { NULL, NULL }, /* placeholder for comparing against no token */
  635. { szPicsVersion, RatParseNumber },
  636. { szRatingSystem, RatParseString },
  637. { szRatingService, RatParseString },
  638. { szRatingBureau, RatParseString },
  639. { szBureauRequired, RatParseBool },
  640. { szCategory, RatParseCategory },
  641. { szTransmitAs, RatParseString },
  642. { szLabel, RatParseLabel },
  643. { szValue, RatParseNumber },
  644. { szDefault, RatParseDefault },
  645. { szDescription, RatParseString },
  646. { szExtensionOption, RatParseExtension },
  647. { szMandatory, RatParseMandatory },
  648. { szOptional, RatParseOptional },
  649. { szIcon, RatParseString },
  650. { szInteger, RatParseBool },
  651. { szLabelOnly, RatParseBool },
  652. { szMax, RatParseNumber },
  653. { szMin, RatParseNumber },
  654. { szMultiValue, RatParseBool },
  655. { szName, RatParseString },
  656. { szUnordered, RatParseBool }
  657. };
  658. /* ParseToOpening eats the opening '(' of a parenthesized object, and
  659. * verifies that the token just inside it is one of the expected ones.
  660. * If so, *ppIn is advanced past that token to the next non-whitespace
  661. * character; otherwise, an error is returned.
  662. *
  663. * For example, if *ppIn is pointing at "(PICS-version 1.1)", and
  664. * ROID_PICSVERSION is in the allowable option table supplied, then
  665. * NOERROR is returned and *ppIn will point at "1.1)".
  666. *
  667. * If the function is successful, *ppFound is set to point to the element
  668. * in the allowable-options table which matches the type of thing this
  669. * object actually is.
  670. */
  671. HRESULT RatFileParser::ParseToOpening(LPSTR *ppIn, AllowableOption *paoExpected,
  672. AllowableOption **ppFound)
  673. {
  674. LPSTR pszCurrent = *ppIn;
  675. pszCurrent = FindNonWhite(pszCurrent);
  676. if (*pszCurrent != '(')
  677. {
  678. TraceMsg( TF_WARNING, "RatFileParser::ParseToOpening() - Left Parenthesis Expected!" );
  679. return RAT_E_EXPECTEDLEFT;
  680. }
  681. pszCurrent = FindNonWhite(pszCurrent+1); /* skip '(' and whitespace */
  682. LPSTR pszTokenEnd = FindTokenEnd(pszCurrent);
  683. for (; paoExpected->roid != ROID_INVALID; paoExpected++)
  684. {
  685. LPCSTR pszThisToken = aObjectDescriptions[paoExpected->roid].pszToken;
  686. /* Special case for beginning of RAT file structure: no token at all. */
  687. if (pszThisToken == NULL)
  688. {
  689. if (*pszCurrent == '(')
  690. {
  691. *ppIn = pszCurrent;
  692. *ppFound = paoExpected;
  693. return NOERROR;
  694. }
  695. else
  696. {
  697. TraceMsg( TF_WARNING, "RatFileParser::ParseToOpening() - Token Left Parenthesis Expected!" );
  698. return RAT_E_EXPECTEDLEFT;
  699. }
  700. }
  701. else if (IsEqualToken(pszCurrent, pszTokenEnd, pszThisToken))
  702. break;
  703. }
  704. if (paoExpected->roid != ROID_INVALID)
  705. {
  706. *ppIn = FindNonWhite(pszTokenEnd); /* skip token and whitespace */
  707. *ppFound = paoExpected;
  708. return NOERROR;
  709. }
  710. else
  711. {
  712. TraceMsg( TF_WARNING, "RatFileParser::ParseToOpening() - Unknown Rat Object!" );
  713. return RAT_E_UNKNOWNITEM;
  714. }
  715. }
  716. /***************************************************************************
  717. The top-level entrypoint for parsing out a whole rating system.
  718. ***************************************************************************/
  719. AllowableOption aaoPicsRatingSystem[] = {
  720. { ROID_PICSVERSION, AO_SINGLE | AO_MANDATORY },
  721. { ROID_RATINGSYSTEM, AO_SINGLE | AO_MANDATORY },
  722. { ROID_RATINGSERVICE, AO_SINGLE | AO_MANDATORY },
  723. { ROID_RATINGBUREAU, AO_SINGLE },
  724. { ROID_BUREAUREQUIRED, AO_SINGLE },
  725. { ROID_DEFAULT, 0 },
  726. { ROID_DESCRIPTION, AO_SINGLE },
  727. { ROID_EXTENSION, 0 },
  728. { ROID_ICON, AO_SINGLE },
  729. { ROID_NAME, AO_SINGLE },
  730. { ROID_CATEGORY, AO_MANDATORY },
  731. { ROID_INVALID, 0 }
  732. };
  733. const UINT caoPicsRatingSystem = sizeof(aaoPicsRatingSystem) / sizeof(aaoPicsRatingSystem[0]);
  734. HRESULT PicsRatingSystem::Parse(LPCSTR pszFilename, LPSTR pIn)
  735. {
  736. /* This guy is small enough to just init directly on the stack */
  737. AllowableOption aaoRoot[] = { { ROID_PICSDOCUMENT, 0 }, { ROID_INVALID, 0 } };
  738. AllowableOption aao[caoPicsRatingSystem];
  739. ::memcpyf(aao, ::aaoPicsRatingSystem, sizeof(aao));
  740. AllowableOption *pFound;
  741. RatFileParser parser;
  742. HRESULT hres = parser.ParseToOpening(&pIn, aaoRoot, &pFound);
  743. if (FAILED(hres))
  744. {
  745. TraceMsg( TF_WARNING, "PicsRatingSystem::Parse() - Failed ParseToOpening() with hres=0x%x!", hres );
  746. return hres; /* some error early on */
  747. }
  748. hres = parser.ParseParenthesizedObject(
  749. &pIn, /* var containing current ptr */
  750. aao, /* what's legal in this object */
  751. this); /* object to add items back to */
  752. if(SUCCEEDED(hres))
  753. {
  754. if(*pIn!=')') //check for a closing parenthesis
  755. {
  756. hres=RAT_E_EXPECTEDRIGHT;
  757. }
  758. else
  759. {
  760. LPTSTR lpszEnd=NonWhite(pIn+1);
  761. if(*lpszEnd!='\0') // make sure we're at the end of the file
  762. {
  763. hres=RAT_E_EXPECTEDEND;
  764. }
  765. }
  766. }
  767. if(FAILED(hres))
  768. {
  769. nErrLine=parser.m_nLine;
  770. TraceMsg( TF_WARNING, "PicsRatingSystem::Parse() - Failed ParseParenthesizedObject() at nErrLine=%d with hres=0x%x!", nErrLine, hres );
  771. }
  772. return hres;
  773. }
  774. /***************************************************************************
  775. Callbacks into the various class objects to add parsed properties.
  776. ***************************************************************************/
  777. HRESULT PicsRatingSystem::AddItem(RatObjectID roid, LPVOID pData)
  778. {
  779. HRESULT hres = S_OK;
  780. switch (roid) {
  781. case ROID_PICSVERSION:
  782. etnPicsVersion.Set(PtrToLong(pData));
  783. break;
  784. case ROID_RATINGSYSTEM:
  785. etstrRatingSystem.SetTo((LPSTR)pData);
  786. break;
  787. case ROID_RATINGSERVICE:
  788. etstrRatingService.SetTo((LPSTR)pData);
  789. break;
  790. case ROID_RATINGBUREAU:
  791. etstrRatingBureau.SetTo((LPSTR)pData);
  792. break;
  793. case ROID_BUREAUREQUIRED:
  794. etbBureauRequired.Set((bool)pData);
  795. break;
  796. case ROID_DEFAULT:
  797. m_pDefaultOptions = (PicsDefault *)pData;
  798. break;
  799. case ROID_DESCRIPTION:
  800. etstrDesc.SetTo((LPSTR)pData);
  801. break;
  802. case ROID_EXTENSION:
  803. {
  804. /* just eat extensions for now */
  805. PicsExtension *pExtension = (PicsExtension *)pData;
  806. if (pExtension != NULL)
  807. {
  808. /* If this is a rating bureau extension, take his bureau
  809. * string and store it in this PicsRatingSystem. We now
  810. * own the memory, so NULL out the extension's pointer to
  811. * it so he won't delete it.
  812. */
  813. if (pExtension->m_pszRatingBureau != NULL)
  814. {
  815. etstrRatingBureau.SetTo(pExtension->m_pszRatingBureau);
  816. pExtension->m_pszRatingBureau = NULL;
  817. }
  818. delete pExtension;
  819. pExtension = NULL;
  820. }
  821. }
  822. break;
  823. case ROID_ICON:
  824. etstrIcon.SetTo((LPSTR)pData);
  825. break;
  826. case ROID_NAME:
  827. etstrName.SetTo((LPSTR)pData);
  828. break;
  829. case ROID_CATEGORY:
  830. {
  831. PicsCategory *pCategory = (PicsCategory *)pData;
  832. hres = arrpPC.Append(pCategory) ? S_OK : E_OUTOFMEMORY;
  833. if (FAILED(hres))
  834. {
  835. delete pCategory;
  836. pCategory = NULL;
  837. }
  838. else
  839. {
  840. InitializeMyDefaults(pCategory); /* category inherits default settings */
  841. pCategory->SetParents(this); /* set pPRS fields in whole tree */
  842. }
  843. }
  844. break;
  845. default:
  846. ASSERT(FALSE); /* shouldn't have been given a ROID that wasn't in
  847. * the table we passed to the parser! */
  848. hres = E_UNEXPECTED;
  849. break;
  850. }
  851. return hres;
  852. }
  853. HRESULT PicsCategory::AddItem(RatObjectID roid, LPVOID pData)
  854. {
  855. HRESULT hres = S_OK;
  856. switch (roid)
  857. {
  858. case ROID_TRANSMITAS:
  859. etstrTransmitAs.SetTo((LPSTR)pData);
  860. break;
  861. case ROID_NAME:
  862. etstrName.SetTo((LPSTR)pData);
  863. break;
  864. case ROID_DESCRIPTION:
  865. etstrDesc.SetTo((LPSTR)pData);
  866. break;
  867. case ROID_ICON:
  868. etstrIcon.SetTo((LPSTR)pData);
  869. break;
  870. case ROID_EXTENSION:
  871. { /* we support no extensions below the rating system level */
  872. PicsExtension *pExtension = (PicsExtension *)pData;
  873. if (pExtension != NULL)
  874. {
  875. delete pExtension;
  876. pExtension = NULL;
  877. }
  878. }
  879. break;
  880. case ROID_INTEGER:
  881. etfInteger.Set((bool) pData);
  882. break;
  883. case ROID_LABELONLY:
  884. etfLabelled.Set((bool) pData);
  885. break;
  886. case ROID_MULTIVALUE:
  887. etfMulti.Set((bool)pData);
  888. break;
  889. case ROID_UNORDERED:
  890. etfUnordered.Set((bool)pData);
  891. break;
  892. case ROID_MIN:
  893. etnMin.Set(PtrToLong(pData));
  894. break;
  895. case ROID_MAX:
  896. etnMax.Set(PtrToLong(pData));
  897. break;
  898. case ROID_LABEL:
  899. {
  900. PicsEnum *pEnum = (PicsEnum *)pData;
  901. hres = arrpPE.Append(pEnum) ? S_OK : E_OUTOFMEMORY;
  902. if (FAILED(hres))
  903. {
  904. delete pEnum;
  905. pEnum = NULL;
  906. }
  907. }
  908. break;
  909. case ROID_CATEGORY:
  910. {
  911. PicsCategory *pCategory = (PicsCategory *)pData;
  912. /* For a nested category, synthesize the transmit-name from
  913. * ours and the child's (e.g., parent category 'color' plus
  914. * child category 'hue' becomes 'color/hue'.
  915. *
  916. * Note that the memory we allocate for the new name will be
  917. * owned by pCategory->etstrTransmitAs. There is no memory
  918. * leak there.
  919. */
  920. UINT cbCombined = strlenf(etstrTransmitAs.Get()) +
  921. strlenf(pCategory->etstrTransmitAs.Get()) +
  922. 2; /* for PicsDelimChar + null */
  923. LPSTR pszTemp = new char[cbCombined];
  924. if (pszTemp == NULL)
  925. hres = E_OUTOFMEMORY;
  926. else {
  927. wsprintf(pszTemp, "%s%c%s", etstrTransmitAs.Get(),
  928. PicsDelimChar, pCategory->etstrTransmitAs.Get());
  929. pCategory->etstrTransmitAs.SetTo(pszTemp);
  930. hres = arrpPC.Append(pCategory) ? S_OK : E_OUTOFMEMORY;
  931. }
  932. if (FAILED(hres))
  933. {
  934. delete pCategory;
  935. pCategory = NULL;
  936. }
  937. }
  938. break;
  939. default:
  940. ASSERT(FALSE); /* shouldn't have been given a ROID that wasn't in
  941. * the table we passed to the parser! */
  942. hres = E_UNEXPECTED;
  943. break;
  944. }
  945. return hres;
  946. }
  947. HRESULT PicsEnum::AddItem(RatObjectID roid, LPVOID pData)
  948. {
  949. HRESULT hres = S_OK;
  950. switch (roid)
  951. {
  952. case ROID_NAME:
  953. etstrName.SetTo((LPSTR)pData);
  954. break;
  955. case ROID_DESCRIPTION:
  956. etstrDesc.SetTo((LPSTR)pData);
  957. break;
  958. case ROID_ICON:
  959. etstrIcon.SetTo((LPSTR)pData);
  960. break;
  961. case ROID_VALUE:
  962. etnValue.Set(PtrToLong(pData));
  963. break;
  964. default:
  965. ASSERT(FALSE); /* shouldn't have been given a ROID that wasn't in
  966. * the table we passed to the parser! */
  967. hres = E_UNEXPECTED;
  968. break;
  969. }
  970. return hres;
  971. }
  972. HRESULT PicsDefault::AddItem(RatObjectID roid, LPVOID pData)
  973. {
  974. HRESULT hres = S_OK;
  975. switch (roid)
  976. {
  977. case ROID_EXTENSION:
  978. { /* we support no extensions below the rating system level */
  979. PicsExtension *pExtension = (PicsExtension *)pData;
  980. if (pExtension != NULL)
  981. {
  982. delete pExtension;
  983. pExtension = NULL;
  984. }
  985. }
  986. break;
  987. case ROID_INTEGER:
  988. etfInteger.Set((bool)pData);
  989. break;
  990. case ROID_LABELONLY:
  991. etfLabelled.Set((bool)pData);
  992. break;
  993. case ROID_MULTIVALUE:
  994. etfMulti.Set((bool)pData);
  995. break;
  996. case ROID_UNORDERED:
  997. etfUnordered.Set((bool)pData);
  998. break;
  999. case ROID_MIN:
  1000. etnMin.Set(PtrToLong(pData));
  1001. break;
  1002. case ROID_MAX:
  1003. etnMax.Set(PtrToLong(pData));
  1004. break;
  1005. default:
  1006. ASSERT(FALSE); /* shouldn't have been given a ROID that wasn't in
  1007. * the table we passed to the parser! */
  1008. hres = E_UNEXPECTED;
  1009. break;
  1010. }
  1011. return hres;
  1012. }
  1013. HRESULT PicsExtension::AddItem(RatObjectID roid, LPVOID pData)
  1014. {
  1015. HRESULT hres = S_OK;
  1016. switch (roid) {
  1017. case ROID_OPTIONAL:
  1018. case ROID_MANDATORY:
  1019. /* Only data we should get is a label bureau string. */
  1020. if (pData != NULL)
  1021. m_pszRatingBureau = (LPSTR)pData;
  1022. break;
  1023. default:
  1024. ASSERT(FALSE); /* shouldn't have been given a ROID that wasn't in
  1025. * the table we passed to the parser! */
  1026. hres = E_UNEXPECTED;
  1027. break;
  1028. }
  1029. return hres;
  1030. }
  1031. /***************************************************************************
  1032. The main loop of the parser.
  1033. ***************************************************************************/
  1034. /* ParseParenthesizedObjectContents is called with a text pointer pointing at
  1035. * the first non-whitespace thing following the token identifying the type of
  1036. * object. It parses the rest of the contents of the object, up to and
  1037. * including the ')' which closes it. The array of AllowableOption structures
  1038. * specifies which understood options are allowed to occur within this object.
  1039. */
  1040. HRESULT RatFileParser::ParseParenthesizedObject(
  1041. LPSTR *ppIn, /* where we are in the text stream */
  1042. AllowableOption aao[], /* allowable things inside this object */
  1043. PicsObjectBase *pObject /* object to set parameters into */
  1044. )
  1045. {
  1046. HRESULT hres = S_OK;
  1047. LPSTR pszCurrent = *ppIn;
  1048. AllowableOption *pFound;
  1049. for (pFound = aao; pFound->roid != ROID_INVALID; pFound++)
  1050. {
  1051. pFound->fdwOptions &= ~AO_SEEN;
  1052. }
  1053. pFound = NULL;
  1054. while (*pszCurrent != ')' && *pszCurrent != '\0' && SUCCEEDED(hres))
  1055. {
  1056. hres = ParseToOpening(&pszCurrent, aao, &pFound);
  1057. if (SUCCEEDED(hres))
  1058. {
  1059. LPVOID pData;
  1060. hres = (*(aObjectDescriptions[pFound->roid].pHandler))(&pszCurrent, &pData, this);
  1061. if (SUCCEEDED(hres))
  1062. {
  1063. if ((pFound->fdwOptions & (AO_SINGLE | AO_SEEN)) == (AO_SINGLE | AO_SEEN))
  1064. {
  1065. hres = RAT_E_DUPLICATEITEM;
  1066. }
  1067. else
  1068. {
  1069. pFound->fdwOptions |= AO_SEEN;
  1070. hres = pObject->AddItem(pFound->roid, pData);
  1071. if (SUCCEEDED(hres))
  1072. {
  1073. if (*pszCurrent != ')')
  1074. hres = RAT_E_EXPECTEDRIGHT;
  1075. else
  1076. pszCurrent = FindNonWhite(pszCurrent+1);
  1077. }
  1078. }
  1079. }
  1080. }
  1081. }
  1082. if (FAILED(hres))
  1083. {
  1084. return hres;
  1085. }
  1086. for (pFound = aao; pFound->roid != ROID_INVALID; pFound++)
  1087. {
  1088. if ((pFound->fdwOptions & (AO_MANDATORY | AO_SEEN)) == AO_MANDATORY)
  1089. {
  1090. return RAT_E_MISSINGITEM; /* mandatory item not found */
  1091. }
  1092. }
  1093. *ppIn = pszCurrent;
  1094. return hres;
  1095. }