GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.bin/mandoc/mdoc_html.c Lines: 137 743 18.4 %
Date: 2017-11-13 Branches: 68 597 11.4 %

Line Branch Exec Source
1
/*	$OpenBSD: mdoc_html.c,v 1.169 2017/07/15 17:57:46 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 <sys/types.h>
19
20
#include <assert.h>
21
#include <ctype.h>
22
#include <stdio.h>
23
#include <stdlib.h>
24
#include <string.h>
25
#include <unistd.h>
26
27
#include "mandoc_aux.h"
28
#include "mandoc.h"
29
#include "roff.h"
30
#include "mdoc.h"
31
#include "out.h"
32
#include "html.h"
33
#include "main.h"
34
35
#define	INDENT		 5
36
37
#define	MDOC_ARGS	  const struct roff_meta *meta, \
38
			  struct roff_node *n, \
39
			  struct html *h
40
41
#ifndef MIN
42
#define	MIN(a,b)	((/*CONSTCOND*/(a)<(b))?(a):(b))
43
#endif
44
45
struct	htmlmdoc {
46
	int		(*pre)(MDOC_ARGS);
47
	void		(*post)(MDOC_ARGS);
48
};
49
50
static	char		 *cond_id(const struct roff_node *);
51
static	void		  print_mdoc_head(MDOC_ARGS);
52
static	void		  print_mdoc_node(MDOC_ARGS);
53
static	void		  print_mdoc_nodelist(MDOC_ARGS);
54
static	void		  synopsis_pre(struct html *,
55
				const struct roff_node *);
56
57
static	void		  mdoc_root_post(MDOC_ARGS);
58
static	int		  mdoc_root_pre(MDOC_ARGS);
59
60
static	void		  mdoc__x_post(MDOC_ARGS);
61
static	int		  mdoc__x_pre(MDOC_ARGS);
62
static	int		  mdoc_ad_pre(MDOC_ARGS);
63
static	int		  mdoc_an_pre(MDOC_ARGS);
64
static	int		  mdoc_ap_pre(MDOC_ARGS);
65
static	int		  mdoc_ar_pre(MDOC_ARGS);
66
static	int		  mdoc_bd_pre(MDOC_ARGS);
67
static	int		  mdoc_bf_pre(MDOC_ARGS);
68
static	void		  mdoc_bk_post(MDOC_ARGS);
69
static	int		  mdoc_bk_pre(MDOC_ARGS);
70
static	int		  mdoc_bl_pre(MDOC_ARGS);
71
static	int		  mdoc_cd_pre(MDOC_ARGS);
72
static	int		  mdoc_cm_pre(MDOC_ARGS);
73
static	int		  mdoc_d1_pre(MDOC_ARGS);
74
static	int		  mdoc_dv_pre(MDOC_ARGS);
75
static	int		  mdoc_fa_pre(MDOC_ARGS);
76
static	int		  mdoc_fd_pre(MDOC_ARGS);
77
static	int		  mdoc_fl_pre(MDOC_ARGS);
78
static	int		  mdoc_fn_pre(MDOC_ARGS);
79
static	int		  mdoc_ft_pre(MDOC_ARGS);
80
static	int		  mdoc_em_pre(MDOC_ARGS);
81
static	void		  mdoc_eo_post(MDOC_ARGS);
82
static	int		  mdoc_eo_pre(MDOC_ARGS);
83
static	int		  mdoc_er_pre(MDOC_ARGS);
84
static	int		  mdoc_ev_pre(MDOC_ARGS);
85
static	int		  mdoc_ex_pre(MDOC_ARGS);
86
static	void		  mdoc_fo_post(MDOC_ARGS);
87
static	int		  mdoc_fo_pre(MDOC_ARGS);
88
static	int		  mdoc_ic_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_li_pre(MDOC_ARGS);
94
static	int		  mdoc_lk_pre(MDOC_ARGS);
95
static	int		  mdoc_mt_pre(MDOC_ARGS);
96
static	int		  mdoc_ms_pre(MDOC_ARGS);
97
static	int		  mdoc_nd_pre(MDOC_ARGS);
98
static	int		  mdoc_nm_pre(MDOC_ARGS);
99
static	int		  mdoc_no_pre(MDOC_ARGS);
100
static	int		  mdoc_ns_pre(MDOC_ARGS);
101
static	int		  mdoc_pa_pre(MDOC_ARGS);
102
static	void		  mdoc_pf_post(MDOC_ARGS);
103
static	int		  mdoc_pp_pre(MDOC_ARGS);
104
static	void		  mdoc_quote_post(MDOC_ARGS);
105
static	int		  mdoc_quote_pre(MDOC_ARGS);
106
static	int		  mdoc_rs_pre(MDOC_ARGS);
107
static	int		  mdoc_sh_pre(MDOC_ARGS);
108
static	int		  mdoc_skip_pre(MDOC_ARGS);
109
static	int		  mdoc_sm_pre(MDOC_ARGS);
110
static	int		  mdoc_ss_pre(MDOC_ARGS);
111
static	int		  mdoc_st_pre(MDOC_ARGS);
112
static	int		  mdoc_sx_pre(MDOC_ARGS);
113
static	int		  mdoc_sy_pre(MDOC_ARGS);
114
static	int		  mdoc_va_pre(MDOC_ARGS);
115
static	int		  mdoc_vt_pre(MDOC_ARGS);
116
static	int		  mdoc_xr_pre(MDOC_ARGS);
117
static	int		  mdoc_xx_pre(MDOC_ARGS);
118
119
static	const struct htmlmdoc __mdocs[MDOC_MAX - MDOC_Dd] = {
120
	{NULL, NULL}, /* Dd */
121
	{NULL, NULL}, /* Dt */
122
	{NULL, NULL}, /* Os */
123
	{mdoc_sh_pre, NULL }, /* Sh */
124
	{mdoc_ss_pre, NULL }, /* Ss */
125
	{mdoc_pp_pre, NULL}, /* Pp */
126
	{mdoc_d1_pre, NULL}, /* D1 */
127
	{mdoc_d1_pre, NULL}, /* Dl */
128
	{mdoc_bd_pre, NULL}, /* Bd */
129
	{NULL, NULL}, /* Ed */
130
	{mdoc_bl_pre, NULL}, /* Bl */
131
	{NULL, NULL}, /* El */
132
	{mdoc_it_pre, NULL}, /* It */
133
	{mdoc_ad_pre, NULL}, /* Ad */
134
	{mdoc_an_pre, NULL}, /* An */
135
	{mdoc_ap_pre, NULL}, /* Ap */
136
	{mdoc_ar_pre, NULL}, /* Ar */
137
	{mdoc_cd_pre, NULL}, /* Cd */
138
	{mdoc_cm_pre, NULL}, /* Cm */
139
	{mdoc_dv_pre, NULL}, /* Dv */
140
	{mdoc_er_pre, NULL}, /* Er */
141
	{mdoc_ev_pre, NULL}, /* Ev */
142
	{mdoc_ex_pre, NULL}, /* Ex */
143
	{mdoc_fa_pre, NULL}, /* Fa */
144
	{mdoc_fd_pre, NULL}, /* Fd */
145
	{mdoc_fl_pre, NULL}, /* Fl */
146
	{mdoc_fn_pre, NULL}, /* Fn */
147
	{mdoc_ft_pre, NULL}, /* Ft */
148
	{mdoc_ic_pre, NULL}, /* Ic */
149
	{mdoc_in_pre, NULL}, /* In */
150
	{mdoc_li_pre, NULL}, /* Li */
151
	{mdoc_nd_pre, NULL}, /* Nd */
152
	{mdoc_nm_pre, NULL}, /* Nm */
153
	{mdoc_quote_pre, mdoc_quote_post}, /* Op */
154
	{mdoc_ft_pre, NULL}, /* Ot */
155
	{mdoc_pa_pre, NULL}, /* Pa */
156
	{mdoc_ex_pre, NULL}, /* Rv */
157
	{mdoc_st_pre, NULL}, /* St */
158
	{mdoc_va_pre, NULL}, /* Va */
159
	{mdoc_vt_pre, NULL}, /* Vt */
160
	{mdoc_xr_pre, NULL}, /* Xr */
161
	{mdoc__x_pre, mdoc__x_post}, /* %A */
162
	{mdoc__x_pre, mdoc__x_post}, /* %B */
163
	{mdoc__x_pre, mdoc__x_post}, /* %D */
164
	{mdoc__x_pre, mdoc__x_post}, /* %I */
165
	{mdoc__x_pre, mdoc__x_post}, /* %J */
166
	{mdoc__x_pre, mdoc__x_post}, /* %N */
167
	{mdoc__x_pre, mdoc__x_post}, /* %O */
168
	{mdoc__x_pre, mdoc__x_post}, /* %P */
169
	{mdoc__x_pre, mdoc__x_post}, /* %R */
170
	{mdoc__x_pre, mdoc__x_post}, /* %T */
171
	{mdoc__x_pre, mdoc__x_post}, /* %V */
172
	{NULL, NULL}, /* Ac */
173
	{mdoc_quote_pre, mdoc_quote_post}, /* Ao */
174
	{mdoc_quote_pre, mdoc_quote_post}, /* Aq */
175
	{mdoc_xx_pre, NULL}, /* At */
176
	{NULL, NULL}, /* Bc */
177
	{mdoc_bf_pre, NULL}, /* Bf */
178
	{mdoc_quote_pre, mdoc_quote_post}, /* Bo */
179
	{mdoc_quote_pre, mdoc_quote_post}, /* Bq */
180
	{mdoc_xx_pre, NULL}, /* Bsx */
181
	{mdoc_xx_pre, NULL}, /* Bx */
182
	{mdoc_skip_pre, NULL}, /* Db */
183
	{NULL, NULL}, /* Dc */
184
	{mdoc_quote_pre, mdoc_quote_post}, /* Do */
185
	{mdoc_quote_pre, mdoc_quote_post}, /* Dq */
186
	{NULL, NULL}, /* Ec */ /* FIXME: no space */
187
	{NULL, NULL}, /* Ef */
188
	{mdoc_em_pre, NULL}, /* Em */
189
	{mdoc_eo_pre, mdoc_eo_post}, /* Eo */
190
	{mdoc_xx_pre, NULL}, /* Fx */
191
	{mdoc_ms_pre, NULL}, /* Ms */
192
	{mdoc_no_pre, NULL}, /* No */
193
	{mdoc_ns_pre, NULL}, /* Ns */
194
	{mdoc_xx_pre, NULL}, /* Nx */
195
	{mdoc_xx_pre, NULL}, /* Ox */
196
	{NULL, NULL}, /* Pc */
197
	{mdoc_igndelim_pre, mdoc_pf_post}, /* Pf */
198
	{mdoc_quote_pre, mdoc_quote_post}, /* Po */
199
	{mdoc_quote_pre, mdoc_quote_post}, /* Pq */
200
	{NULL, NULL}, /* Qc */
201
	{mdoc_quote_pre, mdoc_quote_post}, /* Ql */
202
	{mdoc_quote_pre, mdoc_quote_post}, /* Qo */
203
	{mdoc_quote_pre, mdoc_quote_post}, /* Qq */
204
	{NULL, NULL}, /* Re */
205
	{mdoc_rs_pre, NULL}, /* Rs */
206
	{NULL, NULL}, /* Sc */
207
	{mdoc_quote_pre, mdoc_quote_post}, /* So */
208
	{mdoc_quote_pre, mdoc_quote_post}, /* Sq */
209
	{mdoc_sm_pre, NULL}, /* Sm */
210
	{mdoc_sx_pre, NULL}, /* Sx */
211
	{mdoc_sy_pre, NULL}, /* Sy */
212
	{NULL, NULL}, /* Tn */
213
	{mdoc_xx_pre, NULL}, /* Ux */
214
	{NULL, NULL}, /* Xc */
215
	{NULL, NULL}, /* Xo */
216
	{mdoc_fo_pre, mdoc_fo_post}, /* Fo */
217
	{NULL, NULL}, /* Fc */
218
	{mdoc_quote_pre, mdoc_quote_post}, /* Oo */
219
	{NULL, NULL}, /* Oc */
220
	{mdoc_bk_pre, mdoc_bk_post}, /* Bk */
221
	{NULL, NULL}, /* Ek */
222
	{NULL, NULL}, /* Bt */
223
	{NULL, NULL}, /* Hf */
224
	{mdoc_em_pre, NULL}, /* Fr */
225
	{NULL, NULL}, /* Ud */
226
	{mdoc_lb_pre, NULL}, /* Lb */
227
	{mdoc_pp_pre, NULL}, /* Lp */
228
	{mdoc_lk_pre, NULL}, /* Lk */
229
	{mdoc_mt_pre, NULL}, /* Mt */
230
	{mdoc_quote_pre, mdoc_quote_post}, /* Brq */
231
	{mdoc_quote_pre, mdoc_quote_post}, /* Bro */
232
	{NULL, NULL}, /* Brc */
233
	{mdoc__x_pre, mdoc__x_post}, /* %C */
234
	{mdoc_skip_pre, NULL}, /* Es */
235
	{mdoc_quote_pre, mdoc_quote_post}, /* En */
236
	{mdoc_xx_pre, NULL}, /* Dx */
237
	{mdoc__x_pre, mdoc__x_post}, /* %Q */
238
	{mdoc__x_pre, mdoc__x_post}, /* %U */
239
	{NULL, NULL}, /* Ta */
240
};
241
static	const struct htmlmdoc *const mdocs = __mdocs - MDOC_Dd;
242
243
244
/*
245
 * See the same function in mdoc_term.c for documentation.
246
 */
247
static void
248
synopsis_pre(struct html *h, const struct roff_node *n)
249
{
250
251
	if (NULL == n->prev || ! (NODE_SYNPRETTY & n->flags))
252
		return;
253
254
	if (n->prev->tok == n->tok &&
255
	    MDOC_Fo != n->tok &&
256
	    MDOC_Ft != n->tok &&
257
	    MDOC_Fn != n->tok) {
258
		print_otag(h, TAG_BR, "");
259
		return;
260
	}
261
262
	switch (n->prev->tok) {
263
	case MDOC_Fd:
264
	case MDOC_Fn:
265
	case MDOC_Fo:
266
	case MDOC_In:
267
	case MDOC_Vt:
268
		print_paragraph(h);
269
		break;
270
	case MDOC_Ft:
271
		if (MDOC_Fn != n->tok && MDOC_Fo != n->tok) {
272
			print_paragraph(h);
273
			break;
274
		}
275
		/* FALLTHROUGH */
276
	default:
277
		print_otag(h, TAG_BR, "");
278
		break;
279
	}
280
}
281
282
void
283
html_mdoc(void *arg, const struct roff_man *mdoc)
284
{
285
	struct html	*h;
286
	struct tag	*t;
287
288
102
	h = (struct html *)arg;
289
290
51
	if ((h->oflags & HTML_FRAGMENT) == 0) {
291
51
		print_gen_decls(h);
292
51
		print_otag(h, TAG_HTML, "");
293
51
		t = print_otag(h, TAG_HEAD, "");
294
51
		print_mdoc_head(&mdoc->meta, mdoc->first->child, h);
295
51
		print_tagq(h, t);
296
51
		print_otag(h, TAG_BODY, "");
297
51
	}
298
299
51
	mdoc_root_pre(&mdoc->meta, mdoc->first->child, h);
300
51
	t = print_otag(h, TAG_DIV, "c", "manual-text");
301
51
	print_mdoc_nodelist(&mdoc->meta, mdoc->first->child, h);
302
51
	print_tagq(h, t);
303
51
	mdoc_root_post(&mdoc->meta, mdoc->first->child, h);
304
51
	print_tagq(h, NULL);
305
51
}
306
307
static void
308
print_mdoc_head(MDOC_ARGS)
309
{
310
102
	char	*cp;
311
312
51
	print_gen_head(h);
313
314

51
	if (meta->arch != NULL && meta->msec != NULL)
315
		mandoc_asprintf(&cp, "%s(%s) (%s)", meta->title,
316
		    meta->msec, meta->arch);
317
51
	else if (meta->msec != NULL)
318
51
		mandoc_asprintf(&cp, "%s(%s)", meta->title, meta->msec);
319
	else if (meta->arch != NULL)
320
		mandoc_asprintf(&cp, "%s (%s)", meta->title, meta->arch);
321
	else
322
		cp = mandoc_strdup(meta->title);
323
324
51
	print_otag(h, TAG_TITLE, "");
325
51
	print_text(h, cp);
326
51
	free(cp);
327
51
}
328
329
static void
330
print_mdoc_nodelist(MDOC_ARGS)
331
{
332
333
3579
	while (n != NULL) {
334
1020
		print_mdoc_node(meta, n, h);
335
1020
		n = n->next;
336
	}
337
513
}
338
339
static void
340
print_mdoc_node(MDOC_ARGS)
341
{
342
	int		 child;
343
	struct tag	*t;
344
345
2088
	if (n->flags & NODE_NOPRT)
346
153
		return;
347
348
	child = 1;
349
891
	t = h->tag;
350
891
	n->flags &= ~NODE_ENDED;
351
352

891
	switch (n->type) {
353
	case ROFFT_TEXT:
354
		/* No tables in this mode... */
355
324
		assert(NULL == h->tblt);
356
357
		/*
358
		 * Make sure that if we're in a literal mode already
359
		 * (i.e., within a <PRE>) don't print the newline.
360
		 */
361

324
		if (*n->string == ' ' && n->flags & NODE_LINE &&
362
		    (h->flags & (HTML_LITERAL | HTML_NONEWLINE)) == 0)
363
			print_otag(h, TAG_BR, "");
364
324
		if (NODE_DELIMC & n->flags)
365
			h->flags |= HTML_NOSPACE;
366
324
		print_text(h, n->string);
367
324
		if (NODE_DELIMO & n->flags)
368
			h->flags |= HTML_NOSPACE;
369
324
		return;
370
	case ROFFT_EQN:
371
48
		print_eqn(h, n->eqn);
372
48
		break;
373
	case ROFFT_TBL:
374
		/*
375
		 * This will take care of initialising all of the table
376
		 * state data for the first table, then tearing it down
377
		 * for the last one.
378
		 */
379
		print_tbl(h, n->span);
380
		return;
381
	default:
382
		/*
383
		 * Close out the current table, if it's open, and unset
384
		 * the "meta" table state.  This will be reopened on the
385
		 * next table element.
386
		 */
387
519
		if (h->tblt != NULL) {
388
			print_tblclose(h);
389
			t = h->tag;
390
		}
391
519
		assert(h->tblt == NULL);
392
519
		if (n->tok < ROFF_MAX) {
393
			roff_html_pre(h, n);
394
			child = 0;
395
			break;
396
		}
397

1038
		assert(n->tok >= MDOC_Dd && n->tok < MDOC_MAX);
398

519
		if (mdocs[n->tok].pre != NULL &&
399
519
		    (n->end == ENDBODY_NOT || n->child != NULL))
400
519
			child = (*mdocs[n->tok].pre)(meta, n, h);
401
		break;
402
	}
403
404

567
	if (h->flags & HTML_KEEP && n->flags & NODE_LINE) {
405
		h->flags &= ~HTML_KEEP;
406
		h->flags |= HTML_PREKEEP;
407
	}
408
409

1128
	if (child && n->child)
410
462
		print_mdoc_nodelist(meta, n->child, h);
411
412
567
	print_stagq(h, t);
413
414
567
	switch (n->type) {
415
	case ROFFT_EQN:
416
		break;
417
	default:
418

519
		if (n->tok < ROFF_MAX ||
419
519
		    mdocs[n->tok].post == NULL ||
420
		    n->flags & NODE_ENDED)
421
			break;
422
		(*mdocs[n->tok].post)(meta, n, h);
423
		if (n->end != ENDBODY_NOT)
424
			n->body->flags |= NODE_ENDED;
425
		break;
426
	}
427
1611
}
428
429
static void
430
mdoc_root_post(MDOC_ARGS)
431
{
432
	struct tag	*t, *tt;
433
434
102
	t = print_otag(h, TAG_TABLE, "c", "foot");
435
51
	tt = print_otag(h, TAG_TR, "");
436
437
51
	print_otag(h, TAG_TD, "c", "foot-date");
438
51
	print_text(h, meta->date);
439
51
	print_stagq(h, tt);
440
441
51
	print_otag(h, TAG_TD, "c", "foot-os");
442
51
	print_text(h, meta->os);
443
51
	print_tagq(h, t);
444
51
}
445
446
static int
447
mdoc_root_pre(MDOC_ARGS)
448
{
449
	struct tag	*t, *tt;
450
102
	char		*volume, *title;
451
452
51
	if (NULL == meta->arch)
453
51
		volume = mandoc_strdup(meta->vol);
454
	else
455
		mandoc_asprintf(&volume, "%s (%s)",
456
		    meta->vol, meta->arch);
457
458
51
	if (NULL == meta->msec)
459
		title = mandoc_strdup(meta->title);
460
	else
461
51
		mandoc_asprintf(&title, "%s(%s)",
462
		    meta->title, meta->msec);
463
464
51
	t = print_otag(h, TAG_TABLE, "c", "head");
465
51
	tt = print_otag(h, TAG_TR, "");
466
467
51
	print_otag(h, TAG_TD, "c", "head-ltitle");
468
51
	print_text(h, title);
469
51
	print_stagq(h, tt);
470
471
51
	print_otag(h, TAG_TD, "c", "head-vol");
472
51
	print_text(h, volume);
473
51
	print_stagq(h, tt);
474
475
51
	print_otag(h, TAG_TD, "c", "head-rtitle");
476
51
	print_text(h, title);
477
51
	print_tagq(h, t);
478
479
51
	free(title);
480
51
	free(volume);
481
51
	return 1;
482
51
}
483
484
static char *
485
cond_id(const struct roff_node *n)
486
{
487
	if (n->child != NULL &&
488
	    n->child->type == ROFFT_TEXT &&
489
	    (n->prev == NULL ||
490
	     (n->prev->type == ROFFT_TEXT &&
491
	      strcmp(n->prev->string, "|") == 0)) &&
492
	    (n->parent->tok == MDOC_It ||
493
	     (n->parent->tok == MDOC_Xo &&
494
	      n->parent->parent->prev == NULL &&
495
	      n->parent->parent->parent->tok == MDOC_It)))
496
		return html_make_id(n);
497
	return NULL;
498
}
499
500
static int
501
mdoc_sh_pre(MDOC_ARGS)
502
{
503
	char	*id;
504
505
816
	switch (n->type) {
506
	case ROFFT_HEAD:
507
102
		id = html_make_id(n);
508
102
		print_otag(h, TAG_H1, "cTi", "Sh", id);
509
102
		if (id != NULL)
510
102
			print_otag(h, TAG_A, "chR", "selflink", id);
511
102
		free(id);
512
102
		break;
513
	case ROFFT_BODY:
514
102
		if (n->sec == SEC_AUTHORS)
515
			h->flags &= ~(HTML_SPLIT|HTML_NOSPLIT);
516
		break;
517
	default:
518
		break;
519
	}
520
306
	return 1;
521
}
522
523
static int
524
mdoc_ss_pre(MDOC_ARGS)
525
{
526
	char	*id;
527
528
	if (n->type != ROFFT_HEAD)
529
		return 1;
530
531
	id = html_make_id(n);
532
	print_otag(h, TAG_H2, "cTi", "Ss", id);
533
	if (id != NULL)
534
		print_otag(h, TAG_A, "chR", "selflink", id);
535
	free(id);
536
	return 1;
537
}
538
539
static int
540
mdoc_fl_pre(MDOC_ARGS)
541
{
542
	char	*id;
543
544
	if ((id = cond_id(n)) != NULL)
545
		print_otag(h, TAG_A, "chR", "selflink", id);
546
	print_otag(h, TAG_B, "cTi", "Fl", id);
547
	free(id);
548
549
	print_text(h, "\\-");
550
	if (!(n->child == NULL &&
551
	    (n->next == NULL ||
552
	     n->next->type == ROFFT_TEXT ||
553
	     n->next->flags & NODE_LINE)))
554
		h->flags |= HTML_NOSPACE;
555
556
	return 1;
557
}
558
559
static int
560
mdoc_cm_pre(MDOC_ARGS)
561
{
562
	char	*id;
563
564
	if ((id = cond_id(n)) != NULL)
565
		print_otag(h, TAG_A, "chR", "selflink", id);
566
	print_otag(h, TAG_B, "cTi", "Cm", id);
567
	free(id);
568
	return 1;
569
}
570
571
static int
572
mdoc_nd_pre(MDOC_ARGS)
573
{
574
306
	if (n->type != ROFFT_BODY)
575
102
		return 1;
576
577
	/* XXX: this tag in theory can contain block elements. */
578
579
51
	print_text(h, "\\(em");
580
51
	print_otag(h, TAG_SPAN, "cT", "Nd");
581
51
	return 1;
582
153
}
583
584
static int
585
mdoc_nm_pre(MDOC_ARGS)
586
{
587

102
	switch (n->type) {
588
	case ROFFT_HEAD:
589
		print_otag(h, TAG_TD, "");
590
		/* FALLTHROUGH */
591
	case ROFFT_ELEM:
592
51
		print_otag(h, TAG_B, "cT", "Nm");
593
51
		return 1;
594
	case ROFFT_BODY:
595
		print_otag(h, TAG_TD, "");
596
		return 1;
597
	default:
598
		break;
599
	}
600
	synopsis_pre(h, n);
601
	print_otag(h, TAG_TABLE, "c", "Nm");
602
	print_otag(h, TAG_TR, "");
603
	return 1;
604
51
}
605
606
static int
607
mdoc_xr_pre(MDOC_ARGS)
608
{
609
	if (NULL == n->child)
610
		return 0;
611
612
	if (h->base_man)
613
		print_otag(h, TAG_A, "cThM", "Xr",
614
		    n->child->string, n->child->next == NULL ?
615
		    NULL : n->child->next->string);
616
	else
617
		print_otag(h, TAG_A, "cT", "Xr");
618
619
	n = n->child;
620
	print_text(h, n->string);
621
622
	if (NULL == (n = n->next))
623
		return 0;
624
625
	h->flags |= HTML_NOSPACE;
626
	print_text(h, "(");
627
	h->flags |= HTML_NOSPACE;
628
	print_text(h, n->string);
629
	h->flags |= HTML_NOSPACE;
630
	print_text(h, ")");
631
	return 0;
632
}
633
634
static int
635
mdoc_ns_pre(MDOC_ARGS)
636
{
637
638
	if ( ! (NODE_LINE & n->flags))
639
		h->flags |= HTML_NOSPACE;
640
	return 1;
641
}
642
643
static int
644
mdoc_ar_pre(MDOC_ARGS)
645
{
646
	print_otag(h, TAG_VAR, "cT", "Ar");
647
	return 1;
648
}
649
650
static int
651
mdoc_xx_pre(MDOC_ARGS)
652
{
653
	print_otag(h, TAG_SPAN, "c", "Ux");
654
	return 1;
655
}
656
657
static int
658
mdoc_it_pre(MDOC_ARGS)
659
{
660
	const struct roff_node	*bl;
661
	struct tag		*t;
662
	const char		*cattr;
663
	enum mdoc_list		 type;
664
665
	bl = n->parent;
666
	while (bl->tok != MDOC_Bl)
667
		bl = bl->parent;
668
	type = bl->norm->Bl.type;
669
670
	switch (type) {
671
	case LIST_bullet:
672
		cattr = "It-bullet";
673
		break;
674
	case LIST_dash:
675
	case LIST_hyphen:
676
		cattr = "It-dash";
677
		break;
678
	case LIST_item:
679
		cattr = "It-item";
680
		break;
681
	case LIST_enum:
682
		cattr = "It-enum";
683
		break;
684
	case LIST_diag:
685
		cattr = "It-diag";
686
		break;
687
	case LIST_hang:
688
		cattr = "It-hang";
689
		break;
690
	case LIST_inset:
691
		cattr = "It-inset";
692
		break;
693
	case LIST_ohang:
694
		cattr = "It-ohang";
695
		break;
696
	case LIST_tag:
697
		cattr = "It-tag";
698
		break;
699
	case LIST_column:
700
		cattr = "It-column";
701
		break;
702
	default:
703
		break;
704
	}
705
706
	switch (type) {
707
	case LIST_bullet:
708
	case LIST_dash:
709
	case LIST_hyphen:
710
	case LIST_item:
711
	case LIST_enum:
712
		switch (n->type) {
713
		case ROFFT_HEAD:
714
			return 0;
715
		case ROFFT_BODY:
716
			print_otag(h, TAG_LI, "c", cattr);
717
			break;
718
		default:
719
			break;
720
		}
721
		break;
722
	case LIST_diag:
723
	case LIST_hang:
724
	case LIST_inset:
725
	case LIST_ohang:
726
		switch (n->type) {
727
		case ROFFT_HEAD:
728
			print_otag(h, TAG_DT, "c", cattr);
729
			if (type == LIST_diag)
730
				print_otag(h, TAG_B, "c", cattr);
731
			break;
732
		case ROFFT_BODY:
733
			print_otag(h, TAG_DD, "csw*+l", cattr,
734
			    bl->norm->Bl.width);
735
			break;
736
		default:
737
			break;
738
		}
739
		break;
740
	case LIST_tag:
741
		switch (n->type) {
742
		case ROFFT_HEAD:
743
			if (h->style != NULL && !bl->norm->Bl.comp &&
744
			    (n->parent->prev == NULL ||
745
			     n->parent->prev->body == NULL ||
746
			     n->parent->prev->body->child != NULL)) {
747
				t = print_otag(h, TAG_DT, "csw*+-l",
748
				    cattr, bl->norm->Bl.width);
749
				print_text(h, "\\ ");
750
				print_tagq(h, t);
751
				t = print_otag(h, TAG_DD, "c", cattr);
752
				print_text(h, "\\ ");
753
				print_tagq(h, t);
754
			}
755
			print_otag(h, TAG_DT, "csw*+-l", cattr,
756
			    bl->norm->Bl.width);
757
			break;
758
		case ROFFT_BODY:
759
			if (n->child == NULL) {
760
				print_otag(h, TAG_DD, "css?", cattr,
761
				    "width", "auto");
762
				print_text(h, "\\ ");
763
			} else
764
				print_otag(h, TAG_DD, "c", cattr);
765
			break;
766
		default:
767
			break;
768
		}
769
		break;
770
	case LIST_column:
771
		switch (n->type) {
772
		case ROFFT_HEAD:
773
			break;
774
		case ROFFT_BODY:
775
			print_otag(h, TAG_TD, "c", cattr);
776
			break;
777
		default:
778
			print_otag(h, TAG_TR, "c", cattr);
779
		}
780
	default:
781
		break;
782
	}
783
784
	return 1;
785
}
786
787
static int
788
mdoc_bl_pre(MDOC_ARGS)
789
{
790
	char		 cattr[21];
791
	struct tag	*t;
792
	struct mdoc_bl	*bl;
793
	size_t		 i;
794
	enum htmltag	 elemtype;
795
796
	bl = &n->norm->Bl;
797
798
	switch (n->type) {
799
	case ROFFT_BODY:
800
		return 1;
801
802
	case ROFFT_HEAD:
803
		if (bl->type != LIST_column || bl->ncols == 0)
804
			return 0;
805
806
		/*
807
		 * For each column, print out the <COL> tag with our
808
		 * suggested width.  The last column gets min-width, as
809
		 * in terminal mode it auto-sizes to the width of the
810
		 * screen and we want to preserve that behaviour.
811
		 */
812
813
		t = print_otag(h, TAG_COLGROUP, "");
814
		for (i = 0; i < bl->ncols - 1; i++)
815
			print_otag(h, TAG_COL, "sw+w", bl->cols[i]);
816
		print_otag(h, TAG_COL, "swW", bl->cols[i]);
817
		print_tagq(h, t);
818
		return 0;
819
820
	default:
821
		break;
822
	}
823
824
	switch (bl->type) {
825
	case LIST_bullet:
826
		elemtype = TAG_UL;
827
		(void)strlcpy(cattr, "Bl-bullet", sizeof(cattr));
828
		break;
829
	case LIST_dash:
830
	case LIST_hyphen:
831
		elemtype = TAG_UL;
832
		(void)strlcpy(cattr, "Bl-dash", sizeof(cattr));
833
		break;
834
	case LIST_item:
835
		elemtype = TAG_UL;
836
		(void)strlcpy(cattr, "Bl-item", sizeof(cattr));
837
		break;
838
	case LIST_enum:
839
		elemtype = TAG_OL;
840
		(void)strlcpy(cattr, "Bl-enum", sizeof(cattr));
841
		break;
842
	case LIST_diag:
843
		elemtype = TAG_DL;
844
		(void)strlcpy(cattr, "Bl-diag", sizeof(cattr));
845
		break;
846
	case LIST_hang:
847
		elemtype = TAG_DL;
848
		(void)strlcpy(cattr, "Bl-hang", sizeof(cattr));
849
		break;
850
	case LIST_inset:
851
		elemtype = TAG_DL;
852
		(void)strlcpy(cattr, "Bl-inset", sizeof(cattr));
853
		break;
854
	case LIST_ohang:
855
		elemtype = TAG_DL;
856
		(void)strlcpy(cattr, "Bl-ohang", sizeof(cattr));
857
		break;
858
	case LIST_tag:
859
		if (bl->offs)
860
			print_otag(h, TAG_DIV, "cswl", "Bl-tag", bl->offs);
861
		print_otag(h, TAG_DL, "csw*+l", bl->comp ?
862
		    "Bl-tag Bl-compact" : "Bl-tag", bl->width);
863
		return 1;
864
	case LIST_column:
865
		elemtype = TAG_TABLE;
866
		(void)strlcpy(cattr, "Bl-column", sizeof(cattr));
867
		break;
868
	default:
869
		abort();
870
	}
871
	if (bl->comp)
872
		(void)strlcat(cattr, " Bl-compact", sizeof(cattr));
873
	print_otag(h, elemtype, "cswl", cattr, bl->offs);
874
	return 1;
875
}
876
877
static int
878
mdoc_ex_pre(MDOC_ARGS)
879
{
880
	if (n->prev)
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, "cT", "St");
889
	return 1;
890
}
891
892
static int
893
mdoc_em_pre(MDOC_ARGS)
894
{
895
	print_otag(h, TAG_I, "cT", "Em");
896
	return 1;
897
}
898
899
static int
900
mdoc_d1_pre(MDOC_ARGS)
901
{
902
	if (n->type != ROFFT_BLOCK)
903
		return 1;
904
905
	print_otag(h, TAG_DIV, "c", "D1");
906
907
	if (n->tok == MDOC_Dl)
908
		print_otag(h, TAG_CODE, "c", "Li");
909
910
	return 1;
911
}
912
913
static int
914
mdoc_sx_pre(MDOC_ARGS)
915
{
916
	char	*id;
917
918
	id = html_make_id(n);
919
	print_otag(h, TAG_A, "cThR", "Sx", id);
920
	free(id);
921
	return 1;
922
}
923
924
static int
925
mdoc_bd_pre(MDOC_ARGS)
926
{
927
	int			 comp, offs, sv;
928
	struct roff_node	*nn;
929
930
18
	if (n->type == ROFFT_HEAD)
931
3
		return 0;
932
933
6
	if (n->type == ROFFT_BLOCK) {
934
3
		comp = n->norm->Bd.comp;
935

27
		for (nn = n; nn && ! comp; nn = nn->parent) {
936
9
			if (nn->type != ROFFT_BLOCK)
937
				continue;
938

12
			if (MDOC_Ss == nn->tok || MDOC_Sh == nn->tok)
939
3
				comp = 1;
940
6
			if (nn->prev)
941
				break;
942
		}
943
3
		if ( ! comp)
944
			print_paragraph(h);
945
3
		return 1;
946
	}
947
948
	/* Handle the -offset argument. */
949
950

3
	if (n->norm->Bd.offs == NULL ||
951
	    ! strcmp(n->norm->Bd.offs, "left"))
952
3
		offs = 0;
953
	else if ( ! strcmp(n->norm->Bd.offs, "indent"))
954
		offs = INDENT;
955
	else if ( ! strcmp(n->norm->Bd.offs, "indent-two"))
956
		offs = INDENT * 2;
957
	else
958
		offs = -1;
959
960
3
	if (offs == -1)
961
		print_otag(h, TAG_DIV, "cswl", "Bd", n->norm->Bd.offs);
962
	else
963
3
		print_otag(h, TAG_DIV, "cshl", "Bd", offs);
964
965

3
	if (n->norm->Bd.type != DISP_unfilled &&
966
	    n->norm->Bd.type != DISP_literal)
967
		return 1;
968
969
3
	print_otag(h, TAG_PRE, "c", "Li");
970
971
	/* This can be recursive: save & set our literal state. */
972
973
3
	sv = h->flags & HTML_LITERAL;
974
3
	h->flags |= HTML_LITERAL;
975
976
54
	for (nn = n->child; nn; nn = nn->next) {
977
48
		print_mdoc_node(meta, nn, h);
978
		/*
979
		 * If the printed node flushes its own line, then we
980
		 * needn't do it here as well.  This is hacky, but the
981
		 * notion of selective eoln whitespace is pretty dumb
982
		 * anyway, so don't sweat it.
983
		 */
984


48
		switch (nn->tok) {
985
		case ROFF_br:
986
		case ROFF_sp:
987
		case MDOC_Sm:
988
		case MDOC_Bl:
989
		case MDOC_D1:
990
		case MDOC_Dl:
991
		case MDOC_Lp:
992
		case MDOC_Pp:
993
			continue;
994
		default:
995
			break;
996
		}
997

45
		if (h->flags & HTML_NONEWLINE ||
998
45
		    (nn->next && ! (nn->next->flags & NODE_LINE)))
999
			continue;
1000
24
		else if (nn->next)
1001
21
			print_text(h, "\n");
1002
1003
24
		h->flags |= HTML_NOSPACE;
1004
24
	}
1005
1006
3
	if (0 == sv)
1007
3
		h->flags &= ~HTML_LITERAL;
1008
1009
3
	return 0;
1010
9
}
1011
1012
static int
1013
mdoc_pa_pre(MDOC_ARGS)
1014
{
1015
	print_otag(h, TAG_I, "cT", "Pa");
1016
	return 1;
1017
}
1018
1019
static int
1020
mdoc_ad_pre(MDOC_ARGS)
1021
{
1022
	print_otag(h, TAG_I, "c", "Ad");
1023
	return 1;
1024
}
1025
1026
static int
1027
mdoc_an_pre(MDOC_ARGS)
1028
{
1029
	if (n->norm->An.auth == AUTH_split) {
1030
		h->flags &= ~HTML_NOSPLIT;
1031
		h->flags |= HTML_SPLIT;
1032
		return 0;
1033
	}
1034
	if (n->norm->An.auth == AUTH_nosplit) {
1035
		h->flags &= ~HTML_SPLIT;
1036
		h->flags |= HTML_NOSPLIT;
1037
		return 0;
1038
	}
1039
1040
	if (h->flags & HTML_SPLIT)
1041
		print_otag(h, TAG_BR, "");
1042
1043
	if (n->sec == SEC_AUTHORS && ! (h->flags & HTML_NOSPLIT))
1044
		h->flags |= HTML_SPLIT;
1045
1046
	print_otag(h, TAG_SPAN, "cT", "An");
1047
	return 1;
1048
}
1049
1050
static int
1051
mdoc_cd_pre(MDOC_ARGS)
1052
{
1053
	synopsis_pre(h, n);
1054
	print_otag(h, TAG_B, "cT", "Cd");
1055
	return 1;
1056
}
1057
1058
static int
1059
mdoc_dv_pre(MDOC_ARGS)
1060
{
1061
	char	*id;
1062
1063
	if ((id = cond_id(n)) != NULL)
1064
		print_otag(h, TAG_A, "chR", "selflink", id);
1065
	print_otag(h, TAG_CODE, "cTi", "Dv", id);
1066
	free(id);
1067
	return 1;
1068
}
1069
1070
static int
1071
mdoc_ev_pre(MDOC_ARGS)
1072
{
1073
	char	*id;
1074
1075
	if ((id = cond_id(n)) != NULL)
1076
		print_otag(h, TAG_A, "chR", "selflink", id);
1077
	print_otag(h, TAG_CODE, "cTi", "Ev", id);
1078
	free(id);
1079
	return 1;
1080
}
1081
1082
static int
1083
mdoc_er_pre(MDOC_ARGS)
1084
{
1085
	char	*id;
1086
1087
	id = n->sec == SEC_ERRORS &&
1088
	    (n->parent->tok == MDOC_It ||
1089
	     (n->parent->tok == MDOC_Bq &&
1090
	      n->parent->parent->parent->tok == MDOC_It)) ?
1091
	    html_make_id(n) : NULL;
1092
1093
	if (id != NULL)
1094
		print_otag(h, TAG_A, "chR", "selflink", id);
1095
	print_otag(h, TAG_CODE, "cTi", "Er", id);
1096
	free(id);
1097
	return 1;
1098
}
1099
1100
static int
1101
mdoc_fa_pre(MDOC_ARGS)
1102
{
1103
	const struct roff_node	*nn;
1104
	struct tag		*t;
1105
1106
	if (n->parent->tok != MDOC_Fo) {
1107
		print_otag(h, TAG_VAR, "cT", "Fa");
1108
		return 1;
1109
	}
1110
1111
	for (nn = n->child; nn; nn = nn->next) {
1112
		t = print_otag(h, TAG_VAR, "cT", "Fa");
1113
		print_text(h, nn->string);
1114
		print_tagq(h, t);
1115
		if (nn->next) {
1116
			h->flags |= HTML_NOSPACE;
1117
			print_text(h, ",");
1118
		}
1119
	}
1120
1121
	if (n->child && n->next && n->next->tok == MDOC_Fa) {
1122
		h->flags |= HTML_NOSPACE;
1123
		print_text(h, ",");
1124
	}
1125
1126
	return 0;
1127
}
1128
1129
static int
1130
mdoc_fd_pre(MDOC_ARGS)
1131
{
1132
	struct tag	*t;
1133
	char		*buf, *cp;
1134
1135
	synopsis_pre(h, n);
1136
1137
	if (NULL == (n = n->child))
1138
		return 0;
1139
1140
	assert(n->type == ROFFT_TEXT);
1141
1142
	if (strcmp(n->string, "#include")) {
1143
		print_otag(h, TAG_B, "cT", "Fd");
1144
		return 1;
1145
	}
1146
1147
	print_otag(h, TAG_B, "cT", "In");
1148
	print_text(h, n->string);
1149
1150
	if (NULL != (n = n->next)) {
1151
		assert(n->type == ROFFT_TEXT);
1152
1153
		if (h->base_includes) {
1154
			cp = n->string;
1155
			if (*cp == '<' || *cp == '"')
1156
				cp++;
1157
			buf = mandoc_strdup(cp);
1158
			cp = strchr(buf, '\0') - 1;
1159
			if (cp >= buf && (*cp == '>' || *cp == '"'))
1160
				*cp = '\0';
1161
			t = print_otag(h, TAG_A, "cThI", "In", buf);
1162
			free(buf);
1163
		} else
1164
			t = print_otag(h, TAG_A, "cT", "In");
1165
1166
		print_text(h, n->string);
1167
		print_tagq(h, t);
1168
1169
		n = n->next;
1170
	}
1171
1172
	for ( ; n; n = n->next) {
1173
		assert(n->type == ROFFT_TEXT);
1174
		print_text(h, n->string);
1175
	}
1176
1177
	return 0;
1178
}
1179
1180
static int
1181
mdoc_vt_pre(MDOC_ARGS)
1182
{
1183
	if (n->type == ROFFT_BLOCK) {
1184
		synopsis_pre(h, n);
1185
		return 1;
1186
	} else if (n->type == ROFFT_ELEM) {
1187
		synopsis_pre(h, n);
1188
	} else if (n->type == ROFFT_HEAD)
1189
		return 0;
1190
1191
	print_otag(h, TAG_VAR, "cT", "Vt");
1192
	return 1;
1193
}
1194
1195
static int
1196
mdoc_ft_pre(MDOC_ARGS)
1197
{
1198
	synopsis_pre(h, n);
1199
	print_otag(h, TAG_VAR, "cT", "Ft");
1200
	return 1;
1201
}
1202
1203
static int
1204
mdoc_fn_pre(MDOC_ARGS)
1205
{
1206
	struct tag	*t;
1207
	char		 nbuf[BUFSIZ];
1208
	const char	*sp, *ep;
1209
	int		 sz, pretty;
1210
1211
	pretty = NODE_SYNPRETTY & n->flags;
1212
	synopsis_pre(h, n);
1213
1214
	/* Split apart into type and name. */
1215
	assert(n->child->string);
1216
	sp = n->child->string;
1217
1218
	ep = strchr(sp, ' ');
1219
	if (NULL != ep) {
1220
		t = print_otag(h, TAG_VAR, "cT", "Ft");
1221
1222
		while (ep) {
1223
			sz = MIN((int)(ep - sp), BUFSIZ - 1);
1224
			(void)memcpy(nbuf, sp, (size_t)sz);
1225
			nbuf[sz] = '\0';
1226
			print_text(h, nbuf);
1227
			sp = ++ep;
1228
			ep = strchr(sp, ' ');
1229
		}
1230
		print_tagq(h, t);
1231
	}
1232
1233
	t = print_otag(h, TAG_B, "cT", "Fn");
1234
1235
	if (sp)
1236
		print_text(h, sp);
1237
1238
	print_tagq(h, t);
1239
1240
	h->flags |= HTML_NOSPACE;
1241
	print_text(h, "(");
1242
	h->flags |= HTML_NOSPACE;
1243
1244
	for (n = n->child->next; n; n = n->next) {
1245
		if (NODE_SYNPRETTY & n->flags)
1246
			t = print_otag(h, TAG_VAR, "cTss?", "Fa",
1247
			    "white-space", "nowrap");
1248
		else
1249
			t = print_otag(h, TAG_VAR, "cT", "Fa");
1250
		print_text(h, n->string);
1251
		print_tagq(h, t);
1252
		if (n->next) {
1253
			h->flags |= HTML_NOSPACE;
1254
			print_text(h, ",");
1255
		}
1256
	}
1257
1258
	h->flags |= HTML_NOSPACE;
1259
	print_text(h, ")");
1260
1261
	if (pretty) {
1262
		h->flags |= HTML_NOSPACE;
1263
		print_text(h, ";");
1264
	}
1265
1266
	return 0;
1267
}
1268
1269
static int
1270
mdoc_sm_pre(MDOC_ARGS)
1271
{
1272
1273
	if (NULL == n->child)
1274
		h->flags ^= HTML_NONOSPACE;
1275
	else if (0 == strcmp("on", n->child->string))
1276
		h->flags &= ~HTML_NONOSPACE;
1277
	else
1278
		h->flags |= HTML_NONOSPACE;
1279
1280
	if ( ! (HTML_NONOSPACE & h->flags))
1281
		h->flags &= ~HTML_NOSPACE;
1282
1283
	return 0;
1284
}
1285
1286
static int
1287
mdoc_skip_pre(MDOC_ARGS)
1288
{
1289
1290
	return 0;
1291
}
1292
1293
static int
1294
mdoc_pp_pre(MDOC_ARGS)
1295
{
1296
1297
	print_paragraph(h);
1298
	return 0;
1299
}
1300
1301
static int
1302
mdoc_lk_pre(MDOC_ARGS)
1303
{
1304
	const struct roff_node *link, *descr, *punct;
1305
	struct tag	*t;
1306
1307
	if ((link = n->child) == NULL)
1308
		return 0;
1309
1310
	/* Find beginning of trailing punctuation. */
1311
	punct = n->last;
1312
	while (punct != link && punct->flags & NODE_DELIMC)
1313
		punct = punct->prev;
1314
	punct = punct->next;
1315
1316
	/* Link target and link text. */
1317
	descr = link->next;
1318
	if (descr == punct)
1319
		descr = link;  /* no text */
1320
	t = print_otag(h, TAG_A, "cTh", "Lk", link->string);
1321
	do {
1322
		if (descr->flags & (NODE_DELIMC | NODE_DELIMO))
1323
			h->flags |= HTML_NOSPACE;
1324
		print_text(h, descr->string);
1325
		descr = descr->next;
1326
	} while (descr != punct);
1327
	print_tagq(h, t);
1328
1329
	/* Trailing punctuation. */
1330
	while (punct != NULL) {
1331
		h->flags |= HTML_NOSPACE;
1332
		print_text(h, punct->string);
1333
		punct = punct->next;
1334
	}
1335
	return 0;
1336
}
1337
1338
static int
1339
mdoc_mt_pre(MDOC_ARGS)
1340
{
1341
	struct tag	*t;
1342
	char		*cp;
1343
1344
	for (n = n->child; n; n = n->next) {
1345
		assert(n->type == ROFFT_TEXT);
1346
1347
		mandoc_asprintf(&cp, "mailto:%s", n->string);
1348
		t = print_otag(h, TAG_A, "cTh", "Mt", cp);
1349
		print_text(h, n->string);
1350
		print_tagq(h, t);
1351
		free(cp);
1352
	}
1353
1354
	return 0;
1355
}
1356
1357
static int
1358
mdoc_fo_pre(MDOC_ARGS)
1359
{
1360
	struct tag	*t;
1361
1362
	if (n->type == ROFFT_BODY) {
1363
		h->flags |= HTML_NOSPACE;
1364
		print_text(h, "(");
1365
		h->flags |= HTML_NOSPACE;
1366
		return 1;
1367
	} else if (n->type == ROFFT_BLOCK) {
1368
		synopsis_pre(h, n);
1369
		return 1;
1370
	}
1371
1372
	if (n->child == NULL)
1373
		return 0;
1374
1375
	assert(n->child->string);
1376
	t = print_otag(h, TAG_B, "cT", "Fn");
1377
	print_text(h, n->child->string);
1378
	print_tagq(h, t);
1379
	return 0;
1380
}
1381
1382
static void
1383
mdoc_fo_post(MDOC_ARGS)
1384
{
1385
1386
	if (n->type != ROFFT_BODY)
1387
		return;
1388
	h->flags |= HTML_NOSPACE;
1389
	print_text(h, ")");
1390
	h->flags |= HTML_NOSPACE;
1391
	print_text(h, ";");
1392
}
1393
1394
static int
1395
mdoc_in_pre(MDOC_ARGS)
1396
{
1397
	struct tag	*t;
1398
1399
	synopsis_pre(h, n);
1400
	print_otag(h, TAG_B, "cT", "In");
1401
1402
	/*
1403
	 * The first argument of the `In' gets special treatment as
1404
	 * being a linked value.  Subsequent values are printed
1405
	 * afterward.  groff does similarly.  This also handles the case
1406
	 * of no children.
1407
	 */
1408
1409
	if (NODE_SYNPRETTY & n->flags && NODE_LINE & n->flags)
1410
		print_text(h, "#include");
1411
1412
	print_text(h, "<");
1413
	h->flags |= HTML_NOSPACE;
1414
1415
	if (NULL != (n = n->child)) {
1416
		assert(n->type == ROFFT_TEXT);
1417
1418
		if (h->base_includes)
1419
			t = print_otag(h, TAG_A, "cThI", "In", n->string);
1420
		else
1421
			t = print_otag(h, TAG_A, "cT", "In");
1422
		print_text(h, n->string);
1423
		print_tagq(h, t);
1424
1425
		n = n->next;
1426
	}
1427
1428
	h->flags |= HTML_NOSPACE;
1429
	print_text(h, ">");
1430
1431
	for ( ; n; n = n->next) {
1432
		assert(n->type == ROFFT_TEXT);
1433
		print_text(h, n->string);
1434
	}
1435
1436
	return 0;
1437
}
1438
1439
static int
1440
mdoc_ic_pre(MDOC_ARGS)
1441
{
1442
	char	*id;
1443
1444
	if ((id = cond_id(n)) != NULL)
1445
		print_otag(h, TAG_A, "chR", "selflink", id);
1446
	print_otag(h, TAG_B, "cTi", "Ic", id);
1447
	free(id);
1448
	return 1;
1449
}
1450
1451
static int
1452
mdoc_va_pre(MDOC_ARGS)
1453
{
1454
	print_otag(h, TAG_VAR, "cT", "Va");
1455
	return 1;
1456
}
1457
1458
static int
1459
mdoc_ap_pre(MDOC_ARGS)
1460
{
1461
1462
	h->flags |= HTML_NOSPACE;
1463
	print_text(h, "\\(aq");
1464
	h->flags |= HTML_NOSPACE;
1465
	return 1;
1466
}
1467
1468
static int
1469
mdoc_bf_pre(MDOC_ARGS)
1470
{
1471
	const char	*cattr;
1472
1473
	if (n->type == ROFFT_HEAD)
1474
		return 0;
1475
	else if (n->type != ROFFT_BODY)
1476
		return 1;
1477
1478
	if (FONT_Em == n->norm->Bf.font)
1479
		cattr = "Em";
1480
	else if (FONT_Sy == n->norm->Bf.font)
1481
		cattr = "Sy";
1482
	else if (FONT_Li == n->norm->Bf.font)
1483
		cattr = "Li";
1484
	else
1485
		cattr = "No";
1486
1487
	/*
1488
	 * We want this to be inline-formatted, but needs to be div to
1489
	 * accept block children.
1490
	 */
1491
1492
	print_otag(h, TAG_DIV, "css?hl", cattr, "display", "inline", 1);
1493
	return 1;
1494
}
1495
1496
static int
1497
mdoc_ms_pre(MDOC_ARGS)
1498
{
1499
	char *id;
1500
1501
	if ((id = cond_id(n)) != NULL)
1502
		print_otag(h, TAG_A, "chR", "selflink", id);
1503
	print_otag(h, TAG_B, "cTi", "Ms", id);
1504
	free(id);
1505
	return 1;
1506
}
1507
1508
static int
1509
mdoc_igndelim_pre(MDOC_ARGS)
1510
{
1511
1512
	h->flags |= HTML_IGNDELIM;
1513
	return 1;
1514
}
1515
1516
static void
1517
mdoc_pf_post(MDOC_ARGS)
1518
{
1519
1520
	if ( ! (n->next == NULL || n->next->flags & NODE_LINE))
1521
		h->flags |= HTML_NOSPACE;
1522
}
1523
1524
static int
1525
mdoc_rs_pre(MDOC_ARGS)
1526
{
1527
	if (n->type != ROFFT_BLOCK)
1528
		return 1;
1529
1530
	if (n->prev && SEC_SEE_ALSO == n->sec)
1531
		print_paragraph(h);
1532
1533
	print_otag(h, TAG_CITE, "cT", "Rs");
1534
	return 1;
1535
}
1536
1537
static int
1538
mdoc_no_pre(MDOC_ARGS)
1539
{
1540
	char *id;
1541
1542
	if ((id = cond_id(n)) != NULL)
1543
		print_otag(h, TAG_A, "chR", "selflink", id);
1544
	print_otag(h, TAG_SPAN, "ci", "No", id);
1545
	free(id);
1546
	return 1;
1547
}
1548
1549
static int
1550
mdoc_li_pre(MDOC_ARGS)
1551
{
1552
	char	*id;
1553
1554
	if ((id = cond_id(n)) != NULL)
1555
		print_otag(h, TAG_A, "chR", "selflink", id);
1556
	print_otag(h, TAG_CODE, "ci", "Li", id);
1557
	free(id);
1558
	return 1;
1559
}
1560
1561
static int
1562
mdoc_sy_pre(MDOC_ARGS)
1563
{
1564
	print_otag(h, TAG_B, "cT", "Sy");
1565
	return 1;
1566
}
1567
1568
static int
1569
mdoc_lb_pre(MDOC_ARGS)
1570
{
1571
	if (SEC_LIBRARY == n->sec && NODE_LINE & n->flags && n->prev)
1572
		print_otag(h, TAG_BR, "");
1573
1574
	print_otag(h, TAG_SPAN, "cT", "Lb");
1575
	return 1;
1576
}
1577
1578
static int
1579
mdoc__x_pre(MDOC_ARGS)
1580
{
1581
	const char	*cattr;
1582
	enum htmltag	 t;
1583
1584
	t = TAG_SPAN;
1585
1586
	switch (n->tok) {
1587
	case MDOC__A:
1588
		cattr = "RsA";
1589
		if (n->prev && MDOC__A == n->prev->tok)
1590
			if (NULL == n->next || MDOC__A != n->next->tok)
1591
				print_text(h, "and");
1592
		break;
1593
	case MDOC__B:
1594
		t = TAG_I;
1595
		cattr = "RsB";
1596
		break;
1597
	case MDOC__C:
1598
		cattr = "RsC";
1599
		break;
1600
	case MDOC__D:
1601
		cattr = "RsD";
1602
		break;
1603
	case MDOC__I:
1604
		t = TAG_I;
1605
		cattr = "RsI";
1606
		break;
1607
	case MDOC__J:
1608
		t = TAG_I;
1609
		cattr = "RsJ";
1610
		break;
1611
	case MDOC__N:
1612
		cattr = "RsN";
1613
		break;
1614
	case MDOC__O:
1615
		cattr = "RsO";
1616
		break;
1617
	case MDOC__P:
1618
		cattr = "RsP";
1619
		break;
1620
	case MDOC__Q:
1621
		cattr = "RsQ";
1622
		break;
1623
	case MDOC__R:
1624
		cattr = "RsR";
1625
		break;
1626
	case MDOC__T:
1627
		cattr = "RsT";
1628
		break;
1629
	case MDOC__U:
1630
		print_otag(h, TAG_A, "ch", "RsU", n->child->string);
1631
		return 1;
1632
	case MDOC__V:
1633
		cattr = "RsV";
1634
		break;
1635
	default:
1636
		abort();
1637
	}
1638
1639
	print_otag(h, t, "c", cattr);
1640
	return 1;
1641
}
1642
1643
static void
1644
mdoc__x_post(MDOC_ARGS)
1645
{
1646
1647
	if (MDOC__A == n->tok && n->next && MDOC__A == n->next->tok)
1648
		if (NULL == n->next->next || MDOC__A != n->next->next->tok)
1649
			if (NULL == n->prev || MDOC__A != n->prev->tok)
1650
				return;
1651
1652
	/* TODO: %U */
1653
1654
	if (NULL == n->parent || MDOC_Rs != n->parent->tok)
1655
		return;
1656
1657
	h->flags |= HTML_NOSPACE;
1658
	print_text(h, n->next ? "," : ".");
1659
}
1660
1661
static int
1662
mdoc_bk_pre(MDOC_ARGS)
1663
{
1664
1665
	switch (n->type) {
1666
	case ROFFT_BLOCK:
1667
		break;
1668
	case ROFFT_HEAD:
1669
		return 0;
1670
	case ROFFT_BODY:
1671
		if (n->parent->args != NULL || n->prev->child == NULL)
1672
			h->flags |= HTML_PREKEEP;
1673
		break;
1674
	default:
1675
		abort();
1676
	}
1677
1678
	return 1;
1679
}
1680
1681
static void
1682
mdoc_bk_post(MDOC_ARGS)
1683
{
1684
1685
	if (n->type == ROFFT_BODY)
1686
		h->flags &= ~(HTML_KEEP | HTML_PREKEEP);
1687
}
1688
1689
static int
1690
mdoc_quote_pre(MDOC_ARGS)
1691
{
1692
	if (n->type != ROFFT_BODY)
1693
		return 1;
1694
1695
	switch (n->tok) {
1696
	case MDOC_Ao:
1697
	case MDOC_Aq:
1698
		print_text(h, n->child != NULL && n->child->next == NULL &&
1699
		    n->child->tok == MDOC_Mt ?  "<" : "\\(la");
1700
		break;
1701
	case MDOC_Bro:
1702
	case MDOC_Brq:
1703
		print_text(h, "\\(lC");
1704
		break;
1705
	case MDOC_Bo:
1706
	case MDOC_Bq:
1707
		print_text(h, "\\(lB");
1708
		break;
1709
	case MDOC_Oo:
1710
	case MDOC_Op:
1711
		print_text(h, "\\(lB");
1712
		h->flags |= HTML_NOSPACE;
1713
		print_otag(h, TAG_SPAN, "c", "Op");
1714
		break;
1715
	case MDOC_En:
1716
		if (NULL == n->norm->Es ||
1717
		    NULL == n->norm->Es->child)
1718
			return 1;
1719
		print_text(h, n->norm->Es->child->string);
1720
		break;
1721
	case MDOC_Do:
1722
	case MDOC_Dq:
1723
	case MDOC_Qo:
1724
	case MDOC_Qq:
1725
		print_text(h, "\\(lq");
1726
		break;
1727
	case MDOC_Po:
1728
	case MDOC_Pq:
1729
		print_text(h, "(");
1730
		break;
1731
	case MDOC_Ql:
1732
		print_text(h, "\\(oq");
1733
		h->flags |= HTML_NOSPACE;
1734
		print_otag(h, TAG_CODE, "c", "Li");
1735
		break;
1736
	case MDOC_So:
1737
	case MDOC_Sq:
1738
		print_text(h, "\\(oq");
1739
		break;
1740
	default:
1741
		abort();
1742
	}
1743
1744
	h->flags |= HTML_NOSPACE;
1745
	return 1;
1746
}
1747
1748
static void
1749
mdoc_quote_post(MDOC_ARGS)
1750
{
1751
1752
	if (n->type != ROFFT_BODY && n->type != ROFFT_ELEM)
1753
		return;
1754
1755
	h->flags |= HTML_NOSPACE;
1756
1757
	switch (n->tok) {
1758
	case MDOC_Ao:
1759
	case MDOC_Aq:
1760
		print_text(h, n->child != NULL && n->child->next == NULL &&
1761
		    n->child->tok == MDOC_Mt ?  ">" : "\\(ra");
1762
		break;
1763
	case MDOC_Bro:
1764
	case MDOC_Brq:
1765
		print_text(h, "\\(rC");
1766
		break;
1767
	case MDOC_Oo:
1768
	case MDOC_Op:
1769
	case MDOC_Bo:
1770
	case MDOC_Bq:
1771
		print_text(h, "\\(rB");
1772
		break;
1773
	case MDOC_En:
1774
		if (n->norm->Es == NULL ||
1775
		    n->norm->Es->child == NULL ||
1776
		    n->norm->Es->child->next == NULL)
1777
			h->flags &= ~HTML_NOSPACE;
1778
		else
1779
			print_text(h, n->norm->Es->child->next->string);
1780
		break;
1781
	case MDOC_Qo:
1782
	case MDOC_Qq:
1783
	case MDOC_Do:
1784
	case MDOC_Dq:
1785
		print_text(h, "\\(rq");
1786
		break;
1787
	case MDOC_Po:
1788
	case MDOC_Pq:
1789
		print_text(h, ")");
1790
		break;
1791
	case MDOC_Ql:
1792
	case MDOC_So:
1793
	case MDOC_Sq:
1794
		print_text(h, "\\(cq");
1795
		break;
1796
	default:
1797
		abort();
1798
	}
1799
}
1800
1801
static int
1802
mdoc_eo_pre(MDOC_ARGS)
1803
{
1804
1805
	if (n->type != ROFFT_BODY)
1806
		return 1;
1807
1808
	if (n->end == ENDBODY_NOT &&
1809
	    n->parent->head->child == NULL &&
1810
	    n->child != NULL &&
1811
	    n->child->end != ENDBODY_NOT)
1812
		print_text(h, "\\&");
1813
	else if (n->end != ENDBODY_NOT ? n->child != NULL :
1814
	    n->parent->head->child != NULL && (n->child != NULL ||
1815
	    (n->parent->tail != NULL && n->parent->tail->child != NULL)))
1816
		h->flags |= HTML_NOSPACE;
1817
	return 1;
1818
}
1819
1820
static void
1821
mdoc_eo_post(MDOC_ARGS)
1822
{
1823
	int	 body, tail;
1824
1825
	if (n->type != ROFFT_BODY)
1826
		return;
1827
1828
	if (n->end != ENDBODY_NOT) {
1829
		h->flags &= ~HTML_NOSPACE;
1830
		return;
1831
	}
1832
1833
	body = n->child != NULL || n->parent->head->child != NULL;
1834
	tail = n->parent->tail != NULL && n->parent->tail->child != NULL;
1835
1836
	if (body && tail)
1837
		h->flags |= HTML_NOSPACE;
1838
	else if ( ! tail)
1839
		h->flags &= ~HTML_NOSPACE;
1840
}