nested.c 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905
  1. /**
  2. * \file nested.c
  3. *
  4. * Handle options with arguments that contain nested values.
  5. *
  6. * @addtogroup autoopts
  7. * @{
  8. */
  9. /*
  10. * Automated Options Nested Values module.
  11. *
  12. * This file is part of AutoOpts, a companion to AutoGen.
  13. * AutoOpts is free software.
  14. * AutoOpts is Copyright (C) 1992-2018 by Bruce Korb - all rights reserved
  15. *
  16. * AutoOpts is available under any one of two licenses. The license
  17. * in use must be one of these two and the choice is under the control
  18. * of the user of the license.
  19. *
  20. * The GNU Lesser General Public License, version 3 or later
  21. * See the files "COPYING.lgplv3" and "COPYING.gplv3"
  22. *
  23. * The Modified Berkeley Software Distribution License
  24. * See the file "COPYING.mbsd"
  25. *
  26. * These files have the following sha256 sums:
  27. *
  28. * 8584710e9b04216a394078dc156b781d0b47e1729104d666658aecef8ee32e95 COPYING.gplv3
  29. * 4379e7444a0e2ce2b12dd6f5a52a27a4d02d39d247901d3285c88cf0d37f477b COPYING.lgplv3
  30. * 13aa749a5b0a454917a944ed8fffc530b784f5ead522b1aacaf4ec8aa55a6239 COPYING.mbsd
  31. */
  32. typedef struct {
  33. int xml_ch;
  34. int xml_len;
  35. char xml_txt[8];
  36. } xml_xlate_t;
  37. static xml_xlate_t const xml_xlate[] = {
  38. { '&', 4, "amp;" },
  39. { '<', 3, "lt;" },
  40. { '>', 3, "gt;" },
  41. { '"', 5, "quot;" },
  42. { '\'',5, "apos;" }
  43. };
  44. #ifndef ENOMSG
  45. #define ENOMSG ENOENT
  46. #endif
  47. /**
  48. * Backslashes are used for line continuations. We keep the newline
  49. * characters, but trim out the backslash:
  50. */
  51. static void
  52. remove_continuation(char * src)
  53. {
  54. char * pzD;
  55. do {
  56. while (*src == NL) src++;
  57. pzD = strchr(src, NL);
  58. if (pzD == NULL)
  59. return;
  60. /*
  61. * pzD has skipped at least one non-newline character and now
  62. * points to a newline character. It now becomes the source and
  63. * pzD goes to the previous character.
  64. */
  65. src = pzD--;
  66. if (*pzD != '\\')
  67. pzD++;
  68. } while (pzD == src);
  69. /*
  70. * Start shifting text.
  71. */
  72. for (;;) {
  73. char ch = ((*pzD++) = *(src++));
  74. switch (ch) {
  75. case NUL: return;
  76. case '\\':
  77. if (*src == NL)
  78. --pzD; /* rewrite on next iteration */
  79. }
  80. }
  81. }
  82. /**
  83. * Find the end of a quoted string, skipping escaped quote characters.
  84. */
  85. static char const *
  86. scan_q_str(char const * pzTxt)
  87. {
  88. char q = *(pzTxt++); /* remember the type of quote */
  89. for (;;) {
  90. char ch = *(pzTxt++);
  91. if (ch == NUL)
  92. return pzTxt-1;
  93. if (ch == q)
  94. return pzTxt;
  95. if (ch == '\\') {
  96. ch = *(pzTxt++);
  97. /*
  98. * IF the next character is NUL, drop the backslash, too.
  99. */
  100. if (ch == NUL)
  101. return pzTxt - 2;
  102. /*
  103. * IF the quote character or the escape character were escaped,
  104. * then skip both, as long as the string does not end.
  105. */
  106. if ((ch == q) || (ch == '\\')) {
  107. if (*(pzTxt++) == NUL)
  108. return pzTxt-1;
  109. }
  110. }
  111. }
  112. }
  113. /**
  114. * Associate a name with either a string or no value.
  115. *
  116. * @param[in,out] pp argument list to add to
  117. * @param[in] name the name of the "suboption"
  118. * @param[in] nm_len the length of the name
  119. * @param[in] val the string value for the suboption
  120. * @param[in] d_len the length of the value
  121. *
  122. * @returns the new value structure
  123. */
  124. static tOptionValue *
  125. add_string(void ** pp, char const * name, size_t nm_len,
  126. char const * val, size_t d_len)
  127. {
  128. tOptionValue * pNV;
  129. size_t sz = nm_len + d_len + sizeof(*pNV);
  130. pNV = AGALOC(sz, "option name/str value pair");
  131. if (val == NULL) {
  132. pNV->valType = OPARG_TYPE_NONE;
  133. pNV->pzName = pNV->v.strVal;
  134. } else {
  135. pNV->valType = OPARG_TYPE_STRING;
  136. if (d_len > 0) {
  137. char const * src = val;
  138. char * pzDst = pNV->v.strVal;
  139. int ct = (int)d_len;
  140. do {
  141. int ch = *(src++) & 0xFF;
  142. if (ch == NUL) goto data_copy_done;
  143. if (ch == '&')
  144. ch = get_special_char(&src, &ct);
  145. *(pzDst++) = (char)ch;
  146. } while (--ct > 0);
  147. data_copy_done:
  148. *pzDst = NUL;
  149. } else {
  150. pNV->v.strVal[0] = NUL;
  151. }
  152. pNV->pzName = pNV->v.strVal + d_len + 1;
  153. }
  154. memcpy(pNV->pzName, name, nm_len);
  155. pNV->pzName[ nm_len ] = NUL;
  156. addArgListEntry(pp, pNV);
  157. return pNV;
  158. }
  159. /**
  160. * Associate a name with a boolean value
  161. *
  162. * @param[in,out] pp argument list to add to
  163. * @param[in] name the name of the "suboption"
  164. * @param[in] nm_len the length of the name
  165. * @param[in] val the boolean value for the suboption
  166. * @param[in] d_len the length of the value
  167. *
  168. * @returns the new value structure
  169. */
  170. static tOptionValue *
  171. add_bool(void ** pp, char const * name, size_t nm_len,
  172. char const * val, size_t d_len)
  173. {
  174. size_t sz = nm_len + sizeof(tOptionValue) + 1;
  175. tOptionValue * new_val = AGALOC(sz, "bool val");
  176. /*
  177. * Scan over whitespace is constrained by "d_len"
  178. */
  179. while (IS_WHITESPACE_CHAR(*val) && (d_len > 0)) {
  180. d_len--; val++;
  181. }
  182. if (d_len == 0)
  183. new_val->v.boolVal = 0;
  184. else if (IS_DEC_DIGIT_CHAR(*val))
  185. new_val->v.boolVal = (unsigned)atoi(val);
  186. else new_val->v.boolVal = ! IS_FALSE_TYPE_CHAR(*val);
  187. new_val->valType = OPARG_TYPE_BOOLEAN;
  188. new_val->pzName = (char *)(new_val + 1);
  189. memcpy(new_val->pzName, name, nm_len);
  190. new_val->pzName[ nm_len ] = NUL;
  191. addArgListEntry(pp, new_val);
  192. return new_val;
  193. }
  194. /**
  195. * Associate a name with strtol() value, defaulting to zero.
  196. *
  197. * @param[in,out] pp argument list to add to
  198. * @param[in] name the name of the "suboption"
  199. * @param[in] nm_len the length of the name
  200. * @param[in] val the numeric value for the suboption
  201. * @param[in] d_len the length of the value
  202. *
  203. * @returns the new value structure
  204. */
  205. static tOptionValue *
  206. add_number(void ** pp, char const * name, size_t nm_len,
  207. char const * val, size_t d_len)
  208. {
  209. size_t sz = nm_len + sizeof(tOptionValue) + 1;
  210. tOptionValue * new_val = AGALOC(sz, "int val");
  211. /*
  212. * Scan over whitespace is constrained by "d_len"
  213. */
  214. while (IS_WHITESPACE_CHAR(*val) && (d_len > 0)) {
  215. d_len--; val++;
  216. }
  217. if (d_len == 0)
  218. new_val->v.longVal = 0;
  219. else
  220. new_val->v.longVal = strtol(val, 0, 0);
  221. new_val->valType = OPARG_TYPE_NUMERIC;
  222. new_val->pzName = (char *)(new_val + 1);
  223. memcpy(new_val->pzName, name, nm_len);
  224. new_val->pzName[ nm_len ] = NUL;
  225. addArgListEntry(pp, new_val);
  226. return new_val;
  227. }
  228. /**
  229. * Associate a name with a nested/hierarchical value.
  230. *
  231. * @param[in,out] pp argument list to add to
  232. * @param[in] name the name of the "suboption"
  233. * @param[in] nm_len the length of the name
  234. * @param[in] val the nested values for the suboption
  235. * @param[in] d_len the length of the value
  236. *
  237. * @returns the new value structure
  238. */
  239. static tOptionValue *
  240. add_nested(void ** pp, char const * name, size_t nm_len,
  241. char * val, size_t d_len)
  242. {
  243. tOptionValue * new_val;
  244. if (d_len == 0) {
  245. size_t sz = nm_len + sizeof(*new_val) + 1;
  246. new_val = AGALOC(sz, "empty nest");
  247. new_val->v.nestVal = NULL;
  248. new_val->valType = OPARG_TYPE_HIERARCHY;
  249. new_val->pzName = (char *)(new_val + 1);
  250. memcpy(new_val->pzName, name, nm_len);
  251. new_val->pzName[ nm_len ] = NUL;
  252. } else {
  253. new_val = optionLoadNested(val, name, nm_len);
  254. }
  255. if (new_val != NULL)
  256. addArgListEntry(pp, new_val);
  257. return new_val;
  258. }
  259. /**
  260. * We have an entry that starts with a name. Find the end of it, cook it
  261. * (if called for) and create the name/value association.
  262. */
  263. static char const *
  264. scan_name(char const * name, tOptionValue * res)
  265. {
  266. tOptionValue * new_val;
  267. char const * pzScan = name+1; /* we know first char is a name char */
  268. char const * pzVal;
  269. size_t nm_len = 1;
  270. size_t d_len = 0;
  271. /*
  272. * Scan over characters that name a value. These names may not end
  273. * with a colon, but they may contain colons.
  274. */
  275. pzScan = SPN_VALUE_NAME_CHARS(name + 1);
  276. if (pzScan[-1] == ':')
  277. pzScan--;
  278. nm_len = (size_t)(pzScan - name);
  279. pzScan = SPN_HORIZ_WHITE_CHARS(pzScan);
  280. re_switch:
  281. switch (*pzScan) {
  282. case '=':
  283. case ':':
  284. pzScan = SPN_HORIZ_WHITE_CHARS(pzScan + 1);
  285. if ((*pzScan == '=') || (*pzScan == ':'))
  286. goto default_char;
  287. goto re_switch;
  288. case NL:
  289. case ',':
  290. pzScan++;
  291. /* FALLTHROUGH */
  292. case NUL:
  293. add_string(&(res->v.nestVal), name, nm_len, NULL, (size_t)0);
  294. break;
  295. case '"':
  296. case '\'':
  297. pzVal = pzScan;
  298. pzScan = scan_q_str(pzScan);
  299. d_len = (size_t)(pzScan - pzVal);
  300. new_val = add_string(&(res->v.nestVal), name, nm_len, pzVal,
  301. d_len);
  302. if ((new_val != NULL) && (option_load_mode == OPTION_LOAD_COOKED))
  303. ao_string_cook(new_val->v.strVal, NULL);
  304. break;
  305. default:
  306. default_char:
  307. /*
  308. * We have found some strange text value. It ends with a newline
  309. * or a comma.
  310. */
  311. pzVal = pzScan;
  312. for (;;) {
  313. char ch = *(pzScan++);
  314. switch (ch) {
  315. case NUL:
  316. pzScan--;
  317. d_len = (size_t)(pzScan - pzVal);
  318. goto string_done;
  319. /* FALLTHROUGH */
  320. case NL:
  321. if ( (pzScan > pzVal + 2)
  322. && (pzScan[-2] == '\\')
  323. && (pzScan[ 0] != NUL))
  324. continue;
  325. /* FALLTHROUGH */
  326. case ',':
  327. d_len = (size_t)(pzScan - pzVal) - 1;
  328. string_done:
  329. new_val = add_string(&(res->v.nestVal), name, nm_len,
  330. pzVal, d_len);
  331. if (new_val != NULL)
  332. remove_continuation(new_val->v.strVal);
  333. goto leave_scan_name;
  334. }
  335. }
  336. break;
  337. } leave_scan_name:;
  338. return pzScan;
  339. }
  340. /**
  341. * Some xml element that does not start with a name.
  342. * The next character must be either '!' (introducing a comment),
  343. * or '?' (introducing an XML meta-marker of some sort).
  344. * We ignore these and indicate an error (NULL result) otherwise.
  345. *
  346. * @param[in] txt the text within an xml bracket
  347. * @returns the address of the character after the closing marker, or NULL.
  348. */
  349. static char const *
  350. unnamed_xml(char const * txt)
  351. {
  352. switch (*txt) {
  353. default:
  354. txt = NULL;
  355. break;
  356. case '!':
  357. txt = strstr(txt, "-->");
  358. if (txt != NULL)
  359. txt += 3;
  360. break;
  361. case '?':
  362. txt = strchr(txt, '>');
  363. if (txt != NULL)
  364. txt++;
  365. break;
  366. }
  367. return txt;
  368. }
  369. /**
  370. * Scan off the xml element name, and the rest of the header, too.
  371. * Set the value type to NONE if it ends with "/>".
  372. *
  373. * @param[in] name the first name character (alphabetic)
  374. * @param[out] nm_len the length of the name
  375. * @param[out] val set valType field to STRING or NONE.
  376. *
  377. * @returns the scan resumption point, or NULL on error
  378. */
  379. static char const *
  380. scan_xml_name(char const * name, size_t * nm_len, tOptionValue * val)
  381. {
  382. char const * scan = SPN_VALUE_NAME_CHARS(name + 1);
  383. *nm_len = (size_t)(scan - name);
  384. if (*nm_len > 64)
  385. return NULL;
  386. val->valType = OPARG_TYPE_STRING;
  387. if (IS_WHITESPACE_CHAR(*scan)) {
  388. /*
  389. * There are attributes following the name. Parse 'em.
  390. */
  391. scan = SPN_WHITESPACE_CHARS(scan);
  392. scan = parse_attrs(NULL, scan, &option_load_mode, val);
  393. if (scan == NULL)
  394. return NULL; /* oops */
  395. }
  396. if (! IS_END_XML_TOKEN_CHAR(*scan))
  397. return NULL; /* oops */
  398. if (*scan == '/') {
  399. /*
  400. * Single element XML entries get inserted as an empty string.
  401. */
  402. if (*++scan != '>')
  403. return NULL;
  404. val->valType = OPARG_TYPE_NONE;
  405. }
  406. return scan+1;
  407. }
  408. /**
  409. * We've found a closing '>' without a preceding '/', thus we must search
  410. * the text for '<name/>' where "name" is the name of the XML element.
  411. *
  412. * @param[in] name the start of the name in the element header
  413. * @param[in] nm_len the length of that name
  414. * @param[out] len the length of the value (string between header and
  415. * the trailer/tail.
  416. * @returns the character after the trailer, or NULL if not found.
  417. */
  418. static char const *
  419. find_end_xml(char const * src, size_t nm_len, char const * val, size_t * len)
  420. {
  421. char z[72] = "</";
  422. char * dst = z + 2;
  423. do {
  424. *(dst++) = *(src++);
  425. } while (--nm_len > 0); /* nm_len is known to be 64 or less */
  426. *(dst++) = '>';
  427. *dst = NUL;
  428. {
  429. char const * res = strstr(val, z);
  430. if (res != NULL) {
  431. char const * end = (option_load_mode != OPTION_LOAD_KEEP)
  432. ? SPN_WHITESPACE_BACK(val, res)
  433. : res;
  434. *len = (size_t)(end - val); /* includes trailing white space */
  435. res = SPN_WHITESPACE_CHARS(res + (dst - z));
  436. }
  437. return res;
  438. }
  439. }
  440. /**
  441. * We've found a '<' character. We ignore this if it is a comment or a
  442. * directive. If it is something else, then whatever it is we are looking
  443. * at is bogus. Returning NULL stops processing.
  444. *
  445. * @param[in] xml_name the name of an xml bracket (usually)
  446. * @param[in,out] res_val the option data derived from the XML element
  447. *
  448. * @returns the place to resume scanning input
  449. */
  450. static char const *
  451. scan_xml(char const * xml_name, tOptionValue * res_val)
  452. {
  453. size_t nm_len, v_len;
  454. char const * scan;
  455. char const * val_str;
  456. tOptionValue valu;
  457. tOptionLoadMode save_mode = option_load_mode;
  458. if (! IS_VAR_FIRST_CHAR(*++xml_name))
  459. return unnamed_xml(xml_name);
  460. /*
  461. * "scan_xml_name()" may change "option_load_mode".
  462. */
  463. val_str = scan_xml_name(xml_name, &nm_len, &valu);
  464. if (val_str == NULL)
  465. goto bail_scan_xml;
  466. if (valu.valType == OPARG_TYPE_NONE)
  467. scan = val_str;
  468. else {
  469. if (option_load_mode != OPTION_LOAD_KEEP)
  470. val_str = SPN_WHITESPACE_CHARS(val_str);
  471. scan = find_end_xml(xml_name, nm_len, val_str, &v_len);
  472. if (scan == NULL)
  473. goto bail_scan_xml;
  474. }
  475. /*
  476. * "scan" now points to where the scan is to resume after returning.
  477. * It either points after "/>" at the end of the XML element header,
  478. * or it points after the "</name>" tail based on the name in the header.
  479. */
  480. switch (valu.valType) {
  481. case OPARG_TYPE_NONE:
  482. add_string(&(res_val->v.nestVal), xml_name, nm_len, NULL, 0);
  483. break;
  484. case OPARG_TYPE_STRING:
  485. {
  486. tOptionValue * new_val = add_string(
  487. &(res_val->v.nestVal), xml_name, nm_len, val_str, v_len);
  488. if (option_load_mode != OPTION_LOAD_KEEP)
  489. munge_str(new_val->v.strVal, option_load_mode);
  490. break;
  491. }
  492. case OPARG_TYPE_BOOLEAN:
  493. add_bool(&(res_val->v.nestVal), xml_name, nm_len, val_str, v_len);
  494. break;
  495. case OPARG_TYPE_NUMERIC:
  496. add_number(&(res_val->v.nestVal), xml_name, nm_len, val_str, v_len);
  497. break;
  498. case OPARG_TYPE_HIERARCHY:
  499. {
  500. char * pz = AGALOC(v_len+1, "h scan");
  501. memcpy(pz, val_str, v_len);
  502. pz[v_len] = NUL;
  503. add_nested(&(res_val->v.nestVal), xml_name, nm_len, pz, v_len);
  504. AGFREE(pz);
  505. break;
  506. }
  507. case OPARG_TYPE_ENUMERATION:
  508. case OPARG_TYPE_MEMBERSHIP:
  509. default:
  510. break;
  511. }
  512. option_load_mode = save_mode;
  513. return scan;
  514. bail_scan_xml:
  515. option_load_mode = save_mode;
  516. return NULL;
  517. }
  518. /**
  519. * Deallocate a list of option arguments. This must have been gotten from
  520. * a hierarchical option argument, not a stacked list of strings. It is
  521. * an internal call, so it is not validated. The caller is responsible for
  522. * knowing what they are doing.
  523. */
  524. static void
  525. unload_arg_list(tArgList * arg_list)
  526. {
  527. int ct = arg_list->useCt;
  528. char const ** pnew_val = arg_list->apzArgs;
  529. while (ct-- > 0) {
  530. tOptionValue * new_val = (tOptionValue *)VOIDP(*(pnew_val++));
  531. if (new_val->valType == OPARG_TYPE_HIERARCHY)
  532. unload_arg_list(new_val->v.nestVal);
  533. AGFREE(new_val);
  534. }
  535. AGFREE(arg_list);
  536. }
  537. /*=export_func optionUnloadNested
  538. *
  539. * what: Deallocate the memory for a nested value
  540. * arg: + tOptionValue const * + pOptVal + the hierarchical value +
  541. *
  542. * doc:
  543. * A nested value needs to be deallocated. The pointer passed in should
  544. * have been gotten from a call to @code{configFileLoad()} (See
  545. * @pxref{libopts-configFileLoad}).
  546. =*/
  547. void
  548. optionUnloadNested(tOptionValue const * opt_val)
  549. {
  550. if (opt_val == NULL) return;
  551. if (opt_val->valType != OPARG_TYPE_HIERARCHY) {
  552. errno = EINVAL;
  553. return;
  554. }
  555. unload_arg_list(opt_val->v.nestVal);
  556. AGFREE(opt_val);
  557. }
  558. /**
  559. * This is a _stable_ sort. The entries are sorted alphabetically,
  560. * but within entries of the same name the ordering is unchanged.
  561. * Typically, we also hope the input is sorted.
  562. */
  563. static void
  564. sort_list(tArgList * arg_list)
  565. {
  566. int ix;
  567. int lm = arg_list->useCt;
  568. /*
  569. * This loop iterates "useCt" - 1 times.
  570. */
  571. for (ix = 0; ++ix < lm;) {
  572. int iy = ix-1;
  573. tOptionValue * new_v = C(tOptionValue *, arg_list->apzArgs[ix]);
  574. tOptionValue * old_v = C(tOptionValue *, arg_list->apzArgs[iy]);
  575. /*
  576. * For as long as the new entry precedes the "old" entry,
  577. * move the old pointer. Stop before trying to extract the
  578. * "-1" entry.
  579. */
  580. while (strcmp(old_v->pzName, new_v->pzName) > 0) {
  581. arg_list->apzArgs[iy+1] = VOIDP(old_v);
  582. old_v = (tOptionValue *)VOIDP(arg_list->apzArgs[--iy]);
  583. if (iy < 0)
  584. break;
  585. }
  586. /*
  587. * Always store the pointer. Sometimes it is redundant,
  588. * but the redundancy is cheaper than a test and branch sequence.
  589. */
  590. arg_list->apzArgs[iy+1] = VOIDP(new_v);
  591. }
  592. }
  593. /*=
  594. * private:
  595. *
  596. * what: parse a hierarchical option argument
  597. * arg: + char const * + pzTxt + the text to scan +
  598. * arg: + char const * + pzName + the name for the text +
  599. * arg: + size_t + nm_len + the length of "name" +
  600. *
  601. * ret_type: tOptionValue *
  602. * ret_desc: An allocated, compound value structure
  603. *
  604. * doc:
  605. * A block of text represents a series of values. It may be an
  606. * entire configuration file, or it may be an argument to an
  607. * option that takes a hierarchical value.
  608. *
  609. * If NULL is returned, errno will be set:
  610. * @itemize @bullet
  611. * @item
  612. * @code{EINVAL} the input text was NULL.
  613. * @item
  614. * @code{ENOMEM} the storage structures could not be allocated
  615. * @item
  616. * @code{ENOMSG} no configuration values were found
  617. * @end itemize
  618. =*/
  619. static tOptionValue *
  620. optionLoadNested(char const * text, char const * name, size_t nm_len)
  621. {
  622. tOptionValue * res_val;
  623. /*
  624. * Make sure we have some data and we have space to put what we find.
  625. */
  626. if (text == NULL) {
  627. errno = EINVAL;
  628. return NULL;
  629. }
  630. text = SPN_WHITESPACE_CHARS(text);
  631. if (*text == NUL) {
  632. errno = ENOMSG;
  633. return NULL;
  634. }
  635. res_val = AGALOC(sizeof(*res_val) + nm_len + 1, "nest args");
  636. res_val->valType = OPARG_TYPE_HIERARCHY;
  637. res_val->pzName = (char *)(res_val + 1);
  638. memcpy(res_val->pzName, name, nm_len);
  639. res_val->pzName[nm_len] = NUL;
  640. {
  641. tArgList * arg_list = AGALOC(sizeof(*arg_list), "nest arg l");
  642. res_val->v.nestVal = arg_list;
  643. arg_list->useCt = 0;
  644. arg_list->allocCt = MIN_ARG_ALLOC_CT;
  645. }
  646. /*
  647. * Scan until we hit a NUL.
  648. */
  649. do {
  650. text = SPN_WHITESPACE_CHARS(text);
  651. if (IS_VAR_FIRST_CHAR(*text))
  652. text = scan_name(text, res_val);
  653. else switch (*text) {
  654. case NUL:
  655. goto scan_done;
  656. case '<':
  657. text = scan_xml(text, res_val);
  658. if (text == NULL)
  659. goto woops;
  660. if (*text == ',')
  661. text++;
  662. break;
  663. case '#':
  664. text = strchr(text, NL);
  665. break;
  666. default:
  667. goto woops;
  668. }
  669. } while (text != NULL); scan_done:;
  670. {
  671. tArgList * al = res_val->v.nestVal;
  672. if (al->useCt == 0) {
  673. errno = ENOMSG;
  674. goto woops;
  675. }
  676. if (al->useCt > 1)
  677. sort_list(al);
  678. }
  679. return res_val;
  680. woops:
  681. AGFREE(res_val->v.nestVal);
  682. AGFREE(res_val);
  683. return NULL;
  684. }
  685. /*=export_func optionNestedVal
  686. * private:
  687. *
  688. * what: parse a hierarchical option argument
  689. * arg: + tOptions * + opts + program options descriptor +
  690. * arg: + tOptDesc * + od + the descriptor for this arg +
  691. *
  692. * doc:
  693. * Nested value was found on the command line
  694. =*/
  695. void
  696. optionNestedVal(tOptions * opts, tOptDesc * od)
  697. {
  698. if (opts < OPTPROC_EMIT_LIMIT)
  699. return;
  700. if (od->fOptState & OPTST_RESET) {
  701. tArgList * arg_list = od->optCookie;
  702. int ct;
  703. char const ** av;
  704. if (arg_list == NULL)
  705. return;
  706. ct = arg_list->useCt;
  707. av = arg_list->apzArgs;
  708. while (--ct >= 0) {
  709. void * p = VOIDP(*(av++));
  710. optionUnloadNested((tOptionValue const *)p);
  711. }
  712. AGFREE(od->optCookie);
  713. } else {
  714. tOptionValue * opt_val = optionLoadNested(
  715. od->optArg.argString, od->pz_Name, strlen(od->pz_Name));
  716. if (opt_val != NULL)
  717. addArgListEntry(&(od->optCookie), VOIDP(opt_val));
  718. }
  719. }
  720. /**
  721. * get_special_char
  722. */
  723. static int
  724. get_special_char(char const ** ppz, int * ct)
  725. {
  726. char const * pz = *ppz;
  727. if (*ct < 3)
  728. return '&';
  729. if (*pz == '#') {
  730. int base = 10;
  731. int retch;
  732. pz++;
  733. if (*pz == 'x') {
  734. base = 16;
  735. pz++;
  736. }
  737. retch = (int)strtoul(pz, (char **)&pz, base);
  738. if (*pz != ';')
  739. return '&';
  740. base = (int)(++pz - *ppz);
  741. if (base > *ct)
  742. return '&';
  743. *ct -= base;
  744. *ppz = pz;
  745. return retch;
  746. }
  747. {
  748. int ctr = sizeof(xml_xlate) / sizeof(xml_xlate[0]);
  749. xml_xlate_t const * xlatp = xml_xlate;
  750. for (;;) {
  751. if ( (*ct >= xlatp->xml_len)
  752. && (strncmp(pz, xlatp->xml_txt, (size_t)xlatp->xml_len) == 0)) {
  753. *ppz += xlatp->xml_len;
  754. *ct -= xlatp->xml_len;
  755. return xlatp->xml_ch;
  756. }
  757. if (--ctr <= 0)
  758. break;
  759. xlatp++;
  760. }
  761. }
  762. return '&';
  763. }
  764. /**
  765. * emit_special_char
  766. */
  767. static void
  768. emit_special_char(FILE * fp, int ch)
  769. {
  770. int ctr = sizeof(xml_xlate) / sizeof(xml_xlate[0]);
  771. xml_xlate_t const * xlatp = xml_xlate;
  772. putc('&', fp);
  773. for (;;) {
  774. if (ch == xlatp->xml_ch) {
  775. fputs(xlatp->xml_txt, fp);
  776. return;
  777. }
  778. if (--ctr <= 0)
  779. break;
  780. xlatp++;
  781. }
  782. fprintf(fp, XML_HEX_BYTE_FMT, (ch & 0xFF));
  783. }
  784. /** @}
  785. *
  786. * Local Variables:
  787. * mode: C
  788. * c-file-style: "stroustrup"
  789. * indent-tabs-mode: nil
  790. * End:
  791. * end of autoopts/nested.c */