1/*	$NetBSD: nested.c,v 1.2 2010/12/04 23:08:34 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
31typedef struct {
32    int     xml_ch;
33    int     xml_len;
34    char    xml_txt[8];
35} xml_xlate_t;
36
37static 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 */
47static void
48removeLineContinue( char* pzSrc );
49
50static char const*
51scanQuotedString( char const* pzTxt );
52
53static tOptionValue*
54addStringValue( void** pp, char const* pzName, size_t nameLen,
55                char const* pzValue, size_t dataLen );
56
57static tOptionValue*
58addBoolValue( void** pp, char const* pzName, size_t nameLen,
59                char const* pzValue, size_t dataLen );
60
61static tOptionValue*
62addNumberValue( void** pp, char const* pzName, size_t nameLen,
63                char const* pzValue, size_t dataLen );
64
65static tOptionValue*
66addNestedValue( void** pp, char const* pzName, size_t nameLen,
67                char* pzValue, size_t dataLen );
68
69static char const*
70scanNameEntry(char const* pzName, tOptionValue* pRes);
71
72static char const*
73scanXmlEntry( char const* pzName, tOptionValue* pRes );
74
75static void
76unloadNestedArglist( tArgList* pAL );
77
78static void
79sortNestedList( 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 */
87static void
88removeLineContinue( 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 */
127static char const*
128scanQuotedString( 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 */
165static tOptionValue*
166addStringValue( 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 */
214static tOptionValue*
215addBoolValue( 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 */
248static tOptionValue*
249addNumberValue( 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 */
279static tOptionValue*
280addNestedValue( 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 */
312static char const*
313scanNameEntry(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
329re_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 */
404static char const*
405scanXmlEntry( 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(NULL, (char *)(intptr_t)pzScan,
445	    &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 */
554static void
555unloadNestedArglist( tArgList* pAL )
556{
557    int ct = pAL->useCt;
558    tCC** ppNV = pAL->apzArgs;
559
560    while (ct-- > 0) {
561        tOptionValue* pNV = (tOptionValue*)(void*)(intptr_t)*(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=*/
581void
582optionUnloadNested( 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 */
602static void
603sortNestedList( 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*)(intptr_t)(pAL->apzArgs[ix]);
614        tOptionValue* pOldNV = (tOptionValue*)(void*)(intptr_t)(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*)(intptr_t)(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 */
653LOCAL tOptionValue*
654optionLoadNested(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=*/
731void
732optionNestedVal(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 *)(intptr_t)*(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 */
767LOCAL int
768get_special_char(char const ** ppz, int * ct)
769{
770    char const * pz = *ppz;
771    char *rz;
772
773    if (*ct < 3)
774        return '&';
775
776    if (*pz == '#') {
777        int base = 10;
778        int retch;
779
780        pz++;
781        if (*pz == 'x') {
782            base = 16;
783            pz++;
784        }
785        retch = (int)strtoul(pz, &rz, base);
786        if (*rz != ';')
787            return '&';
788        base = ++rz - *ppz;
789        if (base > *ct)
790            return '&';
791
792        *ct -= base;
793        *ppz = rz;
794        return retch;
795    }
796
797    {
798        int ctr = sizeof(xml_xlate) / sizeof(xml_xlate[0]);
799        xml_xlate_t const * xlatp = xml_xlate;
800
801        for (;;) {
802            if (  (*ct >= xlatp->xml_len)
803               && (strncmp(pz, xlatp->xml_txt, xlatp->xml_len) == 0)) {
804                *ppz += xlatp->xml_len;
805                *ct  -= xlatp->xml_len;
806                return xlatp->xml_ch;
807            }
808
809            if (--ctr <= 0)
810                break;
811            xlatp++;
812        }
813    }
814    return '&';
815}
816
817
818/*
819 * emit_special_char
820 */
821LOCAL void
822emit_special_char(FILE * fp, int ch)
823{
824    int ctr = sizeof(xml_xlate) / sizeof(xml_xlate[0]);
825    xml_xlate_t const * xlatp = xml_xlate;
826
827    putc('&', fp);
828    for (;;) {
829        if (ch == xlatp->xml_ch) {
830            fputs(xlatp->xml_txt, fp);
831            return;
832        }
833        if (--ctr <= 0)
834            break;
835        xlatp++;
836    }
837    fprintf(fp, "#x%02X;", (ch & 0xFF));
838}
839
840/*
841 * Local Variables:
842 * mode: C
843 * c-file-style: "stroustrup"
844 * indent-tabs-mode: nil
845 * End:
846 * end of autoopts/nested.c */
847