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