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