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