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