xref: /netbsd-src/external/bsd/ntp/dist/sntp/libopts/nested.c (revision b757af438b42b93f8c6571f026d8b8ef3eaf5fc9)
1 /*	$NetBSD: nested.c,v 1.4 2012/02/03 21:36:40 christos 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 
32 typedef struct {
33     int     xml_ch;
34     int     xml_len;
35     char    xml_txt[8];
36 } xml_xlate_t;
37 
38 static 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 = = = */
47 static void
48 remove_continuation(char* pzSrc);
49 
50 static char const*
51 scan_q_str(char const* pzTxt);
52 
53 static tOptionValue*
54 add_string(void** pp, char const* pzName, size_t nameLen,
55                char const* pzValue, size_t dataLen);
56 
57 static tOptionValue*
58 add_bool(void** pp, char const* pzName, size_t nameLen,
59          char const* pzValue, size_t dataLen);
60 
61 static tOptionValue*
62 add_number(void** pp, char const* pzName, size_t nameLen,
63            char const* pzValue, size_t dataLen);
64 
65 static tOptionValue*
66 add_nested(void** pp, char const* pzName, size_t nameLen,
67            char* pzValue, size_t dataLen);
68 
69 static char const *
70 scan_name(char const* pzName, tOptionValue* pRes);
71 
72 static char const*
73 scan_xml(char const* pzName, tOptionValue* pRes);
74 
75 static void
76 sort_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  */
83 static void
84 remove_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  */
121 static char const*
122 scan_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  */
158 static tOptionValue*
159 add_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  */
205 static tOptionValue*
206 add_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  */
237 static tOptionValue*
238 add_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  */
266 static tOptionValue*
267 add_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  */
297 static char const *
298 scan_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  */
388 static char const*
389 scan_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  */
537 LOCAL void
538 unload_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 =*/
563 void
564 optionUnloadNested(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  */
582 static void
583 sort_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  */
632 LOCAL tOptionValue*
633 optionLoadNested(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 =*/
713 void
714 optionNestedVal(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  */
748 LOCAL int
749 get_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  */
800 LOCAL void
801 emit_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