Home | History | Annotate | Line # | Download | only in dist
      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 (at) openbsd.org>
      4  * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons <kristaps (at) 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 
     47 struct	mdoc_html_act {
     48 	int		(*pre)(MDOC_ARGS);
     49 	void		(*post)(MDOC_ARGS);
     50 };
     51 
     52 static	void		  print_mdoc_head(const struct roff_meta *,
     53 				struct html *);
     54 static	void		  print_mdoc_node(MDOC_ARGS);
     55 static	void		  print_mdoc_nodelist(MDOC_ARGS);
     56 static	void		  synopsis_pre(struct html *, struct roff_node *);
     57 
     58 static	void		  mdoc_root_post(const struct roff_meta *,
     59 				struct html *);
     60 static	int		  mdoc_root_pre(const struct roff_meta *,
     61 				struct html *);
     62 
     63 static	void		  mdoc__x_post(MDOC_ARGS);
     64 static	int		  mdoc__x_pre(MDOC_ARGS);
     65 static	int		  mdoc_abort_pre(MDOC_ARGS);
     66 static	int		  mdoc_ad_pre(MDOC_ARGS);
     67 static	int		  mdoc_an_pre(MDOC_ARGS);
     68 static	int		  mdoc_ap_pre(MDOC_ARGS);
     69 static	int		  mdoc_ar_pre(MDOC_ARGS);
     70 static	int		  mdoc_bd_pre(MDOC_ARGS);
     71 static	int		  mdoc_bf_pre(MDOC_ARGS);
     72 static	void		  mdoc_bk_post(MDOC_ARGS);
     73 static	int		  mdoc_bk_pre(MDOC_ARGS);
     74 static	int		  mdoc_bl_pre(MDOC_ARGS);
     75 static	int		  mdoc_cd_pre(MDOC_ARGS);
     76 static	int		  mdoc_code_pre(MDOC_ARGS);
     77 static	int		  mdoc_d1_pre(MDOC_ARGS);
     78 static	int		  mdoc_fa_pre(MDOC_ARGS);
     79 static	int		  mdoc_fd_pre(MDOC_ARGS);
     80 static	int		  mdoc_fl_pre(MDOC_ARGS);
     81 static	int		  mdoc_fn_pre(MDOC_ARGS);
     82 static	int		  mdoc_ft_pre(MDOC_ARGS);
     83 static	int		  mdoc_em_pre(MDOC_ARGS);
     84 static	void		  mdoc_eo_post(MDOC_ARGS);
     85 static	int		  mdoc_eo_pre(MDOC_ARGS);
     86 static	int		  mdoc_ex_pre(MDOC_ARGS);
     87 static	void		  mdoc_fo_post(MDOC_ARGS);
     88 static	int		  mdoc_fo_pre(MDOC_ARGS);
     89 static	int		  mdoc_igndelim_pre(MDOC_ARGS);
     90 static	int		  mdoc_in_pre(MDOC_ARGS);
     91 static	int		  mdoc_it_pre(MDOC_ARGS);
     92 static	int		  mdoc_lb_pre(MDOC_ARGS);
     93 static	int		  mdoc_lk_pre(MDOC_ARGS);
     94 static	int		  mdoc_mt_pre(MDOC_ARGS);
     95 static	int		  mdoc_nd_pre(MDOC_ARGS);
     96 static	int		  mdoc_nm_pre(MDOC_ARGS);
     97 static	int		  mdoc_no_pre(MDOC_ARGS);
     98 static	int		  mdoc_ns_pre(MDOC_ARGS);
     99 static	int		  mdoc_pa_pre(MDOC_ARGS);
    100 static	void		  mdoc_pf_post(MDOC_ARGS);
    101 static	int		  mdoc_pp_pre(MDOC_ARGS);
    102 static	void		  mdoc_quote_post(MDOC_ARGS);
    103 static	int		  mdoc_quote_pre(MDOC_ARGS);
    104 static	int		  mdoc_rs_pre(MDOC_ARGS);
    105 static	int		  mdoc_sh_pre(MDOC_ARGS);
    106 static	int		  mdoc_skip_pre(MDOC_ARGS);
    107 static	int		  mdoc_sm_pre(MDOC_ARGS);
    108 static	int		  mdoc_ss_pre(MDOC_ARGS);
    109 static	int		  mdoc_st_pre(MDOC_ARGS);
    110 static	int		  mdoc_sx_pre(MDOC_ARGS);
    111 static	int		  mdoc_sy_pre(MDOC_ARGS);
    112 static	int		  mdoc_tg_pre(MDOC_ARGS);
    113 static	int		  mdoc_va_pre(MDOC_ARGS);
    114 static	int		  mdoc_vt_pre(MDOC_ARGS);
    115 static	int		  mdoc_xr_pre(MDOC_ARGS);
    116 static	int		  mdoc_xx_pre(MDOC_ARGS);
    117 
    118 static 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  */
    246 static void
    247 synopsis_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 
    282 void
    283 html_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 
    311 static void
    312 print_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 
    333 static void
    334 print_mdoc_nodelist(MDOC_ARGS)
    335 {
    336 
    337 	while (n != NULL) {
    338 		print_mdoc_node(meta, n, h);
    339 		n = n->next;
    340 	}
    341 }
    342 
    343 static void
    344 print_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 
    452 static void
    453 mdoc_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 
    469 static int
    470 mdoc_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 
    507 static int
    508 mdoc_code_pre(MDOC_ARGS)
    509 {
    510 	print_otag_id(h, TAG_CODE, roff_name[n->tok], n);
    511 	return 1;
    512 }
    513 
    514 static int
    515 mdoc_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 
    586 static int
    587 mdoc_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 
    605 static int
    606 mdoc_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 
    621 static int
    622 mdoc_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 
    639 static int
    640 mdoc_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 
    664 static int
    665 mdoc_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 
    692 static int
    693 mdoc_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 
    704 static int
    705 mdoc_ns_pre(MDOC_ARGS)
    706 {
    707 
    708 	if ( ! (NODE_LINE & n->flags))
    709 		h->flags |= HTML_NOSPACE;
    710 	return 1;
    711 }
    712 
    713 static int
    714 mdoc_ar_pre(MDOC_ARGS)
    715 {
    716 	print_otag(h, TAG_VAR, "c", "Ar");
    717 	return 1;
    718 }
    719 
    720 static int
    721 mdoc_xx_pre(MDOC_ARGS)
    722 {
    723 	print_otag(h, TAG_SPAN, "c", "Ux");
    724 	return 1;
    725 }
    726 
    727 static int
    728 mdoc_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 
    802 static int
    803 mdoc_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 
    877 static int
    878 mdoc_ex_pre(MDOC_ARGS)
    879 {
    880 	if (roff_node_prev(n) != NULL)
    881 		print_otag(h, TAG_BR, "");
    882 	return 1;
    883 }
    884 
    885 static int
    886 mdoc_st_pre(MDOC_ARGS)
    887 {
    888 	print_otag(h, TAG_SPAN, "c", "St");
    889 	return 1;
    890 }
    891 
    892 static int
    893 mdoc_em_pre(MDOC_ARGS)
    894 {
    895 	print_otag_id(h, TAG_I, "Em", n);
    896 	return 1;
    897 }
    898 
    899 static int
    900 mdoc_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 
    919 static int
    920 mdoc_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 
    930 static int
    931 mdoc_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 
    977 static int
    978 mdoc_pa_pre(MDOC_ARGS)
    979 {
    980 	print_otag(h, TAG_SPAN, "c", "Pa");
    981 	return 1;
    982 }
    983 
    984 static int
    985 mdoc_ad_pre(MDOC_ARGS)
    986 {
    987 	print_otag(h, TAG_SPAN, "c", "Ad");
    988 	return 1;
    989 }
    990 
    991 static int
    992 mdoc_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 
   1015 static int
   1016 mdoc_cd_pre(MDOC_ARGS)
   1017 {
   1018 	synopsis_pre(h, n);
   1019 	print_otag(h, TAG_CODE, "c", "Cd");
   1020 	return 1;
   1021 }
   1022 
   1023 static int
   1024 mdoc_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 
   1051 static int
   1052 mdoc_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 
   1102 static int
   1103 mdoc_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 
   1117 static int
   1118 mdoc_ft_pre(MDOC_ARGS)
   1119 {
   1120 	synopsis_pre(h, n);
   1121 	print_otag(h, TAG_VAR, "c", "Ft");
   1122 	return 1;
   1123 }
   1124 
   1125 static int
   1126 mdoc_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 
   1191 static int
   1192 mdoc_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 
   1208 static int
   1209 mdoc_skip_pre(MDOC_ARGS)
   1210 {
   1211 
   1212 	return 0;
   1213 }
   1214 
   1215 static int
   1216 mdoc_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 
   1237 static int
   1238 mdoc_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 
   1274 static int
   1275 mdoc_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 
   1291 static int
   1292 mdoc_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 
   1317 static void
   1318 mdoc_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 
   1328 static int
   1329 mdoc_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 
   1372 static int
   1373 mdoc_va_pre(MDOC_ARGS)
   1374 {
   1375 	print_otag(h, TAG_VAR, "c", "Va");
   1376 	return 1;
   1377 }
   1378 
   1379 static int
   1380 mdoc_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 
   1388 static int
   1389 mdoc_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 
   1419 static int
   1420 mdoc_igndelim_pre(MDOC_ARGS)
   1421 {
   1422 	h->flags |= HTML_IGNDELIM;
   1423 	return 1;
   1424 }
   1425 
   1426 static void
   1427 mdoc_pf_post(MDOC_ARGS)
   1428 {
   1429 	if ( ! (n->next == NULL || n->next->flags & NODE_LINE))
   1430 		h->flags |= HTML_NOSPACE;
   1431 }
   1432 
   1433 static int
   1434 mdoc_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 
   1454 static int
   1455 mdoc_no_pre(MDOC_ARGS)
   1456 {
   1457 	print_otag_id(h, TAG_SPAN, roff_name[n->tok], n);
   1458 	return 1;
   1459 }
   1460 
   1461 static int
   1462 mdoc_sy_pre(MDOC_ARGS)
   1463 {
   1464 	print_otag_id(h, TAG_B, "Sy", n);
   1465 	return 1;
   1466 }
   1467 
   1468 static int
   1469 mdoc_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 
   1480 static int
   1481 mdoc__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 
   1546 static void
   1547 mdoc__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 
   1566 static int
   1567 mdoc_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 
   1586 static void
   1587 mdoc_bk_post(MDOC_ARGS)
   1588 {
   1589 
   1590 	if (n->type == ROFFT_BODY)
   1591 		h->flags &= ~(HTML_KEEP | HTML_PREKEEP);
   1592 }
   1593 
   1594 static int
   1595 mdoc_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 
   1662 static void
   1663 mdoc_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 
   1717 static int
   1718 mdoc_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 
   1736 static void
   1737 mdoc_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 
   1758 static int
   1759 mdoc_abort_pre(MDOC_ARGS)
   1760 {
   1761 	abort();
   1762 }
   1763