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