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
36typedef struct {
37    int     xml_ch;
38    int     xml_len;
39    char    xml_txt[8];
40} xml_xlate_t;
41
42static 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 = = = */
55static void
56remove_continuation(char * src);
57
58static char const *
59scan_q_str(char const * pzTxt);
60
61static tOptionValue *
62add_string(void ** pp, char const * name, size_t nm_len,
63           char const * val, size_t d_len);
64
65static tOptionValue *
66add_bool(void ** pp, char const * name, size_t nm_len,
67         char const * val, size_t d_len);
68
69static tOptionValue *
70add_number(void ** pp, char const * name, size_t nm_len,
71           char const * val, size_t d_len);
72
73static tOptionValue *
74add_nested(void ** pp, char const * name, size_t nm_len,
75           char * val, size_t d_len);
76
77static char const *
78scan_name(char const * name, tOptionValue * res);
79
80static char const *
81unnamed_xml(char const * txt);
82
83static char const *
84scan_xml_name(char const * name, size_t * nm_len, tOptionValue * val);
85
86static char const *
87find_end_xml(char const * src, size_t nm_len, char const * val, size_t * len);
88
89static char const *
90scan_xml(char const * xml_name, tOptionValue * res_val);
91
92static void
93sort_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 */
100static void
101remove_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 */
138static char const *
139scan_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 */
183static tOptionValue *
184add_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 */
236static tOptionValue *
237add_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 */
277static tOptionValue *
278add_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 */
314static tOptionValue *
315add_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 */
343static char const *
344scan_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 */
441static char const *
442unnamed_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 */
474static char const *
475scan_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 */
517static char const *
518find_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 */
553static char const *
554scan_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
631bail_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 */
643LOCAL void
644unload_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=*/
669void
670optionUnloadNested(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 */
688static void
689sort_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=*/
748LOCAL tOptionValue *
749optionLoadNested(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=*/
826void
827optionNestedVal(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 */
861LOCAL int
862get_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 */
915LOCAL void
916emit_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