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