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