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