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