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