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