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