xref: /openbsd-src/usr.bin/mandoc/mdoc_html.c (revision ae3cb403620ab940fbaabb3055fac045a63d56b7)
1 /*	$OpenBSD: mdoc_html.c,v 1.169 2017/07/15 17:57:46 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 			print_otag(h, TAG_LI, "c", cattr);
717 			break;
718 		default:
719 			break;
720 		}
721 		break;
722 	case LIST_diag:
723 	case LIST_hang:
724 	case LIST_inset:
725 	case LIST_ohang:
726 		switch (n->type) {
727 		case ROFFT_HEAD:
728 			print_otag(h, TAG_DT, "c", cattr);
729 			if (type == LIST_diag)
730 				print_otag(h, TAG_B, "c", cattr);
731 			break;
732 		case ROFFT_BODY:
733 			print_otag(h, TAG_DD, "csw*+l", cattr,
734 			    bl->norm->Bl.width);
735 			break;
736 		default:
737 			break;
738 		}
739 		break;
740 	case LIST_tag:
741 		switch (n->type) {
742 		case ROFFT_HEAD:
743 			if (h->style != NULL && !bl->norm->Bl.comp &&
744 			    (n->parent->prev == NULL ||
745 			     n->parent->prev->body == NULL ||
746 			     n->parent->prev->body->child != NULL)) {
747 				t = print_otag(h, TAG_DT, "csw*+-l",
748 				    cattr, bl->norm->Bl.width);
749 				print_text(h, "\\ ");
750 				print_tagq(h, t);
751 				t = print_otag(h, TAG_DD, "c", cattr);
752 				print_text(h, "\\ ");
753 				print_tagq(h, t);
754 			}
755 			print_otag(h, TAG_DT, "csw*+-l", cattr,
756 			    bl->norm->Bl.width);
757 			break;
758 		case ROFFT_BODY:
759 			if (n->child == NULL) {
760 				print_otag(h, TAG_DD, "css?", cattr,
761 				    "width", "auto");
762 				print_text(h, "\\ ");
763 			} else
764 				print_otag(h, TAG_DD, "c", cattr);
765 			break;
766 		default:
767 			break;
768 		}
769 		break;
770 	case LIST_column:
771 		switch (n->type) {
772 		case ROFFT_HEAD:
773 			break;
774 		case ROFFT_BODY:
775 			print_otag(h, TAG_TD, "c", cattr);
776 			break;
777 		default:
778 			print_otag(h, TAG_TR, "c", cattr);
779 		}
780 	default:
781 		break;
782 	}
783 
784 	return 1;
785 }
786 
787 static int
788 mdoc_bl_pre(MDOC_ARGS)
789 {
790 	char		 cattr[21];
791 	struct tag	*t;
792 	struct mdoc_bl	*bl;
793 	size_t		 i;
794 	enum htmltag	 elemtype;
795 
796 	bl = &n->norm->Bl;
797 
798 	switch (n->type) {
799 	case ROFFT_BODY:
800 		return 1;
801 
802 	case ROFFT_HEAD:
803 		if (bl->type != LIST_column || bl->ncols == 0)
804 			return 0;
805 
806 		/*
807 		 * For each column, print out the <COL> tag with our
808 		 * suggested width.  The last column gets min-width, as
809 		 * in terminal mode it auto-sizes to the width of the
810 		 * screen and we want to preserve that behaviour.
811 		 */
812 
813 		t = print_otag(h, TAG_COLGROUP, "");
814 		for (i = 0; i < bl->ncols - 1; i++)
815 			print_otag(h, TAG_COL, "sw+w", bl->cols[i]);
816 		print_otag(h, TAG_COL, "swW", bl->cols[i]);
817 		print_tagq(h, t);
818 		return 0;
819 
820 	default:
821 		break;
822 	}
823 
824 	switch (bl->type) {
825 	case LIST_bullet:
826 		elemtype = TAG_UL;
827 		(void)strlcpy(cattr, "Bl-bullet", sizeof(cattr));
828 		break;
829 	case LIST_dash:
830 	case LIST_hyphen:
831 		elemtype = TAG_UL;
832 		(void)strlcpy(cattr, "Bl-dash", sizeof(cattr));
833 		break;
834 	case LIST_item:
835 		elemtype = TAG_UL;
836 		(void)strlcpy(cattr, "Bl-item", sizeof(cattr));
837 		break;
838 	case LIST_enum:
839 		elemtype = TAG_OL;
840 		(void)strlcpy(cattr, "Bl-enum", sizeof(cattr));
841 		break;
842 	case LIST_diag:
843 		elemtype = TAG_DL;
844 		(void)strlcpy(cattr, "Bl-diag", sizeof(cattr));
845 		break;
846 	case LIST_hang:
847 		elemtype = TAG_DL;
848 		(void)strlcpy(cattr, "Bl-hang", sizeof(cattr));
849 		break;
850 	case LIST_inset:
851 		elemtype = TAG_DL;
852 		(void)strlcpy(cattr, "Bl-inset", sizeof(cattr));
853 		break;
854 	case LIST_ohang:
855 		elemtype = TAG_DL;
856 		(void)strlcpy(cattr, "Bl-ohang", sizeof(cattr));
857 		break;
858 	case LIST_tag:
859 		if (bl->offs)
860 			print_otag(h, TAG_DIV, "cswl", "Bl-tag", bl->offs);
861 		print_otag(h, TAG_DL, "csw*+l", bl->comp ?
862 		    "Bl-tag Bl-compact" : "Bl-tag", bl->width);
863 		return 1;
864 	case LIST_column:
865 		elemtype = TAG_TABLE;
866 		(void)strlcpy(cattr, "Bl-column", sizeof(cattr));
867 		break;
868 	default:
869 		abort();
870 	}
871 	if (bl->comp)
872 		(void)strlcat(cattr, " Bl-compact", sizeof(cattr));
873 	print_otag(h, elemtype, "cswl", cattr, bl->offs);
874 	return 1;
875 }
876 
877 static int
878 mdoc_ex_pre(MDOC_ARGS)
879 {
880 	if (n->prev)
881 		print_otag(h, TAG_BR, "");
882 	return 1;
883 }
884 
885 static int
886 mdoc_st_pre(MDOC_ARGS)
887 {
888 	print_otag(h, TAG_SPAN, "cT", "St");
889 	return 1;
890 }
891 
892 static int
893 mdoc_em_pre(MDOC_ARGS)
894 {
895 	print_otag(h, TAG_I, "cT", "Em");
896 	return 1;
897 }
898 
899 static int
900 mdoc_d1_pre(MDOC_ARGS)
901 {
902 	if (n->type != ROFFT_BLOCK)
903 		return 1;
904 
905 	print_otag(h, TAG_DIV, "c", "D1");
906 
907 	if (n->tok == MDOC_Dl)
908 		print_otag(h, TAG_CODE, "c", "Li");
909 
910 	return 1;
911 }
912 
913 static int
914 mdoc_sx_pre(MDOC_ARGS)
915 {
916 	char	*id;
917 
918 	id = html_make_id(n);
919 	print_otag(h, TAG_A, "cThR", "Sx", id);
920 	free(id);
921 	return 1;
922 }
923 
924 static int
925 mdoc_bd_pre(MDOC_ARGS)
926 {
927 	int			 comp, offs, sv;
928 	struct roff_node	*nn;
929 
930 	if (n->type == ROFFT_HEAD)
931 		return 0;
932 
933 	if (n->type == ROFFT_BLOCK) {
934 		comp = n->norm->Bd.comp;
935 		for (nn = n; nn && ! comp; nn = nn->parent) {
936 			if (nn->type != ROFFT_BLOCK)
937 				continue;
938 			if (MDOC_Ss == nn->tok || MDOC_Sh == nn->tok)
939 				comp = 1;
940 			if (nn->prev)
941 				break;
942 		}
943 		if ( ! comp)
944 			print_paragraph(h);
945 		return 1;
946 	}
947 
948 	/* Handle the -offset argument. */
949 
950 	if (n->norm->Bd.offs == NULL ||
951 	    ! strcmp(n->norm->Bd.offs, "left"))
952 		offs = 0;
953 	else if ( ! strcmp(n->norm->Bd.offs, "indent"))
954 		offs = INDENT;
955 	else if ( ! strcmp(n->norm->Bd.offs, "indent-two"))
956 		offs = INDENT * 2;
957 	else
958 		offs = -1;
959 
960 	if (offs == -1)
961 		print_otag(h, TAG_DIV, "cswl", "Bd", n->norm->Bd.offs);
962 	else
963 		print_otag(h, TAG_DIV, "cshl", "Bd", offs);
964 
965 	if (n->norm->Bd.type != DISP_unfilled &&
966 	    n->norm->Bd.type != DISP_literal)
967 		return 1;
968 
969 	print_otag(h, TAG_PRE, "c", "Li");
970 
971 	/* This can be recursive: save & set our literal state. */
972 
973 	sv = h->flags & HTML_LITERAL;
974 	h->flags |= HTML_LITERAL;
975 
976 	for (nn = n->child; nn; nn = nn->next) {
977 		print_mdoc_node(meta, nn, h);
978 		/*
979 		 * If the printed node flushes its own line, then we
980 		 * needn't do it here as well.  This is hacky, but the
981 		 * notion of selective eoln whitespace is pretty dumb
982 		 * anyway, so don't sweat it.
983 		 */
984 		switch (nn->tok) {
985 		case ROFF_br:
986 		case ROFF_sp:
987 		case MDOC_Sm:
988 		case MDOC_Bl:
989 		case MDOC_D1:
990 		case MDOC_Dl:
991 		case MDOC_Lp:
992 		case MDOC_Pp:
993 			continue;
994 		default:
995 			break;
996 		}
997 		if (h->flags & HTML_NONEWLINE ||
998 		    (nn->next && ! (nn->next->flags & NODE_LINE)))
999 			continue;
1000 		else if (nn->next)
1001 			print_text(h, "\n");
1002 
1003 		h->flags |= HTML_NOSPACE;
1004 	}
1005 
1006 	if (0 == sv)
1007 		h->flags &= ~HTML_LITERAL;
1008 
1009 	return 0;
1010 }
1011 
1012 static int
1013 mdoc_pa_pre(MDOC_ARGS)
1014 {
1015 	print_otag(h, TAG_I, "cT", "Pa");
1016 	return 1;
1017 }
1018 
1019 static int
1020 mdoc_ad_pre(MDOC_ARGS)
1021 {
1022 	print_otag(h, TAG_I, "c", "Ad");
1023 	return 1;
1024 }
1025 
1026 static int
1027 mdoc_an_pre(MDOC_ARGS)
1028 {
1029 	if (n->norm->An.auth == AUTH_split) {
1030 		h->flags &= ~HTML_NOSPLIT;
1031 		h->flags |= HTML_SPLIT;
1032 		return 0;
1033 	}
1034 	if (n->norm->An.auth == AUTH_nosplit) {
1035 		h->flags &= ~HTML_SPLIT;
1036 		h->flags |= HTML_NOSPLIT;
1037 		return 0;
1038 	}
1039 
1040 	if (h->flags & HTML_SPLIT)
1041 		print_otag(h, TAG_BR, "");
1042 
1043 	if (n->sec == SEC_AUTHORS && ! (h->flags & HTML_NOSPLIT))
1044 		h->flags |= HTML_SPLIT;
1045 
1046 	print_otag(h, TAG_SPAN, "cT", "An");
1047 	return 1;
1048 }
1049 
1050 static int
1051 mdoc_cd_pre(MDOC_ARGS)
1052 {
1053 	synopsis_pre(h, n);
1054 	print_otag(h, TAG_B, "cT", "Cd");
1055 	return 1;
1056 }
1057 
1058 static int
1059 mdoc_dv_pre(MDOC_ARGS)
1060 {
1061 	char	*id;
1062 
1063 	if ((id = cond_id(n)) != NULL)
1064 		print_otag(h, TAG_A, "chR", "selflink", id);
1065 	print_otag(h, TAG_CODE, "cTi", "Dv", id);
1066 	free(id);
1067 	return 1;
1068 }
1069 
1070 static int
1071 mdoc_ev_pre(MDOC_ARGS)
1072 {
1073 	char	*id;
1074 
1075 	if ((id = cond_id(n)) != NULL)
1076 		print_otag(h, TAG_A, "chR", "selflink", id);
1077 	print_otag(h, TAG_CODE, "cTi", "Ev", id);
1078 	free(id);
1079 	return 1;
1080 }
1081 
1082 static int
1083 mdoc_er_pre(MDOC_ARGS)
1084 {
1085 	char	*id;
1086 
1087 	id = n->sec == SEC_ERRORS &&
1088 	    (n->parent->tok == MDOC_It ||
1089 	     (n->parent->tok == MDOC_Bq &&
1090 	      n->parent->parent->parent->tok == MDOC_It)) ?
1091 	    html_make_id(n) : NULL;
1092 
1093 	if (id != NULL)
1094 		print_otag(h, TAG_A, "chR", "selflink", id);
1095 	print_otag(h, TAG_CODE, "cTi", "Er", id);
1096 	free(id);
1097 	return 1;
1098 }
1099 
1100 static int
1101 mdoc_fa_pre(MDOC_ARGS)
1102 {
1103 	const struct roff_node	*nn;
1104 	struct tag		*t;
1105 
1106 	if (n->parent->tok != MDOC_Fo) {
1107 		print_otag(h, TAG_VAR, "cT", "Fa");
1108 		return 1;
1109 	}
1110 
1111 	for (nn = n->child; nn; nn = nn->next) {
1112 		t = print_otag(h, TAG_VAR, "cT", "Fa");
1113 		print_text(h, nn->string);
1114 		print_tagq(h, t);
1115 		if (nn->next) {
1116 			h->flags |= HTML_NOSPACE;
1117 			print_text(h, ",");
1118 		}
1119 	}
1120 
1121 	if (n->child && n->next && n->next->tok == MDOC_Fa) {
1122 		h->flags |= HTML_NOSPACE;
1123 		print_text(h, ",");
1124 	}
1125 
1126 	return 0;
1127 }
1128 
1129 static int
1130 mdoc_fd_pre(MDOC_ARGS)
1131 {
1132 	struct tag	*t;
1133 	char		*buf, *cp;
1134 
1135 	synopsis_pre(h, n);
1136 
1137 	if (NULL == (n = n->child))
1138 		return 0;
1139 
1140 	assert(n->type == ROFFT_TEXT);
1141 
1142 	if (strcmp(n->string, "#include")) {
1143 		print_otag(h, TAG_B, "cT", "Fd");
1144 		return 1;
1145 	}
1146 
1147 	print_otag(h, TAG_B, "cT", "In");
1148 	print_text(h, n->string);
1149 
1150 	if (NULL != (n = n->next)) {
1151 		assert(n->type == ROFFT_TEXT);
1152 
1153 		if (h->base_includes) {
1154 			cp = n->string;
1155 			if (*cp == '<' || *cp == '"')
1156 				cp++;
1157 			buf = mandoc_strdup(cp);
1158 			cp = strchr(buf, '\0') - 1;
1159 			if (cp >= buf && (*cp == '>' || *cp == '"'))
1160 				*cp = '\0';
1161 			t = print_otag(h, TAG_A, "cThI", "In", buf);
1162 			free(buf);
1163 		} else
1164 			t = print_otag(h, TAG_A, "cT", "In");
1165 
1166 		print_text(h, n->string);
1167 		print_tagq(h, t);
1168 
1169 		n = n->next;
1170 	}
1171 
1172 	for ( ; n; n = n->next) {
1173 		assert(n->type == ROFFT_TEXT);
1174 		print_text(h, n->string);
1175 	}
1176 
1177 	return 0;
1178 }
1179 
1180 static int
1181 mdoc_vt_pre(MDOC_ARGS)
1182 {
1183 	if (n->type == ROFFT_BLOCK) {
1184 		synopsis_pre(h, n);
1185 		return 1;
1186 	} else if (n->type == ROFFT_ELEM) {
1187 		synopsis_pre(h, n);
1188 	} else if (n->type == ROFFT_HEAD)
1189 		return 0;
1190 
1191 	print_otag(h, TAG_VAR, "cT", "Vt");
1192 	return 1;
1193 }
1194 
1195 static int
1196 mdoc_ft_pre(MDOC_ARGS)
1197 {
1198 	synopsis_pre(h, n);
1199 	print_otag(h, TAG_VAR, "cT", "Ft");
1200 	return 1;
1201 }
1202 
1203 static int
1204 mdoc_fn_pre(MDOC_ARGS)
1205 {
1206 	struct tag	*t;
1207 	char		 nbuf[BUFSIZ];
1208 	const char	*sp, *ep;
1209 	int		 sz, pretty;
1210 
1211 	pretty = NODE_SYNPRETTY & n->flags;
1212 	synopsis_pre(h, n);
1213 
1214 	/* Split apart into type and name. */
1215 	assert(n->child->string);
1216 	sp = n->child->string;
1217 
1218 	ep = strchr(sp, ' ');
1219 	if (NULL != ep) {
1220 		t = print_otag(h, TAG_VAR, "cT", "Ft");
1221 
1222 		while (ep) {
1223 			sz = MIN((int)(ep - sp), BUFSIZ - 1);
1224 			(void)memcpy(nbuf, sp, (size_t)sz);
1225 			nbuf[sz] = '\0';
1226 			print_text(h, nbuf);
1227 			sp = ++ep;
1228 			ep = strchr(sp, ' ');
1229 		}
1230 		print_tagq(h, t);
1231 	}
1232 
1233 	t = print_otag(h, TAG_B, "cT", "Fn");
1234 
1235 	if (sp)
1236 		print_text(h, sp);
1237 
1238 	print_tagq(h, t);
1239 
1240 	h->flags |= HTML_NOSPACE;
1241 	print_text(h, "(");
1242 	h->flags |= HTML_NOSPACE;
1243 
1244 	for (n = n->child->next; n; n = n->next) {
1245 		if (NODE_SYNPRETTY & n->flags)
1246 			t = print_otag(h, TAG_VAR, "cTss?", "Fa",
1247 			    "white-space", "nowrap");
1248 		else
1249 			t = print_otag(h, TAG_VAR, "cT", "Fa");
1250 		print_text(h, n->string);
1251 		print_tagq(h, t);
1252 		if (n->next) {
1253 			h->flags |= HTML_NOSPACE;
1254 			print_text(h, ",");
1255 		}
1256 	}
1257 
1258 	h->flags |= HTML_NOSPACE;
1259 	print_text(h, ")");
1260 
1261 	if (pretty) {
1262 		h->flags |= HTML_NOSPACE;
1263 		print_text(h, ";");
1264 	}
1265 
1266 	return 0;
1267 }
1268 
1269 static int
1270 mdoc_sm_pre(MDOC_ARGS)
1271 {
1272 
1273 	if (NULL == n->child)
1274 		h->flags ^= HTML_NONOSPACE;
1275 	else if (0 == strcmp("on", n->child->string))
1276 		h->flags &= ~HTML_NONOSPACE;
1277 	else
1278 		h->flags |= HTML_NONOSPACE;
1279 
1280 	if ( ! (HTML_NONOSPACE & h->flags))
1281 		h->flags &= ~HTML_NOSPACE;
1282 
1283 	return 0;
1284 }
1285 
1286 static int
1287 mdoc_skip_pre(MDOC_ARGS)
1288 {
1289 
1290 	return 0;
1291 }
1292 
1293 static int
1294 mdoc_pp_pre(MDOC_ARGS)
1295 {
1296 
1297 	print_paragraph(h);
1298 	return 0;
1299 }
1300 
1301 static int
1302 mdoc_lk_pre(MDOC_ARGS)
1303 {
1304 	const struct roff_node *link, *descr, *punct;
1305 	struct tag	*t;
1306 
1307 	if ((link = n->child) == NULL)
1308 		return 0;
1309 
1310 	/* Find beginning of trailing punctuation. */
1311 	punct = n->last;
1312 	while (punct != link && punct->flags & NODE_DELIMC)
1313 		punct = punct->prev;
1314 	punct = punct->next;
1315 
1316 	/* Link target and link text. */
1317 	descr = link->next;
1318 	if (descr == punct)
1319 		descr = link;  /* no text */
1320 	t = print_otag(h, TAG_A, "cTh", "Lk", link->string);
1321 	do {
1322 		if (descr->flags & (NODE_DELIMC | NODE_DELIMO))
1323 			h->flags |= HTML_NOSPACE;
1324 		print_text(h, descr->string);
1325 		descr = descr->next;
1326 	} while (descr != punct);
1327 	print_tagq(h, t);
1328 
1329 	/* Trailing punctuation. */
1330 	while (punct != NULL) {
1331 		h->flags |= HTML_NOSPACE;
1332 		print_text(h, punct->string);
1333 		punct = punct->next;
1334 	}
1335 	return 0;
1336 }
1337 
1338 static int
1339 mdoc_mt_pre(MDOC_ARGS)
1340 {
1341 	struct tag	*t;
1342 	char		*cp;
1343 
1344 	for (n = n->child; n; n = n->next) {
1345 		assert(n->type == ROFFT_TEXT);
1346 
1347 		mandoc_asprintf(&cp, "mailto:%s", n->string);
1348 		t = print_otag(h, TAG_A, "cTh", "Mt", cp);
1349 		print_text(h, n->string);
1350 		print_tagq(h, t);
1351 		free(cp);
1352 	}
1353 
1354 	return 0;
1355 }
1356 
1357 static int
1358 mdoc_fo_pre(MDOC_ARGS)
1359 {
1360 	struct tag	*t;
1361 
1362 	if (n->type == ROFFT_BODY) {
1363 		h->flags |= HTML_NOSPACE;
1364 		print_text(h, "(");
1365 		h->flags |= HTML_NOSPACE;
1366 		return 1;
1367 	} else if (n->type == ROFFT_BLOCK) {
1368 		synopsis_pre(h, n);
1369 		return 1;
1370 	}
1371 
1372 	if (n->child == NULL)
1373 		return 0;
1374 
1375 	assert(n->child->string);
1376 	t = print_otag(h, TAG_B, "cT", "Fn");
1377 	print_text(h, n->child->string);
1378 	print_tagq(h, t);
1379 	return 0;
1380 }
1381 
1382 static void
1383 mdoc_fo_post(MDOC_ARGS)
1384 {
1385 
1386 	if (n->type != ROFFT_BODY)
1387 		return;
1388 	h->flags |= HTML_NOSPACE;
1389 	print_text(h, ")");
1390 	h->flags |= HTML_NOSPACE;
1391 	print_text(h, ";");
1392 }
1393 
1394 static int
1395 mdoc_in_pre(MDOC_ARGS)
1396 {
1397 	struct tag	*t;
1398 
1399 	synopsis_pre(h, n);
1400 	print_otag(h, TAG_B, "cT", "In");
1401 
1402 	/*
1403 	 * The first argument of the `In' gets special treatment as
1404 	 * being a linked value.  Subsequent values are printed
1405 	 * afterward.  groff does similarly.  This also handles the case
1406 	 * of no children.
1407 	 */
1408 
1409 	if (NODE_SYNPRETTY & n->flags && NODE_LINE & n->flags)
1410 		print_text(h, "#include");
1411 
1412 	print_text(h, "<");
1413 	h->flags |= HTML_NOSPACE;
1414 
1415 	if (NULL != (n = n->child)) {
1416 		assert(n->type == ROFFT_TEXT);
1417 
1418 		if (h->base_includes)
1419 			t = print_otag(h, TAG_A, "cThI", "In", n->string);
1420 		else
1421 			t = print_otag(h, TAG_A, "cT", "In");
1422 		print_text(h, n->string);
1423 		print_tagq(h, t);
1424 
1425 		n = n->next;
1426 	}
1427 
1428 	h->flags |= HTML_NOSPACE;
1429 	print_text(h, ">");
1430 
1431 	for ( ; n; n = n->next) {
1432 		assert(n->type == ROFFT_TEXT);
1433 		print_text(h, n->string);
1434 	}
1435 
1436 	return 0;
1437 }
1438 
1439 static int
1440 mdoc_ic_pre(MDOC_ARGS)
1441 {
1442 	char	*id;
1443 
1444 	if ((id = cond_id(n)) != NULL)
1445 		print_otag(h, TAG_A, "chR", "selflink", id);
1446 	print_otag(h, TAG_B, "cTi", "Ic", id);
1447 	free(id);
1448 	return 1;
1449 }
1450 
1451 static int
1452 mdoc_va_pre(MDOC_ARGS)
1453 {
1454 	print_otag(h, TAG_VAR, "cT", "Va");
1455 	return 1;
1456 }
1457 
1458 static int
1459 mdoc_ap_pre(MDOC_ARGS)
1460 {
1461 
1462 	h->flags |= HTML_NOSPACE;
1463 	print_text(h, "\\(aq");
1464 	h->flags |= HTML_NOSPACE;
1465 	return 1;
1466 }
1467 
1468 static int
1469 mdoc_bf_pre(MDOC_ARGS)
1470 {
1471 	const char	*cattr;
1472 
1473 	if (n->type == ROFFT_HEAD)
1474 		return 0;
1475 	else if (n->type != ROFFT_BODY)
1476 		return 1;
1477 
1478 	if (FONT_Em == n->norm->Bf.font)
1479 		cattr = "Em";
1480 	else if (FONT_Sy == n->norm->Bf.font)
1481 		cattr = "Sy";
1482 	else if (FONT_Li == n->norm->Bf.font)
1483 		cattr = "Li";
1484 	else
1485 		cattr = "No";
1486 
1487 	/*
1488 	 * We want this to be inline-formatted, but needs to be div to
1489 	 * accept block children.
1490 	 */
1491 
1492 	print_otag(h, TAG_DIV, "css?hl", cattr, "display", "inline", 1);
1493 	return 1;
1494 }
1495 
1496 static int
1497 mdoc_ms_pre(MDOC_ARGS)
1498 {
1499 	char *id;
1500 
1501 	if ((id = cond_id(n)) != NULL)
1502 		print_otag(h, TAG_A, "chR", "selflink", id);
1503 	print_otag(h, TAG_B, "cTi", "Ms", id);
1504 	free(id);
1505 	return 1;
1506 }
1507 
1508 static int
1509 mdoc_igndelim_pre(MDOC_ARGS)
1510 {
1511 
1512 	h->flags |= HTML_IGNDELIM;
1513 	return 1;
1514 }
1515 
1516 static void
1517 mdoc_pf_post(MDOC_ARGS)
1518 {
1519 
1520 	if ( ! (n->next == NULL || n->next->flags & NODE_LINE))
1521 		h->flags |= HTML_NOSPACE;
1522 }
1523 
1524 static int
1525 mdoc_rs_pre(MDOC_ARGS)
1526 {
1527 	if (n->type != ROFFT_BLOCK)
1528 		return 1;
1529 
1530 	if (n->prev && SEC_SEE_ALSO == n->sec)
1531 		print_paragraph(h);
1532 
1533 	print_otag(h, TAG_CITE, "cT", "Rs");
1534 	return 1;
1535 }
1536 
1537 static int
1538 mdoc_no_pre(MDOC_ARGS)
1539 {
1540 	char *id;
1541 
1542 	if ((id = cond_id(n)) != NULL)
1543 		print_otag(h, TAG_A, "chR", "selflink", id);
1544 	print_otag(h, TAG_SPAN, "ci", "No", id);
1545 	free(id);
1546 	return 1;
1547 }
1548 
1549 static int
1550 mdoc_li_pre(MDOC_ARGS)
1551 {
1552 	char	*id;
1553 
1554 	if ((id = cond_id(n)) != NULL)
1555 		print_otag(h, TAG_A, "chR", "selflink", id);
1556 	print_otag(h, TAG_CODE, "ci", "Li", id);
1557 	free(id);
1558 	return 1;
1559 }
1560 
1561 static int
1562 mdoc_sy_pre(MDOC_ARGS)
1563 {
1564 	print_otag(h, TAG_B, "cT", "Sy");
1565 	return 1;
1566 }
1567 
1568 static int
1569 mdoc_lb_pre(MDOC_ARGS)
1570 {
1571 	if (SEC_LIBRARY == n->sec && NODE_LINE & n->flags && n->prev)
1572 		print_otag(h, TAG_BR, "");
1573 
1574 	print_otag(h, TAG_SPAN, "cT", "Lb");
1575 	return 1;
1576 }
1577 
1578 static int
1579 mdoc__x_pre(MDOC_ARGS)
1580 {
1581 	const char	*cattr;
1582 	enum htmltag	 t;
1583 
1584 	t = TAG_SPAN;
1585 
1586 	switch (n->tok) {
1587 	case MDOC__A:
1588 		cattr = "RsA";
1589 		if (n->prev && MDOC__A == n->prev->tok)
1590 			if (NULL == n->next || MDOC__A != n->next->tok)
1591 				print_text(h, "and");
1592 		break;
1593 	case MDOC__B:
1594 		t = TAG_I;
1595 		cattr = "RsB";
1596 		break;
1597 	case MDOC__C:
1598 		cattr = "RsC";
1599 		break;
1600 	case MDOC__D:
1601 		cattr = "RsD";
1602 		break;
1603 	case MDOC__I:
1604 		t = TAG_I;
1605 		cattr = "RsI";
1606 		break;
1607 	case MDOC__J:
1608 		t = TAG_I;
1609 		cattr = "RsJ";
1610 		break;
1611 	case MDOC__N:
1612 		cattr = "RsN";
1613 		break;
1614 	case MDOC__O:
1615 		cattr = "RsO";
1616 		break;
1617 	case MDOC__P:
1618 		cattr = "RsP";
1619 		break;
1620 	case MDOC__Q:
1621 		cattr = "RsQ";
1622 		break;
1623 	case MDOC__R:
1624 		cattr = "RsR";
1625 		break;
1626 	case MDOC__T:
1627 		cattr = "RsT";
1628 		break;
1629 	case MDOC__U:
1630 		print_otag(h, TAG_A, "ch", "RsU", n->child->string);
1631 		return 1;
1632 	case MDOC__V:
1633 		cattr = "RsV";
1634 		break;
1635 	default:
1636 		abort();
1637 	}
1638 
1639 	print_otag(h, t, "c", cattr);
1640 	return 1;
1641 }
1642 
1643 static void
1644 mdoc__x_post(MDOC_ARGS)
1645 {
1646 
1647 	if (MDOC__A == n->tok && n->next && MDOC__A == n->next->tok)
1648 		if (NULL == n->next->next || MDOC__A != n->next->next->tok)
1649 			if (NULL == n->prev || MDOC__A != n->prev->tok)
1650 				return;
1651 
1652 	/* TODO: %U */
1653 
1654 	if (NULL == n->parent || MDOC_Rs != n->parent->tok)
1655 		return;
1656 
1657 	h->flags |= HTML_NOSPACE;
1658 	print_text(h, n->next ? "," : ".");
1659 }
1660 
1661 static int
1662 mdoc_bk_pre(MDOC_ARGS)
1663 {
1664 
1665 	switch (n->type) {
1666 	case ROFFT_BLOCK:
1667 		break;
1668 	case ROFFT_HEAD:
1669 		return 0;
1670 	case ROFFT_BODY:
1671 		if (n->parent->args != NULL || n->prev->child == NULL)
1672 			h->flags |= HTML_PREKEEP;
1673 		break;
1674 	default:
1675 		abort();
1676 	}
1677 
1678 	return 1;
1679 }
1680 
1681 static void
1682 mdoc_bk_post(MDOC_ARGS)
1683 {
1684 
1685 	if (n->type == ROFFT_BODY)
1686 		h->flags &= ~(HTML_KEEP | HTML_PREKEEP);
1687 }
1688 
1689 static int
1690 mdoc_quote_pre(MDOC_ARGS)
1691 {
1692 	if (n->type != ROFFT_BODY)
1693 		return 1;
1694 
1695 	switch (n->tok) {
1696 	case MDOC_Ao:
1697 	case MDOC_Aq:
1698 		print_text(h, n->child != NULL && n->child->next == NULL &&
1699 		    n->child->tok == MDOC_Mt ?  "<" : "\\(la");
1700 		break;
1701 	case MDOC_Bro:
1702 	case MDOC_Brq:
1703 		print_text(h, "\\(lC");
1704 		break;
1705 	case MDOC_Bo:
1706 	case MDOC_Bq:
1707 		print_text(h, "\\(lB");
1708 		break;
1709 	case MDOC_Oo:
1710 	case MDOC_Op:
1711 		print_text(h, "\\(lB");
1712 		h->flags |= HTML_NOSPACE;
1713 		print_otag(h, TAG_SPAN, "c", "Op");
1714 		break;
1715 	case MDOC_En:
1716 		if (NULL == n->norm->Es ||
1717 		    NULL == n->norm->Es->child)
1718 			return 1;
1719 		print_text(h, n->norm->Es->child->string);
1720 		break;
1721 	case MDOC_Do:
1722 	case MDOC_Dq:
1723 	case MDOC_Qo:
1724 	case MDOC_Qq:
1725 		print_text(h, "\\(lq");
1726 		break;
1727 	case MDOC_Po:
1728 	case MDOC_Pq:
1729 		print_text(h, "(");
1730 		break;
1731 	case MDOC_Ql:
1732 		print_text(h, "\\(oq");
1733 		h->flags |= HTML_NOSPACE;
1734 		print_otag(h, TAG_CODE, "c", "Li");
1735 		break;
1736 	case MDOC_So:
1737 	case MDOC_Sq:
1738 		print_text(h, "\\(oq");
1739 		break;
1740 	default:
1741 		abort();
1742 	}
1743 
1744 	h->flags |= HTML_NOSPACE;
1745 	return 1;
1746 }
1747 
1748 static void
1749 mdoc_quote_post(MDOC_ARGS)
1750 {
1751 
1752 	if (n->type != ROFFT_BODY && n->type != ROFFT_ELEM)
1753 		return;
1754 
1755 	h->flags |= HTML_NOSPACE;
1756 
1757 	switch (n->tok) {
1758 	case MDOC_Ao:
1759 	case MDOC_Aq:
1760 		print_text(h, n->child != NULL && n->child->next == NULL &&
1761 		    n->child->tok == MDOC_Mt ?  ">" : "\\(ra");
1762 		break;
1763 	case MDOC_Bro:
1764 	case MDOC_Brq:
1765 		print_text(h, "\\(rC");
1766 		break;
1767 	case MDOC_Oo:
1768 	case MDOC_Op:
1769 	case MDOC_Bo:
1770 	case MDOC_Bq:
1771 		print_text(h, "\\(rB");
1772 		break;
1773 	case MDOC_En:
1774 		if (n->norm->Es == NULL ||
1775 		    n->norm->Es->child == NULL ||
1776 		    n->norm->Es->child->next == NULL)
1777 			h->flags &= ~HTML_NOSPACE;
1778 		else
1779 			print_text(h, n->norm->Es->child->next->string);
1780 		break;
1781 	case MDOC_Qo:
1782 	case MDOC_Qq:
1783 	case MDOC_Do:
1784 	case MDOC_Dq:
1785 		print_text(h, "\\(rq");
1786 		break;
1787 	case MDOC_Po:
1788 	case MDOC_Pq:
1789 		print_text(h, ")");
1790 		break;
1791 	case MDOC_Ql:
1792 	case MDOC_So:
1793 	case MDOC_Sq:
1794 		print_text(h, "\\(cq");
1795 		break;
1796 	default:
1797 		abort();
1798 	}
1799 }
1800 
1801 static int
1802 mdoc_eo_pre(MDOC_ARGS)
1803 {
1804 
1805 	if (n->type != ROFFT_BODY)
1806 		return 1;
1807 
1808 	if (n->end == ENDBODY_NOT &&
1809 	    n->parent->head->child == NULL &&
1810 	    n->child != NULL &&
1811 	    n->child->end != ENDBODY_NOT)
1812 		print_text(h, "\\&");
1813 	else if (n->end != ENDBODY_NOT ? n->child != NULL :
1814 	    n->parent->head->child != NULL && (n->child != NULL ||
1815 	    (n->parent->tail != NULL && n->parent->tail->child != NULL)))
1816 		h->flags |= HTML_NOSPACE;
1817 	return 1;
1818 }
1819 
1820 static void
1821 mdoc_eo_post(MDOC_ARGS)
1822 {
1823 	int	 body, tail;
1824 
1825 	if (n->type != ROFFT_BODY)
1826 		return;
1827 
1828 	if (n->end != ENDBODY_NOT) {
1829 		h->flags &= ~HTML_NOSPACE;
1830 		return;
1831 	}
1832 
1833 	body = n->child != NULL || n->parent->head->child != NULL;
1834 	tail = n->parent->tail != NULL && n->parent->tail->child != NULL;
1835 
1836 	if (body && tail)
1837 		h->flags |= HTML_NOSPACE;
1838 	else if ( ! tail)
1839 		h->flags &= ~HTML_NOSPACE;
1840 }
1841