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