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