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