xref: /netbsd/external/bsd/ntp/dist/sntp/libopts/nested.c (revision 6550d01e)
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