/* $NetBSD: nested.c,v 1.1.1.2 2007/06/24 15:49:25 kardel Exp $ */ /* * 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 void removeBackslashes( 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 void unloadNestedArglist( tArgList* pAL ); static void sortNestedList( tArgList* pAL ); /* = = = END-STATIC-FORWARD = = = */ /* removeBackslashes * * This function assumes that all newline characters were preceeded by * backslashes that need removal. */ static void removeBackslashes( 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 void unloadNestedArglist( 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}). =*/ void optionUnloadNested( 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 void sortNestedList( 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 =*/ void optionNestedVal( 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 */