xref: /illumos-gate/usr/src/cmd/mandoc/mdoc_html.c (revision a6bde1a23b60f140c7ed78df979c2e22b1ed9b2c)
1 /*	$Id: mdoc_html.c,v 1.186 2013/12/24 20:45:27 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->lastline != n->line) :
464 		    (n->parent && n->parent->line != n->line)) {
465 			h->flags &= ~HTML_KEEP;
466 			h->flags |= HTML_PREKEEP;
467 		}
468 	}
469 
470 	if (child && n->child)
471 		print_mdoc_nodelist(meta, n->child, h);
472 
473 	print_stagq(h, t);
474 
475 	switch (n->type) {
476 	case (MDOC_ROOT):
477 		mdoc_root_post(meta, n, h);
478 		break;
479 	case (MDOC_EQN):
480 		break;
481 	default:
482 		if (mdocs[n->tok].post && ENDBODY_NOT == n->end)
483 			(*mdocs[n->tok].post)(meta, n, h);
484 		break;
485 	}
486 }
487 
488 /* ARGSUSED */
489 static void
490 mdoc_root_post(MDOC_ARGS)
491 {
492 	struct htmlpair	 tag[3];
493 	struct tag	*t, *tt;
494 
495 	PAIR_SUMMARY_INIT(&tag[0], "Document Footer");
496 	PAIR_CLASS_INIT(&tag[1], "foot");
497 	PAIR_INIT(&tag[2], ATTR_WIDTH, "100%");
498 	t = print_otag(h, TAG_TABLE, 3, tag);
499 	PAIR_INIT(&tag[0], ATTR_WIDTH, "50%");
500 	print_otag(h, TAG_COL, 1, tag);
501 	print_otag(h, TAG_COL, 1, tag);
502 
503 	print_otag(h, TAG_TBODY, 0, NULL);
504 
505 	tt = print_otag(h, TAG_TR, 0, NULL);
506 
507 	PAIR_CLASS_INIT(&tag[0], "foot-date");
508 	print_otag(h, TAG_TD, 1, tag);
509 	print_text(h, meta->date);
510 	print_stagq(h, tt);
511 
512 	PAIR_CLASS_INIT(&tag[0], "foot-os");
513 	PAIR_INIT(&tag[1], ATTR_ALIGN, "right");
514 	print_otag(h, TAG_TD, 2, tag);
515 	print_text(h, meta->os);
516 	print_tagq(h, t);
517 }
518 
519 
520 /* ARGSUSED */
521 static int
522 mdoc_root_pre(MDOC_ARGS)
523 {
524 	struct htmlpair	 tag[3];
525 	struct tag	*t, *tt;
526 	char		 b[BUFSIZ], title[BUFSIZ];
527 
528 	strlcpy(b, meta->vol, BUFSIZ);
529 
530 	if (meta->arch) {
531 		strlcat(b, " (", BUFSIZ);
532 		strlcat(b, meta->arch, BUFSIZ);
533 		strlcat(b, ")", BUFSIZ);
534 	}
535 
536 	snprintf(title, BUFSIZ - 1, "%s(%s)", meta->title, meta->msec);
537 
538 	PAIR_SUMMARY_INIT(&tag[0], "Document Header");
539 	PAIR_CLASS_INIT(&tag[1], "head");
540 	PAIR_INIT(&tag[2], ATTR_WIDTH, "100%");
541 	t = print_otag(h, TAG_TABLE, 3, tag);
542 	PAIR_INIT(&tag[0], ATTR_WIDTH, "30%");
543 	print_otag(h, TAG_COL, 1, tag);
544 	print_otag(h, TAG_COL, 1, tag);
545 	print_otag(h, TAG_COL, 1, tag);
546 
547 	print_otag(h, TAG_TBODY, 0, NULL);
548 
549 	tt = print_otag(h, TAG_TR, 0, NULL);
550 
551 	PAIR_CLASS_INIT(&tag[0], "head-ltitle");
552 	print_otag(h, TAG_TD, 1, tag);
553 	print_text(h, title);
554 	print_stagq(h, tt);
555 
556 	PAIR_CLASS_INIT(&tag[0], "head-vol");
557 	PAIR_INIT(&tag[1], ATTR_ALIGN, "center");
558 	print_otag(h, TAG_TD, 2, tag);
559 	print_text(h, b);
560 	print_stagq(h, tt);
561 
562 	PAIR_CLASS_INIT(&tag[0], "head-rtitle");
563 	PAIR_INIT(&tag[1], ATTR_ALIGN, "right");
564 	print_otag(h, TAG_TD, 2, tag);
565 	print_text(h, title);
566 	print_tagq(h, t);
567 	return(1);
568 }
569 
570 
571 /* ARGSUSED */
572 static int
573 mdoc_sh_pre(MDOC_ARGS)
574 {
575 	struct htmlpair	 tag;
576 
577 	if (MDOC_BLOCK == n->type) {
578 		PAIR_CLASS_INIT(&tag, "section");
579 		print_otag(h, TAG_DIV, 1, &tag);
580 		return(1);
581 	} else if (MDOC_BODY == n->type)
582 		return(1);
583 
584 	bufinit(h);
585 	bufcat(h, "x");
586 
587 	for (n = n->child; n && MDOC_TEXT == n->type; ) {
588 		bufcat_id(h, n->string);
589 		if (NULL != (n = n->next))
590 			bufcat_id(h, " ");
591 	}
592 
593 	if (NULL == n) {
594 		PAIR_ID_INIT(&tag, h->buf);
595 		print_otag(h, TAG_H1, 1, &tag);
596 	} else
597 		print_otag(h, TAG_H1, 0, NULL);
598 
599 	return(1);
600 }
601 
602 /* ARGSUSED */
603 static int
604 mdoc_ss_pre(MDOC_ARGS)
605 {
606 	struct htmlpair	 tag;
607 
608 	if (MDOC_BLOCK == n->type) {
609 		PAIR_CLASS_INIT(&tag, "subsection");
610 		print_otag(h, TAG_DIV, 1, &tag);
611 		return(1);
612 	} else if (MDOC_BODY == n->type)
613 		return(1);
614 
615 	bufinit(h);
616 	bufcat(h, "x");
617 
618 	for (n = n->child; n && MDOC_TEXT == n->type; ) {
619 		bufcat_id(h, n->string);
620 		if (NULL != (n = n->next))
621 			bufcat_id(h, " ");
622 	}
623 
624 	if (NULL == n) {
625 		PAIR_ID_INIT(&tag, h->buf);
626 		print_otag(h, TAG_H2, 1, &tag);
627 	} else
628 		print_otag(h, TAG_H2, 0, NULL);
629 
630 	return(1);
631 }
632 
633 
634 /* ARGSUSED */
635 static int
636 mdoc_fl_pre(MDOC_ARGS)
637 {
638 	struct htmlpair	 tag;
639 
640 	PAIR_CLASS_INIT(&tag, "flag");
641 	print_otag(h, TAG_B, 1, &tag);
642 
643 	/* `Cm' has no leading hyphen. */
644 
645 	if (MDOC_Cm == n->tok)
646 		return(1);
647 
648 	print_text(h, "\\-");
649 
650 	if (n->child)
651 		h->flags |= HTML_NOSPACE;
652 	else if (n->next && n->next->line == n->line)
653 		h->flags |= HTML_NOSPACE;
654 
655 	return(1);
656 }
657 
658 
659 /* ARGSUSED */
660 static int
661 mdoc_nd_pre(MDOC_ARGS)
662 {
663 	struct htmlpair	 tag;
664 
665 	if (MDOC_BODY != n->type)
666 		return(1);
667 
668 	/* XXX: this tag in theory can contain block elements. */
669 
670 	print_text(h, "\\(em");
671 	PAIR_CLASS_INIT(&tag, "desc");
672 	print_otag(h, TAG_SPAN, 1, &tag);
673 	return(1);
674 }
675 
676 
677 static int
678 mdoc_nm_pre(MDOC_ARGS)
679 {
680 	struct htmlpair	 tag;
681 	struct roffsu	 su;
682 	int		 len;
683 
684 	switch (n->type) {
685 	case (MDOC_ELEM):
686 		synopsis_pre(h, n);
687 		PAIR_CLASS_INIT(&tag, "name");
688 		print_otag(h, TAG_B, 1, &tag);
689 		if (NULL == n->child && meta->name)
690 			print_text(h, meta->name);
691 		return(1);
692 	case (MDOC_HEAD):
693 		print_otag(h, TAG_TD, 0, NULL);
694 		if (NULL == n->child && meta->name)
695 			print_text(h, meta->name);
696 		return(1);
697 	case (MDOC_BODY):
698 		print_otag(h, TAG_TD, 0, NULL);
699 		return(1);
700 	default:
701 		break;
702 	}
703 
704 	synopsis_pre(h, n);
705 	PAIR_CLASS_INIT(&tag, "synopsis");
706 	print_otag(h, TAG_TABLE, 1, &tag);
707 
708 	for (len = 0, n = n->child; n; n = n->next)
709 		if (MDOC_TEXT == n->type)
710 			len += html_strlen(n->string);
711 
712 	if (0 == len && meta->name)
713 		len = html_strlen(meta->name);
714 
715 	SCALE_HS_INIT(&su, (double)len);
716 	bufinit(h);
717 	bufcat_su(h, "width", &su);
718 	PAIR_STYLE_INIT(&tag, h);
719 	print_otag(h, TAG_COL, 1, &tag);
720 	print_otag(h, TAG_COL, 0, NULL);
721 	print_otag(h, TAG_TBODY, 0, NULL);
722 	print_otag(h, TAG_TR, 0, NULL);
723 	return(1);
724 }
725 
726 
727 /* ARGSUSED */
728 static int
729 mdoc_xr_pre(MDOC_ARGS)
730 {
731 	struct htmlpair	 tag[2];
732 
733 	if (NULL == n->child)
734 		return(0);
735 
736 	PAIR_CLASS_INIT(&tag[0], "link-man");
737 
738 	if (h->base_man) {
739 		buffmt_man(h, n->child->string,
740 				n->child->next ?
741 				n->child->next->string : NULL);
742 		PAIR_HREF_INIT(&tag[1], h->buf);
743 		print_otag(h, TAG_A, 2, tag);
744 	} else
745 		print_otag(h, TAG_A, 1, tag);
746 
747 	n = n->child;
748 	print_text(h, n->string);
749 
750 	if (NULL == (n = n->next))
751 		return(0);
752 
753 	h->flags |= HTML_NOSPACE;
754 	print_text(h, "(");
755 	h->flags |= HTML_NOSPACE;
756 	print_text(h, n->string);
757 	h->flags |= HTML_NOSPACE;
758 	print_text(h, ")");
759 	return(0);
760 }
761 
762 
763 /* ARGSUSED */
764 static int
765 mdoc_ns_pre(MDOC_ARGS)
766 {
767 
768 	if ( ! (MDOC_LINE & n->flags))
769 		h->flags |= HTML_NOSPACE;
770 	return(1);
771 }
772 
773 
774 /* ARGSUSED */
775 static int
776 mdoc_ar_pre(MDOC_ARGS)
777 {
778 	struct htmlpair tag;
779 
780 	PAIR_CLASS_INIT(&tag, "arg");
781 	print_otag(h, TAG_I, 1, &tag);
782 	return(1);
783 }
784 
785 
786 /* ARGSUSED */
787 static int
788 mdoc_xx_pre(MDOC_ARGS)
789 {
790 	const char	*pp;
791 	struct htmlpair	 tag;
792 	int		 flags;
793 
794 	switch (n->tok) {
795 	case (MDOC_Bsx):
796 		pp = "BSD/OS";
797 		break;
798 	case (MDOC_Dx):
799 		pp = "DragonFly";
800 		break;
801 	case (MDOC_Fx):
802 		pp = "FreeBSD";
803 		break;
804 	case (MDOC_Nx):
805 		pp = "NetBSD";
806 		break;
807 	case (MDOC_Ox):
808 		pp = "OpenBSD";
809 		break;
810 	case (MDOC_Ux):
811 		pp = "UNIX";
812 		break;
813 	default:
814 		return(1);
815 	}
816 
817 	PAIR_CLASS_INIT(&tag, "unix");
818 	print_otag(h, TAG_SPAN, 1, &tag);
819 
820 	print_text(h, pp);
821 	if (n->child) {
822 		flags = h->flags;
823 		h->flags |= HTML_KEEP;
824 		print_text(h, n->child->string);
825 		h->flags = flags;
826 	}
827 	return(0);
828 }
829 
830 
831 /* ARGSUSED */
832 static int
833 mdoc_bx_pre(MDOC_ARGS)
834 {
835 	struct htmlpair	 tag;
836 
837 	PAIR_CLASS_INIT(&tag, "unix");
838 	print_otag(h, TAG_SPAN, 1, &tag);
839 
840 	if (NULL != (n = n->child)) {
841 		print_text(h, n->string);
842 		h->flags |= HTML_NOSPACE;
843 		print_text(h, "BSD");
844 	} else {
845 		print_text(h, "BSD");
846 		return(0);
847 	}
848 
849 	if (NULL != (n = n->next)) {
850 		h->flags |= HTML_NOSPACE;
851 		print_text(h, "-");
852 		h->flags |= HTML_NOSPACE;
853 		print_text(h, n->string);
854 	}
855 
856 	return(0);
857 }
858 
859 /* ARGSUSED */
860 static int
861 mdoc_it_pre(MDOC_ARGS)
862 {
863 	struct roffsu	 su;
864 	enum mdoc_list	 type;
865 	struct htmlpair	 tag[2];
866 	const struct mdoc_node *bl;
867 
868 	bl = n->parent;
869 	while (bl && MDOC_Bl != bl->tok)
870 		bl = bl->parent;
871 
872 	assert(bl);
873 
874 	type = bl->norm->Bl.type;
875 
876 	assert(lists[type]);
877 	PAIR_CLASS_INIT(&tag[0], lists[type]);
878 
879 	bufinit(h);
880 
881 	if (MDOC_HEAD == n->type) {
882 		switch (type) {
883 		case(LIST_bullet):
884 			/* FALLTHROUGH */
885 		case(LIST_dash):
886 			/* FALLTHROUGH */
887 		case(LIST_item):
888 			/* FALLTHROUGH */
889 		case(LIST_hyphen):
890 			/* FALLTHROUGH */
891 		case(LIST_enum):
892 			return(0);
893 		case(LIST_diag):
894 			/* FALLTHROUGH */
895 		case(LIST_hang):
896 			/* FALLTHROUGH */
897 		case(LIST_inset):
898 			/* FALLTHROUGH */
899 		case(LIST_ohang):
900 			/* FALLTHROUGH */
901 		case(LIST_tag):
902 			SCALE_VS_INIT(&su, ! bl->norm->Bl.comp);
903 			bufcat_su(h, "margin-top", &su);
904 			PAIR_STYLE_INIT(&tag[1], h);
905 			print_otag(h, TAG_DT, 2, tag);
906 			if (LIST_diag != type)
907 				break;
908 			PAIR_CLASS_INIT(&tag[0], "diag");
909 			print_otag(h, TAG_B, 1, tag);
910 			break;
911 		case(LIST_column):
912 			break;
913 		default:
914 			break;
915 		}
916 	} else if (MDOC_BODY == n->type) {
917 		switch (type) {
918 		case(LIST_bullet):
919 			/* FALLTHROUGH */
920 		case(LIST_hyphen):
921 			/* FALLTHROUGH */
922 		case(LIST_dash):
923 			/* FALLTHROUGH */
924 		case(LIST_enum):
925 			/* FALLTHROUGH */
926 		case(LIST_item):
927 			SCALE_VS_INIT(&su, ! bl->norm->Bl.comp);
928 			bufcat_su(h, "margin-top", &su);
929 			PAIR_STYLE_INIT(&tag[1], h);
930 			print_otag(h, TAG_LI, 2, tag);
931 			break;
932 		case(LIST_diag):
933 			/* FALLTHROUGH */
934 		case(LIST_hang):
935 			/* FALLTHROUGH */
936 		case(LIST_inset):
937 			/* FALLTHROUGH */
938 		case(LIST_ohang):
939 			/* FALLTHROUGH */
940 		case(LIST_tag):
941 			if (NULL == bl->norm->Bl.width) {
942 				print_otag(h, TAG_DD, 1, tag);
943 				break;
944 			}
945 			a2width(bl->norm->Bl.width, &su);
946 			bufcat_su(h, "margin-left", &su);
947 			PAIR_STYLE_INIT(&tag[1], h);
948 			print_otag(h, TAG_DD, 2, tag);
949 			break;
950 		case(LIST_column):
951 			SCALE_VS_INIT(&su, ! bl->norm->Bl.comp);
952 			bufcat_su(h, "margin-top", &su);
953 			PAIR_STYLE_INIT(&tag[1], h);
954 			print_otag(h, TAG_TD, 2, tag);
955 			break;
956 		default:
957 			break;
958 		}
959 	} else {
960 		switch (type) {
961 		case (LIST_column):
962 			print_otag(h, TAG_TR, 1, tag);
963 			break;
964 		default:
965 			break;
966 		}
967 	}
968 
969 	return(1);
970 }
971 
972 /* ARGSUSED */
973 static int
974 mdoc_bl_pre(MDOC_ARGS)
975 {
976 	int		 i;
977 	struct htmlpair	 tag[3];
978 	struct roffsu	 su;
979 	char		 buf[BUFSIZ];
980 
981 	if (MDOC_BODY == n->type) {
982 		if (LIST_column == n->norm->Bl.type)
983 			print_otag(h, TAG_TBODY, 0, NULL);
984 		return(1);
985 	}
986 
987 	if (MDOC_HEAD == n->type) {
988 		if (LIST_column != n->norm->Bl.type)
989 			return(0);
990 
991 		/*
992 		 * For each column, print out the <COL> tag with our
993 		 * suggested width.  The last column gets min-width, as
994 		 * in terminal mode it auto-sizes to the width of the
995 		 * screen and we want to preserve that behaviour.
996 		 */
997 
998 		for (i = 0; i < (int)n->norm->Bl.ncols; i++) {
999 			bufinit(h);
1000 			a2width(n->norm->Bl.cols[i], &su);
1001 			if (i < (int)n->norm->Bl.ncols - 1)
1002 				bufcat_su(h, "width", &su);
1003 			else
1004 				bufcat_su(h, "min-width", &su);
1005 			PAIR_STYLE_INIT(&tag[0], h);
1006 			print_otag(h, TAG_COL, 1, tag);
1007 		}
1008 
1009 		return(0);
1010 	}
1011 
1012 	SCALE_VS_INIT(&su, 0);
1013 	bufinit(h);
1014 	bufcat_su(h, "margin-top", &su);
1015 	bufcat_su(h, "margin-bottom", &su);
1016 	PAIR_STYLE_INIT(&tag[0], h);
1017 
1018 	assert(lists[n->norm->Bl.type]);
1019 	strlcpy(buf, "list ", BUFSIZ);
1020 	strlcat(buf, lists[n->norm->Bl.type], BUFSIZ);
1021 	PAIR_INIT(&tag[1], ATTR_CLASS, buf);
1022 
1023 	/* Set the block's left-hand margin. */
1024 
1025 	if (n->norm->Bl.offs) {
1026 		a2offs(n->norm->Bl.offs, &su);
1027 		bufcat_su(h, "margin-left", &su);
1028 	}
1029 
1030 	switch (n->norm->Bl.type) {
1031 	case(LIST_bullet):
1032 		/* FALLTHROUGH */
1033 	case(LIST_dash):
1034 		/* FALLTHROUGH */
1035 	case(LIST_hyphen):
1036 		/* FALLTHROUGH */
1037 	case(LIST_item):
1038 		print_otag(h, TAG_UL, 2, tag);
1039 		break;
1040 	case(LIST_enum):
1041 		print_otag(h, TAG_OL, 2, tag);
1042 		break;
1043 	case(LIST_diag):
1044 		/* FALLTHROUGH */
1045 	case(LIST_hang):
1046 		/* FALLTHROUGH */
1047 	case(LIST_inset):
1048 		/* FALLTHROUGH */
1049 	case(LIST_ohang):
1050 		/* FALLTHROUGH */
1051 	case(LIST_tag):
1052 		print_otag(h, TAG_DL, 2, tag);
1053 		break;
1054 	case(LIST_column):
1055 		print_otag(h, TAG_TABLE, 2, tag);
1056 		break;
1057 	default:
1058 		abort();
1059 		/* NOTREACHED */
1060 	}
1061 
1062 	return(1);
1063 }
1064 
1065 /* ARGSUSED */
1066 static int
1067 mdoc_ex_pre(MDOC_ARGS)
1068 {
1069 	struct tag	*t;
1070 	struct htmlpair	 tag;
1071 	int		 nchild;
1072 
1073 	if (n->prev)
1074 		print_otag(h, TAG_BR, 0, NULL);
1075 
1076 	PAIR_CLASS_INIT(&tag, "utility");
1077 
1078 	print_text(h, "The");
1079 
1080 	nchild = n->nchild;
1081 	for (n = n->child; n; n = n->next) {
1082 		assert(MDOC_TEXT == n->type);
1083 
1084 		t = print_otag(h, TAG_B, 1, &tag);
1085 		print_text(h, n->string);
1086 		print_tagq(h, t);
1087 
1088 		if (nchild > 2 && n->next) {
1089 			h->flags |= HTML_NOSPACE;
1090 			print_text(h, ",");
1091 		}
1092 
1093 		if (n->next && NULL == n->next->next)
1094 			print_text(h, "and");
1095 	}
1096 
1097 	if (nchild > 1)
1098 		print_text(h, "utilities exit");
1099 	else
1100 		print_text(h, "utility exits");
1101 
1102        	print_text(h, "0 on success, and >0 if an error occurs.");
1103 	return(0);
1104 }
1105 
1106 
1107 /* ARGSUSED */
1108 static int
1109 mdoc_em_pre(MDOC_ARGS)
1110 {
1111 	struct htmlpair	tag;
1112 
1113 	PAIR_CLASS_INIT(&tag, "emph");
1114 	print_otag(h, TAG_SPAN, 1, &tag);
1115 	return(1);
1116 }
1117 
1118 
1119 /* ARGSUSED */
1120 static int
1121 mdoc_d1_pre(MDOC_ARGS)
1122 {
1123 	struct htmlpair	 tag[2];
1124 	struct roffsu	 su;
1125 
1126 	if (MDOC_BLOCK != n->type)
1127 		return(1);
1128 
1129 	SCALE_VS_INIT(&su, 0);
1130 	bufinit(h);
1131 	bufcat_su(h, "margin-top", &su);
1132 	bufcat_su(h, "margin-bottom", &su);
1133 	PAIR_STYLE_INIT(&tag[0], h);
1134 	print_otag(h, TAG_BLOCKQUOTE, 1, tag);
1135 
1136 	/* BLOCKQUOTE needs a block body. */
1137 
1138 	PAIR_CLASS_INIT(&tag[0], "display");
1139 	print_otag(h, TAG_DIV, 1, tag);
1140 
1141 	if (MDOC_Dl == n->tok) {
1142 		PAIR_CLASS_INIT(&tag[0], "lit");
1143 		print_otag(h, TAG_CODE, 1, tag);
1144 	}
1145 
1146 	return(1);
1147 }
1148 
1149 
1150 /* ARGSUSED */
1151 static int
1152 mdoc_sx_pre(MDOC_ARGS)
1153 {
1154 	struct htmlpair	 tag[2];
1155 
1156 	bufinit(h);
1157 	bufcat(h, "#x");
1158 
1159 	for (n = n->child; n; ) {
1160 		bufcat_id(h, n->string);
1161 		if (NULL != (n = n->next))
1162 			bufcat_id(h, " ");
1163 	}
1164 
1165 	PAIR_CLASS_INIT(&tag[0], "link-sec");
1166 	PAIR_HREF_INIT(&tag[1], h->buf);
1167 
1168 	print_otag(h, TAG_I, 1, tag);
1169 	print_otag(h, TAG_A, 2, tag);
1170 	return(1);
1171 }
1172 
1173 
1174 /* ARGSUSED */
1175 static int
1176 mdoc_bd_pre(MDOC_ARGS)
1177 {
1178 	struct htmlpair	 	 tag[2];
1179 	int		 	 comp, sv;
1180 	const struct mdoc_node	*nn;
1181 	struct roffsu		 su;
1182 
1183 	if (MDOC_HEAD == n->type)
1184 		return(0);
1185 
1186 	if (MDOC_BLOCK == n->type) {
1187 		comp = n->norm->Bd.comp;
1188 		for (nn = n; nn && ! comp; nn = nn->parent) {
1189 			if (MDOC_BLOCK != nn->type)
1190 				continue;
1191 			if (MDOC_Ss == nn->tok || MDOC_Sh == nn->tok)
1192 				comp = 1;
1193 			if (nn->prev)
1194 				break;
1195 		}
1196 		if ( ! comp)
1197 			print_otag(h, TAG_P, 0, NULL);
1198 		return(1);
1199 	}
1200 
1201 	SCALE_HS_INIT(&su, 0);
1202 	if (n->norm->Bd.offs)
1203 		a2offs(n->norm->Bd.offs, &su);
1204 
1205 	bufinit(h);
1206 	bufcat_su(h, "margin-left", &su);
1207 	PAIR_STYLE_INIT(&tag[0], h);
1208 
1209 	if (DISP_unfilled != n->norm->Bd.type &&
1210 			DISP_literal != n->norm->Bd.type) {
1211 		PAIR_CLASS_INIT(&tag[1], "display");
1212 		print_otag(h, TAG_DIV, 2, tag);
1213 		return(1);
1214 	}
1215 
1216 	PAIR_CLASS_INIT(&tag[1], "lit display");
1217 	print_otag(h, TAG_PRE, 2, tag);
1218 
1219 	/* This can be recursive: save & set our literal state. */
1220 
1221 	sv = h->flags & HTML_LITERAL;
1222 	h->flags |= HTML_LITERAL;
1223 
1224 	for (nn = n->child; nn; nn = nn->next) {
1225 		print_mdoc_node(meta, nn, h);
1226 		/*
1227 		 * If the printed node flushes its own line, then we
1228 		 * needn't do it here as well.  This is hacky, but the
1229 		 * notion of selective eoln whitespace is pretty dumb
1230 		 * anyway, so don't sweat it.
1231 		 */
1232 		switch (nn->tok) {
1233 		case (MDOC_Sm):
1234 			/* FALLTHROUGH */
1235 		case (MDOC_br):
1236 			/* FALLTHROUGH */
1237 		case (MDOC_sp):
1238 			/* FALLTHROUGH */
1239 		case (MDOC_Bl):
1240 			/* FALLTHROUGH */
1241 		case (MDOC_D1):
1242 			/* FALLTHROUGH */
1243 		case (MDOC_Dl):
1244 			/* FALLTHROUGH */
1245 		case (MDOC_Lp):
1246 			/* FALLTHROUGH */
1247 		case (MDOC_Pp):
1248 			continue;
1249 		default:
1250 			break;
1251 		}
1252 		if (nn->next && nn->next->line == nn->line)
1253 			continue;
1254 		else if (nn->next)
1255 			print_text(h, "\n");
1256 
1257 		h->flags |= HTML_NOSPACE;
1258 	}
1259 
1260 	if (0 == sv)
1261 		h->flags &= ~HTML_LITERAL;
1262 
1263 	return(0);
1264 }
1265 
1266 
1267 /* ARGSUSED */
1268 static int
1269 mdoc_pa_pre(MDOC_ARGS)
1270 {
1271 	struct htmlpair	tag;
1272 
1273 	PAIR_CLASS_INIT(&tag, "file");
1274 	print_otag(h, TAG_I, 1, &tag);
1275 	return(1);
1276 }
1277 
1278 
1279 /* ARGSUSED */
1280 static int
1281 mdoc_ad_pre(MDOC_ARGS)
1282 {
1283 	struct htmlpair	tag;
1284 
1285 	PAIR_CLASS_INIT(&tag, "addr");
1286 	print_otag(h, TAG_I, 1, &tag);
1287 	return(1);
1288 }
1289 
1290 
1291 /* ARGSUSED */
1292 static int
1293 mdoc_an_pre(MDOC_ARGS)
1294 {
1295 	struct htmlpair	tag;
1296 
1297 	/* TODO: -split and -nosplit (see termp_an_pre()). */
1298 
1299 	PAIR_CLASS_INIT(&tag, "author");
1300 	print_otag(h, TAG_SPAN, 1, &tag);
1301 	return(1);
1302 }
1303 
1304 
1305 /* ARGSUSED */
1306 static int
1307 mdoc_cd_pre(MDOC_ARGS)
1308 {
1309 	struct htmlpair	tag;
1310 
1311 	synopsis_pre(h, n);
1312 	PAIR_CLASS_INIT(&tag, "config");
1313 	print_otag(h, TAG_B, 1, &tag);
1314 	return(1);
1315 }
1316 
1317 
1318 /* ARGSUSED */
1319 static int
1320 mdoc_dv_pre(MDOC_ARGS)
1321 {
1322 	struct htmlpair	tag;
1323 
1324 	PAIR_CLASS_INIT(&tag, "define");
1325 	print_otag(h, TAG_SPAN, 1, &tag);
1326 	return(1);
1327 }
1328 
1329 
1330 /* ARGSUSED */
1331 static int
1332 mdoc_ev_pre(MDOC_ARGS)
1333 {
1334 	struct htmlpair	tag;
1335 
1336 	PAIR_CLASS_INIT(&tag, "env");
1337 	print_otag(h, TAG_SPAN, 1, &tag);
1338 	return(1);
1339 }
1340 
1341 
1342 /* ARGSUSED */
1343 static int
1344 mdoc_er_pre(MDOC_ARGS)
1345 {
1346 	struct htmlpair	tag;
1347 
1348 	PAIR_CLASS_INIT(&tag, "errno");
1349 	print_otag(h, TAG_SPAN, 1, &tag);
1350 	return(1);
1351 }
1352 
1353 
1354 /* ARGSUSED */
1355 static int
1356 mdoc_fa_pre(MDOC_ARGS)
1357 {
1358 	const struct mdoc_node	*nn;
1359 	struct htmlpair		 tag;
1360 	struct tag		*t;
1361 
1362 	PAIR_CLASS_INIT(&tag, "farg");
1363 	if (n->parent->tok != MDOC_Fo) {
1364 		print_otag(h, TAG_I, 1, &tag);
1365 		return(1);
1366 	}
1367 
1368 	for (nn = n->child; nn; nn = nn->next) {
1369 		t = print_otag(h, TAG_I, 1, &tag);
1370 		print_text(h, nn->string);
1371 		print_tagq(h, t);
1372 		if (nn->next) {
1373 			h->flags |= HTML_NOSPACE;
1374 			print_text(h, ",");
1375 		}
1376 	}
1377 
1378 	if (n->child && n->next && n->next->tok == MDOC_Fa) {
1379 		h->flags |= HTML_NOSPACE;
1380 		print_text(h, ",");
1381 	}
1382 
1383 	return(0);
1384 }
1385 
1386 
1387 /* ARGSUSED */
1388 static int
1389 mdoc_fd_pre(MDOC_ARGS)
1390 {
1391 	struct htmlpair	 tag[2];
1392 	char		 buf[BUFSIZ];
1393 	size_t		 sz;
1394 	int		 i;
1395 	struct tag	*t;
1396 
1397 	synopsis_pre(h, n);
1398 
1399 	if (NULL == (n = n->child))
1400 		return(0);
1401 
1402 	assert(MDOC_TEXT == n->type);
1403 
1404 	if (strcmp(n->string, "#include")) {
1405 		PAIR_CLASS_INIT(&tag[0], "macro");
1406 		print_otag(h, TAG_B, 1, tag);
1407 		return(1);
1408 	}
1409 
1410 	PAIR_CLASS_INIT(&tag[0], "includes");
1411 	print_otag(h, TAG_B, 1, tag);
1412 	print_text(h, n->string);
1413 
1414 	if (NULL != (n = n->next)) {
1415 		assert(MDOC_TEXT == n->type);
1416 		strlcpy(buf, '<' == *n->string || '"' == *n->string ?
1417 				n->string + 1 : n->string, BUFSIZ);
1418 
1419 		sz = strlen(buf);
1420 		if (sz && ('>' == buf[sz - 1] || '"' == buf[sz - 1]))
1421 			buf[sz - 1] = '\0';
1422 
1423 		PAIR_CLASS_INIT(&tag[0], "link-includes");
1424 
1425 		i = 1;
1426 		if (h->base_includes) {
1427 			buffmt_includes(h, buf);
1428 			PAIR_HREF_INIT(&tag[i], h->buf);
1429 			i++;
1430 		}
1431 
1432 		t = print_otag(h, TAG_A, i, tag);
1433 		print_text(h, n->string);
1434 		print_tagq(h, t);
1435 
1436 		n = n->next;
1437 	}
1438 
1439 	for ( ; n; n = n->next) {
1440 		assert(MDOC_TEXT == n->type);
1441 		print_text(h, n->string);
1442 	}
1443 
1444 	return(0);
1445 }
1446 
1447 
1448 /* ARGSUSED */
1449 static int
1450 mdoc_vt_pre(MDOC_ARGS)
1451 {
1452 	struct htmlpair	 tag;
1453 
1454 	if (MDOC_BLOCK == n->type) {
1455 		synopsis_pre(h, n);
1456 		return(1);
1457 	} else if (MDOC_ELEM == n->type) {
1458 		synopsis_pre(h, n);
1459 	} else if (MDOC_HEAD == n->type)
1460 		return(0);
1461 
1462 	PAIR_CLASS_INIT(&tag, "type");
1463 	print_otag(h, TAG_SPAN, 1, &tag);
1464 	return(1);
1465 }
1466 
1467 
1468 /* ARGSUSED */
1469 static int
1470 mdoc_ft_pre(MDOC_ARGS)
1471 {
1472 	struct htmlpair	 tag;
1473 
1474 	synopsis_pre(h, n);
1475 	PAIR_CLASS_INIT(&tag, "ftype");
1476 	print_otag(h, TAG_I, 1, &tag);
1477 	return(1);
1478 }
1479 
1480 
1481 /* ARGSUSED */
1482 static int
1483 mdoc_fn_pre(MDOC_ARGS)
1484 {
1485 	struct tag	*t;
1486 	struct htmlpair	 tag[2];
1487 	char		 nbuf[BUFSIZ];
1488 	const char	*sp, *ep;
1489 	int		 sz, i, pretty;
1490 
1491 	pretty = MDOC_SYNPRETTY & n->flags;
1492 	synopsis_pre(h, n);
1493 
1494 	/* Split apart into type and name. */
1495 	assert(n->child->string);
1496 	sp = n->child->string;
1497 
1498 	ep = strchr(sp, ' ');
1499 	if (NULL != ep) {
1500 		PAIR_CLASS_INIT(&tag[0], "ftype");
1501 		t = print_otag(h, TAG_I, 1, tag);
1502 
1503 		while (ep) {
1504 			sz = MIN((int)(ep - sp), BUFSIZ - 1);
1505 			(void)memcpy(nbuf, sp, (size_t)sz);
1506 			nbuf[sz] = '\0';
1507 			print_text(h, nbuf);
1508 			sp = ++ep;
1509 			ep = strchr(sp, ' ');
1510 		}
1511 		print_tagq(h, t);
1512 	}
1513 
1514 	PAIR_CLASS_INIT(&tag[0], "fname");
1515 
1516 	/*
1517 	 * FIXME: only refer to IDs that we know exist.
1518 	 */
1519 
1520 #if 0
1521 	if (MDOC_SYNPRETTY & n->flags) {
1522 		nbuf[0] = '\0';
1523 		html_idcat(nbuf, sp, BUFSIZ);
1524 		PAIR_ID_INIT(&tag[1], nbuf);
1525 	} else {
1526 		strlcpy(nbuf, "#", BUFSIZ);
1527 		html_idcat(nbuf, sp, BUFSIZ);
1528 		PAIR_HREF_INIT(&tag[1], nbuf);
1529 	}
1530 #endif
1531 
1532 	t = print_otag(h, TAG_B, 1, tag);
1533 
1534 	if (sp) {
1535 		strlcpy(nbuf, sp, BUFSIZ);
1536 		print_text(h, nbuf);
1537 	}
1538 
1539 	print_tagq(h, t);
1540 
1541 	h->flags |= HTML_NOSPACE;
1542 	print_text(h, "(");
1543 	h->flags |= HTML_NOSPACE;
1544 
1545 	PAIR_CLASS_INIT(&tag[0], "farg");
1546 	bufinit(h);
1547 	bufcat_style(h, "white-space", "nowrap");
1548 	PAIR_STYLE_INIT(&tag[1], h);
1549 
1550 	for (n = n->child->next; n; n = n->next) {
1551 		i = 1;
1552 		if (MDOC_SYNPRETTY & n->flags)
1553 			i = 2;
1554 		t = print_otag(h, TAG_I, i, tag);
1555 		print_text(h, n->string);
1556 		print_tagq(h, t);
1557 		if (n->next) {
1558 			h->flags |= HTML_NOSPACE;
1559 			print_text(h, ",");
1560 		}
1561 	}
1562 
1563 	h->flags |= HTML_NOSPACE;
1564 	print_text(h, ")");
1565 
1566 	if (pretty) {
1567 		h->flags |= HTML_NOSPACE;
1568 		print_text(h, ";");
1569 	}
1570 
1571 	return(0);
1572 }
1573 
1574 
1575 /* ARGSUSED */
1576 static int
1577 mdoc_sm_pre(MDOC_ARGS)
1578 {
1579 
1580 	assert(n->child && MDOC_TEXT == n->child->type);
1581 	if (0 == strcmp("on", n->child->string)) {
1582 		/*
1583 		 * FIXME: no p->col to check.  Thus, if we have
1584 		 *  .Bd -literal
1585 		 *  .Sm off
1586 		 *  1 2
1587 		 *  .Sm on
1588 		 *  3
1589 		 *  .Ed
1590 		 * the "3" is preceded by a space.
1591 		 */
1592 		h->flags &= ~HTML_NOSPACE;
1593 		h->flags &= ~HTML_NONOSPACE;
1594 	} else
1595 		h->flags |= HTML_NONOSPACE;
1596 
1597 	return(0);
1598 }
1599 
1600 /* ARGSUSED */
1601 static int
1602 mdoc_pp_pre(MDOC_ARGS)
1603 {
1604 
1605 	print_otag(h, TAG_P, 0, NULL);
1606 	return(0);
1607 
1608 }
1609 
1610 /* ARGSUSED */
1611 static int
1612 mdoc_sp_pre(MDOC_ARGS)
1613 {
1614 	struct roffsu	 su;
1615 	struct htmlpair	 tag;
1616 
1617 	SCALE_VS_INIT(&su, 1);
1618 
1619 	if (MDOC_sp == n->tok) {
1620 		if (NULL != (n = n->child))
1621 			if ( ! a2roffsu(n->string, &su, SCALE_VS))
1622 				SCALE_VS_INIT(&su, atoi(n->string));
1623 	} else
1624 		su.scale = 0;
1625 
1626 	bufinit(h);
1627 	bufcat_su(h, "height", &su);
1628 	PAIR_STYLE_INIT(&tag, h);
1629 	print_otag(h, TAG_DIV, 1, &tag);
1630 
1631 	/* So the div isn't empty: */
1632 	print_text(h, "\\~");
1633 
1634 	return(0);
1635 
1636 }
1637 
1638 /* ARGSUSED */
1639 static int
1640 mdoc_lk_pre(MDOC_ARGS)
1641 {
1642 	struct htmlpair	 tag[2];
1643 
1644 	if (NULL == (n = n->child))
1645 		return(0);
1646 
1647 	assert(MDOC_TEXT == n->type);
1648 
1649 	PAIR_CLASS_INIT(&tag[0], "link-ext");
1650 	PAIR_HREF_INIT(&tag[1], n->string);
1651 
1652 	print_otag(h, TAG_A, 2, tag);
1653 
1654 	if (NULL == n->next)
1655 		print_text(h, n->string);
1656 
1657 	for (n = n->next; n; n = n->next)
1658 		print_text(h, n->string);
1659 
1660 	return(0);
1661 }
1662 
1663 
1664 /* ARGSUSED */
1665 static int
1666 mdoc_mt_pre(MDOC_ARGS)
1667 {
1668 	struct htmlpair	 tag[2];
1669 	struct tag	*t;
1670 
1671 	PAIR_CLASS_INIT(&tag[0], "link-mail");
1672 
1673 	for (n = n->child; n; n = n->next) {
1674 		assert(MDOC_TEXT == n->type);
1675 
1676 		bufinit(h);
1677 		bufcat(h, "mailto:");
1678 		bufcat(h, n->string);
1679 
1680 		PAIR_HREF_INIT(&tag[1], h->buf);
1681 		t = print_otag(h, TAG_A, 2, tag);
1682 		print_text(h, n->string);
1683 		print_tagq(h, t);
1684 	}
1685 
1686 	return(0);
1687 }
1688 
1689 
1690 /* ARGSUSED */
1691 static int
1692 mdoc_fo_pre(MDOC_ARGS)
1693 {
1694 	struct htmlpair	 tag;
1695 	struct tag	*t;
1696 
1697 	if (MDOC_BODY == n->type) {
1698 		h->flags |= HTML_NOSPACE;
1699 		print_text(h, "(");
1700 		h->flags |= HTML_NOSPACE;
1701 		return(1);
1702 	} else if (MDOC_BLOCK == n->type) {
1703 		synopsis_pre(h, n);
1704 		return(1);
1705 	}
1706 
1707 	/* XXX: we drop non-initial arguments as per groff. */
1708 
1709 	assert(n->child);
1710 	assert(n->child->string);
1711 
1712 	PAIR_CLASS_INIT(&tag, "fname");
1713 	t = print_otag(h, TAG_B, 1, &tag);
1714 	print_text(h, n->child->string);
1715 	print_tagq(h, t);
1716 	return(0);
1717 }
1718 
1719 
1720 /* ARGSUSED */
1721 static void
1722 mdoc_fo_post(MDOC_ARGS)
1723 {
1724 
1725 	if (MDOC_BODY != n->type)
1726 		return;
1727 	h->flags |= HTML_NOSPACE;
1728 	print_text(h, ")");
1729 	h->flags |= HTML_NOSPACE;
1730 	print_text(h, ";");
1731 }
1732 
1733 
1734 /* ARGSUSED */
1735 static int
1736 mdoc_in_pre(MDOC_ARGS)
1737 {
1738 	struct tag	*t;
1739 	struct htmlpair	 tag[2];
1740 	int		 i;
1741 
1742 	synopsis_pre(h, n);
1743 
1744 	PAIR_CLASS_INIT(&tag[0], "includes");
1745 	print_otag(h, TAG_B, 1, tag);
1746 
1747 	/*
1748 	 * The first argument of the `In' gets special treatment as
1749 	 * being a linked value.  Subsequent values are printed
1750 	 * afterward.  groff does similarly.  This also handles the case
1751 	 * of no children.
1752 	 */
1753 
1754 	if (MDOC_SYNPRETTY & n->flags && MDOC_LINE & n->flags)
1755 		print_text(h, "#include");
1756 
1757 	print_text(h, "<");
1758 	h->flags |= HTML_NOSPACE;
1759 
1760 	if (NULL != (n = n->child)) {
1761 		assert(MDOC_TEXT == n->type);
1762 
1763 		PAIR_CLASS_INIT(&tag[0], "link-includes");
1764 
1765 		i = 1;
1766 		if (h->base_includes) {
1767 			buffmt_includes(h, n->string);
1768 			PAIR_HREF_INIT(&tag[i], h->buf);
1769 			i++;
1770 		}
1771 
1772 		t = print_otag(h, TAG_A, i, tag);
1773 		print_text(h, n->string);
1774 		print_tagq(h, t);
1775 
1776 		n = n->next;
1777 	}
1778 
1779 	h->flags |= HTML_NOSPACE;
1780 	print_text(h, ">");
1781 
1782 	for ( ; n; n = n->next) {
1783 		assert(MDOC_TEXT == n->type);
1784 		print_text(h, n->string);
1785 	}
1786 
1787 	return(0);
1788 }
1789 
1790 
1791 /* ARGSUSED */
1792 static int
1793 mdoc_ic_pre(MDOC_ARGS)
1794 {
1795 	struct htmlpair	tag;
1796 
1797 	PAIR_CLASS_INIT(&tag, "cmd");
1798 	print_otag(h, TAG_B, 1, &tag);
1799 	return(1);
1800 }
1801 
1802 
1803 /* ARGSUSED */
1804 static int
1805 mdoc_rv_pre(MDOC_ARGS)
1806 {
1807 	struct htmlpair	 tag;
1808 	struct tag	*t;
1809 	int		 nchild;
1810 
1811 	if (n->prev)
1812 		print_otag(h, TAG_BR, 0, NULL);
1813 
1814 	PAIR_CLASS_INIT(&tag, "fname");
1815 
1816 	print_text(h, "The");
1817 
1818 	nchild = n->nchild;
1819 	for (n = n->child; n; n = n->next) {
1820 		assert(MDOC_TEXT == n->type);
1821 
1822 		t = print_otag(h, TAG_B, 1, &tag);
1823 		print_text(h, n->string);
1824 		print_tagq(h, t);
1825 
1826 		h->flags |= HTML_NOSPACE;
1827 		print_text(h, "()");
1828 
1829 		if (nchild > 2 && n->next) {
1830 			h->flags |= HTML_NOSPACE;
1831 			print_text(h, ",");
1832 		}
1833 
1834 		if (n->next && NULL == n->next->next)
1835 			print_text(h, "and");
1836 	}
1837 
1838 	if (nchild > 1)
1839 		print_text(h, "functions return");
1840 	else
1841 		print_text(h, "function returns");
1842 
1843        	print_text(h, "the value 0 if successful; otherwise the value "
1844 			"-1 is returned and the global variable");
1845 
1846 	PAIR_CLASS_INIT(&tag, "var");
1847 	t = print_otag(h, TAG_B, 1, &tag);
1848 	print_text(h, "errno");
1849 	print_tagq(h, t);
1850        	print_text(h, "is set to indicate the error.");
1851 	return(0);
1852 }
1853 
1854 
1855 /* ARGSUSED */
1856 static int
1857 mdoc_va_pre(MDOC_ARGS)
1858 {
1859 	struct htmlpair	tag;
1860 
1861 	PAIR_CLASS_INIT(&tag, "var");
1862 	print_otag(h, TAG_B, 1, &tag);
1863 	return(1);
1864 }
1865 
1866 
1867 /* ARGSUSED */
1868 static int
1869 mdoc_ap_pre(MDOC_ARGS)
1870 {
1871 
1872 	h->flags |= HTML_NOSPACE;
1873 	print_text(h, "\\(aq");
1874 	h->flags |= HTML_NOSPACE;
1875 	return(1);
1876 }
1877 
1878 
1879 /* ARGSUSED */
1880 static int
1881 mdoc_bf_pre(MDOC_ARGS)
1882 {
1883 	struct htmlpair	 tag[2];
1884 	struct roffsu	 su;
1885 
1886 	if (MDOC_HEAD == n->type)
1887 		return(0);
1888 	else if (MDOC_BODY != n->type)
1889 		return(1);
1890 
1891 	if (FONT_Em == n->norm->Bf.font)
1892 		PAIR_CLASS_INIT(&tag[0], "emph");
1893 	else if (FONT_Sy == n->norm->Bf.font)
1894 		PAIR_CLASS_INIT(&tag[0], "symb");
1895 	else if (FONT_Li == n->norm->Bf.font)
1896 		PAIR_CLASS_INIT(&tag[0], "lit");
1897 	else
1898 		PAIR_CLASS_INIT(&tag[0], "none");
1899 
1900 	/*
1901 	 * We want this to be inline-formatted, but needs to be div to
1902 	 * accept block children.
1903 	 */
1904 	bufinit(h);
1905 	bufcat_style(h, "display", "inline");
1906 	SCALE_HS_INIT(&su, 1);
1907 	/* Needs a left-margin for spacing. */
1908 	bufcat_su(h, "margin-left", &su);
1909 	PAIR_STYLE_INIT(&tag[1], h);
1910 	print_otag(h, TAG_DIV, 2, tag);
1911 	return(1);
1912 }
1913 
1914 
1915 /* ARGSUSED */
1916 static int
1917 mdoc_ms_pre(MDOC_ARGS)
1918 {
1919 	struct htmlpair	tag;
1920 
1921 	PAIR_CLASS_INIT(&tag, "symb");
1922 	print_otag(h, TAG_SPAN, 1, &tag);
1923 	return(1);
1924 }
1925 
1926 
1927 /* ARGSUSED */
1928 static int
1929 mdoc_igndelim_pre(MDOC_ARGS)
1930 {
1931 
1932 	h->flags |= HTML_IGNDELIM;
1933 	return(1);
1934 }
1935 
1936 
1937 /* ARGSUSED */
1938 static void
1939 mdoc_pf_post(MDOC_ARGS)
1940 {
1941 
1942 	h->flags |= HTML_NOSPACE;
1943 }
1944 
1945 
1946 /* ARGSUSED */
1947 static int
1948 mdoc_rs_pre(MDOC_ARGS)
1949 {
1950 	struct htmlpair	 tag;
1951 
1952 	if (MDOC_BLOCK != n->type)
1953 		return(1);
1954 
1955 	if (n->prev && SEC_SEE_ALSO == n->sec)
1956 		print_otag(h, TAG_P, 0, NULL);
1957 
1958 	PAIR_CLASS_INIT(&tag, "ref");
1959 	print_otag(h, TAG_SPAN, 1, &tag);
1960 	return(1);
1961 }
1962 
1963 
1964 
1965 /* ARGSUSED */
1966 static int
1967 mdoc_li_pre(MDOC_ARGS)
1968 {
1969 	struct htmlpair	tag;
1970 
1971 	PAIR_CLASS_INIT(&tag, "lit");
1972 	print_otag(h, TAG_CODE, 1, &tag);
1973 	return(1);
1974 }
1975 
1976 
1977 /* ARGSUSED */
1978 static int
1979 mdoc_sy_pre(MDOC_ARGS)
1980 {
1981 	struct htmlpair	tag;
1982 
1983 	PAIR_CLASS_INIT(&tag, "symb");
1984 	print_otag(h, TAG_SPAN, 1, &tag);
1985 	return(1);
1986 }
1987 
1988 
1989 /* ARGSUSED */
1990 static int
1991 mdoc_bt_pre(MDOC_ARGS)
1992 {
1993 
1994 	print_text(h, "is currently in beta test.");
1995 	return(0);
1996 }
1997 
1998 
1999 /* ARGSUSED */
2000 static int
2001 mdoc_ud_pre(MDOC_ARGS)
2002 {
2003 
2004 	print_text(h, "currently under development.");
2005 	return(0);
2006 }
2007 
2008 
2009 /* ARGSUSED */
2010 static int
2011 mdoc_lb_pre(MDOC_ARGS)
2012 {
2013 	struct htmlpair	tag;
2014 
2015 	if (SEC_LIBRARY == n->sec && MDOC_LINE & n->flags && n->prev)
2016 		print_otag(h, TAG_BR, 0, NULL);
2017 
2018 	PAIR_CLASS_INIT(&tag, "lib");
2019 	print_otag(h, TAG_SPAN, 1, &tag);
2020 	return(1);
2021 }
2022 
2023 
2024 /* ARGSUSED */
2025 static int
2026 mdoc__x_pre(MDOC_ARGS)
2027 {
2028 	struct htmlpair	tag[2];
2029 	enum htmltag	t;
2030 
2031 	t = TAG_SPAN;
2032 
2033 	switch (n->tok) {
2034 	case(MDOC__A):
2035 		PAIR_CLASS_INIT(&tag[0], "ref-auth");
2036 		if (n->prev && MDOC__A == n->prev->tok)
2037 			if (NULL == n->next || MDOC__A != n->next->tok)
2038 				print_text(h, "and");
2039 		break;
2040 	case(MDOC__B):
2041 		PAIR_CLASS_INIT(&tag[0], "ref-book");
2042 		t = TAG_I;
2043 		break;
2044 	case(MDOC__C):
2045 		PAIR_CLASS_INIT(&tag[0], "ref-city");
2046 		break;
2047 	case(MDOC__D):
2048 		PAIR_CLASS_INIT(&tag[0], "ref-date");
2049 		break;
2050 	case(MDOC__I):
2051 		PAIR_CLASS_INIT(&tag[0], "ref-issue");
2052 		t = TAG_I;
2053 		break;
2054 	case(MDOC__J):
2055 		PAIR_CLASS_INIT(&tag[0], "ref-jrnl");
2056 		t = TAG_I;
2057 		break;
2058 	case(MDOC__N):
2059 		PAIR_CLASS_INIT(&tag[0], "ref-num");
2060 		break;
2061 	case(MDOC__O):
2062 		PAIR_CLASS_INIT(&tag[0], "ref-opt");
2063 		break;
2064 	case(MDOC__P):
2065 		PAIR_CLASS_INIT(&tag[0], "ref-page");
2066 		break;
2067 	case(MDOC__Q):
2068 		PAIR_CLASS_INIT(&tag[0], "ref-corp");
2069 		break;
2070 	case(MDOC__R):
2071 		PAIR_CLASS_INIT(&tag[0], "ref-rep");
2072 		break;
2073 	case(MDOC__T):
2074 		PAIR_CLASS_INIT(&tag[0], "ref-title");
2075 		break;
2076 	case(MDOC__U):
2077 		PAIR_CLASS_INIT(&tag[0], "link-ref");
2078 		break;
2079 	case(MDOC__V):
2080 		PAIR_CLASS_INIT(&tag[0], "ref-vol");
2081 		break;
2082 	default:
2083 		abort();
2084 		/* NOTREACHED */
2085 	}
2086 
2087 	if (MDOC__U != n->tok) {
2088 		print_otag(h, t, 1, tag);
2089 		return(1);
2090 	}
2091 
2092 	PAIR_HREF_INIT(&tag[1], n->child->string);
2093 	print_otag(h, TAG_A, 2, tag);
2094 
2095 	return(1);
2096 }
2097 
2098 
2099 /* ARGSUSED */
2100 static void
2101 mdoc__x_post(MDOC_ARGS)
2102 {
2103 
2104 	if (MDOC__A == n->tok && n->next && MDOC__A == n->next->tok)
2105 		if (NULL == n->next->next || MDOC__A != n->next->next->tok)
2106 			if (NULL == n->prev || MDOC__A != n->prev->tok)
2107 				return;
2108 
2109 	/* TODO: %U */
2110 
2111 	if (NULL == n->parent || MDOC_Rs != n->parent->tok)
2112 		return;
2113 
2114 	h->flags |= HTML_NOSPACE;
2115 	print_text(h, n->next ? "," : ".");
2116 }
2117 
2118 
2119 /* ARGSUSED */
2120 static int
2121 mdoc_bk_pre(MDOC_ARGS)
2122 {
2123 
2124 	switch (n->type) {
2125 	case (MDOC_BLOCK):
2126 		break;
2127 	case (MDOC_HEAD):
2128 		return(0);
2129 	case (MDOC_BODY):
2130 		if (n->parent->args || 0 == n->prev->nchild)
2131 			h->flags |= HTML_PREKEEP;
2132 		break;
2133 	default:
2134 		abort();
2135 		/* NOTREACHED */
2136 	}
2137 
2138 	return(1);
2139 }
2140 
2141 
2142 /* ARGSUSED */
2143 static void
2144 mdoc_bk_post(MDOC_ARGS)
2145 {
2146 
2147 	if (MDOC_BODY == n->type)
2148 		h->flags &= ~(HTML_KEEP | HTML_PREKEEP);
2149 }
2150 
2151 
2152 /* ARGSUSED */
2153 static int
2154 mdoc_quote_pre(MDOC_ARGS)
2155 {
2156 	struct htmlpair	tag;
2157 
2158 	if (MDOC_BODY != n->type)
2159 		return(1);
2160 
2161 	switch (n->tok) {
2162 	case (MDOC_Ao):
2163 		/* FALLTHROUGH */
2164 	case (MDOC_Aq):
2165 		print_text(h, "\\(la");
2166 		break;
2167 	case (MDOC_Bro):
2168 		/* FALLTHROUGH */
2169 	case (MDOC_Brq):
2170 		print_text(h, "\\(lC");
2171 		break;
2172 	case (MDOC_Bo):
2173 		/* FALLTHROUGH */
2174 	case (MDOC_Bq):
2175 		print_text(h, "\\(lB");
2176 		break;
2177 	case (MDOC_Oo):
2178 		/* FALLTHROUGH */
2179 	case (MDOC_Op):
2180 		print_text(h, "\\(lB");
2181 		h->flags |= HTML_NOSPACE;
2182 		PAIR_CLASS_INIT(&tag, "opt");
2183 		print_otag(h, TAG_SPAN, 1, &tag);
2184 		break;
2185 	case (MDOC_Eo):
2186 		break;
2187 	case (MDOC_Do):
2188 		/* FALLTHROUGH */
2189 	case (MDOC_Dq):
2190 		/* FALLTHROUGH */
2191 	case (MDOC_Qo):
2192 		/* FALLTHROUGH */
2193 	case (MDOC_Qq):
2194 		print_text(h, "\\(lq");
2195 		break;
2196 	case (MDOC_Po):
2197 		/* FALLTHROUGH */
2198 	case (MDOC_Pq):
2199 		print_text(h, "(");
2200 		break;
2201 	case (MDOC_Ql):
2202 		print_text(h, "\\(oq");
2203 		h->flags |= HTML_NOSPACE;
2204 		PAIR_CLASS_INIT(&tag, "lit");
2205 		print_otag(h, TAG_CODE, 1, &tag);
2206 		break;
2207 	case (MDOC_So):
2208 		/* FALLTHROUGH */
2209 	case (MDOC_Sq):
2210 		print_text(h, "\\(oq");
2211 		break;
2212 	default:
2213 		abort();
2214 		/* NOTREACHED */
2215 	}
2216 
2217 	h->flags |= HTML_NOSPACE;
2218 	return(1);
2219 }
2220 
2221 
2222 /* ARGSUSED */
2223 static void
2224 mdoc_quote_post(MDOC_ARGS)
2225 {
2226 
2227 	if (MDOC_BODY != n->type)
2228 		return;
2229 
2230 	h->flags |= HTML_NOSPACE;
2231 
2232 	switch (n->tok) {
2233 	case (MDOC_Ao):
2234 		/* FALLTHROUGH */
2235 	case (MDOC_Aq):
2236 		print_text(h, "\\(ra");
2237 		break;
2238 	case (MDOC_Bro):
2239 		/* FALLTHROUGH */
2240 	case (MDOC_Brq):
2241 		print_text(h, "\\(rC");
2242 		break;
2243 	case (MDOC_Oo):
2244 		/* FALLTHROUGH */
2245 	case (MDOC_Op):
2246 		/* FALLTHROUGH */
2247 	case (MDOC_Bo):
2248 		/* FALLTHROUGH */
2249 	case (MDOC_Bq):
2250 		print_text(h, "\\(rB");
2251 		break;
2252 	case (MDOC_Eo):
2253 		break;
2254 	case (MDOC_Qo):
2255 		/* FALLTHROUGH */
2256 	case (MDOC_Qq):
2257 		/* FALLTHROUGH */
2258 	case (MDOC_Do):
2259 		/* FALLTHROUGH */
2260 	case (MDOC_Dq):
2261 		print_text(h, "\\(rq");
2262 		break;
2263 	case (MDOC_Po):
2264 		/* FALLTHROUGH */
2265 	case (MDOC_Pq):
2266 		print_text(h, ")");
2267 		break;
2268 	case (MDOC_Ql):
2269 		/* FALLTHROUGH */
2270 	case (MDOC_So):
2271 		/* FALLTHROUGH */
2272 	case (MDOC_Sq):
2273 		print_text(h, "\\(cq");
2274 		break;
2275 	default:
2276 		abort();
2277 		/* NOTREACHED */
2278 	}
2279 }
2280 
2281 
2282