1/*
2 * This program was written by Richard Verhoeven (NL:5482ZX35)
3 * at the Eindhoven University of Technology. Email: rcb5@win.tue.nl
4 *
5 * Permission is granted to distribute, modify and use this program as long
6 * as this comment is not removed or changed.
7 *
8 * THIS IS A MODIFIED VERSION.  IT WAS MODIFIED BY chet@po.cwru.edu FOR
9 * USE BY BASH.
10 */
11
12/*
13 * man2html will add links to the converted manpages. The function add_links
14 * is used for that. At the moment it will add links as follows, where
15 *     indicates what should match to start with:
16 * ^^^
17 * Recognition           Item            Link
18 * ----------------------------------------------------------
19 * name(*)               Manpage         ../man?/name.*
20 *     ^
21 * name@hostname         Email address   mailto:name@hostname
22 *     ^
23 * method://string       URL             method://string
24 *       ^^^
25 * www.host.name         WWW server      http://www.host.name
26 * ^^^^
27 * ftp.host.name         FTP server      ftp://ftp.host.name
28 * ^^^^
29 * <file.h>              Include file    file:/usr/include/file.h
30 *      ^^^
31 *
32 * Since man2html does not check if manpages, hosts or email addresses exist,
33 * some links might not work. For manpages, some extra checks are performed
34 * to make sure not every () pair creates a link. Also out of date pages
35 * might point to incorrect places.
36 *
37 * The program will not allow users to get system specific files, such as
38 * /etc/passwd. It will check that "man" is part of the specified file and
39 * that  "/../" isn't. Even if someone manages to get such file, man2html will
40 * handle it like a manpage and will usually not produce any output (or crash).
41 *
42 * If you find any bugs when normal manpages are converted, please report
43 * them to me (rcb5@win.tue.nl) after you have checked that man(1) can handle
44 * the manpage correct.
45 *
46 * Known bugs and missing features:
47 *
48 *  * Equations are not converted at all.
49 *  * Tables are converted but some features are not possible in html.
50 *  * The tabbing environment is converted by counting characters and adding
51 *    spaces. This might go wrong (outside <PRE>)
52 *  * Some pages look beter if man2html works in troff mode, especially pages
53 *    with tables. You can deside at compile time which made you want to use.
54 *
55 *    -DNROFF=0     troff mode
56 *    -DNROFF=1     nroff mode   (default)
57 *
58 *    if you install both modes, you should compile with the correct CGIBASE.
59 *  * Some manpages rely on the fact that troff/nroff is used to convert
60 *    them and use features which are not descripted in the man manpages.
61 *    (definitions, calculations, conditionals, requests). I can't guarantee
62 *    that all these features work on all manpages. (I didn't have the
63 *    time to look through all the available manpages.)
64 */
65#ifdef HAVE_CONFIG_H
66#include <config.h>
67#endif
68
69#define NROFF 0
70
71#include <stdio.h>
72#include <stdlib.h>
73#include <string.h>
74#include <sys/stat.h>
75#include <ctype.h>
76#include <sys/types.h>
77#include <time.h>
78#include <sys/time.h>
79#include <errno.h>
80
81#define NULL_TERMINATED(n) ((n) + 1)
82
83#define HUGE_STR_MAX  10000
84#define LARGE_STR_MAX 2000
85#define MED_STR_MAX   500
86#define SMALL_STR_MAX 100
87#define TINY_STR_MAX  10
88
89#define MAX_MAN_PATHS 100	/* Max number of directories */
90#define MAX_ZCATS     10	/* Max number of zcat style programs */
91#define MAX_WORDLIST  100
92
93#ifndef EXIT_SUCCESS
94#define EXIT_SUCCESS 0
95#endif
96#ifndef EXIT_FAILURE
97#define EXIT_FAILURE 1
98#endif
99#ifndef EXIT_USAGE
100#define EXIT_USAGE 2
101#endif
102
103static char location_base[NULL_TERMINATED(MED_STR_MAX)] = "";
104
105static char th_page_and_sec[128] = { '\0' };
106static char th_datestr[128] = { '\0' };
107static char th_version[128] = { '\0' };
108
109char   *signature = "<HR>\nThis document was created by man2html from %s.<BR>\nTime: %s\n";
110
111/* timeformat for signature */
112#define TIMEFORMAT "%d %B %Y %T %Z"
113
114char *manpage;
115
116/* BSD mandoc Bl/El lists to HTML list types */
117#define BL_DESC_LIST   1
118#define BL_BULLET_LIST 2
119#define BL_ENUM_LIST   4
120
121/* BSD mandoc Bd/Ed example(?) blocks */
122#define BD_LITERAL  1
123#define BD_INDENT   2
124
125#ifndef HAVE_STRERROR
126static char *
127strerror(int e)
128{
129	static char emsg[40];
130
131#if defined (HAVE_SYS_ERRLIST)
132	extern int sys_nerr;
133	extern char *sys_errlist[];
134
135	if (e > 0 && e < sys_nerr)
136		return (sys_errlist[e]);
137	else
138#endif				/* HAVE_SYS_ERRLIST */
139	{
140		sprintf(emsg, "Unknown system error %d", e);
141		return (&emsg[0]);
142	}
143}
144#endif				/* !HAVE_STRERROR */
145
146static char *
147strgrow(char *old, int len)
148{
149	char   *new = realloc(old, (strlen(old) + len + 1) * sizeof(char));
150
151	if (!new) {
152		fprintf(stderr, "man2html: out of memory");
153		exit(EXIT_FAILURE);
154	}
155	return new;
156}
157
158static char *
159stralloc(int len)
160{
161	/* allocate enough for len + NULL */
162	char   *new = malloc((len + 1) * sizeof(char));
163
164	if (!new) {
165		fprintf(stderr, "man2html: out of memory");
166		exit(EXIT_FAILURE);
167	}
168	return new;
169}
170
171/*
172 * Some systems don't have strdup so lets use our own - which can also
173 * check for out of memory.
174 */
175static char *
176strduplicate(char *from)
177{
178	char   *new = stralloc(strlen(from));
179
180	strcpy(new, from);
181	return new;
182}
183
184/* Assumes space for n plus a null */
185static char *
186strmaxcpy(char *to, char *from, int n)
187{
188	int     len = strlen(from);
189
190	strncpy(to, from, n);
191	to[(len <= n) ? len : n] = '\0';
192	return to;
193}
194
195static char *
196strmaxcat(char *to, char *from, int n)
197{
198	int     to_len = strlen(to);
199
200	if (to_len < n) {
201		int     from_len = strlen(from);
202		int     cp = (to_len + from_len <= n) ? from_len : n - to_len;
203
204		strncpy(to + to_len, from, cp);
205		to[to_len + cp] = '\0';
206	}
207	return to;
208}
209
210/* Assumes space for limit plus a null */
211static char *
212strlimitcpy(char *to, char *from, int n, int limit)
213{
214	int     len = n > limit ? limit : n;
215
216	strmaxcpy(to, from, len);
217	to[len] = '\0';
218	return to;
219}
220
221/*
222 * takes string and escapes all metacharacters.  should be used before
223 * including string in system() or similar call.
224 */
225static char *
226escape_input(char *str)
227{
228	int     i, j = 0;
229	static char new[NULL_TERMINATED(MED_STR_MAX)];
230
231	if (strlen(str) * 2 + 1 > MED_STR_MAX) {
232		fprintf(stderr,
233			"man2html: escape_input - str too long:\n%-80s...\n",
234			str);
235		exit(EXIT_FAILURE);
236	}
237	for (i = 0; i < strlen(str); i++) {
238		if (!(((str[i] >= 'A') && (str[i] <= 'Z')) ||
239		      ((str[i] >= 'a') && (str[i] <= 'z')) ||
240		      ((str[i] >= '0') && (str[i] <= '9')))) {
241			new[j] = '\\';
242			j++;
243		}
244		new[j] = str[i];
245		j++;
246	}
247	new[j] = '\0';
248	return new;
249}
250
251static void
252usage(void)
253{
254	fprintf(stderr, "man2html: usage: man2html filename\n");
255}
256
257
258
259/*
260 * below this you should not change anything unless you know a lot
261 * about this program or about troff.
262 */
263
264typedef struct STRDEF STRDEF;
265struct STRDEF {
266	int     nr, slen;
267	char   *st;
268	STRDEF *next;
269};
270
271typedef struct INTDEF INTDEF;
272struct INTDEF {
273	int     nr;
274	int     val;
275	int     incr;
276	INTDEF *next;
277};
278
279static char NEWLINE[2] = "\n";
280static char idxlabel[6] = "ixAAA";
281
282#define INDEXFILE "/tmp/manindex.list"
283
284static char *fname;
285static FILE *idxfile;
286
287static STRDEF *chardef, *strdef, *defdef;
288static INTDEF *intdef;
289
290#define V(A,B) ((A)*256+(B))
291
292static INTDEF standardint[] = {
293	{V('n', ' '), NROFF, 0, NULL},
294	{V('t', ' '), 1 - NROFF, 0, NULL},
295	{V('o', ' '), 1, 0, NULL},
296	{V('e', ' '), 0, 0, NULL},
297	{V('.', 'l'), 70, 0, NULL},
298	{V('.', '$'), 0, 0, NULL},
299	{V('.', 'A'), NROFF, 0, NULL},
300	{V('.', 'T'), 1 - NROFF, 0, NULL},
301	{V('.', 'V'), 1, 0, NULL},	/* the me package tests for this */
302{0, 0, 0, NULL}};
303
304static STRDEF standardstring[] = {
305	{V('R', ' '), 1, "&#174;", NULL},
306	{V('l', 'q'), 2, "``", NULL},
307	{V('r', 'q'), 2, "''", NULL},
308	{0, 0, NULL, NULL}
309};
310
311
312static STRDEF standardchar[] = {
313	{V('*', '*'), 1, "*", NULL},
314	{V('*', 'A'), 1, "A", NULL},
315	{V('*', 'B'), 1, "B", NULL},
316	{V('*', 'C'), 2, "Xi", NULL},
317	{V('*', 'D'), 5, "Delta", NULL},
318	{V('*', 'E'), 1, "E", NULL},
319	{V('*', 'F'), 3, "Phi", NULL},
320	{V('*', 'G'), 5, "Gamma", NULL},
321	{V('*', 'H'), 5, "Theta", NULL},
322	{V('*', 'I'), 1, "I", NULL},
323	{V('*', 'K'), 1, "K", NULL},
324	{V('*', 'L'), 6, "Lambda", NULL},
325	{V('*', 'M'), 1, "M", NULL},
326	{V('*', 'N'), 1, "N", NULL},
327	{V('*', 'O'), 1, "O", NULL},
328	{V('*', 'P'), 2, "Pi", NULL},
329	{V('*', 'Q'), 3, "Psi", NULL},
330	{V('*', 'R'), 1, "P", NULL},
331	{V('*', 'S'), 5, "Sigma", NULL},
332	{V('*', 'T'), 1, "T", NULL},
333	{V('*', 'U'), 1, "Y", NULL},
334	{V('*', 'W'), 5, "Omega", NULL},
335	{V('*', 'X'), 1, "X", NULL},
336	{V('*', 'Y'), 1, "H", NULL},
337	{V('*', 'Z'), 1, "Z", NULL},
338	{V('*', 'a'), 5, "alpha", NULL},
339	{V('*', 'b'), 4, "beta", NULL},
340	{V('*', 'c'), 2, "xi", NULL},
341	{V('*', 'd'), 5, "delta", NULL},
342	{V('*', 'e'), 7, "epsilon", NULL},
343	{V('*', 'f'), 3, "phi", NULL},
344	{V('*', 'g'), 5, "gamma", NULL},
345	{V('*', 'h'), 5, "theta", NULL},
346	{V('*', 'i'), 4, "iota", NULL},
347	{V('*', 'k'), 5, "kappa", NULL},
348	{V('*', 'l'), 6, "lambda", NULL},
349	{V('*', 'm'), 1, "&#181;", NULL},
350	{V('*', 'n'), 2, "nu", NULL},
351	{V('*', 'o'), 1, "o", NULL},
352	{V('*', 'p'), 2, "pi", NULL},
353	{V('*', 'q'), 3, "psi", NULL},
354	{V('*', 'r'), 3, "rho", NULL},
355	{V('*', 's'), 5, "sigma", NULL},
356	{V('*', 't'), 3, "tau", NULL},
357	{V('*', 'u'), 7, "upsilon", NULL},
358	{V('*', 'w'), 5, "omega", NULL},
359	{V('*', 'x'), 3, "chi", NULL},
360	{V('*', 'y'), 3, "eta", NULL},
361	{V('*', 'z'), 4, "zeta", NULL},
362	{V('t', 's'), 5, "sigma", NULL},
363	{V('+', '-'), 1, "&#177;", NULL},
364	{V('1', '2'), 1, "&#189;", NULL},
365	{V('1', '4'), 1, "&#188;", NULL},
366	{V('3', '4'), 1, "&#190;", NULL},
367	{V('F', 'i'), 3, "ffi", NULL},
368	{V('F', 'l'), 3, "ffl", NULL},
369	{V('a', 'a'), 1, "&#180;", NULL},
370	{V('a', 'p'), 1, "~", NULL},
371	{V('b', 'r'), 1, "|", NULL},
372	{V('b', 'u'), 1, "*", NULL},
373	{V('b', 'v'), 1, "|", NULL},
374	{V('c', 'i'), 1, "o", NULL},
375	{V('c', 'o'), 1, "&#169;", NULL},
376	{V('c', 't'), 1, "&#162;", NULL},
377	{V('d', 'e'), 1, "&#176;", NULL},
378	{V('d', 'g'), 1, "+", NULL},
379	{V('d', 'i'), 1, "&#247;", NULL},
380	{V('e', 'm'), 1, "-", NULL},
381	{V('e', 'm'), 3, "---", NULL},
382	{V('e', 'q'), 1, "=", NULL},
383	{V('e', 's'), 1, "&#216;", NULL},
384	{V('f', 'f'), 2, "ff", NULL},
385	{V('f', 'i'), 2, "fi", NULL},
386	{V('f', 'l'), 2, "fl", NULL},
387	{V('f', 'm'), 1, "&#180;", NULL},
388	{V('g', 'a'), 1, "`", NULL},
389	{V('h', 'y'), 1, "-", NULL},
390	{V('l', 'c'), 2, "|&#175;", NULL},
391	{V('l', 'f'), 2, "|_", NULL},
392	{V('l', 'k'), 1, "<FONT SIZE=+2>{</FONT>", NULL},
393	{V('m', 'i'), 1, "-", NULL},
394	{V('m', 'u'), 1, "&#215;", NULL},
395	{V('n', 'o'), 1, "&#172;", NULL},
396	{V('o', 'r'), 1, "|", NULL},
397	{V('p', 'l'), 1, "+", NULL},
398	{V('r', 'c'), 2, "&#175;|", NULL},
399	{V('r', 'f'), 2, "_|", NULL},
400	{V('r', 'g'), 1, "&#174;", NULL},
401	{V('r', 'k'), 1, "<FONT SIZE=+2>}</FONT>", NULL},
402	{V('r', 'n'), 1, "&#175;", NULL},
403	{V('r', 'u'), 1, "_", NULL},
404	{V('s', 'c'), 1, "&#167;", NULL},
405	{V('s', 'l'), 1, "/", NULL},
406	{V('s', 'q'), 2, "[]", NULL},
407	{V('u', 'l'), 1, "_", NULL},
408	{0, 0, NULL, NULL}
409};
410
411/* default: print code */
412
413
414static char eqndelimopen = 0, eqndelimclose = 0;
415static char escapesym = '\\', nobreaksym = '\'', controlsym = '.', fieldsym = 0, padsym = 0;
416
417static char *buffer = NULL;
418static int buffpos = 0, buffmax = 0;
419static int scaninbuff = 0;
420static int itemdepth = 0;
421static int dl_set[20] = {0};
422static int still_dd = 0;
423static int tabstops[20] = {8, 16, 24, 32, 40, 48, 56, 64, 72, 80, 88, 96};
424static int maxtstop = 12;
425static int curpos = 0;
426
427static char *scan_troff(char *c, int san, char **result);
428static char *scan_troff_mandoc(char *c, int san, char **result);
429
430static char **argument = NULL;
431
432static char charb[TINY_STR_MAX];
433
434static void
435print_sig(void)
436{
437	char    datbuf[NULL_TERMINATED(MED_STR_MAX)];
438	struct tm *timetm;
439	time_t  clock;
440
441	datbuf[0] = '\0';
442	clock = time(NULL);
443	timetm = localtime(&clock);
444	strftime(datbuf, MED_STR_MAX, TIMEFORMAT, timetm);
445	printf(signature, manpage, datbuf);
446}
447
448static char *
449expand_char(int nr)
450{
451	STRDEF *h;
452
453	h = chardef;
454	if (!nr)
455		return NULL;
456	while (h)
457		if (h->nr == nr) {
458			curpos += h->slen;
459			return h->st;
460		} else
461			h = h->next;
462	charb[0] = nr / 256;
463	charb[1] = nr % 256;
464	charb[2] = '\0';
465	if (charb[0] == '<') {	/* Fix up <= */
466		charb[4] = charb[1];
467		strncpy(charb, "&lt;", 4);
468		charb[5] = '\0';
469	}
470	curpos += 2;
471	return charb;
472}
473
474static char *
475expand_string(int nr)
476{
477	STRDEF *h = strdef;
478
479	if (!nr)
480		return NULL;
481	while (h)
482		if (h->nr == nr) {
483			curpos += h->slen;
484			return h->st;
485		} else
486			h = h->next;
487	return NULL;
488}
489
490static char *
491read_man_page(char *filename)
492{
493	char   *man_buf = NULL;
494	int     i;
495	FILE   *man_stream = NULL;
496	struct stat stbuf;
497	int     buf_size;
498
499	if (stat(filename, &stbuf) == -1)
500		return NULL;
501
502	buf_size = stbuf.st_size;
503	man_buf = stralloc(buf_size + 5);
504	man_stream = fopen(filename, "r");
505	if (man_stream) {
506		man_buf[0] = '\n';
507		if (fread(man_buf + 1, 1, buf_size, man_stream) == buf_size) {
508			man_buf[buf_size] = '\n';
509			man_buf[buf_size + 1] = man_buf[buf_size + 2] = '\0';
510		} else {
511			man_buf = NULL;
512		}
513		fclose(man_stream);
514	}
515	return man_buf;
516}
517
518
519static char outbuffer[NULL_TERMINATED(HUGE_STR_MAX)];
520static int obp = 0;
521static int no_newline_output = 0;
522static int newline_for_fun = 0;
523static int output_possible = 0;
524static int out_length = 0;
525
526/*
527 * Add the links to the output. At the moment the following are
528 * recognized:
529 *
530#if 0
531 *	name(*)                 -> ../man?/name.*
532#endif
533 *	method://string         -> method://string
534 *	www.host.name		-> http://www.host.name
535 *	ftp.host.name           -> ftp://ftp.host.name
536 *	name@host               -> mailto:name@host
537 *	<name.h>                -> file:/usr/include/name.h   (guess)
538 *
539 * Other possible links to add in the future:
540 *
541 * /dir/dir/file  -> file:/dir/dir/file
542 */
543static void
544add_links(char *c)
545{
546	int     i, j, nr;
547	char   *f, *g, *h;
548	char   *idtest[6];	/* url, mailto, www, ftp, manpage */
549
550	out_length += strlen(c);
551	/* search for (section) */
552	nr = 0;
553	idtest[0] = strstr(c + 1, "://");
554	idtest[1] = strchr(c + 1, '@');
555	idtest[2] = strstr(c, "www.");
556	idtest[3] = strstr(c, "ftp.");
557#if 0
558	idtest[4] = strchr(c + 1, '(');
559#else
560	idtest[4] = 0;
561#endif
562	idtest[5] = strstr(c + 1, ".h&gt;");
563	for (i = 0; i < 6; i++)
564		nr += (idtest[i] != NULL);
565	while (nr) {
566		j = -1;
567		for (i = 0; i < 6; i++)
568			if (idtest[i] && (j < 0 || idtest[i] < idtest[j]))
569				j = i;
570		switch (j) {
571		case 5:	/* <name.h> */
572			f = idtest[5];
573			h = f + 2;
574			g = f;
575			while (g > c && g[-1] != ';')
576				g--;
577			if (g != c) {
578				char    t;
579
580				t = *g;
581				*g = '\0';
582				fputs(c, stdout);
583				*g = t;
584				*h = '\0';
585				printf("<A HREF=\"file:/usr/include/%s\">%s</A>&gt;", g, g);
586				c = f + 6;
587			} else {
588				f[5] = '\0';
589				fputs(c, stdout);
590				f[5] = ';';
591				c = f + 5;
592			}
593			break;
594		case 4:	/* manpage */
595#if 0
596			f = idtest[j];
597			/* check section */
598			g = strchr(f, ')');
599			if (g && f - g < 6 && (isalnum(f[-1]) || f[-1] == '>') &&
600			    ((isdigit(f[1]) && f[1] != '0' &&
601			      (f[2] == ')' || (isalpha(f[2]) && f[3] == ')') || f[2] == 'X')) ||
602			   (f[2] == ')' && (f[1] == 'n' || f[1] == 'l')))) {
603				/* this might be a link */
604				h = f - 1;
605				/* skip html makeup */
606				while (h > c && *h == '>') {
607					while (h != c && *h != '<')
608						h--;
609					if (h != c)
610						h--;
611				}
612				if (isalnum(*h)) {
613					char    t, sec, subsec, *e;
614
615					e = h + 1;
616					sec = f[1];
617					subsec = f[2];
618					if ((subsec == 'X' && f[3] != ')') || subsec == ')')
619						subsec = '\0';
620					while (h > c && (isalnum(h[-1]) || h[-1] == '_' ||
621					      h[-1] == '-' || h[-1] == '.'))
622						h--;
623					t = *h;
624					*h = '\0';
625					fputs(c, stdout);
626					*h = t;
627					t = *e;
628					*e = '\0';
629					if (subsec)
630						printf("<A HREF=\""
631						       CGIBASE
632						  "?man%c/%s.%c%c\">%s</A>",
633						       sec, h, sec, tolower(subsec), h);
634					else
635						printf("<A HREF=\""
636						       CGIBASE
637						    "?man%c/%s.%c\">%s</A>",
638						       sec, h, sec, h);
639					*e = t;
640					c = e;
641				}
642			}
643			*f = '\0';
644			fputs(c, stdout);
645			*f = '(';
646			idtest[4] = f - 1;
647			c = f;
648#endif
649			break;	/* manpage */
650		case 3:	/* ftp */
651		case 2:	/* www */
652			g = f = idtest[j];
653			while (*g && (isalnum(*g) || *g == '_' || *g == '-' || *g == '+' ||
654				      *g == '.'))
655				g++;
656			if (g[-1] == '.')
657				g--;
658			if (g - f > 4) {
659				char    t;
660
661				t = *f;
662				*f = '\0';
663				fputs(c, stdout);
664				*f = t;
665				t = *g;
666				*g = '\0';
667				printf("<A HREF=\"%s://%s\">%s</A>", (j == 3 ? "ftp" : "http"),
668				       f, f);
669				*g = t;
670				c = g;
671			} else {
672				f[3] = '\0';
673				fputs(c, stdout);
674				c = f + 3;
675				f[3] = '.';
676			}
677			break;
678		case 1:	/* mailto */
679			g = f = idtest[1];
680			while (g > c && (isalnum(g[-1]) || g[-1] == '_' || g[-1] == '-' ||
681			      g[-1] == '+' || g[-1] == '.' || g[-1] == '%'))
682				g--;
683			h = f + 1;
684			while (*h && (isalnum(*h) || *h == '_' || *h == '-' || *h == '+' ||
685				      *h == '.'))
686				h++;
687			if (*h == '.')
688				h--;
689			if (h - f > 4 && f - g > 1) {
690				char    t;
691
692				t = *g;
693				*g = '\0';
694				fputs(c, stdout);
695				*g = t;
696				t = *h;
697				*h = '\0';
698				printf("<A HREF=\"mailto:%s\">%s</A>", g, g);
699				*h = t;
700				c = h;
701			} else {
702				*f = '\0';
703				fputs(c, stdout);
704				*f = '@';
705				idtest[1] = c;
706				c = f;
707			}
708			break;
709		case 0:	/* url */
710			g = f = idtest[0];
711			while (g > c && isalpha(g[-1]) && islower(g[-1]))
712				g--;
713			h = f + 3;
714			while (*h && !isspace(*h) && *h != '<' && *h != '>' && *h != '"' &&
715			       *h != '&')
716				h++;
717			if (f - g > 2 && f - g < 7 && h - f > 3) {
718				char    t;
719
720				t = *g;
721				*g = '\0';
722				fputs(c, stdout);
723				*g = t;
724				t = *h;
725				*h = '\0';
726				printf("<A HREF=\"%s\">%s</A>", g, g);
727				*h = t;
728				c = h;
729			} else {
730				f[1] = '\0';
731				fputs(c, stdout);
732				f[1] = '/';
733				c = f + 1;
734			}
735			break;
736		default:
737			break;
738		}
739		nr = 0;
740		if (idtest[0] && idtest[0] < c)
741			idtest[0] = strstr(c + 1, "://");
742		if (idtest[1] && idtest[1] < c)
743			idtest[1] = strchr(c + 1, '@');
744		if (idtest[2] && idtest[2] < c)
745			idtest[2] = strstr(c, "www.");
746		if (idtest[3] && idtest[3] < c)
747			idtest[3] = strstr(c, "ftp.");
748		if (idtest[4] && idtest[4] < c)
749			idtest[4] = strchr(c + 1, '(');
750		if (idtest[5] && idtest[5] < c)
751			idtest[5] = strstr(c + 1, ".h&gt;");
752		for (i = 0; i < 6; i++)
753			nr += (idtest[i] != NULL);
754	}
755	fputs(c, stdout);
756}
757
758static int current_font = 0;
759static int current_size = 0;
760static int fillout = 1;
761
762static void
763out_html(char *c)
764{
765	if (!c)
766		return;
767	if (no_newline_output) {
768		int     i = 0;
769
770		no_newline_output = 1;
771		while (c[i]) {
772			if (!no_newline_output)
773				c[i - 1] = c[i];
774			if (c[i] == '\n')
775				no_newline_output = 1;
776			i++;
777		}
778		if (!no_newline_output)
779			c[i - 1] = 0;
780	}
781	if (scaninbuff) {
782		while (*c) {
783			if (buffpos >= buffmax) {
784				char   *h;
785
786				h = realloc(buffer, buffmax * 2);
787				if (!h)
788					return;
789				buffer = h;
790				buffmax *= 2;
791			}
792			buffer[buffpos++] = *c++;
793		}
794	} else if (output_possible) {
795		while (*c) {
796			outbuffer[obp++] = *c;
797			if (*c == '\n' || obp > HUGE_STR_MAX) {
798				outbuffer[obp] = '\0';
799				add_links(outbuffer);
800				obp = 0;
801			}
802			c++;
803		}
804	}
805}
806
807#define FO0 ""
808#define FC0 ""
809#define FO1 "<I>"
810#define FC1 "</I>"
811#define FO2 "<B>"
812#define FC2 "</B>"
813#define FO3 "<TT>"
814#define FC3 "</TT>"
815
816static char *switchfont[16] = {
817	"", FC0 FO1, FC0 FO2, FC0 FO3,
818	FC1 FO0, "", FC1 FO2, FC1 FO3,
819	FC2 FO0, FC2 FO1, "", FC2 FO3,
820	FC3 FO0, FC3 FO1, FC3 FO2, ""
821};
822
823static char *
824change_to_font(int nr)
825{
826	int     i;
827
828	switch (nr) {
829	case '0':
830		nr++;
831	case '1':
832	case '2':
833	case '3':
834	case '4':
835		nr = nr - '1';
836		break;
837	case V('C', 'W'):
838		nr = 3;
839		break;
840	case 'L':
841		nr = 3;
842		break;
843	case 'B':
844		nr = 2;
845		break;
846	case 'I':
847		nr = 1;
848		break;
849	case 'P':
850	case 'R':
851		nr = 0;
852		break;
853	case 0:
854	case 1:
855	case 2:
856	case 3:
857		break;
858	default:
859		nr = 0;
860		break;
861	}
862	i = current_font * 4 + nr % 4;
863	current_font = nr % 4;
864	return switchfont[i];
865}
866
867static char sizebuf[200];
868
869static char *
870change_to_size(int nr)
871{
872	int     i;
873
874	switch (nr) {
875	case '0':
876	case '1':
877	case '2':
878	case '3':
879	case '4':
880	case '5':
881	case '6':
882	case '7':
883	case '8':
884	case '9':
885		nr = nr - '0';
886		break;
887	case '\0':
888		break;
889	default:
890		nr = current_size + nr;
891		if (nr > 9)
892			nr = 9;
893		if (nr < -9)
894			nr = -9;
895		break;
896	}
897	if (nr == current_size)
898		return "";
899	i = current_font;
900	sizebuf[0] = '\0';
901	strcat(sizebuf, change_to_font(0));
902	if (current_size)
903		strcat(sizebuf, "</FONT>");
904	current_size = nr;
905	if (nr) {
906		int     l;
907
908		strcat(sizebuf, "<FONT SIZE=");
909		l = strlen(sizebuf);
910		if (nr > 0)
911			sizebuf[l++] = '+';
912		else
913			sizebuf[l++] = '-', nr = -nr;
914		sizebuf[l++] = nr + '0';
915		sizebuf[l++] = '>';
916		sizebuf[l] = '\0';
917	}
918	strcat(sizebuf, change_to_font(i));
919	return sizebuf;
920}
921
922static int asint = 0;
923static int intresult = 0;
924
925#define SKIPEOL while (*c && *c++!='\n')
926
927static int skip_escape = 0;
928static int single_escape = 0;
929
930static char *
931scan_escape(char *c)
932{
933	char   *h = NULL;
934	char    b[5];
935	INTDEF *intd;
936	int     exoutputp, exskipescape;
937	int     i, j;
938
939	intresult = 0;
940	switch (*c) {
941	case 'e':
942		h = "\\";
943		curpos++;
944		break;
945	case '0':
946	case ' ':
947		h = "&nbsp;";
948		curpos++;
949		break;
950	case '|':
951		h = "";
952		break;
953	case '"':
954		SKIPEOL;
955		c--;
956		h = "";
957		break;
958	case '$':
959		if (argument) {
960			c++;
961			i = (*c - '1');
962			if (!(h = argument[i]))
963				h = "";
964		}
965		break;
966	case 'z':
967		c++;
968		if (*c == '\\') {
969			c = scan_escape(c + 1);
970			c--;
971			h = "";
972		} else {
973			b[0] = *c;
974			b[1] = '\0';
975			h = "";
976		}
977		break;
978	case 'k':
979		c++;
980		if (*c == '(')
981			c += 2;
982	case '^':
983	case '!':
984	case '%':
985	case 'a':
986	case 'd':
987	case 'r':
988	case 'u':
989	case '\n':
990	case '&':
991		h = "";
992		break;
993	case '(':
994		c++;
995		i = c[0] * 256 + c[1];
996		c++;
997		h = expand_char(i);
998		break;
999	case '*':
1000		c++;
1001		if (*c == '(') {
1002			c++;
1003			i = c[0] * 256 + c[1];
1004			c++;
1005		} else
1006			i = *c * 256 + ' ';
1007		h = expand_string(i);
1008		break;
1009	case 'f':
1010		c++;
1011		if (*c == '\\') {
1012			c++;
1013			c = scan_escape(c);
1014			c--;
1015			i = intresult;
1016		} else if (*c != '(')
1017			i = *c;
1018		else {
1019			c++;
1020			i = c[0] * 256 + c[1];
1021			c++;
1022		}
1023		if (!skip_escape)
1024			h = change_to_font(i);
1025		else
1026			h = "";
1027		break;
1028	case 's':
1029		c++;
1030		j = 0;
1031		i = 0;
1032		if (*c == '-') {
1033			j = -1;
1034			c++;
1035		} else if (*c == '+') {
1036			j = 1;
1037			c++;
1038		}
1039		if (*c == '0')
1040			c++;
1041		else if (*c == '\\') {
1042			c++;
1043			c = scan_escape(c);
1044			i = intresult;
1045			if (!j)
1046				j = 1;
1047		} else
1048			while (isdigit(*c) && (!i || (!j && i < 4)))
1049				i = i * 10 + (*c++) - '0';
1050		if (!j) {
1051			j = 1;
1052			if (i)
1053				i = i - 10;
1054		}
1055		if (!skip_escape)
1056			h = change_to_size(i * j);
1057		else
1058			h = "";
1059		c--;
1060		break;
1061	case 'n':
1062		c++;
1063		j = 0;
1064		switch (*c) {
1065		case '+':
1066			j = 1;
1067			c++;
1068			break;
1069		case '-':
1070			j = -1;
1071			c++;
1072			break;
1073		default:
1074			break;
1075		}
1076		if (*c == '(') {
1077			c++;
1078			i = V(c[0], c[1]);
1079			c = c + 1;
1080		} else {
1081			i = V(c[0], ' ');
1082		}
1083		intd = intdef;
1084		while (intd && intd->nr != i)
1085			intd = intd->next;
1086		if (intd) {
1087			intd->val = intd->val + j * intd->incr;
1088			intresult = intd->val;
1089		} else {
1090			switch (i) {
1091			case V('.', 's'):
1092				intresult = current_size;
1093				break;
1094			case V('.', 'f'):
1095				intresult = current_font;
1096				break;
1097			default:
1098				intresult = 0;
1099				break;
1100			}
1101		}
1102		h = "";
1103		break;
1104	case 'w':
1105		c++;
1106		i = *c;
1107		c++;
1108		exoutputp = output_possible;
1109		exskipescape = skip_escape;
1110		output_possible = 0;
1111		skip_escape = 1;
1112		j = 0;
1113		while (*c != i) {
1114			j++;
1115			if (*c == escapesym)
1116				c = scan_escape(c + 1);
1117			else
1118				c++;
1119		}
1120		output_possible = exoutputp;
1121		skip_escape = exskipescape;
1122		intresult = j;
1123		break;
1124	case 'l':
1125		h = "<HR>";
1126		curpos = 0;
1127	case 'b':
1128	case 'v':
1129	case 'x':
1130	case 'o':
1131	case 'L':
1132	case 'h':
1133		c++;
1134		i = *c;
1135		c++;
1136		exoutputp = output_possible;
1137		exskipescape = skip_escape;
1138		output_possible = 0;
1139		skip_escape = 1;
1140		while (*c != i)
1141			if (*c == escapesym)
1142				c = scan_escape(c + 1);
1143			else
1144				c++;
1145		output_possible = exoutputp;
1146		skip_escape = exskipescape;
1147		break;
1148	case 'c':
1149		no_newline_output = 1;
1150		break;
1151	case '{':
1152		newline_for_fun++;
1153		h = "";
1154		break;
1155	case '}':
1156		if (newline_for_fun)
1157			newline_for_fun--;
1158		h = "";
1159		break;
1160	case 'p':
1161		h = "<BR>\n";
1162		curpos = 0;
1163		break;
1164	case 't':
1165		h = "\t";
1166		curpos = (curpos + 8) & 0xfff8;
1167		break;
1168	case '<':
1169		h = "&lt;";
1170		curpos++;
1171		break;
1172	case '>':
1173		h = "&gt;";
1174		curpos++;
1175		break;
1176	case '\\':
1177		if (single_escape) {
1178			c--;
1179			break;
1180		}
1181	default:
1182		b[0] = *c;
1183		b[1] = 0;
1184		h = b;
1185		curpos++;
1186		break;
1187	}
1188	c++;
1189	if (!skip_escape)
1190		out_html(h);
1191	return c;
1192}
1193
1194typedef struct TABLEITEM TABLEITEM;
1195
1196struct TABLEITEM {
1197	char   *contents;
1198	int     size, align, valign, colspan, rowspan, font, vleft, vright, space,
1199	        width;
1200	TABLEITEM *next;
1201};
1202
1203static TABLEITEM emptyfield = {NULL, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, NULL};
1204
1205typedef struct TABLEROW TABLEROW;
1206
1207struct TABLEROW {
1208	TABLEITEM *first;
1209	TABLEROW *prev, *next;
1210};
1211
1212static char *tableopt[] = {
1213	"center", "expand", "box", "allbox", "doublebox",
1214	"tab", "linesize", "delim", NULL
1215};
1216static int tableoptl[] = {6, 6, 3, 6, 9, 3, 8, 5, 0};
1217
1218static void
1219clear_table(TABLEROW * table)
1220{
1221	TABLEROW *tr1, *tr2;
1222	TABLEITEM *ti1, *ti2;
1223
1224	tr1 = table;
1225	while (tr1->prev)
1226		tr1 = tr1->prev;
1227	while (tr1) {
1228		ti1 = tr1->first;
1229		while (ti1) {
1230			ti2 = ti1->next;
1231			if (ti1->contents)
1232				free(ti1->contents);
1233			free(ti1);
1234			ti1 = ti2;
1235		}
1236		tr2 = tr1;
1237		tr1 = tr1->next;
1238		free(tr2);
1239	}
1240}
1241
1242static char *scan_expression(char *c, int *result);
1243
1244static char *
1245scan_format(char *c, TABLEROW ** result, int *maxcol)
1246{
1247	TABLEROW *layout, *currow;
1248	TABLEITEM *curfield;
1249	int     i, j;
1250
1251	if (*result) {
1252		clear_table(*result);
1253	}
1254	layout = currow = (TABLEROW *) malloc(sizeof(TABLEROW));
1255	currow->next = currow->prev = NULL;
1256	currow->first = curfield = (TABLEITEM *) malloc(sizeof(TABLEITEM));
1257	*curfield = emptyfield;
1258	while (*c && *c != '.') {
1259		switch (*c) {
1260		case 'C':
1261		case 'c':
1262		case 'N':
1263		case 'n':
1264		case 'R':
1265		case 'r':
1266		case 'A':
1267		case 'a':
1268		case 'L':
1269		case 'l':
1270		case 'S':
1271		case 's':
1272		case '^':
1273		case '_':
1274			if (curfield->align) {
1275				curfield->next = (TABLEITEM *) malloc(sizeof(TABLEITEM));
1276				curfield = curfield->next;
1277				*curfield = emptyfield;
1278			}
1279			curfield->align = toupper(*c);
1280			c++;
1281			break;
1282		case 'i':
1283		case 'I':
1284		case 'B':
1285		case 'b':
1286			curfield->font = toupper(*c);
1287			c++;
1288			break;
1289		case 'f':
1290		case 'F':
1291			c++;
1292			curfield->font = toupper(*c);
1293			c++;
1294			if (!isspace(*c))
1295				c++;
1296			break;
1297		case 't':
1298		case 'T':
1299			curfield->valign = 't';
1300			c++;
1301			break;
1302		case 'p':
1303		case 'P':
1304			c++;
1305			i = j = 0;
1306			if (*c == '+') {
1307				j = 1;
1308				c++;
1309			}
1310			if (*c == '-') {
1311				j = -1;
1312				c++;
1313			}
1314			while (isdigit(*c))
1315				i = i * 10 + (*c++) - '0';
1316			if (j)
1317				curfield->size = i * j;
1318			else
1319				curfield->size = j - 10;
1320			break;
1321		case 'v':
1322		case 'V':
1323		case 'w':
1324		case 'W':
1325			c = scan_expression(c + 2, &curfield->width);
1326			break;
1327		case '|':
1328			if (curfield->align)
1329				curfield->vleft++;
1330			else
1331				curfield->vright++;
1332			c++;
1333			break;
1334		case 'e':
1335		case 'E':
1336			c++;
1337			break;
1338		case '0':
1339		case '1':
1340		case '2':
1341		case '3':
1342		case '4':
1343		case '5':
1344		case '6':
1345		case '7':
1346		case '8':
1347		case '9':
1348			i = 0;
1349			while (isdigit(*c))
1350				i = i * 10 + (*c++) - '0';
1351			curfield->space = i;
1352			break;
1353		case ',':
1354		case '\n':
1355			currow->next = (TABLEROW *) malloc(sizeof(TABLEROW));
1356			currow->next->prev = currow;
1357			currow = currow->next;
1358			currow->next = NULL;
1359			curfield = currow->first = (TABLEITEM *) malloc(sizeof(TABLEITEM));
1360			*curfield = emptyfield;
1361			c++;
1362			break;
1363		default:
1364			c++;
1365			break;
1366		}
1367	}
1368	if (*c == '.')
1369		while (*c++ != '\n');
1370	*maxcol = 0;
1371	currow = layout;
1372	while (currow) {
1373		curfield = layout->first;
1374		i = 0;
1375		while (curfield) {
1376			i++;
1377			curfield = curfield->next;
1378		}
1379		if (i > *maxcol)
1380			*maxcol = i;
1381		currow = currow->next;
1382	}
1383	*result = layout;
1384	return c;
1385}
1386
1387static TABLEROW *
1388next_row(TABLEROW * tr)
1389{
1390	if (tr->next) {
1391		tr = tr->next;
1392		if (!tr->next)
1393			next_row(tr);
1394		return tr;
1395	} else {
1396		TABLEITEM *ti, *ti2;
1397
1398		tr->next = (TABLEROW *) malloc(sizeof(TABLEROW));
1399		tr->next->prev = tr;
1400		ti = tr->first;
1401		tr = tr->next;
1402		tr->next = NULL;
1403		if (ti)
1404			tr->first = ti2 = (TABLEITEM *) malloc(sizeof(TABLEITEM));
1405		else
1406			tr->first = ti2 = NULL;
1407		while (ti != ti2) {
1408			*ti2 = *ti;
1409			ti2->contents = NULL;
1410			if ((ti = ti->next)) {
1411				ti2->next = (TABLEITEM *) malloc(sizeof(TABLEITEM));
1412			}
1413			ti2 = ti2->next;
1414		}
1415		return tr;
1416	}
1417}
1418
1419static char itemreset[20] = "\\fR\\s0";
1420
1421static char *
1422scan_table(char *c)
1423{
1424	char   *t, *h, *g;
1425	int     center = 0, expand = 0, box = 0, border = 0, linesize = 1;
1426	int     i, j, maxcol = 0, finished = 0;
1427	int     oldfont, oldsize, oldfillout;
1428	char    itemsep = '\t';
1429	TABLEROW *layout = NULL, *currow, *ftable;
1430	TABLEITEM *curfield;
1431
1432	while (*c++ != '\n');
1433	h = c;
1434	if (*h == '.')
1435		return c - 1;
1436	oldfont = current_font;
1437	oldsize = current_size;
1438	oldfillout = fillout;
1439	out_html(change_to_font(0));
1440	out_html(change_to_size(0));
1441	if (!fillout) {
1442		fillout = 1;
1443		out_html("</PRE>");
1444	}
1445	while (*h && *h != '\n')
1446		h++;
1447	if (h[-1] == ';') {
1448		/* scan table options */
1449		while (c < h) {
1450			while (isspace(*c))
1451				c++;
1452			for (i = 0; tableopt[i] && strncmp(tableopt[i], c, tableoptl[i]); i++);
1453			c = c + tableoptl[i];
1454			switch (i) {
1455			case 0:
1456				center = 1;
1457				break;
1458			case 1:
1459				expand = 1;
1460				break;
1461			case 2:
1462				box = 1;
1463				break;
1464			case 3:
1465				border = 1;
1466				break;
1467			case 4:
1468				box = 2;
1469				break;
1470			case 5:
1471				while (*c++ != '(');
1472				itemsep = *c++;
1473				break;
1474			case 6:
1475				while (*c++ != '(');
1476				linesize = 0;
1477				while (isdigit(*c))
1478					linesize = linesize * 10 + (*c++) - '0';
1479				break;
1480			case 7:
1481				while (*c != ')')
1482					c++;
1483			default:
1484				break;
1485			}
1486			c++;
1487		}
1488		c = h + 1;
1489	}
1490	/* scan layout */
1491	c = scan_format(c, &layout, &maxcol);
1492	currow = layout;
1493	next_row(currow);
1494	curfield = layout->first;
1495	i = 0;
1496	while (!finished) {
1497		/* search item */
1498		h = c;
1499		if ((*c == '_' || *c == '=') && (c[1] == itemsep || c[1] == '\n')) {
1500			if (c[-1] == '\n' && c[1] == '\n') {
1501				if (currow->prev) {
1502					currow->prev->next = (TABLEROW *) malloc(sizeof(TABLEROW));
1503					currow->prev->next->next = currow;
1504					currow->prev->next->prev = currow->prev;
1505					currow->prev = currow->prev->next;
1506				} else {
1507					currow->prev = layout = (TABLEROW *) malloc(sizeof(TABLEROW));
1508					currow->prev->prev = NULL;
1509					currow->prev->next = currow;
1510				}
1511				curfield = currow->prev->first =
1512					(TABLEITEM *) malloc(sizeof(TABLEITEM));
1513				*curfield = emptyfield;
1514				curfield->align = *c;
1515				curfield->colspan = maxcol;
1516				curfield = currow->first;
1517				c = c + 2;
1518			} else {
1519				if (curfield) {
1520					curfield->align = *c;
1521					do {
1522						curfield = curfield->next;
1523					} while (curfield && curfield->align == 'S');
1524				}
1525				if (c[1] == '\n') {
1526					currow = next_row(currow);
1527					curfield = currow->first;
1528				}
1529				c = c + 2;
1530			}
1531		} else if (*c == 'T' && c[1] == '{') {
1532			h = c + 2;
1533			c = strstr(h, "\nT}");
1534			c++;
1535			*c = '\0';
1536			g = NULL;
1537			scan_troff(h, 0, &g);
1538			scan_troff(itemreset, 0, &g);
1539			*c = 'T';
1540			c += 3;
1541			if (curfield) {
1542				curfield->contents = g;
1543				do {
1544					curfield = curfield->next;
1545				} while (curfield && curfield->align == 'S');
1546			} else if (g)
1547				free(g);
1548			if (c[-1] == '\n') {
1549				currow = next_row(currow);
1550				curfield = currow->first;
1551			}
1552		} else if (*c == '.' && c[1] == 'T' && c[2] == '&' && c[-1] == '\n') {
1553			TABLEROW *hr;
1554
1555			while (*c++ != '\n');
1556			hr = currow;
1557			currow = currow->prev;
1558			hr->prev = NULL;
1559			c = scan_format(c, &hr, &i);
1560			hr->prev = currow;
1561			currow->next = hr;
1562			currow = hr;
1563			next_row(currow);
1564			curfield = currow->first;
1565		} else if (*c == '.' && c[1] == 'T' && c[2] == 'E' && c[-1] == '\n') {
1566			finished = 1;
1567			while (*c++ != '\n');
1568			if (currow->prev)
1569				currow->prev->next = NULL;
1570			currow->prev = NULL;
1571			clear_table(currow);
1572		} else if (*c == '.' && c[-1] == '\n' && !isdigit(c[1])) {
1573			/*
1574			 * skip troff request inside table (usually only .sp
1575			 * )
1576			 */
1577			while (*c++ != '\n');
1578		} else {
1579			h = c;
1580			while (*c && (*c != itemsep || c[-1] == '\\') &&
1581			       (*c != '\n' || c[-1] == '\\'))
1582				c++;
1583			i = 0;
1584			if (*c == itemsep) {
1585				i = 1;
1586				*c = '\n';
1587			}
1588			if (h[0] == '\\' && h[2] == '\n' &&
1589			    (h[1] == '_' || h[1] == '^')) {
1590				if (curfield) {
1591					curfield->align = h[1];
1592					do {
1593						curfield = curfield->next;
1594					} while (curfield && curfield->align == 'S');
1595				}
1596				h = h + 3;
1597			} else {
1598				g = NULL;
1599				h = scan_troff(h, 1, &g);
1600				scan_troff(itemreset, 0, &g);
1601				if (curfield) {
1602					curfield->contents = g;
1603					do {
1604						curfield = curfield->next;
1605					} while (curfield && curfield->align == 'S');
1606				} else if (g)
1607					free(g);
1608			}
1609			if (i)
1610				*c = itemsep;
1611			c = h;
1612			if (c[-1] == '\n') {
1613				currow = next_row(currow);
1614				curfield = currow->first;
1615			}
1616		}
1617	}
1618	/* calculate colspan and rowspan */
1619	currow = layout;
1620	while (currow->next)
1621		currow = currow->next;
1622	while (currow) {
1623		TABLEITEM *ti, *ti1 = NULL, *ti2 = NULL;
1624
1625		ti = currow->first;
1626		if (currow->prev)
1627			ti1 = currow->prev->first;
1628		while (ti) {
1629			switch (ti->align) {
1630			case 'S':
1631				if (ti2) {
1632					ti2->colspan++;
1633					if (ti2->rowspan < ti->rowspan)
1634						ti2->rowspan = ti->rowspan;
1635				}
1636				break;
1637			case '^':
1638				if (ti1)
1639					ti1->rowspan++;
1640			default:
1641				if (!ti2)
1642					ti2 = ti;
1643				else {
1644					do {
1645						ti2 = ti2->next;
1646					} while (ti2 && curfield->align == 'S');
1647				}
1648				break;
1649			}
1650			ti = ti->next;
1651			if (ti1)
1652				ti1 = ti1->next;
1653		}
1654		currow = currow->prev;
1655	}
1656	/* produce html output */
1657	if (center)
1658		out_html("<CENTER>");
1659	if (box == 2)
1660		out_html("<TABLE BORDER><TR><TD>");
1661	out_html("<TABLE");
1662	if (box || border) {
1663		out_html(" BORDER");
1664		if (!border)
1665			out_html("><TR><TD><TABLE");
1666		if (expand)
1667			out_html(" WIDTH=100%");
1668	}
1669	out_html(">\n");
1670	currow = layout;
1671	while (currow) {
1672		j = 0;
1673		out_html("<TR VALIGN=top>");
1674		curfield = currow->first;
1675		while (curfield) {
1676			if (curfield->align != 'S' && curfield->align != '^') {
1677				out_html("<TD");
1678				switch (curfield->align) {
1679				case 'N':
1680					curfield->space += 4;
1681				case 'R':
1682					out_html(" ALIGN=right");
1683					break;
1684				case 'C':
1685					out_html(" ALIGN=center");
1686				default:
1687					break;
1688				}
1689				if (!curfield->valign && curfield->rowspan > 1)
1690					out_html(" VALIGN=center");
1691				if (curfield->colspan > 1) {
1692					char    buf[5];
1693
1694					out_html(" COLSPAN=");
1695					sprintf(buf, "%i", curfield->colspan);
1696					out_html(buf);
1697				}
1698				if (curfield->rowspan > 1) {
1699					char    buf[5];
1700
1701					out_html(" ROWSPAN=");
1702					sprintf(buf, "%i", curfield->rowspan);
1703					out_html(buf);
1704				}
1705				j = j + curfield->colspan;
1706				out_html(">");
1707				if (curfield->size)
1708					out_html(change_to_size(curfield->size));
1709				if (curfield->font)
1710					out_html(change_to_font(curfield->font));
1711				switch (curfield->align) {
1712				case '=':
1713					out_html("<HR><HR>");
1714					break;
1715				case '_':
1716					out_html("<HR>");
1717					break;
1718				default:
1719					if (curfield->contents)
1720						out_html(curfield->contents);
1721					break;
1722				}
1723				if (curfield->space)
1724					for (i = 0; i < curfield->space; i++)
1725						out_html("&nbsp;");
1726				if (curfield->font)
1727					out_html(change_to_font(0));
1728				if (curfield->size)
1729					out_html(change_to_size(0));
1730				if (j >= maxcol && curfield->align > '@' && curfield->align != '_')
1731					out_html("<BR>");
1732				out_html("</TD>");
1733			}
1734			curfield = curfield->next;
1735		}
1736		out_html("</TR>\n");
1737		currow = currow->next;
1738	}
1739	if (box && !border)
1740		out_html("</TABLE>");
1741	out_html("</TABLE>");
1742	if (box == 2)
1743		out_html("</TABLE>");
1744	if (center)
1745		out_html("</CENTER>\n");
1746	else
1747		out_html("\n");
1748	if (!oldfillout)
1749		out_html("<PRE>");
1750	fillout = oldfillout;
1751	out_html(change_to_size(oldsize));
1752	out_html(change_to_font(oldfont));
1753	return c;
1754}
1755
1756static char *
1757scan_expression(char *c, int *result)
1758{
1759	int     value = 0, value2, j = 0, sign = 1, opex = 0;
1760	char    oper = 'c';
1761
1762	if (*c == '!') {
1763		c = scan_expression(c + 1, &value);
1764		value = (!value);
1765	} else if (*c == 'n') {
1766		c++;
1767		value = NROFF;
1768	} else if (*c == 't') {
1769		c++;
1770		value = 1 - NROFF;
1771	} else if (*c == '\'' || *c == '"' || *c < ' ' || (*c == '\\' && c[1] == '(')) {
1772		/*
1773		 * ?string1?string2? test if string1 equals string2.
1774		 */
1775		char   *st1 = NULL, *st2 = NULL, *h;
1776		char   *tcmp = NULL;
1777		char    sep;
1778
1779		sep = *c;
1780		if (sep == '\\') {
1781			tcmp = c;
1782			c = c + 3;
1783		}
1784		c++;
1785		h = c;
1786		while (*c != sep && (!tcmp || strncmp(c, tcmp, 4)))
1787			c++;
1788		*c = '\n';
1789		scan_troff(h, 1, &st1);
1790		*c = sep;
1791		if (tcmp)
1792			c = c + 3;
1793		c++;
1794		h = c;
1795		while (*c != sep && (!tcmp || strncmp(c, tcmp, 4)))
1796			c++;
1797		*c = '\n';
1798		scan_troff(h, 1, &st2);
1799		*c = sep;
1800		if (!st1 && !st2)
1801			value = 1;
1802		else if (!st1 || !st2)
1803			value = 0;
1804		else
1805			value = (!strcmp(st1, st2));
1806		if (st1)
1807			free(st1);
1808		if (st2)
1809			free(st2);
1810		if (tcmp)
1811			c = c + 3;
1812		c++;
1813	} else {
1814		while (*c && !isspace(*c) && *c != ')') {
1815			opex = 0;
1816			switch (*c) {
1817			case '(':
1818				c = scan_expression(c + 1, &value2);
1819				value2 = sign * value2;
1820				opex = 1;
1821				break;
1822			case '.':
1823			case '0':
1824			case '1':
1825			case '2':
1826			case '3':
1827			case '4':
1828			case '5':
1829			case '6':
1830			case '7':
1831			case '8':
1832			case '9':{
1833					int     num = 0, denum = 1;
1834
1835					value2 = 0;
1836					while (isdigit(*c))
1837						value2 = value2 * 10 + ((*c++) - '0');
1838					if (*c == '.') {
1839						c++;
1840						while (isdigit(*c)) {
1841							num = num * 10 + ((*c++) - '0');
1842							denum = denum * 10;
1843						}
1844					}
1845					if (isalpha(*c)) {
1846						/* scale indicator */
1847						switch (*c) {
1848						case 'i':	/* inch -> 10pt */
1849							value2 = value2 * 10 + (num * 10 + denum / 2) / denum;
1850							num = 0;
1851							break;
1852						default:
1853							break;
1854						}
1855						c++;
1856					}
1857					value2 = value2 + (num + denum / 2) / denum;
1858					value2 = sign * value2;
1859					opex = 1;
1860					break;
1861				}
1862			case '\\':
1863				c = scan_escape(c + 1);
1864				value2 = intresult * sign;
1865				if (isalpha(*c))
1866					c++;	/* scale indicator */
1867				opex = 1;
1868				break;
1869			case '-':
1870				if (oper) {
1871					sign = -1;
1872					c++;
1873					break;
1874				}
1875			case '>':
1876			case '<':
1877			case '+':
1878			case '/':
1879			case '*':
1880			case '%':
1881			case '&':
1882			case '=':
1883			case ':':
1884				if (c[1] == '=')
1885					oper = (*c++) + 16;
1886				else
1887					oper = *c;
1888				c++;
1889				break;
1890			default:
1891				c++;
1892				break;
1893			}
1894			if (opex) {
1895				sign = 1;
1896				switch (oper) {
1897				case 'c':
1898					value = value2;
1899					break;
1900				case '-':
1901					value = value - value2;
1902					break;
1903				case '+':
1904					value = value + value2;
1905					break;
1906				case '*':
1907					value = value * value2;
1908					break;
1909				case '/':
1910					if (value2)
1911						value = value / value2;
1912					break;
1913				case '%':
1914					if (value2)
1915						value = value % value2;
1916					break;
1917				case '<':
1918					value = (value < value2);
1919					break;
1920				case '>':
1921					value = (value > value2);
1922					break;
1923				case '>' + 16:
1924					value = (value >= value2);
1925					break;
1926				case '<' + 16:
1927					value = (value <= value2);
1928					break;
1929				case '=':
1930				case '=' + 16:
1931					value = (value == value2);
1932					break;
1933				case '&':
1934					value = (value && value2);
1935					break;
1936				case ':':
1937					value = (value || value2);
1938					break;
1939				default:
1940					fprintf(stderr, "man2html: unknown operator %c.\n", oper);
1941				}
1942				oper = 0;
1943			}
1944		}
1945		if (*c == ')')
1946			c++;
1947	}
1948	*result = value;
1949	return c;
1950}
1951
1952static void
1953trans_char(char *c, char s, char t)
1954{
1955	char   *sl = c;
1956	int     slash = 0;
1957
1958	while (*sl != '\n' || slash) {
1959		if (!slash) {
1960			if (*sl == escapesym)
1961				slash = 1;
1962			else if (*sl == s)
1963				*sl = t;
1964		} else
1965			slash = 0;
1966		sl++;
1967	}
1968}
1969
1970/* Remove \a from C in place.  Return modified C. */
1971static char *
1972unescape (char *c)
1973{
1974	int	i, l;
1975
1976	l = strlen (c);
1977	i = 0;
1978	while (i < l && c[i]) {
1979		if (c[i] == '\a') {
1980			if (c[i+1])
1981				memmove(c + i, c + i + 1, strlen(c + i + 1) + 1);
1982			else {
1983				c[i] = '\0';
1984				break;
1985			}
1986		}
1987		i++;
1988	}
1989	return c;
1990}
1991
1992static char *
1993fill_words(char *c, char *words[], int *n)
1994{
1995	char   *sl = c;
1996	int     slash = 0;
1997	int     skipspace = 0;
1998
1999	*n = 0;
2000	words[*n] = sl;
2001	while (*sl && (*sl != '\n' || slash)) {
2002		if (!slash) {
2003			if (*sl == '"') {
2004				*sl = '\a';
2005				skipspace = !skipspace;
2006			} else if (*sl == '\a') {
2007				/* handle already-translated " */
2008				skipspace = !skipspace;
2009			} else if (*sl == escapesym)
2010				slash = 1;
2011			else if ((*sl == ' ' || *sl == '\t') && !skipspace) {
2012				*sl = '\n';
2013				if (words[*n] != sl)
2014					(*n)++;
2015				words[*n] = sl + 1;
2016			}
2017		} else {
2018			if (*sl == '"') {
2019				sl--;
2020				*sl = '\n';
2021				if (words[*n] != sl)
2022					(*n)++;
2023				sl++;
2024				while (*sl && *sl != '\n')
2025					sl++;
2026				words[*n] = sl;
2027				sl--;
2028			}
2029			slash = 0;
2030		}
2031		sl++;
2032	}
2033	if (sl != words[*n])
2034		(*n)++;
2035	return sl;
2036}
2037
2038static char *abbrev_list[] = {
2039	"GSBG", "Getting Started ",
2040	"SUBG", "Customizing SunOS",
2041	"SHBG", "Basic Troubleshooting",
2042	"SVBG", "SunView User's Guide",
2043	"MMBG", "Mail and Messages",
2044	"DMBG", "Doing More with SunOS",
2045	"UNBG", "Using the Network",
2046	"GDBG", "Games, Demos &amp; Other Pursuits",
2047	"CHANGE", "SunOS 4.1 Release Manual",
2048	"INSTALL", "Installing SunOS 4.1",
2049	"ADMIN", "System and Network Administration",
2050	"SECUR", "Security Features Guide",
2051	"PROM", "PROM User's Manual",
2052	"DIAG", "Sun System Diagnostics",
2053	"SUNDIAG", "Sundiag User's Guide",
2054	"MANPAGES", "SunOS Reference Manual",
2055	"REFMAN", "SunOS Reference Manual",
2056	"SSI", "Sun System Introduction",
2057	"SSO", "System Services Overview",
2058	"TEXT", "Editing Text Files",
2059	"DOCS", "Formatting Documents",
2060	"TROFF", "Using <B>nroff</B> and <B>troff</B>",
2061	"INDEX", "Global Index",
2062	"CPG", "C Programmer's Guide",
2063	"CREF", "C Reference Manual",
2064	"ASSY", "Assembly Language Reference",
2065	"PUL", "Programming Utilities and Libraries",
2066	"DEBUG", "Debugging Tools",
2067	"NETP", "Network Programming",
2068	"DRIVER", "Writing Device Drivers",
2069	"STREAMS", "STREAMS Programming",
2070	"SBDK", "SBus Developer's Kit",
2071	"WDDS", "Writing Device Drivers for the SBus",
2072	"FPOINT", "Floating-Point Programmer's Guide",
2073	"SVPG", "SunView 1 Programmer's Guide",
2074	"SVSPG", "SunView 1 System Programmer's Guide",
2075	"PIXRCT", "Pixrect Reference Manual",
2076	"CGI", "SunCGI Reference Manual",
2077	"CORE", "SunCore Reference Manual",
2078	"4ASSY", "Sun-4 Assembly Language Reference",
2079	"SARCH", "<FONT SIZE=-1>SPARC</FONT> Architecture Manual",
2080	"KR", "The C Programming Language",
2081NULL, NULL};
2082
2083static char *
2084lookup_abbrev(char *c)
2085{
2086	int     i = 0;
2087
2088	if (!c)
2089		return "";
2090	while (abbrev_list[i] && strcmp(c, abbrev_list[i]))
2091		i = i + 2;
2092	if (abbrev_list[i])
2093		return abbrev_list[i + 1];
2094	else
2095		return c;
2096}
2097
2098static char manidx[NULL_TERMINATED(HUGE_STR_MAX)];
2099static int subs = 0;
2100static int mip = 0;
2101static char label[5] = "lbAA";
2102
2103static void
2104add_to_index(int level, char *item)
2105{
2106	char   *c = NULL;
2107
2108	label[3]++;
2109	if (label[3] > 'Z') {
2110		label[3] = 'A';
2111		label[2]++;
2112	}
2113	if (level != subs) {
2114		if (subs) {
2115			strmaxcpy(manidx + mip, "</DL>\n", HUGE_STR_MAX - mip);
2116			mip += 6;
2117		} else {
2118			strmaxcpy(manidx + mip, "<DL>\n", HUGE_STR_MAX - mip);
2119			mip += 5;
2120		}
2121	}
2122	subs = level;
2123	scan_troff(item, 1, &c);
2124	sprintf(manidx + mip, "<DT><A HREF=\"#%s\">%s</A><DD>\n", label, c);
2125	if (c)
2126		free(c);
2127	while (manidx[mip])
2128		mip++;
2129}
2130
2131static char *
2132skip_till_newline(char *c)
2133{
2134	int     lvl = 0;
2135
2136	while (*c && *c != '\n' || lvl > 0) {
2137		if (*c == '\\') {
2138			c++;
2139			if (*c == '}')
2140				lvl--;
2141			else if (*c == '{')
2142				lvl++;
2143		}
2144		c++;
2145	}
2146	c++;
2147	if (lvl < 0 && newline_for_fun) {
2148		newline_for_fun = newline_for_fun + lvl;
2149		if (newline_for_fun < 0)
2150			newline_for_fun = 0;
2151	}
2152	return c;
2153}
2154
2155static void
2156outputPageHeader(char *l, char *c, char *r)
2157{
2158	out_html("<TABLE WIDTH=100%>\n<TR>\n");
2159	out_html("<TH ALIGN=LEFT width=33%>");
2160	out_html(l);
2161	out_html("<TH ALIGN=CENTER width=33%>");
2162	out_html(c);
2163	out_html("<TH ALIGN=RIGHT width=33%>");
2164	out_html(r);
2165	out_html("\n</TR>\n</TABLE>\n");
2166}
2167
2168static void
2169outputPageFooter(char *l, char *c, char *r)
2170{
2171	out_html("<HR>\n");
2172	outputPageHeader(l, c, r);
2173}
2174
2175static int ifelseval = 0;
2176
2177static char *
2178scan_request(char *c)
2179{
2180	/* BSD Mandoc stuff */
2181	static int mandoc_synopsis = 0;	/* True if we are in the synopsis
2182					 * section */
2183	static int mandoc_command = 0;	/* True if this is mandoc page */
2184	static int mandoc_bd_options;	/* Only copes with non-nested Bd's */
2185
2186	int     i, j, mode = 0;
2187	char   *h;
2188	char   *wordlist[MAX_WORDLIST];
2189	int     words;
2190	char   *sl;
2191	STRDEF *owndef;
2192
2193	while (*c == ' ' || *c == '\t')
2194		c++;
2195	if (c[0] == '\n')
2196		return c + 1;
2197	if (c[1] == '\n')
2198		j = 1;
2199	else
2200		j = 2;
2201	while (c[j] == ' ' || c[j] == '\t')
2202		j++;
2203	if (c[0] == escapesym) {
2204		/* some pages use .\" .\$1 .\} */
2205		/* .\$1 is too difficult/stupid */
2206		if (c[1] == '$')
2207			c = skip_till_newline(c);
2208		else
2209			c = scan_escape(c + 1);
2210	} else {
2211		i = V(c[0], c[1]);
2212		switch (i) {
2213		case V('a', 'b'):
2214			h = c + j;
2215			while (*h && *h != '\n')
2216				h++;
2217			*h = '\0';
2218			if (scaninbuff && buffpos) {
2219				buffer[buffpos] = '\0';
2220				puts(buffer);
2221			}
2222			/* fprintf(stderr, "%s\n", c+2); */
2223			exit(0);
2224			break;
2225		case V('d', 'i'):
2226			{
2227				STRDEF *de;
2228				int     oldcurpos = curpos;
2229
2230				c = c + j;
2231				i = V(c[0], c[1]);
2232				if (*c == '\n') {
2233					c++;
2234					break;
2235				}
2236				while (*c && *c != '\n')
2237					c++;
2238				c++;
2239				h = c;
2240				while (*c && strncmp(c, ".di", 3))
2241					while (*c && *c++ != '\n');
2242				*c = '\0';
2243				de = strdef;
2244				while (de && de->nr != i)
2245					de = de->next;
2246				if (!de) {
2247					de = (STRDEF *) malloc(sizeof(STRDEF));
2248					de->nr = i;
2249					de->slen = 0;
2250					de->next = strdef;
2251					de->st = NULL;
2252					strdef = de;
2253				} else {
2254					if (de->st)
2255						free(de->st);
2256					de->slen = 0;
2257					de->st = NULL;
2258				}
2259				scan_troff(h, 0, &de->st);
2260				*c = '.';
2261				while (*c && *c++ != '\n');
2262				break;
2263			}
2264		case V('d', 's'):
2265			mode = 1;
2266		case V('a', 's'):
2267			{
2268				STRDEF *de;
2269				int     oldcurpos = curpos;
2270
2271				c = c + j;
2272				i = V(c[0], c[1]);
2273				j = 0;
2274				while (c[j] && c[j] != '\n')
2275					j++;
2276				if (j < 3) {
2277					c = c + j;
2278					break;
2279				}
2280				if (c[1] == ' ')
2281					c = c + 1;
2282				else
2283					c = c + 2;
2284				while (isspace(*c))
2285					c++;
2286				if (*c == '"')
2287					c++;
2288				de = strdef;
2289				while (de && de->nr != i)
2290					de = de->next;
2291				single_escape = 1;
2292				curpos = 0;
2293				if (!de) {
2294					char   *h;
2295
2296					de = (STRDEF *) malloc(sizeof(STRDEF));
2297					de->nr = i;
2298					de->slen = 0;
2299					de->next = strdef;
2300					de->st = NULL;
2301					strdef = de;
2302					h = NULL;
2303					c = scan_troff(c, 1, &h);
2304					de->st = h;
2305					de->slen = curpos;
2306				} else {
2307					if (mode) {
2308						char   *h = NULL;
2309
2310						c = scan_troff(c, 1, &h);
2311						free(de->st);
2312						de->slen = 0;
2313						de->st = h;
2314					} else
2315						c = scan_troff(c, 1, &de->st);
2316					de->slen += curpos;
2317				}
2318				single_escape = 0;
2319				curpos = oldcurpos;
2320			}
2321			break;
2322		case V('b', 'r'):
2323			if (still_dd)
2324				out_html("<DD>");
2325			else
2326				out_html("<BR>\n");
2327			curpos = 0;
2328			c = c + j;
2329			if (c[0] == escapesym) {
2330				c = scan_escape(c + 1);
2331			}
2332			c = skip_till_newline(c);
2333			break;
2334		case V('c', '2'):
2335			c = c + j;
2336			if (*c != '\n') {
2337				nobreaksym = *c;
2338			} else
2339				nobreaksym = '\'';
2340			c = skip_till_newline(c);
2341			break;
2342		case V('c', 'c'):
2343			c = c + j;
2344			if (*c != '\n') {
2345				controlsym = *c;
2346			} else
2347				controlsym = '.';
2348			c = skip_till_newline(c);
2349			break;
2350		case V('c', 'e'):
2351			c = c + j;
2352			if (*c == '\n') {
2353				i = 1;
2354			} else {
2355				i = 0;
2356				while ('0' <= *c && *c <= '9') {
2357					i = i * 10 + *c - '0';
2358					c++;
2359				}
2360			}
2361			c = skip_till_newline(c);
2362			/* center next i lines */
2363			if (i > 0) {
2364				out_html("<CENTER>\n");
2365				while (i && *c) {
2366					char   *line = NULL;
2367
2368					c = scan_troff(c, 1, &line);
2369					if (line && strncmp(line, "<BR>", 4)) {
2370						out_html(line);
2371						out_html("<BR>\n");
2372						i--;
2373					}
2374				}
2375				out_html("</CENTER>\n");
2376				curpos = 0;
2377			}
2378			break;
2379		case V('e', 'c'):
2380			c = c + j;
2381			if (*c != '\n') {
2382				escapesym = *c;
2383			} else
2384				escapesym = '\\';
2385			break;
2386			c = skip_till_newline(c);
2387		case V('e', 'o'):
2388			escapesym = '\0';
2389			c = skip_till_newline(c);
2390			break;
2391		case V('e', 'x'):
2392			exit(0);
2393			break;
2394		case V('f', 'c'):
2395			c = c + j;
2396			if (*c == '\n') {
2397				fieldsym = padsym = '\0';
2398			} else {
2399				fieldsym = c[0];
2400				padsym = c[1];
2401			}
2402			c = skip_till_newline(c);
2403			break;
2404		case V('f', 'i'):
2405			if (!fillout) {
2406				out_html(change_to_font(0));
2407				out_html(change_to_size('0'));
2408				out_html("</PRE>\n");
2409			}
2410			curpos = 0;
2411			fillout = 1;
2412			c = skip_till_newline(c);
2413			break;
2414		case V('f', 't'):
2415			c = c + j;
2416			if (*c == '\n') {
2417				out_html(change_to_font(0));
2418			} else {
2419				if (*c == escapesym) {
2420					int     fn;
2421
2422					c = scan_expression(c, &fn);
2423					c--;
2424					out_html(change_to_font(fn));
2425				} else {
2426					out_html(change_to_font(*c));
2427					c++;
2428				}
2429			}
2430			c = skip_till_newline(c);
2431			break;
2432		case V('e', 'l'):
2433			/* .el anything : else part of if else */
2434			if (ifelseval) {
2435				c = c + j;
2436				c[-1] = '\n';
2437				c = scan_troff(c, 1, NULL);
2438			} else
2439				c = skip_till_newline(c + j);
2440			break;
2441		case V('i', 'e'):
2442			/* .ie c anything : then part of if else */
2443		case V('i', 'f'):
2444			/*
2445			 * .if c anything .if !c anything .if N anything .if
2446			 * !N anything .if 'string1'string2' anything .if
2447			 * !'string1'string2' anything
2448			 */
2449			c = c + j;
2450			c = scan_expression(c, &i);
2451			ifelseval = !i;
2452			if (i) {
2453				*c = '\n';
2454				c++;
2455				c = scan_troff(c, 1, NULL);
2456			} else
2457				c = skip_till_newline(c);
2458			break;
2459		case V('i', 'g'):
2460			{
2461				char   *endwith = "..\n";
2462
2463				i = 3;
2464				c = c + j;
2465				if (*c != '\n') {
2466					endwith = c - 1;
2467					i = 1;
2468					c[-1] = '.';
2469					while (*c && *c != '\n')
2470						c++, i++;
2471				}
2472				c++;
2473				while (*c && strncmp(c, endwith, i))
2474					while (*c++ != '\n');
2475				while (*c++ != '\n');
2476				break;
2477			}
2478		case V('n', 'f'):
2479			if (fillout) {
2480				out_html(change_to_font(0));
2481				out_html(change_to_size('0'));
2482				out_html("<PRE>\n");
2483			}
2484			curpos = 0;
2485			fillout = 0;
2486			c = skip_till_newline(c);
2487			break;
2488		case V('p', 's'):
2489			c = c + j;
2490			if (*c == '\n') {
2491				out_html(change_to_size('0'));
2492			} else {
2493				j = 0;
2494				i = 0;
2495				if (*c == '-') {
2496					j = -1;
2497					c++;
2498				} else if (*c == '+') {
2499					j = 1;
2500					c++;
2501				}
2502				c = scan_expression(c, &i);
2503				if (!j) {
2504					j = 1;
2505					if (i > 5)
2506						i = i - 10;
2507				}
2508				out_html(change_to_size(i * j));
2509			}
2510			c = skip_till_newline(c);
2511			break;
2512		case V('s', 'p'):
2513			c = c + j;
2514			if (fillout)
2515				out_html("<P>");
2516			else {
2517				out_html(NEWLINE);
2518				NEWLINE[0] = '\n';
2519			}
2520			curpos = 0;
2521			c = skip_till_newline(c);
2522			break;
2523		case V('s', 'o'):
2524			{
2525				FILE   *f;
2526				struct stat stbuf;
2527				int     l = 0;
2528				char   *buf;
2529				char   *name = NULL;
2530
2531				curpos = 0;
2532				c = c + j;
2533				if (*c == '/') {
2534					h = c;
2535				} else {
2536					h = c - 3;
2537					h[0] = '.';
2538					h[1] = '.';
2539					h[2] = '/';
2540				}
2541				while (*c != '\n')
2542					c++;
2543				*c = '\0';
2544				scan_troff(h, 1, &name);
2545				if (name[3] == '/')
2546					h = name + 3;
2547				else
2548					h = name;
2549				if (stat(h, &stbuf) != -1)
2550					l = stbuf.st_size;
2551				buf = stralloc(l + 4);
2552#if NOCGI
2553				if (!out_length) {
2554					char   *t, *s;
2555
2556					t = strrchr(fname, '/');
2557					if (!t)
2558						t = fname;
2559					fprintf(stderr, "ln -s %s.html %s.html\n", h, t);
2560					s = strrchr(t, '.');
2561					if (!s)
2562						s = t;
2563					printf("<HTML><HEAD><TITLE> Manpage of %s</TITLE>\n"
2564					       "</HEAD><BODY>\n"
2565					       "See the manpage for <A HREF=\"%s.html\">%s</A>.\n"
2566					       "</BODY></HTML>\n",
2567					       s, h, h);
2568				} else
2569#endif
2570				{
2571					/*
2572					 * this works alright, except for
2573					 * section 3
2574					 */
2575					buf = read_man_page(h);
2576					if (!buf) {
2577
2578						fprintf(stderr, "man2html: unable to open or read file %s.\n",
2579							h);
2580						out_html("<BLOCKQUOTE>"
2581							 "man2html: unable to open or read file.\n");
2582						out_html(h);
2583						out_html("</BLOCKQUOTE>\n");
2584					} else {
2585						buf[0] = buf[l] = '\n';
2586						buf[l + 1] = buf[l + 2] = '\0';
2587						scan_troff(buf + 1, 0, NULL);
2588					}
2589					if (buf)
2590						free(buf);
2591				}
2592				*c++ = '\n';
2593				break;
2594			}
2595		case V('t', 'a'):
2596			c = c + j;
2597			j = 0;
2598			while (*c != '\n') {
2599				sl = scan_expression(c, &tabstops[j]);
2600				if (*c == '-' || *c == '+')
2601					tabstops[j] += tabstops[j - 1];
2602				c = sl;
2603				while (*c == ' ' || *c == '\t')
2604					c++;
2605				j++;
2606			}
2607			maxtstop = j;
2608			curpos = 0;
2609			break;
2610		case V('t', 'i'):
2611			/*
2612			 * while (itemdepth || dl_set[itemdepth]) {
2613			 * out_html("</DL>\n"); if (dl_set[itemdepth])
2614			 * dl_set[itemdepth]=0; else itemdepth--; }
2615			 */
2616			out_html("<BR>\n");
2617			c = c + j;
2618			c = scan_expression(c, &j);
2619			for (i = 0; i < j; i++)
2620				out_html("&nbsp;");
2621			curpos = j;
2622			c = skip_till_newline(c);
2623			break;
2624		case V('t', 'm'):
2625			c = c + j;
2626			h = c;
2627			while (*c != '\n')
2628				c++;
2629			*c = '\0';
2630			/* fprintf(stderr,"%s\n", h); */
2631			*c = '\n';
2632			break;
2633		case V('B', ' '):
2634		case V('B', '\n'):
2635		case V('I', ' '):
2636		case V('I', '\n'):
2637			/* parse one line in a certain font */
2638			out_html(change_to_font(*c));
2639			trans_char(c, '"', '\a');
2640			c = c + j;
2641			if (*c == '\n')
2642				c++;
2643			c = scan_troff(c, 1, NULL);
2644			out_html(change_to_font('R'));
2645			out_html(NEWLINE);
2646			if (fillout)
2647				curpos++;
2648			else
2649				curpos = 0;
2650			break;
2651		case V('O', 'P'):	/* groff manpages use this
2652					 * construction */
2653			/* .OP a b : [ <B>a</B> <I>b</I> ] */
2654			mode = 1;
2655			c[0] = 'B';
2656			c[1] = 'I';
2657			out_html(change_to_font('R'));
2658			out_html("[");
2659			curpos++;
2660		case V('B', 'R'):
2661		case V('B', 'I'):
2662		case V('I', 'B'):
2663		case V('I', 'R'):
2664		case V('R', 'B'):
2665		case V('R', 'I'):
2666			{
2667				char    font[2];
2668
2669				font[0] = c[0];
2670				font[1] = c[1];
2671				c = c + j;
2672				if (*c == '\n')
2673					c++;
2674				sl = fill_words(c, wordlist, &words);
2675				c = sl + 1;
2676				/*
2677				 * .BR name (section) indicates a link. It
2678				 * will be added in the output routine.
2679				 */
2680				for (i = 0; i < words; i++) {
2681					if (mode) {
2682						out_html(" ");
2683						curpos++;
2684					}
2685					wordlist[i][-1] = ' ';
2686					out_html(change_to_font(font[i & 1]));
2687					scan_troff(wordlist[i], 1, NULL);
2688				}
2689				out_html(change_to_font('R'));
2690				if (mode) {
2691					out_html(" ]");
2692					curpos++;
2693				}
2694				out_html(NEWLINE);
2695				if (!fillout)
2696					curpos = 0;
2697				else
2698					curpos++;
2699			}
2700			break;
2701		case V('D', 'T'):
2702			for (j = 0; j < 20; j++)
2703				tabstops[j] = (j + 1) * 8;
2704			maxtstop = 20;
2705			c = skip_till_newline(c);
2706			break;
2707		case V('I', 'P'):
2708			sl = fill_words(c + j, wordlist, &words);
2709			c = sl + 1;
2710			if (!dl_set[itemdepth]) {
2711				out_html("<DL COMPACT>\n");
2712				dl_set[itemdepth] = 1;
2713			}
2714			out_html("<DT>");
2715			if (words) {
2716				scan_troff(wordlist[0], 1, NULL);
2717			}
2718			out_html("<DD>");
2719			curpos = 0;
2720			break;
2721		case V('T', 'P'):
2722			if (!dl_set[itemdepth]) {
2723				out_html("<DL COMPACT>\n");
2724				dl_set[itemdepth] = 1;
2725			}
2726			out_html("<DT>");
2727			c = skip_till_newline(c);
2728			/* somewhere a definition ends with '.TP' */
2729			if (!*c)
2730				still_dd = 1;
2731			else {
2732				c = scan_troff(c, 1, NULL);
2733				out_html("<DD>");
2734			}
2735			curpos = 0;
2736			break;
2737		case V('I', 'X'):
2738			/* general index */
2739			sl = fill_words(c + j, wordlist, &words);
2740			c = sl + 1;
2741			j = 4;
2742			while (idxlabel[j] == 'Z')
2743				idxlabel[j--] = 'A';
2744			idxlabel[j]++;
2745#ifdef MAKEINDEX
2746			fprintf(idxfile, "%s@%s@", fname, idxlabel);
2747			for (j = 0; j < words; j++) {
2748				h = NULL;
2749				scan_troff(wordlist[j], 1, &h);
2750				fprintf(idxfile, "_\b@%s", h);
2751				free(h);
2752			}
2753			fprintf(idxfile, "\n");
2754#endif
2755			out_html("<A NAME=\"");
2756			out_html(idxlabel);
2757			/*
2758			 * this will not work in mosaic (due to a bug).
2759			 * Adding '&nbsp;' between '>' and '<' solves it, but
2760			 * creates some space. A normal space does not work.
2761			 */
2762			out_html("\"></A>");
2763			break;
2764		case V('L', 'P'):
2765		case V('P', 'P'):
2766			if (dl_set[itemdepth]) {
2767				out_html("</DL>\n");
2768				dl_set[itemdepth] = 0;
2769			}
2770			if (fillout)
2771				out_html("<P>\n");
2772			else {
2773				out_html(NEWLINE);
2774				NEWLINE[0] = '\n';
2775			}
2776			curpos = 0;
2777			c = skip_till_newline(c);
2778			break;
2779		case V('H', 'P'):
2780			if (!dl_set[itemdepth]) {
2781				out_html("<DL COMPACT>");
2782				dl_set[itemdepth] = 1;
2783			}
2784			out_html("<DT>\n");
2785			still_dd = 1;
2786			c = skip_till_newline(c);
2787			curpos = 0;
2788			break;
2789		case V('P', 'D'):
2790			c = skip_till_newline(c);
2791			break;
2792		case V('R', 's'):	/* BSD mandoc */
2793		case V('R', 'S'):
2794			sl = fill_words(c + j, wordlist, &words);
2795			j = 1;
2796			if (words > 0)
2797				scan_expression(wordlist[0], &j);
2798			if (j >= 0) {
2799				itemdepth++;
2800				dl_set[itemdepth] = 0;
2801				out_html("<DL COMPACT><DT><DD>");
2802				c = skip_till_newline(c);
2803				curpos = 0;
2804				break;
2805			}
2806		case V('R', 'e'):	/* BSD mandoc */
2807		case V('R', 'E'):
2808			if (itemdepth > 0) {
2809				if (dl_set[itemdepth])
2810					out_html("</DL>");
2811				out_html("</DL>\n");
2812				itemdepth--;
2813			}
2814			c = skip_till_newline(c);
2815			curpos = 0;
2816			break;
2817		case V('S', 'B'):
2818			out_html(change_to_size(-1));
2819			out_html(change_to_font('B'));
2820			c = scan_troff(c + j, 1, NULL);
2821			out_html(change_to_font('R'));
2822			out_html(change_to_size('0'));
2823			break;
2824		case V('S', 'M'):
2825			c = c + j;
2826			if (*c == '\n')
2827				c++;
2828			out_html(change_to_size(-1));
2829			trans_char(c, '"', '\a');
2830			c = scan_troff(c, 1, NULL);
2831			out_html(change_to_size('0'));
2832			break;
2833		case V('S', 's'):	/* BSD mandoc */
2834			mandoc_command = 1;
2835		case V('S', 'S'):
2836			mode = 1;
2837		case V('S', 'h'):	/* BSD mandoc */
2838			/* hack for fallthru from above */
2839			mandoc_command = !mode || mandoc_command;
2840		case V('S', 'H'):
2841			c = c + j;
2842			if (*c == '\n')
2843				c++;
2844			while (itemdepth || dl_set[itemdepth]) {
2845				out_html("</DL>\n");
2846				if (dl_set[itemdepth])
2847					dl_set[itemdepth] = 0;
2848				else if (itemdepth > 0)
2849					itemdepth--;
2850			}
2851			out_html(change_to_font(0));
2852			out_html(change_to_size(0));
2853			if (!fillout) {
2854				fillout = 1;
2855				out_html("</PRE>");
2856			}
2857			trans_char(c, '"', '\a');
2858			add_to_index(mode, c);
2859			out_html("<A NAME=\"");
2860			out_html(label);
2861			/* &nbsp; for mosaic users */
2862			if (mode)
2863				out_html("\">&nbsp;</A>\n<H4>");
2864			else
2865				out_html("\">&nbsp;</A>\n<H3>");
2866			mandoc_synopsis = strncmp(c, "SYNOPSIS", 8) == 0;
2867			c = mandoc_command ? scan_troff_mandoc(c, 1, NULL) : scan_troff(c, 1, NULL);
2868			if (mode)
2869				out_html("</H4>\n");
2870			else
2871				out_html("</H3>\n");
2872			curpos = 0;
2873			break;
2874		case V('T', 'S'):
2875			c = scan_table(c);
2876			break;
2877		case V('D', 't'):	/* BSD mandoc */
2878			mandoc_command = 1;
2879		case V('T', 'H'):
2880			if (!output_possible) {
2881				sl = fill_words(c + j, wordlist, &words);
2882				if (words > 1) {
2883					char	*t;
2884					for (i = 1; i < words; i++)
2885						wordlist[i][-1] = '\0';
2886					*sl = '\0';
2887					output_possible = 1;
2888					sprintf(th_page_and_sec, "%s(%s)", wordlist[0], wordlist[1]);
2889					if (words > 2) {
2890						t = unescape(wordlist[2]);
2891						strncpy(th_datestr, t, sizeof(th_datestr));
2892						th_datestr[sizeof(th_datestr) - 1] = '\0';
2893					} else
2894						th_datestr[0] = '\0';
2895					if (words > 3) {
2896						t = unescape(wordlist[3]);
2897						strncpy(th_version, t, sizeof(th_version));
2898						th_version[sizeof(th_version) - 1] = '\0';
2899					} else
2900						th_version[0] = '\0';
2901					out_html("<HTML><HEAD>\n<TITLE>");
2902					out_html(th_page_and_sec);
2903					out_html(" Manual Page");
2904					out_html("</TITLE>\n</HEAD>\n<BODY>");
2905
2906					outputPageHeader(th_page_and_sec, th_datestr, th_page_and_sec);
2907
2908					out_html("<BR><A HREF=\"#index\">Index</A>\n");
2909					*sl = '\n';
2910					out_html("<HR>\n");
2911					if (mandoc_command)
2912						out_html("<BR>BSD mandoc<BR>");
2913				}
2914				c = sl + 1;
2915			} else
2916				c = skip_till_newline(c);
2917			curpos = 0;
2918			break;
2919		case V('T', 'X'):
2920			sl = fill_words(c + j, wordlist, &words);
2921			*sl = '\0';
2922			out_html(change_to_font('I'));
2923			if (words > 1)
2924				wordlist[1][-1] = '\0';
2925			c = lookup_abbrev(wordlist[0]);
2926			curpos += strlen(c);
2927			out_html(c);
2928			out_html(change_to_font('R'));
2929			if (words > 1)
2930				out_html(wordlist[1]);
2931			*sl = '\n';
2932			c = sl + 1;
2933			break;
2934		case V('r', 'm'):
2935			/* .rm xx : Remove request, macro or string */
2936		case V('r', 'n'):
2937			/*
2938			 * .rn xx yy : Rename request, macro or string xx to
2939			 * yy
2940			 */
2941			{
2942				STRDEF *de;
2943
2944				c = c + j;
2945				i = V(c[0], c[1]);
2946				c = c + 2;
2947				while (isspace(*c) && *c != '\n')
2948					c++;
2949				j = V(c[0], c[1]);
2950				while (*c && *c != '\n')
2951					c++;
2952				c++;
2953				de = strdef;
2954				while (de && de->nr != j)
2955					de = de->next;
2956				if (de) {
2957					if (de->st)
2958						free(de->st);
2959					de->nr = 0;
2960				}
2961				de = strdef;
2962				while (de && de->nr != i)
2963					de = de->next;
2964				if (de)
2965					de->nr = j;
2966				break;
2967			}
2968		case V('n', 'x'):
2969			/* .nx filename : next file. */
2970		case V('i', 'n'):
2971			/* .in +-N : Indent */
2972			c = skip_till_newline(c);
2973			break;
2974		case V('n', 'r'):
2975			/*
2976			 * .nr R +-N M: define and set number register R by
2977			 * +-N; auto-increment by M
2978			 */
2979			{
2980				INTDEF *intd;
2981
2982				c = c + j;
2983				i = V(c[0], c[1]);
2984				c = c + 2;
2985				intd = intdef;
2986				while (intd && intd->nr != i)
2987					intd = intd->next;
2988				if (!intd) {
2989					intd = (INTDEF *) malloc(sizeof(INTDEF));
2990					intd->nr = i;
2991					intd->val = 0;
2992					intd->incr = 0;
2993					intd->next = intdef;
2994					intdef = intd;
2995				}
2996				while (*c == ' ' || *c == '\t')
2997					c++;
2998				c = scan_expression(c, &intd->val);
2999				if (*c != '\n') {
3000					while (*c == ' ' || *c == '\t')
3001						c++;
3002					c = scan_expression(c, &intd->incr);
3003				}
3004				c = skip_till_newline(c);
3005				break;
3006			}
3007		case V('a', 'm'):
3008			/* .am xx yy : append to a macro. */
3009			/* define or handle as .ig yy */
3010			mode = 1;
3011		case V('d', 'e'):
3012			/*
3013			 * .de xx yy : define or redefine macro xx; end at
3014			 * .yy (..)
3015			 */
3016			/* define or handle as .ig yy */
3017			{
3018				STRDEF *de;
3019				int     olen = 0;
3020
3021				c = c + j;
3022				sl = fill_words(c, wordlist, &words);
3023				i = V(c[0], c[1]);
3024				j = 2;
3025				if (words == 1)
3026					wordlist[1] = "..";
3027				else {
3028					wordlist[1]--;
3029					wordlist[1][0] = '.';
3030					j = 3;
3031				}
3032				c = sl + 1;
3033				sl = c;
3034				while (*c && strncmp(c, wordlist[1], j))
3035					c = skip_till_newline(c);
3036				de = defdef;
3037				while (de && de->nr != i)
3038					de = de->next;
3039				if (mode && de)
3040					olen = strlen(de->st);
3041				j = olen + c - sl;
3042				h = stralloc(j * 2 + 4);
3043				if (h) {
3044					for (j = 0; j < olen; j++)
3045						h[j] = de->st[j];
3046					if (!j || h[j - 1] != '\n')
3047						h[j++] = '\n';
3048					while (sl != c) {
3049						if (sl[0] == '\\' && sl[1] == '\\') {
3050							h[j++] = '\\';
3051							sl++;
3052						} else
3053							h[j++] = *sl;
3054						sl++;
3055					}
3056					h[j] = '\0';
3057					if (de) {
3058						if (de->st)
3059							free(de->st);
3060						de->st = h;
3061					} else {
3062						de = (STRDEF *) malloc(sizeof(STRDEF));
3063						de->nr = i;
3064						de->next = defdef;
3065						de->st = h;
3066						defdef = de;
3067					}
3068				}
3069			}
3070			c = skip_till_newline(c);
3071			break;
3072		case V('B', 'l'):	/* BSD mandoc */
3073			{
3074				char    list_options[NULL_TERMINATED(MED_STR_MAX)];
3075				char   *nl = strchr(c, '\n');
3076
3077				c = c + j;
3078				if (dl_set[itemdepth]) {	/* These things can
3079								 * nest. */
3080					itemdepth++;
3081				}
3082				if (nl) {	/* Parse list options */
3083					strlimitcpy(list_options, c, nl - c, MED_STR_MAX);
3084				}
3085				if (strstr(list_options, "-bullet")) {	/* HTML Unnumbered List */
3086					dl_set[itemdepth] = BL_BULLET_LIST;
3087					out_html("<UL>\n");
3088				} else if (strstr(list_options, "-enum")) {	/* HTML Ordered List */
3089					dl_set[itemdepth] = BL_ENUM_LIST;
3090					out_html("<OL>\n");
3091				} else {	/* HTML Descriptive List */
3092					dl_set[itemdepth] = BL_DESC_LIST;
3093					out_html("<DL COMPACT>\n");
3094				}
3095				if (fillout)
3096					out_html("<P>\n");
3097				else {
3098					out_html(NEWLINE);
3099					NEWLINE[0] = '\n';
3100				}
3101				curpos = 0;
3102				c = skip_till_newline(c);
3103				break;
3104			}
3105		case V('E', 'l'):	/* BSD mandoc */
3106			c = c + j;
3107			if (dl_set[itemdepth] & BL_DESC_LIST) {
3108				out_html("</DL>\n");
3109			} else if (dl_set[itemdepth] & BL_BULLET_LIST) {
3110				out_html("</UL>\n");
3111			} else if (dl_set[itemdepth] & BL_ENUM_LIST) {
3112				out_html("</OL>\n");
3113			}
3114			dl_set[itemdepth] = 0;
3115			if (itemdepth > 0)
3116				itemdepth--;
3117			if (fillout)
3118				out_html("<P>\n");
3119			else {
3120				out_html(NEWLINE);
3121				NEWLINE[0] = '\n';
3122			}
3123			curpos = 0;
3124			c = skip_till_newline(c);
3125			break;
3126		case V('I', 't'):	/* BSD mandoc */
3127			c = c + j;
3128			if (strncmp(c, "Xo", 2) == 0 && isspace(*(c + 2))) {
3129				c = skip_till_newline(c);
3130			}
3131			if (dl_set[itemdepth] & BL_DESC_LIST) {
3132				out_html("<DT>");
3133				out_html(change_to_font('B'));
3134				if (*c == '\n') {	/* Don't allow embedded
3135							 * comms after a newline */
3136					c++;
3137					c = scan_troff(c, 1, NULL);
3138				} else {	/* Do allow embedded comms on
3139						 * the same line. */
3140					c = scan_troff_mandoc(c, 1, NULL);
3141				}
3142				out_html(change_to_font('R'));
3143				out_html(NEWLINE);
3144				out_html("<DD>");
3145			} else if (dl_set[itemdepth] & (BL_BULLET_LIST | BL_ENUM_LIST)) {
3146				out_html("<LI>");
3147				c = scan_troff_mandoc(c, 1, NULL);
3148				out_html(NEWLINE);
3149			}
3150			if (fillout)
3151				curpos++;
3152			else
3153				curpos = 0;
3154			break;
3155		case V('B', 'k'):	/* BSD mandoc */
3156		case V('E', 'k'):	/* BSD mandoc */
3157		case V('D', 'd'):	/* BSD mandoc */
3158		case V('O', 's'):	/* BSD mandoc */
3159			trans_char(c, '"', '\a');
3160			c = c + j;
3161			if (*c == '\n')
3162				c++;
3163			c = scan_troff_mandoc(c, 1, NULL);
3164			out_html(NEWLINE);
3165			if (fillout)
3166				curpos++;
3167			else
3168				curpos = 0;
3169			break;
3170		case V('B', 't'):	/* BSD mandoc */
3171			trans_char(c, '"', '\a');
3172			c = c + j;
3173			out_html(" is currently in beta test.");
3174			if (fillout)
3175				curpos++;
3176			else
3177				curpos = 0;
3178			break;
3179		case V('B', 'x'):	/* BSD mandoc */
3180			trans_char(c, '"', '\a');
3181			c = c + j;
3182			if (*c == '\n')
3183				c++;
3184			out_html("BSD ");
3185			c = scan_troff_mandoc(c, 1, NULL);
3186			if (fillout)
3187				curpos++;
3188			else
3189				curpos = 0;
3190			break;
3191		case V('D', 'l'):	/* BSD mandoc */
3192			c = c + j;
3193			out_html(NEWLINE);
3194			out_html("<BLOCKQUOTE>");
3195			out_html(change_to_font('L'));
3196			if (*c == '\n')
3197				c++;
3198			c = scan_troff_mandoc(c, 1, NULL);
3199			out_html(change_to_font('R'));
3200			out_html("</BLOCKQUOTE>");
3201			if (fillout)
3202				curpos++;
3203			else
3204				curpos = 0;
3205			break;
3206		case V('B', 'd'):	/* BSD mandoc */
3207			{	/* Seems like a kind of example/literal mode */
3208				char    bd_options[NULL_TERMINATED(MED_STR_MAX)];
3209				char   *nl = strchr(c, '\n');
3210
3211				c = c + j;
3212				if (nl) {
3213					strlimitcpy(bd_options, c, nl - c, MED_STR_MAX);
3214				}
3215				out_html(NEWLINE);
3216				mandoc_bd_options = 0;	/* Remember options for
3217							 * terminating Bl */
3218				if (strstr(bd_options, "-offset indent")) {
3219					mandoc_bd_options |= BD_INDENT;
3220					out_html("<BLOCKQUOTE>\n");
3221				}
3222				if (strstr(bd_options, "-literal")
3223				    || strstr(bd_options, "-unfilled")) {
3224					if (fillout) {
3225						mandoc_bd_options |= BD_LITERAL;
3226						out_html(change_to_font(0));
3227						out_html(change_to_size('0'));
3228						out_html("<PRE>\n");
3229					}
3230					curpos = 0;
3231					fillout = 0;
3232				}
3233				c = skip_till_newline(c);
3234				break;
3235			}
3236		case V('E', 'd'):	/* BSD mandoc */
3237			if (mandoc_bd_options & BD_LITERAL) {
3238				if (!fillout) {
3239					out_html(change_to_font(0));
3240					out_html(change_to_size('0'));
3241					out_html("</PRE>\n");
3242				}
3243			}
3244			if (mandoc_bd_options & BD_INDENT)
3245				out_html("</BLOCKQUOTE>\n");
3246			curpos = 0;
3247			fillout = 1;
3248			c = skip_till_newline(c);
3249			break;
3250		case V('B', 'e'):	/* BSD mandoc */
3251			c = c + j;
3252			if (fillout)
3253				out_html("<P>");
3254			else {
3255				out_html(NEWLINE);
3256				NEWLINE[0] = '\n';
3257			}
3258			curpos = 0;
3259			c = skip_till_newline(c);
3260			break;
3261		case V('X', 'r'):	/* BSD mandoc */
3262			{
3263				/*
3264				 * Translate xyz 1 to xyz(1) Allow for
3265				 * multiple spaces.  Allow the section to be
3266				 * missing.
3267				 */
3268				char    buff[NULL_TERMINATED(MED_STR_MAX)];
3269				char   *bufptr;
3270
3271				trans_char(c, '"', '\a');
3272				bufptr = buff;
3273				c = c + j;
3274				if (*c == '\n')
3275					c++;	/* Skip spaces */
3276				while (isspace(*c) && *c != '\n')
3277					c++;
3278				while (isalnum(*c)) {	/* Copy the xyz part */
3279					*bufptr = *c;
3280					bufptr++;
3281					if (bufptr >= buff + MED_STR_MAX)
3282						break;
3283					c++;
3284				}
3285				while (isspace(*c) && *c != '\n')
3286					c++;	/* Skip spaces */
3287				if (isdigit(*c)) {	/* Convert the number if
3288							 * there is one */
3289					*bufptr = '(';
3290					bufptr++;
3291					if (bufptr < buff + MED_STR_MAX) {
3292						while (isalnum(*c)) {
3293							*bufptr = *c;
3294							bufptr++;
3295							if (bufptr >= buff + MED_STR_MAX)
3296								break;
3297							c++;
3298						}
3299						if (bufptr < buff + MED_STR_MAX) {
3300							*bufptr = ')';
3301							bufptr++;
3302						}
3303					}
3304				}
3305				while (*c != '\n') {	/* Copy the remainder */
3306					if (!isspace(*c)) {
3307						*bufptr = *c;
3308						bufptr++;
3309						if (bufptr >= buff + MED_STR_MAX)
3310							break;
3311					}
3312					c++;
3313				}
3314				*bufptr = '\n';
3315				scan_troff_mandoc(buff, 1, NULL);
3316
3317				out_html(NEWLINE);
3318				if (fillout)
3319					curpos++;
3320				else
3321					curpos = 0;
3322			}
3323			break;
3324		case V('F', 'l'):	/* BSD mandoc */
3325			trans_char(c, '"', '\a');
3326			c = c + j;
3327			out_html("-");
3328			if (*c != '\n') {
3329				out_html(change_to_font('B'));
3330				c = scan_troff_mandoc(c, 1, NULL);
3331				out_html(change_to_font('R'));
3332			}
3333			out_html(NEWLINE);
3334			if (fillout)
3335				curpos++;
3336			else
3337				curpos = 0;
3338			break;
3339		case V('P', 'a'):	/* BSD mandoc */
3340		case V('P', 'f'):	/* BSD mandoc */
3341			trans_char(c, '"', '\a');
3342			c = c + j;
3343			if (*c == '\n')
3344				c++;
3345			c = scan_troff_mandoc(c, 1, NULL);
3346			out_html(NEWLINE);
3347			if (fillout)
3348				curpos++;
3349			else
3350				curpos = 0;
3351			break;
3352		case V('P', 'p'):	/* BSD mandoc */
3353			if (fillout)
3354				out_html("<P>\n");
3355			else {
3356				out_html(NEWLINE);
3357				NEWLINE[0] = '\n';
3358			}
3359			curpos = 0;
3360			c = skip_till_newline(c);
3361			break;
3362		case V('D', 'q'):	/* BSD mandoc */
3363			trans_char(c, '"', '\a');
3364			c = c + j;
3365			if (*c == '\n')
3366				c++;
3367			out_html("``");
3368			c = scan_troff_mandoc(c, 1, NULL);
3369			out_html("''");
3370			out_html(NEWLINE);
3371			if (fillout)
3372				curpos++;
3373			else
3374				curpos = 0;
3375			break;
3376		case V('O', 'p'):	/* BSD mandoc */
3377			trans_char(c, '"', '\a');
3378			c = c + j;
3379			if (*c == '\n')
3380				c++;
3381			out_html(change_to_font('R'));
3382			out_html("[");
3383			c = scan_troff_mandoc(c, 1, NULL);
3384			out_html(change_to_font('R'));
3385			out_html("]");
3386			out_html(NEWLINE);
3387			if (fillout)
3388				curpos++;
3389			else
3390				curpos = 0;
3391			break;
3392		case V('O', 'o'):	/* BSD mandoc */
3393			trans_char(c, '"', '\a');
3394			c = c + j;
3395			if (*c == '\n')
3396				c++;
3397			out_html(change_to_font('R'));
3398			out_html("[");
3399			c = scan_troff_mandoc(c, 1, NULL);
3400			if (fillout)
3401				curpos++;
3402			else
3403				curpos = 0;
3404			break;
3405		case V('O', 'c'):	/* BSD mandoc */
3406			trans_char(c, '"', '\a');
3407			c = c + j;
3408			c = scan_troff_mandoc(c, 1, NULL);
3409			out_html(change_to_font('R'));
3410			out_html("]");
3411			if (fillout)
3412				curpos++;
3413			else
3414				curpos = 0;
3415			break;
3416		case V('P', 'q'):	/* BSD mandoc */
3417			trans_char(c, '"', '\a');
3418			c = c + j;
3419			if (*c == '\n')
3420				c++;
3421			out_html("(");
3422			c = scan_troff_mandoc(c, 1, NULL);
3423			out_html(")");
3424			out_html(NEWLINE);
3425			if (fillout)
3426				curpos++;
3427			else
3428				curpos = 0;
3429			break;
3430		case V('Q', 'l'):	/* BSD mandoc */
3431			{	/* Single quote first word in the line */
3432				char   *sp;
3433
3434				trans_char(c, '"', '\a');
3435				c = c + j;
3436				if (*c == '\n')
3437					c++;
3438				sp = c;
3439				do {	/* Find first whitespace after the
3440					 * first word that isn't a mandoc
3441					 * macro */
3442					while (*sp && isspace(*sp))
3443						sp++;
3444					while (*sp && !isspace(*sp))
3445						sp++;
3446				} while (*sp && isupper(*(sp - 2)) && islower(*(sp - 1)));
3447
3448				/*
3449				 * Use a newline to mark the end of text to
3450				 * be quoted
3451				 */
3452				if (*sp)
3453					*sp = '\n';
3454				out_html("`");	/* Quote the text */
3455				c = scan_troff_mandoc(c, 1, NULL);
3456				out_html("'");
3457				out_html(NEWLINE);
3458				if (fillout)
3459					curpos++;
3460				else
3461					curpos = 0;
3462				break;
3463			}
3464		case V('S', 'q'):	/* BSD mandoc */
3465			trans_char(c, '"', '\a');
3466			c = c + j;
3467			if (*c == '\n')
3468				c++;
3469			out_html("`");
3470			c = scan_troff_mandoc(c, 1, NULL);
3471			out_html("'");
3472			out_html(NEWLINE);
3473			if (fillout)
3474				curpos++;
3475			else
3476				curpos = 0;
3477			break;
3478		case V('A', 'r'):	/* BSD mandoc */
3479			/* parse one line in italics */
3480			out_html(change_to_font('I'));
3481			trans_char(c, '"', '\a');
3482			c = c + j;
3483			if (*c == '\n') {	/* An empty Ar means "file
3484						 * ..." */
3485				out_html("file ...");
3486			} else {
3487				c = scan_troff_mandoc(c, 1, NULL);
3488			}
3489			out_html(change_to_font('R'));
3490			out_html(NEWLINE);
3491			if (fillout)
3492				curpos++;
3493			else
3494				curpos = 0;
3495			break;
3496		case V('A', 'd'):	/* BSD mandoc */
3497		case V('E', 'm'):	/* BSD mandoc */
3498		case V('V', 'a'):	/* BSD mandoc */
3499		case V('X', 'c'):	/* BSD mandoc */
3500			/* parse one line in italics */
3501			out_html(change_to_font('I'));
3502			trans_char(c, '"', '\a');
3503			c = c + j;
3504			if (*c == '\n')
3505				c++;
3506			c = scan_troff_mandoc(c, 1, NULL);
3507			out_html(change_to_font('R'));
3508			out_html(NEWLINE);
3509			if (fillout)
3510				curpos++;
3511			else
3512				curpos = 0;
3513			break;
3514		case V('N', 'd'):	/* BSD mandoc */
3515			trans_char(c, '"', '\a');
3516			c = c + j;
3517			if (*c == '\n')
3518				c++;
3519			out_html(" - ");
3520			c = scan_troff_mandoc(c, 1, NULL);
3521			out_html(NEWLINE);
3522			if (fillout)
3523				curpos++;
3524			else
3525				curpos = 0;
3526			break;
3527		case V('N', 'm'):	/* BSD mandoc */
3528			{
3529				static char mandoc_name[NULL_TERMINATED(SMALL_STR_MAX)] = "";
3530
3531				trans_char(c, '"', '\a');
3532				c = c + j;
3533				if (mandoc_synopsis) {	/* Break lines only in
3534							 * the Synopsis. The
3535							 * Synopsis section
3536							 * seems to be treated
3537							 * as a special case -
3538							 * Bummer! */
3539					static int count = 0;	/* Don't break on the
3540								 * first Nm */
3541
3542					if (count) {
3543						out_html("<BR>");
3544					} else {
3545						char   *end = strchr(c, '\n');
3546
3547						if (end) {	/* Remember the name for
3548								 * later. */
3549							strlimitcpy(mandoc_name, c, end - c, SMALL_STR_MAX);
3550						}
3551					}
3552					count++;
3553				}
3554				out_html(change_to_font('B'));
3555				while (*c == ' ' || *c == '\t')
3556					c++;
3557				if (*c == '\n') {	/* If Nm has no
3558							 * argument, use one
3559							 * from an earlier Nm
3560							 * command that did have
3561							 * one.  Hope there
3562							 * aren't too many
3563							 * commands that do
3564							 * this. */
3565					out_html(mandoc_name);
3566				} else {
3567					c = scan_troff_mandoc(c, 1, NULL);
3568				}
3569				out_html(change_to_font('R'));
3570				out_html(NEWLINE);
3571				if (fillout)
3572					curpos++;
3573				else
3574					curpos = 0;
3575				break;
3576			}
3577		case V('C', 'd'):	/* BSD mandoc */
3578		case V('C', 'm'):	/* BSD mandoc */
3579		case V('I', 'c'):	/* BSD mandoc */
3580		case V('M', 's'):	/* BSD mandoc */
3581		case V('O', 'r'):	/* BSD mandoc */
3582		case V('S', 'y'):	/* BSD mandoc */
3583			/* parse one line in bold */
3584			out_html(change_to_font('B'));
3585			trans_char(c, '"', '\a');
3586			c = c + j;
3587			if (*c == '\n')
3588				c++;
3589			c = scan_troff_mandoc(c, 1, NULL);
3590			out_html(change_to_font('R'));
3591			out_html(NEWLINE);
3592			if (fillout)
3593				curpos++;
3594			else
3595				curpos = 0;
3596			break;
3597		case V('D', 'v'):	/* BSD mandoc */
3598		case V('E', 'v'):	/* BSD mandoc */
3599		case V('F', 'r'):	/* BSD mandoc */
3600		case V('L', 'i'):	/* BSD mandoc */
3601		case V('N', 'o'):	/* BSD mandoc */
3602		case V('N', 's'):	/* BSD mandoc */
3603		case V('T', 'n'):	/* BSD mandoc */
3604		case V('n', 'N'):	/* BSD mandoc */
3605			trans_char(c, '"', '\a');
3606			c = c + j;
3607			if (*c == '\n')
3608				c++;
3609			out_html(change_to_font('B'));
3610			c = scan_troff_mandoc(c, 1, NULL);
3611			out_html(change_to_font('R'));
3612			out_html(NEWLINE);
3613			if (fillout)
3614				curpos++;
3615			else
3616				curpos = 0;
3617			break;
3618		case V('%', 'A'):	/* BSD mandoc biblio stuff */
3619		case V('%', 'D'):
3620		case V('%', 'N'):
3621		case V('%', 'O'):
3622		case V('%', 'P'):
3623		case V('%', 'Q'):
3624		case V('%', 'V'):
3625			c = c + j;
3626			if (*c == '\n')
3627				c++;
3628			c = scan_troff(c, 1, NULL);	/* Don't allow embedded
3629							 * mandoc coms */
3630			if (fillout)
3631				curpos++;
3632			else
3633				curpos = 0;
3634			break;
3635		case V('%', 'B'):
3636		case V('%', 'J'):
3637		case V('%', 'R'):
3638		case V('%', 'T'):
3639			c = c + j;
3640			out_html(change_to_font('I'));
3641			if (*c == '\n')
3642				c++;
3643			c = scan_troff(c, 1, NULL);	/* Don't allow embedded
3644							 * mandoc coms */
3645			out_html(change_to_font('R'));
3646			if (fillout)
3647				curpos++;
3648			else
3649				curpos = 0;
3650			break;
3651		default:
3652			/* search macro database of self-defined macros */
3653			owndef = defdef;
3654			while (owndef && owndef->nr != i)
3655				owndef = owndef->next;
3656			if (owndef) {
3657				char  **oldargument;
3658				int     deflen;
3659				int     onff;
3660
3661				sl = fill_words(c + j, wordlist, &words);
3662				c = sl + 1;
3663				*sl = '\0';
3664				for (i = 1; i < words; i++)
3665					wordlist[i][-1] = '\0';
3666				for (i = 0; i < words; i++) {
3667					char   *h = NULL;
3668
3669					if (mandoc_command) {
3670						scan_troff_mandoc(wordlist[i], 1, &h);
3671					} else {
3672						scan_troff(wordlist[i], 1, &h);
3673					}
3674					wordlist[i] = h;
3675				}
3676				for (i = words; i < 20; i++)
3677					wordlist[i] = NULL;
3678				deflen = strlen(owndef->st);
3679				for (i = 0; owndef->st[deflen + 2 + i] = owndef->st[i]; i++);
3680				oldargument = argument;
3681				argument = wordlist;
3682				onff = newline_for_fun;
3683				if (mandoc_command) {
3684					scan_troff_mandoc(owndef->st + deflen + 2, 0, NULL);
3685				} else {
3686					scan_troff(owndef->st + deflen + 2, 0, NULL);
3687				}
3688				newline_for_fun = onff;
3689				argument = oldargument;
3690				for (i = 0; i < words; i++)
3691					if (wordlist[i])
3692						free(wordlist[i]);
3693				*sl = '\n';
3694			} else if (mandoc_command &&
3695				   ((isupper(*c) && islower(*(c + 1)))
3696				    || (islower(*c) && isupper(*(c + 1))))
3697				) {	/* Let through any BSD mandoc
3698					 * commands that haven't been delt
3699					 * with. I don't want to miss
3700					 * anything out of the text. */
3701				char    buf[4];
3702
3703				strncpy(buf, c, 2);
3704				buf[2] = ' ';
3705				buf[3] = '\0';
3706				out_html(buf);	/* Print the command (it
3707						 * might just be text). */
3708				c = c + j;
3709				trans_char(c, '"', '\a');
3710				if (*c == '\n')
3711					c++;
3712				out_html(change_to_font('R'));
3713				c = scan_troff(c, 1, NULL);
3714				out_html(NEWLINE);
3715				if (fillout)
3716					curpos++;
3717				else
3718					curpos = 0;
3719			} else {
3720				c = skip_till_newline(c);
3721			}
3722			break;
3723		}
3724	}
3725	if (fillout) {
3726		out_html(NEWLINE);
3727		curpos++;
3728	}
3729	NEWLINE[0] = '\n';
3730	return c;
3731}
3732
3733static void
3734flush(void)
3735{
3736}
3737
3738static int contained_tab = 0;
3739static int mandoc_line = 0;	/* Signals whether to look for embedded
3740				 * mandoc commands. */
3741
3742/* san : stop at newline */
3743static char *
3744scan_troff(char *c, int san, char **result)
3745{
3746	char   *h;
3747	char    intbuff[NULL_TERMINATED(MED_STR_MAX)];
3748	int     ibp = 0;
3749	int     i;
3750	char   *exbuffer;
3751	int     exbuffpos, exbuffmax, exscaninbuff, exnewline_for_fun;
3752	int     usenbsp = 0;
3753
3754#define FLUSHIBP  if (ibp) { intbuff[ibp]=0; out_html(intbuff); ibp=0; }
3755
3756	exbuffer = buffer;
3757	exbuffpos = buffpos;
3758	exbuffmax = buffmax;
3759	exnewline_for_fun = newline_for_fun;
3760	exscaninbuff = scaninbuff;
3761	newline_for_fun = 0;
3762	if (result) {
3763		if (*result) {
3764			buffer = *result;
3765			buffpos = strlen(buffer);
3766			buffmax = buffpos;
3767		} else {
3768			buffer = stralloc(LARGE_STR_MAX);
3769			buffpos = 0;
3770			buffmax = LARGE_STR_MAX;
3771		}
3772		scaninbuff = 1;
3773	}
3774	h = c;
3775	/* start scanning */
3776
3777	while (*h && (!san || newline_for_fun || *h != '\n')) {
3778
3779		if (*h == escapesym) {
3780			h++;
3781			FLUSHIBP;
3782			h = scan_escape(h);
3783		} else if (*h == controlsym && h[-1] == '\n') {
3784			h++;
3785			FLUSHIBP;
3786			h = scan_request(h);
3787			if (san && h[-1] == '\n')
3788				h--;
3789		} else if (mandoc_line
3790			   && *(h) && isupper(*(h))
3791			   && *(h + 1) && islower(*(h + 1))
3792			   && *(h + 2) && isspace(*(h + 2))) {
3793			/*
3794			 * BSD imbedded command eg ".It Fl Ar arg1 Fl Ar
3795			 * arg2"
3796			 */
3797			FLUSHIBP;
3798			h = scan_request(h);
3799			if (san && h[-1] == '\n')
3800				h--;
3801		} else if (*h == nobreaksym && h[-1] == '\n') {
3802			h++;
3803			FLUSHIBP;
3804			h = scan_request(h);
3805			if (san && h[-1] == '\n')
3806				h--;
3807		} else {
3808			int     mx;
3809
3810			if (h[-1] == '\n' && still_dd && isalnum(*h)) {
3811				/*
3812				 * sometimes a .HP request is not followed by
3813				 * a .br request
3814				 */
3815				FLUSHIBP;
3816				out_html("<DD>");
3817				curpos = 0;
3818				still_dd = 0;
3819			}
3820			switch (*h) {
3821			case '&':
3822				intbuff[ibp++] = '&';
3823				intbuff[ibp++] = 'a';
3824				intbuff[ibp++] = 'm';
3825				intbuff[ibp++] = 'p';
3826				intbuff[ibp++] = ';';
3827				curpos++;
3828				break;
3829			case '<':
3830				intbuff[ibp++] = '&';
3831				intbuff[ibp++] = 'l';
3832				intbuff[ibp++] = 't';
3833				intbuff[ibp++] = ';';
3834				curpos++;
3835				break;
3836			case '>':
3837				intbuff[ibp++] = '&';
3838				intbuff[ibp++] = 'g';
3839				intbuff[ibp++] = 't';
3840				intbuff[ibp++] = ';';
3841				curpos++;
3842				break;
3843			case '"':
3844				intbuff[ibp++] = '&';
3845				intbuff[ibp++] = 'q';
3846				intbuff[ibp++] = 'u';
3847				intbuff[ibp++] = 'o';
3848				intbuff[ibp++] = 't';
3849				intbuff[ibp++] = ';';
3850				curpos++;
3851				break;
3852			case '\n':
3853				if (h[-1] == '\n' && fillout) {
3854					intbuff[ibp++] = '<';
3855					intbuff[ibp++] = 'P';
3856					intbuff[ibp++] = '>';
3857				}
3858				if (contained_tab && fillout) {
3859					intbuff[ibp++] = '<';
3860					intbuff[ibp++] = 'B';
3861					intbuff[ibp++] = 'R';
3862					intbuff[ibp++] = '>';
3863				}
3864				contained_tab = 0;
3865				curpos = 0;
3866				usenbsp = 0;
3867				intbuff[ibp++] = '\n';
3868				break;
3869			case '\t':
3870				{
3871					int     curtab = 0;
3872
3873					contained_tab = 1;
3874					FLUSHIBP;
3875					/* like a typewriter, not like TeX */
3876					tabstops[19] = curpos + 1;
3877					while (curtab < maxtstop && tabstops[curtab] <= curpos)
3878						curtab++;
3879					if (curtab < maxtstop) {
3880						if (!fillout) {
3881							while (curpos < tabstops[curtab]) {
3882								intbuff[ibp++] = ' ';
3883								if (ibp > 480) {
3884									FLUSHIBP;
3885								}
3886								curpos++;
3887							}
3888						} else {
3889							out_html("<TT>");
3890							while (curpos < tabstops[curtab]) {
3891								out_html("&nbsp;");
3892								curpos++;
3893							}
3894							out_html("</TT>");
3895						}
3896					}
3897				}
3898				break;
3899			default:
3900				if (*h == ' ' && (h[-1] == '\n' || usenbsp)) {
3901					FLUSHIBP;
3902					if (!usenbsp && fillout) {
3903						out_html("<BR>");
3904						curpos = 0;
3905					}
3906					usenbsp = fillout;
3907					if (usenbsp)
3908						out_html("&nbsp;");
3909					else
3910						intbuff[ibp++] = ' ';
3911				} else if (*h > 31 && *h < 127)
3912					intbuff[ibp++] = *h;
3913				else if (((unsigned char) (*h)) > 127) {
3914					intbuff[ibp++] = '&';
3915					intbuff[ibp++] = '#';
3916					intbuff[ibp++] = '0' + ((unsigned char) (*h)) / 100;
3917					intbuff[ibp++] = '0' + (((unsigned char) (*h)) % 100) / 10;
3918					intbuff[ibp++] = '0' + ((unsigned char) (*h)) % 10;
3919					intbuff[ibp++] = ';';
3920				}
3921				curpos++;
3922				break;
3923			}
3924			if (ibp > (MED_STR_MAX - 20))
3925				FLUSHIBP;
3926			h++;
3927		}
3928	}
3929	FLUSHIBP;
3930	if (buffer)
3931		buffer[buffpos] = '\0';
3932	if (san && *h)
3933		h++;
3934	newline_for_fun = exnewline_for_fun;
3935	if (result) {
3936		*result = buffer;
3937		buffer = exbuffer;
3938		buffpos = exbuffpos;
3939		buffmax = exbuffmax;
3940		scaninbuff = exscaninbuff;
3941	}
3942	return h;
3943}
3944
3945
3946static char *
3947scan_troff_mandoc(char *c, int san, char **result)
3948{
3949	char   *ret, *end = c;
3950	int     oldval = mandoc_line;
3951
3952	mandoc_line = 1;
3953	while (*end && *end != '\n') {
3954		end++;
3955	}
3956
3957	if (end > c + 2
3958	    && ispunct(*(end - 1))
3959	    && isspace(*(end - 2)) && *(end - 2) != '\n') {
3960		/*
3961		 * Don't format lonely punctuation E.g. in "xyz ," format the
3962		 * xyz and then append the comma removing the space.
3963		 */
3964		*(end - 2) = '\n';
3965		ret = scan_troff(c, san, result);
3966		*(end - 2) = *(end - 1);
3967		*(end - 1) = ' ';
3968	} else {
3969		ret = scan_troff(c, san, result);
3970	}
3971	mandoc_line = oldval;
3972	return ret;
3973}
3974
3975main(int argc, char **argv)
3976{
3977	FILE   *f;
3978	char   *t;
3979	int     l, i;
3980	char   *buf;
3981	char   *h, *fullname;
3982	STRDEF *stdf;
3983
3984	t = NULL;
3985	while ((i = getopt(argc, argv, "")) != EOF) {
3986		switch (i) {
3987		default:
3988			usage();
3989			exit(EXIT_USAGE);
3990		}
3991	}
3992
3993	if (argc != 2) {
3994		usage();
3995		exit(EXIT_USAGE);
3996	}
3997	manpage = h = t = argv[1];
3998	i = 0;
3999
4000	buf = read_man_page(h);
4001	if (!buf) {
4002		fprintf(stderr, "man2html: cannot read %s: %s\n", h, strerror(errno));
4003		exit(1);
4004	}
4005#ifdef MAKEINDEX
4006	idxfile = fopen(INDEXFILE, "a");
4007#endif
4008	stdf = &standardchar[0];
4009	i = 0;
4010	while (stdf->nr) {
4011		stdf->next = &standardchar[i];
4012		stdf = stdf->next;
4013		i++;
4014	}
4015	chardef = &standardchar[0];
4016
4017	stdf = &standardstring[0];
4018	i = 0;
4019	while (stdf->nr) {
4020		stdf->next = &standardstring[i];
4021		stdf = stdf->next;
4022		i++;
4023	}
4024	strdef = &standardstring[0];
4025
4026	intdef = &standardint[0];
4027	i = 0;
4028	while (intdef->nr) {
4029		intdef->next = &standardint[i];
4030		intdef = intdef->next;
4031		i++;
4032	}
4033	intdef = &standardint[0];
4034
4035	defdef = NULL;
4036
4037	scan_troff(buf + 1, 0, NULL);
4038
4039	while (itemdepth || dl_set[itemdepth]) {
4040		out_html("</DL>\n");
4041		if (dl_set[itemdepth])
4042			dl_set[itemdepth] = 0;
4043		else if (itemdepth > 0)
4044			itemdepth--;
4045	}
4046
4047	out_html(change_to_font(0));
4048	out_html(change_to_size(0));
4049	if (!fillout) {
4050		fillout = 1;
4051		out_html("</PRE>");
4052	}
4053	out_html(NEWLINE);
4054
4055	if (output_possible) {
4056		outputPageFooter(th_version, th_datestr, th_page_and_sec);
4057		/* &nbsp; for mosaic users */
4058		fputs("<HR>\n<A NAME=\"index\">&nbsp;</A><H2>Index</H2>\n<DL>\n", stdout);
4059		manidx[mip] = 0;
4060		fputs(manidx, stdout);
4061		if (subs)
4062			fputs("</DL>\n", stdout);
4063		fputs("</DL>\n", stdout);
4064		print_sig();
4065		fputs("</BODY>\n</HTML>\n", stdout);
4066	} else
4067		fprintf(stderr, "man2html: no output produced\n");
4068#ifdef MAKEINDEX
4069	if (idxfile)
4070		fclose(idxfile);
4071#endif
4072	exit(EXIT_SUCCESS);
4073}
4074