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
34typedef 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 */
56static void
57remove_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 */
94static char const *
95scan_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 */
139static tOptionValue *
140add_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 */
192static tOptionValue *
193add_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 */
233static tOptionValue *
234add_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 */
270static tOptionValue *
271add_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 */
299static char const *
300scan_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 */
397static char const *
398unnamed_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 */
430static char const *
431scan_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 */
473static char const *
474find_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 */
509static char const *
510scan_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
587bail_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 */
599static void
600unload_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=*/
625void
626optionUnloadNested(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 */
644static void
645sort_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=*/
704static tOptionValue *
705optionLoadNested(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=*/
792void
793optionNestedVal(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 */
827static int
828get_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 */
879static void
880emit_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