GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.bin/mandoc/man_html.c Lines: 104 251 41.4 %
Date: 2017-11-07 Branches: 51 177 28.8 %

Line Branch Exec Source
1
/*	$OpenBSD: man_html.c,v 1.98 2017/06/25 07:23:53 bentley Exp $ */
2
/*
3
 * Copyright (c) 2008-2012, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
4
 * Copyright (c) 2013, 2014, 2015, 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
26
#include "mandoc_aux.h"
27
#include "mandoc.h"
28
#include "roff.h"
29
#include "man.h"
30
#include "out.h"
31
#include "html.h"
32
#include "main.h"
33
34
/* FIXME: have PD set the default vspace width. */
35
36
#define	INDENT		  5
37
38
#define	MAN_ARGS	  const struct roff_meta *man, \
39
			  const struct roff_node *n, \
40
			  struct html *h
41
42
struct	htmlman {
43
	int		(*pre)(MAN_ARGS);
44
	int		(*post)(MAN_ARGS);
45
};
46
47
static	void		  print_bvspace(struct html *,
48
				const struct roff_node *);
49
static	void		  print_man_head(MAN_ARGS);
50
static	void		  print_man_nodelist(MAN_ARGS);
51
static	void		  print_man_node(MAN_ARGS);
52
static	int		  fillmode(struct html *, int);
53
static	int		  a2width(const struct roff_node *,
54
				struct roffsu *);
55
static	int		  man_B_pre(MAN_ARGS);
56
static	int		  man_HP_pre(MAN_ARGS);
57
static	int		  man_IP_pre(MAN_ARGS);
58
static	int		  man_I_pre(MAN_ARGS);
59
static	int		  man_OP_pre(MAN_ARGS);
60
static	int		  man_PP_pre(MAN_ARGS);
61
static	int		  man_RS_pre(MAN_ARGS);
62
static	int		  man_SH_pre(MAN_ARGS);
63
static	int		  man_SM_pre(MAN_ARGS);
64
static	int		  man_SS_pre(MAN_ARGS);
65
static	int		  man_UR_pre(MAN_ARGS);
66
static	int		  man_alt_pre(MAN_ARGS);
67
static	int		  man_ign_pre(MAN_ARGS);
68
static	int		  man_in_pre(MAN_ARGS);
69
static	void		  man_root_post(MAN_ARGS);
70
static	void		  man_root_pre(MAN_ARGS);
71
72
static	const struct htmlman __mans[MAN_MAX - MAN_TH] = {
73
	{ NULL, NULL }, /* TH */
74
	{ man_SH_pre, NULL }, /* SH */
75
	{ man_SS_pre, NULL }, /* SS */
76
	{ man_IP_pre, NULL }, /* TP */
77
	{ man_PP_pre, NULL }, /* LP */
78
	{ man_PP_pre, NULL }, /* PP */
79
	{ man_PP_pre, NULL }, /* P */
80
	{ man_IP_pre, NULL }, /* IP */
81
	{ man_HP_pre, NULL }, /* HP */
82
	{ man_SM_pre, NULL }, /* SM */
83
	{ man_SM_pre, NULL }, /* SB */
84
	{ man_alt_pre, NULL }, /* BI */
85
	{ man_alt_pre, NULL }, /* IB */
86
	{ man_alt_pre, NULL }, /* BR */
87
	{ man_alt_pre, NULL }, /* RB */
88
	{ NULL, NULL }, /* R */
89
	{ man_B_pre, NULL }, /* B */
90
	{ man_I_pre, NULL }, /* I */
91
	{ man_alt_pre, NULL }, /* IR */
92
	{ man_alt_pre, NULL }, /* RI */
93
	{ NULL, NULL }, /* nf */
94
	{ NULL, NULL }, /* fi */
95
	{ NULL, NULL }, /* RE */
96
	{ man_RS_pre, NULL }, /* RS */
97
	{ man_ign_pre, NULL }, /* DT */
98
	{ man_ign_pre, NULL }, /* UC */
99
	{ man_ign_pre, NULL }, /* PD */
100
	{ man_ign_pre, NULL }, /* AT */
101
	{ man_in_pre, NULL }, /* in */
102
	{ man_OP_pre, NULL }, /* OP */
103
	{ NULL, NULL }, /* EX */
104
	{ NULL, NULL }, /* EE */
105
	{ man_UR_pre, NULL }, /* UR */
106
	{ NULL, NULL }, /* UE */
107
	{ man_UR_pre, NULL }, /* MT */
108
	{ NULL, NULL }, /* ME */
109
};
110
static	const struct htmlman *const mans = __mans - MAN_TH;
111
112
113
/*
114
 * Printing leading vertical space before a block.
115
 * This is used for the paragraph macros.
116
 * The rules are pretty simple, since there's very little nesting going
117
 * on here.  Basically, if we're the first within another block (SS/SH),
118
 * then don't emit vertical space.  If we are (RS), then do.  If not the
119
 * first, print it.
120
 */
121
static void
122
print_bvspace(struct html *h, const struct roff_node *n)
123
{
124
125
	if (n->body && n->body->child)
126
		if (n->body->child->type == ROFFT_TBL)
127
			return;
128
129
	if (n->parent->type == ROFFT_ROOT || n->parent->tok != MAN_RS)
130
		if (NULL == n->prev)
131
			return;
132
133
	print_paragraph(h);
134
}
135
136
void
137
html_man(void *arg, const struct roff_man *man)
138
{
139
	struct html	*h;
140
	struct tag	*t;
141
142
126
	h = (struct html *)arg;
143
144
63
	if ((h->oflags & HTML_FRAGMENT) == 0) {
145
63
		print_gen_decls(h);
146
63
		print_otag(h, TAG_HTML, "");
147
63
		t = print_otag(h, TAG_HEAD, "");
148
63
		print_man_head(&man->meta, man->first, h);
149
63
		print_tagq(h, t);
150
63
		print_otag(h, TAG_BODY, "");
151
63
	}
152
153
63
	man_root_pre(&man->meta, man->first, h);
154
63
	t = print_otag(h, TAG_DIV, "c", "manual-text");
155
63
	print_man_nodelist(&man->meta, man->first->child, h);
156
63
	print_tagq(h, t);
157
63
	man_root_post(&man->meta, man->first, h);
158
63
	print_tagq(h, NULL);
159
63
}
160
161
static void
162
print_man_head(MAN_ARGS)
163
{
164
126
	char	*cp;
165
166
63
	print_gen_head(h);
167
63
	mandoc_asprintf(&cp, "%s(%s)", man->title, man->msec);
168
63
	print_otag(h, TAG_TITLE, "");
169
63
	print_text(h, cp);
170
63
	free(cp);
171
63
}
172
173
static void
174
print_man_nodelist(MAN_ARGS)
175
{
176
177
9189
	while (n != NULL) {
178
3933
		print_man_node(man, n, h);
179
3933
		n = n->next;
180
	}
181
441
}
182
183
static void
184
print_man_node(MAN_ARGS)
185
{
186
	static int	 want_fillmode = MAN_fi;
187
	static int	 save_fillmode;
188
189
	struct tag	*t;
190
	int		 child;
191
192
	/*
193
	 * Handle fill mode switch requests up front,
194
	 * they would just cause trouble in the subsequent code.
195
	 */
196
197

7866
	switch (n->tok) {
198
	case MAN_nf:
199
	case MAN_EX:
200
63
		want_fillmode = MAN_nf;
201
63
		return;
202
	case MAN_fi:
203
	case MAN_EE:
204
63
		want_fillmode = MAN_fi;
205
63
		if (fillmode(h, 0) == MAN_fi)
206
			print_otag(h, TAG_BR, "");
207
63
		return;
208
	default:
209
		break;
210
	}
211
212
	/* Set up fill mode for the upcoming node. */
213
214

7236
	switch (n->type) {
215
	case ROFFT_BLOCK:
216
378
		save_fillmode = 0;
217
		/* Some block macros suspend or cancel .nf. */
218



378
		switch (n->tok) {
219
		case MAN_TP:  /* Tagged paragraphs		*/
220
		case MAN_IP:  /* temporarily disable .nf	*/
221
		case MAN_HP:  /* for the head.			*/
222
			save_fillmode = want_fillmode;
223
			/* FALLTHROUGH */
224
		case MAN_SH:  /* Section headers		*/
225
		case MAN_SS:  /* permanently cancel .nf.	*/
226
126
			want_fillmode = MAN_fi;
227
			/* FALLTHROUGH */
228
		case MAN_PP:  /* These have no head.		*/
229
		case MAN_LP:  /* They will simply		*/
230
		case MAN_P:   /* reopen .nf in the body.	*/
231
		case MAN_RS:
232
		case MAN_UR:
233
		case MAN_MT:
234
126
			fillmode(h, MAN_fi);
235
126
			break;
236
		default:
237
			break;
238
		}
239
		break;
240
	case ROFFT_TBL:
241
		fillmode(h, MAN_fi);
242
		break;
243
	case ROFFT_ELEM:
244
		/*
245
		 * Some in-line macros produce tags and/or text
246
		 * in the handler, so they require fill mode to be
247
		 * configured up front just like for text nodes.
248
		 * For the others, keep the traditional approach
249
		 * of doing the same, for now.
250
		 */
251
		fillmode(h, want_fillmode);
252
		break;
253
	case ROFFT_TEXT:
254

6858
		if (fillmode(h, want_fillmode) == MAN_fi &&
255
3429
		    want_fillmode == MAN_fi &&
256

252
		    n->flags & NODE_LINE && *n->string == ' ' &&
257
		    (h->flags & HTML_NONEWLINE) == 0)
258
			print_otag(h, TAG_BR, "");
259
3429
		if (*n->string != '\0')
260
			break;
261
		print_paragraph(h);
262
		return;
263
	default:
264
		break;
265
	}
266
267
	/* Produce output for this node. */
268
269
	child = 1;
270

3807
	switch (n->type) {
271
	case ROFFT_TEXT:
272
3429
		t = h->tag;
273
3429
		print_text(h, n->string);
274
3429
		break;
275
	case ROFFT_EQN:
276
		t = h->tag;
277
		print_eqn(h, n->eqn);
278
		break;
279
	case ROFFT_TBL:
280
		/*
281
		 * This will take care of initialising all of the table
282
		 * state data for the first table, then tearing it down
283
		 * for the last one.
284
		 */
285
		print_tbl(h, n->span);
286
		return;
287
	default:
288
		/*
289
		 * Close out scope of font prior to opening a macro
290
		 * scope.
291
		 */
292
378
		if (HTMLFONT_NONE != h->metac) {
293
			h->metal = h->metac;
294
			h->metac = HTMLFONT_NONE;
295
		}
296
297
		/*
298
		 * Close out the current table, if it's open, and unset
299
		 * the "meta" table state.  This will be reopened on the
300
		 * next table element.
301
		 */
302
378
		if (h->tblt)
303
			print_tblclose(h);
304
305
378
		t = h->tag;
306
378
		if (n->tok < ROFF_MAX) {
307
			roff_html_pre(h, n);
308
			child = 0;
309
			break;
310
		}
311
312

756
		assert(n->tok >= MAN_TH && n->tok < MAN_MAX);
313
378
		if (mans[n->tok].pre)
314
378
			child = (*mans[n->tok].pre)(man, n, h);
315
316
		/* Some block macros resume .nf in the body. */
317

378
		if (save_fillmode && n->type == ROFFT_BODY)
318
			want_fillmode = save_fillmode;
319
320
		break;
321
	}
322
323

7614
	if (child && n->child)
324
378
		print_man_nodelist(man, n->child, h);
325
326
	/* This will automatically close out any font scope. */
327
3807
	print_stagq(h, t);
328
329

7047
	if (fillmode(h, 0) == MAN_nf &&
330
6480
	    n->next != NULL && n->next->flags & NODE_LINE)
331
3240
		print_endline(h);
332
7740
}
333
334
/*
335
 * MAN_nf switches to no-fill mode, MAN_fi to fill mode.
336
 * Other arguments do not switch.
337
 * The old mode is returned.
338
 */
339
static int
340
fillmode(struct html *h, int want)
341
{
342
	struct tag	*pre;
343
	int		 had;
344
345
28953
	for (pre = h->tag; pre != NULL; pre = pre->next)
346
9819
		if (pre->tag == TAG_PRE)
347
			break;
348
349
7425
	had = pre == NULL ? MAN_fi : MAN_nf;
350
351

10980
	if (want && want != had) {
352
63
		if (want == MAN_nf)
353
63
			print_otag(h, TAG_PRE, "");
354
		else
355
			print_tagq(h, pre);
356
	}
357
7425
	return had;
358
}
359
360
static int
361
a2width(const struct roff_node *n, struct roffsu *su)
362
{
363
	if (n->type != ROFFT_TEXT)
364
		return 0;
365
	return a2roffsu(n->string, su, SCALE_EN) != NULL;
366
}
367
368
static void
369
man_root_pre(MAN_ARGS)
370
{
371
	struct tag	*t, *tt;
372
126
	char		*title;
373
374
63
	assert(man->title);
375
63
	assert(man->msec);
376
63
	mandoc_asprintf(&title, "%s(%s)", man->title, man->msec);
377
378
63
	t = print_otag(h, TAG_TABLE, "c", "head");
379
63
	tt = print_otag(h, TAG_TR, "");
380
381
63
	print_otag(h, TAG_TD, "c", "head-ltitle");
382
63
	print_text(h, title);
383
63
	print_stagq(h, tt);
384
385
63
	print_otag(h, TAG_TD, "c", "head-vol");
386
63
	if (NULL != man->vol)
387
63
		print_text(h, man->vol);
388
63
	print_stagq(h, tt);
389
390
63
	print_otag(h, TAG_TD, "c", "head-rtitle");
391
63
	print_text(h, title);
392
63
	print_tagq(h, t);
393
63
	free(title);
394
63
}
395
396
static void
397
man_root_post(MAN_ARGS)
398
{
399
	struct tag	*t, *tt;
400
401
126
	t = print_otag(h, TAG_TABLE, "c", "foot");
402
63
	tt = print_otag(h, TAG_TR, "");
403
404
63
	print_otag(h, TAG_TD, "c", "foot-date");
405
63
	print_text(h, man->date);
406
63
	print_stagq(h, tt);
407
408
63
	print_otag(h, TAG_TD, "c", "foot-os");
409
63
	if (man->os)
410
		print_text(h, man->os);
411
63
	print_tagq(h, t);
412
63
}
413
414
static int
415
man_SH_pre(MAN_ARGS)
416
{
417
	char	*id;
418
419
756
	if (n->type == ROFFT_HEAD) {
420
126
		id = html_make_id(n);
421
126
		print_otag(h, TAG_H1, "cTi", "Sh", id);
422
126
		if (id != NULL)
423
126
			print_otag(h, TAG_A, "chR", "selflink", id);
424
126
		free(id);
425
126
	}
426
378
	return 1;
427
}
428
429
static int
430
man_alt_pre(MAN_ARGS)
431
{
432
	const struct roff_node	*nn;
433
	int		 i;
434
	enum htmltag	 fp;
435
	struct tag	*t;
436
437
	for (i = 0, nn = n->child; nn; nn = nn->next, i++) {
438
		switch (n->tok) {
439
		case MAN_BI:
440
			fp = i % 2 ? TAG_I : TAG_B;
441
			break;
442
		case MAN_IB:
443
			fp = i % 2 ? TAG_B : TAG_I;
444
			break;
445
		case MAN_RI:
446
			fp = i % 2 ? TAG_I : TAG_MAX;
447
			break;
448
		case MAN_IR:
449
			fp = i % 2 ? TAG_MAX : TAG_I;
450
			break;
451
		case MAN_BR:
452
			fp = i % 2 ? TAG_MAX : TAG_B;
453
			break;
454
		case MAN_RB:
455
			fp = i % 2 ? TAG_B : TAG_MAX;
456
			break;
457
		default:
458
			abort();
459
		}
460
461
		if (i)
462
			h->flags |= HTML_NOSPACE;
463
464
		if (fp != TAG_MAX)
465
			t = print_otag(h, fp, "");
466
467
		print_text(h, nn->string);
468
469
		if (fp != TAG_MAX)
470
			print_tagq(h, t);
471
	}
472
	return 0;
473
}
474
475
static int
476
man_SM_pre(MAN_ARGS)
477
{
478
	print_otag(h, TAG_SMALL, "");
479
	if (MAN_SB == n->tok)
480
		print_otag(h, TAG_B, "");
481
	return 1;
482
}
483
484
static int
485
man_SS_pre(MAN_ARGS)
486
{
487
	char	*id;
488
489
	if (n->type == ROFFT_HEAD) {
490
		id = html_make_id(n);
491
		print_otag(h, TAG_H2, "cTi", "Ss", id);
492
		if (id != NULL)
493
			print_otag(h, TAG_A, "chR", "selflink", id);
494
		free(id);
495
	}
496
	return 1;
497
}
498
499
static int
500
man_PP_pre(MAN_ARGS)
501
{
502
503
	if (n->type == ROFFT_HEAD)
504
		return 0;
505
	else if (n->type == ROFFT_BLOCK)
506
		print_bvspace(h, n);
507
508
	return 1;
509
}
510
511
static int
512
man_IP_pre(MAN_ARGS)
513
{
514
	const struct roff_node	*nn;
515
516
	if (n->type == ROFFT_BODY) {
517
		print_otag(h, TAG_DD, "c", "It-tag");
518
		return 1;
519
	} else if (n->type != ROFFT_HEAD) {
520
		print_otag(h, TAG_DL, "c", "Bl-tag");
521
		return 1;
522
	}
523
524
	/* FIXME: width specification. */
525
526
	print_otag(h, TAG_DT, "c", "It-tag");
527
528
	/* For IP, only print the first header element. */
529
530
	if (MAN_IP == n->tok && n->child)
531
		print_man_node(man, n->child, h);
532
533
	/* For TP, only print next-line header elements. */
534
535
	if (MAN_TP == n->tok) {
536
		nn = n->child;
537
		while (NULL != nn && 0 == (NODE_LINE & nn->flags))
538
			nn = nn->next;
539
		while (NULL != nn) {
540
			print_man_node(man, nn, h);
541
			nn = nn->next;
542
		}
543
	}
544
545
	return 0;
546
}
547
548
static int
549
man_HP_pre(MAN_ARGS)
550
{
551
	struct roffsu	 sum, sui;
552
	const struct roff_node *np;
553
554
	if (n->type == ROFFT_HEAD)
555
		return 0;
556
	else if (n->type != ROFFT_BLOCK)
557
		return 1;
558
559
	np = n->head->child;
560
561
	if (np == NULL || !a2width(np, &sum))
562
		SCALE_HS_INIT(&sum, INDENT);
563
564
	sui.unit = sum.unit;
565
	sui.scale = -sum.scale;
566
567
	print_bvspace(h, n);
568
	print_otag(h, TAG_DIV, "csului", "Pp", &sum, &sui);
569
	return 1;
570
}
571
572
static int
573
man_OP_pre(MAN_ARGS)
574
{
575
	struct tag	*tt;
576
577
	print_text(h, "[");
578
	h->flags |= HTML_NOSPACE;
579
	tt = print_otag(h, TAG_SPAN, "c", "Op");
580
581
	if (NULL != (n = n->child)) {
582
		print_otag(h, TAG_B, "");
583
		print_text(h, n->string);
584
	}
585
586
	print_stagq(h, tt);
587
588
	if (NULL != n && NULL != n->next) {
589
		print_otag(h, TAG_I, "");
590
		print_text(h, n->next->string);
591
	}
592
593
	print_stagq(h, tt);
594
	h->flags |= HTML_NOSPACE;
595
	print_text(h, "]");
596
	return 0;
597
}
598
599
static int
600
man_B_pre(MAN_ARGS)
601
{
602
	print_otag(h, TAG_B, "");
603
	return 1;
604
}
605
606
static int
607
man_I_pre(MAN_ARGS)
608
{
609
	print_otag(h, TAG_I, "");
610
	return 1;
611
}
612
613
static int
614
man_in_pre(MAN_ARGS)
615
{
616
	print_otag(h, TAG_BR, "");
617
	return 0;
618
}
619
620
static int
621
man_ign_pre(MAN_ARGS)
622
{
623
624
	return 0;
625
}
626
627
static int
628
man_RS_pre(MAN_ARGS)
629
{
630
	struct roffsu	 su;
631
632
	if (n->type == ROFFT_HEAD)
633
		return 0;
634
	else if (n->type == ROFFT_BODY)
635
		return 1;
636
637
	SCALE_HS_INIT(&su, INDENT);
638
	if (n->head->child)
639
		a2width(n->head->child, &su);
640
641
	print_otag(h, TAG_DIV, "sul", &su);
642
	return 1;
643
}
644
645
static int
646
man_UR_pre(MAN_ARGS)
647
{
648
	char *cp;
649
	n = n->child;
650
	assert(n->type == ROFFT_HEAD);
651
	if (n->child != NULL) {
652
		assert(n->child->type == ROFFT_TEXT);
653
		if (n->tok == MAN_MT) {
654
			mandoc_asprintf(&cp, "mailto:%s", n->child->string);
655
			print_otag(h, TAG_A, "cTh", "Mt", cp);
656
			free(cp);
657
		} else
658
			print_otag(h, TAG_A, "cTh", "Lk", n->child->string);
659
	}
660
661
	assert(n->next->type == ROFFT_BODY);
662
	if (n->next->child != NULL)
663
		n = n->next;
664
665
	print_man_nodelist(man, n->child, h);
666
667
	return 0;
668
}