xref: /openbsd-src/usr.bin/mandoc/man_html.c (revision f66971330e58e0567a0c2de8fbfcab971f206621)
1*f6697133Sschwarze /* $OpenBSD: man_html.c,v 1.140 2023/10/24 20:30:49 schwarze Exp $ */
24175bdabSschwarze /*
3*f6697133Sschwarze  * Copyright (c) 2013-15,2017-20,2022-23 Ingo Schwarze <schwarze@openbsd.org>
40ac7e6ecSschwarze  * Copyright (c) 2008-2012, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
54175bdabSschwarze  *
64175bdabSschwarze  * Permission to use, copy, modify, and distribute this software for any
74175bdabSschwarze  * purpose with or without fee is hereby granted, provided that the above
84175bdabSschwarze  * copyright notice and this permission notice appear in all copies.
94175bdabSschwarze  *
10d1982c71Sschwarze  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
114175bdabSschwarze  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12d1982c71Sschwarze  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
134175bdabSschwarze  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
144175bdabSschwarze  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
154175bdabSschwarze  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
164175bdabSschwarze  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
170ac7e6ecSschwarze  *
180ac7e6ecSschwarze  * HTML formatter for man(7) used by mandoc(1).
194175bdabSschwarze  */
204175bdabSschwarze #include <sys/types.h>
214175bdabSschwarze 
224175bdabSschwarze #include <assert.h>
234175bdabSschwarze #include <ctype.h>
244175bdabSschwarze #include <stdio.h>
254175bdabSschwarze #include <stdlib.h>
264175bdabSschwarze #include <string.h>
274175bdabSschwarze 
28a92c1cd8Sschwarze #include "mandoc_aux.h"
292e362670Sschwarze #include "mandoc.h"
30d1982c71Sschwarze #include "roff.h"
31dd617d76Sschwarze #include "man.h"
324175bdabSschwarze #include "out.h"
334175bdabSschwarze #include "html.h"
344175bdabSschwarze #include "main.h"
354175bdabSschwarze 
362a238f45Sschwarze #define	MAN_ARGS	  const struct roff_meta *man, \
377ebbefbeSschwarze 			  struct roff_node *n, \
384175bdabSschwarze 			  struct html *h
394175bdabSschwarze 
4016fe0cfcSschwarze struct	man_html_act {
414175bdabSschwarze 	int		(*pre)(MAN_ARGS);
424175bdabSschwarze 	int		(*post)(MAN_ARGS);
434175bdabSschwarze };
444175bdabSschwarze 
45cc202ecaSschwarze static	void		  print_man_head(const struct roff_meta *,
46cc202ecaSschwarze 				struct html *);
474175bdabSschwarze static	void		  print_man_nodelist(MAN_ARGS);
484175bdabSschwarze static	void		  print_man_node(MAN_ARGS);
496f6388b4Sschwarze static	char		  list_continues(const struct roff_node *,
506f6388b4Sschwarze 				const struct roff_node *);
5166ae7cc0Sschwarze static	int		  man_B_pre(MAN_ARGS);
5266ae7cc0Sschwarze static	int		  man_IP_pre(MAN_ARGS);
5366ae7cc0Sschwarze static	int		  man_I_pre(MAN_ARGS);
54*f6697133Sschwarze static	int		  man_MR_pre(MAN_ARGS);
5566ae7cc0Sschwarze static	int		  man_OP_pre(MAN_ARGS);
5666ae7cc0Sschwarze static	int		  man_PP_pre(MAN_ARGS);
5766ae7cc0Sschwarze static	int		  man_RS_pre(MAN_ARGS);
5866ae7cc0Sschwarze static	int		  man_SH_pre(MAN_ARGS);
5966ae7cc0Sschwarze static	int		  man_SM_pre(MAN_ARGS);
605e5a9c61Sschwarze static	int		  man_SY_pre(MAN_ARGS);
613aeff926Sschwarze static	int		  man_UR_pre(MAN_ARGS);
624175bdabSschwarze static	int		  man_alt_pre(MAN_ARGS);
634175bdabSschwarze static	int		  man_ign_pre(MAN_ARGS);
64ddce0b0cSschwarze static	int		  man_in_pre(MAN_ARGS);
65cc202ecaSschwarze static	void		  man_root_post(const struct roff_meta *,
66cc202ecaSschwarze 				struct html *);
67cc202ecaSschwarze static	void		  man_root_pre(const struct roff_meta *,
68cc202ecaSschwarze 				struct html *);
694175bdabSschwarze 
7016fe0cfcSschwarze static	const struct man_html_act man_html_acts[MAN_MAX - MAN_TH] = {
714175bdabSschwarze 	{ NULL, NULL }, /* TH */
724175bdabSschwarze 	{ man_SH_pre, NULL }, /* SH */
737f442bfeSschwarze 	{ man_SH_pre, NULL }, /* SS */
744175bdabSschwarze 	{ man_IP_pre, NULL }, /* TP */
75d991fc2cSschwarze 	{ man_IP_pre, NULL }, /* TQ */
763f3c303aSschwarze 	{ man_PP_pre, NULL }, /* LP */
774175bdabSschwarze 	{ man_PP_pre, NULL }, /* PP */
783f3c303aSschwarze 	{ man_PP_pre, NULL }, /* P */
794175bdabSschwarze 	{ man_IP_pre, NULL }, /* IP */
807f442bfeSschwarze 	{ man_PP_pre, NULL }, /* HP */
814175bdabSschwarze 	{ man_SM_pre, NULL }, /* SM */
8264f4b2e7Sschwarze 	{ man_SM_pre, NULL }, /* SB */
834175bdabSschwarze 	{ man_alt_pre, NULL }, /* BI */
844175bdabSschwarze 	{ man_alt_pre, NULL }, /* IB */
854175bdabSschwarze 	{ man_alt_pre, NULL }, /* BR */
864175bdabSschwarze 	{ man_alt_pre, NULL }, /* RB */
874175bdabSschwarze 	{ NULL, NULL }, /* R */
884175bdabSschwarze 	{ man_B_pre, NULL }, /* B */
894175bdabSschwarze 	{ man_I_pre, NULL }, /* I */
904175bdabSschwarze 	{ man_alt_pre, NULL }, /* IR */
914175bdabSschwarze 	{ man_alt_pre, NULL }, /* RI */
924175bdabSschwarze 	{ NULL, NULL }, /* RE */
934175bdabSschwarze 	{ man_RS_pre, NULL }, /* RS */
944175bdabSschwarze 	{ man_ign_pre, NULL }, /* DT */
954175bdabSschwarze 	{ man_ign_pre, NULL }, /* UC */
96b822ca0dSschwarze 	{ man_ign_pre, NULL }, /* PD */
97dfc705a3Sschwarze 	{ man_ign_pre, NULL }, /* AT */
98ddce0b0cSschwarze 	{ man_in_pre, NULL }, /* in */
995e5a9c61Sschwarze 	{ man_SY_pre, NULL }, /* SY */
1005e5a9c61Sschwarze 	{ NULL, NULL }, /* YS */
10166ae7cc0Sschwarze 	{ man_OP_pre, NULL }, /* OP */
102de63e416Sschwarze 	{ NULL, NULL }, /* EX */
103de63e416Sschwarze 	{ NULL, NULL }, /* EE */
1043aeff926Sschwarze 	{ man_UR_pre, NULL }, /* UR */
1053aeff926Sschwarze 	{ NULL, NULL }, /* UE */
106df9a9479Sbentley 	{ man_UR_pre, NULL }, /* MT */
107df9a9479Sbentley 	{ NULL, NULL }, /* ME */
108*f6697133Sschwarze 	{ man_MR_pre, NULL }, /* MR */
1094175bdabSschwarze };
1104175bdabSschwarze 
11149aff9f8Sschwarze 
1124175bdabSschwarze void
html_man(void * arg,const struct roff_meta * man)1136b86842eSschwarze html_man(void *arg, const struct roff_meta *man)
1144175bdabSschwarze {
1156d0e9b63Sschwarze 	struct html		*h;
116cc202ecaSschwarze 	struct roff_node	*n;
117ce781f36Sschwarze 	struct tag		*t;
1184175bdabSschwarze 
1196d0e9b63Sschwarze 	h = (struct html *)arg;
120cc202ecaSschwarze 	n = man->first->child;
121ca0ce676Sschwarze 
122ce781f36Sschwarze 	if ((h->oflags & HTML_FRAGMENT) == 0) {
123ca0ce676Sschwarze 		print_gen_decls(h);
124ce781f36Sschwarze 		print_otag(h, TAG_HTML, "");
125ce781f36Sschwarze 		t = print_otag(h, TAG_HEAD, "");
1266b86842eSschwarze 		print_man_head(man, h);
127ce781f36Sschwarze 		print_tagq(h, t);
128501adfefSschwarze 		if (n != NULL && n->type == ROFFT_COMMENT)
129501adfefSschwarze 			print_gen_comment(h, n);
130229cc7fdSschwarze 		print_otag(h, TAG_BODY, "");
131ce781f36Sschwarze 	}
132467b61c6Sschwarze 
1336b86842eSschwarze 	man_root_pre(man, h);
1346774f271Sschwarze 	t = print_otag(h, TAG_MAIN, "c", "manual-text");
1356b86842eSschwarze 	print_man_nodelist(man, n, h);
136c7402a19Sschwarze 	print_tagq(h, t);
1376b86842eSschwarze 	man_root_post(man, h);
138ce781f36Sschwarze 	print_tagq(h, NULL);
1394175bdabSschwarze }
1404175bdabSschwarze 
1414175bdabSschwarze static void
print_man_head(const struct roff_meta * man,struct html * h)142cc202ecaSschwarze print_man_head(const struct roff_meta *man, struct html *h)
1434175bdabSschwarze {
144fef1eecdSschwarze 	char	*cp;
1454175bdabSschwarze 
1464175bdabSschwarze 	print_gen_head(h);
147fef1eecdSschwarze 	mandoc_asprintf(&cp, "%s(%s)", man->title, man->msec);
148229cc7fdSschwarze 	print_otag(h, TAG_TITLE, "");
149fef1eecdSschwarze 	print_text(h, cp);
150fef1eecdSschwarze 	free(cp);
1514175bdabSschwarze }
1524175bdabSschwarze 
1534175bdabSschwarze static void
print_man_nodelist(MAN_ARGS)1544175bdabSschwarze print_man_nodelist(MAN_ARGS)
1554175bdabSschwarze {
156e4534905Sschwarze 	while (n != NULL) {
157de63e416Sschwarze 		print_man_node(man, n, h);
158e4534905Sschwarze 		n = n->next;
159e4534905Sschwarze 	}
1604175bdabSschwarze }
1614175bdabSschwarze 
1624175bdabSschwarze static void
print_man_node(MAN_ARGS)1634175bdabSschwarze print_man_node(MAN_ARGS)
1644175bdabSschwarze {
165de63e416Sschwarze 	struct tag	*t;
166de63e416Sschwarze 	int		 child;
167de63e416Sschwarze 
1682dd33770Sschwarze 	if (n->type == ROFFT_COMMENT || n->flags & NODE_NOPRT)
1692dd33770Sschwarze 		return;
1702dd33770Sschwarze 
17152e71e33Sschwarze 	if ((n->flags & NODE_NOFILL) == 0)
17252e71e33Sschwarze 		html_fillmode(h, ROFF_fi);
17352e71e33Sschwarze 	else if (html_fillmode(h, ROFF_nf) == ROFF_nf &&
17452e71e33Sschwarze 	    n->tok != ROFF_fi && n->flags & NODE_LINE &&
17552e71e33Sschwarze 	    (n->prev == NULL || n->prev->tok != MAN_YS))
17652e71e33Sschwarze 		print_endline(h);
177de63e416Sschwarze 
178de63e416Sschwarze 	child = 1;
179de63e416Sschwarze 	switch (n->type) {
180de63e416Sschwarze 	case ROFFT_TEXT:
181275804acSschwarze 		if (*n->string == '\0') {
182275804acSschwarze 			print_endline(h);
183275804acSschwarze 			return;
184275804acSschwarze 		}
185275804acSschwarze 		if (*n->string == ' ' && n->flags & NODE_LINE &&
186275804acSschwarze 		    (h->flags & HTML_NONEWLINE) == 0)
187d28d6572Sschwarze 			print_otag(h, TAG_BR, "");
188275804acSschwarze 		else if (n->flags & NODE_DELIMC)
189275804acSschwarze 			h->flags |= HTML_NOSPACE;
190520a575cSschwarze 		t = h->tag;
191520a575cSschwarze 		t->refcnt++;
1924175bdabSschwarze 		print_text(h, n->string);
1930692510cSschwarze 		break;
194d1982c71Sschwarze 	case ROFFT_EQN:
195520a575cSschwarze 		t = h->tag;
196520a575cSschwarze 		t->refcnt++;
197f8618d99Sschwarze 		print_eqn(h, n->eqn);
19819a69263Sschwarze 		break;
199d1982c71Sschwarze 	case ROFFT_TBL:
200366f22eeSschwarze 		/*
201366f22eeSschwarze 		 * This will take care of initialising all of the table
202366f22eeSschwarze 		 * state data for the first table, then tearing it down
203366f22eeSschwarze 		 * for the last one.
204366f22eeSschwarze 		 */
2052791bd1cSschwarze 		print_tbl(h, n->span);
20639fa4f70Sschwarze 		return;
2074175bdabSschwarze 	default:
208fa70b73eSschwarze 		/*
209fa70b73eSschwarze 		 * Close out scope of font prior to opening a macro
210366f22eeSschwarze 		 * scope.
211fa70b73eSschwarze 		 */
212cefe8974Sschwarze 		if (h->metac != ESCAPE_FONTROMAN) {
2133a7b861cSschwarze 			h->metal = h->metac;
214cefe8974Sschwarze 			h->metac = ESCAPE_FONTROMAN;
215fa70b73eSschwarze 		}
216366f22eeSschwarze 
217366f22eeSschwarze 		/*
218366f22eeSschwarze 		 * Close out the current table, if it's open, and unset
219366f22eeSschwarze 		 * the "meta" table state.  This will be reopened on the
220366f22eeSschwarze 		 * next table element.
221366f22eeSschwarze 		 */
222520a575cSschwarze 		if (h->tblt != NULL)
223366f22eeSschwarze 			print_tblclose(h);
2244614a369Sschwarze 		t = h->tag;
225520a575cSschwarze 		t->refcnt++;
22629478532Sschwarze 		if (n->tok < ROFF_MAX) {
22796a5de47Sschwarze 			roff_html_pre(h, n);
228520a575cSschwarze 			t->refcnt--;
229275804acSschwarze 			print_stagq(h, t);
230275804acSschwarze 			return;
23129478532Sschwarze 		}
23229478532Sschwarze 		assert(n->tok >= MAN_TH && n->tok < MAN_MAX);
23316fe0cfcSschwarze 		if (man_html_acts[n->tok - MAN_TH].pre != NULL)
23416fe0cfcSschwarze 			child = (*man_html_acts[n->tok - MAN_TH].pre)(man,
23516fe0cfcSschwarze 			    n, h);
2364175bdabSschwarze 		break;
2374175bdabSschwarze 	}
2384175bdabSschwarze 
23918c80517Sschwarze 	if (child && n->child != NULL)
240de63e416Sschwarze 		print_man_nodelist(man, n->child, h);
2414175bdabSschwarze 
242fa70b73eSschwarze 	/* This will automatically close out any font scope. */
243520a575cSschwarze 	t->refcnt--;
244ed811782Sschwarze 	if (n->type == ROFFT_BLOCK &&
245ed811782Sschwarze 	    (n->tok == MAN_IP || n->tok == MAN_TP || n->tok == MAN_TQ)) {
246ed811782Sschwarze 		t = h->tag;
2476f6388b4Sschwarze 		while (t->tag != TAG_DL && t->tag != TAG_UL)
248ed811782Sschwarze 			t = t->next;
249ed811782Sschwarze 		/*
250ed811782Sschwarze 		 * Close the list if no further item of the same type
251ed811782Sschwarze 		 * follows; otherwise, close the item only.
252ed811782Sschwarze 		 */
2537ebbefbeSschwarze 		if (list_continues(n, roff_node_next(n)) == '\0') {
254ed811782Sschwarze 			print_tagq(h, t);
255ed811782Sschwarze 			t = NULL;
256ed811782Sschwarze 		}
257ed811782Sschwarze 	}
258ed811782Sschwarze 	if (t != NULL)
259de63e416Sschwarze 		print_stagq(h, t);
26047657bd5Sschwarze }
2614175bdabSschwarze 
262a5e11edeSschwarze static void
man_root_pre(const struct roff_meta * man,struct html * h)263cc202ecaSschwarze man_root_pre(const struct roff_meta *man, struct html *h)
2644175bdabSschwarze {
2658f48bc46Sschwarze 	struct tag	*t;
266a92c1cd8Sschwarze 	char		*title;
2674175bdabSschwarze 
2687ead8a4eSschwarze 	assert(man->title);
2697ead8a4eSschwarze 	assert(man->msec);
270a92c1cd8Sschwarze 	mandoc_asprintf(&title, "%s(%s)", man->title, man->msec);
2714175bdabSschwarze 
2728f48bc46Sschwarze 	t = print_otag(h, TAG_DIV, "cr?", "head", "doc-pageheader",
273f0f927fcSschwarze 	    "aria-label", "Manual header line");
274a66b65d0Sschwarze 
2758f48bc46Sschwarze 	print_otag(h, TAG_SPAN, "c", "head-ltitle");
2764175bdabSschwarze 	print_text(h, title);
2778f48bc46Sschwarze 	print_stagq(h, t);
2784175bdabSschwarze 
2798f48bc46Sschwarze 	print_otag(h, TAG_SPAN, "c", "head-vol");
28018c80517Sschwarze 	if (man->vol != NULL)
2810b2f1307Sschwarze 		print_text(h, man->vol);
2828f48bc46Sschwarze 	print_stagq(h, t);
2834175bdabSschwarze 
2848f48bc46Sschwarze 	print_otag(h, TAG_SPAN, "c", "head-rtitle");
2854175bdabSschwarze 	print_text(h, title);
2864175bdabSschwarze 	print_tagq(h, t);
287a92c1cd8Sschwarze 	free(title);
2884175bdabSschwarze }
2894175bdabSschwarze 
2904175bdabSschwarze static void
man_root_post(const struct roff_meta * man,struct html * h)291cc202ecaSschwarze man_root_post(const struct roff_meta *man, struct html *h)
2924175bdabSschwarze {
2938f48bc46Sschwarze 	struct tag	*t;
2944175bdabSschwarze 
2958f48bc46Sschwarze 	t = print_otag(h, TAG_DIV, "cr?", "foot", "doc-pagefooter",
296f0f927fcSschwarze 	    "aria-label", "Manual footer line");
297a66b65d0Sschwarze 
2988f48bc46Sschwarze 	print_otag(h, TAG_SPAN, "c", "foot-left");
2998f48bc46Sschwarze 	print_stagq(h, t);
3008f48bc46Sschwarze 
3018f48bc46Sschwarze 	print_otag(h, TAG_SPAN, "c", "foot-date");
3027ead8a4eSschwarze 	print_text(h, man->date);
3038f48bc46Sschwarze 	print_stagq(h, t);
3044175bdabSschwarze 
3058f48bc46Sschwarze 	print_otag(h, TAG_SPAN, "c", "foot-os");
30618c80517Sschwarze 	if (man->os != NULL)
3072a238f45Sschwarze 		print_text(h, man->os);
3084175bdabSschwarze 	print_tagq(h, t);
3094175bdabSschwarze }
3104175bdabSschwarze 
3114175bdabSschwarze static int
man_SH_pre(MAN_ARGS)3124175bdabSschwarze man_SH_pre(MAN_ARGS)
3134175bdabSschwarze {
3141a2b7b3cSschwarze 	const char	*class;
3151a2b7b3cSschwarze 	enum htmltag	 tag;
3166ef173c4Sschwarze 
3171a2b7b3cSschwarze 	if (n->tok == MAN_SH) {
31800b92a3fSschwarze 		tag = TAG_H2;
3191a2b7b3cSschwarze 		class = "Sh";
3201a2b7b3cSschwarze 	} else {
32100b92a3fSschwarze 		tag = TAG_H3;
3221a2b7b3cSschwarze 		class = "Ss";
3231a2b7b3cSschwarze 	}
3247f442bfeSschwarze 	switch (n->type) {
3257f442bfeSschwarze 	case ROFFT_BLOCK:
3267f442bfeSschwarze 		html_close_paragraph(h);
3271a2b7b3cSschwarze 		print_otag(h, TAG_SECTION, "c", class);
3287f442bfeSschwarze 		break;
3297f442bfeSschwarze 	case ROFFT_HEAD:
3300ac7e6ecSschwarze 		print_otag_id(h, tag, class, n);
3317f442bfeSschwarze 		break;
3327f442bfeSschwarze 	case ROFFT_BODY:
3337f442bfeSschwarze 		break;
3347f442bfeSschwarze 	default:
3357f442bfeSschwarze 		abort();
3366ef173c4Sschwarze 	}
337526e306bSschwarze 	return 1;
3384175bdabSschwarze }
3394175bdabSschwarze 
3404175bdabSschwarze static int
man_alt_pre(MAN_ARGS)3414175bdabSschwarze man_alt_pre(MAN_ARGS)
3424175bdabSschwarze {
3433a0d07afSschwarze 	const struct roff_node	*nn;
34418c80517Sschwarze 	struct tag	*t;
3450692510cSschwarze 	int		 i;
3463a7b861cSschwarze 	enum htmltag	 fp;
3474175bdabSschwarze 
34818c80517Sschwarze 	for (i = 0, nn = n->child; nn != NULL; nn = nn->next, i++) {
3494175bdabSschwarze 		switch (n->tok) {
35049aff9f8Sschwarze 		case MAN_BI:
3513a7b861cSschwarze 			fp = i % 2 ? TAG_I : TAG_B;
3524175bdabSschwarze 			break;
35349aff9f8Sschwarze 		case MAN_IB:
3543a7b861cSschwarze 			fp = i % 2 ? TAG_B : TAG_I;
3554175bdabSschwarze 			break;
35649aff9f8Sschwarze 		case MAN_RI:
3573a7b861cSschwarze 			fp = i % 2 ? TAG_I : TAG_MAX;
3584175bdabSschwarze 			break;
35949aff9f8Sschwarze 		case MAN_IR:
3603a7b861cSschwarze 			fp = i % 2 ? TAG_MAX : TAG_I;
3614175bdabSschwarze 			break;
36249aff9f8Sschwarze 		case MAN_BR:
3633a7b861cSschwarze 			fp = i % 2 ? TAG_MAX : TAG_B;
3644175bdabSschwarze 			break;
36549aff9f8Sschwarze 		case MAN_RB:
3663a7b861cSschwarze 			fp = i % 2 ? TAG_B : TAG_MAX;
3674175bdabSschwarze 			break;
3684175bdabSschwarze 		default:
3694175bdabSschwarze 			abort();
3704175bdabSschwarze 		}
3714175bdabSschwarze 
3724175bdabSschwarze 		if (i)
3734175bdabSschwarze 			h->flags |= HTML_NOSPACE;
3744175bdabSschwarze 
3750692510cSschwarze 		if (fp != TAG_MAX)
376229cc7fdSschwarze 			t = print_otag(h, fp, "");
3773a7b861cSschwarze 
3780692510cSschwarze 		print_text(h, nn->string);
3793a7b861cSschwarze 
3800692510cSschwarze 		if (fp != TAG_MAX)
3814175bdabSschwarze 			print_tagq(h, t);
3824175bdabSschwarze 	}
383526e306bSschwarze 	return 0;
3844175bdabSschwarze }
3854175bdabSschwarze 
3864175bdabSschwarze static int
man_SM_pre(MAN_ARGS)3874175bdabSschwarze man_SM_pre(MAN_ARGS)
3884175bdabSschwarze {
389229cc7fdSschwarze 	print_otag(h, TAG_SMALL, "");
39018c80517Sschwarze 	if (n->tok == MAN_SB)
391229cc7fdSschwarze 		print_otag(h, TAG_B, "");
392526e306bSschwarze 	return 1;
3934175bdabSschwarze }
3944175bdabSschwarze 
3954175bdabSschwarze static int
man_PP_pre(MAN_ARGS)3964175bdabSschwarze man_PP_pre(MAN_ARGS)
3974175bdabSschwarze {
3987f442bfeSschwarze 	switch (n->type) {
3997f442bfeSschwarze 	case ROFFT_BLOCK:
4007f442bfeSschwarze 		html_close_paragraph(h);
4017f442bfeSschwarze 		break;
4027f442bfeSschwarze 	case ROFFT_HEAD:
403526e306bSschwarze 		return 0;
4047f442bfeSschwarze 	case ROFFT_BODY:
4057f442bfeSschwarze 		if (n->child != NULL &&
4067f442bfeSschwarze 		    (n->child->flags & NODE_NOFILL) == 0)
4077f442bfeSschwarze 			print_otag(h, TAG_P, "c",
4083f3c303aSschwarze 			    n->tok == MAN_HP ? "Pp HP" : "Pp");
4097f442bfeSschwarze 		break;
4107f442bfeSschwarze 	default:
4117f442bfeSschwarze 		abort();
4127f442bfeSschwarze 	}
413526e306bSschwarze 	return 1;
4144175bdabSschwarze }
4154175bdabSschwarze 
4166f6388b4Sschwarze static char
list_continues(const struct roff_node * n1,const struct roff_node * n2)4176f6388b4Sschwarze list_continues(const struct roff_node *n1, const struct roff_node *n2)
4186f6388b4Sschwarze {
4196f6388b4Sschwarze 	const char *s1, *s2;
4206f6388b4Sschwarze 	char c1, c2;
4216f6388b4Sschwarze 
4226f6388b4Sschwarze 	if (n1 == NULL || n1->type != ROFFT_BLOCK ||
4236f6388b4Sschwarze 	    n2 == NULL || n2->type != ROFFT_BLOCK)
4246f6388b4Sschwarze 		return '\0';
4256f6388b4Sschwarze 	if ((n1->tok == MAN_TP || n1->tok == MAN_TQ) &&
4266f6388b4Sschwarze 	    (n2->tok == MAN_TP || n2->tok == MAN_TQ))
4276f6388b4Sschwarze 		return ' ';
4286f6388b4Sschwarze 	if (n1->tok != MAN_IP || n2->tok != MAN_IP)
4296f6388b4Sschwarze 		return '\0';
4306f6388b4Sschwarze 	n1 = n1->head->child;
4316f6388b4Sschwarze 	n2 = n2->head->child;
4326f6388b4Sschwarze 	s1 = n1 == NULL ? "" : n1->string;
4336f6388b4Sschwarze 	s2 = n2 == NULL ? "" : n2->string;
4346f6388b4Sschwarze 	c1 = strcmp(s1, "*") == 0 ? '*' :
4356f6388b4Sschwarze 	     strcmp(s1, "\\-") == 0 ? '-' :
4360f7f2ebbSschwarze 	     strcmp(s1, "\\(bu") == 0 ? 'b' :
4370f7f2ebbSschwarze 	     strcmp(s1, "\\[bu]") == 0 ? 'b' : ' ';
4386f6388b4Sschwarze 	c2 = strcmp(s2, "*") == 0 ? '*' :
4396f6388b4Sschwarze 	     strcmp(s2, "\\-") == 0 ? '-' :
4400f7f2ebbSschwarze 	     strcmp(s2, "\\(bu") == 0 ? 'b' :
4410f7f2ebbSschwarze 	     strcmp(s2, "\\[bu]") == 0 ? 'b' : ' ';
4426f6388b4Sschwarze 	return c1 != c2 ? '\0' : c1 == 'b' ? '*' : c1;
4436f6388b4Sschwarze }
4446f6388b4Sschwarze 
4454175bdabSschwarze static int
man_IP_pre(MAN_ARGS)4464175bdabSschwarze man_IP_pre(MAN_ARGS)
4474175bdabSschwarze {
4487ebbefbeSschwarze 	struct roff_node	*nn;
4496f6388b4Sschwarze 	const char		*list_class;
4506f6388b4Sschwarze 	enum htmltag		 list_elem, body_elem;
4516f6388b4Sschwarze 	char			 list_type;
4526f6388b4Sschwarze 
4536f6388b4Sschwarze 	nn = n->type == ROFFT_BLOCK ? n : n->parent;
4547ebbefbeSschwarze 	list_type = list_continues(roff_node_prev(nn), nn);
4557ebbefbeSschwarze 	if (list_type == '\0') {
4566f6388b4Sschwarze 		/* Start a new list. */
4577ebbefbeSschwarze 		list_type = list_continues(nn, roff_node_next(nn));
4587ebbefbeSschwarze 		if (list_type == '\0')
4596f6388b4Sschwarze 			list_type = ' ';
4606f6388b4Sschwarze 		switch (list_type) {
4616f6388b4Sschwarze 		case ' ':
4626f6388b4Sschwarze 			list_class = "Bl-tag";
4636f6388b4Sschwarze 			list_elem = TAG_DL;
4646f6388b4Sschwarze 			break;
4656f6388b4Sschwarze 		case '*':
4666f6388b4Sschwarze 			list_class = "Bl-bullet";
4676f6388b4Sschwarze 			list_elem = TAG_UL;
4686f6388b4Sschwarze 			break;
4696f6388b4Sschwarze 		case '-':
4706f6388b4Sschwarze 			list_class = "Bl-dash";
4716f6388b4Sschwarze 			list_elem = TAG_UL;
4726f6388b4Sschwarze 			break;
4736f6388b4Sschwarze 		default:
4746f6388b4Sschwarze 			abort();
4756f6388b4Sschwarze 		}
4766f6388b4Sschwarze 	} else {
4776f6388b4Sschwarze 		/* Continue a list that was started earlier. */
4786f6388b4Sschwarze 		list_class = NULL;
4796f6388b4Sschwarze 		list_elem = TAG_MAX;
4806f6388b4Sschwarze 	}
4816f6388b4Sschwarze 	body_elem = list_type == ' ' ? TAG_DD : TAG_LI;
4824175bdabSschwarze 
4837f442bfeSschwarze 	switch (n->type) {
4847f442bfeSschwarze 	case ROFFT_BLOCK:
4857f442bfeSschwarze 		html_close_paragraph(h);
4866f6388b4Sschwarze 		if (list_elem != TAG_MAX)
4876f6388b4Sschwarze 			print_otag(h, list_elem, "c", list_class);
488526e306bSschwarze 		return 1;
4897f442bfeSschwarze 	case ROFFT_HEAD:
4906f6388b4Sschwarze 		if (body_elem == TAG_LI)
4916f6388b4Sschwarze 			return 0;
4920ac7e6ecSschwarze 		print_otag_id(h, TAG_DT, NULL, n);
4937f442bfeSschwarze 		break;
4947f442bfeSschwarze 	case ROFFT_BODY:
4956f6388b4Sschwarze 		print_otag(h, body_elem, "");
4967f442bfeSschwarze 		return 1;
4977f442bfeSschwarze 	default:
4987f442bfeSschwarze 		abort();
4997f442bfeSschwarze 	}
500d991fc2cSschwarze 	switch(n->tok) {
501d991fc2cSschwarze 	case MAN_IP:  /* Only print the first header element. */
502d991fc2cSschwarze 		if (n->child != NULL)
503de63e416Sschwarze 			print_man_node(man, n->child, h);
504d991fc2cSschwarze 		break;
505d991fc2cSschwarze 	case MAN_TP:  /* Only print next-line header elements. */
506d991fc2cSschwarze 	case MAN_TQ:
507074125cbSschwarze 		nn = n->child;
508d991fc2cSschwarze 		while (nn != NULL && (NODE_LINE & nn->flags) == 0)
509074125cbSschwarze 			nn = nn->next;
510d991fc2cSschwarze 		while (nn != NULL) {
511de63e416Sschwarze 			print_man_node(man, nn, h);
512074125cbSschwarze 			nn = nn->next;
513074125cbSschwarze 		}
514d991fc2cSschwarze 		break;
515d991fc2cSschwarze 	default:
516d991fc2cSschwarze 		abort();
517074125cbSschwarze 	}
518526e306bSschwarze 	return 0;
5194175bdabSschwarze }
5204175bdabSschwarze 
5214175bdabSschwarze static int
man_MR_pre(MAN_ARGS)522*f6697133Sschwarze man_MR_pre(MAN_ARGS)
523*f6697133Sschwarze {
524*f6697133Sschwarze 	struct tag	*t;
525*f6697133Sschwarze 	const char	*name, *section, *suffix;
526*f6697133Sschwarze 	char		*label;
527*f6697133Sschwarze 
528*f6697133Sschwarze 	html_setfont(h, ESCAPE_FONTROMAN);
529*f6697133Sschwarze 	name = section = suffix = label = NULL;
530*f6697133Sschwarze 	if (n->child != NULL) {
531*f6697133Sschwarze 		name = n->child->string;
532*f6697133Sschwarze 		if (n->child->next != NULL) {
533*f6697133Sschwarze 			section = n->child->next->string;
534*f6697133Sschwarze 			mandoc_asprintf(&label,
535*f6697133Sschwarze 			    "%s, section %s", name, section);
536*f6697133Sschwarze 			if (n->child->next->next != NULL)
537*f6697133Sschwarze 				suffix = n->child->next->next->string;
538*f6697133Sschwarze 		}
539*f6697133Sschwarze 	}
540*f6697133Sschwarze 
541*f6697133Sschwarze 	if (name != NULL && section != NULL && h->base_man1 != NULL)
542*f6697133Sschwarze 		t = print_otag(h, TAG_A, "chM?", "Xr",
543*f6697133Sschwarze 		    name, section, "aria-label", label);
544*f6697133Sschwarze 	else
545*f6697133Sschwarze 		t = print_otag(h, TAG_A, "c?", "Xr", "aria-label", label);
546*f6697133Sschwarze 
547*f6697133Sschwarze 	free(label);
548*f6697133Sschwarze 	if (name != NULL) {
549*f6697133Sschwarze 		print_text(h, name);
550*f6697133Sschwarze 		h->flags |= HTML_NOSPACE;
551*f6697133Sschwarze 	}
552*f6697133Sschwarze 	print_text(h, "(");
553*f6697133Sschwarze 	h->flags |= HTML_NOSPACE;
554*f6697133Sschwarze 	if (section != NULL) {
555*f6697133Sschwarze 		print_text(h, section);
556*f6697133Sschwarze 		h->flags |= HTML_NOSPACE;
557*f6697133Sschwarze 	}
558*f6697133Sschwarze 	print_text(h, ")");
559*f6697133Sschwarze 	print_tagq(h, t);
560*f6697133Sschwarze 	if (suffix != NULL) {
561*f6697133Sschwarze 		h->flags |= HTML_NOSPACE;
562*f6697133Sschwarze 		print_text(h, suffix);
563*f6697133Sschwarze 	}
564*f6697133Sschwarze 	return 0;
565*f6697133Sschwarze }
566*f6697133Sschwarze 
567*f6697133Sschwarze static int
man_OP_pre(MAN_ARGS)56866ae7cc0Sschwarze man_OP_pre(MAN_ARGS)
56966ae7cc0Sschwarze {
57066ae7cc0Sschwarze 	struct tag	*tt;
57166ae7cc0Sschwarze 
57266ae7cc0Sschwarze 	print_text(h, "[");
57366ae7cc0Sschwarze 	h->flags |= HTML_NOSPACE;
574774a248cSschwarze 	tt = print_otag(h, TAG_SPAN, "c", "Op");
57566ae7cc0Sschwarze 
57618c80517Sschwarze 	if ((n = n->child) != NULL) {
577229cc7fdSschwarze 		print_otag(h, TAG_B, "");
57866ae7cc0Sschwarze 		print_text(h, n->string);
57966ae7cc0Sschwarze 	}
58066ae7cc0Sschwarze 
58166ae7cc0Sschwarze 	print_stagq(h, tt);
58266ae7cc0Sschwarze 
58318c80517Sschwarze 	if (n != NULL && n->next != NULL) {
584229cc7fdSschwarze 		print_otag(h, TAG_I, "");
58566ae7cc0Sschwarze 		print_text(h, n->next->string);
58666ae7cc0Sschwarze 	}
58766ae7cc0Sschwarze 
58866ae7cc0Sschwarze 	print_stagq(h, tt);
58966ae7cc0Sschwarze 	h->flags |= HTML_NOSPACE;
59066ae7cc0Sschwarze 	print_text(h, "]");
591526e306bSschwarze 	return 0;
59266ae7cc0Sschwarze }
59366ae7cc0Sschwarze 
59466ae7cc0Sschwarze static int
man_B_pre(MAN_ARGS)5954175bdabSschwarze man_B_pre(MAN_ARGS)
5964175bdabSschwarze {
597229cc7fdSschwarze 	print_otag(h, TAG_B, "");
598526e306bSschwarze 	return 1;
5994175bdabSschwarze }
6004175bdabSschwarze 
6014175bdabSschwarze static int
man_I_pre(MAN_ARGS)6024175bdabSschwarze man_I_pre(MAN_ARGS)
6034175bdabSschwarze {
604229cc7fdSschwarze 	print_otag(h, TAG_I, "");
605526e306bSschwarze 	return 1;
6064175bdabSschwarze }
6074175bdabSschwarze 
6084175bdabSschwarze static int
man_in_pre(MAN_ARGS)609ddce0b0cSschwarze man_in_pre(MAN_ARGS)
610ddce0b0cSschwarze {
611229cc7fdSschwarze 	print_otag(h, TAG_BR, "");
612526e306bSschwarze 	return 0;
613ddce0b0cSschwarze }
614ddce0b0cSschwarze 
615ddce0b0cSschwarze static int
man_ign_pre(MAN_ARGS)6164175bdabSschwarze man_ign_pre(MAN_ARGS)
6174175bdabSschwarze {
618526e306bSschwarze 	return 0;
6194175bdabSschwarze }
6204175bdabSschwarze 
6214175bdabSschwarze static int
man_RS_pre(MAN_ARGS)6224175bdabSschwarze man_RS_pre(MAN_ARGS)
6234175bdabSschwarze {
6247f442bfeSschwarze 	switch (n->type) {
6257f442bfeSschwarze 	case ROFFT_BLOCK:
6267f442bfeSschwarze 		html_close_paragraph(h);
6277f442bfeSschwarze 		break;
6287f442bfeSschwarze 	case ROFFT_HEAD:
629526e306bSschwarze 		return 0;
6307f442bfeSschwarze 	case ROFFT_BODY:
631b9015fd6Sschwarze 		print_otag(h, TAG_DIV, "c", "Bd-indent");
6327f442bfeSschwarze 		break;
6337f442bfeSschwarze 	default:
6347f442bfeSschwarze 		abort();
6357f442bfeSschwarze 	}
636526e306bSschwarze 	return 1;
6374175bdabSschwarze }
6383aeff926Sschwarze 
6393aeff926Sschwarze static int
man_SY_pre(MAN_ARGS)6405e5a9c61Sschwarze man_SY_pre(MAN_ARGS)
6415e5a9c61Sschwarze {
6425e5a9c61Sschwarze 	switch (n->type) {
6435e5a9c61Sschwarze 	case ROFFT_BLOCK:
6447f442bfeSschwarze 		html_close_paragraph(h);
6455e5a9c61Sschwarze 		print_otag(h, TAG_TABLE, "c", "Nm");
6465e5a9c61Sschwarze 		print_otag(h, TAG_TR, "");
6475e5a9c61Sschwarze 		break;
6485e5a9c61Sschwarze 	case ROFFT_HEAD:
6495e5a9c61Sschwarze 		print_otag(h, TAG_TD, "");
6500ce603abSschwarze 		print_otag(h, TAG_CODE, "c", "Nm");
6515e5a9c61Sschwarze 		break;
6525e5a9c61Sschwarze 	case ROFFT_BODY:
6535e5a9c61Sschwarze 		print_otag(h, TAG_TD, "");
6545e5a9c61Sschwarze 		break;
6555e5a9c61Sschwarze 	default:
6565e5a9c61Sschwarze 		abort();
6575e5a9c61Sschwarze 	}
6585e5a9c61Sschwarze 	return 1;
6595e5a9c61Sschwarze }
6605e5a9c61Sschwarze 
6615e5a9c61Sschwarze static int
man_UR_pre(MAN_ARGS)6623aeff926Sschwarze man_UR_pre(MAN_ARGS)
6633aeff926Sschwarze {
664df9a9479Sbentley 	char *cp;
66518c80517Sschwarze 
6663aeff926Sschwarze 	n = n->child;
667d1982c71Sschwarze 	assert(n->type == ROFFT_HEAD);
66830e5ee06Sschwarze 	if (n->child != NULL) {
669d1982c71Sschwarze 		assert(n->child->type == ROFFT_TEXT);
670df9a9479Sbentley 		if (n->tok == MAN_MT) {
671df9a9479Sbentley 			mandoc_asprintf(&cp, "mailto:%s", n->child->string);
6720ce603abSschwarze 			print_otag(h, TAG_A, "ch", "Mt", cp);
673df9a9479Sbentley 			free(cp);
674df9a9479Sbentley 		} else
6750ce603abSschwarze 			print_otag(h, TAG_A, "ch", "Lk", n->child->string);
6763aeff926Sschwarze 	}
6773aeff926Sschwarze 
678d1982c71Sschwarze 	assert(n->next->type == ROFFT_BODY);
67930e5ee06Sschwarze 	if (n->next->child != NULL)
6803aeff926Sschwarze 		n = n->next;
6813aeff926Sschwarze 
682de63e416Sschwarze 	print_man_nodelist(man, n->child, h);
683526e306bSschwarze 	return 0;
6843aeff926Sschwarze }
685