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