1/* $NetBSD: nested.c,v 1.12 2020/05/25 20:47:34 christos Exp $ */ 2 3 4/** 5 * \file nested.c 6 * 7 * Handle options with arguments that contain nested values. 8 * 9 * @addtogroup autoopts 10 * @{ 11 */ 12/* 13 * Automated Options Nested Values module. 14 * 15 * This file is part of AutoOpts, a companion to AutoGen. 16 * AutoOpts is free software. 17 * AutoOpts is Copyright (C) 1992-2015 by Bruce Korb - all rights reserved 18 * 19 * AutoOpts is available under any one of two licenses. The license 20 * in use must be one of these two and the choice is under the control 21 * of the user of the license. 22 * 23 * The GNU Lesser General Public License, version 3 or later 24 * See the files "COPYING.lgplv3" and "COPYING.gplv3" 25 * 26 * The Modified Berkeley Software Distribution License 27 * See the file "COPYING.mbsd" 28 * 29 * These files have the following sha256 sums: 30 * 31 * 8584710e9b04216a394078dc156b781d0b47e1729104d666658aecef8ee32e95 COPYING.gplv3 32 * 4379e7444a0e2ce2b12dd6f5a52a27a4d02d39d247901d3285c88cf0d37f477b COPYING.lgplv3 33 * 13aa749a5b0a454917a944ed8fffc530b784f5ead522b1aacaf4ec8aa55a6239 COPYING.mbsd 34 */ 35 36typedef struct { 37 int xml_ch; 38 int xml_len; 39 char xml_txt[8]; 40} xml_xlate_t; 41 42static xml_xlate_t const xml_xlate[] = { 43 { '&', 4, "amp;" }, 44 { '<', 3, "lt;" }, 45 { '>', 3, "gt;" }, 46 { '"', 5, "quot;" }, 47 { '\'',5, "apos;" } 48}; 49 50#ifndef ENOMSG 51#define ENOMSG ENOENT 52#endif 53 54/* = = = START-STATIC-FORWARD = = = */ 55static void 56remove_continuation(char * src); 57 58static char const * 59scan_q_str(char const * pzTxt); 60 61static tOptionValue * 62add_string(void ** pp, char const * name, size_t nm_len, 63 char const * val, size_t d_len); 64 65static tOptionValue * 66add_bool(void ** pp, char const * name, size_t nm_len, 67 char const * val, size_t d_len); 68 69static tOptionValue * 70add_number(void ** pp, char const * name, size_t nm_len, 71 char const * val, size_t d_len); 72 73static tOptionValue * 74add_nested(void ** pp, char const * name, size_t nm_len, 75 char * val, size_t d_len); 76 77static char const * 78scan_name(char const * name, tOptionValue * res); 79 80static char const * 81unnamed_xml(char const * txt); 82 83static char const * 84scan_xml_name(char const * name, size_t * nm_len, tOptionValue * val); 85 86static char const * 87find_end_xml(char const * src, size_t nm_len, char const * val, size_t * len); 88 89static char const * 90scan_xml(char const * xml_name, tOptionValue * res_val); 91 92static void 93sort_list(tArgList * arg_list); 94/* = = = END-STATIC-FORWARD = = = */ 95 96/** 97 * Backslashes are used for line continuations. We keep the newline 98 * characters, but trim out the backslash: 99 */ 100static void 101remove_continuation(char * src) 102{ 103 char * pzD; 104 105 do { 106 while (*src == NL) src++; 107 pzD = strchr(src, NL); 108 if (pzD == NULL) 109 return; 110 111 /* 112 * pzD has skipped at least one non-newline character and now 113 * points to a newline character. It now becomes the source and 114 * pzD goes to the previous character. 115 */ 116 src = pzD--; 117 if (*pzD != '\\') 118 pzD++; 119 } while (pzD == src); 120 121 /* 122 * Start shifting text. 123 */ 124 for (;;) { 125 char ch = ((*pzD++) = *(src++)); 126 switch (ch) { 127 case NUL: return; 128 case '\\': 129 if (*src == NL) 130 --pzD; /* rewrite on next iteration */ 131 } 132 } 133} 134 135/** 136 * Find the end of a quoted string, skipping escaped quote characters. 137 */ 138static char const * 139scan_q_str(char const * pzTxt) 140{ 141 char q = *(pzTxt++); /* remember the type of quote */ 142 143 for (;;) { 144 char ch = *(pzTxt++); 145 if (ch == NUL) 146 return pzTxt-1; 147 148 if (ch == q) 149 return pzTxt; 150 151 if (ch == '\\') { 152 ch = *(pzTxt++); 153 /* 154 * IF the next character is NUL, drop the backslash, too. 155 */ 156 if (ch == NUL) 157 return pzTxt - 2; 158 159 /* 160 * IF the quote character or the escape character were escaped, 161 * then skip both, as long as the string does not end. 162 */ 163 if ((ch == q) || (ch == '\\')) { 164 if (*(pzTxt++) == NUL) 165 return pzTxt-1; 166 } 167 } 168 } 169} 170 171 172/** 173 * Associate a name with either a string or no value. 174 * 175 * @param[in,out] pp argument list to add to 176 * @param[in] name the name of the "suboption" 177 * @param[in] nm_len the length of the name 178 * @param[in] val the string value for the suboption 179 * @param[in] d_len the length of the value 180 * 181 * @returns the new value structure 182 */ 183static tOptionValue * 184add_string(void ** pp, char const * name, size_t nm_len, 185 char const * val, size_t d_len) 186{ 187 tOptionValue * pNV; 188 size_t sz = nm_len + d_len + sizeof(*pNV); 189 190 pNV = AGALOC(sz, "option name/str value pair"); 191 192 if (val == NULL) { 193 pNV->valType = OPARG_TYPE_NONE; 194 pNV->pzName = pNV->v.strVal; 195 196 } else { 197 pNV->valType = OPARG_TYPE_STRING; 198 if (d_len > 0) { 199 char const * src = val; 200 char * pzDst = pNV->v.strVal; 201 int ct = (int)d_len; 202 do { 203 int ch = *(src++) & 0xFF; 204 if (ch == NUL) goto data_copy_done; 205 if (ch == '&') 206 ch = get_special_char(&src, &ct); 207 *(pzDst++) = (char)ch; 208 } while (--ct > 0); 209 data_copy_done: 210 *pzDst = NUL; 211 212 } else { 213 pNV->v.strVal[0] = NUL; 214 } 215 216 pNV->pzName = pNV->v.strVal + d_len + 1; 217 } 218 219 memcpy(pNV->pzName, name, nm_len); 220 pNV->pzName[ nm_len ] = NUL; 221 addArgListEntry(pp, pNV); 222 return pNV; 223} 224 225/** 226 * Associate a name with a boolean value 227 * 228 * @param[in,out] pp argument list to add to 229 * @param[in] name the name of the "suboption" 230 * @param[in] nm_len the length of the name 231 * @param[in] val the boolean value for the suboption 232 * @param[in] d_len the length of the value 233 * 234 * @returns the new value structure 235 */ 236static tOptionValue * 237add_bool(void ** pp, char const * name, size_t nm_len, 238 char const * val, size_t d_len) 239{ 240 size_t sz = nm_len + sizeof(tOptionValue) + 1; 241 tOptionValue * new_val = AGALOC(sz, "bool val"); 242 243 /* 244 * Scan over whitespace is constrained by "d_len" 245 */ 246 while (IS_WHITESPACE_CHAR(*val) && (d_len > 0)) { 247 d_len--; val++; 248 } 249 250 if (d_len == 0) 251 new_val->v.boolVal = 0; 252 253 else if (IS_DEC_DIGIT_CHAR(*val)) 254 new_val->v.boolVal = (unsigned)atoi(val); 255 256 else new_val->v.boolVal = ! IS_FALSE_TYPE_CHAR(*val); 257 258 new_val->valType = OPARG_TYPE_BOOLEAN; 259 new_val->pzName = (char *)(new_val + 1); 260 memcpy(new_val->pzName, name, nm_len); 261 new_val->pzName[ nm_len ] = NUL; 262 addArgListEntry(pp, new_val); 263 return new_val; 264} 265 266/** 267 * Associate a name with strtol() value, defaulting to zero. 268 * 269 * @param[in,out] pp argument list to add to 270 * @param[in] name the name of the "suboption" 271 * @param[in] nm_len the length of the name 272 * @param[in] val the numeric value for the suboption 273 * @param[in] d_len the length of the value 274 * 275 * @returns the new value structure 276 */ 277static tOptionValue * 278add_number(void ** pp, char const * name, size_t nm_len, 279 char const * val, size_t d_len) 280{ 281 size_t sz = nm_len + sizeof(tOptionValue) + 1; 282 tOptionValue * new_val = AGALOC(sz, "int val"); 283 284 /* 285 * Scan over whitespace is constrained by "d_len" 286 */ 287 while (IS_WHITESPACE_CHAR(*val) && (d_len > 0)) { 288 d_len--; val++; 289 } 290 if (d_len == 0) 291 new_val->v.longVal = 0; 292 else 293 new_val->v.longVal = strtol(val, 0, 0); 294 295 new_val->valType = OPARG_TYPE_NUMERIC; 296 new_val->pzName = (char *)(new_val + 1); 297 memcpy(new_val->pzName, name, nm_len); 298 new_val->pzName[ nm_len ] = NUL; 299 addArgListEntry(pp, new_val); 300 return new_val; 301} 302 303/** 304 * Associate a name with a nested/hierarchical value. 305 * 306 * @param[in,out] pp argument list to add to 307 * @param[in] name the name of the "suboption" 308 * @param[in] nm_len the length of the name 309 * @param[in] val the nested values for the suboption 310 * @param[in] d_len the length of the value 311 * 312 * @returns the new value structure 313 */ 314static tOptionValue * 315add_nested(void ** pp, char const * name, size_t nm_len, 316 char * val, size_t d_len) 317{ 318 tOptionValue * new_val; 319 320 if (d_len == 0) { 321 size_t sz = nm_len + sizeof(*new_val) + 1; 322 new_val = AGALOC(sz, "empty nest"); 323 new_val->v.nestVal = NULL; 324 new_val->valType = OPARG_TYPE_HIERARCHY; 325 new_val->pzName = (char *)(new_val + 1); 326 memcpy(new_val->pzName, name, nm_len); 327 new_val->pzName[ nm_len ] = NUL; 328 329 } else { 330 new_val = optionLoadNested(val, name, nm_len); 331 } 332 333 if (new_val != NULL) 334 addArgListEntry(pp, new_val); 335 336 return new_val; 337} 338 339/** 340 * We have an entry that starts with a name. Find the end of it, cook it 341 * (if called for) and create the name/value association. 342 */ 343static char const * 344scan_name(char const * name, tOptionValue * res) 345{ 346 tOptionValue * new_val; 347 char const * pzScan = name+1; /* we know first char is a name char */ 348 char const * pzVal; 349 size_t nm_len = 1; 350 size_t d_len = 0; 351 352 /* 353 * Scan over characters that name a value. These names may not end 354 * with a colon, but they may contain colons. 355 */ 356 pzScan = SPN_VALUE_NAME_CHARS(name + 1); 357 if (pzScan[-1] == ':') 358 pzScan--; 359 nm_len = (size_t)(pzScan - name); 360 361 pzScan = SPN_HORIZ_WHITE_CHARS(pzScan); 362 363 re_switch: 364 365 switch (*pzScan) { 366 case '=': 367 case ':': 368 pzScan = SPN_HORIZ_WHITE_CHARS(pzScan + 1); 369 if ((*pzScan == '=') || (*pzScan == ':')) 370 goto default_char; 371 goto re_switch; 372 373 case NL: 374 case ',': 375 pzScan++; 376 /* FALLTHROUGH */ 377 378 case NUL: 379 add_string(&(res->v.nestVal), name, nm_len, NULL, (size_t)0); 380 break; 381 382 case '"': 383 case '\'': 384 pzVal = pzScan; 385 pzScan = scan_q_str(pzScan); 386 d_len = (size_t)(pzScan - pzVal); 387 new_val = add_string(&(res->v.nestVal), name, nm_len, pzVal, 388 d_len); 389 if ((new_val != NULL) && (option_load_mode == OPTION_LOAD_COOKED)) 390 ao_string_cook(new_val->v.strVal, NULL); 391 break; 392 393 default: 394 default_char: 395 /* 396 * We have found some strange text value. It ends with a newline 397 * or a comma. 398 */ 399 pzVal = pzScan; 400 for (;;) { 401 char ch = *(pzScan++); 402 switch (ch) { 403 case NUL: 404 pzScan--; 405 d_len = (size_t)(pzScan - pzVal); 406 goto string_done; 407 /* FALLTHROUGH */ 408 409 case NL: 410 if ( (pzScan > pzVal + 2) 411 && (pzScan[-2] == '\\') 412 && (pzScan[ 0] != NUL)) 413 continue; 414 /* FALLTHROUGH */ 415 416 case ',': 417 d_len = (size_t)(pzScan - pzVal) - 1; 418 string_done: 419 new_val = add_string(&(res->v.nestVal), name, nm_len, 420 pzVal, d_len); 421 if (new_val != NULL) 422 remove_continuation(new_val->v.strVal); 423 goto leave_scan_name; 424 } 425 } 426 break; 427 } leave_scan_name:; 428 429 return pzScan; 430} 431 432/** 433 * Some xml element that does not start with a name. 434 * The next character must be either '!' (introducing a comment), 435 * or '?' (introducing an XML meta-marker of some sort). 436 * We ignore these and indicate an error (NULL result) otherwise. 437 * 438 * @param[in] txt the text within an xml bracket 439 * @returns the address of the character after the closing marker, or NULL. 440 */ 441static char const * 442unnamed_xml(char const * txt) 443{ 444 switch (*txt) { 445 default: 446 txt = NULL; 447 break; 448 449 case '!': 450 txt = strstr(txt, "-->"); 451 if (txt != NULL) 452 txt += 3; 453 break; 454 455 case '?': 456 txt = strchr(txt, '>'); 457 if (txt != NULL) 458 txt++; 459 break; 460 } 461 return txt; 462} 463 464/** 465 * Scan off the xml element name, and the rest of the header, too. 466 * Set the value type to NONE if it ends with "/>". 467 * 468 * @param[in] name the first name character (alphabetic) 469 * @param[out] nm_len the length of the name 470 * @param[out] val set valType field to STRING or NONE. 471 * 472 * @returns the scan resumption point, or NULL on error 473 */ 474static char const * 475scan_xml_name(char const * name, size_t * nm_len, tOptionValue * val) 476{ 477 char const * scan = SPN_VALUE_NAME_CHARS(name + 1); 478 *nm_len = (size_t)(scan - name); 479 if (*nm_len > 64) 480 return NULL; 481 val->valType = OPARG_TYPE_STRING; 482 483 if (IS_WHITESPACE_CHAR(*scan)) { 484 /* 485 * There are attributes following the name. Parse 'em. 486 */ 487 scan = SPN_WHITESPACE_CHARS(scan); 488 scan = parse_attrs(NULL, scan, &option_load_mode, val); 489 if (scan == NULL) 490 return NULL; /* oops */ 491 } 492 493 if (! IS_END_XML_TOKEN_CHAR(*scan)) 494 return NULL; /* oops */ 495 496 if (*scan == '/') { 497 /* 498 * Single element XML entries get inserted as an empty string. 499 */ 500 if (*++scan != '>') 501 return NULL; 502 val->valType = OPARG_TYPE_NONE; 503 } 504 return scan+1; 505} 506 507/** 508 * We've found a closing '>' without a preceding '/', thus we must search 509 * the text for '<name/>' where "name" is the name of the XML element. 510 * 511 * @param[in] name the start of the name in the element header 512 * @param[in] nm_len the length of that name 513 * @param[out] len the length of the value (string between header and 514 * the trailer/tail. 515 * @returns the character after the trailer, or NULL if not found. 516 */ 517static char const * 518find_end_xml(char const * src, size_t nm_len, char const * val, size_t * len) 519{ 520 char z[72] = "</"; 521 char * dst = z + 2; 522 523 do { 524 *(dst++) = *(src++); 525 } while (--nm_len > 0); /* nm_len is known to be 64 or less */ 526 *(dst++) = '>'; 527 *dst = NUL; 528 529 { 530 char const * res = strstr(val, z); 531 532 if (res != NULL) { 533 char const * end = (option_load_mode != OPTION_LOAD_KEEP) 534 ? SPN_WHITESPACE_BACK(val, res) 535 : res; 536 *len = (size_t)(end - val); /* includes trailing white space */ 537 res = SPN_WHITESPACE_CHARS(res + (dst - z)); 538 } 539 return res; 540 } 541} 542 543/** 544 * We've found a '<' character. We ignore this if it is a comment or a 545 * directive. If it is something else, then whatever it is we are looking 546 * at is bogus. Returning NULL stops processing. 547 * 548 * @param[in] xml_name the name of an xml bracket (usually) 549 * @param[in,out] res_val the option data derived from the XML element 550 * 551 * @returns the place to resume scanning input 552 */ 553static char const * 554scan_xml(char const * xml_name, tOptionValue * res_val) 555{ 556 size_t nm_len, v_len; 557 char const * scan; 558 char const * val_str; 559 tOptionValue valu; 560 tOptionLoadMode save_mode = option_load_mode; 561 562 if (! IS_VAR_FIRST_CHAR(*++xml_name)) 563 return unnamed_xml(xml_name); 564 565 /* 566 * "scan_xml_name()" may change "option_load_mode". 567 */ 568 val_str = scan_xml_name(xml_name, &nm_len, &valu); 569 if (val_str == NULL) 570 goto bail_scan_xml; 571 572 if (valu.valType == OPARG_TYPE_NONE) 573 scan = val_str; 574 else { 575 if (option_load_mode != OPTION_LOAD_KEEP) 576 val_str = SPN_WHITESPACE_CHARS(val_str); 577 scan = find_end_xml(xml_name, nm_len, val_str, &v_len); 578 if (scan == NULL) 579 goto bail_scan_xml; 580 } 581 582 /* 583 * "scan" now points to where the scan is to resume after returning. 584 * It either points after "/>" at the end of the XML element header, 585 * or it points after the "</name>" tail based on the name in the header. 586 */ 587 588 switch (valu.valType) { 589 case OPARG_TYPE_NONE: 590 add_string(&(res_val->v.nestVal), xml_name, nm_len, NULL, 0); 591 break; 592 593 case OPARG_TYPE_STRING: 594 { 595 tOptionValue * new_val = add_string( 596 &(res_val->v.nestVal), xml_name, nm_len, val_str, v_len); 597 598 if (option_load_mode != OPTION_LOAD_KEEP) 599 munge_str(new_val->v.strVal, option_load_mode); 600 601 break; 602 } 603 604 case OPARG_TYPE_BOOLEAN: 605 add_bool(&(res_val->v.nestVal), xml_name, nm_len, val_str, v_len); 606 break; 607 608 case OPARG_TYPE_NUMERIC: 609 add_number(&(res_val->v.nestVal), xml_name, nm_len, val_str, v_len); 610 break; 611 612 case OPARG_TYPE_HIERARCHY: 613 { 614 char * pz = AGALOC(v_len+1, "h scan"); 615 memcpy(pz, val_str, v_len); 616 pz[v_len] = NUL; 617 add_nested(&(res_val->v.nestVal), xml_name, nm_len, pz, v_len); 618 AGFREE(pz); 619 break; 620 } 621 622 case OPARG_TYPE_ENUMERATION: 623 case OPARG_TYPE_MEMBERSHIP: 624 default: 625 break; 626 } 627 628 option_load_mode = save_mode; 629 return scan; 630 631bail_scan_xml: 632 option_load_mode = save_mode; 633 return NULL; 634} 635 636 637/** 638 * Deallocate a list of option arguments. This must have been gotten from 639 * a hierarchical option argument, not a stacked list of strings. It is 640 * an internal call, so it is not validated. The caller is responsible for 641 * knowing what they are doing. 642 */ 643LOCAL void 644unload_arg_list(tArgList * arg_list) 645{ 646 int ct = arg_list->useCt; 647 char const ** pnew_val = arg_list->apzArgs; 648 649 while (ct-- > 0) { 650 tOptionValue * new_val = (tOptionValue *)VOIDP(*(pnew_val++)); 651 if (new_val->valType == OPARG_TYPE_HIERARCHY) 652 unload_arg_list(new_val->v.nestVal); 653 AGFREE(new_val); 654 } 655 656 AGFREE(arg_list); 657} 658 659/*=export_func optionUnloadNested 660 * 661 * what: Deallocate the memory for a nested value 662 * arg: + tOptionValue const * + pOptVal + the hierarchical value + 663 * 664 * doc: 665 * A nested value needs to be deallocated. The pointer passed in should 666 * have been gotten from a call to @code{configFileLoad()} (See 667 * @pxref{libopts-configFileLoad}). 668=*/ 669void 670optionUnloadNested(tOptionValue const * opt_val) 671{ 672 if (opt_val == NULL) return; 673 if (opt_val->valType != OPARG_TYPE_HIERARCHY) { 674 errno = EINVAL; 675 return; 676 } 677 678 unload_arg_list(opt_val->v.nestVal); 679 680 AGFREE(opt_val); 681} 682 683/** 684 * This is a _stable_ sort. The entries are sorted alphabetically, 685 * but within entries of the same name the ordering is unchanged. 686 * Typically, we also hope the input is sorted. 687 */ 688static void 689sort_list(tArgList * arg_list) 690{ 691 int ix; 692 int lm = arg_list->useCt; 693 694 /* 695 * This loop iterates "useCt" - 1 times. 696 */ 697 for (ix = 0; ++ix < lm;) { 698 int iy = ix-1; 699 tOptionValue * new_v = C(tOptionValue *, arg_list->apzArgs[ix]); 700 tOptionValue * old_v = C(tOptionValue *, arg_list->apzArgs[iy]); 701 702 /* 703 * For as long as the new entry precedes the "old" entry, 704 * move the old pointer. Stop before trying to extract the 705 * "-1" entry. 706 */ 707 while (strcmp(old_v->pzName, new_v->pzName) > 0) { 708 arg_list->apzArgs[iy+1] = VOIDP(old_v); 709 old_v = (tOptionValue *)VOIDP(arg_list->apzArgs[--iy]); 710 if (iy < 0) 711 break; 712 } 713 714 /* 715 * Always store the pointer. Sometimes it is redundant, 716 * but the redundancy is cheaper than a test and branch sequence. 717 */ 718 arg_list->apzArgs[iy+1] = VOIDP(new_v); 719 } 720} 721 722/*= 723 * private: 724 * 725 * what: parse a hierarchical option argument 726 * arg: + char const * + pzTxt + the text to scan + 727 * arg: + char const * + pzName + the name for the text + 728 * arg: + size_t + nm_len + the length of "name" + 729 * 730 * ret_type: tOptionValue * 731 * ret_desc: An allocated, compound value structure 732 * 733 * doc: 734 * A block of text represents a series of values. It may be an 735 * entire configuration file, or it may be an argument to an 736 * option that takes a hierarchical value. 737 * 738 * If NULL is returned, errno will be set: 739 * @itemize @bullet 740 * @item 741 * @code{EINVAL} the input text was NULL. 742 * @item 743 * @code{ENOMEM} the storage structures could not be allocated 744 * @item 745 * @code{ENOMSG} no configuration values were found 746 * @end itemize 747=*/ 748LOCAL tOptionValue * 749optionLoadNested(char const * text, char const * name, size_t nm_len) 750{ 751 tOptionValue * res_val; 752 753 /* 754 * Make sure we have some data and we have space to put what we find. 755 */ 756 if (text == NULL) { 757 errno = EINVAL; 758 return NULL; 759 } 760 text = SPN_WHITESPACE_CHARS(text); 761 if (*text == NUL) { 762 errno = ENOMSG; 763 return NULL; 764 } 765 res_val = AGALOC(sizeof(*res_val) + nm_len + 1, "nest args"); 766 res_val->valType = OPARG_TYPE_HIERARCHY; 767 res_val->pzName = (char *)(res_val + 1); 768 memcpy(res_val->pzName, name, nm_len); 769 res_val->pzName[nm_len] = NUL; 770 771 { 772 tArgList * arg_list = AGALOC(sizeof(*arg_list), "nest arg l"); 773 774 res_val->v.nestVal = arg_list; 775 arg_list->useCt = 0; 776 arg_list->allocCt = MIN_ARG_ALLOC_CT; 777 } 778 779 /* 780 * Scan until we hit a NUL. 781 */ 782 do { 783 text = SPN_WHITESPACE_CHARS(text); 784 if (IS_VAR_FIRST_CHAR(*text)) 785 text = scan_name(text, res_val); 786 787 else switch (*text) { 788 case NUL: goto scan_done; 789 case '<': text = scan_xml(text, res_val); 790 if (text == NULL) goto woops; 791 if (*text == ',') text++; 792 break; 793 case '#': text = strchr(text, NL); break; 794 default: goto woops; 795 } 796 } while (text != NULL); scan_done:; 797 798 { 799 tArgList * al = res_val->v.nestVal; 800 if (al->useCt == 0) { 801 errno = ENOMSG; 802 goto woops; 803 } 804 if (al->useCt > 1) 805 sort_list(al); 806 } 807 808 return res_val; 809 810 woops: 811 AGFREE(res_val->v.nestVal); 812 AGFREE(res_val); 813 return NULL; 814} 815 816/*=export_func optionNestedVal 817 * private: 818 * 819 * what: parse a hierarchical option argument 820 * arg: + tOptions * + opts + program options descriptor + 821 * arg: + tOptDesc * + od + the descriptor for this arg + 822 * 823 * doc: 824 * Nested value was found on the command line 825=*/ 826void 827optionNestedVal(tOptions * opts, tOptDesc * od) 828{ 829 if (opts < OPTPROC_EMIT_LIMIT) 830 return; 831 832 if (od->fOptState & OPTST_RESET) { 833 tArgList * arg_list = od->optCookie; 834 int ct; 835 char const ** av; 836 837 if (arg_list == NULL) 838 return; 839 ct = arg_list->useCt; 840 av = arg_list->apzArgs; 841 842 while (--ct >= 0) { 843 void * p = VOIDP(*(av++)); 844 optionUnloadNested((tOptionValue const *)p); 845 } 846 847 AGFREE(od->optCookie); 848 849 } else { 850 tOptionValue * opt_val = optionLoadNested( 851 od->optArg.argString, od->pz_Name, strlen(od->pz_Name)); 852 853 if (opt_val != NULL) 854 addArgListEntry(&(od->optCookie), VOIDP(opt_val)); 855 } 856} 857 858/** 859 * get_special_char 860 */ 861LOCAL int 862get_special_char(char const ** ppz, int * ct) 863{ 864 char const * pz = *ppz; 865 char * rz; 866 867 if (*ct < 3) 868 return '&'; 869 870 if (*pz == '#') { 871 int base = 10; 872 int retch; 873 874 pz++; 875 if (*pz == 'x') { 876 base = 16; 877 pz++; 878 } 879 retch = (int)strtoul(pz, &rz, base); 880 pz = rz; 881 if (*pz != ';') 882 return '&'; 883 base = (int)(++pz - *ppz); 884 if (base > *ct) 885 return '&'; 886 887 *ct -= base; 888 *ppz = pz; 889 return retch; 890 } 891 892 { 893 int ctr = sizeof(xml_xlate) / sizeof(xml_xlate[0]); 894 xml_xlate_t const * xlatp = xml_xlate; 895 896 for (;;) { 897 if ( (*ct >= xlatp->xml_len) 898 && (strncmp(pz, xlatp->xml_txt, (size_t)xlatp->xml_len) == 0)) { 899 *ppz += xlatp->xml_len; 900 *ct -= xlatp->xml_len; 901 return xlatp->xml_ch; 902 } 903 904 if (--ctr <= 0) 905 break; 906 xlatp++; 907 } 908 } 909 return '&'; 910} 911 912/** 913 * emit_special_char 914 */ 915LOCAL void 916emit_special_char(FILE * fp, int ch) 917{ 918 int ctr = sizeof(xml_xlate) / sizeof(xml_xlate[0]); 919 xml_xlate_t const * xlatp = xml_xlate; 920 921 putc('&', fp); 922 for (;;) { 923 if (ch == xlatp->xml_ch) { 924 fputs(xlatp->xml_txt, fp); 925 return; 926 } 927 if (--ctr <= 0) 928 break; 929 xlatp++; 930 } 931 fprintf(fp, XML_HEX_BYTE_FMT, (ch & 0xFF)); 932} 933 934/** @} 935 * 936 * Local Variables: 937 * mode: C 938 * c-file-style: "stroustrup" 939 * indent-tabs-mode: nil 940 * End: 941 * end of autoopts/nested.c */ 942