nested.c revision 182007
1
2/*
3 *  $Id: nested.c,v 4.14 2007/02/04 17:44:12 bkorb Exp $
4 *  Time-stamp:      "2007-01-26 11:04:35 bkorb"
5 *
6 *   Automated Options Nested Values module.
7 */
8
9/*
10 *  Automated Options copyright 1992-2007 Bruce Korb
11 *
12 *  Automated Options is free software.
13 *  You may redistribute it and/or modify it under the terms of the
14 *  GNU General Public License, as published by the Free Software
15 *  Foundation; either version 2, or (at your option) any later version.
16 *
17 *  Automated Options is distributed in the hope that it will be useful,
18 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
19 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 *  GNU General Public License for more details.
21 *
22 *  You should have received a copy of the GNU General Public License
23 *  along with Automated Options.  See the file "COPYING".  If not,
24 *  write to:  The Free Software Foundation, Inc.,
25 *             51 Franklin Street, Fifth Floor,
26 *             Boston, MA  02110-1301, USA.
27 *
28 * As a special exception, Bruce Korb gives permission for additional
29 * uses of the text contained in his release of AutoOpts.
30 *
31 * The exception is that, if you link the AutoOpts library with other
32 * files to produce an executable, this does not by itself cause the
33 * resulting executable to be covered by the GNU General Public License.
34 * Your use of that executable is in no way restricted on account of
35 * linking the AutoOpts library code into it.
36 *
37 * This exception does not however invalidate any other reasons why
38 * the executable file might be covered by the GNU General Public License.
39 *
40 * This exception applies only to the code released by Bruce Korb under
41 * the name AutoOpts.  If you copy code from other sources under the
42 * General Public License into a copy of AutoOpts, as the General Public
43 * License permits, the exception does not apply to the code that you add
44 * in this way.  To avoid misleading anyone as to the status of such
45 * modified files, you must delete this exception notice from them.
46 *
47 * If you write modifications of your own for AutoOpts, it is your choice
48 * whether to permit this exception to apply to your modifications.
49 * If you do not wish that, delete this exception notice.
50 */
51/* = = = START-STATIC-FORWARD = = = */
52/* static forward declarations maintained by :mkfwd */
53static void
54removeBackslashes( char* pzSrc );
55
56static char const*
57scanQuotedString( char const* pzTxt );
58
59static tOptionValue*
60addStringValue( void** pp, char const* pzName, size_t nameLen,
61                char const* pzValue, size_t dataLen );
62
63static tOptionValue*
64addBoolValue( void** pp, char const* pzName, size_t nameLen,
65                char const* pzValue, size_t dataLen );
66
67static tOptionValue*
68addNumberValue( void** pp, char const* pzName, size_t nameLen,
69                char const* pzValue, size_t dataLen );
70
71static tOptionValue*
72addNestedValue( void** pp, char const* pzName, size_t nameLen,
73                char* pzValue, size_t dataLen );
74
75static char const*
76scanNameEntry(char const* pzName, tOptionValue* pRes);
77
78static char const*
79scanXmlEntry( char const* pzName, tOptionValue* pRes );
80
81static void
82unloadNestedArglist( tArgList* pAL );
83
84static void
85sortNestedList( tArgList* pAL );
86/* = = = END-STATIC-FORWARD = = = */
87
88/*  removeBackslashes
89 *
90 *  This function assumes that all newline characters were preceeded by
91 *  backslashes that need removal.
92 */
93static void
94removeBackslashes( char* pzSrc )
95{
96    char* pzD = strchr(pzSrc, '\n');
97
98    if (pzD == NULL)
99        return;
100    *--pzD = '\n';
101
102    for (;;) {
103        char ch = ((*pzD++) = *(pzSrc++));
104        switch (ch) {
105        case '\n': *--pzD = ch; break;
106        case NUL:  return;
107        default:
108            ;
109        }
110    }
111}
112
113
114/*  scanQuotedString
115 *
116 *  Find the end of a quoted string, skipping escaped quote characters.
117 */
118static char const*
119scanQuotedString( char const* pzTxt )
120{
121    char q = *(pzTxt++); /* remember the type of quote */
122
123    for (;;) {
124        char ch = *(pzTxt++);
125        if (ch == NUL)
126            return pzTxt-1;
127
128        if (ch == q)
129            return pzTxt;
130
131        if (ch == '\\') {
132            ch = *(pzTxt++);
133            /*
134             *  IF the next character is NUL, drop the backslash, too.
135             */
136            if (ch == NUL)
137                return pzTxt - 2;
138
139            /*
140             *  IF the quote character or the escape character were escaped,
141             *  then skip both, as long as the string does not end.
142             */
143            if ((ch == q) || (ch == '\\')) {
144                if (*(pzTxt++) == NUL)
145                    return pzTxt-1;
146            }
147        }
148    }
149}
150
151
152/*  addStringValue
153 *
154 *  Associate a name with either a string or no value.
155 */
156static tOptionValue*
157addStringValue( void** pp, char const* pzName, size_t nameLen,
158                char const* pzValue, size_t dataLen )
159{
160    tOptionValue* pNV;
161    size_t sz = nameLen + dataLen + sizeof(*pNV);
162
163    pNV = AGALOC( sz, "option name/str value pair" );
164    if (pNV == NULL)
165        return NULL;
166
167    if (pzValue == NULL) {
168        pNV->valType = OPARG_TYPE_NONE;
169        pNV->pzName = pNV->v.strVal;
170
171    } else {
172        pNV->valType = OPARG_TYPE_STRING;
173        if (dataLen > 0)
174            memcpy( pNV->v.strVal, pzValue, dataLen );
175        pNV->v.strVal[dataLen] = NUL;
176        pNV->pzName = pNV->v.strVal + dataLen + 1;
177    }
178
179    memcpy( pNV->pzName, pzName, nameLen );
180    pNV->pzName[ nameLen ] = NUL;
181    addArgListEntry( pp, pNV );
182    return pNV;
183}
184
185
186/*  addBoolValue
187 *
188 *  Associate a name with either a string or no value.
189 */
190static tOptionValue*
191addBoolValue( void** pp, char const* pzName, size_t nameLen,
192                char const* pzValue, size_t dataLen )
193{
194    tOptionValue* pNV;
195    size_t sz = nameLen + sizeof(*pNV) + 1;
196
197    pNV = AGALOC( sz, "option name/bool value pair" );
198    if (pNV == NULL)
199        return NULL;
200    while (isspace( (int)*pzValue ) && (dataLen > 0)) {
201        dataLen--; pzValue++;
202    }
203    if (dataLen == 0)
204        pNV->v.boolVal = 0;
205    else if (isdigit( (int)*pzValue ))
206        pNV->v.boolVal = atoi( pzValue );
207    else switch (*pzValue) {
208    case 'f':
209    case 'F':
210    case 'n':
211    case 'N':
212        pNV->v.boolVal = 0; break;
213    default:
214        pNV->v.boolVal = 1;
215    }
216
217    pNV->valType = OPARG_TYPE_BOOLEAN;
218    pNV->pzName = (char*)(pNV + 1);
219    memcpy( pNV->pzName, pzName, nameLen );
220    pNV->pzName[ nameLen ] = NUL;
221    addArgListEntry( pp, pNV );
222    return pNV;
223}
224
225
226/*  addNumberValue
227 *
228 *  Associate a name with either a string or no value.
229 */
230static tOptionValue*
231addNumberValue( void** pp, char const* pzName, size_t nameLen,
232                char const* pzValue, size_t dataLen )
233{
234    tOptionValue* pNV;
235    size_t sz = nameLen + sizeof(*pNV) + 1;
236
237    pNV = AGALOC( sz, "option name/bool value pair" );
238    if (pNV == NULL)
239        return NULL;
240    while (isspace( (int)*pzValue ) && (dataLen > 0)) {
241        dataLen--; pzValue++;
242    }
243    if (dataLen == 0)
244        pNV->v.boolVal = 0;
245    else
246        pNV->v.boolVal = atoi( pzValue );
247
248    pNV->valType = OPARG_TYPE_NUMERIC;
249    pNV->pzName = (char*)(pNV + 1);
250    memcpy( pNV->pzName, pzName, nameLen );
251    pNV->pzName[ nameLen ] = NUL;
252    addArgListEntry( pp, pNV );
253    return pNV;
254}
255
256
257/*  addNestedValue
258 *
259 *  Associate a name with either a string or no value.
260 */
261static tOptionValue*
262addNestedValue( void** pp, char const* pzName, size_t nameLen,
263                char* pzValue, size_t dataLen )
264{
265    tOptionValue* pNV;
266
267    if (dataLen == 0) {
268        size_t sz = nameLen + sizeof(*pNV) + 1;
269        pNV = AGALOC( sz, "empty nested value pair" );
270        if (pNV == NULL)
271            return NULL;
272        pNV->v.nestVal = NULL;
273        pNV->valType = OPARG_TYPE_HIERARCHY;
274        pNV->pzName = (char*)(pNV + 1);
275        memcpy( pNV->pzName, pzName, nameLen );
276        pNV->pzName[ nameLen ] = NUL;
277
278    } else {
279        pNV = optionLoadNested( pzValue, pzName, nameLen );
280    }
281
282    if (pNV != NULL)
283        addArgListEntry( pp, pNV );
284
285    return pNV;
286}
287
288
289/*  scanNameEntry
290 *
291 *  We have an entry that starts with a name.  Find the end of it, cook it
292 *  (if called for) and create the name/value association.
293 */
294static char const*
295scanNameEntry(char const* pzName, tOptionValue* pRes)
296{
297    tOptionValue* pNV;
298    char const * pzScan = pzName+1;
299    char const * pzVal;
300    size_t       nameLen = 1;
301    size_t       dataLen = 0;
302
303    while (ISNAMECHAR( (int)*pzScan ))  { pzScan++; nameLen++; }
304
305    while (isspace( (int)*pzScan )) {
306        char ch = *(pzScan++);
307        if ((ch == '\n') || (ch == ',')) {
308            addStringValue(&(pRes->v.nestVal), pzName, nameLen, NULL,(size_t)0);
309            return pzScan - 1;
310        }
311    }
312
313    switch (*pzScan) {
314    case '=':
315    case ':':
316        while (isspace( (int)*++pzScan ))  ;
317        switch (*pzScan) {
318        case ',':  goto comma_char;
319        case '"':
320        case '\'': goto quote_char;
321        case NUL:  goto nul_byte;
322        default:   goto default_char;
323        }
324
325    case ',':
326    comma_char:
327        pzScan++;
328        /* FALLTHROUGH */
329
330    case NUL:
331    nul_byte:
332        addStringValue(&(pRes->v.nestVal), pzName, nameLen, NULL, (size_t)0);
333        break;
334
335    case '"':
336    case '\'':
337    quote_char:
338        pzVal = pzScan;
339        pzScan = scanQuotedString( pzScan );
340        dataLen = pzScan - pzVal;
341        pNV = addStringValue( &(pRes->v.nestVal), pzName, nameLen, pzVal,
342                              dataLen );
343        if ((pNV != NULL) && (option_load_mode == OPTION_LOAD_COOKED))
344            ao_string_cook( pNV->v.strVal, NULL );
345        break;
346
347    default:
348    default_char:
349        /*
350         *  We have found some strange text value.  It ends with a newline
351         *  or a comma.
352         */
353        pzVal = pzScan;
354        for (;;) {
355            char ch = *(pzScan++);
356            switch (ch) {
357            case NUL:
358                pzScan--;
359                dataLen = pzScan - pzVal;
360                goto string_done;
361                /* FALLTHROUGH */
362
363            case '\n':
364                if (   (pzScan > pzVal + 2)
365                    && (pzScan[-2] == '\\')
366                    && (pzScan[ 0] != NUL))
367                    continue;
368                /* FALLTHROUGH */
369
370            case ',':
371                dataLen = (pzScan - pzVal) - 1;
372            string_done:
373                pNV = addStringValue( &(pRes->v.nestVal), pzName, nameLen,
374                                      pzVal, dataLen );
375                if (pNV != NULL)
376                    removeBackslashes( pNV->v.strVal );
377                goto leave_scan_name;
378            }
379        }
380        break;
381    } leave_scan_name:;
382
383    return pzScan;
384}
385
386
387/*  scanXmlEntry
388 *
389 *  We've found a '<' character.  We ignore this if it is a comment or a
390 *  directive.  If it is something else, then whatever it is we are looking
391 *  at is bogus.  Returning NULL stops processing.
392 */
393static char const*
394scanXmlEntry( char const* pzName, tOptionValue* pRes )
395{
396    size_t nameLen = 1, valLen = 0;
397    char const*   pzScan = ++pzName;
398    char const*   pzVal;
399    tOptionValue  valu;
400    tOptionValue* pNewVal;
401    tOptionLoadMode save_mode = option_load_mode;
402
403    if (! isalpha((int)*pzName)) {
404        switch (*pzName) {
405        default:
406            pzName = NULL;
407            break;
408
409        case '!':
410            pzName = strstr( pzName, "-->" );
411            if (pzName != NULL)
412                pzName += 3;
413            break;
414
415        case '?':
416            pzName = strchr( pzName, '>' );
417            if (pzName != NULL)
418                pzName++;
419            break;
420        }
421        return pzName;
422    }
423
424    while (isalpha( (int)*++pzScan ))  nameLen++;
425    if (nameLen > 64)
426        return NULL;
427    valu.valType = OPARG_TYPE_STRING;
428
429    switch (*pzScan) {
430    case ' ':
431    case '\t':
432        pzScan = parseAttributes(
433            NULL, (char*)pzScan, &option_load_mode, &valu );
434        if (*pzScan == '>') {
435            pzScan++;
436            break;
437        }
438
439        if (*pzScan != '/') {
440            option_load_mode = save_mode;
441            return NULL;
442        }
443        /* FALLTHROUGH */
444
445    case '/':
446        if (*++pzScan != '>') {
447            option_load_mode = save_mode;
448            return NULL;
449        }
450        addStringValue(&(pRes->v.nestVal), pzName, nameLen, NULL, (size_t)0);
451        option_load_mode = save_mode;
452        return pzScan+2;
453
454    default:
455        option_load_mode = save_mode;
456        return NULL;
457
458    case '>':
459        pzScan++;
460        break;
461    }
462
463    pzVal = pzScan;
464
465    {
466        char z[68];
467        char* pzD = z;
468        int  ct = nameLen;
469        char const* pzS = pzName;
470
471        *(pzD++) = '<';
472        *(pzD++) = '/';
473
474        do  {
475            *(pzD++) = *(pzS++);
476        } while (--ct > 0);
477        *(pzD++) = '>';
478        *pzD = NUL;
479
480        pzScan = strstr( pzScan, z );
481        if (pzScan == NULL) {
482            option_load_mode = save_mode;
483            return NULL;
484        }
485        valLen = (pzScan - pzVal);
486        pzScan += nameLen + 3;
487        while (isspace(  (int)*pzScan ))  pzScan++;
488    }
489
490    switch (valu.valType) {
491    case OPARG_TYPE_NONE:
492        addStringValue( &(pRes->v.nestVal), pzName, nameLen, NULL, (size_t)0);
493        break;
494
495    case OPARG_TYPE_STRING:
496        pNewVal = addStringValue(
497            &(pRes->v.nestVal), pzName, nameLen, pzVal, valLen);
498
499        if (option_load_mode == OPTION_LOAD_KEEP)
500            break;
501        mungeString( pNewVal->v.strVal, option_load_mode );
502        break;
503
504    case OPARG_TYPE_BOOLEAN:
505        addBoolValue( &(pRes->v.nestVal), pzName, nameLen, pzVal, valLen );
506        break;
507
508    case OPARG_TYPE_NUMERIC:
509        addNumberValue( &(pRes->v.nestVal), pzName, nameLen, pzVal, valLen );
510        break;
511
512    case OPARG_TYPE_HIERARCHY:
513    {
514        char* pz = AGALOC( valLen+1, "hierarchical scan" );
515        if (pz == NULL)
516            break;
517        memcpy( pz, pzVal, valLen );
518        pz[valLen] = NUL;
519        addNestedValue( &(pRes->v.nestVal), pzName, nameLen, pz, valLen );
520        AGFREE(pz);
521        break;
522    }
523
524    case OPARG_TYPE_ENUMERATION:
525    case OPARG_TYPE_MEMBERSHIP:
526    default:
527        break;
528    }
529
530    option_load_mode = save_mode;
531    return pzScan;
532}
533
534
535/*  unloadNestedArglist
536 *
537 *  Deallocate a list of option arguments.  This must have been gotten from
538 *  a hierarchical option argument, not a stacked list of strings.  It is
539 *  an internal call, so it is not validated.  The caller is responsible for
540 *  knowing what they are doing.
541 */
542static void
543unloadNestedArglist( tArgList* pAL )
544{
545    int ct = pAL->useCt;
546    tCC** ppNV = pAL->apzArgs;
547
548    while (ct-- > 0) {
549        tOptionValue* pNV = (tOptionValue*)(void*)*(ppNV++);
550        if (pNV->valType == OPARG_TYPE_HIERARCHY)
551            unloadNestedArglist( pNV->v.nestVal );
552        AGFREE( pNV );
553    }
554
555    AGFREE( (void*)pAL );
556}
557
558
559/*=export_func  optionUnloadNested
560 *
561 * what:  Deallocate the memory for a nested value
562 * arg:   + tOptionValue const * + pOptVal + the hierarchical value +
563 *
564 * doc:
565 *  A nested value needs to be deallocated.  The pointer passed in should
566 *  have been gotten from a call to @code{configFileLoad()} (See
567 *  @pxref{libopts-configFileLoad}).
568=*/
569void
570optionUnloadNested( tOptionValue const * pOV )
571{
572    if (pOV == NULL) return;
573    if (pOV->valType != OPARG_TYPE_HIERARCHY) {
574        errno = EINVAL;
575        return;
576    }
577
578    unloadNestedArglist( pOV->v.nestVal );
579
580    AGFREE( (void*)pOV );
581}
582
583
584/*  sortNestedList
585 *
586 *  This is a _stable_ sort.  The entries are sorted alphabetically,
587 *  but within entries of the same name the ordering is unchanged.
588 *  Typically, we also hope the input is sorted.
589 */
590static void
591sortNestedList( tArgList* pAL )
592{
593    int ix;
594    int lm = pAL->useCt;
595
596    /*
597     *  This loop iterates "useCt" - 1 times.
598     */
599    for (ix = 0; ++ix < lm;) {
600        int iy = ix-1;
601        tOptionValue* pNewNV = (tOptionValue*)(void*)(pAL->apzArgs[ix]);
602        tOptionValue* pOldNV = (tOptionValue*)(void*)(pAL->apzArgs[iy]);
603
604        /*
605         *  For as long as the new entry precedes the "old" entry,
606         *  move the old pointer.  Stop before trying to extract the
607         *  "-1" entry.
608         */
609        while (strcmp( pOldNV->pzName, pNewNV->pzName ) > 0) {
610            pAL->apzArgs[iy+1] = (void*)pOldNV;
611            pOldNV = (tOptionValue*)(void*)(pAL->apzArgs[--iy]);
612            if (iy < 0)
613                break;
614        }
615
616        /*
617         *  Always store the pointer.  Sometimes it is redundant,
618         *  but the redundancy is cheaper than a test and branch sequence.
619         */
620        pAL->apzArgs[iy+1] = (void*)pNewNV;
621    }
622}
623
624
625/* optionLoadNested
626 * private:
627 *
628 * what:  parse a hierarchical option argument
629 * arg:   + char const*     + pzTxt   + the text to scan +
630 * arg:   + char const*     + pzName  + the name for the text +
631 * arg:   + size_t          + nameLen + the length of "name"  +
632 *
633 * ret_type:  tOptionValue*
634 * ret_desc:  An allocated, compound value structure
635 *
636 * doc:
637 *  A block of text represents a series of values.  It may be an
638 *  entire configuration file, or it may be an argument to an
639 *  option that takes a hierarchical value.
640 */
641LOCAL tOptionValue*
642optionLoadNested(char const* pzTxt, char const* pzName, size_t nameLen)
643{
644    tOptionValue* pRes;
645    tArgList*     pAL;
646
647    /*
648     *  Make sure we have some data and we have space to put what we find.
649     */
650    if (pzTxt == NULL) {
651        errno = EINVAL;
652        return NULL;
653    }
654    while (isspace( (int)*pzTxt ))  pzTxt++;
655    if (*pzTxt == NUL) {
656        errno = ENOENT;
657        return NULL;
658    }
659    pRes = AGALOC( sizeof(*pRes) + nameLen + 1, "nested args" );
660    if (pRes == NULL) {
661        errno = ENOMEM;
662        return NULL;
663    }
664    pRes->valType   = OPARG_TYPE_HIERARCHY;
665    pRes->pzName    = (char*)(pRes + 1);
666    memcpy( pRes->pzName, pzName, nameLen );
667    pRes->pzName[ nameLen ] = NUL;
668
669    pAL = AGALOC( sizeof(*pAL), "nested arg list" );
670    if (pAL == NULL) {
671        AGFREE( pRes );
672        return NULL;
673    }
674    pRes->v.nestVal = pAL;
675    pAL->useCt   = 0;
676    pAL->allocCt = MIN_ARG_ALLOC_CT;
677
678    /*
679     *  Scan until we hit a NUL.
680     */
681    do  {
682        while (isspace( (int)*pzTxt ))  pzTxt++;
683        if (isalpha( (int)*pzTxt )) {
684            pzTxt = scanNameEntry( pzTxt, pRes );
685        }
686        else switch (*pzTxt) {
687        case NUL: goto scan_done;
688        case '<': pzTxt = scanXmlEntry( pzTxt, pRes );
689                  if (*pzTxt == ',') pzTxt++;     break;
690        case '#': pzTxt = strchr( pzTxt, '\n' );  break;
691        default:  goto woops;
692        }
693    } while (pzTxt != NULL); scan_done:;
694
695    pAL = pRes->v.nestVal;
696    if (pAL->useCt != 0) {
697        sortNestedList( pAL );
698        return pRes;
699    }
700
701 woops:
702    AGFREE( pRes->v.nestVal );
703    AGFREE( pRes );
704    return NULL;
705}
706
707
708/*=export_func  optionNestedVal
709 * private:
710 *
711 * what:  parse a hierarchical option argument
712 * arg:   + tOptions* + pOpts    + program options descriptor +
713 * arg:   + tOptDesc* + pOptDesc + the descriptor for this arg +
714 *
715 * doc:
716 *  Nested value was found on the command line
717=*/
718void
719optionNestedVal( tOptions* pOpts, tOptDesc* pOD )
720{
721    tOptionValue* pOV = optionLoadNested(
722        pOD->optArg.argString, pOD->pz_Name, strlen(pOD->pz_Name));
723
724    if (pOV != NULL)
725        addArgListEntry( &(pOD->optCookie), (void*)pOV );
726}
727/*
728 * Local Variables:
729 * mode: C
730 * c-file-style: "stroustrup"
731 * indent-tabs-mode: nil
732 * End:
733 * end of autoopts/nested.c */
734