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