nested.c revision 280849
1139969Simp
21556Srgrimes/**
31556Srgrimes * \file nested.c
41556Srgrimes *
51556Srgrimes *  Handle options with arguments that contain nested values.
61556Srgrimes *
71556Srgrimes * @addtogroup autoopts
81556Srgrimes * @{
91556Srgrimes */
101556Srgrimes/*
111556Srgrimes *   Automated Options Nested Values module.
121556Srgrimes *
131556Srgrimes *  This file is part of AutoOpts, a companion to AutoGen.
141556Srgrimes *  AutoOpts is free software.
151556Srgrimes *  AutoOpts is Copyright (C) 1992-2014 by Bruce Korb - all rights reserved
161556Srgrimes *
171556Srgrimes *  AutoOpts is available under any one of two licenses.  The license
181556Srgrimes *  in use must be one of these two and the choice is under the control
191556Srgrimes *  of the user of the license.
201556Srgrimes *
211556Srgrimes *   The GNU Lesser General Public License, version 3 or later
221556Srgrimes *      See the files "COPYING.lgplv3" and "COPYING.gplv3"
231556Srgrimes *
241556Srgrimes *   The Modified Berkeley Software Distribution License
251556Srgrimes *      See the file "COPYING.mbsd"
261556Srgrimes *
271556Srgrimes *  These files have the following sha256 sums:
281556Srgrimes *
291556Srgrimes *  8584710e9b04216a394078dc156b781d0b47e1729104d666658aecef8ee32e95  COPYING.gplv3
301556Srgrimes *  4379e7444a0e2ce2b12dd6f5a52a27a4d02d39d247901d3285c88cf0d37f477b  COPYING.lgplv3
311556Srgrimes *  13aa749a5b0a454917a944ed8fffc530b784f5ead522b1aacaf4ec8aa55a6239  COPYING.mbsd
321556Srgrimes */
3350471Speter
341556Srgrimestypedef struct {
35187734Strhodes    int     xml_ch;
361556Srgrimes    int     xml_len;
371556Srgrimes    char    xml_txt[8];
381556Srgrimes} xml_xlate_t;
391556Srgrimes
401556Srgrimesstatic xml_xlate_t const xml_xlate[] = {
411556Srgrimes    { '&', 4, "amp;"  },
4268935Sru    { '<', 3, "lt;"   },
4377342Sru    { '>', 3, "gt;"   },
4477342Sru    { '"', 5, "quot;" },
451556Srgrimes    { '\'',5, "apos;" }
4672432Sru};
471556Srgrimes
481556Srgrimes#ifndef ENOMSG
4935773Scharnier#define ENOMSG ENOENT
501556Srgrimes#endif
511556Srgrimes
521556Srgrimes/* = = = START-STATIC-FORWARD = = = */
531556Srgrimesstatic void
541556Srgrimesremove_continuation(char * src);
551556Srgrimes
56107230Srustatic char const*
57107230Sruscan_q_str(char const* pzTxt);
58107230Sru
59107230Srustatic tOptionValue *
60107230Sruadd_string(void ** pp, char const * name, size_t nm_len,
61107230Sru           char const * val, size_t d_len);
62107230Sru
631556Srgrimesstatic tOptionValue *
641556Srgrimesadd_bool(void ** pp, char const * name, size_t nm_len,
651556Srgrimes         char const * val, size_t d_len);
661556Srgrimes
6757274Sunfurlstatic tOptionValue*
6857274Sunfurladd_number(void ** pp, char const * name, size_t nm_len,
69107230Sru           char const * val, size_t d_len);
70107230Sru
71107230Srustatic tOptionValue*
721556Srgrimesadd_nested(void ** pp, char const * name, size_t nm_len,
731556Srgrimes           char * val, size_t d_len);
741556Srgrimes
751556Srgrimesstatic char const *
761556Srgrimesscan_name(char const * name, tOptionValue * res);
771556Srgrimes
781556Srgrimesstatic char const *
791556Srgrimesunnamed_xml(char const * txt);
8077160Sru
811556Srgrimesstatic char const *
821556Srgrimesscan_xml_name(char const * name, size_t * nm_len, tOptionValue * val);
831556Srgrimes
8453780Sobrienstatic char const *
8553780Sobrienfind_end_xml(char const * src, size_t nm_len, char const * val, size_t * len);
8653780Sobrien
87101569Srustatic char const *
88101569Sruscan_xml(char const * xml_name, tOptionValue * res_val);
89101297Sobrien
90101297Sobrienstatic void
91101297Sobriensort_list(tArgList * arg_list);
921556Srgrimes/* = = = END-STATIC-FORWARD = = = */
931556Srgrimes
941556Srgrimes/**
951556Srgrimes *  Backslashes are used for line continuations.  We keep the newline
961556Srgrimes *  characters, but trim out the backslash:
971556Srgrimes */
981556Srgrimesstatic void
991556Srgrimesremove_continuation(char * src)
1001556Srgrimes{
1011556Srgrimes    char* pzD;
1021556Srgrimes
1031556Srgrimes    do  {
1041556Srgrimes        while (*src == NL)  src++;
1051556Srgrimes        pzD = strchr(src, NL);
1061556Srgrimes        if (pzD == NULL)
107140353Sru            return;
10881687Sru
1091556Srgrimes        /*
1101556Srgrimes         *  pzD has skipped at least one non-newline character and now
11136175Sjkoshy         *  points to a newline character.  It now becomes the source and
11236175Sjkoshy         *  pzD goes to the previous character.
1131556Srgrimes         */
1141556Srgrimes        src = pzD--;
1151556Srgrimes        if (*pzD != '\\')
116107230Sru            pzD++;
117107230Sru    } while (pzD == src);
11836175Sjkoshy
119107230Sru    /*
12079754Sdd     *  Start shifting text.
12179754Sdd     */
12279754Sdd    for (;;) {
12331144Sjulian        char ch = ((*pzD++) = *(src++));
12479754Sdd        switch (ch) {
125107230Sru        case NUL:  return;
12636175Sjkoshy        case '\\':
12749721Schris            if (*src == NL)
1281556Srgrimes                --pzD; /* rewrite on next iteration */
129107230Sru        }
130107230Sru    }
13179754Sdd}
1321556Srgrimes
133107230Sru/**
13499847Skeramida *  Find the end of a quoted string, skipping escaped quote characters.
13599847Skeramida */
13699847Skeramidastatic char const*
137221845Spluknetscan_q_str(char const* pzTxt)
1381556Srgrimes{
13936175Sjkoshy    char q = *(pzTxt++); /* remember the type of quote */
1401556Srgrimes
14136175Sjkoshy    for (;;) {
1421556Srgrimes        char ch = *(pzTxt++);
143101569Sru        if (ch == NUL)
144101569Sru            return pzTxt-1;
14536175Sjkoshy
14636175Sjkoshy        if (ch == q)
14736175Sjkoshy            return pzTxt;
14836175Sjkoshy
14936175Sjkoshy        if (ch == '\\') {
15036175Sjkoshy            ch = *(pzTxt++);
151101569Sru            /*
152101569Sru             *  IF the next character is NUL, drop the backslash, too.
15336175Sjkoshy             */
15436175Sjkoshy            if (ch == NUL)
15536175Sjkoshy                return pzTxt - 2;
15636175Sjkoshy
15736175Sjkoshy            /*
15836175Sjkoshy             *  IF the quote character or the escape character were escaped,
159101569Sru             *  then skip both, as long as the string does not end.
160101569Sru             */
16136175Sjkoshy            if ((ch == q) || (ch == '\\')) {
1621556Srgrimes                if (*(pzTxt++) == NUL)
1631556Srgrimes                    return pzTxt-1;
16479754Sdd            }
16579754Sdd        }
166104318Strhodes    }
16736175Sjkoshy}
1681556Srgrimes
1691556Srgrimes
1701556Srgrimes/**
1711556Srgrimes *  Associate a name with either a string or no value.
17257274Sunfurl *
1731556Srgrimes * @param[in,out] pp        argument list to add to
1741556Srgrimes * @param[in]     name      the name of the "suboption"
1751556Srgrimes * @param[in]     nm_len    the length of the name
1761556Srgrimes * @param[in]     val       the string value for the suboption
1771556Srgrimes * @param[in]     d_len     the length of the value
1781556Srgrimes *
1791556Srgrimes * @returns the new value structure
1801556Srgrimes */
1811556Srgrimesstatic tOptionValue *
1821556Srgrimesadd_string(void ** pp, char const * name, size_t nm_len,
1831556Srgrimes           char const * val, size_t d_len)
1841556Srgrimes{
1851556Srgrimes    tOptionValue* pNV;
1861556Srgrimes    size_t sz = nm_len + d_len + sizeof(*pNV);
1871556Srgrimes
1881556Srgrimes    pNV = AGALOC(sz, "option name/str value pair");
1891556Srgrimes
1901556Srgrimes    if (val == NULL) {
1911556Srgrimes        pNV->valType = OPARG_TYPE_NONE;
1921556Srgrimes        pNV->pzName = pNV->v.strVal;
1931556Srgrimes
1941556Srgrimes    } else {
1951556Srgrimes        pNV->valType = OPARG_TYPE_STRING;
1961556Srgrimes        if (d_len > 0) {
1971556Srgrimes            char const * src = val;
1981556Srgrimes            char * pzDst = pNV->v.strVal;
1991556Srgrimes            int    ct    = (int)d_len;
2001556Srgrimes            do  {
2011556Srgrimes                int ch = *(src++) & 0xFF;
2021556Srgrimes                if (ch == NUL) goto data_copy_done;
2031556Srgrimes                if (ch == '&')
2041556Srgrimes                    ch = get_special_char(&src, &ct);
2051556Srgrimes                *(pzDst++) = (char)ch;
2061556Srgrimes            } while (--ct > 0);
2071556Srgrimes        data_copy_done:
2081556Srgrimes            *pzDst = NUL;
2091556Srgrimes
2101556Srgrimes        } else {
21190059Ssheldonh            pNV->v.strVal[0] = NUL;
2121556Srgrimes        }
21390059Ssheldonh
2141556Srgrimes        pNV->pzName = pNV->v.strVal + d_len + 1;
21590059Ssheldonh    }
2161556Srgrimes
2171556Srgrimes    memcpy(pNV->pzName, name, nm_len);
2181556Srgrimes    pNV->pzName[ nm_len ] = NUL;
2191556Srgrimes    addArgListEntry(pp, pNV);
2201556Srgrimes    return pNV;
2211556Srgrimes}
2221556Srgrimes
2231556Srgrimes/**
2241556Srgrimes *  Associate a name with a boolean value
2251556Srgrimes *
2261556Srgrimes * @param[in,out] pp        argument list to add to
2271556Srgrimes * @param[in]     name      the name of the "suboption"
2281556Srgrimes * @param[in]     nm_len    the length of the name
2291556Srgrimes * @param[in]     val       the boolean value for the suboption
2301556Srgrimes * @param[in]     d_len     the length of the value
231165463Sru *
232165463Sru * @returns the new value structure
2331556Srgrimes */
2341556Srgrimesstatic tOptionValue *
2351556Srgrimesadd_bool(void ** pp, char const * name, size_t nm_len,
2361556Srgrimes         char const * val, size_t d_len)
2371556Srgrimes{
2381556Srgrimes    size_t sz = nm_len + sizeof(tOptionValue) + 1;
2391556Srgrimes    tOptionValue * new_val = AGALOC(sz, "bool val");
2401556Srgrimes
2411556Srgrimes    /*
2421556Srgrimes     * Scan over whitespace is constrained by "d_len"
2431556Srgrimes     */
2441556Srgrimes    while (IS_WHITESPACE_CHAR(*val) && (d_len > 0)) {
2451556Srgrimes        d_len--; val++;
2461556Srgrimes    }
2471556Srgrimes
2481556Srgrimes    if (d_len == 0)
2491556Srgrimes        new_val->v.boolVal = 0;
2501556Srgrimes
2511556Srgrimes    else if (IS_DEC_DIGIT_CHAR(*val))
2521556Srgrimes        new_val->v.boolVal = (unsigned)atoi(val);
2531556Srgrimes
2541556Srgrimes    else new_val->v.boolVal = ! IS_FALSE_TYPE_CHAR(*val);
2551556Srgrimes
2561556Srgrimes    new_val->valType = OPARG_TYPE_BOOLEAN;
257165463Sru    new_val->pzName = (char*)(new_val + 1);
258165463Sru    memcpy(new_val->pzName, name, nm_len);
259165463Sru    new_val->pzName[ nm_len ] = NUL;
2601556Srgrimes    addArgListEntry(pp, new_val);
2611556Srgrimes    return new_val;
2621556Srgrimes}
2631556Srgrimes
2641556Srgrimes/**
2651556Srgrimes *  Associate a name with strtol() value, defaulting to zero.
2661556Srgrimes *
2671556Srgrimes * @param[in,out] pp        argument list to add to
2681556Srgrimes * @param[in]     name      the name of the "suboption"
2691556Srgrimes * @param[in]     nm_len    the length of the name
2701556Srgrimes * @param[in]     val       the numeric value for the suboption
2711556Srgrimes * @param[in]     d_len     the length of the value
2721556Srgrimes *
2731556Srgrimes * @returns the new value structure
2741556Srgrimes */
2751556Srgrimesstatic tOptionValue*
2761556Srgrimesadd_number(void ** pp, char const * name, size_t nm_len,
2771556Srgrimes           char const * val, size_t d_len)
2781556Srgrimes{
2791556Srgrimes    size_t sz = nm_len + sizeof(tOptionValue) + 1;
2801556Srgrimes    tOptionValue * new_val = AGALOC(sz, "int val");
2811556Srgrimes
2821556Srgrimes    /*
2831556Srgrimes     * Scan over whitespace is constrained by "d_len"
284187627Strhodes     */
285187627Strhodes    while (IS_WHITESPACE_CHAR(*val) && (d_len > 0)) {
286187627Strhodes        d_len--; val++;
287187627Strhodes    }
2881556Srgrimes    if (d_len == 0)
2891556Srgrimes        new_val->v.longVal = 0;
2901556Srgrimes    else
2911556Srgrimes        new_val->v.longVal = strtol(val, 0, 0);
2921556Srgrimes
2931556Srgrimes    new_val->valType = OPARG_TYPE_NUMERIC;
2941556Srgrimes    new_val->pzName  = (char*)(new_val + 1);
2951556Srgrimes    memcpy(new_val->pzName, name, nm_len);
2961556Srgrimes    new_val->pzName[ nm_len ] = NUL;
2971556Srgrimes    addArgListEntry(pp, new_val);
2981556Srgrimes    return new_val;
2991556Srgrimes}
3001556Srgrimes
3011556Srgrimes/**
3021556Srgrimes *  Associate a name with a nested/hierarchical value.
3031556Srgrimes *
3041556Srgrimes * @param[in,out] pp        argument list to add to
3051556Srgrimes * @param[in]     name      the name of the "suboption"
3061556Srgrimes * @param[in]     nm_len    the length of the name
3071556Srgrimes * @param[in]     val       the nested values for the suboption
3081556Srgrimes * @param[in]     d_len     the length of the value
3091556Srgrimes *
3101556Srgrimes * @returns the new value structure
3111556Srgrimes */
3121556Srgrimesstatic tOptionValue*
3131556Srgrimesadd_nested(void ** pp, char const * name, size_t nm_len,
3141556Srgrimes           char * val, size_t d_len)
31553780Sobrien{
31653780Sobrien    tOptionValue* new_val;
31753780Sobrien
31853780Sobrien    if (d_len == 0) {
3191556Srgrimes        size_t sz = nm_len + sizeof(*new_val) + 1;
32014105Sjoerg        new_val = AGALOC(sz, "empty nest");
3211556Srgrimes        new_val->v.nestVal = NULL;
322106399Stjr        new_val->valType = OPARG_TYPE_HIERARCHY;
3231556Srgrimes        new_val->pzName = (char*)(new_val + 1);
3241556Srgrimes        memcpy(new_val->pzName, name, nm_len);
3251556Srgrimes        new_val->pzName[ nm_len ] = NUL;
3261556Srgrimes
3271556Srgrimes    } else {
328221845Spluknet        new_val = optionLoadNested(val, name, nm_len);
3291556Srgrimes    }
33036175Sjkoshy
331221845Spluknet    if (new_val != NULL)
3321556Srgrimes        addArgListEntry(pp, new_val);
3331556Srgrimes
33435773Scharnier    return new_val;
33520411Ssteve}
33620411Ssteve
3371556Srgrimes/**
3381556Srgrimes *  We have an entry that starts with a name.  Find the end of it, cook it
33973281Sben *  (if called for) and create the name/value association.
3401556Srgrimes */
34173281Sbenstatic char const *
34217891Swoschscan_name(char const * name, tOptionValue * res)
34317891Swosch{
34417891Swosch    tOptionValue* new_val;
34517891Swosch    char const * pzScan = name+1; /* we know first char is a name char */
34617891Swosch    char const * pzVal;
347140353Sru    size_t       nm_len = 1;
348141851Sru    size_t       d_len = 0;
349140353Sru
350187734Strhodes    /*
351     *  Scan over characters that name a value.  These names may not end
352     *  with a colon, but they may contain colons.
353     */
354    pzScan = SPN_VALUE_NAME_CHARS(name + 1);
355    if (pzScan[-1] == ':')
356        pzScan--;
357    nm_len = (size_t)(pzScan - name);
358
359    pzScan = SPN_HORIZ_WHITE_CHARS(pzScan);
360
361 re_switch:
362
363    switch (*pzScan) {
364    case '=':
365    case ':':
366        pzScan = SPN_HORIZ_WHITE_CHARS(pzScan + 1);
367        if ((*pzScan == '=') || (*pzScan == ':'))
368            goto default_char;
369        goto re_switch;
370
371    case NL:
372    case ',':
373        pzScan++;
374        /* FALLTHROUGH */
375
376    case NUL:
377        add_string(&(res->v.nestVal), name, nm_len, NULL, (size_t)0);
378        break;
379
380    case '"':
381    case '\'':
382        pzVal = pzScan;
383        pzScan = scan_q_str(pzScan);
384        d_len = (size_t)(pzScan - pzVal);
385        new_val = add_string(&(res->v.nestVal), name, nm_len, pzVal,
386                         d_len);
387        if ((new_val != NULL) && (option_load_mode == OPTION_LOAD_COOKED))
388            ao_string_cook(new_val->v.strVal, NULL);
389        break;
390
391    default:
392    default_char:
393        /*
394         *  We have found some strange text value.  It ends with a newline
395         *  or a comma.
396         */
397        pzVal = pzScan;
398        for (;;) {
399            char ch = *(pzScan++);
400            switch (ch) {
401            case NUL:
402                pzScan--;
403                d_len = (size_t)(pzScan - pzVal);
404                goto string_done;
405                /* FALLTHROUGH */
406
407            case NL:
408                if (   (pzScan > pzVal + 2)
409                    && (pzScan[-2] == '\\')
410                    && (pzScan[ 0] != NUL))
411                    continue;
412                /* FALLTHROUGH */
413
414            case ',':
415                d_len = (size_t)(pzScan - pzVal) - 1;
416            string_done:
417                new_val = add_string(&(res->v.nestVal), name, nm_len,
418                                     pzVal, d_len);
419                if (new_val != NULL)
420                    remove_continuation(new_val->v.strVal);
421                goto leave_scan_name;
422            }
423        }
424        break;
425    } leave_scan_name:;
426
427    return pzScan;
428}
429
430/**
431 * Some xml element that does not start with a name.
432 * The next character must be either '!' (introducing a comment),
433 * or '?' (introducing an XML meta-marker of some sort).
434 * We ignore these and indicate an error (NULL result) otherwise.
435 *
436 * @param[in] txt  the text within an xml bracket
437 * @returns the address of the character after the closing marker, or NULL.
438 */
439static char const *
440unnamed_xml(char const * txt)
441{
442    switch (*txt) {
443    default:
444        txt = NULL;
445        break;
446
447    case '!':
448        txt = strstr(txt, "-->");
449        if (txt != NULL)
450            txt += 3;
451        break;
452
453    case '?':
454        txt = strchr(txt, '>');
455        if (txt != NULL)
456            txt++;
457        break;
458    }
459    return txt;
460}
461
462/**
463 *  Scan off the xml element name, and the rest of the header, too.
464 *  Set the value type to NONE if it ends with "/>".
465 *
466 * @param[in]  name    the first name character (alphabetic)
467 * @param[out] nm_len  the length of the name
468 * @param[out] val     set valType field to STRING or NONE.
469 *
470 * @returns the scan resumption point, or NULL on error
471 */
472static char const *
473scan_xml_name(char const * name, size_t * nm_len, tOptionValue * val)
474{
475    char const * scan = SPN_VALUE_NAME_CHARS(name + 1);
476    *nm_len = (size_t)(scan - name);
477    if (*nm_len > 64)
478        return NULL;
479    val->valType = OPARG_TYPE_STRING;
480
481    if (IS_WHITESPACE_CHAR(*scan)) {
482        /*
483         * There are attributes following the name.  Parse 'em.
484         */
485        scan = SPN_WHITESPACE_CHARS(scan);
486        scan = parse_attrs(NULL, scan, &option_load_mode, val);
487        if (scan == NULL)
488            return NULL; /* oops */
489    }
490
491    if (! IS_END_XML_TOKEN_CHAR(*scan))
492        return NULL; /* oops */
493
494    if (*scan == '/') {
495        /*
496         * Single element XML entries get inserted as an empty string.
497         */
498        if (*++scan != '>')
499            return NULL;
500        val->valType = OPARG_TYPE_NONE;
501    }
502    return scan+1;
503}
504
505/**
506 * We've found a closing '>' without a preceding '/', thus we must search
507 * the text for '<name/>' where "name" is the name of the XML element.
508 *
509 * @param[in]  name     the start of the name in the element header
510 * @param[in]  nm_len   the length of that name
511 * @param[out] len      the length of the value (string between header and
512 *                      the trailer/tail.
513 * @returns the character after the trailer, or NULL if not found.
514 */
515static char const *
516find_end_xml(char const * src, size_t nm_len, char const * val, size_t * len)
517{
518    char z[72] = "</";
519    char * dst = z + 2;
520
521    do  {
522        *(dst++) = *(src++);
523    } while (--nm_len > 0); /* nm_len is known to be 64 or less */
524    *(dst++) = '>';
525    *dst = NUL;
526
527    {
528        char const * res = strstr(val, z);
529
530        if (res != NULL) {
531            char const * end = (option_load_mode != OPTION_LOAD_KEEP)
532                ? SPN_WHITESPACE_BACK(val, res)
533                : res;
534            *len = (size_t)(end - val); /* includes trailing white space */
535            res =  SPN_WHITESPACE_CHARS(res + (dst - z));
536        }
537        return res;
538    }
539}
540
541/**
542 *  We've found a '<' character.  We ignore this if it is a comment or a
543 *  directive.  If it is something else, then whatever it is we are looking
544 *  at is bogus.  Returning NULL stops processing.
545 *
546 * @param[in]     xml_name  the name of an xml bracket (usually)
547 * @param[in,out] res_val   the option data derived from the XML element
548 *
549 * @returns the place to resume scanning input
550 */
551static char const *
552scan_xml(char const * xml_name, tOptionValue * res_val)
553{
554    size_t          nm_len, v_len;
555    char const *    scan;
556    char const *    val_str;
557    tOptionValue    valu;
558    tOptionLoadMode save_mode = option_load_mode;
559
560    if (! IS_VAR_FIRST_CHAR(*++xml_name))
561        return unnamed_xml(xml_name);
562
563    /*
564     * "scan_xml_name()" may change "option_load_mode".
565     */
566    val_str = scan_xml_name(xml_name, &nm_len, &valu);
567    if (val_str == NULL)
568        goto bail_scan_xml;
569
570    if (valu.valType == OPARG_TYPE_NONE)
571        scan = val_str;
572    else {
573        if (option_load_mode != OPTION_LOAD_KEEP)
574            val_str = SPN_WHITESPACE_CHARS(val_str);
575        scan = find_end_xml(xml_name, nm_len, val_str, &v_len);
576        if (scan == NULL)
577            goto bail_scan_xml;
578    }
579
580    /*
581     * "scan" now points to where the scan is to resume after returning.
582     * It either points after "/>" at the end of the XML element header,
583     * or it points after the "</name>" tail based on the name in the header.
584     */
585
586    switch (valu.valType) {
587    case OPARG_TYPE_NONE:
588        add_string(&(res_val->v.nestVal), xml_name, nm_len, NULL, 0);
589        break;
590
591    case OPARG_TYPE_STRING:
592    {
593        tOptionValue * new_val = add_string(
594            &(res_val->v.nestVal), xml_name, nm_len, val_str, v_len);
595
596        if (option_load_mode != OPTION_LOAD_KEEP)
597            munge_str(new_val->v.strVal, option_load_mode);
598
599        break;
600    }
601
602    case OPARG_TYPE_BOOLEAN:
603        add_bool(&(res_val->v.nestVal), xml_name, nm_len, val_str, v_len);
604        break;
605
606    case OPARG_TYPE_NUMERIC:
607        add_number(&(res_val->v.nestVal), xml_name, nm_len, val_str, v_len);
608        break;
609
610    case OPARG_TYPE_HIERARCHY:
611    {
612        char * pz = AGALOC(v_len+1, "h scan");
613        memcpy(pz, val_str, v_len);
614        pz[v_len] = NUL;
615        add_nested(&(res_val->v.nestVal), xml_name, nm_len, pz, v_len);
616        AGFREE(pz);
617        break;
618    }
619
620    case OPARG_TYPE_ENUMERATION:
621    case OPARG_TYPE_MEMBERSHIP:
622    default:
623        break;
624    }
625
626    option_load_mode = save_mode;
627    return scan;
628
629bail_scan_xml:
630    option_load_mode = save_mode;
631    return NULL;
632}
633
634
635/**
636 *  Deallocate a list of option arguments.  This must have been gotten from
637 *  a hierarchical option argument, not a stacked list of strings.  It is
638 *  an internal call, so it is not validated.  The caller is responsible for
639 *  knowing what they are doing.
640 */
641LOCAL void
642unload_arg_list(tArgList * arg_list)
643{
644    int ct = arg_list->useCt;
645    char const ** pnew_val = arg_list->apzArgs;
646
647    while (ct-- > 0) {
648        tOptionValue* new_val = (tOptionValue*)(void*)(intptr_t)*(pnew_val++);
649        if (new_val->valType == OPARG_TYPE_HIERARCHY)
650            unload_arg_list(new_val->v.nestVal);
651        AGFREE(new_val);
652    }
653
654    AGFREE((void*)arg_list);
655}
656
657/*=export_func  optionUnloadNested
658 *
659 * what:  Deallocate the memory for a nested value
660 * arg:   + tOptionValue const * + pOptVal + the hierarchical value +
661 *
662 * doc:
663 *  A nested value needs to be deallocated.  The pointer passed in should
664 *  have been gotten from a call to @code{configFileLoad()} (See
665 *  @pxref{libopts-configFileLoad}).
666=*/
667void
668optionUnloadNested(tOptionValue const * opt_val)
669{
670    if (opt_val == NULL) return;
671    if (opt_val->valType != OPARG_TYPE_HIERARCHY) {
672        errno = EINVAL;
673        return;
674    }
675
676    unload_arg_list(opt_val->v.nestVal);
677
678    AGFREE((void*)(intptr_t)opt_val);
679}
680
681/**
682 *  This is a _stable_ sort.  The entries are sorted alphabetically,
683 *  but within entries of the same name the ordering is unchanged.
684 *  Typically, we also hope the input is sorted.
685 */
686static void
687sort_list(tArgList * arg_list)
688{
689    int ix;
690    int lm = arg_list->useCt;
691
692    /*
693     *  This loop iterates "useCt" - 1 times.
694     */
695    for (ix = 0; ++ix < lm;) {
696        int iy = ix-1;
697        tOptionValue * new_v = C(tOptionValue *, (intptr_t)arg_list->apzArgs[ix]);
698        tOptionValue * old_v = C(tOptionValue *, (intptr_t)arg_list->apzArgs[iy]);
699
700        /*
701         *  For as long as the new entry precedes the "old" entry,
702         *  move the old pointer.  Stop before trying to extract the
703         *  "-1" entry.
704         */
705        while (strcmp(old_v->pzName, new_v->pzName) > 0) {
706            arg_list->apzArgs[iy+1] = (void*)old_v;
707            old_v = (tOptionValue*)(void*)(intptr_t)(arg_list->apzArgs[--iy]);
708            if (iy < 0)
709                break;
710        }
711
712        /*
713         *  Always store the pointer.  Sometimes it is redundant,
714         *  but the redundancy is cheaper than a test and branch sequence.
715         */
716        arg_list->apzArgs[iy+1] = (void*)new_v;
717    }
718}
719
720/*=
721 * private:
722 *
723 * what:  parse a hierarchical option argument
724 * arg:   + char const * + pzTxt  + the text to scan      +
725 * arg:   + char const * + pzName + the name for the text +
726 * arg:   + size_t       + nm_len + the length of "name"  +
727 *
728 * ret_type:  tOptionValue*
729 * ret_desc:  An allocated, compound value structure
730 *
731 * doc:
732 *  A block of text represents a series of values.  It may be an
733 *  entire configuration file, or it may be an argument to an
734 *  option that takes a hierarchical value.
735 *
736 *  If NULL is returned, errno will be set:
737 *  @itemize @bullet
738 *  @item
739 *  @code{EINVAL} the input text was NULL.
740 *  @item
741 *  @code{ENOMEM} the storage structures could not be allocated
742 *  @item
743 *  @code{ENOMSG} no configuration values were found
744 *  @end itemize
745=*/
746LOCAL tOptionValue *
747optionLoadNested(char const * text, char const * name, size_t nm_len)
748{
749    tOptionValue* res_val;
750
751    /*
752     *  Make sure we have some data and we have space to put what we find.
753     */
754    if (text == NULL) {
755        errno = EINVAL;
756        return NULL;
757    }
758    text = SPN_WHITESPACE_CHARS(text);
759    if (*text == NUL) {
760        errno = ENOMSG;
761        return NULL;
762    }
763    res_val = AGALOC(sizeof(*res_val) + nm_len + 1, "nest args");
764    res_val->valType = OPARG_TYPE_HIERARCHY;
765    res_val->pzName  = (char*)(res_val + 1);
766    memcpy(res_val->pzName, name, nm_len);
767    res_val->pzName[nm_len] = NUL;
768
769    {
770        tArgList * arg_list = AGALOC(sizeof(*arg_list), "nest arg l");
771
772        res_val->v.nestVal = arg_list;
773        arg_list->useCt   = 0;
774        arg_list->allocCt = MIN_ARG_ALLOC_CT;
775    }
776
777    /*
778     *  Scan until we hit a NUL.
779     */
780    do  {
781        text = SPN_WHITESPACE_CHARS(text);
782        if (IS_VAR_FIRST_CHAR(*text))
783            text = scan_name(text, res_val);
784
785        else switch (*text) {
786        case NUL: goto scan_done;
787        case '<': text = scan_xml(text, res_val);
788                  if (text == NULL) goto woops;
789                  if (*text == ',') text++; break;
790        case '#': text = strchr(text, NL);  break;
791        default:  goto woops;
792        }
793    } while (text != NULL); scan_done:;
794
795    {
796        tArgList * al = res_val->v.nestVal;
797        if (al->useCt == 0) {
798            errno = ENOMSG;
799            goto woops;
800        }
801        if (al->useCt > 1)
802            sort_list(al);
803    }
804
805    return res_val;
806
807 woops:
808    AGFREE(res_val->v.nestVal);
809    AGFREE(res_val);
810    return NULL;
811}
812
813/*=export_func  optionNestedVal
814 * private:
815 *
816 * what:  parse a hierarchical option argument
817 * arg:   + tOptions* + opts + program options descriptor +
818 * arg:   + tOptDesc* + od   + the descriptor for this arg +
819 *
820 * doc:
821 *  Nested value was found on the command line
822=*/
823void
824optionNestedVal(tOptions * opts, tOptDesc * od)
825{
826    if (opts < OPTPROC_EMIT_LIMIT)
827        return;
828
829    if (od->fOptState & OPTST_RESET) {
830        tArgList *    arg_list = od->optCookie;
831        int           ct;
832        char const ** av;
833
834        if (arg_list == NULL)
835            return;
836        ct = arg_list->useCt;
837        av = arg_list->apzArgs;
838
839        while (--ct >= 0) {
840            void * p = (void *)(intptr_t)*(av++);
841            optionUnloadNested((tOptionValue const *)p);
842        }
843
844        AGFREE(od->optCookie);
845
846    } else {
847        tOptionValue * opt_val = optionLoadNested(
848            od->optArg.argString, od->pz_Name, strlen(od->pz_Name));
849
850        if (opt_val != NULL)
851            addArgListEntry(&(od->optCookie), (void*)opt_val);
852    }
853}
854
855/**
856 * get_special_char
857 */
858LOCAL int
859get_special_char(char const ** ppz, int * ct)
860{
861    char const * pz = *ppz;
862
863    if (*ct < 3)
864        return '&';
865
866    if (*pz == '#') {
867        int base = 10;
868        int retch;
869
870        pz++;
871        if (*pz == 'x') {
872            base = 16;
873            pz++;
874        }
875        retch = (int)strtoul(pz, (char **)(intptr_t)&pz, base);
876        if (*pz != ';')
877            return '&';
878        base = (int)(++pz - *ppz);
879        if (base > *ct)
880            return '&';
881
882        *ct -= base;
883        *ppz = pz;
884        return retch;
885    }
886
887    {
888        int ctr = sizeof(xml_xlate) / sizeof(xml_xlate[0]);
889        xml_xlate_t const * xlatp = xml_xlate;
890
891        for (;;) {
892            if (  (*ct >= xlatp->xml_len)
893               && (strncmp(pz, xlatp->xml_txt, (size_t)xlatp->xml_len) == 0)) {
894                *ppz += xlatp->xml_len;
895                *ct  -= xlatp->xml_len;
896                return xlatp->xml_ch;
897            }
898
899            if (--ctr <= 0)
900                break;
901            xlatp++;
902        }
903    }
904    return '&';
905}
906
907/**
908 * emit_special_char
909 */
910LOCAL void
911emit_special_char(FILE * fp, int ch)
912{
913    int ctr = sizeof(xml_xlate) / sizeof(xml_xlate[0]);
914    xml_xlate_t const * xlatp = xml_xlate;
915
916    putc('&', fp);
917    for (;;) {
918        if (ch == xlatp->xml_ch) {
919            fputs(xlatp->xml_txt, fp);
920            return;
921        }
922        if (--ctr <= 0)
923            break;
924        xlatp++;
925    }
926    fprintf(fp, XML_HEX_BYTE_FMT, (ch & 0xFF));
927}
928
929/** @}
930 *
931 * Local Variables:
932 * mode: C
933 * c-file-style: "stroustrup"
934 * indent-tabs-mode: nil
935 * End:
936 * end of autoopts/nested.c */
937