| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947 | 
							
- /**
 
-  * \file nested.c
 
-  *
 
-  *  Handle options with arguments that contain nested values.
 
-  *
 
-  * @addtogroup autoopts
 
-  * @{
 
-  */
 
- /*
 
-  *   Automated Options Nested Values module.
 
-  *
 
-  *  This file is part of AutoOpts, a companion to AutoGen.
 
-  *  AutoOpts is free software.
 
-  *  AutoOpts is Copyright (C) 1992-2016 by Bruce Korb - all rights reserved
 
-  *
 
-  *  AutoOpts is available under any one of two licenses.  The license
 
-  *  in use must be one of these two and the choice is under the control
 
-  *  of the user of the license.
 
-  *
 
-  *   The GNU Lesser General Public License, version 3 or later
 
-  *      See the files "COPYING.lgplv3" and "COPYING.gplv3"
 
-  *
 
-  *   The Modified Berkeley Software Distribution License
 
-  *      See the file "COPYING.mbsd"
 
-  *
 
-  *  These files have the following sha256 sums:
 
-  *
 
-  *  8584710e9b04216a394078dc156b781d0b47e1729104d666658aecef8ee32e95  COPYING.gplv3
 
-  *  4379e7444a0e2ce2b12dd6f5a52a27a4d02d39d247901d3285c88cf0d37f477b  COPYING.lgplv3
 
-  *  13aa749a5b0a454917a944ed8fffc530b784f5ead522b1aacaf4ec8aa55a6239  COPYING.mbsd
 
-  */
 
- typedef struct {
 
-     int     xml_ch;
 
-     int     xml_len;
 
-     char    xml_txt[8];
 
- } xml_xlate_t;
 
- static xml_xlate_t const xml_xlate[] = {
 
-     { '&', 4, "amp;"  },
 
-     { '<', 3, "lt;"   },
 
-     { '>', 3, "gt;"   },
 
-     { '"', 5, "quot;" },
 
-     { '\'',5, "apos;" }
 
- };
 
- #ifndef ENOMSG
 
- #define ENOMSG ENOENT
 
- #endif
 
- /* = = = START-STATIC-FORWARD = = = */
 
- static void
 
- remove_continuation(char * src);
 
- static char const *
 
- scan_q_str(char const * pzTxt);
 
- static tOptionValue *
 
- add_string(void ** pp, char const * name, size_t nm_len,
 
-            char const * val, size_t d_len);
 
- static tOptionValue *
 
- add_bool(void ** pp, char const * name, size_t nm_len,
 
-          char const * val, size_t d_len);
 
- static tOptionValue *
 
- add_number(void ** pp, char const * name, size_t nm_len,
 
-            char const * val, size_t d_len);
 
- static tOptionValue *
 
- add_nested(void ** pp, char const * name, size_t nm_len,
 
-            char * val, size_t d_len);
 
- static char const *
 
- scan_name(char const * name, tOptionValue * res);
 
- static char const *
 
- unnamed_xml(char const * txt);
 
- static char const *
 
- scan_xml_name(char const * name, size_t * nm_len, tOptionValue * val);
 
- static char const *
 
- find_end_xml(char const * src, size_t nm_len, char const * val, size_t * len);
 
- static char const *
 
- scan_xml(char const * xml_name, tOptionValue * res_val);
 
- static void
 
- sort_list(tArgList * arg_list);
 
- /* = = = END-STATIC-FORWARD = = = */
 
- /**
 
-  *  Backslashes are used for line continuations.  We keep the newline
 
-  *  characters, but trim out the backslash:
 
-  */
 
- static void
 
- remove_continuation(char * src)
 
- {
 
-     char * pzD;
 
-     do  {
 
-         while (*src == NL)  src++;
 
-         pzD = strchr(src, NL);
 
-         if (pzD == NULL)
 
-             return;
 
-         /*
 
-          *  pzD has skipped at least one non-newline character and now
 
-          *  points to a newline character.  It now becomes the source and
 
-          *  pzD goes to the previous character.
 
-          */
 
-         src = pzD--;
 
-         if (*pzD != '\\')
 
-             pzD++;
 
-     } while (pzD == src);
 
-     /*
 
-      *  Start shifting text.
 
-      */
 
-     for (;;) {
 
-         char ch = ((*pzD++) = *(src++));
 
-         switch (ch) {
 
-         case NUL:  return;
 
-         case '\\':
 
-             if (*src == NL)
 
-                 --pzD; /* rewrite on next iteration */
 
-         }
 
-     }
 
- }
 
- /**
 
-  *  Find the end of a quoted string, skipping escaped quote characters.
 
-  */
 
- static char const *
 
- scan_q_str(char const * pzTxt)
 
- {
 
-     char q = *(pzTxt++); /* remember the type of quote */
 
-     for (;;) {
 
-         char ch = *(pzTxt++);
 
-         if (ch == NUL)
 
-             return pzTxt-1;
 
-         if (ch == q)
 
-             return pzTxt;
 
-         if (ch == '\\') {
 
-             ch = *(pzTxt++);
 
-             /*
 
-              *  IF the next character is NUL, drop the backslash, too.
 
-              */
 
-             if (ch == NUL)
 
-                 return pzTxt - 2;
 
-             /*
 
-              *  IF the quote character or the escape character were escaped,
 
-              *  then skip both, as long as the string does not end.
 
-              */
 
-             if ((ch == q) || (ch == '\\')) {
 
-                 if (*(pzTxt++) == NUL)
 
-                     return pzTxt-1;
 
-             }
 
-         }
 
-     }
 
- }
 
- /**
 
-  *  Associate a name with either a string or no value.
 
-  *
 
-  * @param[in,out] pp        argument list to add to
 
-  * @param[in]     name      the name of the "suboption"
 
-  * @param[in]     nm_len    the length of the name
 
-  * @param[in]     val       the string value for the suboption
 
-  * @param[in]     d_len     the length of the value
 
-  *
 
-  * @returns the new value structure
 
-  */
 
- static tOptionValue *
 
- add_string(void ** pp, char const * name, size_t nm_len,
 
-            char const * val, size_t d_len)
 
- {
 
-     tOptionValue * pNV;
 
-     size_t sz = nm_len + d_len + sizeof(*pNV);
 
-     pNV = AGALOC(sz, "option name/str value pair");
 
-     if (val == NULL) {
 
-         pNV->valType = OPARG_TYPE_NONE;
 
-         pNV->pzName = pNV->v.strVal;
 
-     } else {
 
-         pNV->valType = OPARG_TYPE_STRING;
 
-         if (d_len > 0) {
 
-             char const * src = val;
 
-             char * pzDst = pNV->v.strVal;
 
-             int    ct    = (int)d_len;
 
-             do  {
 
-                 int ch = *(src++) & 0xFF;
 
-                 if (ch == NUL) goto data_copy_done;
 
-                 if (ch == '&')
 
-                     ch = get_special_char(&src, &ct);
 
-                 *(pzDst++) = (char)ch;
 
-             } while (--ct > 0);
 
-         data_copy_done:
 
-             *pzDst = NUL;
 
-         } else {
 
-             pNV->v.strVal[0] = NUL;
 
-         }
 
-         pNV->pzName = pNV->v.strVal + d_len + 1;
 
-     }
 
-     memcpy(pNV->pzName, name, nm_len);
 
-     pNV->pzName[ nm_len ] = NUL;
 
-     addArgListEntry(pp, pNV);
 
-     return pNV;
 
- }
 
- /**
 
-  *  Associate a name with a boolean value
 
-  *
 
-  * @param[in,out] pp        argument list to add to
 
-  * @param[in]     name      the name of the "suboption"
 
-  * @param[in]     nm_len    the length of the name
 
-  * @param[in]     val       the boolean value for the suboption
 
-  * @param[in]     d_len     the length of the value
 
-  *
 
-  * @returns the new value structure
 
-  */
 
- static tOptionValue *
 
- add_bool(void ** pp, char const * name, size_t nm_len,
 
-          char const * val, size_t d_len)
 
- {
 
-     size_t sz = nm_len + sizeof(tOptionValue) + 1;
 
-     tOptionValue * new_val = AGALOC(sz, "bool val");
 
-     /*
 
-      * Scan over whitespace is constrained by "d_len"
 
-      */
 
-     while (IS_WHITESPACE_CHAR(*val) && (d_len > 0)) {
 
-         d_len--; val++;
 
-     }
 
-     if (d_len == 0)
 
-         new_val->v.boolVal = 0;
 
-     else if (IS_DEC_DIGIT_CHAR(*val))
 
-         new_val->v.boolVal = (unsigned)atoi(val);
 
-     else new_val->v.boolVal = ! IS_FALSE_TYPE_CHAR(*val);
 
-     new_val->valType = OPARG_TYPE_BOOLEAN;
 
-     new_val->pzName = (char *)(new_val + 1);
 
-     memcpy(new_val->pzName, name, nm_len);
 
-     new_val->pzName[ nm_len ] = NUL;
 
-     addArgListEntry(pp, new_val);
 
-     return new_val;
 
- }
 
- /**
 
-  *  Associate a name with strtol() value, defaulting to zero.
 
-  *
 
-  * @param[in,out] pp        argument list to add to
 
-  * @param[in]     name      the name of the "suboption"
 
-  * @param[in]     nm_len    the length of the name
 
-  * @param[in]     val       the numeric value for the suboption
 
-  * @param[in]     d_len     the length of the value
 
-  *
 
-  * @returns the new value structure
 
-  */
 
- static tOptionValue *
 
- add_number(void ** pp, char const * name, size_t nm_len,
 
-            char const * val, size_t d_len)
 
- {
 
-     size_t sz = nm_len + sizeof(tOptionValue) + 1;
 
-     tOptionValue * new_val = AGALOC(sz, "int val");
 
-     /*
 
-      * Scan over whitespace is constrained by "d_len"
 
-      */
 
-     while (IS_WHITESPACE_CHAR(*val) && (d_len > 0)) {
 
-         d_len--; val++;
 
-     }
 
-     if (d_len == 0)
 
-         new_val->v.longVal = 0;
 
-     else
 
-         new_val->v.longVal = strtol(val, 0, 0);
 
-     new_val->valType = OPARG_TYPE_NUMERIC;
 
-     new_val->pzName  = (char *)(new_val + 1);
 
-     memcpy(new_val->pzName, name, nm_len);
 
-     new_val->pzName[ nm_len ] = NUL;
 
-     addArgListEntry(pp, new_val);
 
-     return new_val;
 
- }
 
- /**
 
-  *  Associate a name with a nested/hierarchical value.
 
-  *
 
-  * @param[in,out] pp        argument list to add to
 
-  * @param[in]     name      the name of the "suboption"
 
-  * @param[in]     nm_len    the length of the name
 
-  * @param[in]     val       the nested values for the suboption
 
-  * @param[in]     d_len     the length of the value
 
-  *
 
-  * @returns the new value structure
 
-  */
 
- static tOptionValue *
 
- add_nested(void ** pp, char const * name, size_t nm_len,
 
-            char * val, size_t d_len)
 
- {
 
-     tOptionValue * new_val;
 
-     if (d_len == 0) {
 
-         size_t sz = nm_len + sizeof(*new_val) + 1;
 
-         new_val = AGALOC(sz, "empty nest");
 
-         new_val->v.nestVal = NULL;
 
-         new_val->valType = OPARG_TYPE_HIERARCHY;
 
-         new_val->pzName = (char *)(new_val + 1);
 
-         memcpy(new_val->pzName, name, nm_len);
 
-         new_val->pzName[ nm_len ] = NUL;
 
-     } else {
 
-         new_val = optionLoadNested(val, name, nm_len);
 
-     }
 
-     if (new_val != NULL)
 
-         addArgListEntry(pp, new_val);
 
-     return new_val;
 
- }
 
- /**
 
-  *  We have an entry that starts with a name.  Find the end of it, cook it
 
-  *  (if called for) and create the name/value association.
 
-  */
 
- static char const *
 
- scan_name(char const * name, tOptionValue * res)
 
- {
 
-     tOptionValue * new_val;
 
-     char const *   pzScan = name+1; /* we know first char is a name char */
 
-     char const *   pzVal;
 
-     size_t         nm_len = 1;
 
-     size_t         d_len = 0;
 
-     /*
 
-      *  Scan over characters that name a value.  These names may not end
 
-      *  with a colon, but they may contain colons.
 
-      */
 
-     pzScan = SPN_VALUE_NAME_CHARS(name + 1);
 
-     if (pzScan[-1] == ':')
 
-         pzScan--;
 
-     nm_len = (size_t)(pzScan - name);
 
-     pzScan = SPN_HORIZ_WHITE_CHARS(pzScan);
 
-  re_switch:
 
-     switch (*pzScan) {
 
-     case '=':
 
-     case ':':
 
-         pzScan = SPN_HORIZ_WHITE_CHARS(pzScan + 1);
 
-         if ((*pzScan == '=') || (*pzScan == ':'))
 
-             goto default_char;
 
-         goto re_switch;
 
-     case NL:
 
-     case ',':
 
-         pzScan++;
 
-         /* FALLTHROUGH */
 
-     case NUL:
 
-         add_string(&(res->v.nestVal), name, nm_len, NULL, (size_t)0);
 
-         break;
 
-     case '"':
 
-     case '\'':
 
-         pzVal = pzScan;
 
-         pzScan = scan_q_str(pzScan);
 
-         d_len = (size_t)(pzScan - pzVal);
 
-         new_val = add_string(&(res->v.nestVal), name, nm_len, pzVal,
 
-                          d_len);
 
-         if ((new_val != NULL) && (option_load_mode == OPTION_LOAD_COOKED))
 
-             ao_string_cook(new_val->v.strVal, NULL);
 
-         break;
 
-     default:
 
-     default_char:
 
-         /*
 
-          *  We have found some strange text value.  It ends with a newline
 
-          *  or a comma.
 
-          */
 
-         pzVal = pzScan;
 
-         for (;;) {
 
-             char ch = *(pzScan++);
 
-             switch (ch) {
 
-             case NUL:
 
-                 pzScan--;
 
-                 d_len = (size_t)(pzScan - pzVal);
 
-                 goto string_done;
 
-                 /* FALLTHROUGH */
 
-             case NL:
 
-                 if (   (pzScan > pzVal + 2)
 
-                     && (pzScan[-2] == '\\')
 
-                     && (pzScan[ 0] != NUL))
 
-                     continue;
 
-                 /* FALLTHROUGH */
 
-             case ',':
 
-                 d_len = (size_t)(pzScan - pzVal) - 1;
 
-             string_done:
 
-                 new_val = add_string(&(res->v.nestVal), name, nm_len,
 
-                                      pzVal, d_len);
 
-                 if (new_val != NULL)
 
-                     remove_continuation(new_val->v.strVal);
 
-                 goto leave_scan_name;
 
-             }
 
-         }
 
-         break;
 
-     } leave_scan_name:;
 
-     return pzScan;
 
- }
 
- /**
 
-  * Some xml element that does not start with a name.
 
-  * The next character must be either '!' (introducing a comment),
 
-  * or '?' (introducing an XML meta-marker of some sort).
 
-  * We ignore these and indicate an error (NULL result) otherwise.
 
-  *
 
-  * @param[in] txt  the text within an xml bracket
 
-  * @returns the address of the character after the closing marker, or NULL.
 
-  */
 
- static char const *
 
- unnamed_xml(char const * txt)
 
- {
 
-     switch (*txt) {
 
-     default:
 
-         txt = NULL;
 
-         break;
 
-     case '!':
 
-         txt = strstr(txt, "-->");
 
-         if (txt != NULL)
 
-             txt += 3;
 
-         break;
 
-     case '?':
 
-         txt = strchr(txt, '>');
 
-         if (txt != NULL)
 
-             txt++;
 
-         break;
 
-     }
 
-     return txt;
 
- }
 
- /**
 
-  *  Scan off the xml element name, and the rest of the header, too.
 
-  *  Set the value type to NONE if it ends with "/>".
 
-  *
 
-  * @param[in]  name    the first name character (alphabetic)
 
-  * @param[out] nm_len  the length of the name
 
-  * @param[out] val     set valType field to STRING or NONE.
 
-  *
 
-  * @returns the scan resumption point, or NULL on error
 
-  */
 
- static char const *
 
- scan_xml_name(char const * name, size_t * nm_len, tOptionValue * val)
 
- {
 
-     char const * scan = SPN_VALUE_NAME_CHARS(name + 1);
 
-     *nm_len = (size_t)(scan - name);
 
-     if (*nm_len > 64)
 
-         return NULL;
 
-     val->valType = OPARG_TYPE_STRING;
 
-     if (IS_WHITESPACE_CHAR(*scan)) {
 
-         /*
 
-          * There are attributes following the name.  Parse 'em.
 
-          */
 
-         scan = SPN_WHITESPACE_CHARS(scan);
 
-         scan = parse_attrs(NULL, scan, &option_load_mode, val);
 
-         if (scan == NULL)
 
-             return NULL; /* oops */
 
-     }
 
-     if (! IS_END_XML_TOKEN_CHAR(*scan))
 
-         return NULL; /* oops */
 
-     if (*scan == '/') {
 
-         /*
 
-          * Single element XML entries get inserted as an empty string.
 
-          */
 
-         if (*++scan != '>')
 
-             return NULL;
 
-         val->valType = OPARG_TYPE_NONE;
 
-     }
 
-     return scan+1;
 
- }
 
- /**
 
-  * We've found a closing '>' without a preceding '/', thus we must search
 
-  * the text for '<name/>' where "name" is the name of the XML element.
 
-  *
 
-  * @param[in]  name     the start of the name in the element header
 
-  * @param[in]  nm_len   the length of that name
 
-  * @param[out] len      the length of the value (string between header and
 
-  *                      the trailer/tail.
 
-  * @returns the character after the trailer, or NULL if not found.
 
-  */
 
- static char const *
 
- find_end_xml(char const * src, size_t nm_len, char const * val, size_t * len)
 
- {
 
-     char z[72] = "</";
 
-     char * dst = z + 2;
 
-     do  {
 
-         *(dst++) = *(src++);
 
-     } while (--nm_len > 0); /* nm_len is known to be 64 or less */
 
-     *(dst++) = '>';
 
-     *dst = NUL;
 
-     {
 
-         char const * res = strstr(val, z);
 
-         if (res != NULL) {
 
-             char const * end = (option_load_mode != OPTION_LOAD_KEEP)
 
-                 ? SPN_WHITESPACE_BACK(val, res)
 
-                 : res;
 
-             *len = (size_t)(end - val); /* includes trailing white space */
 
-             res =  SPN_WHITESPACE_CHARS(res + (dst - z));
 
-         }
 
-         return res;
 
-     }
 
- }
 
- /**
 
-  *  We've found a '<' character.  We ignore this if it is a comment or a
 
-  *  directive.  If it is something else, then whatever it is we are looking
 
-  *  at is bogus.  Returning NULL stops processing.
 
-  *
 
-  * @param[in]     xml_name  the name of an xml bracket (usually)
 
-  * @param[in,out] res_val   the option data derived from the XML element
 
-  *
 
-  * @returns the place to resume scanning input
 
-  */
 
- static char const *
 
- scan_xml(char const * xml_name, tOptionValue * res_val)
 
- {
 
-     size_t          nm_len, v_len;
 
-     char const *    scan;
 
-     char const *    val_str;
 
-     tOptionValue    valu;
 
-     tOptionLoadMode save_mode = option_load_mode;
 
-     if (! IS_VAR_FIRST_CHAR(*++xml_name))
 
-         return unnamed_xml(xml_name);
 
-     /*
 
-      * "scan_xml_name()" may change "option_load_mode".
 
-      */
 
-     val_str = scan_xml_name(xml_name, &nm_len, &valu);
 
-     if (val_str == NULL)
 
-         goto bail_scan_xml;
 
-     if (valu.valType == OPARG_TYPE_NONE)
 
-         scan = val_str;
 
-     else {
 
-         if (option_load_mode != OPTION_LOAD_KEEP)
 
-             val_str = SPN_WHITESPACE_CHARS(val_str);
 
-         scan = find_end_xml(xml_name, nm_len, val_str, &v_len);
 
-         if (scan == NULL)
 
-             goto bail_scan_xml;
 
-     }
 
-     /*
 
-      * "scan" now points to where the scan is to resume after returning.
 
-      * It either points after "/>" at the end of the XML element header,
 
-      * or it points after the "</name>" tail based on the name in the header.
 
-      */
 
-     switch (valu.valType) {
 
-     case OPARG_TYPE_NONE:
 
-         add_string(&(res_val->v.nestVal), xml_name, nm_len, NULL, 0);
 
-         break;
 
-     case OPARG_TYPE_STRING:
 
-     {
 
-         tOptionValue * new_val = add_string(
 
-             &(res_val->v.nestVal), xml_name, nm_len, val_str, v_len);
 
-         if (option_load_mode != OPTION_LOAD_KEEP)
 
-             munge_str(new_val->v.strVal, option_load_mode);
 
-         break;
 
-     }
 
-     case OPARG_TYPE_BOOLEAN:
 
-         add_bool(&(res_val->v.nestVal), xml_name, nm_len, val_str, v_len);
 
-         break;
 
-     case OPARG_TYPE_NUMERIC:
 
-         add_number(&(res_val->v.nestVal), xml_name, nm_len, val_str, v_len);
 
-         break;
 
-     case OPARG_TYPE_HIERARCHY:
 
-     {
 
-         char * pz = AGALOC(v_len+1, "h scan");
 
-         memcpy(pz, val_str, v_len);
 
-         pz[v_len] = NUL;
 
-         add_nested(&(res_val->v.nestVal), xml_name, nm_len, pz, v_len);
 
-         AGFREE(pz);
 
-         break;
 
-     }
 
-     case OPARG_TYPE_ENUMERATION:
 
-     case OPARG_TYPE_MEMBERSHIP:
 
-     default:
 
-         break;
 
-     }
 
-     option_load_mode = save_mode;
 
-     return scan;
 
- bail_scan_xml:
 
-     option_load_mode = save_mode;
 
-     return NULL;
 
- }
 
- /**
 
-  *  Deallocate a list of option arguments.  This must have been gotten from
 
-  *  a hierarchical option argument, not a stacked list of strings.  It is
 
-  *  an internal call, so it is not validated.  The caller is responsible for
 
-  *  knowing what they are doing.
 
-  */
 
- LOCAL void
 
- unload_arg_list(tArgList * arg_list)
 
- {
 
-     int ct = arg_list->useCt;
 
-     char const ** pnew_val = arg_list->apzArgs;
 
-     while (ct-- > 0) {
 
-         tOptionValue * new_val = (tOptionValue *)VOIDP(*(pnew_val++));
 
-         if (new_val->valType == OPARG_TYPE_HIERARCHY)
 
-             unload_arg_list(new_val->v.nestVal);
 
-         AGFREE(new_val);
 
-     }
 
-     AGFREE(arg_list);
 
- }
 
- /*=export_func  optionUnloadNested
 
-  *
 
-  * what:  Deallocate the memory for a nested value
 
-  * arg:   + tOptionValue const * + pOptVal + the hierarchical value +
 
-  *
 
-  * doc:
 
-  *  A nested value needs to be deallocated.  The pointer passed in should
 
-  *  have been gotten from a call to @code{configFileLoad()} (See
 
-  *  @pxref{libopts-configFileLoad}).
 
- =*/
 
- void
 
- optionUnloadNested(tOptionValue const * opt_val)
 
- {
 
-     if (opt_val == NULL) return;
 
-     if (opt_val->valType != OPARG_TYPE_HIERARCHY) {
 
-         errno = EINVAL;
 
-         return;
 
-     }
 
-     unload_arg_list(opt_val->v.nestVal);
 
-     AGFREE(opt_val);
 
- }
 
- /**
 
-  *  This is a _stable_ sort.  The entries are sorted alphabetically,
 
-  *  but within entries of the same name the ordering is unchanged.
 
-  *  Typically, we also hope the input is sorted.
 
-  */
 
- static void
 
- sort_list(tArgList * arg_list)
 
- {
 
-     int ix;
 
-     int lm = arg_list->useCt;
 
-     /*
 
-      *  This loop iterates "useCt" - 1 times.
 
-      */
 
-     for (ix = 0; ++ix < lm;) {
 
-         int iy = ix-1;
 
-         tOptionValue * new_v = C(tOptionValue *, arg_list->apzArgs[ix]);
 
-         tOptionValue * old_v = C(tOptionValue *, arg_list->apzArgs[iy]);
 
-         /*
 
-          *  For as long as the new entry precedes the "old" entry,
 
-          *  move the old pointer.  Stop before trying to extract the
 
-          *  "-1" entry.
 
-          */
 
-         while (strcmp(old_v->pzName, new_v->pzName) > 0) {
 
-             arg_list->apzArgs[iy+1] = VOIDP(old_v);
 
-             old_v = (tOptionValue *)VOIDP(arg_list->apzArgs[--iy]);
 
-             if (iy < 0)
 
-                 break;
 
-         }
 
-         /*
 
-          *  Always store the pointer.  Sometimes it is redundant,
 
-          *  but the redundancy is cheaper than a test and branch sequence.
 
-          */
 
-         arg_list->apzArgs[iy+1] = VOIDP(new_v);
 
-     }
 
- }
 
- /*=
 
-  * private:
 
-  *
 
-  * what:  parse a hierarchical option argument
 
-  * arg:   + char const * + pzTxt  + the text to scan      +
 
-  * arg:   + char const * + pzName + the name for the text +
 
-  * arg:   + size_t       + nm_len + the length of "name"  +
 
-  *
 
-  * ret_type:  tOptionValue *
 
-  * ret_desc:  An allocated, compound value structure
 
-  *
 
-  * doc:
 
-  *  A block of text represents a series of values.  It may be an
 
-  *  entire configuration file, or it may be an argument to an
 
-  *  option that takes a hierarchical value.
 
-  *
 
-  *  If NULL is returned, errno will be set:
 
-  *  @itemize @bullet
 
-  *  @item
 
-  *  @code{EINVAL} the input text was NULL.
 
-  *  @item
 
-  *  @code{ENOMEM} the storage structures could not be allocated
 
-  *  @item
 
-  *  @code{ENOMSG} no configuration values were found
 
-  *  @end itemize
 
- =*/
 
- LOCAL tOptionValue *
 
- optionLoadNested(char const * text, char const * name, size_t nm_len)
 
- {
 
-     tOptionValue * res_val;
 
-     /*
 
-      *  Make sure we have some data and we have space to put what we find.
 
-      */
 
-     if (text == NULL) {
 
-         errno = EINVAL;
 
-         return NULL;
 
-     }
 
-     text = SPN_WHITESPACE_CHARS(text);
 
-     if (*text == NUL) {
 
-         errno = ENOMSG;
 
-         return NULL;
 
-     }
 
-     res_val = AGALOC(sizeof(*res_val) + nm_len + 1, "nest args");
 
-     res_val->valType = OPARG_TYPE_HIERARCHY;
 
-     res_val->pzName  = (char *)(res_val + 1);
 
-     memcpy(res_val->pzName, name, nm_len);
 
-     res_val->pzName[nm_len] = NUL;
 
-     {
 
-         tArgList * arg_list = AGALOC(sizeof(*arg_list), "nest arg l");
 
-         res_val->v.nestVal = arg_list;
 
-         arg_list->useCt   = 0;
 
-         arg_list->allocCt = MIN_ARG_ALLOC_CT;
 
-     }
 
-     /*
 
-      *  Scan until we hit a NUL.
 
-      */
 
-     do  {
 
-         text = SPN_WHITESPACE_CHARS(text);
 
-         if (IS_VAR_FIRST_CHAR(*text))
 
-             text = scan_name(text, res_val);
 
-         else switch (*text) {
 
-         case NUL:
 
-             goto scan_done;
 
-         case '<':
 
-             text = scan_xml(text, res_val);
 
-             if (text == NULL)
 
-                 goto woops;
 
-             if (*text == ',')
 
-                 text++;
 
-             break;
 
-         case '#':
 
-             text = strchr(text, NL);
 
-             break;
 
-         default:
 
-             goto woops;
 
-         }
 
-     } while (text != NULL); scan_done:;
 
-     {
 
-         tArgList * al = res_val->v.nestVal;
 
-         if (al->useCt == 0) {
 
-             errno = ENOMSG;
 
-             goto woops;
 
-         }
 
-         if (al->useCt > 1)
 
-             sort_list(al);
 
-     }
 
-     return res_val;
 
-  woops:
 
-     AGFREE(res_val->v.nestVal);
 
-     AGFREE(res_val);
 
-     return NULL;
 
- }
 
- /*=export_func  optionNestedVal
 
-  * private:
 
-  *
 
-  * what:  parse a hierarchical option argument
 
-  * arg:   + tOptions * + opts + program options descriptor +
 
-  * arg:   + tOptDesc * + od   + the descriptor for this arg +
 
-  *
 
-  * doc:
 
-  *  Nested value was found on the command line
 
- =*/
 
- void
 
- optionNestedVal(tOptions * opts, tOptDesc * od)
 
- {
 
-     if (opts < OPTPROC_EMIT_LIMIT)
 
-         return;
 
-     if (od->fOptState & OPTST_RESET) {
 
-         tArgList *    arg_list = od->optCookie;
 
-         int           ct;
 
-         char const ** av;
 
-         if (arg_list == NULL)
 
-             return;
 
-         ct = arg_list->useCt;
 
-         av = arg_list->apzArgs;
 
-         while (--ct >= 0) {
 
-             void * p = VOIDP(*(av++));
 
-             optionUnloadNested((tOptionValue const *)p);
 
-         }
 
-         AGFREE(od->optCookie);
 
-     } else {
 
-         tOptionValue * opt_val = optionLoadNested(
 
-             od->optArg.argString, od->pz_Name, strlen(od->pz_Name));
 
-         if (opt_val != NULL)
 
-             addArgListEntry(&(od->optCookie), VOIDP(opt_val));
 
-     }
 
- }
 
- /**
 
-  * get_special_char
 
-  */
 
- LOCAL int
 
- get_special_char(char const ** ppz, int * ct)
 
- {
 
-     char const * pz = *ppz;
 
-     if (*ct < 3)
 
-         return '&';
 
-     if (*pz == '#') {
 
-         int base = 10;
 
-         int retch;
 
-         pz++;
 
-         if (*pz == 'x') {
 
-             base = 16;
 
-             pz++;
 
-         }
 
-         retch = (int)strtoul(pz, (char **)&pz, base);
 
-         if (*pz != ';')
 
-             return '&';
 
-         base = (int)(++pz - *ppz);
 
-         if (base > *ct)
 
-             return '&';
 
-         *ct -= base;
 
-         *ppz = pz;
 
-         return retch;
 
-     }
 
-     {
 
-         int ctr = sizeof(xml_xlate) / sizeof(xml_xlate[0]);
 
-         xml_xlate_t const * xlatp = xml_xlate;
 
-         for (;;) {
 
-             if (  (*ct >= xlatp->xml_len)
 
-                && (strncmp(pz, xlatp->xml_txt, (size_t)xlatp->xml_len) == 0)) {
 
-                 *ppz += xlatp->xml_len;
 
-                 *ct  -= xlatp->xml_len;
 
-                 return xlatp->xml_ch;
 
-             }
 
-             if (--ctr <= 0)
 
-                 break;
 
-             xlatp++;
 
-         }
 
-     }
 
-     return '&';
 
- }
 
- /**
 
-  * emit_special_char
 
-  */
 
- LOCAL void
 
- emit_special_char(FILE * fp, int ch)
 
- {
 
-     int ctr = sizeof(xml_xlate) / sizeof(xml_xlate[0]);
 
-     xml_xlate_t const * xlatp = xml_xlate;
 
-     putc('&', fp);
 
-     for (;;) {
 
-         if (ch == xlatp->xml_ch) {
 
-             fputs(xlatp->xml_txt, fp);
 
-             return;
 
-         }
 
-         if (--ctr <= 0)
 
-             break;
 
-         xlatp++;
 
-     }
 
-     fprintf(fp, XML_HEX_BYTE_FMT, (ch & 0xFF));
 
- }
 
- /** @}
 
-  *
 
-  * Local Variables:
 
-  * mode: C
 
-  * c-file-style: "stroustrup"
 
-  * indent-tabs-mode: nil
 
-  * End:
 
-  * end of autoopts/nested.c */
 
 
  |