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