xref: /freebsd/contrib/ntp/sntp/libopts/nested.c (revision 02e9120893770924227138ba49df1edb3896112a)
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
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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 *
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
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
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
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 *
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
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
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
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