1 2 /** 3 * \file nested.c 4 * 5 * Handle options with arguments that contain nested values. 6 * 7 * @addtogroup autoopts 8 * @{ 9 */ 10 /* 11 * Automated Options Nested Values module. 12 * 13 * This file is part of AutoOpts, a companion to AutoGen. 14 * AutoOpts is free software. 15 * AutoOpts is Copyright (C) 1992-2015 by Bruce Korb - all rights reserved 16 * 17 * AutoOpts is available under any one of two licenses. The license 18 * in use must be one of these two and the choice is under the control 19 * of the user of the license. 20 * 21 * The GNU Lesser General Public License, version 3 or later 22 * See the files "COPYING.lgplv3" and "COPYING.gplv3" 23 * 24 * The Modified Berkeley Software Distribution License 25 * See the file "COPYING.mbsd" 26 * 27 * These files have the following sha256 sums: 28 * 29 * 8584710e9b04216a394078dc156b781d0b47e1729104d666658aecef8ee32e95 COPYING.gplv3 30 * 4379e7444a0e2ce2b12dd6f5a52a27a4d02d39d247901d3285c88cf0d37f477b COPYING.lgplv3 31 * 13aa749a5b0a454917a944ed8fffc530b784f5ead522b1aacaf4ec8aa55a6239 COPYING.mbsd 32 */ 33 34 typedef struct { 35 int xml_ch; 36 int xml_len; 37 char xml_txt[8]; 38 } xml_xlate_t; 39 40 static xml_xlate_t const xml_xlate[] = { 41 { '&', 4, "amp;" }, 42 { '<', 3, "lt;" }, 43 { '>', 3, "gt;" }, 44 { '"', 5, "quot;" }, 45 { '\'',5, "apos;" } 46 }; 47 48 #ifndef ENOMSG 49 #define ENOMSG ENOENT 50 #endif 51 52 /* = = = START-STATIC-FORWARD = = = */ 53 static void 54 remove_continuation(char * src); 55 56 static char const * 57 scan_q_str(char const * pzTxt); 58 59 static tOptionValue * 60 add_string(void ** pp, char const * name, size_t nm_len, 61 char const * val, size_t d_len); 62 63 static tOptionValue * 64 add_bool(void ** pp, char const * name, size_t nm_len, 65 char const * val, size_t d_len); 66 67 static tOptionValue * 68 add_number(void ** pp, char const * name, size_t nm_len, 69 char const * val, size_t d_len); 70 71 static tOptionValue * 72 add_nested(void ** pp, char const * name, size_t nm_len, 73 char * val, size_t d_len); 74 75 static char const * 76 scan_name(char const * name, tOptionValue * res); 77 78 static char const * 79 unnamed_xml(char const * txt); 80 81 static char const * 82 scan_xml_name(char const * name, size_t * nm_len, tOptionValue * val); 83 84 static char const * 85 find_end_xml(char const * src, size_t nm_len, char const * val, size_t * len); 86 87 static char const * 88 scan_xml(char const * xml_name, tOptionValue * res_val); 89 90 static void 91 sort_list(tArgList * arg_list); 92 /* = = = END-STATIC-FORWARD = = = */ 93 94 /** 95 * Backslashes are used for line continuations. We keep the newline 96 * characters, but trim out the backslash: 97 */ 98 static void 99 remove_continuation(char * src) 100 { 101 char * pzD; 102 103 do { 104 while (*src == NL) src++; 105 pzD = strchr(src, NL); 106 if (pzD == NULL) 107 return; 108 109 /* 110 * pzD has skipped at least one non-newline character and now 111 * points to a newline character. It now becomes the source and 112 * pzD goes to the previous character. 113 */ 114 src = pzD--; 115 if (*pzD != '\\') 116 pzD++; 117 } while (pzD == src); 118 119 /* 120 * Start shifting text. 121 */ 122 for (;;) { 123 char ch = ((*pzD++) = *(src++)); 124 switch (ch) { 125 case NUL: return; 126 case '\\': 127 if (*src == NL) 128 --pzD; /* rewrite on next iteration */ 129 } 130 } 131 } 132 133 /** 134 * Find the end of a quoted string, skipping escaped quote characters. 135 */ 136 static char const * 137 scan_q_str(char const * pzTxt) 138 { 139 char q = *(pzTxt++); /* remember the type of quote */ 140 141 for (;;) { 142 char ch = *(pzTxt++); 143 if (ch == NUL) 144 return pzTxt-1; 145 146 if (ch == q) 147 return pzTxt; 148 149 if (ch == '\\') { 150 ch = *(pzTxt++); 151 /* 152 * IF the next character is NUL, drop the backslash, too. 153 */ 154 if (ch == NUL) 155 return pzTxt - 2; 156 157 /* 158 * IF the quote character or the escape character were escaped, 159 * then skip both, as long as the string does not end. 160 */ 161 if ((ch == q) || (ch == '\\')) { 162 if (*(pzTxt++) == NUL) 163 return pzTxt-1; 164 } 165 } 166 } 167 } 168 169 170 /** 171 * Associate a name with either a string or no value. 172 * 173 * @param[in,out] pp argument list to add to 174 * @param[in] name the name of the "suboption" 175 * @param[in] nm_len the length of the name 176 * @param[in] val the string value for the suboption 177 * @param[in] d_len the length of the value 178 * 179 * @returns the new value structure 180 */ 181 static tOptionValue * 182 add_string(void ** pp, char const * name, size_t nm_len, 183 char const * val, size_t d_len) 184 { 185 tOptionValue * pNV; 186 size_t sz = nm_len + d_len + sizeof(*pNV); 187 188 pNV = AGALOC(sz, "option name/str value pair"); 189 190 if (val == NULL) { 191 pNV->valType = OPARG_TYPE_NONE; 192 pNV->pzName = pNV->v.strVal; 193 194 } else { 195 pNV->valType = OPARG_TYPE_STRING; 196 if (d_len > 0) { 197 char const * src = val; 198 char * pzDst = pNV->v.strVal; 199 int ct = (int)d_len; 200 do { 201 int ch = *(src++) & 0xFF; 202 if (ch == NUL) goto data_copy_done; 203 if (ch == '&') 204 ch = get_special_char(&src, &ct); 205 *(pzDst++) = (char)ch; 206 } while (--ct > 0); 207 data_copy_done: 208 *pzDst = NUL; 209 210 } else { 211 pNV->v.strVal[0] = NUL; 212 } 213 214 pNV->pzName = pNV->v.strVal + d_len + 1; 215 } 216 217 memcpy(pNV->pzName, name, nm_len); 218 pNV->pzName[ nm_len ] = NUL; 219 addArgListEntry(pp, pNV); 220 return pNV; 221 } 222 223 /** 224 * Associate a name with a boolean value 225 * 226 * @param[in,out] pp argument list to add to 227 * @param[in] name the name of the "suboption" 228 * @param[in] nm_len the length of the name 229 * @param[in] val the boolean value for the suboption 230 * @param[in] d_len the length of the value 231 * 232 * @returns the new value structure 233 */ 234 static tOptionValue * 235 add_bool(void ** pp, char const * name, size_t nm_len, 236 char const * val, size_t d_len) 237 { 238 size_t sz = nm_len + sizeof(tOptionValue) + 1; 239 tOptionValue * new_val = AGALOC(sz, "bool val"); 240 241 /* 242 * Scan over whitespace is constrained by "d_len" 243 */ 244 while (IS_WHITESPACE_CHAR(*val) && (d_len > 0)) { 245 d_len--; val++; 246 } 247 248 if (d_len == 0) 249 new_val->v.boolVal = 0; 250 251 else if (IS_DEC_DIGIT_CHAR(*val)) 252 new_val->v.boolVal = (unsigned)atoi(val); 253 254 else new_val->v.boolVal = ! IS_FALSE_TYPE_CHAR(*val); 255 256 new_val->valType = OPARG_TYPE_BOOLEAN; 257 new_val->pzName = (char *)(new_val + 1); 258 memcpy(new_val->pzName, name, nm_len); 259 new_val->pzName[ nm_len ] = NUL; 260 addArgListEntry(pp, new_val); 261 return new_val; 262 } 263 264 /** 265 * Associate a name with strtol() value, defaulting to zero. 266 * 267 * @param[in,out] pp argument list to add to 268 * @param[in] name the name of the "suboption" 269 * @param[in] nm_len the length of the name 270 * @param[in] val the numeric value for the suboption 271 * @param[in] d_len the length of the value 272 * 273 * @returns the new value structure 274 */ 275 static tOptionValue * 276 add_number(void ** pp, char const * name, size_t nm_len, 277 char const * val, size_t d_len) 278 { 279 size_t sz = nm_len + sizeof(tOptionValue) + 1; 280 tOptionValue * new_val = AGALOC(sz, "int val"); 281 282 /* 283 * Scan over whitespace is constrained by "d_len" 284 */ 285 while (IS_WHITESPACE_CHAR(*val) && (d_len > 0)) { 286 d_len--; val++; 287 } 288 if (d_len == 0) 289 new_val->v.longVal = 0; 290 else 291 new_val->v.longVal = strtol(val, 0, 0); 292 293 new_val->valType = OPARG_TYPE_NUMERIC; 294 new_val->pzName = (char *)(new_val + 1); 295 memcpy(new_val->pzName, name, nm_len); 296 new_val->pzName[ nm_len ] = NUL; 297 addArgListEntry(pp, new_val); 298 return new_val; 299 } 300 301 /** 302 * Associate a name with a nested/hierarchical value. 303 * 304 * @param[in,out] pp argument list to add to 305 * @param[in] name the name of the "suboption" 306 * @param[in] nm_len the length of the name 307 * @param[in] val the nested values for the suboption 308 * @param[in] d_len the length of the value 309 * 310 * @returns the new value structure 311 */ 312 static tOptionValue * 313 add_nested(void ** pp, char const * name, size_t nm_len, 314 char * val, size_t d_len) 315 { 316 tOptionValue * new_val; 317 318 if (d_len == 0) { 319 size_t sz = nm_len + sizeof(*new_val) + 1; 320 new_val = AGALOC(sz, "empty nest"); 321 new_val->v.nestVal = NULL; 322 new_val->valType = OPARG_TYPE_HIERARCHY; 323 new_val->pzName = (char *)(new_val + 1); 324 memcpy(new_val->pzName, name, nm_len); 325 new_val->pzName[ nm_len ] = NUL; 326 327 } else { 328 new_val = optionLoadNested(val, name, nm_len); 329 } 330 331 if (new_val != NULL) 332 addArgListEntry(pp, new_val); 333 334 return new_val; 335 } 336 337 /** 338 * We have an entry that starts with a name. Find the end of it, cook it 339 * (if called for) and create the name/value association. 340 */ 341 static char const * 342 scan_name(char const * name, tOptionValue * res) 343 { 344 tOptionValue * new_val; 345 char const * pzScan = name+1; /* we know first char is a name char */ 346 char const * pzVal; 347 size_t nm_len = 1; 348 size_t d_len = 0; 349 350 /* 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 */ 439 static char const * 440 unnamed_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 */ 472 static char const * 473 scan_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 */ 515 static char const * 516 find_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 */ 551 static char const * 552 scan_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 629 bail_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 */ 641 LOCAL void 642 unload_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 *)VOIDP(*(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(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 =*/ 667 void 668 optionUnloadNested(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(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 */ 686 static void 687 sort_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 *, arg_list->apzArgs[ix]); 698 tOptionValue * old_v = C(tOptionValue *, 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] = VOIDP(old_v); 707 old_v = (tOptionValue *)VOIDP(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] = VOIDP(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 =*/ 746 LOCAL tOptionValue * 747 optionLoadNested(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 =*/ 823 void 824 optionNestedVal(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 = VOIDP(*(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), VOIDP(opt_val)); 852 } 853 } 854 855 /** 856 * get_special_char 857 */ 858 LOCAL int 859 get_special_char(char const ** ppz, int * ct) 860 { 861 char const * pz = *ppz; 862 char * rz; 863 864 if (*ct < 3) 865 return '&'; 866 867 if (*pz == '#') { 868 int base = 10; 869 int retch; 870 871 pz++; 872 if (*pz == 'x') { 873 base = 16; 874 pz++; 875 } 876 retch = (int)strtoul(pz, &rz, base); 877 pz = rz; 878 if (*pz != ';') 879 return '&'; 880 base = (int)(++pz - *ppz); 881 if (base > *ct) 882 return '&'; 883 884 *ct -= base; 885 *ppz = pz; 886 return retch; 887 } 888 889 { 890 int ctr = sizeof(xml_xlate) / sizeof(xml_xlate[0]); 891 xml_xlate_t const * xlatp = xml_xlate; 892 893 for (;;) { 894 if ( (*ct >= xlatp->xml_len) 895 && (strncmp(pz, xlatp->xml_txt, (size_t)xlatp->xml_len) == 0)) { 896 *ppz += xlatp->xml_len; 897 *ct -= xlatp->xml_len; 898 return xlatp->xml_ch; 899 } 900 901 if (--ctr <= 0) 902 break; 903 xlatp++; 904 } 905 } 906 return '&'; 907 } 908 909 /** 910 * emit_special_char 911 */ 912 LOCAL void 913 emit_special_char(FILE * fp, int ch) 914 { 915 int ctr = sizeof(xml_xlate) / sizeof(xml_xlate[0]); 916 xml_xlate_t const * xlatp = xml_xlate; 917 918 putc('&', fp); 919 for (;;) { 920 if (ch == xlatp->xml_ch) { 921 fputs(xlatp->xml_txt, fp); 922 return; 923 } 924 if (--ctr <= 0) 925 break; 926 xlatp++; 927 } 928 fprintf(fp, XML_HEX_BYTE_FMT, (ch & 0xFF)); 929 } 930 931 /** @} 932 * 933 * Local Variables: 934 * mode: C 935 * c-file-style: "stroustrup" 936 * indent-tabs-mode: nil 937 * End: 938 * end of autoopts/nested.c */ 939