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