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