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;" }
};
#ifndef ENOMSG
#define ENOMSG ENOENT
#endif
static void
remove_continuation(char * src);
static char const *
scan_q_str(char const * pzTxt);
static tOptionValue *
add_string(void ** pp, char const * name, size_t nm_len,
char const * val, size_t d_len);
static tOptionValue *
add_bool(void ** pp, char const * name, size_t nm_len,
char const * val, size_t d_len);
static tOptionValue *
add_number(void ** pp, char const * name, size_t nm_len,
char const * val, size_t d_len);
static tOptionValue *
add_nested(void ** pp, char const * name, size_t nm_len,
char * val, size_t d_len);
static char const *
scan_name(char const * name, tOptionValue * res);
static char const *
unnamed_xml(char const * txt);
static char const *
scan_xml_name(char const * name, size_t * nm_len, tOptionValue * val);
static char const *
find_end_xml(char const * src, size_t nm_len, char const * val, size_t * len);
static char const *
scan_xml(char const * xml_name, tOptionValue * res_val);
static void
sort_list(tArgList * arg_list);
static void
remove_continuation(char * src)
{
char * pzD;
do {
while (*src == NL) src++;
pzD = strchr(src, NL);
if (pzD == NULL)
return;
src = pzD--;
if (*pzD != '\\')
pzD++;
} while (pzD == src);
for (;;) {
char ch = ((*pzD++) = *(src++));
switch (ch) {
case NUL: return;
case '\\':
if (*src == NL)
--pzD;
}
}
}
static char const *
scan_q_str(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 *
add_string(void ** pp, char const * name, size_t nm_len,
char const * val, size_t d_len)
{
tOptionValue * pNV;
size_t sz = nm_len + d_len + sizeof(*pNV);
pNV = AGALOC(sz, "option name/str value pair");
if (val == NULL) {
pNV->valType = OPARG_TYPE_NONE;
pNV->pzName = pNV->v.strVal;
} else {
pNV->valType = OPARG_TYPE_STRING;
if (d_len > 0) {
char const * src = val;
char * pzDst = pNV->v.strVal;
int ct = (int)d_len;
do {
int ch = *(src++) & 0xFF;
if (ch == NUL) goto data_copy_done;
if (ch == '&')
ch = get_special_char(&src, &ct);
*(pzDst++) = (char)ch;
} while (--ct > 0);
data_copy_done:
*pzDst = NUL;
} else {
pNV->v.strVal[0] = NUL;
}
pNV->pzName = pNV->v.strVal + d_len + 1;
}
memcpy(pNV->pzName, name, nm_len);
pNV->pzName[ nm_len ] = NUL;
addArgListEntry(pp, pNV);
return pNV;
}
static tOptionValue *
add_bool(void ** pp, char const * name, size_t nm_len,
char const * val, size_t d_len)
{
size_t sz = nm_len + sizeof(tOptionValue) + 1;
tOptionValue * new_val = AGALOC(sz, "bool val");
while (IS_WHITESPACE_CHAR(*val) && (d_len > 0)) {
d_len--; val++;
}
if (d_len == 0)
new_val->v.boolVal = 0;
else if (IS_DEC_DIGIT_CHAR(*val))
new_val->v.boolVal = (unsigned)atoi(val);
else new_val->v.boolVal = ! IS_FALSE_TYPE_CHAR(*val);
new_val->valType = OPARG_TYPE_BOOLEAN;
new_val->pzName = (char *)(new_val + 1);
memcpy(new_val->pzName, name, nm_len);
new_val->pzName[ nm_len ] = NUL;
addArgListEntry(pp, new_val);
return new_val;
}
static tOptionValue *
add_number(void ** pp, char const * name, size_t nm_len,
char const * val, size_t d_len)
{
size_t sz = nm_len + sizeof(tOptionValue) + 1;
tOptionValue * new_val = AGALOC(sz, "int val");
while (IS_WHITESPACE_CHAR(*val) && (d_len > 0)) {
d_len--; val++;
}
if (d_len == 0)
new_val->v.longVal = 0;
else
new_val->v.longVal = strtol(val, 0, 0);
new_val->valType = OPARG_TYPE_NUMERIC;
new_val->pzName = (char *)(new_val + 1);
memcpy(new_val->pzName, name, nm_len);
new_val->pzName[ nm_len ] = NUL;
addArgListEntry(pp, new_val);
return new_val;
}
static tOptionValue *
add_nested(void ** pp, char const * name, size_t nm_len,
char * val, size_t d_len)
{
tOptionValue * new_val;
if (d_len == 0) {
size_t sz = nm_len + sizeof(*new_val) + 1;
new_val = AGALOC(sz, "empty nest");
new_val->v.nestVal = NULL;
new_val->valType = OPARG_TYPE_HIERARCHY;
new_val->pzName = (char *)(new_val + 1);
memcpy(new_val->pzName, name, nm_len);
new_val->pzName[ nm_len ] = NUL;
} else {
new_val = optionLoadNested(val, name, nm_len);
}
if (new_val != NULL)
addArgListEntry(pp, new_val);
return new_val;
}
static char const *
scan_name(char const * name, tOptionValue * res)
{
tOptionValue * new_val;
char const * pzScan;
char const * pzVal;
size_t nm_len = 1;
size_t d_len = 0;
pzScan = SPN_VALUE_NAME_CHARS(name + 1);
if (pzScan[-1] == ':')
pzScan--;
nm_len = (size_t)(pzScan - name);
pzScan = SPN_HORIZ_WHITE_CHARS(pzScan);
re_switch:
switch (*pzScan) {
case '=':
case ':':
pzScan = SPN_HORIZ_WHITE_CHARS(pzScan + 1);
if ((*pzScan == '=') || (*pzScan == ':'))
goto default_char;
goto re_switch;
case NL:
case ',':
pzScan++;
case NUL:
add_string(&(res->v.nestVal), name, nm_len, NULL, (size_t)0);
break;
case '"':
case '\'':
pzVal = pzScan;
pzScan = scan_q_str(pzScan);
d_len = (size_t)(pzScan - pzVal);
new_val = add_string(&(res->v.nestVal), name, nm_len, pzVal,
d_len);
if ((new_val != NULL) && (option_load_mode == OPTION_LOAD_COOKED))
ao_string_cook(new_val->v.strVal, NULL);
break;
default:
default_char:
pzVal = pzScan;
for (;;) {
char ch = *(pzScan++);
switch (ch) {
case NUL:
pzScan--;
d_len = (size_t)(pzScan - pzVal);
goto string_done;
case NL:
if ( (pzScan > pzVal + 2)
&& (pzScan[-2] == '\\')
&& (pzScan[ 0] != NUL))
continue;
case ',':
d_len = (size_t)(pzScan - pzVal) - 1;
string_done:
new_val = add_string(&(res->v.nestVal), name, nm_len,
pzVal, d_len);
if (new_val != NULL)
remove_continuation(new_val->v.strVal);
goto leave_scan_name;
}
}
break;
} leave_scan_name:;
return pzScan;
}
static char const *
unnamed_xml(char const * txt)
{
switch (*txt) {
default:
txt = NULL;
break;
case '!':
txt = strstr(txt, "-->");
if (txt != NULL)
txt += 3;
break;
case '?':
txt = strchr(txt, '>');
if (txt != NULL)
txt++;
break;
}
return txt;
}
static char const *
scan_xml_name(char const * name, size_t * nm_len, tOptionValue * val)
{
char const * scan = SPN_VALUE_NAME_CHARS(name + 1);
*nm_len = (size_t)(scan - name);
if (*nm_len > 64)
return NULL;
val->valType = OPARG_TYPE_STRING;
if (IS_WHITESPACE_CHAR(*scan)) {
scan = SPN_WHITESPACE_CHARS(scan);
scan = parse_attrs(NULL, scan, &option_load_mode, val);
if (scan == NULL)
return NULL;
}
if (! IS_END_XML_TOKEN_CHAR(*scan))
return NULL;
if (*scan == '/') {
if (*++scan != '>')
return NULL;
val->valType = OPARG_TYPE_NONE;
}
return scan+1;
}
static char const *
find_end_xml(char const * src, size_t nm_len, char const * val, size_t * len)
{
char z[72] = "</";
char * dst = z + 2;
do {
*(dst++) = *(src++);
} while (--nm_len > 0);
*(dst++) = '>';
*dst = NUL;
{
char const * res = strstr(val, z);
if (res != NULL) {
char const * end = (option_load_mode != OPTION_LOAD_KEEP)
? SPN_WHITESPACE_BACK(val, res)
: res;
*len = (size_t)(end - val);
res = SPN_WHITESPACE_CHARS(res + (dst - z));
}
return res;
}
}
static char const *
scan_xml(char const * xml_name, tOptionValue * res_val)
{
size_t nm_len, v_len;
char const * scan;
char const * val_str;
tOptionValue valu;
tOptionLoadMode save_mode = option_load_mode;
if (! IS_VAR_FIRST_CHAR(*++xml_name))
return unnamed_xml(xml_name);
val_str = scan_xml_name(xml_name, &nm_len, &valu);
if (val_str == NULL)
goto bail_scan_xml;
if (valu.valType == OPARG_TYPE_NONE)
scan = val_str;
else {
if (option_load_mode != OPTION_LOAD_KEEP)
val_str = SPN_WHITESPACE_CHARS(val_str);
scan = find_end_xml(xml_name, nm_len, val_str, &v_len);
if (scan == NULL)
goto bail_scan_xml;
}
switch (valu.valType) {
case OPARG_TYPE_NONE:
add_string(&(res_val->v.nestVal), xml_name, nm_len, NULL, 0);
break;
case OPARG_TYPE_STRING:
{
tOptionValue * new_val = add_string(
&(res_val->v.nestVal), xml_name, nm_len, val_str, v_len);
if (option_load_mode != OPTION_LOAD_KEEP)
munge_str(new_val->v.strVal, option_load_mode);
break;
}
case OPARG_TYPE_BOOLEAN:
add_bool(&(res_val->v.nestVal), xml_name, nm_len, val_str, v_len);
break;
case OPARG_TYPE_NUMERIC:
add_number(&(res_val->v.nestVal), xml_name, nm_len, val_str, v_len);
break;
case OPARG_TYPE_HIERARCHY:
{
char * pz = AGALOC(v_len+1, "h scan");
memcpy(pz, val_str, v_len);
pz[v_len] = NUL;
add_nested(&(res_val->v.nestVal), xml_name, nm_len, pz, v_len);
AGFREE(pz);
break;
}
case OPARG_TYPE_ENUMERATION:
case OPARG_TYPE_MEMBERSHIP:
default:
break;
}
option_load_mode = save_mode;
return scan;
bail_scan_xml:
option_load_mode = save_mode;
return NULL;
}
LOCAL void
unload_arg_list(tArgList * arg_list)
{
int ct = arg_list->useCt;
char const ** pnew_val = arg_list->apzArgs;
while (ct-- > 0) {
tOptionValue * new_val = (tOptionValue *)VOIDP(*(pnew_val++));
if (new_val->valType == OPARG_TYPE_HIERARCHY)
unload_arg_list(new_val->v.nestVal);
AGFREE(new_val);
}
AGFREE(arg_list);
}
void
optionUnloadNested(tOptionValue const * opt_val)
{
if (opt_val == NULL) return;
if (opt_val->valType != OPARG_TYPE_HIERARCHY) {
errno = EINVAL;
return;
}
unload_arg_list(opt_val->v.nestVal);
AGFREE(opt_val);
}
static void
sort_list(tArgList * arg_list)
{
int ix;
int lm = arg_list->useCt;
for (ix = 0; ++ix < lm;) {
int iy = ix-1;
tOptionValue * new_v = C(tOptionValue *, arg_list->apzArgs[ix]);
tOptionValue * old_v = C(tOptionValue *, arg_list->apzArgs[iy]);
while (strcmp(old_v->pzName, new_v->pzName) > 0) {
arg_list->apzArgs[iy+1] = VOIDP(old_v);
old_v = (tOptionValue *)VOIDP(arg_list->apzArgs[--iy]);
if (iy < 0)
break;
}
arg_list->apzArgs[iy+1] = VOIDP(new_v);
}
}
LOCAL tOptionValue *
optionLoadNested(char const * text, char const * name, size_t nm_len)
{
tOptionValue * res_val;
if (text == NULL) {
errno = EINVAL;
return NULL;
}
text = SPN_WHITESPACE_CHARS(text);
if (*text == NUL) {
errno = ENOMSG;
return NULL;
}
res_val = AGALOC(sizeof(*res_val) + nm_len + 1, "nest args");
res_val->valType = OPARG_TYPE_HIERARCHY;
res_val->pzName = (char *)(res_val + 1);
memcpy(res_val->pzName, name, nm_len);
res_val->pzName[nm_len] = NUL;
{
tArgList * arg_list = AGALOC(sizeof(*arg_list), "nest arg l");
res_val->v.nestVal = arg_list;
arg_list->useCt = 0;
arg_list->allocCt = MIN_ARG_ALLOC_CT;
}
do {
text = SPN_WHITESPACE_CHARS(text);
if (IS_VAR_FIRST_CHAR(*text))
text = scan_name(text, res_val);
else switch (*text) {
case NUL: goto scan_done;
case '<': text = scan_xml(text, res_val);
if (text == NULL) goto woops;
if (*text == ',') text++; break;
case '#': text = strchr(text, NL); break;
default: goto woops;
}
} while (text != NULL); scan_done:;
{
tArgList * al = res_val->v.nestVal;
if (al->useCt == 0) {
errno = ENOMSG;
goto woops;
}
if (al->useCt > 1)
sort_list(al);
}
return res_val;
woops:
AGFREE(res_val->v.nestVal);
AGFREE(res_val);
return NULL;
}
void
optionNestedVal(tOptions * opts, tOptDesc * od)
{
if (opts < OPTPROC_EMIT_LIMIT)
return;
if (od->fOptState & OPTST_RESET) {
tArgList * arg_list = od->optCookie;
int ct;
char const ** av;
if (arg_list == NULL)
return;
ct = arg_list->useCt;
av = arg_list->apzArgs;
while (--ct >= 0) {
void * p = VOIDP(*(av++));
optionUnloadNested((tOptionValue const *)p);
}
AGFREE(od->optCookie);
} else {
tOptionValue * opt_val = optionLoadNested(
od->optArg.argString, od->pz_Name, strlen(od->pz_Name));
if (opt_val != NULL)
addArgListEntry(&(od->optCookie), VOIDP(opt_val));
}
}
LOCAL int
get_special_char(char const ** ppz, int * ct)
{
char const * pz = *ppz;
char * rz;
if (*ct < 3)
return '&';
if (*pz == '#') {
int base = 10;
int retch;
pz++;
if (*pz == 'x') {
base = 16;
pz++;
}
retch = (int)strtoul(pz, &rz, base);
pz = rz;
if (*pz != ';')
return '&';
base = (int)(++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, (size_t)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, XML_HEX_BYTE_FMT, (ch & 0xFF));
}