nested.c 24 KB

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