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