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