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;" }
};
static void
removeLineContinue( 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 );
static void
removeLineContinue( char* pzSrc )
{
char* pzD;
do {
while (*pzSrc == '\n') pzSrc++;
pzD = strchr(pzSrc, '\n');
if (pzD == NULL)
return;
pzSrc = pzD--;
if (*pzD != '\\')
pzD++;
} while (pzD == pzSrc);
for (;;) {
char ch = ((*pzD++) = *(pzSrc++));
switch (ch) {
case NUL: return;
case '\\':
if (*pzSrc == '\n')
--pzD;
}
}
}
static char const*
scanQuotedString( char const* pzTxt )
{
char q = *(pzTxt++);
for (;;) {
char ch = *(pzTxt++);
if (ch == NUL)
return pzTxt-1;
if (ch == q)
return pzTxt;
if (ch == '\\') {
ch = *(pzTxt++);
if (ch == NUL)
return pzTxt - 2;
if ((ch == q) || (ch == '\\')) {
if (*(pzTxt++) == NUL)
return pzTxt-1;
}
}
}
}
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) {
char const * pzSrc = pzValue;
char * pzDst = pNV->v.strVal;
int ct = dataLen;
do {
int ch = *(pzSrc++) & 0xFF;
if (ch == NUL) goto data_copy_done;
if (ch == '&')
ch = get_special_char(&pzSrc, &ct);
*(pzDst++) = ch;
} while (--ct > 0);
data_copy_done:
*pzDst = NUL;
} else {
pNV->v.strVal[0] = NUL;
}
pNV->pzName = pNV->v.strVal + dataLen + 1;
}
memcpy( pNV->pzName, pzName, nameLen );
pNV->pzName[ nameLen ] = NUL;
addArgListEntry( pp, pNV );
return pNV;
}
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 (IS_WHITESPACE_CHAR(*pzValue) && (dataLen > 0)) {
dataLen--; pzValue++;
}
if (dataLen == 0)
pNV->v.boolVal = 0;
else if (IS_DEC_DIGIT_CHAR(*pzValue))
pNV->v.boolVal = atoi(pzValue);
else pNV->v.boolVal = ! IS_FALSE_TYPE_CHAR(*pzValue);
pNV->valType = OPARG_TYPE_BOOLEAN;
pNV->pzName = (char*)(pNV + 1);
memcpy( pNV->pzName, pzName, nameLen );
pNV->pzName[ nameLen ] = NUL;
addArgListEntry( pp, pNV );
return pNV;
}
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 (IS_WHITESPACE_CHAR(*pzValue) && (dataLen > 0)) {
dataLen--; pzValue++;
}
if (dataLen == 0)
pNV->v.longVal = 0;
else
pNV->v.longVal = strtol(pzValue, 0, 0);
pNV->valType = OPARG_TYPE_NUMERIC;
pNV->pzName = (char*)(pNV + 1);
memcpy( pNV->pzName, pzName, nameLen );
pNV->pzName[ nameLen ] = NUL;
addArgListEntry( pp, pNV );
return pNV;
}
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;
}
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 (IS_VALUE_NAME_CHAR(*pzScan)) { pzScan++; nameLen++; }
if (pzScan[-1] == ':') { pzScan--; nameLen--; }
while (IS_HORIZ_WHITE_CHAR(*pzScan)) pzScan++;
re_switch:
switch (*pzScan) {
case '=':
case ':':
while (IS_HORIZ_WHITE_CHAR( (int)*++pzScan )) ;
if ((*pzScan == '=') || (*pzScan == ':'))
goto default_char;
goto re_switch;
case '\n':
case ',':
pzScan++;
case NUL:
addStringValue(&(pRes->v.nestVal), pzName, nameLen, NULL, (size_t)0);
break;
case '"':
case '\'':
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:
pzVal = pzScan;
for (;;) {
char ch = *(pzScan++);
switch (ch) {
case NUL:
pzScan--;
dataLen = pzScan - pzVal;
goto string_done;
case '\n':
if ( (pzScan > pzVal + 2)
&& (pzScan[-2] == '\\')
&& (pzScan[ 0] != NUL))
continue;
case ',':
dataLen = (pzScan - pzVal) - 1;
string_done:
pNV = addStringValue( &(pRes->v.nestVal), pzName, nameLen,
pzVal, dataLen );
if (pNV != NULL)
removeLineContinue( pNV->v.strVal );
goto leave_scan_name;
}
}
break;
} leave_scan_name:;
return pzScan;
}
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 (! IS_VAR_FIRST_CHAR(*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;
}
pzScan++;
while (IS_VALUE_NAME_CHAR( (int)*pzScan )) { 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;
}
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+1;
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 (IS_WHITESPACE_CHAR(*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;
}
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 );
}
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 );
}
static void
sortNestedList( tArgList* pAL )
{
int ix;
int lm = pAL->useCt;
for (ix = 0; ++ix < lm;) {
int iy = ix-1;
tOptionValue* pNewNV = (tOptionValue*)(void*)(pAL->apzArgs[ix]);
tOptionValue* pOldNV = (tOptionValue*)(void*)(pAL->apzArgs[iy]);
while (strcmp( pOldNV->pzName, pNewNV->pzName ) > 0) {
pAL->apzArgs[iy+1] = (void*)pOldNV;
pOldNV = (tOptionValue*)(void*)(pAL->apzArgs[--iy]);
if (iy < 0)
break;
}
pAL->apzArgs[iy+1] = (void*)pNewNV;
}
}
LOCAL tOptionValue*
optionLoadNested(char const* pzTxt, char const* pzName, size_t nameLen)
{
tOptionValue* pRes;
tArgList* pAL;
if (pzTxt == NULL) {
errno = EINVAL;
return NULL;
}
while (IS_WHITESPACE_CHAR(*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;
do {
while (IS_WHITESPACE_CHAR( (int)*pzTxt )) pzTxt++;
if (IS_VAR_FIRST_CHAR( (int)*pzTxt )) {
pzTxt = scanNameEntry( pzTxt, pRes );
}
else switch (*pzTxt) {
case NUL: goto scan_done;
case '<': pzTxt = scanXmlEntry( pzTxt, pRes );
if (pzTxt == NULL) goto woops;
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;
}
void
optionNestedVal(tOptions* pOpts, tOptDesc* pOD)
{
if (pOpts < OPTPROC_EMIT_LIMIT)
return;
if (pOD->fOptState & OPTST_RESET) {
tArgList* pAL = pOD->optCookie;
int ct;
tCC ** av;
if (pAL == NULL)
return;
ct = pAL->useCt;
av = pAL->apzArgs;
while (--ct >= 0) {
void * p = (void *)*(av++);
optionUnloadNested((tOptionValue const *)p);
}
AGFREE(pOD->optCookie);
} else {
tOptionValue* pOV = optionLoadNested(
pOD->optArg.argString, pOD->pz_Name, strlen(pOD->pz_Name));
if (pOV != NULL)
addArgListEntry( &(pOD->optCookie), (void*)pOV );
}
}
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 = ++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, xlatp->xml_len) == 0)) {
*ppz += xlatp->xml_len;
*ct -= xlatp->xml_len;
return xlatp->xml_ch;
}
if (--ctr <= 0)
break;
xlatp++;
}
}
return '&';
}
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, "#x%02X;", (ch & 0xFF));
}