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