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