xref: /openbsd-src/usr.bin/mandoc/mdoc_html.c (revision d59bb9942320b767f2a19aaa7690c8c6e30b724c)
1 /*	$OpenBSD: mdoc_html.c,v 1.148 2017/03/03 13:55:06 schwarze Exp $ */
2 /*
3  * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
4  * Copyright (c) 2014, 2015, 2016, 2017 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	char		 *make_id(const struct roff_node *);
50 static	void		  print_mdoc_head(MDOC_ARGS);
51 static	void		  print_mdoc_node(MDOC_ARGS);
52 static	void		  print_mdoc_nodelist(MDOC_ARGS);
53 static	void		  synopsis_pre(struct html *,
54 				const struct roff_node *);
55 
56 static	void		  mdoc_root_post(MDOC_ARGS);
57 static	int		  mdoc_root_pre(MDOC_ARGS);
58 
59 static	void		  mdoc__x_post(MDOC_ARGS);
60 static	int		  mdoc__x_pre(MDOC_ARGS);
61 static	int		  mdoc_ad_pre(MDOC_ARGS);
62 static	int		  mdoc_an_pre(MDOC_ARGS);
63 static	int		  mdoc_ap_pre(MDOC_ARGS);
64 static	int		  mdoc_ar_pre(MDOC_ARGS);
65 static	int		  mdoc_bd_pre(MDOC_ARGS);
66 static	int		  mdoc_bf_pre(MDOC_ARGS);
67 static	void		  mdoc_bk_post(MDOC_ARGS);
68 static	int		  mdoc_bk_pre(MDOC_ARGS);
69 static	int		  mdoc_bl_pre(MDOC_ARGS);
70 static	int		  mdoc_cd_pre(MDOC_ARGS);
71 static	int		  mdoc_cm_pre(MDOC_ARGS);
72 static	int		  mdoc_d1_pre(MDOC_ARGS);
73 static	int		  mdoc_dv_pre(MDOC_ARGS);
74 static	int		  mdoc_fa_pre(MDOC_ARGS);
75 static	int		  mdoc_fd_pre(MDOC_ARGS);
76 static	int		  mdoc_fl_pre(MDOC_ARGS);
77 static	int		  mdoc_fn_pre(MDOC_ARGS);
78 static	int		  mdoc_ft_pre(MDOC_ARGS);
79 static	int		  mdoc_em_pre(MDOC_ARGS);
80 static	void		  mdoc_eo_post(MDOC_ARGS);
81 static	int		  mdoc_eo_pre(MDOC_ARGS);
82 static	int		  mdoc_er_pre(MDOC_ARGS);
83 static	int		  mdoc_ev_pre(MDOC_ARGS);
84 static	int		  mdoc_ex_pre(MDOC_ARGS);
85 static	void		  mdoc_fo_post(MDOC_ARGS);
86 static	int		  mdoc_fo_pre(MDOC_ARGS);
87 static	int		  mdoc_ic_pre(MDOC_ARGS);
88 static	int		  mdoc_igndelim_pre(MDOC_ARGS);
89 static	int		  mdoc_in_pre(MDOC_ARGS);
90 static	int		  mdoc_it_pre(MDOC_ARGS);
91 static	int		  mdoc_lb_pre(MDOC_ARGS);
92 static	int		  mdoc_li_pre(MDOC_ARGS);
93 static	int		  mdoc_lk_pre(MDOC_ARGS);
94 static	int		  mdoc_mt_pre(MDOC_ARGS);
95 static	int		  mdoc_ms_pre(MDOC_ARGS);
96 static	int		  mdoc_nd_pre(MDOC_ARGS);
97 static	int		  mdoc_nm_pre(MDOC_ARGS);
98 static	int		  mdoc_no_pre(MDOC_ARGS);
99 static	int		  mdoc_ns_pre(MDOC_ARGS);
100 static	int		  mdoc_pa_pre(MDOC_ARGS);
101 static	void		  mdoc_pf_post(MDOC_ARGS);
102 static	int		  mdoc_pp_pre(MDOC_ARGS);
103 static	void		  mdoc_quote_post(MDOC_ARGS);
104 static	int		  mdoc_quote_pre(MDOC_ARGS);
105 static	int		  mdoc_rs_pre(MDOC_ARGS);
106 static	int		  mdoc_sh_pre(MDOC_ARGS);
107 static	int		  mdoc_skip_pre(MDOC_ARGS);
108 static	int		  mdoc_sm_pre(MDOC_ARGS);
109 static	int		  mdoc_sp_pre(MDOC_ARGS);
110 static	int		  mdoc_ss_pre(MDOC_ARGS);
111 static	int		  mdoc_st_pre(MDOC_ARGS);
112 static	int		  mdoc_sx_pre(MDOC_ARGS);
113 static	int		  mdoc_sy_pre(MDOC_ARGS);
114 static	int		  mdoc_va_pre(MDOC_ARGS);
115 static	int		  mdoc_vt_pre(MDOC_ARGS);
116 static	int		  mdoc_xr_pre(MDOC_ARGS);
117 static	int		  mdoc_xx_pre(MDOC_ARGS);
118 
119 static	const struct htmlmdoc mdocs[MDOC_MAX] = {
120 	{mdoc_ap_pre, NULL}, /* Ap */
121 	{NULL, NULL}, /* Dd */
122 	{NULL, NULL}, /* Dt */
123 	{NULL, NULL}, /* Os */
124 	{mdoc_sh_pre, NULL }, /* Sh */
125 	{mdoc_ss_pre, NULL }, /* Ss */
126 	{mdoc_pp_pre, NULL}, /* Pp */
127 	{mdoc_d1_pre, NULL}, /* D1 */
128 	{mdoc_d1_pre, NULL}, /* Dl */
129 	{mdoc_bd_pre, NULL}, /* Bd */
130 	{NULL, NULL}, /* Ed */
131 	{mdoc_bl_pre, NULL}, /* Bl */
132 	{NULL, NULL}, /* El */
133 	{mdoc_it_pre, NULL}, /* It */
134 	{mdoc_ad_pre, NULL}, /* Ad */
135 	{mdoc_an_pre, NULL}, /* An */
136 	{mdoc_ar_pre, NULL}, /* Ar */
137 	{mdoc_cd_pre, NULL}, /* Cd */
138 	{mdoc_cm_pre, NULL}, /* Cm */
139 	{mdoc_dv_pre, NULL}, /* Dv */
140 	{mdoc_er_pre, NULL}, /* Er */
141 	{mdoc_ev_pre, NULL}, /* Ev */
142 	{mdoc_ex_pre, NULL}, /* Ex */
143 	{mdoc_fa_pre, NULL}, /* Fa */
144 	{mdoc_fd_pre, NULL}, /* Fd */
145 	{mdoc_fl_pre, NULL}, /* Fl */
146 	{mdoc_fn_pre, NULL}, /* Fn */
147 	{mdoc_ft_pre, NULL}, /* Ft */
148 	{mdoc_ic_pre, NULL}, /* Ic */
149 	{mdoc_in_pre, NULL}, /* In */
150 	{mdoc_li_pre, NULL}, /* Li */
151 	{mdoc_nd_pre, NULL}, /* Nd */
152 	{mdoc_nm_pre, NULL}, /* Nm */
153 	{mdoc_quote_pre, mdoc_quote_post}, /* Op */
154 	{mdoc_ft_pre, NULL}, /* Ot */
155 	{mdoc_pa_pre, NULL}, /* Pa */
156 	{mdoc_ex_pre, NULL}, /* Rv */
157 	{mdoc_st_pre, NULL}, /* St */
158 	{mdoc_va_pre, NULL}, /* Va */
159 	{mdoc_vt_pre, NULL}, /* Vt */
160 	{mdoc_xr_pre, NULL}, /* Xr */
161 	{mdoc__x_pre, mdoc__x_post}, /* %A */
162 	{mdoc__x_pre, mdoc__x_post}, /* %B */
163 	{mdoc__x_pre, mdoc__x_post}, /* %D */
164 	{mdoc__x_pre, mdoc__x_post}, /* %I */
165 	{mdoc__x_pre, mdoc__x_post}, /* %J */
166 	{mdoc__x_pre, mdoc__x_post}, /* %N */
167 	{mdoc__x_pre, mdoc__x_post}, /* %O */
168 	{mdoc__x_pre, mdoc__x_post}, /* %P */
169 	{mdoc__x_pre, mdoc__x_post}, /* %R */
170 	{mdoc__x_pre, mdoc__x_post}, /* %T */
171 	{mdoc__x_pre, mdoc__x_post}, /* %V */
172 	{NULL, NULL}, /* Ac */
173 	{mdoc_quote_pre, mdoc_quote_post}, /* Ao */
174 	{mdoc_quote_pre, mdoc_quote_post}, /* Aq */
175 	{mdoc_xx_pre, NULL}, /* At */
176 	{NULL, NULL}, /* Bc */
177 	{mdoc_bf_pre, NULL}, /* Bf */
178 	{mdoc_quote_pre, mdoc_quote_post}, /* Bo */
179 	{mdoc_quote_pre, mdoc_quote_post}, /* Bq */
180 	{mdoc_xx_pre, NULL}, /* Bsx */
181 	{mdoc_xx_pre, NULL}, /* Bx */
182 	{mdoc_skip_pre, NULL}, /* Db */
183 	{NULL, NULL}, /* Dc */
184 	{mdoc_quote_pre, mdoc_quote_post}, /* Do */
185 	{mdoc_quote_pre, mdoc_quote_post}, /* Dq */
186 	{NULL, NULL}, /* Ec */ /* FIXME: no space */
187 	{NULL, NULL}, /* Ef */
188 	{mdoc_em_pre, NULL}, /* Em */
189 	{mdoc_eo_pre, mdoc_eo_post}, /* Eo */
190 	{mdoc_xx_pre, NULL}, /* Fx */
191 	{mdoc_ms_pre, NULL}, /* Ms */
192 	{mdoc_no_pre, NULL}, /* No */
193 	{mdoc_ns_pre, NULL}, /* Ns */
194 	{mdoc_xx_pre, NULL}, /* Nx */
195 	{mdoc_xx_pre, NULL}, /* Ox */
196 	{NULL, NULL}, /* Pc */
197 	{mdoc_igndelim_pre, mdoc_pf_post}, /* Pf */
198 	{mdoc_quote_pre, mdoc_quote_post}, /* Po */
199 	{mdoc_quote_pre, mdoc_quote_post}, /* Pq */
200 	{NULL, NULL}, /* Qc */
201 	{mdoc_quote_pre, mdoc_quote_post}, /* Ql */
202 	{mdoc_quote_pre, mdoc_quote_post}, /* Qo */
203 	{mdoc_quote_pre, mdoc_quote_post}, /* Qq */
204 	{NULL, NULL}, /* Re */
205 	{mdoc_rs_pre, NULL}, /* Rs */
206 	{NULL, NULL}, /* Sc */
207 	{mdoc_quote_pre, mdoc_quote_post}, /* So */
208 	{mdoc_quote_pre, mdoc_quote_post}, /* Sq */
209 	{mdoc_sm_pre, NULL}, /* Sm */
210 	{mdoc_sx_pre, NULL}, /* Sx */
211 	{mdoc_sy_pre, NULL}, /* Sy */
212 	{NULL, NULL}, /* Tn */
213 	{mdoc_xx_pre, NULL}, /* Ux */
214 	{NULL, NULL}, /* Xc */
215 	{NULL, NULL}, /* Xo */
216 	{mdoc_fo_pre, mdoc_fo_post}, /* Fo */
217 	{NULL, NULL}, /* Fc */
218 	{mdoc_quote_pre, mdoc_quote_post}, /* Oo */
219 	{NULL, NULL}, /* Oc */
220 	{mdoc_bk_pre, mdoc_bk_post}, /* Bk */
221 	{NULL, NULL}, /* Ek */
222 	{NULL, NULL}, /* Bt */
223 	{NULL, NULL}, /* Hf */
224 	{mdoc_em_pre, NULL}, /* Fr */
225 	{NULL, NULL}, /* Ud */
226 	{mdoc_lb_pre, NULL}, /* Lb */
227 	{mdoc_pp_pre, NULL}, /* Lp */
228 	{mdoc_lk_pre, NULL}, /* Lk */
229 	{mdoc_mt_pre, NULL}, /* Mt */
230 	{mdoc_quote_pre, mdoc_quote_post}, /* Brq */
231 	{mdoc_quote_pre, mdoc_quote_post}, /* Bro */
232 	{NULL, NULL}, /* Brc */
233 	{mdoc__x_pre, mdoc__x_post}, /* %C */
234 	{mdoc_skip_pre, NULL}, /* Es */
235 	{mdoc_quote_pre, mdoc_quote_post}, /* En */
236 	{mdoc_xx_pre, NULL}, /* Dx */
237 	{mdoc__x_pre, mdoc__x_post}, /* %Q */
238 	{mdoc_sp_pre, NULL}, /* br */
239 	{mdoc_sp_pre, NULL}, /* sp */
240 	{mdoc__x_pre, mdoc__x_post}, /* %U */
241 	{NULL, NULL}, /* Ta */
242 	{mdoc_skip_pre, NULL}, /* ll */
243 };
244 
245 
246 /*
247  * See the same function in mdoc_term.c for documentation.
248  */
249 static void
250 synopsis_pre(struct html *h, const struct roff_node *n)
251 {
252 
253 	if (NULL == n->prev || ! (NODE_SYNPRETTY & n->flags))
254 		return;
255 
256 	if (n->prev->tok == n->tok &&
257 	    MDOC_Fo != n->tok &&
258 	    MDOC_Ft != n->tok &&
259 	    MDOC_Fn != n->tok) {
260 		print_otag(h, TAG_BR, "");
261 		return;
262 	}
263 
264 	switch (n->prev->tok) {
265 	case MDOC_Fd:
266 	case MDOC_Fn:
267 	case MDOC_Fo:
268 	case MDOC_In:
269 	case MDOC_Vt:
270 		print_paragraph(h);
271 		break;
272 	case MDOC_Ft:
273 		if (MDOC_Fn != n->tok && MDOC_Fo != n->tok) {
274 			print_paragraph(h);
275 			break;
276 		}
277 		/* FALLTHROUGH */
278 	default:
279 		print_otag(h, TAG_BR, "");
280 		break;
281 	}
282 }
283 
284 void
285 html_mdoc(void *arg, const struct roff_man *mdoc)
286 {
287 	struct html	*h;
288 	struct tag	*t;
289 
290 	h = (struct html *)arg;
291 
292 	if ((h->oflags & HTML_FRAGMENT) == 0) {
293 		print_gen_decls(h);
294 		print_otag(h, TAG_HTML, "");
295 		t = print_otag(h, TAG_HEAD, "");
296 		print_mdoc_head(&mdoc->meta, mdoc->first->child, h);
297 		print_tagq(h, t);
298 		print_otag(h, TAG_BODY, "");
299 	}
300 
301 	mdoc_root_pre(&mdoc->meta, mdoc->first->child, h);
302 	t = print_otag(h, TAG_DIV, "c", "manual-text");
303 	print_mdoc_nodelist(&mdoc->meta, mdoc->first->child, h);
304 	print_tagq(h, t);
305 	mdoc_root_post(&mdoc->meta, mdoc->first->child, h);
306 	print_tagq(h, NULL);
307 }
308 
309 static void
310 print_mdoc_head(MDOC_ARGS)
311 {
312 	char	*cp;
313 
314 	print_gen_head(h);
315 
316 	if (meta->arch != NULL && meta->msec != NULL)
317 		mandoc_asprintf(&cp, "%s(%s) (%s)", meta->title,
318 		    meta->msec, meta->arch);
319 	else if (meta->msec != NULL)
320 		mandoc_asprintf(&cp, "%s(%s)", meta->title, meta->msec);
321 	else if (meta->arch != NULL)
322 		mandoc_asprintf(&cp, "%s (%s)", meta->title, meta->arch);
323 	else
324 		cp = mandoc_strdup(meta->title);
325 
326 	print_otag(h, TAG_TITLE, "");
327 	print_text(h, cp);
328 	free(cp);
329 }
330 
331 static void
332 print_mdoc_nodelist(MDOC_ARGS)
333 {
334 
335 	while (n != NULL) {
336 		print_mdoc_node(meta, n, h);
337 		n = n->next;
338 	}
339 }
340 
341 static void
342 print_mdoc_node(MDOC_ARGS)
343 {
344 	int		 child;
345 	struct tag	*t;
346 
347 	if (n->flags & NODE_NOPRT)
348 		return;
349 
350 	child = 1;
351 	t = h->tag;
352 	n->flags &= ~NODE_ENDED;
353 
354 	switch (n->type) {
355 	case ROFFT_TEXT:
356 		/* No tables in this mode... */
357 		assert(NULL == h->tblt);
358 
359 		/*
360 		 * Make sure that if we're in a literal mode already
361 		 * (i.e., within a <PRE>) don't print the newline.
362 		 */
363 		if (' ' == *n->string && NODE_LINE & n->flags)
364 			if ( ! (HTML_LITERAL & h->flags))
365 				print_otag(h, TAG_BR, "");
366 		if (NODE_DELIMC & n->flags)
367 			h->flags |= HTML_NOSPACE;
368 		print_text(h, n->string);
369 		if (NODE_DELIMO & n->flags)
370 			h->flags |= HTML_NOSPACE;
371 		return;
372 	case ROFFT_EQN:
373 		print_eqn(h, n->eqn);
374 		break;
375 	case ROFFT_TBL:
376 		/*
377 		 * This will take care of initialising all of the table
378 		 * state data for the first table, then tearing it down
379 		 * for the last one.
380 		 */
381 		print_tbl(h, n->span);
382 		return;
383 	default:
384 		/*
385 		 * Close out the current table, if it's open, and unset
386 		 * the "meta" table state.  This will be reopened on the
387 		 * next table element.
388 		 */
389 		if (h->tblt != NULL) {
390 			print_tblclose(h);
391 			t = h->tag;
392 		}
393 		assert(h->tblt == NULL);
394 		if (mdocs[n->tok].pre && (n->end == ENDBODY_NOT || n->child))
395 			child = (*mdocs[n->tok].pre)(meta, n, h);
396 		break;
397 	}
398 
399 	if (h->flags & HTML_KEEP && n->flags & NODE_LINE) {
400 		h->flags &= ~HTML_KEEP;
401 		h->flags |= HTML_PREKEEP;
402 	}
403 
404 	if (child && n->child)
405 		print_mdoc_nodelist(meta, n->child, h);
406 
407 	print_stagq(h, t);
408 
409 	switch (n->type) {
410 	case ROFFT_EQN:
411 		break;
412 	default:
413 		if ( ! mdocs[n->tok].post || n->flags & NODE_ENDED)
414 			break;
415 		(*mdocs[n->tok].post)(meta, n, h);
416 		if (n->end != ENDBODY_NOT)
417 			n->body->flags |= NODE_ENDED;
418 		break;
419 	}
420 }
421 
422 static void
423 mdoc_root_post(MDOC_ARGS)
424 {
425 	struct tag	*t, *tt;
426 
427 	t = print_otag(h, TAG_TABLE, "c", "foot");
428 	tt = print_otag(h, TAG_TR, "");
429 
430 	print_otag(h, TAG_TD, "c", "foot-date");
431 	print_text(h, meta->date);
432 	print_stagq(h, tt);
433 
434 	print_otag(h, TAG_TD, "c", "foot-os");
435 	print_text(h, meta->os);
436 	print_tagq(h, t);
437 }
438 
439 static int
440 mdoc_root_pre(MDOC_ARGS)
441 {
442 	struct tag	*t, *tt;
443 	char		*volume, *title;
444 
445 	if (NULL == meta->arch)
446 		volume = mandoc_strdup(meta->vol);
447 	else
448 		mandoc_asprintf(&volume, "%s (%s)",
449 		    meta->vol, meta->arch);
450 
451 	if (NULL == meta->msec)
452 		title = mandoc_strdup(meta->title);
453 	else
454 		mandoc_asprintf(&title, "%s(%s)",
455 		    meta->title, meta->msec);
456 
457 	t = print_otag(h, TAG_TABLE, "c", "head");
458 	tt = print_otag(h, TAG_TR, "");
459 
460 	print_otag(h, TAG_TD, "c", "head-ltitle");
461 	print_text(h, title);
462 	print_stagq(h, tt);
463 
464 	print_otag(h, TAG_TD, "c", "head-vol");
465 	print_text(h, volume);
466 	print_stagq(h, tt);
467 
468 	print_otag(h, TAG_TD, "c", "head-rtitle");
469 	print_text(h, title);
470 	print_tagq(h, t);
471 
472 	free(title);
473 	free(volume);
474 	return 1;
475 }
476 
477 static char *
478 make_id(const struct roff_node *n)
479 {
480 	const struct roff_node	*nch;
481 	char			*buf, *cp;
482 
483 	for (nch = n->child; nch != NULL; nch = nch->next)
484 		if (nch->type != ROFFT_TEXT)
485 			return NULL;
486 
487 	buf = NULL;
488 	deroff(&buf, n);
489 
490 	/* http://www.w3.org/TR/html5/dom.html#the-id-attribute */
491 
492 	for (cp = buf; *cp != '\0'; cp++)
493 		if (*cp == ' ')
494 			*cp = '_';
495 
496 	return buf;
497 }
498 
499 static int
500 mdoc_sh_pre(MDOC_ARGS)
501 {
502 	char	*id;
503 
504 	switch (n->type) {
505 	case ROFFT_HEAD:
506 		id = make_id(n);
507 		print_otag(h, TAG_H1, "ci", "Sh", id);
508 		free(id);
509 		break;
510 	case ROFFT_BODY:
511 		if (n->sec == SEC_AUTHORS)
512 			h->flags &= ~(HTML_SPLIT|HTML_NOSPLIT);
513 		break;
514 	default:
515 		break;
516 	}
517 	return 1;
518 }
519 
520 static int
521 mdoc_ss_pre(MDOC_ARGS)
522 {
523 	char	*id;
524 
525 	if (n->type != ROFFT_HEAD)
526 		return 1;
527 
528 	id = make_id(n);
529 	print_otag(h, TAG_H2, "ci", "Ss", id);
530 	free(id);
531 	return 1;
532 }
533 
534 static int
535 mdoc_fl_pre(MDOC_ARGS)
536 {
537 	print_otag(h, TAG_B, "c", "Fl");
538 	print_text(h, "\\-");
539 
540 	if (!(n->child == NULL &&
541 	    (n->next == NULL ||
542 	     n->next->type == ROFFT_TEXT ||
543 	     n->next->flags & NODE_LINE)))
544 		h->flags |= HTML_NOSPACE;
545 
546 	return 1;
547 }
548 
549 static int
550 mdoc_cm_pre(MDOC_ARGS)
551 {
552 	print_otag(h, TAG_B, "c", "Cm");
553 	return 1;
554 }
555 
556 static int
557 mdoc_nd_pre(MDOC_ARGS)
558 {
559 	if (n->type != ROFFT_BODY)
560 		return 1;
561 
562 	/* XXX: this tag in theory can contain block elements. */
563 
564 	print_text(h, "\\(em");
565 	print_otag(h, TAG_SPAN, "c", "Nd");
566 	return 1;
567 }
568 
569 static int
570 mdoc_nm_pre(MDOC_ARGS)
571 {
572 	struct tag	*t;
573 	int		 len;
574 
575 	switch (n->type) {
576 	case ROFFT_HEAD:
577 		print_otag(h, TAG_TD, "");
578 		/* FALLTHROUGH */
579 	case ROFFT_ELEM:
580 		print_otag(h, TAG_B, "c", "Nm");
581 		return 1;
582 	case ROFFT_BODY:
583 		print_otag(h, TAG_TD, "");
584 		return 1;
585 	default:
586 		break;
587 	}
588 
589 	synopsis_pre(h, n);
590 	print_otag(h, TAG_TABLE, "c", "Nm");
591 
592 	for (len = 0, n = n->head->child; n; n = n->next)
593 		if (n->type == ROFFT_TEXT)
594 			len += html_strlen(n->string);
595 
596 	if (len == 0 && meta->name != NULL)
597 		len = html_strlen(meta->name);
598 
599 	t = print_otag(h, TAG_COLGROUP, "");
600 	print_otag(h, TAG_COL, "shw", len);
601 	print_otag(h, TAG_COL, "");
602 	print_tagq(h, t);
603 	print_otag(h, TAG_TR, "");
604 	return 1;
605 }
606 
607 static int
608 mdoc_xr_pre(MDOC_ARGS)
609 {
610 	if (NULL == n->child)
611 		return 0;
612 
613 	if (h->base_man)
614 		print_otag(h, TAG_A, "chM", "Xr",
615 		    n->child->string, n->child->next == NULL ?
616 		    NULL : n->child->next->string);
617 	else
618 		print_otag(h, TAG_A, "c", "Xr");
619 
620 	n = n->child;
621 	print_text(h, n->string);
622 
623 	if (NULL == (n = n->next))
624 		return 0;
625 
626 	h->flags |= HTML_NOSPACE;
627 	print_text(h, "(");
628 	h->flags |= HTML_NOSPACE;
629 	print_text(h, n->string);
630 	h->flags |= HTML_NOSPACE;
631 	print_text(h, ")");
632 	return 0;
633 }
634 
635 static int
636 mdoc_ns_pre(MDOC_ARGS)
637 {
638 
639 	if ( ! (NODE_LINE & n->flags))
640 		h->flags |= HTML_NOSPACE;
641 	return 1;
642 }
643 
644 static int
645 mdoc_ar_pre(MDOC_ARGS)
646 {
647 	print_otag(h, TAG_VAR, "c", "Ar");
648 	return 1;
649 }
650 
651 static int
652 mdoc_xx_pre(MDOC_ARGS)
653 {
654 	print_otag(h, TAG_SPAN, "c", "Ux");
655 	return 1;
656 }
657 
658 static int
659 mdoc_it_pre(MDOC_ARGS)
660 {
661 	const struct roff_node	*bl;
662 	struct tag		*t;
663 	const char		*cattr;
664 	enum mdoc_list		 type;
665 
666 	bl = n->parent;
667 	while (bl->tok != MDOC_Bl)
668 		bl = bl->parent;
669 	type = bl->norm->Bl.type;
670 
671 	switch (type) {
672 	case LIST_bullet:
673 		cattr = "It-bullet";
674 		break;
675 	case LIST_dash:
676 	case LIST_hyphen:
677 		cattr = "It-dash";
678 		break;
679 	case LIST_item:
680 		cattr = "It-item";
681 		break;
682 	case LIST_enum:
683 		cattr = "It-enum";
684 		break;
685 	case LIST_diag:
686 		cattr = "It-diag";
687 		break;
688 	case LIST_hang:
689 		cattr = "It-hang";
690 		break;
691 	case LIST_inset:
692 		cattr = "It-inset";
693 		break;
694 	case LIST_ohang:
695 		cattr = "It-ohang";
696 		break;
697 	case LIST_tag:
698 		cattr = "It-tag";
699 		break;
700 	case LIST_column:
701 		cattr = "It-column";
702 		break;
703 	default:
704 		break;
705 	}
706 
707 	switch (type) {
708 	case LIST_bullet:
709 	case LIST_dash:
710 	case LIST_hyphen:
711 	case LIST_item:
712 	case LIST_enum:
713 		switch (n->type) {
714 		case ROFFT_HEAD:
715 			return 0;
716 		case ROFFT_BODY:
717 			if (bl->norm->Bl.comp)
718 				print_otag(h, TAG_LI, "csvt", cattr, 0);
719 			else
720 				print_otag(h, TAG_LI, "c", cattr);
721 			break;
722 		default:
723 			break;
724 		}
725 		break;
726 	case LIST_diag:
727 	case LIST_hang:
728 	case LIST_inset:
729 	case LIST_ohang:
730 		switch (n->type) {
731 		case ROFFT_HEAD:
732 			if (bl->norm->Bl.comp)
733 				print_otag(h, TAG_DT, "csvt", cattr, 0);
734 			else
735 				print_otag(h, TAG_DT, "c", cattr);
736 			if (type == LIST_diag)
737 				print_otag(h, TAG_B, "c", cattr);
738 			break;
739 		case ROFFT_BODY:
740 			print_otag(h, TAG_DD, "cswl", cattr,
741 			    bl->norm->Bl.width);
742 			break;
743 		default:
744 			break;
745 		}
746 		break;
747 	case LIST_tag:
748 		switch (n->type) {
749 		case ROFFT_HEAD:
750 			if (h->style != NULL && !bl->norm->Bl.comp &&
751 			    (n->parent->prev == NULL ||
752 			     n->parent->prev->body == NULL ||
753 			     n->parent->prev->body->child != NULL)) {
754 				t = print_otag(h, TAG_DT, "csWl",
755 				    cattr, bl->norm->Bl.width);
756 				print_text(h, "\\ ");
757 				print_tagq(h, t);
758 				t = print_otag(h, TAG_DD, "c", cattr);
759 				print_text(h, "\\ ");
760 				print_tagq(h, t);
761 			}
762 			print_otag(h, TAG_DT, "csWl", cattr,
763 			    bl->norm->Bl.width);
764 			break;
765 		case ROFFT_BODY:
766 			if (n->child == NULL) {
767 				print_otag(h, TAG_DD, "css?", cattr,
768 				    "width", "auto");
769 				print_text(h, "\\ ");
770 			} else
771 				print_otag(h, TAG_DD, "c", cattr);
772 			break;
773 		default:
774 			break;
775 		}
776 		break;
777 	case LIST_column:
778 		switch (n->type) {
779 		case ROFFT_HEAD:
780 			break;
781 		case ROFFT_BODY:
782 			if (bl->norm->Bl.comp)
783 				print_otag(h, TAG_TD, "csvt", cattr, 0);
784 			else
785 				print_otag(h, TAG_TD, "c", cattr);
786 			break;
787 		default:
788 			print_otag(h, TAG_TR, "c", cattr);
789 		}
790 	default:
791 		break;
792 	}
793 
794 	return 1;
795 }
796 
797 static int
798 mdoc_bl_pre(MDOC_ARGS)
799 {
800 	struct tag	*t;
801 	struct mdoc_bl	*bl;
802 	const char	*cattr;
803 	size_t		 i;
804 	enum htmltag	 elemtype;
805 
806 	bl = &n->norm->Bl;
807 
808 	switch (n->type) {
809 	case ROFFT_BODY:
810 		return 1;
811 
812 	case ROFFT_HEAD:
813 		if (bl->type != LIST_column || bl->ncols == 0)
814 			return 0;
815 
816 		/*
817 		 * For each column, print out the <COL> tag with our
818 		 * suggested width.  The last column gets min-width, as
819 		 * in terminal mode it auto-sizes to the width of the
820 		 * screen and we want to preserve that behaviour.
821 		 */
822 
823 		t = print_otag(h, TAG_COLGROUP, "");
824 		for (i = 0; i < bl->ncols - 1; i++)
825 			print_otag(h, TAG_COL, "sww", bl->cols[i]);
826 		print_otag(h, TAG_COL, "swW", bl->cols[i]);
827 		print_tagq(h, t);
828 		return 0;
829 
830 	default:
831 		break;
832 	}
833 
834 	switch (bl->type) {
835 	case LIST_bullet:
836 		elemtype = TAG_UL;
837 		cattr = "Bl-bullet";
838 		break;
839 	case LIST_dash:
840 	case LIST_hyphen:
841 		elemtype = TAG_UL;
842 		cattr = "Bl-dash";
843 		break;
844 	case LIST_item:
845 		elemtype = TAG_UL;
846 		cattr = "Bl-item";
847 		break;
848 	case LIST_enum:
849 		elemtype = TAG_OL;
850 		cattr = "Bl-enum";
851 		break;
852 	case LIST_diag:
853 		elemtype = TAG_DL;
854 		cattr = "Bl-diag";
855 		break;
856 	case LIST_hang:
857 		elemtype = TAG_DL;
858 		cattr = "Bl-hang";
859 		break;
860 	case LIST_inset:
861 		elemtype = TAG_DL;
862 		cattr = "Bl-inset";
863 		break;
864 	case LIST_ohang:
865 		elemtype = TAG_DL;
866 		cattr = "Bl-ohang";
867 		break;
868 	case LIST_tag:
869 		cattr = "Bl-tag";
870 		if (bl->offs)
871 			print_otag(h, TAG_DIV, "cswl", cattr, bl->offs);
872 		print_otag(h, TAG_DL, "cswl", cattr, bl->width);
873 		return 1;
874 	case LIST_column:
875 		elemtype = TAG_TABLE;
876 		cattr = "Bl-column";
877 		break;
878 	default:
879 		abort();
880 	}
881 	print_otag(h, elemtype, "cswl", cattr, bl->offs);
882 	return 1;
883 }
884 
885 static int
886 mdoc_ex_pre(MDOC_ARGS)
887 {
888 	if (n->prev)
889 		print_otag(h, TAG_BR, "");
890 	return 1;
891 }
892 
893 static int
894 mdoc_st_pre(MDOC_ARGS)
895 {
896 	print_otag(h, TAG_SPAN, "c", "St");
897 	return 1;
898 }
899 
900 static int
901 mdoc_em_pre(MDOC_ARGS)
902 {
903 	print_otag(h, TAG_I, "c", "Em");
904 	return 1;
905 }
906 
907 static int
908 mdoc_d1_pre(MDOC_ARGS)
909 {
910 	if (n->type != ROFFT_BLOCK)
911 		return 1;
912 
913 	print_otag(h, TAG_DIV, "c", "D1");
914 
915 	if (n->tok == MDOC_Dl)
916 		print_otag(h, TAG_CODE, "c", "Li");
917 
918 	return 1;
919 }
920 
921 static int
922 mdoc_sx_pre(MDOC_ARGS)
923 {
924 	char	*id;
925 
926 	id = make_id(n);
927 	print_otag(h, TAG_A, "chR", "Sx", id);
928 	free(id);
929 	return 1;
930 }
931 
932 static int
933 mdoc_bd_pre(MDOC_ARGS)
934 {
935 	int			 comp, offs, sv;
936 	struct roff_node	*nn;
937 
938 	if (n->type == ROFFT_HEAD)
939 		return 0;
940 
941 	if (n->type == ROFFT_BLOCK) {
942 		comp = n->norm->Bd.comp;
943 		for (nn = n; nn && ! comp; nn = nn->parent) {
944 			if (nn->type != ROFFT_BLOCK)
945 				continue;
946 			if (MDOC_Ss == nn->tok || MDOC_Sh == nn->tok)
947 				comp = 1;
948 			if (nn->prev)
949 				break;
950 		}
951 		if ( ! comp)
952 			print_paragraph(h);
953 		return 1;
954 	}
955 
956 	/* Handle the -offset argument. */
957 
958 	if (n->norm->Bd.offs == NULL ||
959 	    ! strcmp(n->norm->Bd.offs, "left"))
960 		offs = 0;
961 	else if ( ! strcmp(n->norm->Bd.offs, "indent"))
962 		offs = INDENT;
963 	else if ( ! strcmp(n->norm->Bd.offs, "indent-two"))
964 		offs = INDENT * 2;
965 	else
966 		offs = -1;
967 
968 	if (offs == -1)
969 		print_otag(h, TAG_DIV, "cswl", "Bd", n->norm->Bd.offs);
970 	else
971 		print_otag(h, TAG_DIV, "cshl", "Bd", offs);
972 
973 	if (n->norm->Bd.type != DISP_unfilled &&
974 	    n->norm->Bd.type != DISP_literal)
975 		return 1;
976 
977 	print_otag(h, TAG_PRE, "c", "Li");
978 
979 	/* This can be recursive: save & set our literal state. */
980 
981 	sv = h->flags & HTML_LITERAL;
982 	h->flags |= HTML_LITERAL;
983 
984 	for (nn = n->child; nn; nn = nn->next) {
985 		print_mdoc_node(meta, nn, h);
986 		/*
987 		 * If the printed node flushes its own line, then we
988 		 * needn't do it here as well.  This is hacky, but the
989 		 * notion of selective eoln whitespace is pretty dumb
990 		 * anyway, so don't sweat it.
991 		 */
992 		switch (nn->tok) {
993 		case MDOC_Sm:
994 		case MDOC_br:
995 		case MDOC_sp:
996 		case MDOC_Bl:
997 		case MDOC_D1:
998 		case MDOC_Dl:
999 		case MDOC_Lp:
1000 		case MDOC_Pp:
1001 			continue;
1002 		default:
1003 			break;
1004 		}
1005 		if (h->flags & HTML_NONEWLINE ||
1006 		    (nn->next && ! (nn->next->flags & NODE_LINE)))
1007 			continue;
1008 		else if (nn->next)
1009 			print_text(h, "\n");
1010 
1011 		h->flags |= HTML_NOSPACE;
1012 	}
1013 
1014 	if (0 == sv)
1015 		h->flags &= ~HTML_LITERAL;
1016 
1017 	return 0;
1018 }
1019 
1020 static int
1021 mdoc_pa_pre(MDOC_ARGS)
1022 {
1023 	print_otag(h, TAG_I, "c", "Pa");
1024 	return 1;
1025 }
1026 
1027 static int
1028 mdoc_ad_pre(MDOC_ARGS)
1029 {
1030 	print_otag(h, TAG_I, "c", "Ad");
1031 	return 1;
1032 }
1033 
1034 static int
1035 mdoc_an_pre(MDOC_ARGS)
1036 {
1037 	if (n->norm->An.auth == AUTH_split) {
1038 		h->flags &= ~HTML_NOSPLIT;
1039 		h->flags |= HTML_SPLIT;
1040 		return 0;
1041 	}
1042 	if (n->norm->An.auth == AUTH_nosplit) {
1043 		h->flags &= ~HTML_SPLIT;
1044 		h->flags |= HTML_NOSPLIT;
1045 		return 0;
1046 	}
1047 
1048 	if (h->flags & HTML_SPLIT)
1049 		print_otag(h, TAG_BR, "");
1050 
1051 	if (n->sec == SEC_AUTHORS && ! (h->flags & HTML_NOSPLIT))
1052 		h->flags |= HTML_SPLIT;
1053 
1054 	print_otag(h, TAG_SPAN, "c", "An");
1055 	return 1;
1056 }
1057 
1058 static int
1059 mdoc_cd_pre(MDOC_ARGS)
1060 {
1061 	synopsis_pre(h, n);
1062 	print_otag(h, TAG_B, "c", "Cd");
1063 	return 1;
1064 }
1065 
1066 static int
1067 mdoc_dv_pre(MDOC_ARGS)
1068 {
1069 	print_otag(h, TAG_CODE, "c", "Dv");
1070 	return 1;
1071 }
1072 
1073 static int
1074 mdoc_ev_pre(MDOC_ARGS)
1075 {
1076 	print_otag(h, TAG_CODE, "c", "Ev");
1077 	return 1;
1078 }
1079 
1080 static int
1081 mdoc_er_pre(MDOC_ARGS)
1082 {
1083 	print_otag(h, TAG_CODE, "c", "Er");
1084 	return 1;
1085 }
1086 
1087 static int
1088 mdoc_fa_pre(MDOC_ARGS)
1089 {
1090 	const struct roff_node	*nn;
1091 	struct tag		*t;
1092 
1093 	if (n->parent->tok != MDOC_Fo) {
1094 		print_otag(h, TAG_VAR, "c", "Fa");
1095 		return 1;
1096 	}
1097 
1098 	for (nn = n->child; nn; nn = nn->next) {
1099 		t = print_otag(h, TAG_VAR, "c", "Fa");
1100 		print_text(h, nn->string);
1101 		print_tagq(h, t);
1102 		if (nn->next) {
1103 			h->flags |= HTML_NOSPACE;
1104 			print_text(h, ",");
1105 		}
1106 	}
1107 
1108 	if (n->child && n->next && n->next->tok == MDOC_Fa) {
1109 		h->flags |= HTML_NOSPACE;
1110 		print_text(h, ",");
1111 	}
1112 
1113 	return 0;
1114 }
1115 
1116 static int
1117 mdoc_fd_pre(MDOC_ARGS)
1118 {
1119 	struct tag	*t;
1120 	char		*buf, *cp;
1121 
1122 	synopsis_pre(h, n);
1123 
1124 	if (NULL == (n = n->child))
1125 		return 0;
1126 
1127 	assert(n->type == ROFFT_TEXT);
1128 
1129 	if (strcmp(n->string, "#include")) {
1130 		print_otag(h, TAG_B, "c", "Fd");
1131 		return 1;
1132 	}
1133 
1134 	print_otag(h, TAG_B, "c", "In");
1135 	print_text(h, n->string);
1136 
1137 	if (NULL != (n = n->next)) {
1138 		assert(n->type == ROFFT_TEXT);
1139 
1140 		if (h->base_includes) {
1141 			cp = n->string;
1142 			if (*cp == '<' || *cp == '"')
1143 				cp++;
1144 			buf = mandoc_strdup(cp);
1145 			cp = strchr(buf, '\0') - 1;
1146 			if (cp >= buf && (*cp == '>' || *cp == '"'))
1147 				*cp = '\0';
1148 			t = print_otag(h, TAG_A, "chI", "In", buf);
1149 			free(buf);
1150 		} else
1151 			t = print_otag(h, TAG_A, "c", "In");
1152 
1153 		print_text(h, n->string);
1154 		print_tagq(h, t);
1155 
1156 		n = n->next;
1157 	}
1158 
1159 	for ( ; n; n = n->next) {
1160 		assert(n->type == ROFFT_TEXT);
1161 		print_text(h, n->string);
1162 	}
1163 
1164 	return 0;
1165 }
1166 
1167 static int
1168 mdoc_vt_pre(MDOC_ARGS)
1169 {
1170 	if (n->type == ROFFT_BLOCK) {
1171 		synopsis_pre(h, n);
1172 		return 1;
1173 	} else if (n->type == ROFFT_ELEM) {
1174 		synopsis_pre(h, n);
1175 	} else if (n->type == ROFFT_HEAD)
1176 		return 0;
1177 
1178 	print_otag(h, TAG_VAR, "c", "Vt");
1179 	return 1;
1180 }
1181 
1182 static int
1183 mdoc_ft_pre(MDOC_ARGS)
1184 {
1185 	synopsis_pre(h, n);
1186 	print_otag(h, TAG_VAR, "c", "Ft");
1187 	return 1;
1188 }
1189 
1190 static int
1191 mdoc_fn_pre(MDOC_ARGS)
1192 {
1193 	struct tag	*t;
1194 	char		 nbuf[BUFSIZ];
1195 	const char	*sp, *ep;
1196 	int		 sz, pretty;
1197 
1198 	pretty = NODE_SYNPRETTY & n->flags;
1199 	synopsis_pre(h, n);
1200 
1201 	/* Split apart into type and name. */
1202 	assert(n->child->string);
1203 	sp = n->child->string;
1204 
1205 	ep = strchr(sp, ' ');
1206 	if (NULL != ep) {
1207 		t = print_otag(h, TAG_VAR, "c", "Ft");
1208 
1209 		while (ep) {
1210 			sz = MIN((int)(ep - sp), BUFSIZ - 1);
1211 			(void)memcpy(nbuf, sp, (size_t)sz);
1212 			nbuf[sz] = '\0';
1213 			print_text(h, nbuf);
1214 			sp = ++ep;
1215 			ep = strchr(sp, ' ');
1216 		}
1217 		print_tagq(h, t);
1218 	}
1219 
1220 	t = print_otag(h, TAG_B, "c", "Fn");
1221 
1222 	if (sp)
1223 		print_text(h, sp);
1224 
1225 	print_tagq(h, t);
1226 
1227 	h->flags |= HTML_NOSPACE;
1228 	print_text(h, "(");
1229 	h->flags |= HTML_NOSPACE;
1230 
1231 	for (n = n->child->next; n; n = n->next) {
1232 		if (NODE_SYNPRETTY & n->flags)
1233 			t = print_otag(h, TAG_VAR, "css?", "Fa",
1234 			    "white-space", "nowrap");
1235 		else
1236 			t = print_otag(h, TAG_VAR, "c", "Fa");
1237 		print_text(h, n->string);
1238 		print_tagq(h, t);
1239 		if (n->next) {
1240 			h->flags |= HTML_NOSPACE;
1241 			print_text(h, ",");
1242 		}
1243 	}
1244 
1245 	h->flags |= HTML_NOSPACE;
1246 	print_text(h, ")");
1247 
1248 	if (pretty) {
1249 		h->flags |= HTML_NOSPACE;
1250 		print_text(h, ";");
1251 	}
1252 
1253 	return 0;
1254 }
1255 
1256 static int
1257 mdoc_sm_pre(MDOC_ARGS)
1258 {
1259 
1260 	if (NULL == n->child)
1261 		h->flags ^= HTML_NONOSPACE;
1262 	else if (0 == strcmp("on", n->child->string))
1263 		h->flags &= ~HTML_NONOSPACE;
1264 	else
1265 		h->flags |= HTML_NONOSPACE;
1266 
1267 	if ( ! (HTML_NONOSPACE & h->flags))
1268 		h->flags &= ~HTML_NOSPACE;
1269 
1270 	return 0;
1271 }
1272 
1273 static int
1274 mdoc_skip_pre(MDOC_ARGS)
1275 {
1276 
1277 	return 0;
1278 }
1279 
1280 static int
1281 mdoc_pp_pre(MDOC_ARGS)
1282 {
1283 
1284 	print_paragraph(h);
1285 	return 0;
1286 }
1287 
1288 static int
1289 mdoc_sp_pre(MDOC_ARGS)
1290 {
1291 	struct roffsu	 su;
1292 
1293 	SCALE_VS_INIT(&su, 1);
1294 
1295 	if (MDOC_sp == n->tok) {
1296 		if (NULL != (n = n->child)) {
1297 			if ( ! a2roffsu(n->string, &su, SCALE_VS))
1298 				su.scale = 1.0;
1299 			else if (su.scale < 0.0)
1300 				su.scale = 0.0;
1301 		}
1302 	} else
1303 		su.scale = 0.0;
1304 
1305 	print_otag(h, TAG_DIV, "suh", &su);
1306 
1307 	/* So the div isn't empty: */
1308 	print_text(h, "\\~");
1309 
1310 	return 0;
1311 
1312 }
1313 
1314 static int
1315 mdoc_lk_pre(MDOC_ARGS)
1316 {
1317 	if (NULL == (n = n->child))
1318 		return 0;
1319 
1320 	assert(n->type == ROFFT_TEXT);
1321 
1322 	print_otag(h, TAG_A, "ch", "Lk", n->string);
1323 
1324 	if (NULL == n->next)
1325 		print_text(h, n->string);
1326 
1327 	for (n = n->next; n; n = n->next)
1328 		print_text(h, n->string);
1329 
1330 	return 0;
1331 }
1332 
1333 static int
1334 mdoc_mt_pre(MDOC_ARGS)
1335 {
1336 	struct tag	*t;
1337 	char		*cp;
1338 
1339 	for (n = n->child; n; n = n->next) {
1340 		assert(n->type == ROFFT_TEXT);
1341 
1342 		mandoc_asprintf(&cp, "mailto:%s", n->string);
1343 		t = print_otag(h, TAG_A, "ch", "Mt", cp);
1344 		print_text(h, n->string);
1345 		print_tagq(h, t);
1346 		free(cp);
1347 	}
1348 
1349 	return 0;
1350 }
1351 
1352 static int
1353 mdoc_fo_pre(MDOC_ARGS)
1354 {
1355 	struct tag	*t;
1356 
1357 	if (n->type == ROFFT_BODY) {
1358 		h->flags |= HTML_NOSPACE;
1359 		print_text(h, "(");
1360 		h->flags |= HTML_NOSPACE;
1361 		return 1;
1362 	} else if (n->type == ROFFT_BLOCK) {
1363 		synopsis_pre(h, n);
1364 		return 1;
1365 	}
1366 
1367 	if (n->child == NULL)
1368 		return 0;
1369 
1370 	assert(n->child->string);
1371 	t = print_otag(h, TAG_B, "c", "Fn");
1372 	print_text(h, n->child->string);
1373 	print_tagq(h, t);
1374 	return 0;
1375 }
1376 
1377 static void
1378 mdoc_fo_post(MDOC_ARGS)
1379 {
1380 
1381 	if (n->type != ROFFT_BODY)
1382 		return;
1383 	h->flags |= HTML_NOSPACE;
1384 	print_text(h, ")");
1385 	h->flags |= HTML_NOSPACE;
1386 	print_text(h, ";");
1387 }
1388 
1389 static int
1390 mdoc_in_pre(MDOC_ARGS)
1391 {
1392 	struct tag	*t;
1393 
1394 	synopsis_pre(h, n);
1395 	print_otag(h, TAG_B, "c", "In");
1396 
1397 	/*
1398 	 * The first argument of the `In' gets special treatment as
1399 	 * being a linked value.  Subsequent values are printed
1400 	 * afterward.  groff does similarly.  This also handles the case
1401 	 * of no children.
1402 	 */
1403 
1404 	if (NODE_SYNPRETTY & n->flags && NODE_LINE & n->flags)
1405 		print_text(h, "#include");
1406 
1407 	print_text(h, "<");
1408 	h->flags |= HTML_NOSPACE;
1409 
1410 	if (NULL != (n = n->child)) {
1411 		assert(n->type == ROFFT_TEXT);
1412 
1413 		if (h->base_includes)
1414 			t = print_otag(h, TAG_A, "chI", "In", n->string);
1415 		else
1416 			t = print_otag(h, TAG_A, "c", "In");
1417 		print_text(h, n->string);
1418 		print_tagq(h, t);
1419 
1420 		n = n->next;
1421 	}
1422 
1423 	h->flags |= HTML_NOSPACE;
1424 	print_text(h, ">");
1425 
1426 	for ( ; n; n = n->next) {
1427 		assert(n->type == ROFFT_TEXT);
1428 		print_text(h, n->string);
1429 	}
1430 
1431 	return 0;
1432 }
1433 
1434 static int
1435 mdoc_ic_pre(MDOC_ARGS)
1436 {
1437 	print_otag(h, TAG_B, "c", "Ic");
1438 	return 1;
1439 }
1440 
1441 static int
1442 mdoc_va_pre(MDOC_ARGS)
1443 {
1444 	print_otag(h, TAG_VAR, "c", "Va");
1445 	return 1;
1446 }
1447 
1448 static int
1449 mdoc_ap_pre(MDOC_ARGS)
1450 {
1451 
1452 	h->flags |= HTML_NOSPACE;
1453 	print_text(h, "\\(aq");
1454 	h->flags |= HTML_NOSPACE;
1455 	return 1;
1456 }
1457 
1458 static int
1459 mdoc_bf_pre(MDOC_ARGS)
1460 {
1461 	const char	*cattr;
1462 
1463 	if (n->type == ROFFT_HEAD)
1464 		return 0;
1465 	else if (n->type != ROFFT_BODY)
1466 		return 1;
1467 
1468 	if (FONT_Em == n->norm->Bf.font)
1469 		cattr = "Em";
1470 	else if (FONT_Sy == n->norm->Bf.font)
1471 		cattr = "Sy";
1472 	else if (FONT_Li == n->norm->Bf.font)
1473 		cattr = "Li";
1474 	else
1475 		cattr = "No";
1476 
1477 	/*
1478 	 * We want this to be inline-formatted, but needs to be div to
1479 	 * accept block children.
1480 	 */
1481 
1482 	print_otag(h, TAG_DIV, "css?hl", cattr, "display", "inline", 1);
1483 	return 1;
1484 }
1485 
1486 static int
1487 mdoc_ms_pre(MDOC_ARGS)
1488 {
1489 	print_otag(h, TAG_B, "c", "Ms");
1490 	return 1;
1491 }
1492 
1493 static int
1494 mdoc_igndelim_pre(MDOC_ARGS)
1495 {
1496 
1497 	h->flags |= HTML_IGNDELIM;
1498 	return 1;
1499 }
1500 
1501 static void
1502 mdoc_pf_post(MDOC_ARGS)
1503 {
1504 
1505 	if ( ! (n->next == NULL || n->next->flags & NODE_LINE))
1506 		h->flags |= HTML_NOSPACE;
1507 }
1508 
1509 static int
1510 mdoc_rs_pre(MDOC_ARGS)
1511 {
1512 	if (n->type != ROFFT_BLOCK)
1513 		return 1;
1514 
1515 	if (n->prev && SEC_SEE_ALSO == n->sec)
1516 		print_paragraph(h);
1517 
1518 	print_otag(h, TAG_CITE, "c", "Rs");
1519 	return 1;
1520 }
1521 
1522 static int
1523 mdoc_no_pre(MDOC_ARGS)
1524 {
1525 	print_otag(h, TAG_SPAN, "c", "No");
1526 	return 1;
1527 }
1528 
1529 static int
1530 mdoc_li_pre(MDOC_ARGS)
1531 {
1532 	print_otag(h, TAG_CODE, "c", "Li");
1533 	return 1;
1534 }
1535 
1536 static int
1537 mdoc_sy_pre(MDOC_ARGS)
1538 {
1539 	print_otag(h, TAG_B, "c", "Sy");
1540 	return 1;
1541 }
1542 
1543 static int
1544 mdoc_lb_pre(MDOC_ARGS)
1545 {
1546 	if (SEC_LIBRARY == n->sec && NODE_LINE & n->flags && n->prev)
1547 		print_otag(h, TAG_BR, "");
1548 
1549 	print_otag(h, TAG_SPAN, "c", "Lb");
1550 	return 1;
1551 }
1552 
1553 static int
1554 mdoc__x_pre(MDOC_ARGS)
1555 {
1556 	const char	*cattr;
1557 	enum htmltag	 t;
1558 
1559 	t = TAG_SPAN;
1560 
1561 	switch (n->tok) {
1562 	case MDOC__A:
1563 		cattr = "RsA";
1564 		if (n->prev && MDOC__A == n->prev->tok)
1565 			if (NULL == n->next || MDOC__A != n->next->tok)
1566 				print_text(h, "and");
1567 		break;
1568 	case MDOC__B:
1569 		t = TAG_I;
1570 		cattr = "RsB";
1571 		break;
1572 	case MDOC__C:
1573 		cattr = "RsC";
1574 		break;
1575 	case MDOC__D:
1576 		cattr = "RsD";
1577 		break;
1578 	case MDOC__I:
1579 		t = TAG_I;
1580 		cattr = "RsI";
1581 		break;
1582 	case MDOC__J:
1583 		t = TAG_I;
1584 		cattr = "RsJ";
1585 		break;
1586 	case MDOC__N:
1587 		cattr = "RsN";
1588 		break;
1589 	case MDOC__O:
1590 		cattr = "RsO";
1591 		break;
1592 	case MDOC__P:
1593 		cattr = "RsP";
1594 		break;
1595 	case MDOC__Q:
1596 		cattr = "RsQ";
1597 		break;
1598 	case MDOC__R:
1599 		cattr = "RsR";
1600 		break;
1601 	case MDOC__T:
1602 		cattr = "RsT";
1603 		break;
1604 	case MDOC__U:
1605 		print_otag(h, TAG_A, "ch", "RsU", n->child->string);
1606 		return 1;
1607 	case MDOC__V:
1608 		cattr = "RsV";
1609 		break;
1610 	default:
1611 		abort();
1612 	}
1613 
1614 	print_otag(h, t, "c", cattr);
1615 	return 1;
1616 }
1617 
1618 static void
1619 mdoc__x_post(MDOC_ARGS)
1620 {
1621 
1622 	if (MDOC__A == n->tok && n->next && MDOC__A == n->next->tok)
1623 		if (NULL == n->next->next || MDOC__A != n->next->next->tok)
1624 			if (NULL == n->prev || MDOC__A != n->prev->tok)
1625 				return;
1626 
1627 	/* TODO: %U */
1628 
1629 	if (NULL == n->parent || MDOC_Rs != n->parent->tok)
1630 		return;
1631 
1632 	h->flags |= HTML_NOSPACE;
1633 	print_text(h, n->next ? "," : ".");
1634 }
1635 
1636 static int
1637 mdoc_bk_pre(MDOC_ARGS)
1638 {
1639 
1640 	switch (n->type) {
1641 	case ROFFT_BLOCK:
1642 		break;
1643 	case ROFFT_HEAD:
1644 		return 0;
1645 	case ROFFT_BODY:
1646 		if (n->parent->args != NULL || n->prev->child == NULL)
1647 			h->flags |= HTML_PREKEEP;
1648 		break;
1649 	default:
1650 		abort();
1651 	}
1652 
1653 	return 1;
1654 }
1655 
1656 static void
1657 mdoc_bk_post(MDOC_ARGS)
1658 {
1659 
1660 	if (n->type == ROFFT_BODY)
1661 		h->flags &= ~(HTML_KEEP | HTML_PREKEEP);
1662 }
1663 
1664 static int
1665 mdoc_quote_pre(MDOC_ARGS)
1666 {
1667 	if (n->type != ROFFT_BODY)
1668 		return 1;
1669 
1670 	switch (n->tok) {
1671 	case MDOC_Ao:
1672 	case MDOC_Aq:
1673 		print_text(h, n->child != NULL && n->child->next == NULL &&
1674 		    n->child->tok == MDOC_Mt ?  "<" : "\\(la");
1675 		break;
1676 	case MDOC_Bro:
1677 	case MDOC_Brq:
1678 		print_text(h, "\\(lC");
1679 		break;
1680 	case MDOC_Bo:
1681 	case MDOC_Bq:
1682 		print_text(h, "\\(lB");
1683 		break;
1684 	case MDOC_Oo:
1685 	case MDOC_Op:
1686 		print_text(h, "\\(lB");
1687 		h->flags |= HTML_NOSPACE;
1688 		print_otag(h, TAG_SPAN, "c", "Op");
1689 		break;
1690 	case MDOC_En:
1691 		if (NULL == n->norm->Es ||
1692 		    NULL == n->norm->Es->child)
1693 			return 1;
1694 		print_text(h, n->norm->Es->child->string);
1695 		break;
1696 	case MDOC_Do:
1697 	case MDOC_Dq:
1698 	case MDOC_Qo:
1699 	case MDOC_Qq:
1700 		print_text(h, "\\(lq");
1701 		break;
1702 	case MDOC_Po:
1703 	case MDOC_Pq:
1704 		print_text(h, "(");
1705 		break;
1706 	case MDOC_Ql:
1707 		print_text(h, "\\(oq");
1708 		h->flags |= HTML_NOSPACE;
1709 		print_otag(h, TAG_CODE, "c", "Li");
1710 		break;
1711 	case MDOC_So:
1712 	case MDOC_Sq:
1713 		print_text(h, "\\(oq");
1714 		break;
1715 	default:
1716 		abort();
1717 	}
1718 
1719 	h->flags |= HTML_NOSPACE;
1720 	return 1;
1721 }
1722 
1723 static void
1724 mdoc_quote_post(MDOC_ARGS)
1725 {
1726 
1727 	if (n->type != ROFFT_BODY && n->type != ROFFT_ELEM)
1728 		return;
1729 
1730 	h->flags |= HTML_NOSPACE;
1731 
1732 	switch (n->tok) {
1733 	case MDOC_Ao:
1734 	case MDOC_Aq:
1735 		print_text(h, n->child != NULL && n->child->next == NULL &&
1736 		    n->child->tok == MDOC_Mt ?  ">" : "\\(ra");
1737 		break;
1738 	case MDOC_Bro:
1739 	case MDOC_Brq:
1740 		print_text(h, "\\(rC");
1741 		break;
1742 	case MDOC_Oo:
1743 	case MDOC_Op:
1744 	case MDOC_Bo:
1745 	case MDOC_Bq:
1746 		print_text(h, "\\(rB");
1747 		break;
1748 	case MDOC_En:
1749 		if (n->norm->Es == NULL ||
1750 		    n->norm->Es->child == NULL ||
1751 		    n->norm->Es->child->next == NULL)
1752 			h->flags &= ~HTML_NOSPACE;
1753 		else
1754 			print_text(h, n->norm->Es->child->next->string);
1755 		break;
1756 	case MDOC_Qo:
1757 	case MDOC_Qq:
1758 	case MDOC_Do:
1759 	case MDOC_Dq:
1760 		print_text(h, "\\(rq");
1761 		break;
1762 	case MDOC_Po:
1763 	case MDOC_Pq:
1764 		print_text(h, ")");
1765 		break;
1766 	case MDOC_Ql:
1767 	case MDOC_So:
1768 	case MDOC_Sq:
1769 		print_text(h, "\\(cq");
1770 		break;
1771 	default:
1772 		abort();
1773 	}
1774 }
1775 
1776 static int
1777 mdoc_eo_pre(MDOC_ARGS)
1778 {
1779 
1780 	if (n->type != ROFFT_BODY)
1781 		return 1;
1782 
1783 	if (n->end == ENDBODY_NOT &&
1784 	    n->parent->head->child == NULL &&
1785 	    n->child != NULL &&
1786 	    n->child->end != ENDBODY_NOT)
1787 		print_text(h, "\\&");
1788 	else if (n->end != ENDBODY_NOT ? n->child != NULL :
1789 	    n->parent->head->child != NULL && (n->child != NULL ||
1790 	    (n->parent->tail != NULL && n->parent->tail->child != NULL)))
1791 		h->flags |= HTML_NOSPACE;
1792 	return 1;
1793 }
1794 
1795 static void
1796 mdoc_eo_post(MDOC_ARGS)
1797 {
1798 	int	 body, tail;
1799 
1800 	if (n->type != ROFFT_BODY)
1801 		return;
1802 
1803 	if (n->end != ENDBODY_NOT) {
1804 		h->flags &= ~HTML_NOSPACE;
1805 		return;
1806 	}
1807 
1808 	body = n->child != NULL || n->parent->head->child != NULL;
1809 	tail = n->parent->tail != NULL && n->parent->tail->child != NULL;
1810 
1811 	if (body && tail)
1812 		h->flags |= HTML_NOSPACE;
1813 	else if ( ! tail)
1814 		h->flags &= ~HTML_NOSPACE;
1815 }
1816