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