1181834Sroberto 2285612Sdelphij/** 3285612Sdelphij * \file nested.c 4181834Sroberto * 5285612Sdelphij * Handle options with arguments that contain nested values. 6285612Sdelphij * 7285612Sdelphij * @addtogroup autoopts 8285612Sdelphij * @{ 9181834Sroberto */ 10181834Sroberto/* 11285612Sdelphij * Automated Options Nested Values module. 12181834Sroberto * 13285612Sdelphij * This file is part of AutoOpts, a companion to AutoGen. 14285612Sdelphij * AutoOpts is free software. 15285612Sdelphij * AutoOpts is Copyright (C) 1992-2015 by Bruce Korb - all rights reserved 16181834Sroberto * 17285612Sdelphij * AutoOpts is available under any one of two licenses. The license 18285612Sdelphij * in use must be one of these two and the choice is under the control 19285612Sdelphij * of the user of the license. 20181834Sroberto * 21285612Sdelphij * The GNU Lesser General Public License, version 3 or later 22285612Sdelphij * See the files "COPYING.lgplv3" and "COPYING.gplv3" 23181834Sroberto * 24285612Sdelphij * The Modified Berkeley Software Distribution License 25285612Sdelphij * See the file "COPYING.mbsd" 26181834Sroberto * 27285612Sdelphij * These files have the following sha256 sums: 28181834Sroberto * 29285612Sdelphij * 8584710e9b04216a394078dc156b781d0b47e1729104d666658aecef8ee32e95 COPYING.gplv3 30285612Sdelphij * 4379e7444a0e2ce2b12dd6f5a52a27a4d02d39d247901d3285c88cf0d37f477b COPYING.lgplv3 31285612Sdelphij * 13aa749a5b0a454917a944ed8fffc530b784f5ead522b1aacaf4ec8aa55a6239 COPYING.mbsd 32181834Sroberto */ 33285612Sdelphij 34285612Sdelphijtypedef struct { 35285612Sdelphij int xml_ch; 36285612Sdelphij int xml_len; 37285612Sdelphij char xml_txt[8]; 38285612Sdelphij} xml_xlate_t; 39285612Sdelphij 40285612Sdelphijstatic xml_xlate_t const xml_xlate[] = { 41285612Sdelphij { '&', 4, "amp;" }, 42285612Sdelphij { '<', 3, "lt;" }, 43285612Sdelphij { '>', 3, "gt;" }, 44285612Sdelphij { '"', 5, "quot;" }, 45285612Sdelphij { '\'',5, "apos;" } 46285612Sdelphij}; 47285612Sdelphij 48285612Sdelphij#ifndef ENOMSG 49285612Sdelphij#define ENOMSG ENOENT 50285612Sdelphij#endif 51285612Sdelphij 52181834Sroberto/* = = = START-STATIC-FORWARD = = = */ 53181834Srobertostatic void 54285612Sdelphijremove_continuation(char * src); 55181834Sroberto 56285612Sdelphijstatic char const * 57285612Sdelphijscan_q_str(char const * pzTxt); 58181834Sroberto 59285612Sdelphijstatic tOptionValue * 60285612Sdelphijadd_string(void ** pp, char const * name, size_t nm_len, 61285612Sdelphij char const * val, size_t d_len); 62181834Sroberto 63285612Sdelphijstatic tOptionValue * 64285612Sdelphijadd_bool(void ** pp, char const * name, size_t nm_len, 65285612Sdelphij char const * val, size_t d_len); 66181834Sroberto 67285612Sdelphijstatic tOptionValue * 68285612Sdelphijadd_number(void ** pp, char const * name, size_t nm_len, 69285612Sdelphij char const * val, size_t d_len); 70181834Sroberto 71285612Sdelphijstatic tOptionValue * 72285612Sdelphijadd_nested(void ** pp, char const * name, size_t nm_len, 73285612Sdelphij char * val, size_t d_len); 74181834Sroberto 75285612Sdelphijstatic char const * 76285612Sdelphijscan_name(char const * name, tOptionValue * res); 77181834Sroberto 78285612Sdelphijstatic char const * 79285612Sdelphijunnamed_xml(char const * txt); 80181834Sroberto 81285612Sdelphijstatic char const * 82285612Sdelphijscan_xml_name(char const * name, size_t * nm_len, tOptionValue * val); 83181834Sroberto 84285612Sdelphijstatic char const * 85285612Sdelphijfind_end_xml(char const * src, size_t nm_len, char const * val, size_t * len); 86285612Sdelphij 87285612Sdelphijstatic char const * 88285612Sdelphijscan_xml(char const * xml_name, tOptionValue * res_val); 89285612Sdelphij 90181834Srobertostatic void 91285612Sdelphijsort_list(tArgList * arg_list); 92181834Sroberto/* = = = END-STATIC-FORWARD = = = */ 93181834Sroberto 94285612Sdelphij/** 95285612Sdelphij * Backslashes are used for line continuations. We keep the newline 96285612Sdelphij * characters, but trim out the backslash: 97181834Sroberto */ 98181834Srobertostatic void 99285612Sdelphijremove_continuation(char * src) 100181834Sroberto{ 101285612Sdelphij char * pzD; 102181834Sroberto 103285612Sdelphij do { 104285612Sdelphij while (*src == NL) src++; 105285612Sdelphij pzD = strchr(src, NL); 106285612Sdelphij if (pzD == NULL) 107285612Sdelphij return; 108181834Sroberto 109285612Sdelphij /* 110285612Sdelphij * pzD has skipped at least one non-newline character and now 111285612Sdelphij * points to a newline character. It now becomes the source and 112285612Sdelphij * pzD goes to the previous character. 113285612Sdelphij */ 114285612Sdelphij src = pzD--; 115285612Sdelphij if (*pzD != '\\') 116285612Sdelphij pzD++; 117285612Sdelphij } while (pzD == src); 118285612Sdelphij 119285612Sdelphij /* 120285612Sdelphij * Start shifting text. 121285612Sdelphij */ 122181834Sroberto for (;;) { 123285612Sdelphij char ch = ((*pzD++) = *(src++)); 124181834Sroberto switch (ch) { 125181834Sroberto case NUL: return; 126285612Sdelphij case '\\': 127285612Sdelphij if (*src == NL) 128285612Sdelphij --pzD; /* rewrite on next iteration */ 129181834Sroberto } 130181834Sroberto } 131181834Sroberto} 132181834Sroberto 133285612Sdelphij/** 134181834Sroberto * Find the end of a quoted string, skipping escaped quote characters. 135181834Sroberto */ 136285612Sdelphijstatic char const * 137285612Sdelphijscan_q_str(char const * pzTxt) 138181834Sroberto{ 139181834Sroberto char q = *(pzTxt++); /* remember the type of quote */ 140181834Sroberto 141181834Sroberto for (;;) { 142181834Sroberto char ch = *(pzTxt++); 143181834Sroberto if (ch == NUL) 144181834Sroberto return pzTxt-1; 145181834Sroberto 146181834Sroberto if (ch == q) 147181834Sroberto return pzTxt; 148181834Sroberto 149181834Sroberto if (ch == '\\') { 150181834Sroberto ch = *(pzTxt++); 151181834Sroberto /* 152181834Sroberto * IF the next character is NUL, drop the backslash, too. 153181834Sroberto */ 154181834Sroberto if (ch == NUL) 155181834Sroberto return pzTxt - 2; 156181834Sroberto 157181834Sroberto /* 158181834Sroberto * IF the quote character or the escape character were escaped, 159181834Sroberto * then skip both, as long as the string does not end. 160181834Sroberto */ 161181834Sroberto if ((ch == q) || (ch == '\\')) { 162181834Sroberto if (*(pzTxt++) == NUL) 163181834Sroberto return pzTxt-1; 164181834Sroberto } 165181834Sroberto } 166181834Sroberto } 167181834Sroberto} 168181834Sroberto 169181834Sroberto 170285612Sdelphij/** 171285612Sdelphij * Associate a name with either a string or no value. 172181834Sroberto * 173285612Sdelphij * @param[in,out] pp argument list to add to 174285612Sdelphij * @param[in] name the name of the "suboption" 175285612Sdelphij * @param[in] nm_len the length of the name 176285612Sdelphij * @param[in] val the string value for the suboption 177285612Sdelphij * @param[in] d_len the length of the value 178285612Sdelphij * 179285612Sdelphij * @returns the new value structure 180181834Sroberto */ 181285612Sdelphijstatic tOptionValue * 182285612Sdelphijadd_string(void ** pp, char const * name, size_t nm_len, 183285612Sdelphij char const * val, size_t d_len) 184181834Sroberto{ 185285612Sdelphij tOptionValue * pNV; 186285612Sdelphij size_t sz = nm_len + d_len + sizeof(*pNV); 187181834Sroberto 188285612Sdelphij pNV = AGALOC(sz, "option name/str value pair"); 189181834Sroberto 190285612Sdelphij if (val == NULL) { 191181834Sroberto pNV->valType = OPARG_TYPE_NONE; 192181834Sroberto pNV->pzName = pNV->v.strVal; 193181834Sroberto 194181834Sroberto } else { 195181834Sroberto pNV->valType = OPARG_TYPE_STRING; 196285612Sdelphij if (d_len > 0) { 197285612Sdelphij char const * src = val; 198285612Sdelphij char * pzDst = pNV->v.strVal; 199285612Sdelphij int ct = (int)d_len; 200285612Sdelphij do { 201285612Sdelphij int ch = *(src++) & 0xFF; 202285612Sdelphij if (ch == NUL) goto data_copy_done; 203285612Sdelphij if (ch == '&') 204285612Sdelphij ch = get_special_char(&src, &ct); 205285612Sdelphij *(pzDst++) = (char)ch; 206285612Sdelphij } while (--ct > 0); 207285612Sdelphij data_copy_done: 208285612Sdelphij *pzDst = NUL; 209285612Sdelphij 210285612Sdelphij } else { 211285612Sdelphij pNV->v.strVal[0] = NUL; 212285612Sdelphij } 213285612Sdelphij 214285612Sdelphij pNV->pzName = pNV->v.strVal + d_len + 1; 215181834Sroberto } 216181834Sroberto 217285612Sdelphij memcpy(pNV->pzName, name, nm_len); 218285612Sdelphij pNV->pzName[ nm_len ] = NUL; 219285612Sdelphij addArgListEntry(pp, pNV); 220181834Sroberto return pNV; 221181834Sroberto} 222181834Sroberto 223285612Sdelphij/** 224285612Sdelphij * Associate a name with a boolean value 225181834Sroberto * 226285612Sdelphij * @param[in,out] pp argument list to add to 227285612Sdelphij * @param[in] name the name of the "suboption" 228285612Sdelphij * @param[in] nm_len the length of the name 229285612Sdelphij * @param[in] val the boolean value for the suboption 230285612Sdelphij * @param[in] d_len the length of the value 231285612Sdelphij * 232285612Sdelphij * @returns the new value structure 233181834Sroberto */ 234285612Sdelphijstatic tOptionValue * 235285612Sdelphijadd_bool(void ** pp, char const * name, size_t nm_len, 236285612Sdelphij char const * val, size_t d_len) 237181834Sroberto{ 238285612Sdelphij size_t sz = nm_len + sizeof(tOptionValue) + 1; 239285612Sdelphij tOptionValue * new_val = AGALOC(sz, "bool val"); 240181834Sroberto 241285612Sdelphij /* 242285612Sdelphij * Scan over whitespace is constrained by "d_len" 243285612Sdelphij */ 244285612Sdelphij while (IS_WHITESPACE_CHAR(*val) && (d_len > 0)) { 245285612Sdelphij d_len--; val++; 246181834Sroberto } 247181834Sroberto 248285612Sdelphij if (d_len == 0) 249285612Sdelphij new_val->v.boolVal = 0; 250285612Sdelphij 251285612Sdelphij else if (IS_DEC_DIGIT_CHAR(*val)) 252285612Sdelphij new_val->v.boolVal = (unsigned)atoi(val); 253285612Sdelphij 254285612Sdelphij else new_val->v.boolVal = ! IS_FALSE_TYPE_CHAR(*val); 255285612Sdelphij 256285612Sdelphij new_val->valType = OPARG_TYPE_BOOLEAN; 257285612Sdelphij new_val->pzName = (char *)(new_val + 1); 258285612Sdelphij memcpy(new_val->pzName, name, nm_len); 259285612Sdelphij new_val->pzName[ nm_len ] = NUL; 260285612Sdelphij addArgListEntry(pp, new_val); 261285612Sdelphij return new_val; 262181834Sroberto} 263181834Sroberto 264285612Sdelphij/** 265285612Sdelphij * Associate a name with strtol() value, defaulting to zero. 266181834Sroberto * 267285612Sdelphij * @param[in,out] pp argument list to add to 268285612Sdelphij * @param[in] name the name of the "suboption" 269285612Sdelphij * @param[in] nm_len the length of the name 270285612Sdelphij * @param[in] val the numeric value for the suboption 271285612Sdelphij * @param[in] d_len the length of the value 272285612Sdelphij * 273285612Sdelphij * @returns the new value structure 274181834Sroberto */ 275285612Sdelphijstatic tOptionValue * 276285612Sdelphijadd_number(void ** pp, char const * name, size_t nm_len, 277285612Sdelphij char const * val, size_t d_len) 278181834Sroberto{ 279285612Sdelphij size_t sz = nm_len + sizeof(tOptionValue) + 1; 280285612Sdelphij tOptionValue * new_val = AGALOC(sz, "int val"); 281181834Sroberto 282285612Sdelphij /* 283285612Sdelphij * Scan over whitespace is constrained by "d_len" 284285612Sdelphij */ 285285612Sdelphij while (IS_WHITESPACE_CHAR(*val) && (d_len > 0)) { 286285612Sdelphij d_len--; val++; 287181834Sroberto } 288285612Sdelphij if (d_len == 0) 289285612Sdelphij new_val->v.longVal = 0; 290181834Sroberto else 291285612Sdelphij new_val->v.longVal = strtol(val, 0, 0); 292181834Sroberto 293285612Sdelphij new_val->valType = OPARG_TYPE_NUMERIC; 294285612Sdelphij new_val->pzName = (char *)(new_val + 1); 295285612Sdelphij memcpy(new_val->pzName, name, nm_len); 296285612Sdelphij new_val->pzName[ nm_len ] = NUL; 297285612Sdelphij addArgListEntry(pp, new_val); 298285612Sdelphij return new_val; 299181834Sroberto} 300181834Sroberto 301285612Sdelphij/** 302285612Sdelphij * Associate a name with a nested/hierarchical value. 303181834Sroberto * 304285612Sdelphij * @param[in,out] pp argument list to add to 305285612Sdelphij * @param[in] name the name of the "suboption" 306285612Sdelphij * @param[in] nm_len the length of the name 307285612Sdelphij * @param[in] val the nested values for the suboption 308285612Sdelphij * @param[in] d_len the length of the value 309285612Sdelphij * 310285612Sdelphij * @returns the new value structure 311181834Sroberto */ 312285612Sdelphijstatic tOptionValue * 313285612Sdelphijadd_nested(void ** pp, char const * name, size_t nm_len, 314285612Sdelphij char * val, size_t d_len) 315181834Sroberto{ 316285612Sdelphij tOptionValue * new_val; 317181834Sroberto 318285612Sdelphij if (d_len == 0) { 319285612Sdelphij size_t sz = nm_len + sizeof(*new_val) + 1; 320285612Sdelphij new_val = AGALOC(sz, "empty nest"); 321285612Sdelphij new_val->v.nestVal = NULL; 322285612Sdelphij new_val->valType = OPARG_TYPE_HIERARCHY; 323285612Sdelphij new_val->pzName = (char *)(new_val + 1); 324285612Sdelphij memcpy(new_val->pzName, name, nm_len); 325285612Sdelphij new_val->pzName[ nm_len ] = NUL; 326181834Sroberto 327181834Sroberto } else { 328285612Sdelphij new_val = optionLoadNested(val, name, nm_len); 329181834Sroberto } 330181834Sroberto 331285612Sdelphij if (new_val != NULL) 332285612Sdelphij addArgListEntry(pp, new_val); 333181834Sroberto 334285612Sdelphij return new_val; 335181834Sroberto} 336181834Sroberto 337285612Sdelphij/** 338181834Sroberto * We have an entry that starts with a name. Find the end of it, cook it 339181834Sroberto * (if called for) and create the name/value association. 340181834Sroberto */ 341285612Sdelphijstatic char const * 342285612Sdelphijscan_name(char const * name, tOptionValue * res) 343181834Sroberto{ 344285612Sdelphij tOptionValue * new_val; 345285612Sdelphij char const * pzScan = name+1; /* we know first char is a name char */ 346285612Sdelphij char const * pzVal; 347285612Sdelphij size_t nm_len = 1; 348285612Sdelphij size_t d_len = 0; 349181834Sroberto 350285612Sdelphij /* 351285612Sdelphij * Scan over characters that name a value. These names may not end 352285612Sdelphij * with a colon, but they may contain colons. 353285612Sdelphij */ 354285612Sdelphij pzScan = SPN_VALUE_NAME_CHARS(name + 1); 355285612Sdelphij if (pzScan[-1] == ':') 356285612Sdelphij pzScan--; 357285612Sdelphij nm_len = (size_t)(pzScan - name); 358181834Sroberto 359285612Sdelphij pzScan = SPN_HORIZ_WHITE_CHARS(pzScan); 360181834Sroberto 361285612Sdelphij re_switch: 362285612Sdelphij 363181834Sroberto switch (*pzScan) { 364181834Sroberto case '=': 365181834Sroberto case ':': 366285612Sdelphij pzScan = SPN_HORIZ_WHITE_CHARS(pzScan + 1); 367285612Sdelphij if ((*pzScan == '=') || (*pzScan == ':')) 368285612Sdelphij goto default_char; 369285612Sdelphij goto re_switch; 370181834Sroberto 371285612Sdelphij case NL: 372181834Sroberto case ',': 373181834Sroberto pzScan++; 374181834Sroberto /* FALLTHROUGH */ 375181834Sroberto 376181834Sroberto case NUL: 377285612Sdelphij add_string(&(res->v.nestVal), name, nm_len, NULL, (size_t)0); 378181834Sroberto break; 379181834Sroberto 380181834Sroberto case '"': 381181834Sroberto case '\'': 382181834Sroberto pzVal = pzScan; 383285612Sdelphij pzScan = scan_q_str(pzScan); 384285612Sdelphij d_len = (size_t)(pzScan - pzVal); 385285612Sdelphij new_val = add_string(&(res->v.nestVal), name, nm_len, pzVal, 386285612Sdelphij d_len); 387285612Sdelphij if ((new_val != NULL) && (option_load_mode == OPTION_LOAD_COOKED)) 388285612Sdelphij ao_string_cook(new_val->v.strVal, NULL); 389181834Sroberto break; 390181834Sroberto 391181834Sroberto default: 392181834Sroberto default_char: 393181834Sroberto /* 394181834Sroberto * We have found some strange text value. It ends with a newline 395181834Sroberto * or a comma. 396181834Sroberto */ 397181834Sroberto pzVal = pzScan; 398181834Sroberto for (;;) { 399181834Sroberto char ch = *(pzScan++); 400181834Sroberto switch (ch) { 401181834Sroberto case NUL: 402181834Sroberto pzScan--; 403285612Sdelphij d_len = (size_t)(pzScan - pzVal); 404181834Sroberto goto string_done; 405181834Sroberto /* FALLTHROUGH */ 406181834Sroberto 407285612Sdelphij case NL: 408181834Sroberto if ( (pzScan > pzVal + 2) 409181834Sroberto && (pzScan[-2] == '\\') 410181834Sroberto && (pzScan[ 0] != NUL)) 411181834Sroberto continue; 412181834Sroberto /* FALLTHROUGH */ 413181834Sroberto 414181834Sroberto case ',': 415285612Sdelphij d_len = (size_t)(pzScan - pzVal) - 1; 416181834Sroberto string_done: 417285612Sdelphij new_val = add_string(&(res->v.nestVal), name, nm_len, 418285612Sdelphij pzVal, d_len); 419285612Sdelphij if (new_val != NULL) 420285612Sdelphij remove_continuation(new_val->v.strVal); 421181834Sroberto goto leave_scan_name; 422181834Sroberto } 423181834Sroberto } 424181834Sroberto break; 425181834Sroberto } leave_scan_name:; 426181834Sroberto 427181834Sroberto return pzScan; 428181834Sroberto} 429181834Sroberto 430285612Sdelphij/** 431285612Sdelphij * Some xml element that does not start with a name. 432285612Sdelphij * The next character must be either '!' (introducing a comment), 433285612Sdelphij * or '?' (introducing an XML meta-marker of some sort). 434285612Sdelphij * We ignore these and indicate an error (NULL result) otherwise. 435181834Sroberto * 436285612Sdelphij * @param[in] txt the text within an xml bracket 437285612Sdelphij * @returns the address of the character after the closing marker, or NULL. 438181834Sroberto */ 439285612Sdelphijstatic char const * 440285612Sdelphijunnamed_xml(char const * txt) 441181834Sroberto{ 442285612Sdelphij switch (*txt) { 443285612Sdelphij default: 444285612Sdelphij txt = NULL; 445285612Sdelphij break; 446181834Sroberto 447285612Sdelphij case '!': 448285612Sdelphij txt = strstr(txt, "-->"); 449285612Sdelphij if (txt != NULL) 450285612Sdelphij txt += 3; 451285612Sdelphij break; 452181834Sroberto 453285612Sdelphij case '?': 454285612Sdelphij txt = strchr(txt, '>'); 455285612Sdelphij if (txt != NULL) 456285612Sdelphij txt++; 457285612Sdelphij break; 458181834Sroberto } 459285612Sdelphij return txt; 460285612Sdelphij} 461181834Sroberto 462285612Sdelphij/** 463285612Sdelphij * Scan off the xml element name, and the rest of the header, too. 464285612Sdelphij * Set the value type to NONE if it ends with "/>". 465285612Sdelphij * 466285612Sdelphij * @param[in] name the first name character (alphabetic) 467285612Sdelphij * @param[out] nm_len the length of the name 468285612Sdelphij * @param[out] val set valType field to STRING or NONE. 469285612Sdelphij * 470285612Sdelphij * @returns the scan resumption point, or NULL on error 471285612Sdelphij */ 472285612Sdelphijstatic char const * 473285612Sdelphijscan_xml_name(char const * name, size_t * nm_len, tOptionValue * val) 474285612Sdelphij{ 475285612Sdelphij char const * scan = SPN_VALUE_NAME_CHARS(name + 1); 476285612Sdelphij *nm_len = (size_t)(scan - name); 477285612Sdelphij if (*nm_len > 64) 478181834Sroberto return NULL; 479285612Sdelphij val->valType = OPARG_TYPE_STRING; 480181834Sroberto 481285612Sdelphij if (IS_WHITESPACE_CHAR(*scan)) { 482285612Sdelphij /* 483285612Sdelphij * There are attributes following the name. Parse 'em. 484285612Sdelphij */ 485285612Sdelphij scan = SPN_WHITESPACE_CHARS(scan); 486285612Sdelphij scan = parse_attrs(NULL, scan, &option_load_mode, val); 487285612Sdelphij if (scan == NULL) 488285612Sdelphij return NULL; /* oops */ 489285612Sdelphij } 490181834Sroberto 491285612Sdelphij if (! IS_END_XML_TOKEN_CHAR(*scan)) 492285612Sdelphij return NULL; /* oops */ 493181834Sroberto 494285612Sdelphij if (*scan == '/') { 495285612Sdelphij /* 496285612Sdelphij * Single element XML entries get inserted as an empty string. 497285612Sdelphij */ 498285612Sdelphij if (*++scan != '>') 499181834Sroberto return NULL; 500285612Sdelphij val->valType = OPARG_TYPE_NONE; 501285612Sdelphij } 502285612Sdelphij return scan+1; 503285612Sdelphij} 504181834Sroberto 505285612Sdelphij/** 506285612Sdelphij * We've found a closing '>' without a preceding '/', thus we must search 507285612Sdelphij * the text for '<name/>' where "name" is the name of the XML element. 508285612Sdelphij * 509285612Sdelphij * @param[in] name the start of the name in the element header 510285612Sdelphij * @param[in] nm_len the length of that name 511285612Sdelphij * @param[out] len the length of the value (string between header and 512285612Sdelphij * the trailer/tail. 513285612Sdelphij * @returns the character after the trailer, or NULL if not found. 514285612Sdelphij */ 515285612Sdelphijstatic char const * 516285612Sdelphijfind_end_xml(char const * src, size_t nm_len, char const * val, size_t * len) 517285612Sdelphij{ 518285612Sdelphij char z[72] = "</"; 519285612Sdelphij char * dst = z + 2; 520181834Sroberto 521285612Sdelphij do { 522285612Sdelphij *(dst++) = *(src++); 523285612Sdelphij } while (--nm_len > 0); /* nm_len is known to be 64 or less */ 524285612Sdelphij *(dst++) = '>'; 525285612Sdelphij *dst = NUL; 526285612Sdelphij 527285612Sdelphij { 528285612Sdelphij char const * res = strstr(val, z); 529285612Sdelphij 530285612Sdelphij if (res != NULL) { 531285612Sdelphij char const * end = (option_load_mode != OPTION_LOAD_KEEP) 532285612Sdelphij ? SPN_WHITESPACE_BACK(val, res) 533285612Sdelphij : res; 534285612Sdelphij *len = (size_t)(end - val); /* includes trailing white space */ 535285612Sdelphij res = SPN_WHITESPACE_CHARS(res + (dst - z)); 536285612Sdelphij } 537285612Sdelphij return res; 538181834Sroberto } 539285612Sdelphij} 540181834Sroberto 541285612Sdelphij/** 542285612Sdelphij * We've found a '<' character. We ignore this if it is a comment or a 543285612Sdelphij * directive. If it is something else, then whatever it is we are looking 544285612Sdelphij * at is bogus. Returning NULL stops processing. 545285612Sdelphij * 546285612Sdelphij * @param[in] xml_name the name of an xml bracket (usually) 547285612Sdelphij * @param[in,out] res_val the option data derived from the XML element 548285612Sdelphij * 549285612Sdelphij * @returns the place to resume scanning input 550285612Sdelphij */ 551285612Sdelphijstatic char const * 552285612Sdelphijscan_xml(char const * xml_name, tOptionValue * res_val) 553285612Sdelphij{ 554285612Sdelphij size_t nm_len, v_len; 555285612Sdelphij char const * scan; 556285612Sdelphij char const * val_str; 557285612Sdelphij tOptionValue valu; 558285612Sdelphij tOptionLoadMode save_mode = option_load_mode; 559181834Sroberto 560285612Sdelphij if (! IS_VAR_FIRST_CHAR(*++xml_name)) 561285612Sdelphij return unnamed_xml(xml_name); 562181834Sroberto 563285612Sdelphij /* 564285612Sdelphij * "scan_xml_name()" may change "option_load_mode". 565285612Sdelphij */ 566285612Sdelphij val_str = scan_xml_name(xml_name, &nm_len, &valu); 567285612Sdelphij if (val_str == NULL) 568285612Sdelphij goto bail_scan_xml; 569181834Sroberto 570285612Sdelphij if (valu.valType == OPARG_TYPE_NONE) 571285612Sdelphij scan = val_str; 572285612Sdelphij else { 573285612Sdelphij if (option_load_mode != OPTION_LOAD_KEEP) 574285612Sdelphij val_str = SPN_WHITESPACE_CHARS(val_str); 575285612Sdelphij scan = find_end_xml(xml_name, nm_len, val_str, &v_len); 576285612Sdelphij if (scan == NULL) 577285612Sdelphij goto bail_scan_xml; 578181834Sroberto } 579181834Sroberto 580285612Sdelphij /* 581285612Sdelphij * "scan" now points to where the scan is to resume after returning. 582285612Sdelphij * It either points after "/>" at the end of the XML element header, 583285612Sdelphij * or it points after the "</name>" tail based on the name in the header. 584285612Sdelphij */ 585285612Sdelphij 586181834Sroberto switch (valu.valType) { 587181834Sroberto case OPARG_TYPE_NONE: 588285612Sdelphij add_string(&(res_val->v.nestVal), xml_name, nm_len, NULL, 0); 589181834Sroberto break; 590181834Sroberto 591181834Sroberto case OPARG_TYPE_STRING: 592285612Sdelphij { 593285612Sdelphij tOptionValue * new_val = add_string( 594285612Sdelphij &(res_val->v.nestVal), xml_name, nm_len, val_str, v_len); 595181834Sroberto 596285612Sdelphij if (option_load_mode != OPTION_LOAD_KEEP) 597285612Sdelphij munge_str(new_val->v.strVal, option_load_mode); 598285612Sdelphij 599181834Sroberto break; 600285612Sdelphij } 601181834Sroberto 602181834Sroberto case OPARG_TYPE_BOOLEAN: 603285612Sdelphij add_bool(&(res_val->v.nestVal), xml_name, nm_len, val_str, v_len); 604181834Sroberto break; 605181834Sroberto 606181834Sroberto case OPARG_TYPE_NUMERIC: 607285612Sdelphij add_number(&(res_val->v.nestVal), xml_name, nm_len, val_str, v_len); 608181834Sroberto break; 609181834Sroberto 610181834Sroberto case OPARG_TYPE_HIERARCHY: 611181834Sroberto { 612285612Sdelphij char * pz = AGALOC(v_len+1, "h scan"); 613285612Sdelphij memcpy(pz, val_str, v_len); 614285612Sdelphij pz[v_len] = NUL; 615285612Sdelphij add_nested(&(res_val->v.nestVal), xml_name, nm_len, pz, v_len); 616181834Sroberto AGFREE(pz); 617181834Sroberto break; 618181834Sroberto } 619181834Sroberto 620181834Sroberto case OPARG_TYPE_ENUMERATION: 621181834Sroberto case OPARG_TYPE_MEMBERSHIP: 622181834Sroberto default: 623181834Sroberto break; 624181834Sroberto } 625181834Sroberto 626181834Sroberto option_load_mode = save_mode; 627285612Sdelphij return scan; 628285612Sdelphij 629285612Sdelphijbail_scan_xml: 630285612Sdelphij option_load_mode = save_mode; 631285612Sdelphij return NULL; 632181834Sroberto} 633181834Sroberto 634181834Sroberto 635285612Sdelphij/** 636181834Sroberto * Deallocate a list of option arguments. This must have been gotten from 637181834Sroberto * a hierarchical option argument, not a stacked list of strings. It is 638181834Sroberto * an internal call, so it is not validated. The caller is responsible for 639181834Sroberto * knowing what they are doing. 640181834Sroberto */ 641285612SdelphijLOCAL void 642285612Sdelphijunload_arg_list(tArgList * arg_list) 643181834Sroberto{ 644285612Sdelphij int ct = arg_list->useCt; 645285612Sdelphij char const ** pnew_val = arg_list->apzArgs; 646181834Sroberto 647181834Sroberto while (ct-- > 0) { 648285612Sdelphij tOptionValue * new_val = (tOptionValue *)VOIDP(*(pnew_val++)); 649285612Sdelphij if (new_val->valType == OPARG_TYPE_HIERARCHY) 650285612Sdelphij unload_arg_list(new_val->v.nestVal); 651285612Sdelphij AGFREE(new_val); 652181834Sroberto } 653181834Sroberto 654285612Sdelphij AGFREE(arg_list); 655181834Sroberto} 656181834Sroberto 657181834Sroberto/*=export_func optionUnloadNested 658181834Sroberto * 659181834Sroberto * what: Deallocate the memory for a nested value 660181834Sroberto * arg: + tOptionValue const * + pOptVal + the hierarchical value + 661181834Sroberto * 662181834Sroberto * doc: 663181834Sroberto * A nested value needs to be deallocated. The pointer passed in should 664181834Sroberto * have been gotten from a call to @code{configFileLoad()} (See 665181834Sroberto * @pxref{libopts-configFileLoad}). 666181834Sroberto=*/ 667181834Srobertovoid 668285612SdelphijoptionUnloadNested(tOptionValue const * opt_val) 669181834Sroberto{ 670285612Sdelphij if (opt_val == NULL) return; 671285612Sdelphij if (opt_val->valType != OPARG_TYPE_HIERARCHY) { 672181834Sroberto errno = EINVAL; 673181834Sroberto return; 674181834Sroberto } 675181834Sroberto 676285612Sdelphij unload_arg_list(opt_val->v.nestVal); 677181834Sroberto 678285612Sdelphij AGFREE(opt_val); 679181834Sroberto} 680181834Sroberto 681285612Sdelphij/** 682181834Sroberto * This is a _stable_ sort. The entries are sorted alphabetically, 683181834Sroberto * but within entries of the same name the ordering is unchanged. 684181834Sroberto * Typically, we also hope the input is sorted. 685181834Sroberto */ 686181834Srobertostatic void 687285612Sdelphijsort_list(tArgList * arg_list) 688181834Sroberto{ 689181834Sroberto int ix; 690285612Sdelphij int lm = arg_list->useCt; 691181834Sroberto 692181834Sroberto /* 693181834Sroberto * This loop iterates "useCt" - 1 times. 694181834Sroberto */ 695181834Sroberto for (ix = 0; ++ix < lm;) { 696181834Sroberto int iy = ix-1; 697285612Sdelphij tOptionValue * new_v = C(tOptionValue *, arg_list->apzArgs[ix]); 698285612Sdelphij tOptionValue * old_v = C(tOptionValue *, arg_list->apzArgs[iy]); 699181834Sroberto 700181834Sroberto /* 701181834Sroberto * For as long as the new entry precedes the "old" entry, 702181834Sroberto * move the old pointer. Stop before trying to extract the 703181834Sroberto * "-1" entry. 704181834Sroberto */ 705285612Sdelphij while (strcmp(old_v->pzName, new_v->pzName) > 0) { 706285612Sdelphij arg_list->apzArgs[iy+1] = VOIDP(old_v); 707285612Sdelphij old_v = (tOptionValue *)VOIDP(arg_list->apzArgs[--iy]); 708181834Sroberto if (iy < 0) 709181834Sroberto break; 710181834Sroberto } 711181834Sroberto 712181834Sroberto /* 713181834Sroberto * Always store the pointer. Sometimes it is redundant, 714181834Sroberto * but the redundancy is cheaper than a test and branch sequence. 715181834Sroberto */ 716285612Sdelphij arg_list->apzArgs[iy+1] = VOIDP(new_v); 717181834Sroberto } 718181834Sroberto} 719181834Sroberto 720285612Sdelphij/*= 721181834Sroberto * private: 722181834Sroberto * 723181834Sroberto * what: parse a hierarchical option argument 724285612Sdelphij * arg: + char const * + pzTxt + the text to scan + 725285612Sdelphij * arg: + char const * + pzName + the name for the text + 726285612Sdelphij * arg: + size_t + nm_len + the length of "name" + 727181834Sroberto * 728285612Sdelphij * ret_type: tOptionValue * 729181834Sroberto * ret_desc: An allocated, compound value structure 730181834Sroberto * 731181834Sroberto * doc: 732181834Sroberto * A block of text represents a series of values. It may be an 733181834Sroberto * entire configuration file, or it may be an argument to an 734181834Sroberto * option that takes a hierarchical value. 735285612Sdelphij * 736285612Sdelphij * If NULL is returned, errno will be set: 737285612Sdelphij * @itemize @bullet 738285612Sdelphij * @item 739285612Sdelphij * @code{EINVAL} the input text was NULL. 740285612Sdelphij * @item 741285612Sdelphij * @code{ENOMEM} the storage structures could not be allocated 742285612Sdelphij * @item 743285612Sdelphij * @code{ENOMSG} no configuration values were found 744285612Sdelphij * @end itemize 745285612Sdelphij=*/ 746285612SdelphijLOCAL tOptionValue * 747285612SdelphijoptionLoadNested(char const * text, char const * name, size_t nm_len) 748181834Sroberto{ 749285612Sdelphij tOptionValue * res_val; 750181834Sroberto 751181834Sroberto /* 752181834Sroberto * Make sure we have some data and we have space to put what we find. 753181834Sroberto */ 754285612Sdelphij if (text == NULL) { 755181834Sroberto errno = EINVAL; 756181834Sroberto return NULL; 757181834Sroberto } 758285612Sdelphij text = SPN_WHITESPACE_CHARS(text); 759285612Sdelphij if (*text == NUL) { 760285612Sdelphij errno = ENOMSG; 761181834Sroberto return NULL; 762181834Sroberto } 763285612Sdelphij res_val = AGALOC(sizeof(*res_val) + nm_len + 1, "nest args"); 764285612Sdelphij res_val->valType = OPARG_TYPE_HIERARCHY; 765285612Sdelphij res_val->pzName = (char *)(res_val + 1); 766285612Sdelphij memcpy(res_val->pzName, name, nm_len); 767285612Sdelphij res_val->pzName[nm_len] = NUL; 768181834Sroberto 769285612Sdelphij { 770285612Sdelphij tArgList * arg_list = AGALOC(sizeof(*arg_list), "nest arg l"); 771285612Sdelphij 772285612Sdelphij res_val->v.nestVal = arg_list; 773285612Sdelphij arg_list->useCt = 0; 774285612Sdelphij arg_list->allocCt = MIN_ARG_ALLOC_CT; 775181834Sroberto } 776181834Sroberto 777181834Sroberto /* 778181834Sroberto * Scan until we hit a NUL. 779181834Sroberto */ 780181834Sroberto do { 781285612Sdelphij text = SPN_WHITESPACE_CHARS(text); 782285612Sdelphij if (IS_VAR_FIRST_CHAR(*text)) 783285612Sdelphij text = scan_name(text, res_val); 784285612Sdelphij 785285612Sdelphij else switch (*text) { 786181834Sroberto case NUL: goto scan_done; 787285612Sdelphij case '<': text = scan_xml(text, res_val); 788285612Sdelphij if (text == NULL) goto woops; 789285612Sdelphij if (*text == ',') text++; break; 790285612Sdelphij case '#': text = strchr(text, NL); break; 791181834Sroberto default: goto woops; 792181834Sroberto } 793285612Sdelphij } while (text != NULL); scan_done:; 794181834Sroberto 795285612Sdelphij { 796285612Sdelphij tArgList * al = res_val->v.nestVal; 797285612Sdelphij if (al->useCt == 0) { 798285612Sdelphij errno = ENOMSG; 799285612Sdelphij goto woops; 800285612Sdelphij } 801285612Sdelphij if (al->useCt > 1) 802285612Sdelphij sort_list(al); 803181834Sroberto } 804181834Sroberto 805285612Sdelphij return res_val; 806285612Sdelphij 807181834Sroberto woops: 808285612Sdelphij AGFREE(res_val->v.nestVal); 809285612Sdelphij AGFREE(res_val); 810181834Sroberto return NULL; 811181834Sroberto} 812181834Sroberto 813181834Sroberto/*=export_func optionNestedVal 814181834Sroberto * private: 815181834Sroberto * 816181834Sroberto * what: parse a hierarchical option argument 817285612Sdelphij * arg: + tOptions * + opts + program options descriptor + 818285612Sdelphij * arg: + tOptDesc * + od + the descriptor for this arg + 819181834Sroberto * 820181834Sroberto * doc: 821181834Sroberto * Nested value was found on the command line 822181834Sroberto=*/ 823181834Srobertovoid 824285612SdelphijoptionNestedVal(tOptions * opts, tOptDesc * od) 825181834Sroberto{ 826285612Sdelphij if (opts < OPTPROC_EMIT_LIMIT) 827285612Sdelphij return; 828181834Sroberto 829285612Sdelphij if (od->fOptState & OPTST_RESET) { 830285612Sdelphij tArgList * arg_list = od->optCookie; 831285612Sdelphij int ct; 832285612Sdelphij char const ** av; 833285612Sdelphij 834285612Sdelphij if (arg_list == NULL) 835285612Sdelphij return; 836285612Sdelphij ct = arg_list->useCt; 837285612Sdelphij av = arg_list->apzArgs; 838285612Sdelphij 839285612Sdelphij while (--ct >= 0) { 840285612Sdelphij void * p = VOIDP(*(av++)); 841285612Sdelphij optionUnloadNested((tOptionValue const *)p); 842285612Sdelphij } 843285612Sdelphij 844285612Sdelphij AGFREE(od->optCookie); 845285612Sdelphij 846285612Sdelphij } else { 847285612Sdelphij tOptionValue * opt_val = optionLoadNested( 848285612Sdelphij od->optArg.argString, od->pz_Name, strlen(od->pz_Name)); 849285612Sdelphij 850285612Sdelphij if (opt_val != NULL) 851285612Sdelphij addArgListEntry(&(od->optCookie), VOIDP(opt_val)); 852285612Sdelphij } 853181834Sroberto} 854285612Sdelphij 855285612Sdelphij/** 856285612Sdelphij * get_special_char 857285612Sdelphij */ 858285612SdelphijLOCAL int 859285612Sdelphijget_special_char(char const ** ppz, int * ct) 860285612Sdelphij{ 861285612Sdelphij char const * pz = *ppz; 862294569Sdelphij char * rz; 863285612Sdelphij 864285612Sdelphij if (*ct < 3) 865285612Sdelphij return '&'; 866285612Sdelphij 867285612Sdelphij if (*pz == '#') { 868285612Sdelphij int base = 10; 869285612Sdelphij int retch; 870285612Sdelphij 871285612Sdelphij pz++; 872285612Sdelphij if (*pz == 'x') { 873285612Sdelphij base = 16; 874285612Sdelphij pz++; 875285612Sdelphij } 876294569Sdelphij retch = (int)strtoul(pz, &rz, base); 877294569Sdelphij pz = rz; 878285612Sdelphij if (*pz != ';') 879285612Sdelphij return '&'; 880285612Sdelphij base = (int)(++pz - *ppz); 881285612Sdelphij if (base > *ct) 882285612Sdelphij return '&'; 883285612Sdelphij 884285612Sdelphij *ct -= base; 885285612Sdelphij *ppz = pz; 886285612Sdelphij return retch; 887285612Sdelphij } 888285612Sdelphij 889285612Sdelphij { 890285612Sdelphij int ctr = sizeof(xml_xlate) / sizeof(xml_xlate[0]); 891285612Sdelphij xml_xlate_t const * xlatp = xml_xlate; 892285612Sdelphij 893285612Sdelphij for (;;) { 894285612Sdelphij if ( (*ct >= xlatp->xml_len) 895285612Sdelphij && (strncmp(pz, xlatp->xml_txt, (size_t)xlatp->xml_len) == 0)) { 896285612Sdelphij *ppz += xlatp->xml_len; 897285612Sdelphij *ct -= xlatp->xml_len; 898285612Sdelphij return xlatp->xml_ch; 899285612Sdelphij } 900285612Sdelphij 901285612Sdelphij if (--ctr <= 0) 902285612Sdelphij break; 903285612Sdelphij xlatp++; 904285612Sdelphij } 905285612Sdelphij } 906285612Sdelphij return '&'; 907285612Sdelphij} 908285612Sdelphij 909285612Sdelphij/** 910285612Sdelphij * emit_special_char 911285612Sdelphij */ 912285612SdelphijLOCAL void 913285612Sdelphijemit_special_char(FILE * fp, int ch) 914285612Sdelphij{ 915285612Sdelphij int ctr = sizeof(xml_xlate) / sizeof(xml_xlate[0]); 916285612Sdelphij xml_xlate_t const * xlatp = xml_xlate; 917285612Sdelphij 918285612Sdelphij putc('&', fp); 919285612Sdelphij for (;;) { 920285612Sdelphij if (ch == xlatp->xml_ch) { 921285612Sdelphij fputs(xlatp->xml_txt, fp); 922285612Sdelphij return; 923285612Sdelphij } 924285612Sdelphij if (--ctr <= 0) 925285612Sdelphij break; 926285612Sdelphij xlatp++; 927285612Sdelphij } 928285612Sdelphij fprintf(fp, XML_HEX_BYTE_FMT, (ch & 0xFF)); 929285612Sdelphij} 930285612Sdelphij 931285612Sdelphij/** @} 932285612Sdelphij * 933181834Sroberto * Local Variables: 934181834Sroberto * mode: C 935181834Sroberto * c-file-style: "stroustrup" 936181834Sroberto * indent-tabs-mode: nil 937181834Sroberto * End: 938181834Sroberto * end of autoopts/nested.c */ 939