nested.c revision 280849
1139969Simp 21556Srgrimes/** 31556Srgrimes * \file nested.c 41556Srgrimes * 51556Srgrimes * Handle options with arguments that contain nested values. 61556Srgrimes * 71556Srgrimes * @addtogroup autoopts 81556Srgrimes * @{ 91556Srgrimes */ 101556Srgrimes/* 111556Srgrimes * Automated Options Nested Values module. 121556Srgrimes * 131556Srgrimes * This file is part of AutoOpts, a companion to AutoGen. 141556Srgrimes * AutoOpts is free software. 151556Srgrimes * AutoOpts is Copyright (C) 1992-2014 by Bruce Korb - all rights reserved 161556Srgrimes * 171556Srgrimes * AutoOpts is available under any one of two licenses. The license 181556Srgrimes * in use must be one of these two and the choice is under the control 191556Srgrimes * of the user of the license. 201556Srgrimes * 211556Srgrimes * The GNU Lesser General Public License, version 3 or later 221556Srgrimes * See the files "COPYING.lgplv3" and "COPYING.gplv3" 231556Srgrimes * 241556Srgrimes * The Modified Berkeley Software Distribution License 251556Srgrimes * See the file "COPYING.mbsd" 261556Srgrimes * 271556Srgrimes * These files have the following sha256 sums: 281556Srgrimes * 291556Srgrimes * 8584710e9b04216a394078dc156b781d0b47e1729104d666658aecef8ee32e95 COPYING.gplv3 301556Srgrimes * 4379e7444a0e2ce2b12dd6f5a52a27a4d02d39d247901d3285c88cf0d37f477b COPYING.lgplv3 311556Srgrimes * 13aa749a5b0a454917a944ed8fffc530b784f5ead522b1aacaf4ec8aa55a6239 COPYING.mbsd 321556Srgrimes */ 3350471Speter 341556Srgrimestypedef struct { 35187734Strhodes int xml_ch; 361556Srgrimes int xml_len; 371556Srgrimes char xml_txt[8]; 381556Srgrimes} xml_xlate_t; 391556Srgrimes 401556Srgrimesstatic xml_xlate_t const xml_xlate[] = { 411556Srgrimes { '&', 4, "amp;" }, 4268935Sru { '<', 3, "lt;" }, 4377342Sru { '>', 3, "gt;" }, 4477342Sru { '"', 5, "quot;" }, 451556Srgrimes { '\'',5, "apos;" } 4672432Sru}; 471556Srgrimes 481556Srgrimes#ifndef ENOMSG 4935773Scharnier#define ENOMSG ENOENT 501556Srgrimes#endif 511556Srgrimes 521556Srgrimes/* = = = START-STATIC-FORWARD = = = */ 531556Srgrimesstatic void 541556Srgrimesremove_continuation(char * src); 551556Srgrimes 56107230Srustatic char const* 57107230Sruscan_q_str(char const* pzTxt); 58107230Sru 59107230Srustatic tOptionValue * 60107230Sruadd_string(void ** pp, char const * name, size_t nm_len, 61107230Sru char const * val, size_t d_len); 62107230Sru 631556Srgrimesstatic tOptionValue * 641556Srgrimesadd_bool(void ** pp, char const * name, size_t nm_len, 651556Srgrimes char const * val, size_t d_len); 661556Srgrimes 6757274Sunfurlstatic tOptionValue* 6857274Sunfurladd_number(void ** pp, char const * name, size_t nm_len, 69107230Sru char const * val, size_t d_len); 70107230Sru 71107230Srustatic tOptionValue* 721556Srgrimesadd_nested(void ** pp, char const * name, size_t nm_len, 731556Srgrimes char * val, size_t d_len); 741556Srgrimes 751556Srgrimesstatic char const * 761556Srgrimesscan_name(char const * name, tOptionValue * res); 771556Srgrimes 781556Srgrimesstatic char const * 791556Srgrimesunnamed_xml(char const * txt); 8077160Sru 811556Srgrimesstatic char const * 821556Srgrimesscan_xml_name(char const * name, size_t * nm_len, tOptionValue * val); 831556Srgrimes 8453780Sobrienstatic char const * 8553780Sobrienfind_end_xml(char const * src, size_t nm_len, char const * val, size_t * len); 8653780Sobrien 87101569Srustatic char const * 88101569Sruscan_xml(char const * xml_name, tOptionValue * res_val); 89101297Sobrien 90101297Sobrienstatic void 91101297Sobriensort_list(tArgList * arg_list); 921556Srgrimes/* = = = END-STATIC-FORWARD = = = */ 931556Srgrimes 941556Srgrimes/** 951556Srgrimes * Backslashes are used for line continuations. We keep the newline 961556Srgrimes * characters, but trim out the backslash: 971556Srgrimes */ 981556Srgrimesstatic void 991556Srgrimesremove_continuation(char * src) 1001556Srgrimes{ 1011556Srgrimes char* pzD; 1021556Srgrimes 1031556Srgrimes do { 1041556Srgrimes while (*src == NL) src++; 1051556Srgrimes pzD = strchr(src, NL); 1061556Srgrimes if (pzD == NULL) 107140353Sru return; 10881687Sru 1091556Srgrimes /* 1101556Srgrimes * pzD has skipped at least one non-newline character and now 11136175Sjkoshy * points to a newline character. It now becomes the source and 11236175Sjkoshy * pzD goes to the previous character. 1131556Srgrimes */ 1141556Srgrimes src = pzD--; 1151556Srgrimes if (*pzD != '\\') 116107230Sru pzD++; 117107230Sru } while (pzD == src); 11836175Sjkoshy 119107230Sru /* 12079754Sdd * Start shifting text. 12179754Sdd */ 12279754Sdd for (;;) { 12331144Sjulian char ch = ((*pzD++) = *(src++)); 12479754Sdd switch (ch) { 125107230Sru case NUL: return; 12636175Sjkoshy case '\\': 12749721Schris if (*src == NL) 1281556Srgrimes --pzD; /* rewrite on next iteration */ 129107230Sru } 130107230Sru } 13179754Sdd} 1321556Srgrimes 133107230Sru/** 13499847Skeramida * Find the end of a quoted string, skipping escaped quote characters. 13599847Skeramida */ 13699847Skeramidastatic char const* 137221845Spluknetscan_q_str(char const* pzTxt) 1381556Srgrimes{ 13936175Sjkoshy char q = *(pzTxt++); /* remember the type of quote */ 1401556Srgrimes 14136175Sjkoshy for (;;) { 1421556Srgrimes char ch = *(pzTxt++); 143101569Sru if (ch == NUL) 144101569Sru return pzTxt-1; 14536175Sjkoshy 14636175Sjkoshy if (ch == q) 14736175Sjkoshy return pzTxt; 14836175Sjkoshy 14936175Sjkoshy if (ch == '\\') { 15036175Sjkoshy ch = *(pzTxt++); 151101569Sru /* 152101569Sru * IF the next character is NUL, drop the backslash, too. 15336175Sjkoshy */ 15436175Sjkoshy if (ch == NUL) 15536175Sjkoshy return pzTxt - 2; 15636175Sjkoshy 15736175Sjkoshy /* 15836175Sjkoshy * IF the quote character or the escape character were escaped, 159101569Sru * then skip both, as long as the string does not end. 160101569Sru */ 16136175Sjkoshy if ((ch == q) || (ch == '\\')) { 1621556Srgrimes if (*(pzTxt++) == NUL) 1631556Srgrimes return pzTxt-1; 16479754Sdd } 16579754Sdd } 166104318Strhodes } 16736175Sjkoshy} 1681556Srgrimes 1691556Srgrimes 1701556Srgrimes/** 1711556Srgrimes * Associate a name with either a string or no value. 17257274Sunfurl * 1731556Srgrimes * @param[in,out] pp argument list to add to 1741556Srgrimes * @param[in] name the name of the "suboption" 1751556Srgrimes * @param[in] nm_len the length of the name 1761556Srgrimes * @param[in] val the string value for the suboption 1771556Srgrimes * @param[in] d_len the length of the value 1781556Srgrimes * 1791556Srgrimes * @returns the new value structure 1801556Srgrimes */ 1811556Srgrimesstatic tOptionValue * 1821556Srgrimesadd_string(void ** pp, char const * name, size_t nm_len, 1831556Srgrimes char const * val, size_t d_len) 1841556Srgrimes{ 1851556Srgrimes tOptionValue* pNV; 1861556Srgrimes size_t sz = nm_len + d_len + sizeof(*pNV); 1871556Srgrimes 1881556Srgrimes pNV = AGALOC(sz, "option name/str value pair"); 1891556Srgrimes 1901556Srgrimes if (val == NULL) { 1911556Srgrimes pNV->valType = OPARG_TYPE_NONE; 1921556Srgrimes pNV->pzName = pNV->v.strVal; 1931556Srgrimes 1941556Srgrimes } else { 1951556Srgrimes pNV->valType = OPARG_TYPE_STRING; 1961556Srgrimes if (d_len > 0) { 1971556Srgrimes char const * src = val; 1981556Srgrimes char * pzDst = pNV->v.strVal; 1991556Srgrimes int ct = (int)d_len; 2001556Srgrimes do { 2011556Srgrimes int ch = *(src++) & 0xFF; 2021556Srgrimes if (ch == NUL) goto data_copy_done; 2031556Srgrimes if (ch == '&') 2041556Srgrimes ch = get_special_char(&src, &ct); 2051556Srgrimes *(pzDst++) = (char)ch; 2061556Srgrimes } while (--ct > 0); 2071556Srgrimes data_copy_done: 2081556Srgrimes *pzDst = NUL; 2091556Srgrimes 2101556Srgrimes } else { 21190059Ssheldonh pNV->v.strVal[0] = NUL; 2121556Srgrimes } 21390059Ssheldonh 2141556Srgrimes pNV->pzName = pNV->v.strVal + d_len + 1; 21590059Ssheldonh } 2161556Srgrimes 2171556Srgrimes memcpy(pNV->pzName, name, nm_len); 2181556Srgrimes pNV->pzName[ nm_len ] = NUL; 2191556Srgrimes addArgListEntry(pp, pNV); 2201556Srgrimes return pNV; 2211556Srgrimes} 2221556Srgrimes 2231556Srgrimes/** 2241556Srgrimes * Associate a name with a boolean value 2251556Srgrimes * 2261556Srgrimes * @param[in,out] pp argument list to add to 2271556Srgrimes * @param[in] name the name of the "suboption" 2281556Srgrimes * @param[in] nm_len the length of the name 2291556Srgrimes * @param[in] val the boolean value for the suboption 2301556Srgrimes * @param[in] d_len the length of the value 231165463Sru * 232165463Sru * @returns the new value structure 2331556Srgrimes */ 2341556Srgrimesstatic tOptionValue * 2351556Srgrimesadd_bool(void ** pp, char const * name, size_t nm_len, 2361556Srgrimes char const * val, size_t d_len) 2371556Srgrimes{ 2381556Srgrimes size_t sz = nm_len + sizeof(tOptionValue) + 1; 2391556Srgrimes tOptionValue * new_val = AGALOC(sz, "bool val"); 2401556Srgrimes 2411556Srgrimes /* 2421556Srgrimes * Scan over whitespace is constrained by "d_len" 2431556Srgrimes */ 2441556Srgrimes while (IS_WHITESPACE_CHAR(*val) && (d_len > 0)) { 2451556Srgrimes d_len--; val++; 2461556Srgrimes } 2471556Srgrimes 2481556Srgrimes if (d_len == 0) 2491556Srgrimes new_val->v.boolVal = 0; 2501556Srgrimes 2511556Srgrimes else if (IS_DEC_DIGIT_CHAR(*val)) 2521556Srgrimes new_val->v.boolVal = (unsigned)atoi(val); 2531556Srgrimes 2541556Srgrimes else new_val->v.boolVal = ! IS_FALSE_TYPE_CHAR(*val); 2551556Srgrimes 2561556Srgrimes new_val->valType = OPARG_TYPE_BOOLEAN; 257165463Sru new_val->pzName = (char*)(new_val + 1); 258165463Sru memcpy(new_val->pzName, name, nm_len); 259165463Sru new_val->pzName[ nm_len ] = NUL; 2601556Srgrimes addArgListEntry(pp, new_val); 2611556Srgrimes return new_val; 2621556Srgrimes} 2631556Srgrimes 2641556Srgrimes/** 2651556Srgrimes * Associate a name with strtol() value, defaulting to zero. 2661556Srgrimes * 2671556Srgrimes * @param[in,out] pp argument list to add to 2681556Srgrimes * @param[in] name the name of the "suboption" 2691556Srgrimes * @param[in] nm_len the length of the name 2701556Srgrimes * @param[in] val the numeric value for the suboption 2711556Srgrimes * @param[in] d_len the length of the value 2721556Srgrimes * 2731556Srgrimes * @returns the new value structure 2741556Srgrimes */ 2751556Srgrimesstatic tOptionValue* 2761556Srgrimesadd_number(void ** pp, char const * name, size_t nm_len, 2771556Srgrimes char const * val, size_t d_len) 2781556Srgrimes{ 2791556Srgrimes size_t sz = nm_len + sizeof(tOptionValue) + 1; 2801556Srgrimes tOptionValue * new_val = AGALOC(sz, "int val"); 2811556Srgrimes 2821556Srgrimes /* 2831556Srgrimes * Scan over whitespace is constrained by "d_len" 284187627Strhodes */ 285187627Strhodes while (IS_WHITESPACE_CHAR(*val) && (d_len > 0)) { 286187627Strhodes d_len--; val++; 287187627Strhodes } 2881556Srgrimes if (d_len == 0) 2891556Srgrimes new_val->v.longVal = 0; 2901556Srgrimes else 2911556Srgrimes new_val->v.longVal = strtol(val, 0, 0); 2921556Srgrimes 2931556Srgrimes new_val->valType = OPARG_TYPE_NUMERIC; 2941556Srgrimes new_val->pzName = (char*)(new_val + 1); 2951556Srgrimes memcpy(new_val->pzName, name, nm_len); 2961556Srgrimes new_val->pzName[ nm_len ] = NUL; 2971556Srgrimes addArgListEntry(pp, new_val); 2981556Srgrimes return new_val; 2991556Srgrimes} 3001556Srgrimes 3011556Srgrimes/** 3021556Srgrimes * Associate a name with a nested/hierarchical value. 3031556Srgrimes * 3041556Srgrimes * @param[in,out] pp argument list to add to 3051556Srgrimes * @param[in] name the name of the "suboption" 3061556Srgrimes * @param[in] nm_len the length of the name 3071556Srgrimes * @param[in] val the nested values for the suboption 3081556Srgrimes * @param[in] d_len the length of the value 3091556Srgrimes * 3101556Srgrimes * @returns the new value structure 3111556Srgrimes */ 3121556Srgrimesstatic tOptionValue* 3131556Srgrimesadd_nested(void ** pp, char const * name, size_t nm_len, 3141556Srgrimes char * val, size_t d_len) 31553780Sobrien{ 31653780Sobrien tOptionValue* new_val; 31753780Sobrien 31853780Sobrien if (d_len == 0) { 3191556Srgrimes size_t sz = nm_len + sizeof(*new_val) + 1; 32014105Sjoerg new_val = AGALOC(sz, "empty nest"); 3211556Srgrimes new_val->v.nestVal = NULL; 322106399Stjr new_val->valType = OPARG_TYPE_HIERARCHY; 3231556Srgrimes new_val->pzName = (char*)(new_val + 1); 3241556Srgrimes memcpy(new_val->pzName, name, nm_len); 3251556Srgrimes new_val->pzName[ nm_len ] = NUL; 3261556Srgrimes 3271556Srgrimes } else { 328221845Spluknet new_val = optionLoadNested(val, name, nm_len); 3291556Srgrimes } 33036175Sjkoshy 331221845Spluknet if (new_val != NULL) 3321556Srgrimes addArgListEntry(pp, new_val); 3331556Srgrimes 33435773Scharnier return new_val; 33520411Ssteve} 33620411Ssteve 3371556Srgrimes/** 3381556Srgrimes * We have an entry that starts with a name. Find the end of it, cook it 33973281Sben * (if called for) and create the name/value association. 3401556Srgrimes */ 34173281Sbenstatic char const * 34217891Swoschscan_name(char const * name, tOptionValue * res) 34317891Swosch{ 34417891Swosch tOptionValue* new_val; 34517891Swosch char const * pzScan = name+1; /* we know first char is a name char */ 34617891Swosch char const * pzVal; 347140353Sru size_t nm_len = 1; 348141851Sru size_t d_len = 0; 349140353Sru 350187734Strhodes /* 351 * Scan over characters that name a value. These names may not end 352 * with a colon, but they may contain colons. 353 */ 354 pzScan = SPN_VALUE_NAME_CHARS(name + 1); 355 if (pzScan[-1] == ':') 356 pzScan--; 357 nm_len = (size_t)(pzScan - name); 358 359 pzScan = SPN_HORIZ_WHITE_CHARS(pzScan); 360 361 re_switch: 362 363 switch (*pzScan) { 364 case '=': 365 case ':': 366 pzScan = SPN_HORIZ_WHITE_CHARS(pzScan + 1); 367 if ((*pzScan == '=') || (*pzScan == ':')) 368 goto default_char; 369 goto re_switch; 370 371 case NL: 372 case ',': 373 pzScan++; 374 /* FALLTHROUGH */ 375 376 case NUL: 377 add_string(&(res->v.nestVal), name, nm_len, NULL, (size_t)0); 378 break; 379 380 case '"': 381 case '\'': 382 pzVal = pzScan; 383 pzScan = scan_q_str(pzScan); 384 d_len = (size_t)(pzScan - pzVal); 385 new_val = add_string(&(res->v.nestVal), name, nm_len, pzVal, 386 d_len); 387 if ((new_val != NULL) && (option_load_mode == OPTION_LOAD_COOKED)) 388 ao_string_cook(new_val->v.strVal, NULL); 389 break; 390 391 default: 392 default_char: 393 /* 394 * We have found some strange text value. It ends with a newline 395 * or a comma. 396 */ 397 pzVal = pzScan; 398 for (;;) { 399 char ch = *(pzScan++); 400 switch (ch) { 401 case NUL: 402 pzScan--; 403 d_len = (size_t)(pzScan - pzVal); 404 goto string_done; 405 /* FALLTHROUGH */ 406 407 case NL: 408 if ( (pzScan > pzVal + 2) 409 && (pzScan[-2] == '\\') 410 && (pzScan[ 0] != NUL)) 411 continue; 412 /* FALLTHROUGH */ 413 414 case ',': 415 d_len = (size_t)(pzScan - pzVal) - 1; 416 string_done: 417 new_val = add_string(&(res->v.nestVal), name, nm_len, 418 pzVal, d_len); 419 if (new_val != NULL) 420 remove_continuation(new_val->v.strVal); 421 goto leave_scan_name; 422 } 423 } 424 break; 425 } leave_scan_name:; 426 427 return pzScan; 428} 429 430/** 431 * Some xml element that does not start with a name. 432 * The next character must be either '!' (introducing a comment), 433 * or '?' (introducing an XML meta-marker of some sort). 434 * We ignore these and indicate an error (NULL result) otherwise. 435 * 436 * @param[in] txt the text within an xml bracket 437 * @returns the address of the character after the closing marker, or NULL. 438 */ 439static char const * 440unnamed_xml(char const * txt) 441{ 442 switch (*txt) { 443 default: 444 txt = NULL; 445 break; 446 447 case '!': 448 txt = strstr(txt, "-->"); 449 if (txt != NULL) 450 txt += 3; 451 break; 452 453 case '?': 454 txt = strchr(txt, '>'); 455 if (txt != NULL) 456 txt++; 457 break; 458 } 459 return txt; 460} 461 462/** 463 * Scan off the xml element name, and the rest of the header, too. 464 * Set the value type to NONE if it ends with "/>". 465 * 466 * @param[in] name the first name character (alphabetic) 467 * @param[out] nm_len the length of the name 468 * @param[out] val set valType field to STRING or NONE. 469 * 470 * @returns the scan resumption point, or NULL on error 471 */ 472static char const * 473scan_xml_name(char const * name, size_t * nm_len, tOptionValue * val) 474{ 475 char const * scan = SPN_VALUE_NAME_CHARS(name + 1); 476 *nm_len = (size_t)(scan - name); 477 if (*nm_len > 64) 478 return NULL; 479 val->valType = OPARG_TYPE_STRING; 480 481 if (IS_WHITESPACE_CHAR(*scan)) { 482 /* 483 * There are attributes following the name. Parse 'em. 484 */ 485 scan = SPN_WHITESPACE_CHARS(scan); 486 scan = parse_attrs(NULL, scan, &option_load_mode, val); 487 if (scan == NULL) 488 return NULL; /* oops */ 489 } 490 491 if (! IS_END_XML_TOKEN_CHAR(*scan)) 492 return NULL; /* oops */ 493 494 if (*scan == '/') { 495 /* 496 * Single element XML entries get inserted as an empty string. 497 */ 498 if (*++scan != '>') 499 return NULL; 500 val->valType = OPARG_TYPE_NONE; 501 } 502 return scan+1; 503} 504 505/** 506 * We've found a closing '>' without a preceding '/', thus we must search 507 * the text for '<name/>' where "name" is the name of the XML element. 508 * 509 * @param[in] name the start of the name in the element header 510 * @param[in] nm_len the length of that name 511 * @param[out] len the length of the value (string between header and 512 * the trailer/tail. 513 * @returns the character after the trailer, or NULL if not found. 514 */ 515static char const * 516find_end_xml(char const * src, size_t nm_len, char const * val, size_t * len) 517{ 518 char z[72] = "</"; 519 char * dst = z + 2; 520 521 do { 522 *(dst++) = *(src++); 523 } while (--nm_len > 0); /* nm_len is known to be 64 or less */ 524 *(dst++) = '>'; 525 *dst = NUL; 526 527 { 528 char const * res = strstr(val, z); 529 530 if (res != NULL) { 531 char const * end = (option_load_mode != OPTION_LOAD_KEEP) 532 ? SPN_WHITESPACE_BACK(val, res) 533 : res; 534 *len = (size_t)(end - val); /* includes trailing white space */ 535 res = SPN_WHITESPACE_CHARS(res + (dst - z)); 536 } 537 return res; 538 } 539} 540 541/** 542 * We've found a '<' character. We ignore this if it is a comment or a 543 * directive. If it is something else, then whatever it is we are looking 544 * at is bogus. Returning NULL stops processing. 545 * 546 * @param[in] xml_name the name of an xml bracket (usually) 547 * @param[in,out] res_val the option data derived from the XML element 548 * 549 * @returns the place to resume scanning input 550 */ 551static char const * 552scan_xml(char const * xml_name, tOptionValue * res_val) 553{ 554 size_t nm_len, v_len; 555 char const * scan; 556 char const * val_str; 557 tOptionValue valu; 558 tOptionLoadMode save_mode = option_load_mode; 559 560 if (! IS_VAR_FIRST_CHAR(*++xml_name)) 561 return unnamed_xml(xml_name); 562 563 /* 564 * "scan_xml_name()" may change "option_load_mode". 565 */ 566 val_str = scan_xml_name(xml_name, &nm_len, &valu); 567 if (val_str == NULL) 568 goto bail_scan_xml; 569 570 if (valu.valType == OPARG_TYPE_NONE) 571 scan = val_str; 572 else { 573 if (option_load_mode != OPTION_LOAD_KEEP) 574 val_str = SPN_WHITESPACE_CHARS(val_str); 575 scan = find_end_xml(xml_name, nm_len, val_str, &v_len); 576 if (scan == NULL) 577 goto bail_scan_xml; 578 } 579 580 /* 581 * "scan" now points to where the scan is to resume after returning. 582 * It either points after "/>" at the end of the XML element header, 583 * or it points after the "</name>" tail based on the name in the header. 584 */ 585 586 switch (valu.valType) { 587 case OPARG_TYPE_NONE: 588 add_string(&(res_val->v.nestVal), xml_name, nm_len, NULL, 0); 589 break; 590 591 case OPARG_TYPE_STRING: 592 { 593 tOptionValue * new_val = add_string( 594 &(res_val->v.nestVal), xml_name, nm_len, val_str, v_len); 595 596 if (option_load_mode != OPTION_LOAD_KEEP) 597 munge_str(new_val->v.strVal, option_load_mode); 598 599 break; 600 } 601 602 case OPARG_TYPE_BOOLEAN: 603 add_bool(&(res_val->v.nestVal), xml_name, nm_len, val_str, v_len); 604 break; 605 606 case OPARG_TYPE_NUMERIC: 607 add_number(&(res_val->v.nestVal), xml_name, nm_len, val_str, v_len); 608 break; 609 610 case OPARG_TYPE_HIERARCHY: 611 { 612 char * pz = AGALOC(v_len+1, "h scan"); 613 memcpy(pz, val_str, v_len); 614 pz[v_len] = NUL; 615 add_nested(&(res_val->v.nestVal), xml_name, nm_len, pz, v_len); 616 AGFREE(pz); 617 break; 618 } 619 620 case OPARG_TYPE_ENUMERATION: 621 case OPARG_TYPE_MEMBERSHIP: 622 default: 623 break; 624 } 625 626 option_load_mode = save_mode; 627 return scan; 628 629bail_scan_xml: 630 option_load_mode = save_mode; 631 return NULL; 632} 633 634 635/** 636 * Deallocate a list of option arguments. This must have been gotten from 637 * a hierarchical option argument, not a stacked list of strings. It is 638 * an internal call, so it is not validated. The caller is responsible for 639 * knowing what they are doing. 640 */ 641LOCAL void 642unload_arg_list(tArgList * arg_list) 643{ 644 int ct = arg_list->useCt; 645 char const ** pnew_val = arg_list->apzArgs; 646 647 while (ct-- > 0) { 648 tOptionValue* new_val = (tOptionValue*)(void*)(intptr_t)*(pnew_val++); 649 if (new_val->valType == OPARG_TYPE_HIERARCHY) 650 unload_arg_list(new_val->v.nestVal); 651 AGFREE(new_val); 652 } 653 654 AGFREE((void*)arg_list); 655} 656 657/*=export_func optionUnloadNested 658 * 659 * what: Deallocate the memory for a nested value 660 * arg: + tOptionValue const * + pOptVal + the hierarchical value + 661 * 662 * doc: 663 * A nested value needs to be deallocated. The pointer passed in should 664 * have been gotten from a call to @code{configFileLoad()} (See 665 * @pxref{libopts-configFileLoad}). 666=*/ 667void 668optionUnloadNested(tOptionValue const * opt_val) 669{ 670 if (opt_val == NULL) return; 671 if (opt_val->valType != OPARG_TYPE_HIERARCHY) { 672 errno = EINVAL; 673 return; 674 } 675 676 unload_arg_list(opt_val->v.nestVal); 677 678 AGFREE((void*)(intptr_t)opt_val); 679} 680 681/** 682 * This is a _stable_ sort. The entries are sorted alphabetically, 683 * but within entries of the same name the ordering is unchanged. 684 * Typically, we also hope the input is sorted. 685 */ 686static void 687sort_list(tArgList * arg_list) 688{ 689 int ix; 690 int lm = arg_list->useCt; 691 692 /* 693 * This loop iterates "useCt" - 1 times. 694 */ 695 for (ix = 0; ++ix < lm;) { 696 int iy = ix-1; 697 tOptionValue * new_v = C(tOptionValue *, (intptr_t)arg_list->apzArgs[ix]); 698 tOptionValue * old_v = C(tOptionValue *, (intptr_t)arg_list->apzArgs[iy]); 699 700 /* 701 * For as long as the new entry precedes the "old" entry, 702 * move the old pointer. Stop before trying to extract the 703 * "-1" entry. 704 */ 705 while (strcmp(old_v->pzName, new_v->pzName) > 0) { 706 arg_list->apzArgs[iy+1] = (void*)old_v; 707 old_v = (tOptionValue*)(void*)(intptr_t)(arg_list->apzArgs[--iy]); 708 if (iy < 0) 709 break; 710 } 711 712 /* 713 * Always store the pointer. Sometimes it is redundant, 714 * but the redundancy is cheaper than a test and branch sequence. 715 */ 716 arg_list->apzArgs[iy+1] = (void*)new_v; 717 } 718} 719 720/*= 721 * private: 722 * 723 * what: parse a hierarchical option argument 724 * arg: + char const * + pzTxt + the text to scan + 725 * arg: + char const * + pzName + the name for the text + 726 * arg: + size_t + nm_len + the length of "name" + 727 * 728 * ret_type: tOptionValue* 729 * ret_desc: An allocated, compound value structure 730 * 731 * doc: 732 * A block of text represents a series of values. It may be an 733 * entire configuration file, or it may be an argument to an 734 * option that takes a hierarchical value. 735 * 736 * If NULL is returned, errno will be set: 737 * @itemize @bullet 738 * @item 739 * @code{EINVAL} the input text was NULL. 740 * @item 741 * @code{ENOMEM} the storage structures could not be allocated 742 * @item 743 * @code{ENOMSG} no configuration values were found 744 * @end itemize 745=*/ 746LOCAL tOptionValue * 747optionLoadNested(char const * text, char const * name, size_t nm_len) 748{ 749 tOptionValue* res_val; 750 751 /* 752 * Make sure we have some data and we have space to put what we find. 753 */ 754 if (text == NULL) { 755 errno = EINVAL; 756 return NULL; 757 } 758 text = SPN_WHITESPACE_CHARS(text); 759 if (*text == NUL) { 760 errno = ENOMSG; 761 return NULL; 762 } 763 res_val = AGALOC(sizeof(*res_val) + nm_len + 1, "nest args"); 764 res_val->valType = OPARG_TYPE_HIERARCHY; 765 res_val->pzName = (char*)(res_val + 1); 766 memcpy(res_val->pzName, name, nm_len); 767 res_val->pzName[nm_len] = NUL; 768 769 { 770 tArgList * arg_list = AGALOC(sizeof(*arg_list), "nest arg l"); 771 772 res_val->v.nestVal = arg_list; 773 arg_list->useCt = 0; 774 arg_list->allocCt = MIN_ARG_ALLOC_CT; 775 } 776 777 /* 778 * Scan until we hit a NUL. 779 */ 780 do { 781 text = SPN_WHITESPACE_CHARS(text); 782 if (IS_VAR_FIRST_CHAR(*text)) 783 text = scan_name(text, res_val); 784 785 else switch (*text) { 786 case NUL: goto scan_done; 787 case '<': text = scan_xml(text, res_val); 788 if (text == NULL) goto woops; 789 if (*text == ',') text++; break; 790 case '#': text = strchr(text, NL); break; 791 default: goto woops; 792 } 793 } while (text != NULL); scan_done:; 794 795 { 796 tArgList * al = res_val->v.nestVal; 797 if (al->useCt == 0) { 798 errno = ENOMSG; 799 goto woops; 800 } 801 if (al->useCt > 1) 802 sort_list(al); 803 } 804 805 return res_val; 806 807 woops: 808 AGFREE(res_val->v.nestVal); 809 AGFREE(res_val); 810 return NULL; 811} 812 813/*=export_func optionNestedVal 814 * private: 815 * 816 * what: parse a hierarchical option argument 817 * arg: + tOptions* + opts + program options descriptor + 818 * arg: + tOptDesc* + od + the descriptor for this arg + 819 * 820 * doc: 821 * Nested value was found on the command line 822=*/ 823void 824optionNestedVal(tOptions * opts, tOptDesc * od) 825{ 826 if (opts < OPTPROC_EMIT_LIMIT) 827 return; 828 829 if (od->fOptState & OPTST_RESET) { 830 tArgList * arg_list = od->optCookie; 831 int ct; 832 char const ** av; 833 834 if (arg_list == NULL) 835 return; 836 ct = arg_list->useCt; 837 av = arg_list->apzArgs; 838 839 while (--ct >= 0) { 840 void * p = (void *)(intptr_t)*(av++); 841 optionUnloadNested((tOptionValue const *)p); 842 } 843 844 AGFREE(od->optCookie); 845 846 } else { 847 tOptionValue * opt_val = optionLoadNested( 848 od->optArg.argString, od->pz_Name, strlen(od->pz_Name)); 849 850 if (opt_val != NULL) 851 addArgListEntry(&(od->optCookie), (void*)opt_val); 852 } 853} 854 855/** 856 * get_special_char 857 */ 858LOCAL int 859get_special_char(char const ** ppz, int * ct) 860{ 861 char const * pz = *ppz; 862 863 if (*ct < 3) 864 return '&'; 865 866 if (*pz == '#') { 867 int base = 10; 868 int retch; 869 870 pz++; 871 if (*pz == 'x') { 872 base = 16; 873 pz++; 874 } 875 retch = (int)strtoul(pz, (char **)(intptr_t)&pz, base); 876 if (*pz != ';') 877 return '&'; 878 base = (int)(++pz - *ppz); 879 if (base > *ct) 880 return '&'; 881 882 *ct -= base; 883 *ppz = pz; 884 return retch; 885 } 886 887 { 888 int ctr = sizeof(xml_xlate) / sizeof(xml_xlate[0]); 889 xml_xlate_t const * xlatp = xml_xlate; 890 891 for (;;) { 892 if ( (*ct >= xlatp->xml_len) 893 && (strncmp(pz, xlatp->xml_txt, (size_t)xlatp->xml_len) == 0)) { 894 *ppz += xlatp->xml_len; 895 *ct -= xlatp->xml_len; 896 return xlatp->xml_ch; 897 } 898 899 if (--ctr <= 0) 900 break; 901 xlatp++; 902 } 903 } 904 return '&'; 905} 906 907/** 908 * emit_special_char 909 */ 910LOCAL void 911emit_special_char(FILE * fp, int ch) 912{ 913 int ctr = sizeof(xml_xlate) / sizeof(xml_xlate[0]); 914 xml_xlate_t const * xlatp = xml_xlate; 915 916 putc('&', fp); 917 for (;;) { 918 if (ch == xlatp->xml_ch) { 919 fputs(xlatp->xml_txt, fp); 920 return; 921 } 922 if (--ctr <= 0) 923 break; 924 xlatp++; 925 } 926 fprintf(fp, XML_HEX_BYTE_FMT, (ch & 0xFF)); 927} 928 929/** @} 930 * 931 * Local Variables: 932 * mode: C 933 * c-file-style: "stroustrup" 934 * indent-tabs-mode: nil 935 * End: 936 * end of autoopts/nested.c */ 937