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