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