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