xref: /openbsd-src/usr.bin/mandoc/mdoc_html.c (revision c020cf82e0cc147236f01a8dca7052034cf9d30d)
1 /* $OpenBSD: mdoc_html.c,v 1.215 2020/04/19 15:15:54 schwarze Exp $ */
2 /*
3  * Copyright (c) 2014-2020 Ingo Schwarze <schwarze@openbsd.org>
4  * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  *
18  * HTML formatter for mdoc(7) used by mandoc(1).
19  */
20 #include <sys/types.h>
21 
22 #include <assert.h>
23 #include <ctype.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <unistd.h>
28 
29 #include "mandoc_aux.h"
30 #include "mandoc.h"
31 #include "roff.h"
32 #include "mdoc.h"
33 #include "out.h"
34 #include "html.h"
35 #include "main.h"
36 
37 #define	MDOC_ARGS	  const struct roff_meta *meta, \
38 			  struct roff_node *n, \
39 			  struct html *h
40 
41 #ifndef MIN
42 #define	MIN(a,b)	((/*CONSTCOND*/(a)<(b))?(a):(b))
43 #endif
44 
45 struct	mdoc_html_act {
46 	int		(*pre)(MDOC_ARGS);
47 	void		(*post)(MDOC_ARGS);
48 };
49 
50 static	void		  print_mdoc_head(const struct roff_meta *,
51 				struct html *);
52 static	void		  print_mdoc_node(MDOC_ARGS);
53 static	void		  print_mdoc_nodelist(MDOC_ARGS);
54 static	void		  synopsis_pre(struct html *, struct roff_node *);
55 
56 static	void		  mdoc_root_post(const struct roff_meta *,
57 				struct html *);
58 static	int		  mdoc_root_pre(const struct roff_meta *,
59 				struct html *);
60 
61 static	void		  mdoc__x_post(MDOC_ARGS);
62 static	int		  mdoc__x_pre(MDOC_ARGS);
63 static	int		  mdoc_abort_pre(MDOC_ARGS);
64 static	int		  mdoc_ad_pre(MDOC_ARGS);
65 static	int		  mdoc_an_pre(MDOC_ARGS);
66 static	int		  mdoc_ap_pre(MDOC_ARGS);
67 static	int		  mdoc_ar_pre(MDOC_ARGS);
68 static	int		  mdoc_bd_pre(MDOC_ARGS);
69 static	int		  mdoc_bf_pre(MDOC_ARGS);
70 static	void		  mdoc_bk_post(MDOC_ARGS);
71 static	int		  mdoc_bk_pre(MDOC_ARGS);
72 static	int		  mdoc_bl_pre(MDOC_ARGS);
73 static	int		  mdoc_cd_pre(MDOC_ARGS);
74 static	int		  mdoc_code_pre(MDOC_ARGS);
75 static	int		  mdoc_d1_pre(MDOC_ARGS);
76 static	int		  mdoc_fa_pre(MDOC_ARGS);
77 static	int		  mdoc_fd_pre(MDOC_ARGS);
78 static	int		  mdoc_fl_pre(MDOC_ARGS);
79 static	int		  mdoc_fn_pre(MDOC_ARGS);
80 static	int		  mdoc_ft_pre(MDOC_ARGS);
81 static	int		  mdoc_em_pre(MDOC_ARGS);
82 static	void		  mdoc_eo_post(MDOC_ARGS);
83 static	int		  mdoc_eo_pre(MDOC_ARGS);
84 static	int		  mdoc_ex_pre(MDOC_ARGS);
85 static	void		  mdoc_fo_post(MDOC_ARGS);
86 static	int		  mdoc_fo_pre(MDOC_ARGS);
87 static	int		  mdoc_igndelim_pre(MDOC_ARGS);
88 static	int		  mdoc_in_pre(MDOC_ARGS);
89 static	int		  mdoc_it_pre(MDOC_ARGS);
90 static	int		  mdoc_lb_pre(MDOC_ARGS);
91 static	int		  mdoc_lk_pre(MDOC_ARGS);
92 static	int		  mdoc_mt_pre(MDOC_ARGS);
93 static	int		  mdoc_nd_pre(MDOC_ARGS);
94 static	int		  mdoc_nm_pre(MDOC_ARGS);
95 static	int		  mdoc_no_pre(MDOC_ARGS);
96 static	int		  mdoc_ns_pre(MDOC_ARGS);
97 static	int		  mdoc_pa_pre(MDOC_ARGS);
98 static	void		  mdoc_pf_post(MDOC_ARGS);
99 static	int		  mdoc_pp_pre(MDOC_ARGS);
100 static	void		  mdoc_quote_post(MDOC_ARGS);
101 static	int		  mdoc_quote_pre(MDOC_ARGS);
102 static	int		  mdoc_rs_pre(MDOC_ARGS);
103 static	int		  mdoc_sh_pre(MDOC_ARGS);
104 static	int		  mdoc_skip_pre(MDOC_ARGS);
105 static	int		  mdoc_sm_pre(MDOC_ARGS);
106 static	int		  mdoc_ss_pre(MDOC_ARGS);
107 static	int		  mdoc_st_pre(MDOC_ARGS);
108 static	int		  mdoc_sx_pre(MDOC_ARGS);
109 static	int		  mdoc_sy_pre(MDOC_ARGS);
110 static	int		  mdoc_tg_pre(MDOC_ARGS);
111 static	int		  mdoc_va_pre(MDOC_ARGS);
112 static	int		  mdoc_vt_pre(MDOC_ARGS);
113 static	int		  mdoc_xr_pre(MDOC_ARGS);
114 static	int		  mdoc_xx_pre(MDOC_ARGS);
115 
116 static const struct mdoc_html_act mdoc_html_acts[MDOC_MAX - MDOC_Dd] = {
117 	{NULL, NULL}, /* Dd */
118 	{NULL, NULL}, /* Dt */
119 	{NULL, NULL}, /* Os */
120 	{mdoc_sh_pre, NULL }, /* Sh */
121 	{mdoc_ss_pre, NULL }, /* Ss */
122 	{mdoc_pp_pre, NULL}, /* Pp */
123 	{mdoc_d1_pre, NULL}, /* D1 */
124 	{mdoc_d1_pre, NULL}, /* Dl */
125 	{mdoc_bd_pre, NULL}, /* Bd */
126 	{NULL, NULL}, /* Ed */
127 	{mdoc_bl_pre, NULL}, /* Bl */
128 	{NULL, NULL}, /* El */
129 	{mdoc_it_pre, NULL}, /* It */
130 	{mdoc_ad_pre, NULL}, /* Ad */
131 	{mdoc_an_pre, NULL}, /* An */
132 	{mdoc_ap_pre, NULL}, /* Ap */
133 	{mdoc_ar_pre, NULL}, /* Ar */
134 	{mdoc_cd_pre, NULL}, /* Cd */
135 	{mdoc_code_pre, NULL}, /* Cm */
136 	{mdoc_code_pre, NULL}, /* Dv */
137 	{mdoc_code_pre, NULL}, /* Er */
138 	{mdoc_code_pre, NULL}, /* Ev */
139 	{mdoc_ex_pre, NULL}, /* Ex */
140 	{mdoc_fa_pre, NULL}, /* Fa */
141 	{mdoc_fd_pre, NULL}, /* Fd */
142 	{mdoc_fl_pre, NULL}, /* Fl */
143 	{mdoc_fn_pre, NULL}, /* Fn */
144 	{mdoc_ft_pre, NULL}, /* Ft */
145 	{mdoc_code_pre, NULL}, /* Ic */
146 	{mdoc_in_pre, NULL}, /* In */
147 	{mdoc_code_pre, NULL}, /* Li */
148 	{mdoc_nd_pre, NULL}, /* Nd */
149 	{mdoc_nm_pre, NULL}, /* Nm */
150 	{mdoc_quote_pre, mdoc_quote_post}, /* Op */
151 	{mdoc_abort_pre, NULL}, /* Ot */
152 	{mdoc_pa_pre, NULL}, /* Pa */
153 	{mdoc_ex_pre, NULL}, /* Rv */
154 	{mdoc_st_pre, NULL}, /* St */
155 	{mdoc_va_pre, NULL}, /* Va */
156 	{mdoc_vt_pre, NULL}, /* Vt */
157 	{mdoc_xr_pre, NULL}, /* Xr */
158 	{mdoc__x_pre, mdoc__x_post}, /* %A */
159 	{mdoc__x_pre, mdoc__x_post}, /* %B */
160 	{mdoc__x_pre, mdoc__x_post}, /* %D */
161 	{mdoc__x_pre, mdoc__x_post}, /* %I */
162 	{mdoc__x_pre, mdoc__x_post}, /* %J */
163 	{mdoc__x_pre, mdoc__x_post}, /* %N */
164 	{mdoc__x_pre, mdoc__x_post}, /* %O */
165 	{mdoc__x_pre, mdoc__x_post}, /* %P */
166 	{mdoc__x_pre, mdoc__x_post}, /* %R */
167 	{mdoc__x_pre, mdoc__x_post}, /* %T */
168 	{mdoc__x_pre, mdoc__x_post}, /* %V */
169 	{NULL, NULL}, /* Ac */
170 	{mdoc_quote_pre, mdoc_quote_post}, /* Ao */
171 	{mdoc_quote_pre, mdoc_quote_post}, /* Aq */
172 	{mdoc_xx_pre, NULL}, /* At */
173 	{NULL, NULL}, /* Bc */
174 	{mdoc_bf_pre, NULL}, /* Bf */
175 	{mdoc_quote_pre, mdoc_quote_post}, /* Bo */
176 	{mdoc_quote_pre, mdoc_quote_post}, /* Bq */
177 	{mdoc_xx_pre, NULL}, /* Bsx */
178 	{mdoc_xx_pre, NULL}, /* Bx */
179 	{mdoc_skip_pre, NULL}, /* Db */
180 	{NULL, NULL}, /* Dc */
181 	{mdoc_quote_pre, mdoc_quote_post}, /* Do */
182 	{mdoc_quote_pre, mdoc_quote_post}, /* Dq */
183 	{NULL, NULL}, /* Ec */ /* FIXME: no space */
184 	{NULL, NULL}, /* Ef */
185 	{mdoc_em_pre, NULL}, /* Em */
186 	{mdoc_eo_pre, mdoc_eo_post}, /* Eo */
187 	{mdoc_xx_pre, NULL}, /* Fx */
188 	{mdoc_no_pre, NULL}, /* Ms */
189 	{mdoc_no_pre, NULL}, /* No */
190 	{mdoc_ns_pre, NULL}, /* Ns */
191 	{mdoc_xx_pre, NULL}, /* Nx */
192 	{mdoc_xx_pre, NULL}, /* Ox */
193 	{NULL, NULL}, /* Pc */
194 	{mdoc_igndelim_pre, mdoc_pf_post}, /* Pf */
195 	{mdoc_quote_pre, mdoc_quote_post}, /* Po */
196 	{mdoc_quote_pre, mdoc_quote_post}, /* Pq */
197 	{NULL, NULL}, /* Qc */
198 	{mdoc_quote_pre, mdoc_quote_post}, /* Ql */
199 	{mdoc_quote_pre, mdoc_quote_post}, /* Qo */
200 	{mdoc_quote_pre, mdoc_quote_post}, /* Qq */
201 	{NULL, NULL}, /* Re */
202 	{mdoc_rs_pre, NULL}, /* Rs */
203 	{NULL, NULL}, /* Sc */
204 	{mdoc_quote_pre, mdoc_quote_post}, /* So */
205 	{mdoc_quote_pre, mdoc_quote_post}, /* Sq */
206 	{mdoc_sm_pre, NULL}, /* Sm */
207 	{mdoc_sx_pre, NULL}, /* Sx */
208 	{mdoc_sy_pre, NULL}, /* Sy */
209 	{NULL, NULL}, /* Tn */
210 	{mdoc_xx_pre, NULL}, /* Ux */
211 	{NULL, NULL}, /* Xc */
212 	{NULL, NULL}, /* Xo */
213 	{mdoc_fo_pre, mdoc_fo_post}, /* Fo */
214 	{NULL, NULL}, /* Fc */
215 	{mdoc_quote_pre, mdoc_quote_post}, /* Oo */
216 	{NULL, NULL}, /* Oc */
217 	{mdoc_bk_pre, mdoc_bk_post}, /* Bk */
218 	{NULL, NULL}, /* Ek */
219 	{NULL, NULL}, /* Bt */
220 	{NULL, NULL}, /* Hf */
221 	{mdoc_em_pre, NULL}, /* Fr */
222 	{NULL, NULL}, /* Ud */
223 	{mdoc_lb_pre, NULL}, /* Lb */
224 	{mdoc_abort_pre, NULL}, /* Lp */
225 	{mdoc_lk_pre, NULL}, /* Lk */
226 	{mdoc_mt_pre, NULL}, /* Mt */
227 	{mdoc_quote_pre, mdoc_quote_post}, /* Brq */
228 	{mdoc_quote_pre, mdoc_quote_post}, /* Bro */
229 	{NULL, NULL}, /* Brc */
230 	{mdoc__x_pre, mdoc__x_post}, /* %C */
231 	{mdoc_skip_pre, NULL}, /* Es */
232 	{mdoc_quote_pre, mdoc_quote_post}, /* En */
233 	{mdoc_xx_pre, NULL}, /* Dx */
234 	{mdoc__x_pre, mdoc__x_post}, /* %Q */
235 	{mdoc__x_pre, mdoc__x_post}, /* %U */
236 	{NULL, NULL}, /* Ta */
237 	{mdoc_tg_pre, NULL}, /* Tg */
238 };
239 
240 
241 /*
242  * See the same function in mdoc_term.c for documentation.
243  */
244 static void
245 synopsis_pre(struct html *h, struct roff_node *n)
246 {
247 	struct roff_node *np;
248 
249 	if ((n->flags & NODE_SYNPRETTY) == 0 ||
250 	    (np = roff_node_prev(n)) == NULL)
251 		return;
252 
253 	if (np->tok == n->tok &&
254 	    MDOC_Fo != n->tok &&
255 	    MDOC_Ft != n->tok &&
256 	    MDOC_Fn != n->tok) {
257 		print_otag(h, TAG_BR, "");
258 		return;
259 	}
260 
261 	switch (np->tok) {
262 	case MDOC_Fd:
263 	case MDOC_Fn:
264 	case MDOC_Fo:
265 	case MDOC_In:
266 	case MDOC_Vt:
267 		break;
268 	case MDOC_Ft:
269 		if (n->tok != MDOC_Fn && n->tok != MDOC_Fo)
270 			break;
271 		/* FALLTHROUGH */
272 	default:
273 		print_otag(h, TAG_BR, "");
274 		return;
275 	}
276 	html_close_paragraph(h);
277 	print_otag(h, TAG_P, "c", "Pp");
278 }
279 
280 void
281 html_mdoc(void *arg, const struct roff_meta *mdoc)
282 {
283 	struct html		*h;
284 	struct roff_node	*n;
285 	struct tag		*t;
286 
287 	h = (struct html *)arg;
288 	n = mdoc->first->child;
289 
290 	if ((h->oflags & HTML_FRAGMENT) == 0) {
291 		print_gen_decls(h);
292 		print_otag(h, TAG_HTML, "");
293 		if (n != NULL && n->type == ROFFT_COMMENT)
294 			print_gen_comment(h, n);
295 		t = print_otag(h, TAG_HEAD, "");
296 		print_mdoc_head(mdoc, h);
297 		print_tagq(h, t);
298 		print_otag(h, TAG_BODY, "");
299 	}
300 
301 	mdoc_root_pre(mdoc, h);
302 	t = print_otag(h, TAG_DIV, "c", "manual-text");
303 	print_mdoc_nodelist(mdoc, n, h);
304 	print_tagq(h, t);
305 	mdoc_root_post(mdoc, h);
306 	print_tagq(h, NULL);
307 }
308 
309 static void
310 print_mdoc_head(const struct roff_meta *meta, struct html *h)
311 {
312 	char	*cp;
313 
314 	print_gen_head(h);
315 
316 	if (meta->arch != NULL && meta->msec != NULL)
317 		mandoc_asprintf(&cp, "%s(%s) (%s)", meta->title,
318 		    meta->msec, meta->arch);
319 	else if (meta->msec != NULL)
320 		mandoc_asprintf(&cp, "%s(%s)", meta->title, meta->msec);
321 	else if (meta->arch != NULL)
322 		mandoc_asprintf(&cp, "%s (%s)", meta->title, meta->arch);
323 	else
324 		cp = mandoc_strdup(meta->title);
325 
326 	print_otag(h, TAG_TITLE, "");
327 	print_text(h, cp);
328 	free(cp);
329 }
330 
331 static void
332 print_mdoc_nodelist(MDOC_ARGS)
333 {
334 
335 	while (n != NULL) {
336 		print_mdoc_node(meta, n, h);
337 		n = n->next;
338 	}
339 }
340 
341 static void
342 print_mdoc_node(MDOC_ARGS)
343 {
344 	struct tag	*t;
345 	int		 child;
346 
347 	if (n->type == ROFFT_COMMENT || n->flags & NODE_NOPRT)
348 		return;
349 
350 	if (n->flags & NODE_NOFILL) {
351 		html_fillmode(h, ROFF_nf);
352 		if (n->flags & NODE_LINE)
353 			print_endline(h);
354 	} else
355 		html_fillmode(h, ROFF_fi);
356 
357 	child = 1;
358 	n->flags &= ~NODE_ENDED;
359 	switch (n->type) {
360 	case ROFFT_TEXT:
361 		if (n->flags & NODE_LINE) {
362 			switch (*n->string) {
363 			case '\0':
364 				h->col = 1;
365 				print_endline(h);
366 				return;
367 			case ' ':
368 				if ((h->flags & HTML_NONEWLINE) == 0 &&
369 				    (n->flags & NODE_NOFILL) == 0)
370 					print_otag(h, TAG_BR, "");
371 				break;
372 			default:
373 				break;
374 			}
375 		}
376 		t = h->tag;
377 		t->refcnt++;
378 		if (n->flags & NODE_DELIMC)
379 			h->flags |= HTML_NOSPACE;
380 		if (n->flags & NODE_HREF)
381 			print_tagged_text(h, n->string, n);
382 		else
383 			print_text(h, n->string);
384 		if (n->flags & NODE_DELIMO)
385 			h->flags |= HTML_NOSPACE;
386 		break;
387 	case ROFFT_EQN:
388 		t = h->tag;
389 		t->refcnt++;
390 		print_eqn(h, n->eqn);
391 		break;
392 	case ROFFT_TBL:
393 		/*
394 		 * This will take care of initialising all of the table
395 		 * state data for the first table, then tearing it down
396 		 * for the last one.
397 		 */
398 		print_tbl(h, n->span);
399 		return;
400 	default:
401 		/*
402 		 * Close out the current table, if it's open, and unset
403 		 * the "meta" table state.  This will be reopened on the
404 		 * next table element.
405 		 */
406 		if (h->tblt != NULL)
407 			print_tblclose(h);
408 		assert(h->tblt == NULL);
409 		t = h->tag;
410 		t->refcnt++;
411 		if (n->tok < ROFF_MAX) {
412 			roff_html_pre(h, n);
413 			t->refcnt--;
414 			print_stagq(h, t);
415 			return;
416 		}
417 		assert(n->tok >= MDOC_Dd && n->tok < MDOC_MAX);
418 		if (mdoc_html_acts[n->tok - MDOC_Dd].pre != NULL &&
419 		    (n->end == ENDBODY_NOT || n->child != NULL))
420 			child = (*mdoc_html_acts[n->tok - MDOC_Dd].pre)(meta,
421 			    n, h);
422 		break;
423 	}
424 
425 	if (h->flags & HTML_KEEP && n->flags & NODE_LINE) {
426 		h->flags &= ~HTML_KEEP;
427 		h->flags |= HTML_PREKEEP;
428 	}
429 
430 	if (child && n->child != NULL)
431 		print_mdoc_nodelist(meta, n->child, h);
432 
433 	t->refcnt--;
434 	print_stagq(h, t);
435 
436 	switch (n->type) {
437 	case ROFFT_TEXT:
438 	case ROFFT_EQN:
439 		break;
440 	default:
441 		if (mdoc_html_acts[n->tok - MDOC_Dd].post == NULL ||
442 		    n->flags & NODE_ENDED)
443 			break;
444 		(*mdoc_html_acts[n->tok - MDOC_Dd].post)(meta, n, h);
445 		if (n->end != ENDBODY_NOT)
446 			n->body->flags |= NODE_ENDED;
447 		break;
448 	}
449 }
450 
451 static void
452 mdoc_root_post(const struct roff_meta *meta, struct html *h)
453 {
454 	struct tag	*t, *tt;
455 
456 	t = print_otag(h, TAG_TABLE, "c", "foot");
457 	tt = print_otag(h, TAG_TR, "");
458 
459 	print_otag(h, TAG_TD, "c", "foot-date");
460 	print_text(h, meta->date);
461 	print_stagq(h, tt);
462 
463 	print_otag(h, TAG_TD, "c", "foot-os");
464 	print_text(h, meta->os);
465 	print_tagq(h, t);
466 }
467 
468 static int
469 mdoc_root_pre(const struct roff_meta *meta, struct html *h)
470 {
471 	struct tag	*t, *tt;
472 	char		*volume, *title;
473 
474 	if (NULL == meta->arch)
475 		volume = mandoc_strdup(meta->vol);
476 	else
477 		mandoc_asprintf(&volume, "%s (%s)",
478 		    meta->vol, meta->arch);
479 
480 	if (NULL == meta->msec)
481 		title = mandoc_strdup(meta->title);
482 	else
483 		mandoc_asprintf(&title, "%s(%s)",
484 		    meta->title, meta->msec);
485 
486 	t = print_otag(h, TAG_TABLE, "c", "head");
487 	tt = print_otag(h, TAG_TR, "");
488 
489 	print_otag(h, TAG_TD, "c", "head-ltitle");
490 	print_text(h, title);
491 	print_stagq(h, tt);
492 
493 	print_otag(h, TAG_TD, "c", "head-vol");
494 	print_text(h, volume);
495 	print_stagq(h, tt);
496 
497 	print_otag(h, TAG_TD, "c", "head-rtitle");
498 	print_text(h, title);
499 	print_tagq(h, t);
500 
501 	free(title);
502 	free(volume);
503 	return 1;
504 }
505 
506 static int
507 mdoc_code_pre(MDOC_ARGS)
508 {
509 	print_otag_id(h, TAG_CODE, roff_name[n->tok], n);
510 	return 1;
511 }
512 
513 static int
514 mdoc_sh_pre(MDOC_ARGS)
515 {
516 	struct roff_node	*sn, *subn;
517 	struct tag		*t, *tsec, *tsub;
518 	char			*id;
519 	int			 sc;
520 
521 	switch (n->type) {
522 	case ROFFT_BLOCK:
523 		html_close_paragraph(h);
524 		if ((h->oflags & HTML_TOC) == 0 ||
525 		    h->flags & HTML_TOCDONE ||
526 		    n->sec <= SEC_SYNOPSIS) {
527 			print_otag(h, TAG_SECTION, "c", "Sh");
528 			break;
529 		}
530 		h->flags |= HTML_TOCDONE;
531 		sc = 0;
532 		for (sn = n->next; sn != NULL; sn = sn->next)
533 			if (sn->sec == SEC_CUSTOM)
534 				if (++sc == 2)
535 					break;
536 		if (sc < 2)
537 			break;
538 		t = print_otag(h, TAG_H1, "c", "Sh");
539 		print_text(h, "TABLE OF CONTENTS");
540 		print_tagq(h, t);
541 		t = print_otag(h, TAG_UL, "c", "Bl-compact");
542 		for (sn = n; sn != NULL; sn = sn->next) {
543 			tsec = print_otag(h, TAG_LI, "");
544 			id = html_make_id(sn->head, 0);
545 			tsub = print_otag(h, TAG_A, "hR", id);
546 			free(id);
547 			print_mdoc_nodelist(meta, sn->head->child, h);
548 			print_tagq(h, tsub);
549 			tsub = NULL;
550 			for (subn = sn->body->child; subn != NULL;
551 			    subn = subn->next) {
552 				if (subn->tok != MDOC_Ss)
553 					continue;
554 				id = html_make_id(subn->head, 0);
555 				if (id == NULL)
556 					continue;
557 				if (tsub == NULL)
558 					print_otag(h, TAG_UL,
559 					    "c", "Bl-compact");
560 				tsub = print_otag(h, TAG_LI, "");
561 				print_otag(h, TAG_A, "hR", id);
562 				free(id);
563 				print_mdoc_nodelist(meta,
564 				    subn->head->child, h);
565 				print_tagq(h, tsub);
566 			}
567 			print_tagq(h, tsec);
568 		}
569 		print_tagq(h, t);
570 		print_otag(h, TAG_SECTION, "c", "Sh");
571 		break;
572 	case ROFFT_HEAD:
573 		print_otag_id(h, TAG_H1, "Sh", n);
574 		break;
575 	case ROFFT_BODY:
576 		if (n->sec == SEC_AUTHORS)
577 			h->flags &= ~(HTML_SPLIT|HTML_NOSPLIT);
578 		break;
579 	default:
580 		break;
581 	}
582 	return 1;
583 }
584 
585 static int
586 mdoc_ss_pre(MDOC_ARGS)
587 {
588 	switch (n->type) {
589 	case ROFFT_BLOCK:
590 		html_close_paragraph(h);
591 		print_otag(h, TAG_SECTION, "c", "Ss");
592 		break;
593 	case ROFFT_HEAD:
594 		print_otag_id(h, TAG_H2, "Ss", n);
595 		break;
596 	case ROFFT_BODY:
597 		break;
598 	default:
599 		abort();
600 	}
601 	return 1;
602 }
603 
604 static int
605 mdoc_fl_pre(MDOC_ARGS)
606 {
607 	struct roff_node	*nn;
608 
609 	print_otag_id(h, TAG_CODE, "Fl", n);
610 	print_text(h, "\\-");
611 	if (n->child != NULL ||
612 	    ((nn = roff_node_next(n)) != NULL &&
613 	     nn->type != ROFFT_TEXT &&
614 	     (nn->flags & NODE_LINE) == 0))
615 		h->flags |= HTML_NOSPACE;
616 
617 	return 1;
618 }
619 
620 static int
621 mdoc_nd_pre(MDOC_ARGS)
622 {
623 	switch (n->type) {
624 	case ROFFT_BLOCK:
625 		return 1;
626 	case ROFFT_HEAD:
627 		return 0;
628 	case ROFFT_BODY:
629 		break;
630 	default:
631 		abort();
632 	}
633 	print_text(h, "\\(em");
634 	print_otag(h, TAG_SPAN, "c", "Nd");
635 	return 1;
636 }
637 
638 static int
639 mdoc_nm_pre(MDOC_ARGS)
640 {
641 	switch (n->type) {
642 	case ROFFT_BLOCK:
643 		break;
644 	case ROFFT_HEAD:
645 		print_otag(h, TAG_TD, "");
646 		/* FALLTHROUGH */
647 	case ROFFT_ELEM:
648 		print_otag(h, TAG_CODE, "c", "Nm");
649 		return 1;
650 	case ROFFT_BODY:
651 		print_otag(h, TAG_TD, "");
652 		return 1;
653 	default:
654 		abort();
655 	}
656 	html_close_paragraph(h);
657 	synopsis_pre(h, n);
658 	print_otag(h, TAG_TABLE, "c", "Nm");
659 	print_otag(h, TAG_TR, "");
660 	return 1;
661 }
662 
663 static int
664 mdoc_xr_pre(MDOC_ARGS)
665 {
666 	if (NULL == n->child)
667 		return 0;
668 
669 	if (h->base_man1)
670 		print_otag(h, TAG_A, "chM", "Xr",
671 		    n->child->string, n->child->next == NULL ?
672 		    NULL : n->child->next->string);
673 	else
674 		print_otag(h, TAG_A, "c", "Xr");
675 
676 	n = n->child;
677 	print_text(h, n->string);
678 
679 	if (NULL == (n = n->next))
680 		return 0;
681 
682 	h->flags |= HTML_NOSPACE;
683 	print_text(h, "(");
684 	h->flags |= HTML_NOSPACE;
685 	print_text(h, n->string);
686 	h->flags |= HTML_NOSPACE;
687 	print_text(h, ")");
688 	return 0;
689 }
690 
691 static int
692 mdoc_tg_pre(MDOC_ARGS)
693 {
694 	char	*id;
695 
696 	if ((id = html_make_id(n, 1)) != NULL) {
697 		print_tagq(h, print_otag(h, TAG_MARK, "i", id));
698 		free(id);
699 	}
700 	return 0;
701 }
702 
703 static int
704 mdoc_ns_pre(MDOC_ARGS)
705 {
706 
707 	if ( ! (NODE_LINE & n->flags))
708 		h->flags |= HTML_NOSPACE;
709 	return 1;
710 }
711 
712 static int
713 mdoc_ar_pre(MDOC_ARGS)
714 {
715 	print_otag(h, TAG_VAR, "c", "Ar");
716 	return 1;
717 }
718 
719 static int
720 mdoc_xx_pre(MDOC_ARGS)
721 {
722 	print_otag(h, TAG_SPAN, "c", "Ux");
723 	return 1;
724 }
725 
726 static int
727 mdoc_it_pre(MDOC_ARGS)
728 {
729 	const struct roff_node	*bl;
730 	enum mdoc_list		 type;
731 
732 	bl = n->parent;
733 	while (bl->tok != MDOC_Bl)
734 		bl = bl->parent;
735 	type = bl->norm->Bl.type;
736 
737 	switch (type) {
738 	case LIST_bullet:
739 	case LIST_dash:
740 	case LIST_hyphen:
741 	case LIST_item:
742 	case LIST_enum:
743 		switch (n->type) {
744 		case ROFFT_HEAD:
745 			return 0;
746 		case ROFFT_BODY:
747 			print_otag_id(h, TAG_LI, NULL, n);
748 			break;
749 		default:
750 			break;
751 		}
752 		break;
753 	case LIST_diag:
754 	case LIST_hang:
755 	case LIST_inset:
756 	case LIST_ohang:
757 		switch (n->type) {
758 		case ROFFT_HEAD:
759 			print_otag_id(h, TAG_DT, NULL, n);
760 			break;
761 		case ROFFT_BODY:
762 			print_otag(h, TAG_DD, "");
763 			break;
764 		default:
765 			break;
766 		}
767 		break;
768 	case LIST_tag:
769 		switch (n->type) {
770 		case ROFFT_HEAD:
771 			print_otag_id(h, TAG_DT, NULL, n);
772 			break;
773 		case ROFFT_BODY:
774 			if (n->child == NULL) {
775 				print_otag(h, TAG_DD, "s", "width", "auto");
776 				print_text(h, "\\ ");
777 			} else
778 				print_otag(h, TAG_DD, "");
779 			break;
780 		default:
781 			break;
782 		}
783 		break;
784 	case LIST_column:
785 		switch (n->type) {
786 		case ROFFT_HEAD:
787 			break;
788 		case ROFFT_BODY:
789 			print_otag(h, TAG_TD, "");
790 			break;
791 		default:
792 			print_otag_id(h, TAG_TR, NULL, n);
793 		}
794 	default:
795 		break;
796 	}
797 
798 	return 1;
799 }
800 
801 static int
802 mdoc_bl_pre(MDOC_ARGS)
803 {
804 	char		 cattr[32];
805 	struct mdoc_bl	*bl;
806 	enum htmltag	 elemtype;
807 
808 	switch (n->type) {
809 	case ROFFT_BLOCK:
810 		html_close_paragraph(h);
811 		break;
812 	case ROFFT_HEAD:
813 		return 0;
814 	case ROFFT_BODY:
815 		return 1;
816 	default:
817 		abort();
818 	}
819 
820 	bl = &n->norm->Bl;
821 	switch (bl->type) {
822 	case LIST_bullet:
823 		elemtype = TAG_UL;
824 		(void)strlcpy(cattr, "Bl-bullet", sizeof(cattr));
825 		break;
826 	case LIST_dash:
827 	case LIST_hyphen:
828 		elemtype = TAG_UL;
829 		(void)strlcpy(cattr, "Bl-dash", sizeof(cattr));
830 		break;
831 	case LIST_item:
832 		elemtype = TAG_UL;
833 		(void)strlcpy(cattr, "Bl-item", sizeof(cattr));
834 		break;
835 	case LIST_enum:
836 		elemtype = TAG_OL;
837 		(void)strlcpy(cattr, "Bl-enum", sizeof(cattr));
838 		break;
839 	case LIST_diag:
840 		elemtype = TAG_DL;
841 		(void)strlcpy(cattr, "Bl-diag", sizeof(cattr));
842 		break;
843 	case LIST_hang:
844 		elemtype = TAG_DL;
845 		(void)strlcpy(cattr, "Bl-hang", sizeof(cattr));
846 		break;
847 	case LIST_inset:
848 		elemtype = TAG_DL;
849 		(void)strlcpy(cattr, "Bl-inset", sizeof(cattr));
850 		break;
851 	case LIST_ohang:
852 		elemtype = TAG_DL;
853 		(void)strlcpy(cattr, "Bl-ohang", sizeof(cattr));
854 		break;
855 	case LIST_tag:
856 		if (bl->offs)
857 			print_otag(h, TAG_DIV, "c", "Bd-indent");
858 		print_otag_id(h, TAG_DL,
859 		    bl->comp ? "Bl-tag Bl-compact" : "Bl-tag", n->body);
860 		return 1;
861 	case LIST_column:
862 		elemtype = TAG_TABLE;
863 		(void)strlcpy(cattr, "Bl-column", sizeof(cattr));
864 		break;
865 	default:
866 		abort();
867 	}
868 	if (bl->offs != NULL)
869 		(void)strlcat(cattr, " Bd-indent", sizeof(cattr));
870 	if (bl->comp)
871 		(void)strlcat(cattr, " Bl-compact", sizeof(cattr));
872 	print_otag_id(h, elemtype, cattr, n->body);
873 	return 1;
874 }
875 
876 static int
877 mdoc_ex_pre(MDOC_ARGS)
878 {
879 	if (roff_node_prev(n) != NULL)
880 		print_otag(h, TAG_BR, "");
881 	return 1;
882 }
883 
884 static int
885 mdoc_st_pre(MDOC_ARGS)
886 {
887 	print_otag(h, TAG_SPAN, "c", "St");
888 	return 1;
889 }
890 
891 static int
892 mdoc_em_pre(MDOC_ARGS)
893 {
894 	print_otag_id(h, TAG_I, "Em", n);
895 	return 1;
896 }
897 
898 static int
899 mdoc_d1_pre(MDOC_ARGS)
900 {
901 	switch (n->type) {
902 	case ROFFT_BLOCK:
903 		html_close_paragraph(h);
904 		return 1;
905 	case ROFFT_HEAD:
906 		return 0;
907 	case ROFFT_BODY:
908 		break;
909 	default:
910 		abort();
911 	}
912 	print_otag_id(h, TAG_DIV, "Bd Bd-indent", n);
913 	if (n->tok == MDOC_Dl)
914 		print_otag(h, TAG_CODE, "c", "Li");
915 	return 1;
916 }
917 
918 static int
919 mdoc_sx_pre(MDOC_ARGS)
920 {
921 	char	*id;
922 
923 	id = html_make_id(n, 0);
924 	print_otag(h, TAG_A, "chR", "Sx", id);
925 	free(id);
926 	return 1;
927 }
928 
929 static int
930 mdoc_bd_pre(MDOC_ARGS)
931 {
932 	char			 buf[16];
933 	struct roff_node	*nn;
934 	int			 comp;
935 
936 	switch (n->type) {
937 	case ROFFT_BLOCK:
938 		html_close_paragraph(h);
939 		return 1;
940 	case ROFFT_HEAD:
941 		return 0;
942 	case ROFFT_BODY:
943 		break;
944 	default:
945 		abort();
946 	}
947 
948 	/* Handle preceding whitespace. */
949 
950 	comp = n->norm->Bd.comp;
951 	for (nn = n; nn != NULL && comp == 0; nn = nn->parent) {
952 		if (nn->type != ROFFT_BLOCK)
953 			continue;
954 		if (nn->tok == MDOC_Sh || nn->tok == MDOC_Ss)
955 			comp = 1;
956 		if (roff_node_prev(nn) != NULL)
957 			break;
958 	}
959 	(void)strlcpy(buf, "Bd", sizeof(buf));
960 	if (comp == 0)
961 		(void)strlcat(buf, " Pp", sizeof(buf));
962 
963 	/* Handle the -offset argument. */
964 
965 	if (n->norm->Bd.offs != NULL &&
966 	    strcmp(n->norm->Bd.offs, "left") != 0)
967 		(void)strlcat(buf, " Bd-indent", sizeof(buf));
968 
969 	print_otag_id(h, TAG_DIV, buf, n);
970 	return 1;
971 }
972 
973 static int
974 mdoc_pa_pre(MDOC_ARGS)
975 {
976 	print_otag(h, TAG_SPAN, "c", "Pa");
977 	return 1;
978 }
979 
980 static int
981 mdoc_ad_pre(MDOC_ARGS)
982 {
983 	print_otag(h, TAG_SPAN, "c", "Ad");
984 	return 1;
985 }
986 
987 static int
988 mdoc_an_pre(MDOC_ARGS)
989 {
990 	if (n->norm->An.auth == AUTH_split) {
991 		h->flags &= ~HTML_NOSPLIT;
992 		h->flags |= HTML_SPLIT;
993 		return 0;
994 	}
995 	if (n->norm->An.auth == AUTH_nosplit) {
996 		h->flags &= ~HTML_SPLIT;
997 		h->flags |= HTML_NOSPLIT;
998 		return 0;
999 	}
1000 
1001 	if (h->flags & HTML_SPLIT)
1002 		print_otag(h, TAG_BR, "");
1003 
1004 	if (n->sec == SEC_AUTHORS && ! (h->flags & HTML_NOSPLIT))
1005 		h->flags |= HTML_SPLIT;
1006 
1007 	print_otag(h, TAG_SPAN, "c", "An");
1008 	return 1;
1009 }
1010 
1011 static int
1012 mdoc_cd_pre(MDOC_ARGS)
1013 {
1014 	synopsis_pre(h, n);
1015 	print_otag(h, TAG_CODE, "c", "Cd");
1016 	return 1;
1017 }
1018 
1019 static int
1020 mdoc_fa_pre(MDOC_ARGS)
1021 {
1022 	const struct roff_node	*nn;
1023 	struct tag		*t;
1024 
1025 	if (n->parent->tok != MDOC_Fo) {
1026 		print_otag(h, TAG_VAR, "c", "Fa");
1027 		return 1;
1028 	}
1029 	for (nn = n->child; nn != NULL; nn = nn->next) {
1030 		t = print_otag(h, TAG_VAR, "c", "Fa");
1031 		print_text(h, nn->string);
1032 		print_tagq(h, t);
1033 		if (nn->next != NULL) {
1034 			h->flags |= HTML_NOSPACE;
1035 			print_text(h, ",");
1036 		}
1037 	}
1038 	if (n->child != NULL &&
1039 	    (nn = roff_node_next(n)) != NULL &&
1040 	    nn->tok == MDOC_Fa) {
1041 		h->flags |= HTML_NOSPACE;
1042 		print_text(h, ",");
1043 	}
1044 	return 0;
1045 }
1046 
1047 static int
1048 mdoc_fd_pre(MDOC_ARGS)
1049 {
1050 	struct tag	*t;
1051 	char		*buf, *cp;
1052 
1053 	synopsis_pre(h, n);
1054 
1055 	if (NULL == (n = n->child))
1056 		return 0;
1057 
1058 	assert(n->type == ROFFT_TEXT);
1059 
1060 	if (strcmp(n->string, "#include")) {
1061 		print_otag(h, TAG_CODE, "c", "Fd");
1062 		return 1;
1063 	}
1064 
1065 	print_otag(h, TAG_CODE, "c", "In");
1066 	print_text(h, n->string);
1067 
1068 	if (NULL != (n = n->next)) {
1069 		assert(n->type == ROFFT_TEXT);
1070 
1071 		if (h->base_includes) {
1072 			cp = n->string;
1073 			if (*cp == '<' || *cp == '"')
1074 				cp++;
1075 			buf = mandoc_strdup(cp);
1076 			cp = strchr(buf, '\0') - 1;
1077 			if (cp >= buf && (*cp == '>' || *cp == '"'))
1078 				*cp = '\0';
1079 			t = print_otag(h, TAG_A, "chI", "In", buf);
1080 			free(buf);
1081 		} else
1082 			t = print_otag(h, TAG_A, "c", "In");
1083 
1084 		print_text(h, n->string);
1085 		print_tagq(h, t);
1086 
1087 		n = n->next;
1088 	}
1089 
1090 	for ( ; n; n = n->next) {
1091 		assert(n->type == ROFFT_TEXT);
1092 		print_text(h, n->string);
1093 	}
1094 
1095 	return 0;
1096 }
1097 
1098 static int
1099 mdoc_vt_pre(MDOC_ARGS)
1100 {
1101 	if (n->type == ROFFT_BLOCK) {
1102 		synopsis_pre(h, n);
1103 		return 1;
1104 	} else if (n->type == ROFFT_ELEM) {
1105 		synopsis_pre(h, n);
1106 	} else if (n->type == ROFFT_HEAD)
1107 		return 0;
1108 
1109 	print_otag(h, TAG_VAR, "c", "Vt");
1110 	return 1;
1111 }
1112 
1113 static int
1114 mdoc_ft_pre(MDOC_ARGS)
1115 {
1116 	synopsis_pre(h, n);
1117 	print_otag(h, TAG_VAR, "c", "Ft");
1118 	return 1;
1119 }
1120 
1121 static int
1122 mdoc_fn_pre(MDOC_ARGS)
1123 {
1124 	struct tag	*t;
1125 	char		 nbuf[BUFSIZ];
1126 	const char	*sp, *ep;
1127 	int		 sz, pretty;
1128 
1129 	pretty = NODE_SYNPRETTY & n->flags;
1130 	synopsis_pre(h, n);
1131 
1132 	/* Split apart into type and name. */
1133 	assert(n->child->string);
1134 	sp = n->child->string;
1135 
1136 	ep = strchr(sp, ' ');
1137 	if (NULL != ep) {
1138 		t = print_otag(h, TAG_VAR, "c", "Ft");
1139 
1140 		while (ep) {
1141 			sz = MIN((int)(ep - sp), BUFSIZ - 1);
1142 			(void)memcpy(nbuf, sp, (size_t)sz);
1143 			nbuf[sz] = '\0';
1144 			print_text(h, nbuf);
1145 			sp = ++ep;
1146 			ep = strchr(sp, ' ');
1147 		}
1148 		print_tagq(h, t);
1149 	}
1150 
1151 	t = print_otag_id(h, TAG_CODE, "Fn", n);
1152 
1153 	if (sp)
1154 		print_text(h, sp);
1155 
1156 	print_tagq(h, t);
1157 
1158 	h->flags |= HTML_NOSPACE;
1159 	print_text(h, "(");
1160 	h->flags |= HTML_NOSPACE;
1161 
1162 	for (n = n->child->next; n; n = n->next) {
1163 		if (NODE_SYNPRETTY & n->flags)
1164 			t = print_otag(h, TAG_VAR, "cs", "Fa",
1165 			    "white-space", "nowrap");
1166 		else
1167 			t = print_otag(h, TAG_VAR, "c", "Fa");
1168 		print_text(h, n->string);
1169 		print_tagq(h, t);
1170 		if (n->next) {
1171 			h->flags |= HTML_NOSPACE;
1172 			print_text(h, ",");
1173 		}
1174 	}
1175 
1176 	h->flags |= HTML_NOSPACE;
1177 	print_text(h, ")");
1178 
1179 	if (pretty) {
1180 		h->flags |= HTML_NOSPACE;
1181 		print_text(h, ";");
1182 	}
1183 
1184 	return 0;
1185 }
1186 
1187 static int
1188 mdoc_sm_pre(MDOC_ARGS)
1189 {
1190 
1191 	if (NULL == n->child)
1192 		h->flags ^= HTML_NONOSPACE;
1193 	else if (0 == strcmp("on", n->child->string))
1194 		h->flags &= ~HTML_NONOSPACE;
1195 	else
1196 		h->flags |= HTML_NONOSPACE;
1197 
1198 	if ( ! (HTML_NONOSPACE & h->flags))
1199 		h->flags &= ~HTML_NOSPACE;
1200 
1201 	return 0;
1202 }
1203 
1204 static int
1205 mdoc_skip_pre(MDOC_ARGS)
1206 {
1207 
1208 	return 0;
1209 }
1210 
1211 static int
1212 mdoc_pp_pre(MDOC_ARGS)
1213 {
1214 	char	*id;
1215 
1216 	if (n->flags & NODE_NOFILL) {
1217 		print_endline(h);
1218 		if (n->flags & NODE_ID)
1219 			mdoc_tg_pre(meta, n, h);
1220 		else {
1221 			h->col = 1;
1222 			print_endline(h);
1223 		}
1224 	} else {
1225 		html_close_paragraph(h);
1226 		id = n->flags & NODE_ID ? html_make_id(n, 1) : NULL;
1227 		print_otag(h, TAG_P, "ci", "Pp", id);
1228 		free(id);
1229 	}
1230 	return 0;
1231 }
1232 
1233 static int
1234 mdoc_lk_pre(MDOC_ARGS)
1235 {
1236 	const struct roff_node *link, *descr, *punct;
1237 	struct tag	*t;
1238 
1239 	if ((link = n->child) == NULL)
1240 		return 0;
1241 
1242 	/* Find beginning of trailing punctuation. */
1243 	punct = n->last;
1244 	while (punct != link && punct->flags & NODE_DELIMC)
1245 		punct = punct->prev;
1246 	punct = punct->next;
1247 
1248 	/* Link target and link text. */
1249 	descr = link->next;
1250 	if (descr == punct)
1251 		descr = link;  /* no text */
1252 	t = print_otag(h, TAG_A, "ch", "Lk", link->string);
1253 	do {
1254 		if (descr->flags & (NODE_DELIMC | NODE_DELIMO))
1255 			h->flags |= HTML_NOSPACE;
1256 		print_text(h, descr->string);
1257 		descr = descr->next;
1258 	} while (descr != punct);
1259 	print_tagq(h, t);
1260 
1261 	/* Trailing punctuation. */
1262 	while (punct != NULL) {
1263 		h->flags |= HTML_NOSPACE;
1264 		print_text(h, punct->string);
1265 		punct = punct->next;
1266 	}
1267 	return 0;
1268 }
1269 
1270 static int
1271 mdoc_mt_pre(MDOC_ARGS)
1272 {
1273 	struct tag	*t;
1274 	char		*cp;
1275 
1276 	for (n = n->child; n; n = n->next) {
1277 		assert(n->type == ROFFT_TEXT);
1278 		mandoc_asprintf(&cp, "mailto:%s", n->string);
1279 		t = print_otag(h, TAG_A, "ch", "Mt", cp);
1280 		print_text(h, n->string);
1281 		print_tagq(h, t);
1282 		free(cp);
1283 	}
1284 	return 0;
1285 }
1286 
1287 static int
1288 mdoc_fo_pre(MDOC_ARGS)
1289 {
1290 	struct tag	*t;
1291 
1292 	switch (n->type) {
1293 	case ROFFT_BLOCK:
1294 		synopsis_pre(h, n);
1295 		return 1;
1296 	case ROFFT_HEAD:
1297 		if (n->child != NULL) {
1298 			t = print_otag_id(h, TAG_CODE, "Fn", n);
1299 			print_text(h, n->child->string);
1300 			print_tagq(h, t);
1301 		}
1302 		return 0;
1303 	case ROFFT_BODY:
1304 		h->flags |= HTML_NOSPACE;
1305 		print_text(h, "(");
1306 		h->flags |= HTML_NOSPACE;
1307 		return 1;
1308 	default:
1309 		abort();
1310 	}
1311 }
1312 
1313 static void
1314 mdoc_fo_post(MDOC_ARGS)
1315 {
1316 	if (n->type != ROFFT_BODY)
1317 		return;
1318 	h->flags |= HTML_NOSPACE;
1319 	print_text(h, ")");
1320 	h->flags |= HTML_NOSPACE;
1321 	print_text(h, ";");
1322 }
1323 
1324 static int
1325 mdoc_in_pre(MDOC_ARGS)
1326 {
1327 	struct tag	*t;
1328 
1329 	synopsis_pre(h, n);
1330 	print_otag(h, TAG_CODE, "c", "In");
1331 
1332 	/*
1333 	 * The first argument of the `In' gets special treatment as
1334 	 * being a linked value.  Subsequent values are printed
1335 	 * afterward.  groff does similarly.  This also handles the case
1336 	 * of no children.
1337 	 */
1338 
1339 	if (NODE_SYNPRETTY & n->flags && NODE_LINE & n->flags)
1340 		print_text(h, "#include");
1341 
1342 	print_text(h, "<");
1343 	h->flags |= HTML_NOSPACE;
1344 
1345 	if (NULL != (n = n->child)) {
1346 		assert(n->type == ROFFT_TEXT);
1347 
1348 		if (h->base_includes)
1349 			t = print_otag(h, TAG_A, "chI", "In", n->string);
1350 		else
1351 			t = print_otag(h, TAG_A, "c", "In");
1352 		print_text(h, n->string);
1353 		print_tagq(h, t);
1354 
1355 		n = n->next;
1356 	}
1357 
1358 	h->flags |= HTML_NOSPACE;
1359 	print_text(h, ">");
1360 
1361 	for ( ; n; n = n->next) {
1362 		assert(n->type == ROFFT_TEXT);
1363 		print_text(h, n->string);
1364 	}
1365 	return 0;
1366 }
1367 
1368 static int
1369 mdoc_va_pre(MDOC_ARGS)
1370 {
1371 	print_otag(h, TAG_VAR, "c", "Va");
1372 	return 1;
1373 }
1374 
1375 static int
1376 mdoc_ap_pre(MDOC_ARGS)
1377 {
1378 	h->flags |= HTML_NOSPACE;
1379 	print_text(h, "\\(aq");
1380 	h->flags |= HTML_NOSPACE;
1381 	return 1;
1382 }
1383 
1384 static int
1385 mdoc_bf_pre(MDOC_ARGS)
1386 {
1387 	const char	*cattr;
1388 
1389 	switch (n->type) {
1390 	case ROFFT_BLOCK:
1391 		html_close_paragraph(h);
1392 		return 1;
1393 	case ROFFT_HEAD:
1394 		return 0;
1395 	case ROFFT_BODY:
1396 		break;
1397 	default:
1398 		abort();
1399 	}
1400 
1401 	if (FONT_Em == n->norm->Bf.font)
1402 		cattr = "Bf Em";
1403 	else if (FONT_Sy == n->norm->Bf.font)
1404 		cattr = "Bf Sy";
1405 	else if (FONT_Li == n->norm->Bf.font)
1406 		cattr = "Bf Li";
1407 	else
1408 		cattr = "Bf No";
1409 
1410 	/* Cannot use TAG_SPAN because it may contain blocks. */
1411 	print_otag(h, TAG_DIV, "c", cattr);
1412 	return 1;
1413 }
1414 
1415 static int
1416 mdoc_igndelim_pre(MDOC_ARGS)
1417 {
1418 	h->flags |= HTML_IGNDELIM;
1419 	return 1;
1420 }
1421 
1422 static void
1423 mdoc_pf_post(MDOC_ARGS)
1424 {
1425 	if ( ! (n->next == NULL || n->next->flags & NODE_LINE))
1426 		h->flags |= HTML_NOSPACE;
1427 }
1428 
1429 static int
1430 mdoc_rs_pre(MDOC_ARGS)
1431 {
1432 	switch (n->type) {
1433 	case ROFFT_BLOCK:
1434 		if (n->sec == SEC_SEE_ALSO)
1435 			html_close_paragraph(h);
1436 		break;
1437 	case ROFFT_HEAD:
1438 		return 0;
1439 	case ROFFT_BODY:
1440 		if (n->sec == SEC_SEE_ALSO)
1441 			print_otag(h, TAG_P, "c", "Pp");
1442 		print_otag(h, TAG_CITE, "c", "Rs");
1443 		break;
1444 	default:
1445 		abort();
1446 	}
1447 	return 1;
1448 }
1449 
1450 static int
1451 mdoc_no_pre(MDOC_ARGS)
1452 {
1453 	print_otag_id(h, TAG_SPAN, roff_name[n->tok], n);
1454 	return 1;
1455 }
1456 
1457 static int
1458 mdoc_sy_pre(MDOC_ARGS)
1459 {
1460 	print_otag_id(h, TAG_B, "Sy", n);
1461 	return 1;
1462 }
1463 
1464 static int
1465 mdoc_lb_pre(MDOC_ARGS)
1466 {
1467 	if (n->sec == SEC_LIBRARY &&
1468 	    n->flags & NODE_LINE &&
1469 	    roff_node_prev(n) != NULL)
1470 		print_otag(h, TAG_BR, "");
1471 
1472 	print_otag(h, TAG_SPAN, "c", "Lb");
1473 	return 1;
1474 }
1475 
1476 static int
1477 mdoc__x_pre(MDOC_ARGS)
1478 {
1479 	struct roff_node	*nn;
1480 	const char		*cattr;
1481 	enum htmltag		 t;
1482 
1483 	t = TAG_SPAN;
1484 
1485 	switch (n->tok) {
1486 	case MDOC__A:
1487 		cattr = "RsA";
1488 		if ((nn = roff_node_prev(n)) != NULL && nn->tok == MDOC__A &&
1489 		    ((nn = roff_node_next(n)) == NULL || nn->tok != MDOC__A))
1490 			print_text(h, "and");
1491 		break;
1492 	case MDOC__B:
1493 		t = TAG_I;
1494 		cattr = "RsB";
1495 		break;
1496 	case MDOC__C:
1497 		cattr = "RsC";
1498 		break;
1499 	case MDOC__D:
1500 		cattr = "RsD";
1501 		break;
1502 	case MDOC__I:
1503 		t = TAG_I;
1504 		cattr = "RsI";
1505 		break;
1506 	case MDOC__J:
1507 		t = TAG_I;
1508 		cattr = "RsJ";
1509 		break;
1510 	case MDOC__N:
1511 		cattr = "RsN";
1512 		break;
1513 	case MDOC__O:
1514 		cattr = "RsO";
1515 		break;
1516 	case MDOC__P:
1517 		cattr = "RsP";
1518 		break;
1519 	case MDOC__Q:
1520 		cattr = "RsQ";
1521 		break;
1522 	case MDOC__R:
1523 		cattr = "RsR";
1524 		break;
1525 	case MDOC__T:
1526 		cattr = "RsT";
1527 		break;
1528 	case MDOC__U:
1529 		print_otag(h, TAG_A, "ch", "RsU", n->child->string);
1530 		return 1;
1531 	case MDOC__V:
1532 		cattr = "RsV";
1533 		break;
1534 	default:
1535 		abort();
1536 	}
1537 
1538 	print_otag(h, t, "c", cattr);
1539 	return 1;
1540 }
1541 
1542 static void
1543 mdoc__x_post(MDOC_ARGS)
1544 {
1545 	struct roff_node *nn;
1546 
1547 	if (n->tok == MDOC__A &&
1548 	    (nn = roff_node_next(n)) != NULL && nn->tok == MDOC__A &&
1549 	    ((nn = roff_node_next(nn)) == NULL || nn->tok != MDOC__A) &&
1550 	    ((nn = roff_node_prev(n)) == NULL || nn->tok != MDOC__A))
1551 		return;
1552 
1553 	/* TODO: %U */
1554 
1555 	if (n->parent == NULL || n->parent->tok != MDOC_Rs)
1556 		return;
1557 
1558 	h->flags |= HTML_NOSPACE;
1559 	print_text(h, roff_node_next(n) ? "," : ".");
1560 }
1561 
1562 static int
1563 mdoc_bk_pre(MDOC_ARGS)
1564 {
1565 
1566 	switch (n->type) {
1567 	case ROFFT_BLOCK:
1568 		break;
1569 	case ROFFT_HEAD:
1570 		return 0;
1571 	case ROFFT_BODY:
1572 		if (n->parent->args != NULL || n->prev->child == NULL)
1573 			h->flags |= HTML_PREKEEP;
1574 		break;
1575 	default:
1576 		abort();
1577 	}
1578 
1579 	return 1;
1580 }
1581 
1582 static void
1583 mdoc_bk_post(MDOC_ARGS)
1584 {
1585 
1586 	if (n->type == ROFFT_BODY)
1587 		h->flags &= ~(HTML_KEEP | HTML_PREKEEP);
1588 }
1589 
1590 static int
1591 mdoc_quote_pre(MDOC_ARGS)
1592 {
1593 	if (n->type != ROFFT_BODY)
1594 		return 1;
1595 
1596 	switch (n->tok) {
1597 	case MDOC_Ao:
1598 	case MDOC_Aq:
1599 		print_text(h, n->child != NULL && n->child->next == NULL &&
1600 		    n->child->tok == MDOC_Mt ?  "<" : "\\(la");
1601 		break;
1602 	case MDOC_Bro:
1603 	case MDOC_Brq:
1604 		print_text(h, "\\(lC");
1605 		break;
1606 	case MDOC_Bo:
1607 	case MDOC_Bq:
1608 		print_text(h, "\\(lB");
1609 		break;
1610 	case MDOC_Oo:
1611 	case MDOC_Op:
1612 		print_text(h, "\\(lB");
1613 		/*
1614 		 * Give up on semantic markup for now.
1615 		 * We cannot use TAG_SPAN because .Oo may contain blocks.
1616 		 * We cannot use TAG_DIV because we might be in a
1617 		 * phrasing context (like .Dl or .Pp); we cannot
1618 		 * close out a .Pp at this point either because
1619 		 * that would break the line.
1620 		 */
1621 		/* XXX print_otag(h, TAG_???, "c", "Op"); */
1622 		break;
1623 	case MDOC_En:
1624 		if (NULL == n->norm->Es ||
1625 		    NULL == n->norm->Es->child)
1626 			return 1;
1627 		print_text(h, n->norm->Es->child->string);
1628 		break;
1629 	case MDOC_Do:
1630 	case MDOC_Dq:
1631 		print_text(h, "\\(lq");
1632 		break;
1633 	case MDOC_Qo:
1634 	case MDOC_Qq:
1635 		print_text(h, "\"");
1636 		break;
1637 	case MDOC_Po:
1638 	case MDOC_Pq:
1639 		print_text(h, "(");
1640 		break;
1641 	case MDOC_Ql:
1642 		print_text(h, "\\(oq");
1643 		h->flags |= HTML_NOSPACE;
1644 		print_otag(h, TAG_CODE, "c", "Li");
1645 		break;
1646 	case MDOC_So:
1647 	case MDOC_Sq:
1648 		print_text(h, "\\(oq");
1649 		break;
1650 	default:
1651 		abort();
1652 	}
1653 
1654 	h->flags |= HTML_NOSPACE;
1655 	return 1;
1656 }
1657 
1658 static void
1659 mdoc_quote_post(MDOC_ARGS)
1660 {
1661 
1662 	if (n->type != ROFFT_BODY && n->type != ROFFT_ELEM)
1663 		return;
1664 
1665 	h->flags |= HTML_NOSPACE;
1666 
1667 	switch (n->tok) {
1668 	case MDOC_Ao:
1669 	case MDOC_Aq:
1670 		print_text(h, n->child != NULL && n->child->next == NULL &&
1671 		    n->child->tok == MDOC_Mt ?  ">" : "\\(ra");
1672 		break;
1673 	case MDOC_Bro:
1674 	case MDOC_Brq:
1675 		print_text(h, "\\(rC");
1676 		break;
1677 	case MDOC_Oo:
1678 	case MDOC_Op:
1679 	case MDOC_Bo:
1680 	case MDOC_Bq:
1681 		print_text(h, "\\(rB");
1682 		break;
1683 	case MDOC_En:
1684 		if (n->norm->Es == NULL ||
1685 		    n->norm->Es->child == NULL ||
1686 		    n->norm->Es->child->next == NULL)
1687 			h->flags &= ~HTML_NOSPACE;
1688 		else
1689 			print_text(h, n->norm->Es->child->next->string);
1690 		break;
1691 	case MDOC_Do:
1692 	case MDOC_Dq:
1693 		print_text(h, "\\(rq");
1694 		break;
1695 	case MDOC_Qo:
1696 	case MDOC_Qq:
1697 		print_text(h, "\"");
1698 		break;
1699 	case MDOC_Po:
1700 	case MDOC_Pq:
1701 		print_text(h, ")");
1702 		break;
1703 	case MDOC_Ql:
1704 	case MDOC_So:
1705 	case MDOC_Sq:
1706 		print_text(h, "\\(cq");
1707 		break;
1708 	default:
1709 		abort();
1710 	}
1711 }
1712 
1713 static int
1714 mdoc_eo_pre(MDOC_ARGS)
1715 {
1716 
1717 	if (n->type != ROFFT_BODY)
1718 		return 1;
1719 
1720 	if (n->end == ENDBODY_NOT &&
1721 	    n->parent->head->child == NULL &&
1722 	    n->child != NULL &&
1723 	    n->child->end != ENDBODY_NOT)
1724 		print_text(h, "\\&");
1725 	else if (n->end != ENDBODY_NOT ? n->child != NULL :
1726 	    n->parent->head->child != NULL && (n->child != NULL ||
1727 	    (n->parent->tail != NULL && n->parent->tail->child != NULL)))
1728 		h->flags |= HTML_NOSPACE;
1729 	return 1;
1730 }
1731 
1732 static void
1733 mdoc_eo_post(MDOC_ARGS)
1734 {
1735 	int	 body, tail;
1736 
1737 	if (n->type != ROFFT_BODY)
1738 		return;
1739 
1740 	if (n->end != ENDBODY_NOT) {
1741 		h->flags &= ~HTML_NOSPACE;
1742 		return;
1743 	}
1744 
1745 	body = n->child != NULL || n->parent->head->child != NULL;
1746 	tail = n->parent->tail != NULL && n->parent->tail->child != NULL;
1747 
1748 	if (body && tail)
1749 		h->flags |= HTML_NOSPACE;
1750 	else if ( ! tail)
1751 		h->flags &= ~HTML_NOSPACE;
1752 }
1753 
1754 static int
1755 mdoc_abort_pre(MDOC_ARGS)
1756 {
1757 	abort();
1758 }
1759