xref: /openbsd-src/usr.bin/mandoc/mdoc_html.c (revision 0b7734b3d77bb9b21afec6f4621cae6c805dbd45)
1 /*	$OpenBSD: mdoc_html.c,v 1.115 2016/01/08 17:48:04 schwarze Exp $ */
2 /*
3  * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
4  * Copyright (c) 2014, 2015, 2016 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	void		  print_mdoc_head(MDOC_ARGS);
50 static	void		  print_mdoc_node(MDOC_ARGS);
51 static	void		  print_mdoc_nodelist(MDOC_ARGS);
52 static	void		  synopsis_pre(struct html *,
53 				const struct roff_node *);
54 
55 static	void		  a2width(const char *, struct roffsu *);
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_bt_pre(MDOC_ARGS);
72 static	int		  mdoc_bx_pre(MDOC_ARGS);
73 static	int		  mdoc_cd_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_rv_pre(MDOC_ARGS);
109 static	int		  mdoc_sh_pre(MDOC_ARGS);
110 static	int		  mdoc_skip_pre(MDOC_ARGS);
111 static	int		  mdoc_sm_pre(MDOC_ARGS);
112 static	int		  mdoc_sp_pre(MDOC_ARGS);
113 static	int		  mdoc_ss_pre(MDOC_ARGS);
114 static	int		  mdoc_sx_pre(MDOC_ARGS);
115 static	int		  mdoc_sy_pre(MDOC_ARGS);
116 static	int		  mdoc_ud_pre(MDOC_ARGS);
117 static	int		  mdoc_va_pre(MDOC_ARGS);
118 static	int		  mdoc_vt_pre(MDOC_ARGS);
119 static	int		  mdoc_xr_pre(MDOC_ARGS);
120 static	int		  mdoc_xx_pre(MDOC_ARGS);
121 
122 static	const struct htmlmdoc mdocs[MDOC_MAX] = {
123 	{mdoc_ap_pre, NULL}, /* Ap */
124 	{NULL, NULL}, /* Dd */
125 	{NULL, NULL}, /* Dt */
126 	{NULL, NULL}, /* Os */
127 	{mdoc_sh_pre, NULL }, /* Sh */
128 	{mdoc_ss_pre, NULL }, /* Ss */
129 	{mdoc_pp_pre, NULL}, /* Pp */
130 	{mdoc_d1_pre, NULL}, /* D1 */
131 	{mdoc_d1_pre, NULL}, /* Dl */
132 	{mdoc_bd_pre, NULL}, /* Bd */
133 	{NULL, NULL}, /* Ed */
134 	{mdoc_bl_pre, NULL}, /* Bl */
135 	{NULL, NULL}, /* El */
136 	{mdoc_it_pre, NULL}, /* It */
137 	{mdoc_ad_pre, NULL}, /* Ad */
138 	{mdoc_an_pre, NULL}, /* An */
139 	{mdoc_ar_pre, NULL}, /* Ar */
140 	{mdoc_cd_pre, NULL}, /* Cd */
141 	{mdoc_fl_pre, NULL}, /* Cm */
142 	{mdoc_dv_pre, NULL}, /* Dv */
143 	{mdoc_er_pre, NULL}, /* Er */
144 	{mdoc_ev_pre, NULL}, /* Ev */
145 	{mdoc_ex_pre, NULL}, /* Ex */
146 	{mdoc_fa_pre, NULL}, /* Fa */
147 	{mdoc_fd_pre, NULL}, /* Fd */
148 	{mdoc_fl_pre, NULL}, /* Fl */
149 	{mdoc_fn_pre, NULL}, /* Fn */
150 	{mdoc_ft_pre, NULL}, /* Ft */
151 	{mdoc_ic_pre, NULL}, /* Ic */
152 	{mdoc_in_pre, NULL}, /* In */
153 	{mdoc_li_pre, NULL}, /* Li */
154 	{mdoc_nd_pre, NULL}, /* Nd */
155 	{mdoc_nm_pre, NULL}, /* Nm */
156 	{mdoc_quote_pre, mdoc_quote_post}, /* Op */
157 	{mdoc_ft_pre, NULL}, /* Ot */
158 	{mdoc_pa_pre, NULL}, /* Pa */
159 	{mdoc_rv_pre, NULL}, /* Rv */
160 	{NULL, NULL}, /* St */
161 	{mdoc_va_pre, NULL}, /* Va */
162 	{mdoc_vt_pre, NULL}, /* Vt */
163 	{mdoc_xr_pre, NULL}, /* Xr */
164 	{mdoc__x_pre, mdoc__x_post}, /* %A */
165 	{mdoc__x_pre, mdoc__x_post}, /* %B */
166 	{mdoc__x_pre, mdoc__x_post}, /* %D */
167 	{mdoc__x_pre, mdoc__x_post}, /* %I */
168 	{mdoc__x_pre, mdoc__x_post}, /* %J */
169 	{mdoc__x_pre, mdoc__x_post}, /* %N */
170 	{mdoc__x_pre, mdoc__x_post}, /* %O */
171 	{mdoc__x_pre, mdoc__x_post}, /* %P */
172 	{mdoc__x_pre, mdoc__x_post}, /* %R */
173 	{mdoc__x_pre, mdoc__x_post}, /* %T */
174 	{mdoc__x_pre, mdoc__x_post}, /* %V */
175 	{NULL, NULL}, /* Ac */
176 	{mdoc_quote_pre, mdoc_quote_post}, /* Ao */
177 	{mdoc_quote_pre, mdoc_quote_post}, /* Aq */
178 	{NULL, NULL}, /* At */
179 	{NULL, NULL}, /* Bc */
180 	{mdoc_bf_pre, NULL}, /* Bf */
181 	{mdoc_quote_pre, mdoc_quote_post}, /* Bo */
182 	{mdoc_quote_pre, mdoc_quote_post}, /* Bq */
183 	{mdoc_xx_pre, NULL}, /* Bsx */
184 	{mdoc_bx_pre, NULL}, /* Bx */
185 	{mdoc_skip_pre, NULL}, /* Db */
186 	{NULL, NULL}, /* Dc */
187 	{mdoc_quote_pre, mdoc_quote_post}, /* Do */
188 	{mdoc_quote_pre, mdoc_quote_post}, /* Dq */
189 	{NULL, NULL}, /* Ec */ /* FIXME: no space */
190 	{NULL, NULL}, /* Ef */
191 	{mdoc_em_pre, NULL}, /* Em */
192 	{mdoc_eo_pre, mdoc_eo_post}, /* Eo */
193 	{mdoc_xx_pre, NULL}, /* Fx */
194 	{mdoc_ms_pre, NULL}, /* Ms */
195 	{mdoc_no_pre, NULL}, /* No */
196 	{mdoc_ns_pre, NULL}, /* Ns */
197 	{mdoc_xx_pre, NULL}, /* Nx */
198 	{mdoc_xx_pre, NULL}, /* Ox */
199 	{NULL, NULL}, /* Pc */
200 	{mdoc_igndelim_pre, mdoc_pf_post}, /* Pf */
201 	{mdoc_quote_pre, mdoc_quote_post}, /* Po */
202 	{mdoc_quote_pre, mdoc_quote_post}, /* Pq */
203 	{NULL, NULL}, /* Qc */
204 	{mdoc_quote_pre, mdoc_quote_post}, /* Ql */
205 	{mdoc_quote_pre, mdoc_quote_post}, /* Qo */
206 	{mdoc_quote_pre, mdoc_quote_post}, /* Qq */
207 	{NULL, NULL}, /* Re */
208 	{mdoc_rs_pre, NULL}, /* Rs */
209 	{NULL, NULL}, /* Sc */
210 	{mdoc_quote_pre, mdoc_quote_post}, /* So */
211 	{mdoc_quote_pre, mdoc_quote_post}, /* Sq */
212 	{mdoc_sm_pre, NULL}, /* Sm */
213 	{mdoc_sx_pre, NULL}, /* Sx */
214 	{mdoc_sy_pre, NULL}, /* Sy */
215 	{NULL, NULL}, /* Tn */
216 	{mdoc_xx_pre, NULL}, /* Ux */
217 	{NULL, NULL}, /* Xc */
218 	{NULL, NULL}, /* Xo */
219 	{mdoc_fo_pre, mdoc_fo_post}, /* Fo */
220 	{NULL, NULL}, /* Fc */
221 	{mdoc_quote_pre, mdoc_quote_post}, /* Oo */
222 	{NULL, NULL}, /* Oc */
223 	{mdoc_bk_pre, mdoc_bk_post}, /* Bk */
224 	{NULL, NULL}, /* Ek */
225 	{mdoc_bt_pre, NULL}, /* Bt */
226 	{NULL, NULL}, /* Hf */
227 	{mdoc_em_pre, NULL}, /* Fr */
228 	{mdoc_ud_pre, NULL}, /* Ud */
229 	{mdoc_lb_pre, NULL}, /* Lb */
230 	{mdoc_pp_pre, NULL}, /* Lp */
231 	{mdoc_lk_pre, NULL}, /* Lk */
232 	{mdoc_mt_pre, NULL}, /* Mt */
233 	{mdoc_quote_pre, mdoc_quote_post}, /* Brq */
234 	{mdoc_quote_pre, mdoc_quote_post}, /* Bro */
235 	{NULL, NULL}, /* Brc */
236 	{mdoc__x_pre, mdoc__x_post}, /* %C */
237 	{mdoc_skip_pre, NULL}, /* Es */
238 	{mdoc_quote_pre, mdoc_quote_post}, /* En */
239 	{mdoc_xx_pre, NULL}, /* Dx */
240 	{mdoc__x_pre, mdoc__x_post}, /* %Q */
241 	{mdoc_sp_pre, NULL}, /* br */
242 	{mdoc_sp_pre, NULL}, /* sp */
243 	{mdoc__x_pre, mdoc__x_post}, /* %U */
244 	{NULL, NULL}, /* Ta */
245 	{mdoc_skip_pre, NULL}, /* ll */
246 };
247 
248 static	const char * const lists[LIST_MAX] = {
249 	NULL,
250 	"list-bul",
251 	"list-col",
252 	"list-dash",
253 	"list-diag",
254 	"list-enum",
255 	"list-hang",
256 	"list-hyph",
257 	"list-inset",
258 	"list-item",
259 	"list-ohang",
260 	"list-tag"
261 };
262 
263 
264 /*
265  * Calculate the scaling unit passed in a `-width' argument.  This uses
266  * either a native scaling unit (e.g., 1i, 2m) or the string length of
267  * the value.
268  */
269 static void
270 a2width(const char *p, struct roffsu *su)
271 {
272 
273 	if (a2roffsu(p, su, SCALE_MAX) < 2) {
274 		su->unit = SCALE_EN;
275 		su->scale = html_strlen(p);
276 	} else if (su->scale < 0.0)
277 		su->scale = 0.0;
278 }
279 
280 /*
281  * See the same function in mdoc_term.c for documentation.
282  */
283 static void
284 synopsis_pre(struct html *h, const struct roff_node *n)
285 {
286 
287 	if (NULL == n->prev || ! (MDOC_SYNPRETTY & n->flags))
288 		return;
289 
290 	if (n->prev->tok == n->tok &&
291 	    MDOC_Fo != n->tok &&
292 	    MDOC_Ft != n->tok &&
293 	    MDOC_Fn != n->tok) {
294 		print_otag(h, TAG_BR, 0, NULL);
295 		return;
296 	}
297 
298 	switch (n->prev->tok) {
299 	case MDOC_Fd:
300 	case MDOC_Fn:
301 	case MDOC_Fo:
302 	case MDOC_In:
303 	case MDOC_Vt:
304 		print_paragraph(h);
305 		break;
306 	case MDOC_Ft:
307 		if (MDOC_Fn != n->tok && MDOC_Fo != n->tok) {
308 			print_paragraph(h);
309 			break;
310 		}
311 		/* FALLTHROUGH */
312 	default:
313 		print_otag(h, TAG_BR, 0, NULL);
314 		break;
315 	}
316 }
317 
318 void
319 html_mdoc(void *arg, const struct roff_man *mdoc)
320 {
321 	struct htmlpair	 tag;
322 	struct html	*h;
323 	struct tag	*t, *tt;
324 
325 	PAIR_CLASS_INIT(&tag, "mandoc");
326 	h = (struct html *)arg;
327 
328 	if ( ! (HTML_FRAGMENT & h->oflags)) {
329 		print_gen_decls(h);
330 		t = print_otag(h, TAG_HTML, 0, NULL);
331 		tt = print_otag(h, TAG_HEAD, 0, NULL);
332 		print_mdoc_head(&mdoc->meta, mdoc->first->child, h);
333 		print_tagq(h, tt);
334 		print_otag(h, TAG_BODY, 0, NULL);
335 		print_otag(h, TAG_DIV, 1, &tag);
336 	} else
337 		t = print_otag(h, TAG_DIV, 1, &tag);
338 
339 	mdoc_root_pre(&mdoc->meta, mdoc->first->child, h);
340 	print_mdoc_nodelist(&mdoc->meta, mdoc->first->child, h);
341 	mdoc_root_post(&mdoc->meta, mdoc->first->child, h);
342 	print_tagq(h, t);
343 	putchar('\n');
344 }
345 
346 static void
347 print_mdoc_head(MDOC_ARGS)
348 {
349 
350 	print_gen_head(h);
351 	bufinit(h);
352 	bufcat(h, meta->title);
353 	if (meta->msec)
354 		bufcat_fmt(h, "(%s)", meta->msec);
355 	if (meta->arch)
356 		bufcat_fmt(h, " (%s)", meta->arch);
357 
358 	print_otag(h, TAG_TITLE, 0, NULL);
359 	print_text(h, h->buf);
360 }
361 
362 static void
363 print_mdoc_nodelist(MDOC_ARGS)
364 {
365 
366 	while (n != NULL) {
367 		print_mdoc_node(meta, n, h);
368 		n = n->next;
369 	}
370 }
371 
372 static void
373 print_mdoc_node(MDOC_ARGS)
374 {
375 	int		 child;
376 	struct tag	*t;
377 
378 	child = 1;
379 	t = h->tags.head;
380 	n->flags &= ~MDOC_ENDED;
381 
382 	switch (n->type) {
383 	case ROFFT_TEXT:
384 		/* No tables in this mode... */
385 		assert(NULL == h->tblt);
386 
387 		/*
388 		 * Make sure that if we're in a literal mode already
389 		 * (i.e., within a <PRE>) don't print the newline.
390 		 */
391 		if (' ' == *n->string && MDOC_LINE & n->flags)
392 			if ( ! (HTML_LITERAL & h->flags))
393 				print_otag(h, TAG_BR, 0, NULL);
394 		if (MDOC_DELIMC & n->flags)
395 			h->flags |= HTML_NOSPACE;
396 		print_text(h, n->string);
397 		if (MDOC_DELIMO & n->flags)
398 			h->flags |= HTML_NOSPACE;
399 		return;
400 	case ROFFT_EQN:
401 		if (n->flags & MDOC_LINE)
402 			putchar('\n');
403 		print_eqn(h, n->eqn);
404 		break;
405 	case ROFFT_TBL:
406 		/*
407 		 * This will take care of initialising all of the table
408 		 * state data for the first table, then tearing it down
409 		 * for the last one.
410 		 */
411 		print_tbl(h, n->span);
412 		return;
413 	default:
414 		/*
415 		 * Close out the current table, if it's open, and unset
416 		 * the "meta" table state.  This will be reopened on the
417 		 * next table element.
418 		 */
419 		if (h->tblt != NULL) {
420 			print_tblclose(h);
421 			t = h->tags.head;
422 		}
423 		assert(h->tblt == NULL);
424 		if (mdocs[n->tok].pre && (n->end == ENDBODY_NOT || n->child))
425 			child = (*mdocs[n->tok].pre)(meta, n, h);
426 		break;
427 	}
428 
429 	if (h->flags & HTML_KEEP && n->flags & MDOC_LINE) {
430 		h->flags &= ~HTML_KEEP;
431 		h->flags |= HTML_PREKEEP;
432 	}
433 
434 	if (child && n->child)
435 		print_mdoc_nodelist(meta, n->child, h);
436 
437 	print_stagq(h, t);
438 
439 	switch (n->type) {
440 	case ROFFT_EQN:
441 		break;
442 	default:
443 		if ( ! mdocs[n->tok].post || n->flags & MDOC_ENDED)
444 			break;
445 		(*mdocs[n->tok].post)(meta, n, h);
446 		if (n->end != ENDBODY_NOT)
447 			n->body->flags |= MDOC_ENDED;
448 		if (n->end == ENDBODY_NOSPACE)
449 			h->flags |= HTML_NOSPACE;
450 		break;
451 	}
452 }
453 
454 static void
455 mdoc_root_post(MDOC_ARGS)
456 {
457 	struct htmlpair	 tag;
458 	struct tag	*t, *tt;
459 
460 	PAIR_CLASS_INIT(&tag, "foot");
461 	t = print_otag(h, TAG_TABLE, 1, &tag);
462 
463 	print_otag(h, TAG_TBODY, 0, NULL);
464 
465 	tt = print_otag(h, TAG_TR, 0, NULL);
466 
467 	PAIR_CLASS_INIT(&tag, "foot-date");
468 	print_otag(h, TAG_TD, 1, &tag);
469 	print_text(h, meta->date);
470 	print_stagq(h, tt);
471 
472 	PAIR_CLASS_INIT(&tag, "foot-os");
473 	print_otag(h, TAG_TD, 1, &tag);
474 	print_text(h, meta->os);
475 	print_tagq(h, t);
476 }
477 
478 static int
479 mdoc_root_pre(MDOC_ARGS)
480 {
481 	struct htmlpair	 tag;
482 	struct tag	*t, *tt;
483 	char		*volume, *title;
484 
485 	if (NULL == meta->arch)
486 		volume = mandoc_strdup(meta->vol);
487 	else
488 		mandoc_asprintf(&volume, "%s (%s)",
489 		    meta->vol, meta->arch);
490 
491 	if (NULL == meta->msec)
492 		title = mandoc_strdup(meta->title);
493 	else
494 		mandoc_asprintf(&title, "%s(%s)",
495 		    meta->title, meta->msec);
496 
497 	PAIR_CLASS_INIT(&tag, "head");
498 	t = print_otag(h, TAG_TABLE, 1, &tag);
499 
500 	print_otag(h, TAG_TBODY, 0, NULL);
501 
502 	tt = print_otag(h, TAG_TR, 0, NULL);
503 
504 	PAIR_CLASS_INIT(&tag, "head-ltitle");
505 	print_otag(h, TAG_TD, 1, &tag);
506 	print_text(h, title);
507 	print_stagq(h, tt);
508 
509 	PAIR_CLASS_INIT(&tag, "head-vol");
510 	print_otag(h, TAG_TD, 1, &tag);
511 	print_text(h, volume);
512 	print_stagq(h, tt);
513 
514 	PAIR_CLASS_INIT(&tag, "head-rtitle");
515 	print_otag(h, TAG_TD, 1, &tag);
516 	print_text(h, title);
517 	print_tagq(h, t);
518 
519 	free(title);
520 	free(volume);
521 	return 1;
522 }
523 
524 static int
525 mdoc_sh_pre(MDOC_ARGS)
526 {
527 	struct htmlpair	 tag;
528 
529 	switch (n->type) {
530 	case ROFFT_BLOCK:
531 		PAIR_CLASS_INIT(&tag, "section");
532 		print_otag(h, TAG_DIV, 1, &tag);
533 		return 1;
534 	case ROFFT_BODY:
535 		if (n->sec == SEC_AUTHORS)
536 			h->flags &= ~(HTML_SPLIT|HTML_NOSPLIT);
537 		return 1;
538 	default:
539 		break;
540 	}
541 
542 	bufinit(h);
543 
544 	for (n = n->child; n != NULL && n->type == ROFFT_TEXT; ) {
545 		bufcat_id(h, n->string);
546 		if (NULL != (n = n->next))
547 			bufcat_id(h, " ");
548 	}
549 
550 	if (NULL == n) {
551 		PAIR_ID_INIT(&tag, h->buf);
552 		print_otag(h, TAG_H1, 1, &tag);
553 	} else
554 		print_otag(h, TAG_H1, 0, NULL);
555 
556 	return 1;
557 }
558 
559 static int
560 mdoc_ss_pre(MDOC_ARGS)
561 {
562 	struct htmlpair	 tag;
563 
564 	if (n->type == ROFFT_BLOCK) {
565 		PAIR_CLASS_INIT(&tag, "subsection");
566 		print_otag(h, TAG_DIV, 1, &tag);
567 		return 1;
568 	} else if (n->type == ROFFT_BODY)
569 		return 1;
570 
571 	bufinit(h);
572 
573 	for (n = n->child; n != NULL && n->type == ROFFT_TEXT; ) {
574 		bufcat_id(h, n->string);
575 		if (NULL != (n = n->next))
576 			bufcat_id(h, " ");
577 	}
578 
579 	if (NULL == n) {
580 		PAIR_ID_INIT(&tag, h->buf);
581 		print_otag(h, TAG_H2, 1, &tag);
582 	} else
583 		print_otag(h, TAG_H2, 0, NULL);
584 
585 	return 1;
586 }
587 
588 static int
589 mdoc_fl_pre(MDOC_ARGS)
590 {
591 	struct htmlpair	 tag;
592 
593 	PAIR_CLASS_INIT(&tag, "flag");
594 	print_otag(h, TAG_B, 1, &tag);
595 
596 	/* `Cm' has no leading hyphen. */
597 
598 	if (MDOC_Cm == n->tok)
599 		return 1;
600 
601 	print_text(h, "\\-");
602 
603 	if (!(n->child == NULL &&
604 	    (n->next == NULL ||
605 	     n->next->type == ROFFT_TEXT ||
606 	     n->next->flags & MDOC_LINE)))
607 		h->flags |= HTML_NOSPACE;
608 
609 	return 1;
610 }
611 
612 static int
613 mdoc_nd_pre(MDOC_ARGS)
614 {
615 	struct htmlpair	 tag;
616 
617 	if (n->type != ROFFT_BODY)
618 		return 1;
619 
620 	/* XXX: this tag in theory can contain block elements. */
621 
622 	print_text(h, "\\(em");
623 	PAIR_CLASS_INIT(&tag, "desc");
624 	print_otag(h, TAG_SPAN, 1, &tag);
625 	return 1;
626 }
627 
628 static int
629 mdoc_nm_pre(MDOC_ARGS)
630 {
631 	struct htmlpair	 tag;
632 	struct roffsu	 su;
633 	int		 len;
634 
635 	switch (n->type) {
636 	case ROFFT_HEAD:
637 		print_otag(h, TAG_TD, 0, NULL);
638 		/* FALLTHROUGH */
639 	case ROFFT_ELEM:
640 		PAIR_CLASS_INIT(&tag, "name");
641 		print_otag(h, TAG_B, 1, &tag);
642 		if (n->child == NULL && meta->name != NULL)
643 			print_text(h, meta->name);
644 		return 1;
645 	case ROFFT_BODY:
646 		print_otag(h, TAG_TD, 0, NULL);
647 		return 1;
648 	default:
649 		break;
650 	}
651 
652 	synopsis_pre(h, n);
653 	PAIR_CLASS_INIT(&tag, "synopsis");
654 	print_otag(h, TAG_TABLE, 1, &tag);
655 
656 	for (len = 0, n = n->head->child; n; n = n->next)
657 		if (n->type == ROFFT_TEXT)
658 			len += html_strlen(n->string);
659 
660 	if (len == 0 && meta->name != NULL)
661 		len = html_strlen(meta->name);
662 
663 	SCALE_HS_INIT(&su, len);
664 	bufinit(h);
665 	bufcat_su(h, "width", &su);
666 	PAIR_STYLE_INIT(&tag, h);
667 	print_otag(h, TAG_COL, 1, &tag);
668 	print_otag(h, TAG_COL, 0, NULL);
669 	print_otag(h, TAG_TBODY, 0, NULL);
670 	print_otag(h, TAG_TR, 0, NULL);
671 	return 1;
672 }
673 
674 static int
675 mdoc_xr_pre(MDOC_ARGS)
676 {
677 	struct htmlpair	 tag[2];
678 
679 	if (NULL == n->child)
680 		return 0;
681 
682 	PAIR_CLASS_INIT(&tag[0], "link-man");
683 
684 	if (h->base_man) {
685 		buffmt_man(h, n->child->string,
686 		    n->child->next ?
687 		    n->child->next->string : NULL);
688 		PAIR_HREF_INIT(&tag[1], h->buf);
689 		print_otag(h, TAG_A, 2, tag);
690 	} else
691 		print_otag(h, TAG_A, 1, tag);
692 
693 	n = n->child;
694 	print_text(h, n->string);
695 
696 	if (NULL == (n = n->next))
697 		return 0;
698 
699 	h->flags |= HTML_NOSPACE;
700 	print_text(h, "(");
701 	h->flags |= HTML_NOSPACE;
702 	print_text(h, n->string);
703 	h->flags |= HTML_NOSPACE;
704 	print_text(h, ")");
705 	return 0;
706 }
707 
708 static int
709 mdoc_ns_pre(MDOC_ARGS)
710 {
711 
712 	if ( ! (MDOC_LINE & n->flags))
713 		h->flags |= HTML_NOSPACE;
714 	return 1;
715 }
716 
717 static int
718 mdoc_ar_pre(MDOC_ARGS)
719 {
720 	struct htmlpair tag;
721 
722 	PAIR_CLASS_INIT(&tag, "arg");
723 	print_otag(h, TAG_I, 1, &tag);
724 	return 1;
725 }
726 
727 static int
728 mdoc_xx_pre(MDOC_ARGS)
729 {
730 	const char	*pp;
731 	struct htmlpair	 tag;
732 	int		 flags;
733 
734 	switch (n->tok) {
735 	case MDOC_Bsx:
736 		pp = "BSD/OS";
737 		break;
738 	case MDOC_Dx:
739 		pp = "DragonFly";
740 		break;
741 	case MDOC_Fx:
742 		pp = "FreeBSD";
743 		break;
744 	case MDOC_Nx:
745 		pp = "NetBSD";
746 		break;
747 	case MDOC_Ox:
748 		pp = "OpenBSD";
749 		break;
750 	case MDOC_Ux:
751 		pp = "UNIX";
752 		break;
753 	default:
754 		return 1;
755 	}
756 
757 	PAIR_CLASS_INIT(&tag, "unix");
758 	print_otag(h, TAG_SPAN, 1, &tag);
759 
760 	print_text(h, pp);
761 	if (n->child) {
762 		flags = h->flags;
763 		h->flags |= HTML_KEEP;
764 		print_text(h, n->child->string);
765 		h->flags = flags;
766 	}
767 	return 0;
768 }
769 
770 static int
771 mdoc_bx_pre(MDOC_ARGS)
772 {
773 	struct htmlpair	 tag;
774 
775 	PAIR_CLASS_INIT(&tag, "unix");
776 	print_otag(h, TAG_SPAN, 1, &tag);
777 
778 	if (NULL != (n = n->child)) {
779 		print_text(h, n->string);
780 		h->flags |= HTML_NOSPACE;
781 		print_text(h, "BSD");
782 	} else {
783 		print_text(h, "BSD");
784 		return 0;
785 	}
786 
787 	if (NULL != (n = n->next)) {
788 		h->flags |= HTML_NOSPACE;
789 		print_text(h, "-");
790 		h->flags |= HTML_NOSPACE;
791 		print_text(h, n->string);
792 	}
793 
794 	return 0;
795 }
796 
797 static int
798 mdoc_it_pre(MDOC_ARGS)
799 {
800 	struct roffsu	 su;
801 	enum mdoc_list	 type;
802 	struct htmlpair	 tag[2];
803 	const struct roff_node *bl;
804 
805 	bl = n->parent;
806 	while (bl && MDOC_Bl != bl->tok)
807 		bl = bl->parent;
808 
809 	assert(bl);
810 
811 	type = bl->norm->Bl.type;
812 
813 	assert(lists[type]);
814 	PAIR_CLASS_INIT(&tag[0], lists[type]);
815 
816 	bufinit(h);
817 
818 	if (n->type == ROFFT_HEAD) {
819 		switch (type) {
820 		case LIST_bullet:
821 		case LIST_dash:
822 		case LIST_item:
823 		case LIST_hyphen:
824 		case LIST_enum:
825 			return 0;
826 		case LIST_diag:
827 		case LIST_hang:
828 		case LIST_inset:
829 		case LIST_ohang:
830 		case LIST_tag:
831 			SCALE_VS_INIT(&su, ! bl->norm->Bl.comp);
832 			bufcat_su(h, "margin-top", &su);
833 			PAIR_STYLE_INIT(&tag[1], h);
834 			print_otag(h, TAG_DT, 2, tag);
835 			if (LIST_diag != type)
836 				break;
837 			PAIR_CLASS_INIT(&tag[0], "diag");
838 			print_otag(h, TAG_B, 1, tag);
839 			break;
840 		case LIST_column:
841 			break;
842 		default:
843 			break;
844 		}
845 	} else if (n->type == ROFFT_BODY) {
846 		switch (type) {
847 		case LIST_bullet:
848 		case LIST_hyphen:
849 		case LIST_dash:
850 		case LIST_enum:
851 		case LIST_item:
852 			SCALE_VS_INIT(&su, ! bl->norm->Bl.comp);
853 			bufcat_su(h, "margin-top", &su);
854 			PAIR_STYLE_INIT(&tag[1], h);
855 			print_otag(h, TAG_LI, 2, tag);
856 			break;
857 		case LIST_diag:
858 		case LIST_hang:
859 		case LIST_inset:
860 		case LIST_ohang:
861 		case LIST_tag:
862 			if (NULL == bl->norm->Bl.width) {
863 				print_otag(h, TAG_DD, 1, tag);
864 				break;
865 			}
866 			a2width(bl->norm->Bl.width, &su);
867 			bufcat_su(h, "margin-left", &su);
868 			PAIR_STYLE_INIT(&tag[1], h);
869 			print_otag(h, TAG_DD, 2, tag);
870 			break;
871 		case LIST_column:
872 			SCALE_VS_INIT(&su, ! bl->norm->Bl.comp);
873 			bufcat_su(h, "margin-top", &su);
874 			PAIR_STYLE_INIT(&tag[1], h);
875 			print_otag(h, TAG_TD, 2, tag);
876 			break;
877 		default:
878 			break;
879 		}
880 	} else {
881 		switch (type) {
882 		case LIST_column:
883 			print_otag(h, TAG_TR, 1, tag);
884 			break;
885 		default:
886 			break;
887 		}
888 	}
889 
890 	return 1;
891 }
892 
893 static int
894 mdoc_bl_pre(MDOC_ARGS)
895 {
896 	int		 i;
897 	struct htmlpair	 tag[3];
898 	struct roffsu	 su;
899 	char		 buf[BUFSIZ];
900 
901 	if (n->type == ROFFT_BODY) {
902 		if (LIST_column == n->norm->Bl.type)
903 			print_otag(h, TAG_TBODY, 0, NULL);
904 		return 1;
905 	}
906 
907 	if (n->type == ROFFT_HEAD) {
908 		if (LIST_column != n->norm->Bl.type)
909 			return 0;
910 
911 		/*
912 		 * For each column, print out the <COL> tag with our
913 		 * suggested width.  The last column gets min-width, as
914 		 * in terminal mode it auto-sizes to the width of the
915 		 * screen and we want to preserve that behaviour.
916 		 */
917 
918 		for (i = 0; i < (int)n->norm->Bl.ncols; i++) {
919 			bufinit(h);
920 			a2width(n->norm->Bl.cols[i], &su);
921 			if (i < (int)n->norm->Bl.ncols - 1)
922 				bufcat_su(h, "width", &su);
923 			else
924 				bufcat_su(h, "min-width", &su);
925 			PAIR_STYLE_INIT(&tag[0], h);
926 			print_otag(h, TAG_COL, 1, tag);
927 		}
928 
929 		return 0;
930 	}
931 
932 	SCALE_VS_INIT(&su, 0);
933 	bufinit(h);
934 	bufcat_su(h, "margin-top", &su);
935 	bufcat_su(h, "margin-bottom", &su);
936 	PAIR_STYLE_INIT(&tag[0], h);
937 
938 	assert(lists[n->norm->Bl.type]);
939 	(void)strlcpy(buf, "list ", BUFSIZ);
940 	(void)strlcat(buf, lists[n->norm->Bl.type], BUFSIZ);
941 	PAIR_INIT(&tag[1], ATTR_CLASS, buf);
942 
943 	/* Set the block's left-hand margin. */
944 
945 	if (n->norm->Bl.offs) {
946 		a2width(n->norm->Bl.offs, &su);
947 		bufcat_su(h, "margin-left", &su);
948 	}
949 
950 	switch (n->norm->Bl.type) {
951 	case LIST_bullet:
952 	case LIST_dash:
953 	case LIST_hyphen:
954 	case LIST_item:
955 		print_otag(h, TAG_UL, 2, tag);
956 		break;
957 	case LIST_enum:
958 		print_otag(h, TAG_OL, 2, tag);
959 		break;
960 	case LIST_diag:
961 	case LIST_hang:
962 	case LIST_inset:
963 	case LIST_ohang:
964 	case LIST_tag:
965 		print_otag(h, TAG_DL, 2, tag);
966 		break;
967 	case LIST_column:
968 		print_otag(h, TAG_TABLE, 2, tag);
969 		break;
970 	default:
971 		abort();
972 	}
973 
974 	return 1;
975 }
976 
977 static int
978 mdoc_ex_pre(MDOC_ARGS)
979 {
980 	struct htmlpair	  tag;
981 	struct tag	 *t;
982 	struct roff_node *nch;
983 
984 	if (n->prev)
985 		print_otag(h, TAG_BR, 0, NULL);
986 
987 	PAIR_CLASS_INIT(&tag, "utility");
988 
989 	print_text(h, "The");
990 
991 	for (nch = n->child; nch != NULL; nch = nch->next) {
992 		assert(nch->type == ROFFT_TEXT);
993 
994 		t = print_otag(h, TAG_B, 1, &tag);
995 		print_text(h, nch->string);
996 		print_tagq(h, t);
997 
998 		if (nch->next == NULL)
999 			continue;
1000 
1001 		if (nch->prev != NULL || nch->next->next != NULL) {
1002 			h->flags |= HTML_NOSPACE;
1003 			print_text(h, ",");
1004 		}
1005 
1006 		if (nch->next->next == NULL)
1007 			print_text(h, "and");
1008 	}
1009 
1010 	if (n->child != NULL && n->child->next != NULL)
1011 		print_text(h, "utilities exit\\~0");
1012 	else
1013 		print_text(h, "utility exits\\~0");
1014 
1015 	print_text(h, "on success, and\\~>0 if an error occurs.");
1016 	return 0;
1017 }
1018 
1019 static int
1020 mdoc_em_pre(MDOC_ARGS)
1021 {
1022 	struct htmlpair	tag;
1023 
1024 	PAIR_CLASS_INIT(&tag, "emph");
1025 	print_otag(h, TAG_SPAN, 1, &tag);
1026 	return 1;
1027 }
1028 
1029 static int
1030 mdoc_d1_pre(MDOC_ARGS)
1031 {
1032 	struct htmlpair	 tag[2];
1033 	struct roffsu	 su;
1034 
1035 	if (n->type != ROFFT_BLOCK)
1036 		return 1;
1037 
1038 	SCALE_VS_INIT(&su, 0);
1039 	bufinit(h);
1040 	bufcat_su(h, "margin-top", &su);
1041 	bufcat_su(h, "margin-bottom", &su);
1042 	PAIR_STYLE_INIT(&tag[0], h);
1043 	print_otag(h, TAG_BLOCKQUOTE, 1, tag);
1044 
1045 	/* BLOCKQUOTE needs a block body. */
1046 
1047 	PAIR_CLASS_INIT(&tag[0], "display");
1048 	print_otag(h, TAG_DIV, 1, tag);
1049 
1050 	if (MDOC_Dl == n->tok) {
1051 		PAIR_CLASS_INIT(&tag[0], "lit");
1052 		print_otag(h, TAG_CODE, 1, tag);
1053 	}
1054 
1055 	return 1;
1056 }
1057 
1058 static int
1059 mdoc_sx_pre(MDOC_ARGS)
1060 {
1061 	struct htmlpair	 tag[2];
1062 
1063 	bufinit(h);
1064 	bufcat(h, "#");
1065 
1066 	for (n = n->child; n; ) {
1067 		bufcat_id(h, n->string);
1068 		if (NULL != (n = n->next))
1069 			bufcat_id(h, " ");
1070 	}
1071 
1072 	PAIR_CLASS_INIT(&tag[0], "link-sec");
1073 	PAIR_HREF_INIT(&tag[1], h->buf);
1074 
1075 	print_otag(h, TAG_I, 1, tag);
1076 	print_otag(h, TAG_A, 2, tag);
1077 	return 1;
1078 }
1079 
1080 static int
1081 mdoc_bd_pre(MDOC_ARGS)
1082 {
1083 	struct htmlpair		 tag[2];
1084 	int			 comp, sv;
1085 	struct roff_node	*nn;
1086 	struct roffsu		 su;
1087 
1088 	if (n->type == ROFFT_HEAD)
1089 		return 0;
1090 
1091 	if (n->type == ROFFT_BLOCK) {
1092 		comp = n->norm->Bd.comp;
1093 		for (nn = n; nn && ! comp; nn = nn->parent) {
1094 			if (nn->type != ROFFT_BLOCK)
1095 				continue;
1096 			if (MDOC_Ss == nn->tok || MDOC_Sh == nn->tok)
1097 				comp = 1;
1098 			if (nn->prev)
1099 				break;
1100 		}
1101 		if ( ! comp)
1102 			print_paragraph(h);
1103 		return 1;
1104 	}
1105 
1106 	/* Handle the -offset argument. */
1107 
1108 	if (n->norm->Bd.offs == NULL ||
1109 	    ! strcmp(n->norm->Bd.offs, "left"))
1110 		SCALE_HS_INIT(&su, 0);
1111 	else if ( ! strcmp(n->norm->Bd.offs, "indent"))
1112 		SCALE_HS_INIT(&su, INDENT);
1113 	else if ( ! strcmp(n->norm->Bd.offs, "indent-two"))
1114 		SCALE_HS_INIT(&su, INDENT * 2);
1115 	else
1116 		a2width(n->norm->Bd.offs, &su);
1117 
1118 	bufinit(h);
1119 	bufcat_su(h, "margin-left", &su);
1120 	PAIR_STYLE_INIT(&tag[0], h);
1121 
1122 	if (DISP_unfilled != n->norm->Bd.type &&
1123 	    DISP_literal != n->norm->Bd.type) {
1124 		PAIR_CLASS_INIT(&tag[1], "display");
1125 		print_otag(h, TAG_DIV, 2, tag);
1126 		return 1;
1127 	}
1128 
1129 	PAIR_CLASS_INIT(&tag[1], "lit display");
1130 	print_otag(h, TAG_PRE, 2, tag);
1131 
1132 	/* This can be recursive: save & set our literal state. */
1133 
1134 	sv = h->flags & HTML_LITERAL;
1135 	h->flags |= HTML_LITERAL;
1136 
1137 	for (nn = n->child; nn; nn = nn->next) {
1138 		print_mdoc_node(meta, nn, h);
1139 		/*
1140 		 * If the printed node flushes its own line, then we
1141 		 * needn't do it here as well.  This is hacky, but the
1142 		 * notion of selective eoln whitespace is pretty dumb
1143 		 * anyway, so don't sweat it.
1144 		 */
1145 		switch (nn->tok) {
1146 		case MDOC_Sm:
1147 		case MDOC_br:
1148 		case MDOC_sp:
1149 		case MDOC_Bl:
1150 		case MDOC_D1:
1151 		case MDOC_Dl:
1152 		case MDOC_Lp:
1153 		case MDOC_Pp:
1154 			continue;
1155 		default:
1156 			break;
1157 		}
1158 		if (h->flags & HTML_NONEWLINE ||
1159 		    (nn->next && ! (nn->next->flags & MDOC_LINE)))
1160 			continue;
1161 		else if (nn->next)
1162 			print_text(h, "\n");
1163 
1164 		h->flags |= HTML_NOSPACE;
1165 	}
1166 
1167 	if (0 == sv)
1168 		h->flags &= ~HTML_LITERAL;
1169 
1170 	return 0;
1171 }
1172 
1173 static int
1174 mdoc_pa_pre(MDOC_ARGS)
1175 {
1176 	struct htmlpair	tag;
1177 
1178 	PAIR_CLASS_INIT(&tag, "file");
1179 	print_otag(h, TAG_I, 1, &tag);
1180 	return 1;
1181 }
1182 
1183 static int
1184 mdoc_ad_pre(MDOC_ARGS)
1185 {
1186 	struct htmlpair	tag;
1187 
1188 	PAIR_CLASS_INIT(&tag, "addr");
1189 	print_otag(h, TAG_I, 1, &tag);
1190 	return 1;
1191 }
1192 
1193 static int
1194 mdoc_an_pre(MDOC_ARGS)
1195 {
1196 	struct htmlpair	tag;
1197 
1198 	if (n->norm->An.auth == AUTH_split) {
1199 		h->flags &= ~HTML_NOSPLIT;
1200 		h->flags |= HTML_SPLIT;
1201 		return 0;
1202 	}
1203 	if (n->norm->An.auth == AUTH_nosplit) {
1204 		h->flags &= ~HTML_SPLIT;
1205 		h->flags |= HTML_NOSPLIT;
1206 		return 0;
1207 	}
1208 
1209 	if (h->flags & HTML_SPLIT)
1210 		print_otag(h, TAG_BR, 0, NULL);
1211 
1212 	if (n->sec == SEC_AUTHORS && ! (h->flags & HTML_NOSPLIT))
1213 		h->flags |= HTML_SPLIT;
1214 
1215 	PAIR_CLASS_INIT(&tag, "author");
1216 	print_otag(h, TAG_SPAN, 1, &tag);
1217 	return 1;
1218 }
1219 
1220 static int
1221 mdoc_cd_pre(MDOC_ARGS)
1222 {
1223 	struct htmlpair	tag;
1224 
1225 	synopsis_pre(h, n);
1226 	PAIR_CLASS_INIT(&tag, "config");
1227 	print_otag(h, TAG_B, 1, &tag);
1228 	return 1;
1229 }
1230 
1231 static int
1232 mdoc_dv_pre(MDOC_ARGS)
1233 {
1234 	struct htmlpair	tag;
1235 
1236 	PAIR_CLASS_INIT(&tag, "define");
1237 	print_otag(h, TAG_SPAN, 1, &tag);
1238 	return 1;
1239 }
1240 
1241 static int
1242 mdoc_ev_pre(MDOC_ARGS)
1243 {
1244 	struct htmlpair	tag;
1245 
1246 	PAIR_CLASS_INIT(&tag, "env");
1247 	print_otag(h, TAG_SPAN, 1, &tag);
1248 	return 1;
1249 }
1250 
1251 static int
1252 mdoc_er_pre(MDOC_ARGS)
1253 {
1254 	struct htmlpair	tag;
1255 
1256 	PAIR_CLASS_INIT(&tag, "errno");
1257 	print_otag(h, TAG_SPAN, 1, &tag);
1258 	return 1;
1259 }
1260 
1261 static int
1262 mdoc_fa_pre(MDOC_ARGS)
1263 {
1264 	const struct roff_node	*nn;
1265 	struct htmlpair		 tag;
1266 	struct tag		*t;
1267 
1268 	PAIR_CLASS_INIT(&tag, "farg");
1269 	if (n->parent->tok != MDOC_Fo) {
1270 		print_otag(h, TAG_I, 1, &tag);
1271 		return 1;
1272 	}
1273 
1274 	for (nn = n->child; nn; nn = nn->next) {
1275 		t = print_otag(h, TAG_I, 1, &tag);
1276 		print_text(h, nn->string);
1277 		print_tagq(h, t);
1278 		if (nn->next) {
1279 			h->flags |= HTML_NOSPACE;
1280 			print_text(h, ",");
1281 		}
1282 	}
1283 
1284 	if (n->child && n->next && n->next->tok == MDOC_Fa) {
1285 		h->flags |= HTML_NOSPACE;
1286 		print_text(h, ",");
1287 	}
1288 
1289 	return 0;
1290 }
1291 
1292 static int
1293 mdoc_fd_pre(MDOC_ARGS)
1294 {
1295 	struct htmlpair	 tag[2];
1296 	char		 buf[BUFSIZ];
1297 	size_t		 sz;
1298 	int		 i;
1299 	struct tag	*t;
1300 
1301 	synopsis_pre(h, n);
1302 
1303 	if (NULL == (n = n->child))
1304 		return 0;
1305 
1306 	assert(n->type == ROFFT_TEXT);
1307 
1308 	if (strcmp(n->string, "#include")) {
1309 		PAIR_CLASS_INIT(&tag[0], "macro");
1310 		print_otag(h, TAG_B, 1, tag);
1311 		return 1;
1312 	}
1313 
1314 	PAIR_CLASS_INIT(&tag[0], "includes");
1315 	print_otag(h, TAG_B, 1, tag);
1316 	print_text(h, n->string);
1317 
1318 	if (NULL != (n = n->next)) {
1319 		assert(n->type == ROFFT_TEXT);
1320 
1321 		/*
1322 		 * XXX This is broken and not easy to fix.
1323 		 * When using -Oincludes, truncation may occur.
1324 		 * Dynamic allocation wouldn't help because
1325 		 * passing long strings to buffmt_includes()
1326 		 * does not work either.
1327 		 */
1328 
1329 		strlcpy(buf, '<' == *n->string || '"' == *n->string ?
1330 		    n->string + 1 : n->string, BUFSIZ);
1331 
1332 		sz = strlen(buf);
1333 		if (sz && ('>' == buf[sz - 1] || '"' == buf[sz - 1]))
1334 			buf[sz - 1] = '\0';
1335 
1336 		PAIR_CLASS_INIT(&tag[0], "link-includes");
1337 
1338 		i = 1;
1339 		if (h->base_includes) {
1340 			buffmt_includes(h, buf);
1341 			PAIR_HREF_INIT(&tag[i], h->buf);
1342 			i++;
1343 		}
1344 
1345 		t = print_otag(h, TAG_A, i, tag);
1346 		print_text(h, n->string);
1347 		print_tagq(h, t);
1348 
1349 		n = n->next;
1350 	}
1351 
1352 	for ( ; n; n = n->next) {
1353 		assert(n->type == ROFFT_TEXT);
1354 		print_text(h, n->string);
1355 	}
1356 
1357 	return 0;
1358 }
1359 
1360 static int
1361 mdoc_vt_pre(MDOC_ARGS)
1362 {
1363 	struct htmlpair	 tag;
1364 
1365 	if (n->type == ROFFT_BLOCK) {
1366 		synopsis_pre(h, n);
1367 		return 1;
1368 	} else if (n->type == ROFFT_ELEM) {
1369 		synopsis_pre(h, n);
1370 	} else if (n->type == ROFFT_HEAD)
1371 		return 0;
1372 
1373 	PAIR_CLASS_INIT(&tag, "type");
1374 	print_otag(h, TAG_SPAN, 1, &tag);
1375 	return 1;
1376 }
1377 
1378 static int
1379 mdoc_ft_pre(MDOC_ARGS)
1380 {
1381 	struct htmlpair	 tag;
1382 
1383 	synopsis_pre(h, n);
1384 	PAIR_CLASS_INIT(&tag, "ftype");
1385 	print_otag(h, TAG_I, 1, &tag);
1386 	return 1;
1387 }
1388 
1389 static int
1390 mdoc_fn_pre(MDOC_ARGS)
1391 {
1392 	struct tag	*t;
1393 	struct htmlpair	 tag[2];
1394 	char		 nbuf[BUFSIZ];
1395 	const char	*sp, *ep;
1396 	int		 sz, i, pretty;
1397 
1398 	pretty = MDOC_SYNPRETTY & n->flags;
1399 	synopsis_pre(h, n);
1400 
1401 	/* Split apart into type and name. */
1402 	assert(n->child->string);
1403 	sp = n->child->string;
1404 
1405 	ep = strchr(sp, ' ');
1406 	if (NULL != ep) {
1407 		PAIR_CLASS_INIT(&tag[0], "ftype");
1408 		t = print_otag(h, TAG_I, 1, tag);
1409 
1410 		while (ep) {
1411 			sz = MIN((int)(ep - sp), BUFSIZ - 1);
1412 			(void)memcpy(nbuf, sp, (size_t)sz);
1413 			nbuf[sz] = '\0';
1414 			print_text(h, nbuf);
1415 			sp = ++ep;
1416 			ep = strchr(sp, ' ');
1417 		}
1418 		print_tagq(h, t);
1419 	}
1420 
1421 	PAIR_CLASS_INIT(&tag[0], "fname");
1422 
1423 	/*
1424 	 * FIXME: only refer to IDs that we know exist.
1425 	 */
1426 
1427 #if 0
1428 	if (MDOC_SYNPRETTY & n->flags) {
1429 		nbuf[0] = '\0';
1430 		html_idcat(nbuf, sp, BUFSIZ);
1431 		PAIR_ID_INIT(&tag[1], nbuf);
1432 	} else {
1433 		strlcpy(nbuf, "#", BUFSIZ);
1434 		html_idcat(nbuf, sp, BUFSIZ);
1435 		PAIR_HREF_INIT(&tag[1], nbuf);
1436 	}
1437 #endif
1438 
1439 	t = print_otag(h, TAG_B, 1, tag);
1440 
1441 	if (sp)
1442 		print_text(h, sp);
1443 
1444 	print_tagq(h, t);
1445 
1446 	h->flags |= HTML_NOSPACE;
1447 	print_text(h, "(");
1448 	h->flags |= HTML_NOSPACE;
1449 
1450 	PAIR_CLASS_INIT(&tag[0], "farg");
1451 	bufinit(h);
1452 	bufcat_style(h, "white-space", "nowrap");
1453 	PAIR_STYLE_INIT(&tag[1], h);
1454 
1455 	for (n = n->child->next; n; n = n->next) {
1456 		i = 1;
1457 		if (MDOC_SYNPRETTY & n->flags)
1458 			i = 2;
1459 		t = print_otag(h, TAG_I, i, tag);
1460 		print_text(h, n->string);
1461 		print_tagq(h, t);
1462 		if (n->next) {
1463 			h->flags |= HTML_NOSPACE;
1464 			print_text(h, ",");
1465 		}
1466 	}
1467 
1468 	h->flags |= HTML_NOSPACE;
1469 	print_text(h, ")");
1470 
1471 	if (pretty) {
1472 		h->flags |= HTML_NOSPACE;
1473 		print_text(h, ";");
1474 	}
1475 
1476 	return 0;
1477 }
1478 
1479 static int
1480 mdoc_sm_pre(MDOC_ARGS)
1481 {
1482 
1483 	if (NULL == n->child)
1484 		h->flags ^= HTML_NONOSPACE;
1485 	else if (0 == strcmp("on", n->child->string))
1486 		h->flags &= ~HTML_NONOSPACE;
1487 	else
1488 		h->flags |= HTML_NONOSPACE;
1489 
1490 	if ( ! (HTML_NONOSPACE & h->flags))
1491 		h->flags &= ~HTML_NOSPACE;
1492 
1493 	return 0;
1494 }
1495 
1496 static int
1497 mdoc_skip_pre(MDOC_ARGS)
1498 {
1499 
1500 	return 0;
1501 }
1502 
1503 static int
1504 mdoc_pp_pre(MDOC_ARGS)
1505 {
1506 
1507 	print_paragraph(h);
1508 	return 0;
1509 }
1510 
1511 static int
1512 mdoc_sp_pre(MDOC_ARGS)
1513 {
1514 	struct roffsu	 su;
1515 	struct htmlpair	 tag;
1516 
1517 	SCALE_VS_INIT(&su, 1);
1518 
1519 	if (MDOC_sp == n->tok) {
1520 		if (NULL != (n = n->child)) {
1521 			if ( ! a2roffsu(n->string, &su, SCALE_VS))
1522 				su.scale = 1.0;
1523 			else if (su.scale < 0.0)
1524 				su.scale = 0.0;
1525 		}
1526 	} else
1527 		su.scale = 0.0;
1528 
1529 	bufinit(h);
1530 	bufcat_su(h, "height", &su);
1531 	PAIR_STYLE_INIT(&tag, h);
1532 	print_otag(h, TAG_DIV, 1, &tag);
1533 
1534 	/* So the div isn't empty: */
1535 	print_text(h, "\\~");
1536 
1537 	return 0;
1538 
1539 }
1540 
1541 static int
1542 mdoc_lk_pre(MDOC_ARGS)
1543 {
1544 	struct htmlpair	 tag[2];
1545 
1546 	if (NULL == (n = n->child))
1547 		return 0;
1548 
1549 	assert(n->type == ROFFT_TEXT);
1550 
1551 	PAIR_CLASS_INIT(&tag[0], "link-ext");
1552 	PAIR_HREF_INIT(&tag[1], n->string);
1553 
1554 	print_otag(h, TAG_A, 2, tag);
1555 
1556 	if (NULL == n->next)
1557 		print_text(h, n->string);
1558 
1559 	for (n = n->next; n; n = n->next)
1560 		print_text(h, n->string);
1561 
1562 	return 0;
1563 }
1564 
1565 static int
1566 mdoc_mt_pre(MDOC_ARGS)
1567 {
1568 	struct htmlpair	 tag[2];
1569 	struct tag	*t;
1570 
1571 	PAIR_CLASS_INIT(&tag[0], "link-mail");
1572 
1573 	for (n = n->child; n; n = n->next) {
1574 		assert(n->type == ROFFT_TEXT);
1575 
1576 		bufinit(h);
1577 		bufcat(h, "mailto:");
1578 		bufcat(h, n->string);
1579 
1580 		PAIR_HREF_INIT(&tag[1], h->buf);
1581 		t = print_otag(h, TAG_A, 2, tag);
1582 		print_text(h, n->string);
1583 		print_tagq(h, t);
1584 	}
1585 
1586 	return 0;
1587 }
1588 
1589 static int
1590 mdoc_fo_pre(MDOC_ARGS)
1591 {
1592 	struct htmlpair	 tag;
1593 	struct tag	*t;
1594 
1595 	if (n->type == ROFFT_BODY) {
1596 		h->flags |= HTML_NOSPACE;
1597 		print_text(h, "(");
1598 		h->flags |= HTML_NOSPACE;
1599 		return 1;
1600 	} else if (n->type == ROFFT_BLOCK) {
1601 		synopsis_pre(h, n);
1602 		return 1;
1603 	}
1604 
1605 	if (n->child == NULL)
1606 		return 0;
1607 
1608 	assert(n->child->string);
1609 	PAIR_CLASS_INIT(&tag, "fname");
1610 	t = print_otag(h, TAG_B, 1, &tag);
1611 	print_text(h, n->child->string);
1612 	print_tagq(h, t);
1613 	return 0;
1614 }
1615 
1616 static void
1617 mdoc_fo_post(MDOC_ARGS)
1618 {
1619 
1620 	if (n->type != ROFFT_BODY)
1621 		return;
1622 	h->flags |= HTML_NOSPACE;
1623 	print_text(h, ")");
1624 	h->flags |= HTML_NOSPACE;
1625 	print_text(h, ";");
1626 }
1627 
1628 static int
1629 mdoc_in_pre(MDOC_ARGS)
1630 {
1631 	struct tag	*t;
1632 	struct htmlpair	 tag[2];
1633 	int		 i;
1634 
1635 	synopsis_pre(h, n);
1636 
1637 	PAIR_CLASS_INIT(&tag[0], "includes");
1638 	print_otag(h, TAG_B, 1, tag);
1639 
1640 	/*
1641 	 * The first argument of the `In' gets special treatment as
1642 	 * being a linked value.  Subsequent values are printed
1643 	 * afterward.  groff does similarly.  This also handles the case
1644 	 * of no children.
1645 	 */
1646 
1647 	if (MDOC_SYNPRETTY & n->flags && MDOC_LINE & n->flags)
1648 		print_text(h, "#include");
1649 
1650 	print_text(h, "<");
1651 	h->flags |= HTML_NOSPACE;
1652 
1653 	if (NULL != (n = n->child)) {
1654 		assert(n->type == ROFFT_TEXT);
1655 
1656 		PAIR_CLASS_INIT(&tag[0], "link-includes");
1657 
1658 		i = 1;
1659 		if (h->base_includes) {
1660 			buffmt_includes(h, n->string);
1661 			PAIR_HREF_INIT(&tag[i], h->buf);
1662 			i++;
1663 		}
1664 
1665 		t = print_otag(h, TAG_A, i, tag);
1666 		print_text(h, n->string);
1667 		print_tagq(h, t);
1668 
1669 		n = n->next;
1670 	}
1671 
1672 	h->flags |= HTML_NOSPACE;
1673 	print_text(h, ">");
1674 
1675 	for ( ; n; n = n->next) {
1676 		assert(n->type == ROFFT_TEXT);
1677 		print_text(h, n->string);
1678 	}
1679 
1680 	return 0;
1681 }
1682 
1683 static int
1684 mdoc_ic_pre(MDOC_ARGS)
1685 {
1686 	struct htmlpair	tag;
1687 
1688 	PAIR_CLASS_INIT(&tag, "cmd");
1689 	print_otag(h, TAG_B, 1, &tag);
1690 	return 1;
1691 }
1692 
1693 static int
1694 mdoc_rv_pre(MDOC_ARGS)
1695 {
1696 	struct htmlpair	 tag;
1697 	struct tag	*t;
1698 	struct roff_node *nch;
1699 
1700 	if (n->prev)
1701 		print_otag(h, TAG_BR, 0, NULL);
1702 
1703 	PAIR_CLASS_INIT(&tag, "fname");
1704 
1705 	if (n->child != NULL) {
1706 		print_text(h, "The");
1707 
1708 		for (nch = n->child; nch != NULL; nch = nch->next) {
1709 			t = print_otag(h, TAG_B, 1, &tag);
1710 			print_text(h, nch->string);
1711 			print_tagq(h, t);
1712 
1713 			h->flags |= HTML_NOSPACE;
1714 			print_text(h, "()");
1715 
1716 			if (nch->next == NULL)
1717 				continue;
1718 
1719 			if (nch->prev != NULL || nch->next->next != NULL) {
1720 				h->flags |= HTML_NOSPACE;
1721 				print_text(h, ",");
1722 			}
1723 			if (nch->next->next == NULL)
1724 				print_text(h, "and");
1725 		}
1726 
1727 		if (n->child != NULL && n->child->next != NULL)
1728 			print_text(h, "functions return");
1729 		else
1730 			print_text(h, "function returns");
1731 
1732 		print_text(h, "the value\\~0 if successful;");
1733 	} else
1734 		print_text(h, "Upon successful completion,"
1735                     " the value\\~0 is returned;");
1736 
1737 	print_text(h, "otherwise the value\\~\\-1 is returned"
1738 	   " and the global variable");
1739 
1740 	PAIR_CLASS_INIT(&tag, "var");
1741 	t = print_otag(h, TAG_B, 1, &tag);
1742 	print_text(h, "errno");
1743 	print_tagq(h, t);
1744 	print_text(h, "is set to indicate the error.");
1745 	return 0;
1746 }
1747 
1748 static int
1749 mdoc_va_pre(MDOC_ARGS)
1750 {
1751 	struct htmlpair	tag;
1752 
1753 	PAIR_CLASS_INIT(&tag, "var");
1754 	print_otag(h, TAG_B, 1, &tag);
1755 	return 1;
1756 }
1757 
1758 static int
1759 mdoc_ap_pre(MDOC_ARGS)
1760 {
1761 
1762 	h->flags |= HTML_NOSPACE;
1763 	print_text(h, "\\(aq");
1764 	h->flags |= HTML_NOSPACE;
1765 	return 1;
1766 }
1767 
1768 static int
1769 mdoc_bf_pre(MDOC_ARGS)
1770 {
1771 	struct htmlpair	 tag[2];
1772 	struct roffsu	 su;
1773 
1774 	if (n->type == ROFFT_HEAD)
1775 		return 0;
1776 	else if (n->type != ROFFT_BODY)
1777 		return 1;
1778 
1779 	if (FONT_Em == n->norm->Bf.font)
1780 		PAIR_CLASS_INIT(&tag[0], "emph");
1781 	else if (FONT_Sy == n->norm->Bf.font)
1782 		PAIR_CLASS_INIT(&tag[0], "symb");
1783 	else if (FONT_Li == n->norm->Bf.font)
1784 		PAIR_CLASS_INIT(&tag[0], "lit");
1785 	else
1786 		PAIR_CLASS_INIT(&tag[0], "none");
1787 
1788 	/*
1789 	 * We want this to be inline-formatted, but needs to be div to
1790 	 * accept block children.
1791 	 */
1792 	bufinit(h);
1793 	bufcat_style(h, "display", "inline");
1794 	SCALE_HS_INIT(&su, 1);
1795 	/* Needs a left-margin for spacing. */
1796 	bufcat_su(h, "margin-left", &su);
1797 	PAIR_STYLE_INIT(&tag[1], h);
1798 	print_otag(h, TAG_DIV, 2, tag);
1799 	return 1;
1800 }
1801 
1802 static int
1803 mdoc_ms_pre(MDOC_ARGS)
1804 {
1805 	struct htmlpair	tag;
1806 
1807 	PAIR_CLASS_INIT(&tag, "symb");
1808 	print_otag(h, TAG_SPAN, 1, &tag);
1809 	return 1;
1810 }
1811 
1812 static int
1813 mdoc_igndelim_pre(MDOC_ARGS)
1814 {
1815 
1816 	h->flags |= HTML_IGNDELIM;
1817 	return 1;
1818 }
1819 
1820 static void
1821 mdoc_pf_post(MDOC_ARGS)
1822 {
1823 
1824 	if ( ! (n->next == NULL || n->next->flags & MDOC_LINE))
1825 		h->flags |= HTML_NOSPACE;
1826 }
1827 
1828 static int
1829 mdoc_rs_pre(MDOC_ARGS)
1830 {
1831 	struct htmlpair	 tag;
1832 
1833 	if (n->type != ROFFT_BLOCK)
1834 		return 1;
1835 
1836 	if (n->prev && SEC_SEE_ALSO == n->sec)
1837 		print_paragraph(h);
1838 
1839 	PAIR_CLASS_INIT(&tag, "ref");
1840 	print_otag(h, TAG_SPAN, 1, &tag);
1841 	return 1;
1842 }
1843 
1844 static int
1845 mdoc_no_pre(MDOC_ARGS)
1846 {
1847 	struct htmlpair	tag;
1848 
1849 	PAIR_CLASS_INIT(&tag, "none");
1850 	print_otag(h, TAG_CODE, 1, &tag);
1851 	return 1;
1852 }
1853 
1854 static int
1855 mdoc_li_pre(MDOC_ARGS)
1856 {
1857 	struct htmlpair	tag;
1858 
1859 	PAIR_CLASS_INIT(&tag, "lit");
1860 	print_otag(h, TAG_CODE, 1, &tag);
1861 	return 1;
1862 }
1863 
1864 static int
1865 mdoc_sy_pre(MDOC_ARGS)
1866 {
1867 	struct htmlpair	tag;
1868 
1869 	PAIR_CLASS_INIT(&tag, "symb");
1870 	print_otag(h, TAG_SPAN, 1, &tag);
1871 	return 1;
1872 }
1873 
1874 static int
1875 mdoc_bt_pre(MDOC_ARGS)
1876 {
1877 
1878 	print_text(h, "is currently in beta test.");
1879 	return 0;
1880 }
1881 
1882 static int
1883 mdoc_ud_pre(MDOC_ARGS)
1884 {
1885 
1886 	print_text(h, "currently under development.");
1887 	return 0;
1888 }
1889 
1890 static int
1891 mdoc_lb_pre(MDOC_ARGS)
1892 {
1893 	struct htmlpair	tag;
1894 
1895 	if (SEC_LIBRARY == n->sec && MDOC_LINE & n->flags && n->prev)
1896 		print_otag(h, TAG_BR, 0, NULL);
1897 
1898 	PAIR_CLASS_INIT(&tag, "lib");
1899 	print_otag(h, TAG_SPAN, 1, &tag);
1900 	return 1;
1901 }
1902 
1903 static int
1904 mdoc__x_pre(MDOC_ARGS)
1905 {
1906 	struct htmlpair	tag[2];
1907 	enum htmltag	t;
1908 
1909 	t = TAG_SPAN;
1910 
1911 	switch (n->tok) {
1912 	case MDOC__A:
1913 		PAIR_CLASS_INIT(&tag[0], "ref-auth");
1914 		if (n->prev && MDOC__A == n->prev->tok)
1915 			if (NULL == n->next || MDOC__A != n->next->tok)
1916 				print_text(h, "and");
1917 		break;
1918 	case MDOC__B:
1919 		PAIR_CLASS_INIT(&tag[0], "ref-book");
1920 		t = TAG_I;
1921 		break;
1922 	case MDOC__C:
1923 		PAIR_CLASS_INIT(&tag[0], "ref-city");
1924 		break;
1925 	case MDOC__D:
1926 		PAIR_CLASS_INIT(&tag[0], "ref-date");
1927 		break;
1928 	case MDOC__I:
1929 		PAIR_CLASS_INIT(&tag[0], "ref-issue");
1930 		t = TAG_I;
1931 		break;
1932 	case MDOC__J:
1933 		PAIR_CLASS_INIT(&tag[0], "ref-jrnl");
1934 		t = TAG_I;
1935 		break;
1936 	case MDOC__N:
1937 		PAIR_CLASS_INIT(&tag[0], "ref-num");
1938 		break;
1939 	case MDOC__O:
1940 		PAIR_CLASS_INIT(&tag[0], "ref-opt");
1941 		break;
1942 	case MDOC__P:
1943 		PAIR_CLASS_INIT(&tag[0], "ref-page");
1944 		break;
1945 	case MDOC__Q:
1946 		PAIR_CLASS_INIT(&tag[0], "ref-corp");
1947 		break;
1948 	case MDOC__R:
1949 		PAIR_CLASS_INIT(&tag[0], "ref-rep");
1950 		break;
1951 	case MDOC__T:
1952 		PAIR_CLASS_INIT(&tag[0], "ref-title");
1953 		break;
1954 	case MDOC__U:
1955 		PAIR_CLASS_INIT(&tag[0], "link-ref");
1956 		break;
1957 	case MDOC__V:
1958 		PAIR_CLASS_INIT(&tag[0], "ref-vol");
1959 		break;
1960 	default:
1961 		abort();
1962 	}
1963 
1964 	if (MDOC__U != n->tok) {
1965 		print_otag(h, t, 1, tag);
1966 		return 1;
1967 	}
1968 
1969 	PAIR_HREF_INIT(&tag[1], n->child->string);
1970 	print_otag(h, TAG_A, 2, tag);
1971 
1972 	return 1;
1973 }
1974 
1975 static void
1976 mdoc__x_post(MDOC_ARGS)
1977 {
1978 
1979 	if (MDOC__A == n->tok && n->next && MDOC__A == n->next->tok)
1980 		if (NULL == n->next->next || MDOC__A != n->next->next->tok)
1981 			if (NULL == n->prev || MDOC__A != n->prev->tok)
1982 				return;
1983 
1984 	/* TODO: %U */
1985 
1986 	if (NULL == n->parent || MDOC_Rs != n->parent->tok)
1987 		return;
1988 
1989 	h->flags |= HTML_NOSPACE;
1990 	print_text(h, n->next ? "," : ".");
1991 }
1992 
1993 static int
1994 mdoc_bk_pre(MDOC_ARGS)
1995 {
1996 
1997 	switch (n->type) {
1998 	case ROFFT_BLOCK:
1999 		break;
2000 	case ROFFT_HEAD:
2001 		return 0;
2002 	case ROFFT_BODY:
2003 		if (n->parent->args != NULL || n->prev->child == NULL)
2004 			h->flags |= HTML_PREKEEP;
2005 		break;
2006 	default:
2007 		abort();
2008 	}
2009 
2010 	return 1;
2011 }
2012 
2013 static void
2014 mdoc_bk_post(MDOC_ARGS)
2015 {
2016 
2017 	if (n->type == ROFFT_BODY)
2018 		h->flags &= ~(HTML_KEEP | HTML_PREKEEP);
2019 }
2020 
2021 static int
2022 mdoc_quote_pre(MDOC_ARGS)
2023 {
2024 	struct htmlpair	tag;
2025 
2026 	if (n->type != ROFFT_BODY)
2027 		return 1;
2028 
2029 	switch (n->tok) {
2030 	case MDOC_Ao:
2031 	case MDOC_Aq:
2032 		print_text(h, n->child != NULL && n->child->next == NULL &&
2033 		    n->child->tok == MDOC_Mt ?  "<" : "\\(la");
2034 		break;
2035 	case MDOC_Bro:
2036 	case MDOC_Brq:
2037 		print_text(h, "\\(lC");
2038 		break;
2039 	case MDOC_Bo:
2040 	case MDOC_Bq:
2041 		print_text(h, "\\(lB");
2042 		break;
2043 	case MDOC_Oo:
2044 	case MDOC_Op:
2045 		print_text(h, "\\(lB");
2046 		h->flags |= HTML_NOSPACE;
2047 		PAIR_CLASS_INIT(&tag, "opt");
2048 		print_otag(h, TAG_SPAN, 1, &tag);
2049 		break;
2050 	case MDOC_En:
2051 		if (NULL == n->norm->Es ||
2052 		    NULL == n->norm->Es->child)
2053 			return 1;
2054 		print_text(h, n->norm->Es->child->string);
2055 		break;
2056 	case MDOC_Do:
2057 	case MDOC_Dq:
2058 	case MDOC_Qo:
2059 	case MDOC_Qq:
2060 		print_text(h, "\\(lq");
2061 		break;
2062 	case MDOC_Po:
2063 	case MDOC_Pq:
2064 		print_text(h, "(");
2065 		break;
2066 	case MDOC_Ql:
2067 		print_text(h, "\\(oq");
2068 		h->flags |= HTML_NOSPACE;
2069 		PAIR_CLASS_INIT(&tag, "lit");
2070 		print_otag(h, TAG_CODE, 1, &tag);
2071 		break;
2072 	case MDOC_So:
2073 	case MDOC_Sq:
2074 		print_text(h, "\\(oq");
2075 		break;
2076 	default:
2077 		abort();
2078 	}
2079 
2080 	h->flags |= HTML_NOSPACE;
2081 	return 1;
2082 }
2083 
2084 static void
2085 mdoc_quote_post(MDOC_ARGS)
2086 {
2087 
2088 	if (n->type != ROFFT_BODY && n->type != ROFFT_ELEM)
2089 		return;
2090 
2091 	h->flags |= HTML_NOSPACE;
2092 
2093 	switch (n->tok) {
2094 	case MDOC_Ao:
2095 	case MDOC_Aq:
2096 		print_text(h, n->child != NULL && n->child->next == NULL &&
2097 		    n->child->tok == MDOC_Mt ?  ">" : "\\(ra");
2098 		break;
2099 	case MDOC_Bro:
2100 	case MDOC_Brq:
2101 		print_text(h, "\\(rC");
2102 		break;
2103 	case MDOC_Oo:
2104 	case MDOC_Op:
2105 	case MDOC_Bo:
2106 	case MDOC_Bq:
2107 		print_text(h, "\\(rB");
2108 		break;
2109 	case MDOC_En:
2110 		if (n->norm->Es == NULL ||
2111 		    n->norm->Es->child == NULL ||
2112 		    n->norm->Es->child->next == NULL)
2113 			h->flags &= ~HTML_NOSPACE;
2114 		else
2115 			print_text(h, n->norm->Es->child->next->string);
2116 		break;
2117 	case MDOC_Qo:
2118 	case MDOC_Qq:
2119 	case MDOC_Do:
2120 	case MDOC_Dq:
2121 		print_text(h, "\\(rq");
2122 		break;
2123 	case MDOC_Po:
2124 	case MDOC_Pq:
2125 		print_text(h, ")");
2126 		break;
2127 	case MDOC_Ql:
2128 	case MDOC_So:
2129 	case MDOC_Sq:
2130 		print_text(h, "\\(cq");
2131 		break;
2132 	default:
2133 		abort();
2134 	}
2135 }
2136 
2137 static int
2138 mdoc_eo_pre(MDOC_ARGS)
2139 {
2140 
2141 	if (n->type != ROFFT_BODY)
2142 		return 1;
2143 
2144 	if (n->end == ENDBODY_NOT &&
2145 	    n->parent->head->child == NULL &&
2146 	    n->child != NULL &&
2147 	    n->child->end != ENDBODY_NOT)
2148 		print_text(h, "\\&");
2149 	else if (n->end != ENDBODY_NOT ? n->child != NULL :
2150 	    n->parent->head->child != NULL && (n->child != NULL ||
2151 	    (n->parent->tail != NULL && n->parent->tail->child != NULL)))
2152 		h->flags |= HTML_NOSPACE;
2153 	return 1;
2154 }
2155 
2156 static void
2157 mdoc_eo_post(MDOC_ARGS)
2158 {
2159 	int	 body, tail;
2160 
2161 	if (n->type != ROFFT_BODY)
2162 		return;
2163 
2164 	if (n->end != ENDBODY_NOT) {
2165 		h->flags &= ~HTML_NOSPACE;
2166 		return;
2167 	}
2168 
2169 	body = n->child != NULL || n->parent->head->child != NULL;
2170 	tail = n->parent->tail != NULL && n->parent->tail->child != NULL;
2171 
2172 	if (body && tail)
2173 		h->flags |= HTML_NOSPACE;
2174 	else if ( ! tail)
2175 		h->flags &= ~HTML_NOSPACE;
2176 }
2177