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