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