xref: /openbsd-src/usr.bin/mandoc/mdoc_html.c (revision d4c5fc9dc00f5a9cadd8c2de4e52d85d3c1c6003)
1 /*	$OpenBSD: mdoc_html.c,v 1.179 2018/05/25 20:23:39 schwarze Exp $ */
2 /*
3  * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
4  * Copyright (c) 2014,2015,2016,2017,2018 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, 1);
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, 1);
515 		print_otag(h, TAG_H1, "cTi", "Sh", id);
516 		if (id != NULL)
517 			print_otag(h, TAG_A, "chR", "permalink", id);
518 		break;
519 	case ROFFT_BODY:
520 		if (n->sec == SEC_AUTHORS)
521 			h->flags &= ~(HTML_SPLIT|HTML_NOSPLIT);
522 		break;
523 	default:
524 		break;
525 	}
526 	return 1;
527 }
528 
529 static int
530 mdoc_ss_pre(MDOC_ARGS)
531 {
532 	char	*id;
533 
534 	if (n->type != ROFFT_HEAD)
535 		return 1;
536 
537 	id = html_make_id(n, 1);
538 	print_otag(h, TAG_H2, "cTi", "Ss", id);
539 	if (id != NULL)
540 		print_otag(h, TAG_A, "chR", "permalink", id);
541 	return 1;
542 }
543 
544 static int
545 mdoc_fl_pre(MDOC_ARGS)
546 {
547 	char	*id;
548 
549 	if ((id = cond_id(n)) != NULL)
550 		print_otag(h, TAG_A, "chR", "permalink", id);
551 	print_otag(h, TAG_CODE, "cTi", "Fl", id);
552 
553 	print_text(h, "\\-");
554 	if (!(n->child == NULL &&
555 	    (n->next == NULL ||
556 	     n->next->type == ROFFT_TEXT ||
557 	     n->next->flags & NODE_LINE)))
558 		h->flags |= HTML_NOSPACE;
559 
560 	return 1;
561 }
562 
563 static int
564 mdoc_cm_pre(MDOC_ARGS)
565 {
566 	char	*id;
567 
568 	if ((id = cond_id(n)) != NULL)
569 		print_otag(h, TAG_A, "chR", "permalink", id);
570 	print_otag(h, TAG_CODE, "cTi", "Cm", id);
571 	return 1;
572 }
573 
574 static int
575 mdoc_nd_pre(MDOC_ARGS)
576 {
577 	if (n->type != ROFFT_BODY)
578 		return 1;
579 
580 	print_text(h, "\\(em");
581 	/* Cannot use TAG_SPAN because it may contain blocks. */
582 	print_otag(h, TAG_DIV, "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_CODE, "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 	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 	case LIST_dash:
674 	case LIST_hyphen:
675 	case LIST_item:
676 	case LIST_enum:
677 		switch (n->type) {
678 		case ROFFT_HEAD:
679 			return 0;
680 		case ROFFT_BODY:
681 			print_otag(h, TAG_LI, "");
682 			break;
683 		default:
684 			break;
685 		}
686 		break;
687 	case LIST_diag:
688 	case LIST_hang:
689 	case LIST_inset:
690 	case LIST_ohang:
691 		switch (n->type) {
692 		case ROFFT_HEAD:
693 			print_otag(h, TAG_DT, "");
694 			break;
695 		case ROFFT_BODY:
696 			print_otag(h, TAG_DD, "sw*+l", bl->norm->Bl.width);
697 			break;
698 		default:
699 			break;
700 		}
701 		break;
702 	case LIST_tag:
703 		switch (n->type) {
704 		case ROFFT_HEAD:
705 			if (h->style != NULL && !bl->norm->Bl.comp &&
706 			    (n->parent->prev == NULL ||
707 			     n->parent->prev->body == NULL ||
708 			     n->parent->prev->body->child != NULL)) {
709 				t = print_otag(h, TAG_DT, "sw*+-l",
710 				    bl->norm->Bl.width);
711 				print_text(h, "\\ ");
712 				print_tagq(h, t);
713 				t = print_otag(h, TAG_DD, "");
714 				print_text(h, "\\ ");
715 				print_tagq(h, t);
716 			}
717 			print_otag(h, TAG_DT, "sw*+-l", bl->norm->Bl.width);
718 			break;
719 		case ROFFT_BODY:
720 			if (n->child == NULL) {
721 				print_otag(h, TAG_DD, "ss?",
722 				    "width", "auto");
723 				print_text(h, "\\ ");
724 			} else
725 				print_otag(h, TAG_DD, "");
726 			break;
727 		default:
728 			break;
729 		}
730 		break;
731 	case LIST_column:
732 		switch (n->type) {
733 		case ROFFT_HEAD:
734 			break;
735 		case ROFFT_BODY:
736 			print_otag(h, TAG_TD, "");
737 			break;
738 		default:
739 			print_otag(h, TAG_TR, "");
740 		}
741 	default:
742 		break;
743 	}
744 
745 	return 1;
746 }
747 
748 static int
749 mdoc_bl_pre(MDOC_ARGS)
750 {
751 	char		 cattr[21];
752 	struct tag	*t;
753 	struct mdoc_bl	*bl;
754 	size_t		 i;
755 	enum htmltag	 elemtype;
756 
757 	bl = &n->norm->Bl;
758 
759 	switch (n->type) {
760 	case ROFFT_BODY:
761 		return 1;
762 
763 	case ROFFT_HEAD:
764 		if (bl->type != LIST_column || bl->ncols == 0)
765 			return 0;
766 
767 		/*
768 		 * For each column, print out the <COL> tag with our
769 		 * suggested width.  The last column gets min-width, as
770 		 * in terminal mode it auto-sizes to the width of the
771 		 * screen and we want to preserve that behaviour.
772 		 */
773 
774 		t = print_otag(h, TAG_COLGROUP, "");
775 		for (i = 0; i < bl->ncols - 1; i++)
776 			print_otag(h, TAG_COL, "sw+w", bl->cols[i]);
777 		print_otag(h, TAG_COL, "swW", bl->cols[i]);
778 		print_tagq(h, t);
779 		return 0;
780 
781 	default:
782 		break;
783 	}
784 
785 	switch (bl->type) {
786 	case LIST_bullet:
787 		elemtype = TAG_UL;
788 		(void)strlcpy(cattr, "Bl-bullet", sizeof(cattr));
789 		break;
790 	case LIST_dash:
791 	case LIST_hyphen:
792 		elemtype = TAG_UL;
793 		(void)strlcpy(cattr, "Bl-dash", sizeof(cattr));
794 		break;
795 	case LIST_item:
796 		elemtype = TAG_UL;
797 		(void)strlcpy(cattr, "Bl-item", sizeof(cattr));
798 		break;
799 	case LIST_enum:
800 		elemtype = TAG_OL;
801 		(void)strlcpy(cattr, "Bl-enum", sizeof(cattr));
802 		break;
803 	case LIST_diag:
804 		elemtype = TAG_DL;
805 		(void)strlcpy(cattr, "Bl-diag", sizeof(cattr));
806 		break;
807 	case LIST_hang:
808 		elemtype = TAG_DL;
809 		(void)strlcpy(cattr, "Bl-hang", sizeof(cattr));
810 		break;
811 	case LIST_inset:
812 		elemtype = TAG_DL;
813 		(void)strlcpy(cattr, "Bl-inset", sizeof(cattr));
814 		break;
815 	case LIST_ohang:
816 		elemtype = TAG_DL;
817 		(void)strlcpy(cattr, "Bl-ohang", sizeof(cattr));
818 		break;
819 	case LIST_tag:
820 		if (bl->offs)
821 			print_otag(h, TAG_DIV, "cswl", "Bl-tag", bl->offs);
822 		print_otag(h, TAG_DL, "csw*+l", bl->comp ?
823 		    "Bl-tag Bl-compact" : "Bl-tag", bl->width);
824 		return 1;
825 	case LIST_column:
826 		elemtype = TAG_TABLE;
827 		(void)strlcpy(cattr, "Bl-column", sizeof(cattr));
828 		break;
829 	default:
830 		abort();
831 	}
832 	if (bl->comp)
833 		(void)strlcat(cattr, " Bl-compact", sizeof(cattr));
834 	print_otag(h, elemtype, "cswl", cattr, bl->offs);
835 	return 1;
836 }
837 
838 static int
839 mdoc_ex_pre(MDOC_ARGS)
840 {
841 	if (n->prev)
842 		print_otag(h, TAG_BR, "");
843 	return 1;
844 }
845 
846 static int
847 mdoc_st_pre(MDOC_ARGS)
848 {
849 	print_otag(h, TAG_SPAN, "cT", "St");
850 	return 1;
851 }
852 
853 static int
854 mdoc_em_pre(MDOC_ARGS)
855 {
856 	print_otag(h, TAG_I, "cT", "Em");
857 	return 1;
858 }
859 
860 static int
861 mdoc_d1_pre(MDOC_ARGS)
862 {
863 	if (n->type != ROFFT_BLOCK)
864 		return 1;
865 
866 	print_otag(h, TAG_DIV, "c", "D1");
867 
868 	if (n->tok == MDOC_Dl)
869 		print_otag(h, TAG_CODE, "c", "Li");
870 
871 	return 1;
872 }
873 
874 static int
875 mdoc_sx_pre(MDOC_ARGS)
876 {
877 	char	*id;
878 
879 	id = html_make_id(n, 0);
880 	print_otag(h, TAG_A, "cThR", "Sx", id);
881 	free(id);
882 	return 1;
883 }
884 
885 static int
886 mdoc_bd_pre(MDOC_ARGS)
887 {
888 	int			 comp, offs, sv;
889 	struct roff_node	*nn;
890 
891 	if (n->type == ROFFT_HEAD)
892 		return 0;
893 
894 	if (n->type == ROFFT_BLOCK) {
895 		comp = n->norm->Bd.comp;
896 		for (nn = n; nn && ! comp; nn = nn->parent) {
897 			if (nn->type != ROFFT_BLOCK)
898 				continue;
899 			if (MDOC_Ss == nn->tok || MDOC_Sh == nn->tok)
900 				comp = 1;
901 			if (nn->prev)
902 				break;
903 		}
904 		if ( ! comp)
905 			print_paragraph(h);
906 		return 1;
907 	}
908 
909 	/* Handle the -offset argument. */
910 
911 	if (n->norm->Bd.offs == NULL ||
912 	    ! strcmp(n->norm->Bd.offs, "left"))
913 		offs = 0;
914 	else if ( ! strcmp(n->norm->Bd.offs, "indent"))
915 		offs = INDENT;
916 	else if ( ! strcmp(n->norm->Bd.offs, "indent-two"))
917 		offs = INDENT * 2;
918 	else
919 		offs = -1;
920 
921 	if (offs == -1)
922 		print_otag(h, TAG_DIV, "cswl", "Bd", n->norm->Bd.offs);
923 	else
924 		print_otag(h, TAG_DIV, "cshl", "Bd", offs);
925 
926 	if (n->norm->Bd.type != DISP_unfilled &&
927 	    n->norm->Bd.type != DISP_literal)
928 		return 1;
929 
930 	print_otag(h, TAG_PRE, "c", "Li");
931 
932 	/* This can be recursive: save & set our literal state. */
933 
934 	sv = h->flags & HTML_LITERAL;
935 	h->flags |= HTML_LITERAL;
936 
937 	for (nn = n->child; nn; nn = nn->next) {
938 		print_mdoc_node(meta, nn, h);
939 		/*
940 		 * If the printed node flushes its own line, then we
941 		 * needn't do it here as well.  This is hacky, but the
942 		 * notion of selective eoln whitespace is pretty dumb
943 		 * anyway, so don't sweat it.
944 		 */
945 		switch (nn->tok) {
946 		case ROFF_br:
947 		case ROFF_sp:
948 		case MDOC_Sm:
949 		case MDOC_Bl:
950 		case MDOC_D1:
951 		case MDOC_Dl:
952 		case MDOC_Lp:
953 		case MDOC_Pp:
954 			continue;
955 		default:
956 			break;
957 		}
958 		if (h->flags & HTML_NONEWLINE ||
959 		    (nn->next && ! (nn->next->flags & NODE_LINE)))
960 			continue;
961 		else if (nn->next)
962 			print_text(h, "\n");
963 
964 		h->flags |= HTML_NOSPACE;
965 	}
966 
967 	if (0 == sv)
968 		h->flags &= ~HTML_LITERAL;
969 
970 	return 0;
971 }
972 
973 static int
974 mdoc_pa_pre(MDOC_ARGS)
975 {
976 	print_otag(h, TAG_SPAN, "cT", "Pa");
977 	return 1;
978 }
979 
980 static int
981 mdoc_ad_pre(MDOC_ARGS)
982 {
983 	print_otag(h, TAG_SPAN, "c", "Ad");
984 	return 1;
985 }
986 
987 static int
988 mdoc_an_pre(MDOC_ARGS)
989 {
990 	if (n->norm->An.auth == AUTH_split) {
991 		h->flags &= ~HTML_NOSPLIT;
992 		h->flags |= HTML_SPLIT;
993 		return 0;
994 	}
995 	if (n->norm->An.auth == AUTH_nosplit) {
996 		h->flags &= ~HTML_SPLIT;
997 		h->flags |= HTML_NOSPLIT;
998 		return 0;
999 	}
1000 
1001 	if (h->flags & HTML_SPLIT)
1002 		print_otag(h, TAG_BR, "");
1003 
1004 	if (n->sec == SEC_AUTHORS && ! (h->flags & HTML_NOSPLIT))
1005 		h->flags |= HTML_SPLIT;
1006 
1007 	print_otag(h, TAG_SPAN, "cT", "An");
1008 	return 1;
1009 }
1010 
1011 static int
1012 mdoc_cd_pre(MDOC_ARGS)
1013 {
1014 	synopsis_pre(h, n);
1015 	print_otag(h, TAG_CODE, "cT", "Cd");
1016 	return 1;
1017 }
1018 
1019 static int
1020 mdoc_dv_pre(MDOC_ARGS)
1021 {
1022 	char	*id;
1023 
1024 	if ((id = cond_id(n)) != NULL)
1025 		print_otag(h, TAG_A, "chR", "permalink", id);
1026 	print_otag(h, TAG_CODE, "cTi", "Dv", id);
1027 	return 1;
1028 }
1029 
1030 static int
1031 mdoc_ev_pre(MDOC_ARGS)
1032 {
1033 	char	*id;
1034 
1035 	if ((id = cond_id(n)) != NULL)
1036 		print_otag(h, TAG_A, "chR", "permalink", id);
1037 	print_otag(h, TAG_CODE, "cTi", "Ev", id);
1038 	return 1;
1039 }
1040 
1041 static int
1042 mdoc_er_pre(MDOC_ARGS)
1043 {
1044 	char	*id;
1045 
1046 	id = n->sec == SEC_ERRORS &&
1047 	    (n->parent->tok == MDOC_It ||
1048 	     (n->parent->tok == MDOC_Bq &&
1049 	      n->parent->parent->parent->tok == MDOC_It)) ?
1050 	    html_make_id(n, 1) : NULL;
1051 
1052 	if (id != NULL)
1053 		print_otag(h, TAG_A, "chR", "permalink", id);
1054 	print_otag(h, TAG_CODE, "cTi", "Er", id);
1055 	return 1;
1056 }
1057 
1058 static int
1059 mdoc_fa_pre(MDOC_ARGS)
1060 {
1061 	const struct roff_node	*nn;
1062 	struct tag		*t;
1063 
1064 	if (n->parent->tok != MDOC_Fo) {
1065 		print_otag(h, TAG_VAR, "cT", "Fa");
1066 		return 1;
1067 	}
1068 
1069 	for (nn = n->child; nn; nn = nn->next) {
1070 		t = print_otag(h, TAG_VAR, "cT", "Fa");
1071 		print_text(h, nn->string);
1072 		print_tagq(h, t);
1073 		if (nn->next) {
1074 			h->flags |= HTML_NOSPACE;
1075 			print_text(h, ",");
1076 		}
1077 	}
1078 
1079 	if (n->child && n->next && n->next->tok == MDOC_Fa) {
1080 		h->flags |= HTML_NOSPACE;
1081 		print_text(h, ",");
1082 	}
1083 
1084 	return 0;
1085 }
1086 
1087 static int
1088 mdoc_fd_pre(MDOC_ARGS)
1089 {
1090 	struct tag	*t;
1091 	char		*buf, *cp;
1092 
1093 	synopsis_pre(h, n);
1094 
1095 	if (NULL == (n = n->child))
1096 		return 0;
1097 
1098 	assert(n->type == ROFFT_TEXT);
1099 
1100 	if (strcmp(n->string, "#include")) {
1101 		print_otag(h, TAG_CODE, "cT", "Fd");
1102 		return 1;
1103 	}
1104 
1105 	print_otag(h, TAG_CODE, "cT", "In");
1106 	print_text(h, n->string);
1107 
1108 	if (NULL != (n = n->next)) {
1109 		assert(n->type == ROFFT_TEXT);
1110 
1111 		if (h->base_includes) {
1112 			cp = n->string;
1113 			if (*cp == '<' || *cp == '"')
1114 				cp++;
1115 			buf = mandoc_strdup(cp);
1116 			cp = strchr(buf, '\0') - 1;
1117 			if (cp >= buf && (*cp == '>' || *cp == '"'))
1118 				*cp = '\0';
1119 			t = print_otag(h, TAG_A, "cThI", "In", buf);
1120 			free(buf);
1121 		} else
1122 			t = print_otag(h, TAG_A, "cT", "In");
1123 
1124 		print_text(h, n->string);
1125 		print_tagq(h, t);
1126 
1127 		n = n->next;
1128 	}
1129 
1130 	for ( ; n; n = n->next) {
1131 		assert(n->type == ROFFT_TEXT);
1132 		print_text(h, n->string);
1133 	}
1134 
1135 	return 0;
1136 }
1137 
1138 static int
1139 mdoc_vt_pre(MDOC_ARGS)
1140 {
1141 	if (n->type == ROFFT_BLOCK) {
1142 		synopsis_pre(h, n);
1143 		return 1;
1144 	} else if (n->type == ROFFT_ELEM) {
1145 		synopsis_pre(h, n);
1146 	} else if (n->type == ROFFT_HEAD)
1147 		return 0;
1148 
1149 	print_otag(h, TAG_VAR, "cT", "Vt");
1150 	return 1;
1151 }
1152 
1153 static int
1154 mdoc_ft_pre(MDOC_ARGS)
1155 {
1156 	synopsis_pre(h, n);
1157 	print_otag(h, TAG_VAR, "cT", "Ft");
1158 	return 1;
1159 }
1160 
1161 static int
1162 mdoc_fn_pre(MDOC_ARGS)
1163 {
1164 	struct tag	*t;
1165 	char		 nbuf[BUFSIZ];
1166 	const char	*sp, *ep;
1167 	int		 sz, pretty;
1168 
1169 	pretty = NODE_SYNPRETTY & n->flags;
1170 	synopsis_pre(h, n);
1171 
1172 	/* Split apart into type and name. */
1173 	assert(n->child->string);
1174 	sp = n->child->string;
1175 
1176 	ep = strchr(sp, ' ');
1177 	if (NULL != ep) {
1178 		t = print_otag(h, TAG_VAR, "cT", "Ft");
1179 
1180 		while (ep) {
1181 			sz = MIN((int)(ep - sp), BUFSIZ - 1);
1182 			(void)memcpy(nbuf, sp, (size_t)sz);
1183 			nbuf[sz] = '\0';
1184 			print_text(h, nbuf);
1185 			sp = ++ep;
1186 			ep = strchr(sp, ' ');
1187 		}
1188 		print_tagq(h, t);
1189 	}
1190 
1191 	t = print_otag(h, TAG_CODE, "cT", "Fn");
1192 
1193 	if (sp)
1194 		print_text(h, sp);
1195 
1196 	print_tagq(h, t);
1197 
1198 	h->flags |= HTML_NOSPACE;
1199 	print_text(h, "(");
1200 	h->flags |= HTML_NOSPACE;
1201 
1202 	for (n = n->child->next; n; n = n->next) {
1203 		if (NODE_SYNPRETTY & n->flags)
1204 			t = print_otag(h, TAG_VAR, "cTss?", "Fa",
1205 			    "white-space", "nowrap");
1206 		else
1207 			t = print_otag(h, TAG_VAR, "cT", "Fa");
1208 		print_text(h, n->string);
1209 		print_tagq(h, t);
1210 		if (n->next) {
1211 			h->flags |= HTML_NOSPACE;
1212 			print_text(h, ",");
1213 		}
1214 	}
1215 
1216 	h->flags |= HTML_NOSPACE;
1217 	print_text(h, ")");
1218 
1219 	if (pretty) {
1220 		h->flags |= HTML_NOSPACE;
1221 		print_text(h, ";");
1222 	}
1223 
1224 	return 0;
1225 }
1226 
1227 static int
1228 mdoc_sm_pre(MDOC_ARGS)
1229 {
1230 
1231 	if (NULL == n->child)
1232 		h->flags ^= HTML_NONOSPACE;
1233 	else if (0 == strcmp("on", n->child->string))
1234 		h->flags &= ~HTML_NONOSPACE;
1235 	else
1236 		h->flags |= HTML_NONOSPACE;
1237 
1238 	if ( ! (HTML_NONOSPACE & h->flags))
1239 		h->flags &= ~HTML_NOSPACE;
1240 
1241 	return 0;
1242 }
1243 
1244 static int
1245 mdoc_skip_pre(MDOC_ARGS)
1246 {
1247 
1248 	return 0;
1249 }
1250 
1251 static int
1252 mdoc_pp_pre(MDOC_ARGS)
1253 {
1254 
1255 	print_paragraph(h);
1256 	return 0;
1257 }
1258 
1259 static int
1260 mdoc_lk_pre(MDOC_ARGS)
1261 {
1262 	const struct roff_node *link, *descr, *punct;
1263 	struct tag	*t;
1264 
1265 	if ((link = n->child) == NULL)
1266 		return 0;
1267 
1268 	/* Find beginning of trailing punctuation. */
1269 	punct = n->last;
1270 	while (punct != link && punct->flags & NODE_DELIMC)
1271 		punct = punct->prev;
1272 	punct = punct->next;
1273 
1274 	/* Link target and link text. */
1275 	descr = link->next;
1276 	if (descr == punct)
1277 		descr = link;  /* no text */
1278 	t = print_otag(h, TAG_A, "cTh", "Lk", link->string);
1279 	do {
1280 		if (descr->flags & (NODE_DELIMC | NODE_DELIMO))
1281 			h->flags |= HTML_NOSPACE;
1282 		print_text(h, descr->string);
1283 		descr = descr->next;
1284 	} while (descr != punct);
1285 	print_tagq(h, t);
1286 
1287 	/* Trailing punctuation. */
1288 	while (punct != NULL) {
1289 		h->flags |= HTML_NOSPACE;
1290 		print_text(h, punct->string);
1291 		punct = punct->next;
1292 	}
1293 	return 0;
1294 }
1295 
1296 static int
1297 mdoc_mt_pre(MDOC_ARGS)
1298 {
1299 	struct tag	*t;
1300 	char		*cp;
1301 
1302 	for (n = n->child; n; n = n->next) {
1303 		assert(n->type == ROFFT_TEXT);
1304 
1305 		mandoc_asprintf(&cp, "mailto:%s", n->string);
1306 		t = print_otag(h, TAG_A, "cTh", "Mt", cp);
1307 		print_text(h, n->string);
1308 		print_tagq(h, t);
1309 		free(cp);
1310 	}
1311 
1312 	return 0;
1313 }
1314 
1315 static int
1316 mdoc_fo_pre(MDOC_ARGS)
1317 {
1318 	struct tag	*t;
1319 
1320 	if (n->type == ROFFT_BODY) {
1321 		h->flags |= HTML_NOSPACE;
1322 		print_text(h, "(");
1323 		h->flags |= HTML_NOSPACE;
1324 		return 1;
1325 	} else if (n->type == ROFFT_BLOCK) {
1326 		synopsis_pre(h, n);
1327 		return 1;
1328 	}
1329 
1330 	if (n->child == NULL)
1331 		return 0;
1332 
1333 	assert(n->child->string);
1334 	t = print_otag(h, TAG_CODE, "cT", "Fn");
1335 	print_text(h, n->child->string);
1336 	print_tagq(h, t);
1337 	return 0;
1338 }
1339 
1340 static void
1341 mdoc_fo_post(MDOC_ARGS)
1342 {
1343 
1344 	if (n->type != ROFFT_BODY)
1345 		return;
1346 	h->flags |= HTML_NOSPACE;
1347 	print_text(h, ")");
1348 	h->flags |= HTML_NOSPACE;
1349 	print_text(h, ";");
1350 }
1351 
1352 static int
1353 mdoc_in_pre(MDOC_ARGS)
1354 {
1355 	struct tag	*t;
1356 
1357 	synopsis_pre(h, n);
1358 	print_otag(h, TAG_CODE, "cT", "In");
1359 
1360 	/*
1361 	 * The first argument of the `In' gets special treatment as
1362 	 * being a linked value.  Subsequent values are printed
1363 	 * afterward.  groff does similarly.  This also handles the case
1364 	 * of no children.
1365 	 */
1366 
1367 	if (NODE_SYNPRETTY & n->flags && NODE_LINE & n->flags)
1368 		print_text(h, "#include");
1369 
1370 	print_text(h, "<");
1371 	h->flags |= HTML_NOSPACE;
1372 
1373 	if (NULL != (n = n->child)) {
1374 		assert(n->type == ROFFT_TEXT);
1375 
1376 		if (h->base_includes)
1377 			t = print_otag(h, TAG_A, "cThI", "In", n->string);
1378 		else
1379 			t = print_otag(h, TAG_A, "cT", "In");
1380 		print_text(h, n->string);
1381 		print_tagq(h, t);
1382 
1383 		n = n->next;
1384 	}
1385 
1386 	h->flags |= HTML_NOSPACE;
1387 	print_text(h, ">");
1388 
1389 	for ( ; n; n = n->next) {
1390 		assert(n->type == ROFFT_TEXT);
1391 		print_text(h, n->string);
1392 	}
1393 
1394 	return 0;
1395 }
1396 
1397 static int
1398 mdoc_ic_pre(MDOC_ARGS)
1399 {
1400 	char	*id;
1401 
1402 	if ((id = cond_id(n)) != NULL)
1403 		print_otag(h, TAG_A, "chR", "permalink", id);
1404 	print_otag(h, TAG_CODE, "cTi", "Ic", id);
1405 	return 1;
1406 }
1407 
1408 static int
1409 mdoc_va_pre(MDOC_ARGS)
1410 {
1411 	print_otag(h, TAG_VAR, "cT", "Va");
1412 	return 1;
1413 }
1414 
1415 static int
1416 mdoc_ap_pre(MDOC_ARGS)
1417 {
1418 
1419 	h->flags |= HTML_NOSPACE;
1420 	print_text(h, "\\(aq");
1421 	h->flags |= HTML_NOSPACE;
1422 	return 1;
1423 }
1424 
1425 static int
1426 mdoc_bf_pre(MDOC_ARGS)
1427 {
1428 	const char	*cattr;
1429 
1430 	if (n->type == ROFFT_HEAD)
1431 		return 0;
1432 	else if (n->type != ROFFT_BODY)
1433 		return 1;
1434 
1435 	if (FONT_Em == n->norm->Bf.font)
1436 		cattr = "Bf Em";
1437 	else if (FONT_Sy == n->norm->Bf.font)
1438 		cattr = "Bf Sy";
1439 	else if (FONT_Li == n->norm->Bf.font)
1440 		cattr = "Bf Li";
1441 	else
1442 		cattr = "Bf No";
1443 
1444 	/* Cannot use TAG_SPAN because it may contain blocks. */
1445 	print_otag(h, TAG_DIV, "cshl", cattr, 1);
1446 	return 1;
1447 }
1448 
1449 static int
1450 mdoc_ms_pre(MDOC_ARGS)
1451 {
1452 	char *id;
1453 
1454 	if ((id = cond_id(n)) != NULL)
1455 		print_otag(h, TAG_A, "chR", "permalink", id);
1456 	print_otag(h, TAG_SPAN, "cTi", "Ms", id);
1457 	return 1;
1458 }
1459 
1460 static int
1461 mdoc_igndelim_pre(MDOC_ARGS)
1462 {
1463 
1464 	h->flags |= HTML_IGNDELIM;
1465 	return 1;
1466 }
1467 
1468 static void
1469 mdoc_pf_post(MDOC_ARGS)
1470 {
1471 
1472 	if ( ! (n->next == NULL || n->next->flags & NODE_LINE))
1473 		h->flags |= HTML_NOSPACE;
1474 }
1475 
1476 static int
1477 mdoc_rs_pre(MDOC_ARGS)
1478 {
1479 	if (n->type != ROFFT_BLOCK)
1480 		return 1;
1481 
1482 	if (n->prev && SEC_SEE_ALSO == n->sec)
1483 		print_paragraph(h);
1484 
1485 	print_otag(h, TAG_CITE, "cT", "Rs");
1486 	return 1;
1487 }
1488 
1489 static int
1490 mdoc_no_pre(MDOC_ARGS)
1491 {
1492 	char *id;
1493 
1494 	if ((id = cond_id(n)) != NULL)
1495 		print_otag(h, TAG_A, "chR", "permalink", id);
1496 	print_otag(h, TAG_SPAN, "ci", "No", id);
1497 	return 1;
1498 }
1499 
1500 static int
1501 mdoc_li_pre(MDOC_ARGS)
1502 {
1503 	char	*id;
1504 
1505 	if ((id = cond_id(n)) != NULL)
1506 		print_otag(h, TAG_A, "chR", "permalink", id);
1507 	print_otag(h, TAG_CODE, "ci", "Li", id);
1508 	return 1;
1509 }
1510 
1511 static int
1512 mdoc_sy_pre(MDOC_ARGS)
1513 {
1514 	print_otag(h, TAG_B, "cT", "Sy");
1515 	return 1;
1516 }
1517 
1518 static int
1519 mdoc_lb_pre(MDOC_ARGS)
1520 {
1521 	if (SEC_LIBRARY == n->sec && NODE_LINE & n->flags && n->prev)
1522 		print_otag(h, TAG_BR, "");
1523 
1524 	print_otag(h, TAG_SPAN, "cT", "Lb");
1525 	return 1;
1526 }
1527 
1528 static int
1529 mdoc__x_pre(MDOC_ARGS)
1530 {
1531 	const char	*cattr;
1532 	enum htmltag	 t;
1533 
1534 	t = TAG_SPAN;
1535 
1536 	switch (n->tok) {
1537 	case MDOC__A:
1538 		cattr = "RsA";
1539 		if (n->prev && MDOC__A == n->prev->tok)
1540 			if (NULL == n->next || MDOC__A != n->next->tok)
1541 				print_text(h, "and");
1542 		break;
1543 	case MDOC__B:
1544 		t = TAG_I;
1545 		cattr = "RsB";
1546 		break;
1547 	case MDOC__C:
1548 		cattr = "RsC";
1549 		break;
1550 	case MDOC__D:
1551 		cattr = "RsD";
1552 		break;
1553 	case MDOC__I:
1554 		t = TAG_I;
1555 		cattr = "RsI";
1556 		break;
1557 	case MDOC__J:
1558 		t = TAG_I;
1559 		cattr = "RsJ";
1560 		break;
1561 	case MDOC__N:
1562 		cattr = "RsN";
1563 		break;
1564 	case MDOC__O:
1565 		cattr = "RsO";
1566 		break;
1567 	case MDOC__P:
1568 		cattr = "RsP";
1569 		break;
1570 	case MDOC__Q:
1571 		cattr = "RsQ";
1572 		break;
1573 	case MDOC__R:
1574 		cattr = "RsR";
1575 		break;
1576 	case MDOC__T:
1577 		cattr = "RsT";
1578 		break;
1579 	case MDOC__U:
1580 		print_otag(h, TAG_A, "ch", "RsU", n->child->string);
1581 		return 1;
1582 	case MDOC__V:
1583 		cattr = "RsV";
1584 		break;
1585 	default:
1586 		abort();
1587 	}
1588 
1589 	print_otag(h, t, "c", cattr);
1590 	return 1;
1591 }
1592 
1593 static void
1594 mdoc__x_post(MDOC_ARGS)
1595 {
1596 
1597 	if (MDOC__A == n->tok && n->next && MDOC__A == n->next->tok)
1598 		if (NULL == n->next->next || MDOC__A != n->next->next->tok)
1599 			if (NULL == n->prev || MDOC__A != n->prev->tok)
1600 				return;
1601 
1602 	/* TODO: %U */
1603 
1604 	if (NULL == n->parent || MDOC_Rs != n->parent->tok)
1605 		return;
1606 
1607 	h->flags |= HTML_NOSPACE;
1608 	print_text(h, n->next ? "," : ".");
1609 }
1610 
1611 static int
1612 mdoc_bk_pre(MDOC_ARGS)
1613 {
1614 
1615 	switch (n->type) {
1616 	case ROFFT_BLOCK:
1617 		break;
1618 	case ROFFT_HEAD:
1619 		return 0;
1620 	case ROFFT_BODY:
1621 		if (n->parent->args != NULL || n->prev->child == NULL)
1622 			h->flags |= HTML_PREKEEP;
1623 		break;
1624 	default:
1625 		abort();
1626 	}
1627 
1628 	return 1;
1629 }
1630 
1631 static void
1632 mdoc_bk_post(MDOC_ARGS)
1633 {
1634 
1635 	if (n->type == ROFFT_BODY)
1636 		h->flags &= ~(HTML_KEEP | HTML_PREKEEP);
1637 }
1638 
1639 static int
1640 mdoc_quote_pre(MDOC_ARGS)
1641 {
1642 	if (n->type != ROFFT_BODY)
1643 		return 1;
1644 
1645 	switch (n->tok) {
1646 	case MDOC_Ao:
1647 	case MDOC_Aq:
1648 		print_text(h, n->child != NULL && n->child->next == NULL &&
1649 		    n->child->tok == MDOC_Mt ?  "<" : "\\(la");
1650 		break;
1651 	case MDOC_Bro:
1652 	case MDOC_Brq:
1653 		print_text(h, "\\(lC");
1654 		break;
1655 	case MDOC_Bo:
1656 	case MDOC_Bq:
1657 		print_text(h, "\\(lB");
1658 		break;
1659 	case MDOC_Oo:
1660 	case MDOC_Op:
1661 		print_text(h, "\\(lB");
1662 		h->flags |= HTML_NOSPACE;
1663 		/* Cannot use TAG_SPAN because it may contain blocks. */
1664 		print_otag(h, TAG_IDIV, "c", "Op");
1665 		break;
1666 	case MDOC_En:
1667 		if (NULL == n->norm->Es ||
1668 		    NULL == n->norm->Es->child)
1669 			return 1;
1670 		print_text(h, n->norm->Es->child->string);
1671 		break;
1672 	case MDOC_Do:
1673 	case MDOC_Dq:
1674 	case MDOC_Qo:
1675 	case MDOC_Qq:
1676 		print_text(h, "\\(lq");
1677 		break;
1678 	case MDOC_Po:
1679 	case MDOC_Pq:
1680 		print_text(h, "(");
1681 		break;
1682 	case MDOC_Ql:
1683 		print_text(h, "\\(oq");
1684 		h->flags |= HTML_NOSPACE;
1685 		print_otag(h, TAG_CODE, "c", "Li");
1686 		break;
1687 	case MDOC_So:
1688 	case MDOC_Sq:
1689 		print_text(h, "\\(oq");
1690 		break;
1691 	default:
1692 		abort();
1693 	}
1694 
1695 	h->flags |= HTML_NOSPACE;
1696 	return 1;
1697 }
1698 
1699 static void
1700 mdoc_quote_post(MDOC_ARGS)
1701 {
1702 
1703 	if (n->type != ROFFT_BODY && n->type != ROFFT_ELEM)
1704 		return;
1705 
1706 	h->flags |= HTML_NOSPACE;
1707 
1708 	switch (n->tok) {
1709 	case MDOC_Ao:
1710 	case MDOC_Aq:
1711 		print_text(h, n->child != NULL && n->child->next == NULL &&
1712 		    n->child->tok == MDOC_Mt ?  ">" : "\\(ra");
1713 		break;
1714 	case MDOC_Bro:
1715 	case MDOC_Brq:
1716 		print_text(h, "\\(rC");
1717 		break;
1718 	case MDOC_Oo:
1719 	case MDOC_Op:
1720 	case MDOC_Bo:
1721 	case MDOC_Bq:
1722 		print_text(h, "\\(rB");
1723 		break;
1724 	case MDOC_En:
1725 		if (n->norm->Es == NULL ||
1726 		    n->norm->Es->child == NULL ||
1727 		    n->norm->Es->child->next == NULL)
1728 			h->flags &= ~HTML_NOSPACE;
1729 		else
1730 			print_text(h, n->norm->Es->child->next->string);
1731 		break;
1732 	case MDOC_Qo:
1733 	case MDOC_Qq:
1734 	case MDOC_Do:
1735 	case MDOC_Dq:
1736 		print_text(h, "\\(rq");
1737 		break;
1738 	case MDOC_Po:
1739 	case MDOC_Pq:
1740 		print_text(h, ")");
1741 		break;
1742 	case MDOC_Ql:
1743 	case MDOC_So:
1744 	case MDOC_Sq:
1745 		print_text(h, "\\(cq");
1746 		break;
1747 	default:
1748 		abort();
1749 	}
1750 }
1751 
1752 static int
1753 mdoc_eo_pre(MDOC_ARGS)
1754 {
1755 
1756 	if (n->type != ROFFT_BODY)
1757 		return 1;
1758 
1759 	if (n->end == ENDBODY_NOT &&
1760 	    n->parent->head->child == NULL &&
1761 	    n->child != NULL &&
1762 	    n->child->end != ENDBODY_NOT)
1763 		print_text(h, "\\&");
1764 	else if (n->end != ENDBODY_NOT ? n->child != NULL :
1765 	    n->parent->head->child != NULL && (n->child != NULL ||
1766 	    (n->parent->tail != NULL && n->parent->tail->child != NULL)))
1767 		h->flags |= HTML_NOSPACE;
1768 	return 1;
1769 }
1770 
1771 static void
1772 mdoc_eo_post(MDOC_ARGS)
1773 {
1774 	int	 body, tail;
1775 
1776 	if (n->type != ROFFT_BODY)
1777 		return;
1778 
1779 	if (n->end != ENDBODY_NOT) {
1780 		h->flags &= ~HTML_NOSPACE;
1781 		return;
1782 	}
1783 
1784 	body = n->child != NULL || n->parent->head->child != NULL;
1785 	tail = n->parent->tail != NULL && n->parent->tail->child != NULL;
1786 
1787 	if (body && tail)
1788 		h->flags |= HTML_NOSPACE;
1789 	else if ( ! tail)
1790 		h->flags &= ~HTML_NOSPACE;
1791 }
1792