| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733 | 
/* *  $Id: nested.c,v 4.14 2007/02/04 17:44:12 bkorb Exp $ *  Time-stamp:      "2007-01-26 11:04:35 bkorb" * *   Automated Options Nested Values module. *//* *  Automated Options copyright 1992-2007 Bruce Korb * *  Automated Options is free software. *  You may redistribute it and/or modify it under the terms of the *  GNU General Public License, as published by the Free Software *  Foundation; either version 2, or (at your option) any later version. * *  Automated Options is distributed in the hope that it will be useful, *  but WITHOUT ANY WARRANTY; without even the implied warranty of *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the *  GNU General Public License for more details. * *  You should have received a copy of the GNU General Public License *  along with Automated Options.  See the file "COPYING".  If not, *  write to:  The Free Software Foundation, Inc., *             51 Franklin Street, Fifth Floor, *             Boston, MA  02110-1301, USA. * * As a special exception, Bruce Korb gives permission for additional * uses of the text contained in his release of AutoOpts. * * The exception is that, if you link the AutoOpts library with other * files to produce an executable, this does not by itself cause the * resulting executable to be covered by the GNU General Public License. * Your use of that executable is in no way restricted on account of * linking the AutoOpts library code into it. * * This exception does not however invalidate any other reasons why * the executable file might be covered by the GNU General Public License. * * This exception applies only to the code released by Bruce Korb under * the name AutoOpts.  If you copy code from other sources under the * General Public License into a copy of AutoOpts, as the General Public * License permits, the exception does not apply to the code that you add * in this way.  To avoid misleading anyone as to the status of such * modified files, you must delete this exception notice from them. * * If you write modifications of your own for AutoOpts, it is your choice * whether to permit this exception to apply to your modifications. * If you do not wish that, delete this exception notice. *//* = = = START-STATIC-FORWARD = = = *//* static forward declarations maintained by :mkfwd */static voidremoveBackslashes( char* pzSrc );static char const*scanQuotedString( char const* pzTxt );static tOptionValue*addStringValue( void** pp, char const* pzName, size_t nameLen,                char const* pzValue, size_t dataLen );static tOptionValue*addBoolValue( void** pp, char const* pzName, size_t nameLen,                char const* pzValue, size_t dataLen );static tOptionValue*addNumberValue( void** pp, char const* pzName, size_t nameLen,                char const* pzValue, size_t dataLen );static tOptionValue*addNestedValue( void** pp, char const* pzName, size_t nameLen,                char* pzValue, size_t dataLen );static char const*scanNameEntry(char const* pzName, tOptionValue* pRes);static char const*scanXmlEntry( char const* pzName, tOptionValue* pRes );static voidunloadNestedArglist( tArgList* pAL );static voidsortNestedList( tArgList* pAL );/* = = = END-STATIC-FORWARD = = = *//*  removeBackslashes * *  This function assumes that all newline characters were preceeded by *  backslashes that need removal. */static voidremoveBackslashes( char* pzSrc ){    char* pzD = strchr(pzSrc, '\n');    if (pzD == NULL)        return;    *--pzD = '\n';    for (;;) {        char ch = ((*pzD++) = *(pzSrc++));        switch (ch) {        case '\n': *--pzD = ch; break;        case NUL:  return;        default:            ;        }    }}/*  scanQuotedString * *  Find the end of a quoted string, skipping escaped quote characters. */static char const*scanQuotedString( 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;            }        }    }}/*  addStringValue * *  Associate a name with either a string or no value. */static tOptionValue*addStringValue( void** pp, char const* pzName, size_t nameLen,                char const* pzValue, size_t dataLen ){    tOptionValue* pNV;    size_t sz = nameLen + dataLen + sizeof(*pNV);    pNV = AGALOC( sz, "option name/str value pair" );    if (pNV == NULL)        return NULL;    if (pzValue == NULL) {        pNV->valType = OPARG_TYPE_NONE;        pNV->pzName = pNV->v.strVal;    } else {        pNV->valType = OPARG_TYPE_STRING;        if (dataLen > 0)            memcpy( pNV->v.strVal, pzValue, dataLen );        pNV->v.strVal[dataLen] = NUL;        pNV->pzName = pNV->v.strVal + dataLen + 1;    }    memcpy( pNV->pzName, pzName, nameLen );    pNV->pzName[ nameLen ] = NUL;    addArgListEntry( pp, pNV );    return pNV;}/*  addBoolValue * *  Associate a name with either a string or no value. */static tOptionValue*addBoolValue( void** pp, char const* pzName, size_t nameLen,                char const* pzValue, size_t dataLen ){    tOptionValue* pNV;    size_t sz = nameLen + sizeof(*pNV) + 1;    pNV = AGALOC( sz, "option name/bool value pair" );    if (pNV == NULL)        return NULL;    while (isspace( (int)*pzValue ) && (dataLen > 0)) {        dataLen--; pzValue++;    }    if (dataLen == 0)        pNV->v.boolVal = 0;    else if (isdigit( (int)*pzValue ))        pNV->v.boolVal = atoi( pzValue );    else switch (*pzValue) {    case 'f':    case 'F':    case 'n':    case 'N':        pNV->v.boolVal = 0; break;    default:        pNV->v.boolVal = 1;    }    pNV->valType = OPARG_TYPE_BOOLEAN;    pNV->pzName = (char*)(pNV + 1);    memcpy( pNV->pzName, pzName, nameLen );    pNV->pzName[ nameLen ] = NUL;    addArgListEntry( pp, pNV );    return pNV;}/*  addNumberValue * *  Associate a name with either a string or no value. */static tOptionValue*addNumberValue( void** pp, char const* pzName, size_t nameLen,                char const* pzValue, size_t dataLen ){    tOptionValue* pNV;    size_t sz = nameLen + sizeof(*pNV) + 1;    pNV = AGALOC( sz, "option name/bool value pair" );    if (pNV == NULL)        return NULL;    while (isspace( (int)*pzValue ) && (dataLen > 0)) {        dataLen--; pzValue++;    }    if (dataLen == 0)        pNV->v.boolVal = 0;    else        pNV->v.boolVal = atoi( pzValue );    pNV->valType = OPARG_TYPE_NUMERIC;    pNV->pzName = (char*)(pNV + 1);    memcpy( pNV->pzName, pzName, nameLen );    pNV->pzName[ nameLen ] = NUL;    addArgListEntry( pp, pNV );    return pNV;}/*  addNestedValue * *  Associate a name with either a string or no value. */static tOptionValue*addNestedValue( void** pp, char const* pzName, size_t nameLen,                char* pzValue, size_t dataLen ){    tOptionValue* pNV;    if (dataLen == 0) {        size_t sz = nameLen + sizeof(*pNV) + 1;        pNV = AGALOC( sz, "empty nested value pair" );        if (pNV == NULL)            return NULL;        pNV->v.nestVal = NULL;        pNV->valType = OPARG_TYPE_HIERARCHY;        pNV->pzName = (char*)(pNV + 1);        memcpy( pNV->pzName, pzName, nameLen );        pNV->pzName[ nameLen ] = NUL;    } else {        pNV = optionLoadNested( pzValue, pzName, nameLen );    }    if (pNV != NULL)        addArgListEntry( pp, pNV );    return pNV;}/*  scanNameEntry * *  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*scanNameEntry(char const* pzName, tOptionValue* pRes){    tOptionValue* pNV;    char const * pzScan = pzName+1;    char const * pzVal;    size_t       nameLen = 1;    size_t       dataLen = 0;    while (ISNAMECHAR( (int)*pzScan ))  { pzScan++; nameLen++; }    while (isspace( (int)*pzScan )) {        char ch = *(pzScan++);        if ((ch == '\n') || (ch == ',')) {            addStringValue(&(pRes->v.nestVal), pzName, nameLen, NULL,(size_t)0);            return pzScan - 1;        }    }    switch (*pzScan) {    case '=':    case ':':        while (isspace( (int)*++pzScan ))  ;        switch (*pzScan) {        case ',':  goto comma_char;        case '"':        case '\'': goto quote_char;        case NUL:  goto nul_byte;        default:   goto default_char;        }    case ',':    comma_char:        pzScan++;        /* FALLTHROUGH */    case NUL:    nul_byte:        addStringValue(&(pRes->v.nestVal), pzName, nameLen, NULL, (size_t)0);        break;    case '"':    case '\'':    quote_char:        pzVal = pzScan;        pzScan = scanQuotedString( pzScan );        dataLen = pzScan - pzVal;        pNV = addStringValue( &(pRes->v.nestVal), pzName, nameLen, pzVal,                              dataLen );        if ((pNV != NULL) && (option_load_mode == OPTION_LOAD_COOKED))            ao_string_cook( pNV->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--;                dataLen = pzScan - pzVal;                goto string_done;                /* FALLTHROUGH */            case '\n':                if (   (pzScan > pzVal + 2)                    && (pzScan[-2] == '\\')                    && (pzScan[ 0] != NUL))                    continue;                /* FALLTHROUGH */            case ',':                dataLen = (pzScan - pzVal) - 1;            string_done:                pNV = addStringValue( &(pRes->v.nestVal), pzName, nameLen,                                      pzVal, dataLen );                if (pNV != NULL)                    removeBackslashes( pNV->v.strVal );                goto leave_scan_name;            }        }        break;    } leave_scan_name:;    return pzScan;}/*  scanXmlEntry * *  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. */static char const*scanXmlEntry( char const* pzName, tOptionValue* pRes ){    size_t nameLen = 1, valLen = 0;    char const*   pzScan = ++pzName;    char const*   pzVal;    tOptionValue  valu;    tOptionValue* pNewVal;    tOptionLoadMode save_mode = option_load_mode;    if (! isalpha((int)*pzName)) {        switch (*pzName) {        default:            pzName = NULL;            break;        case '!':            pzName = strstr( pzName, "-->" );            if (pzName != NULL)                pzName += 3;            break;        case '?':            pzName = strchr( pzName, '>' );            if (pzName != NULL)                pzName++;            break;        }        return pzName;    }    while (isalpha( (int)*++pzScan ))  nameLen++;    if (nameLen > 64)        return NULL;    valu.valType = OPARG_TYPE_STRING;    switch (*pzScan) {    case ' ':    case '\t':        pzScan = parseAttributes(            NULL, (char*)pzScan, &option_load_mode, &valu );        if (*pzScan == '>') {            pzScan++;            break;        }        if (*pzScan != '/') {            option_load_mode = save_mode;            return NULL;        }        /* FALLTHROUGH */    case '/':        if (*++pzScan != '>') {            option_load_mode = save_mode;            return NULL;        }        addStringValue(&(pRes->v.nestVal), pzName, nameLen, NULL, (size_t)0);        option_load_mode = save_mode;        return pzScan+2;    default:        option_load_mode = save_mode;        return NULL;    case '>':        pzScan++;        break;    }    pzVal = pzScan;    {        char z[68];        char* pzD = z;        int  ct = nameLen;        char const* pzS = pzName;        *(pzD++) = '<';        *(pzD++) = '/';        do  {            *(pzD++) = *(pzS++);        } while (--ct > 0);        *(pzD++) = '>';        *pzD = NUL;        pzScan = strstr( pzScan, z );        if (pzScan == NULL) {            option_load_mode = save_mode;            return NULL;        }        valLen = (pzScan - pzVal);        pzScan += nameLen + 3;        while (isspace(  (int)*pzScan ))  pzScan++;    }    switch (valu.valType) {    case OPARG_TYPE_NONE:        addStringValue( &(pRes->v.nestVal), pzName, nameLen, NULL, (size_t)0);        break;    case OPARG_TYPE_STRING:        pNewVal = addStringValue(            &(pRes->v.nestVal), pzName, nameLen, pzVal, valLen);        if (option_load_mode == OPTION_LOAD_KEEP)            break;        mungeString( pNewVal->v.strVal, option_load_mode );        break;    case OPARG_TYPE_BOOLEAN:        addBoolValue( &(pRes->v.nestVal), pzName, nameLen, pzVal, valLen );        break;    case OPARG_TYPE_NUMERIC:        addNumberValue( &(pRes->v.nestVal), pzName, nameLen, pzVal, valLen );        break;    case OPARG_TYPE_HIERARCHY:    {        char* pz = AGALOC( valLen+1, "hierarchical scan" );        if (pz == NULL)            break;        memcpy( pz, pzVal, valLen );        pz[valLen] = NUL;        addNestedValue( &(pRes->v.nestVal), pzName, nameLen, pz, valLen );        AGFREE(pz);        break;    }    case OPARG_TYPE_ENUMERATION:    case OPARG_TYPE_MEMBERSHIP:    default:        break;    }    option_load_mode = save_mode;    return pzScan;}/*  unloadNestedArglist * *  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. */static voidunloadNestedArglist( tArgList* pAL ){    int ct = pAL->useCt;    tCC** ppNV = pAL->apzArgs;    while (ct-- > 0) {        tOptionValue* pNV = (tOptionValue*)(void*)*(ppNV++);        if (pNV->valType == OPARG_TYPE_HIERARCHY)            unloadNestedArglist( pNV->v.nestVal );        AGFREE( pNV );    }    AGFREE( (void*)pAL );}/*=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}).=*/voidoptionUnloadNested( tOptionValue const * pOV ){    if (pOV == NULL) return;    if (pOV->valType != OPARG_TYPE_HIERARCHY) {        errno = EINVAL;        return;    }    unloadNestedArglist( pOV->v.nestVal );    AGFREE( (void*)pOV );}/*  sortNestedList * *  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 voidsortNestedList( tArgList* pAL ){    int ix;    int lm = pAL->useCt;    /*     *  This loop iterates "useCt" - 1 times.     */    for (ix = 0; ++ix < lm;) {        int iy = ix-1;        tOptionValue* pNewNV = (tOptionValue*)(void*)(pAL->apzArgs[ix]);        tOptionValue* pOldNV = (tOptionValue*)(void*)(pAL->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( pOldNV->pzName, pNewNV->pzName ) > 0) {            pAL->apzArgs[iy+1] = (void*)pOldNV;            pOldNV = (tOptionValue*)(void*)(pAL->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.         */        pAL->apzArgs[iy+1] = (void*)pNewNV;    }}/* optionLoadNested * 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          + nameLen + 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. */LOCAL tOptionValue*optionLoadNested(char const* pzTxt, char const* pzName, size_t nameLen){    tOptionValue* pRes;    tArgList*     pAL;    /*     *  Make sure we have some data and we have space to put what we find.     */    if (pzTxt == NULL) {        errno = EINVAL;        return NULL;    }    while (isspace( (int)*pzTxt ))  pzTxt++;    if (*pzTxt == NUL) {        errno = ENOENT;        return NULL;    }    pRes = AGALOC( sizeof(*pRes) + nameLen + 1, "nested args" );    if (pRes == NULL) {        errno = ENOMEM;        return NULL;    }    pRes->valType   = OPARG_TYPE_HIERARCHY;    pRes->pzName    = (char*)(pRes + 1);    memcpy( pRes->pzName, pzName, nameLen );    pRes->pzName[ nameLen ] = NUL;    pAL = AGALOC( sizeof(*pAL), "nested arg list" );    if (pAL == NULL) {        AGFREE( pRes );        return NULL;    }    pRes->v.nestVal = pAL;    pAL->useCt   = 0;    pAL->allocCt = MIN_ARG_ALLOC_CT;    /*     *  Scan until we hit a NUL.     */    do  {        while (isspace( (int)*pzTxt ))  pzTxt++;        if (isalpha( (int)*pzTxt )) {            pzTxt = scanNameEntry( pzTxt, pRes );        }        else switch (*pzTxt) {        case NUL: goto scan_done;        case '<': pzTxt = scanXmlEntry( pzTxt, pRes );                  if (*pzTxt == ',') pzTxt++;     break;        case '#': pzTxt = strchr( pzTxt, '\n' );  break;        default:  goto woops;        }    } while (pzTxt != NULL); scan_done:;    pAL = pRes->v.nestVal;    if (pAL->useCt != 0) {        sortNestedList( pAL );        return pRes;    } woops:    AGFREE( pRes->v.nestVal );    AGFREE( pRes );    return NULL;}/*=export_func  optionNestedVal * private: * * what:  parse a hierarchical option argument * arg:   + tOptions* + pOpts    + program options descriptor + * arg:   + tOptDesc* + pOptDesc + the descriptor for this arg + * * doc: *  Nested value was found on the command line=*/voidoptionNestedVal( tOptions* pOpts, tOptDesc* pOD ){    tOptionValue* pOV = optionLoadNested(        pOD->optArg.argString, pOD->pz_Name, strlen(pOD->pz_Name));    if (pOV != NULL)        addArgListEntry( &(pOD->optCookie), (void*)pOV );}/* * Local Variables: * mode: C * c-file-style: "stroustrup" * indent-tabs-mode: nil * End: * end of autoopts/nested.c */
 |