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