xref: /openbsd-src/usr.bin/mandoc/man_html.c (revision 897fc685943471cf985a0fe38ba076ea6fe74fa5)
1 /*	$OpenBSD: man_html.c,v 1.100 2018/04/13 16:27:14 schwarze Exp $ */
2 /*
3  * Copyright (c) 2008-2012, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
4  * Copyright (c) 2013,2014,2015,2017,2018 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(const struct roff_meta *,
50 				struct html *);
51 static	void		  print_man_nodelist(MAN_ARGS);
52 static	void		  print_man_node(MAN_ARGS);
53 static	int		  fillmode(struct html *, int);
54 static	int		  a2width(const struct roff_node *,
55 				struct roffsu *);
56 static	int		  man_B_pre(MAN_ARGS);
57 static	int		  man_HP_pre(MAN_ARGS);
58 static	int		  man_IP_pre(MAN_ARGS);
59 static	int		  man_I_pre(MAN_ARGS);
60 static	int		  man_OP_pre(MAN_ARGS);
61 static	int		  man_PP_pre(MAN_ARGS);
62 static	int		  man_RS_pre(MAN_ARGS);
63 static	int		  man_SH_pre(MAN_ARGS);
64 static	int		  man_SM_pre(MAN_ARGS);
65 static	int		  man_SS_pre(MAN_ARGS);
66 static	int		  man_UR_pre(MAN_ARGS);
67 static	int		  man_alt_pre(MAN_ARGS);
68 static	int		  man_ign_pre(MAN_ARGS);
69 static	int		  man_in_pre(MAN_ARGS);
70 static	void		  man_root_post(const struct roff_meta *,
71 				struct html *);
72 static	void		  man_root_pre(const struct roff_meta *,
73 				struct html *);
74 
75 static	const struct htmlman __mans[MAN_MAX - MAN_TH] = {
76 	{ NULL, NULL }, /* TH */
77 	{ man_SH_pre, NULL }, /* SH */
78 	{ man_SS_pre, NULL }, /* SS */
79 	{ man_IP_pre, NULL }, /* TP */
80 	{ man_PP_pre, NULL }, /* LP */
81 	{ man_PP_pre, NULL }, /* PP */
82 	{ man_PP_pre, NULL }, /* P */
83 	{ man_IP_pre, NULL }, /* IP */
84 	{ man_HP_pre, NULL }, /* HP */
85 	{ man_SM_pre, NULL }, /* SM */
86 	{ man_SM_pre, NULL }, /* SB */
87 	{ man_alt_pre, NULL }, /* BI */
88 	{ man_alt_pre, NULL }, /* IB */
89 	{ man_alt_pre, NULL }, /* BR */
90 	{ man_alt_pre, NULL }, /* RB */
91 	{ NULL, NULL }, /* R */
92 	{ man_B_pre, NULL }, /* B */
93 	{ man_I_pre, NULL }, /* I */
94 	{ man_alt_pre, NULL }, /* IR */
95 	{ man_alt_pre, NULL }, /* RI */
96 	{ NULL, NULL }, /* nf */
97 	{ NULL, NULL }, /* fi */
98 	{ NULL, NULL }, /* RE */
99 	{ man_RS_pre, NULL }, /* RS */
100 	{ man_ign_pre, NULL }, /* DT */
101 	{ man_ign_pre, NULL }, /* UC */
102 	{ man_ign_pre, NULL }, /* PD */
103 	{ man_ign_pre, NULL }, /* AT */
104 	{ man_in_pre, NULL }, /* in */
105 	{ man_OP_pre, NULL }, /* OP */
106 	{ NULL, NULL }, /* EX */
107 	{ NULL, NULL }, /* EE */
108 	{ man_UR_pre, NULL }, /* UR */
109 	{ NULL, NULL }, /* UE */
110 	{ man_UR_pre, NULL }, /* MT */
111 	{ NULL, NULL }, /* ME */
112 };
113 static	const struct htmlman *const mans = __mans - MAN_TH;
114 
115 
116 /*
117  * Printing leading vertical space before a block.
118  * This is used for the paragraph macros.
119  * The rules are pretty simple, since there's very little nesting going
120  * on here.  Basically, if we're the first within another block (SS/SH),
121  * then don't emit vertical space.  If we are (RS), then do.  If not the
122  * first, print it.
123  */
124 static void
125 print_bvspace(struct html *h, const struct roff_node *n)
126 {
127 
128 	if (n->body && n->body->child)
129 		if (n->body->child->type == ROFFT_TBL)
130 			return;
131 
132 	if (n->parent->type == ROFFT_ROOT || n->parent->tok != MAN_RS)
133 		if (NULL == n->prev)
134 			return;
135 
136 	print_paragraph(h);
137 }
138 
139 void
140 html_man(void *arg, const struct roff_man *man)
141 {
142 	struct html		*h;
143 	struct roff_node	*n;
144 	struct tag		*t;
145 
146 	h = (struct html *)arg;
147 	n = man->first->child;
148 
149 	if ((h->oflags & HTML_FRAGMENT) == 0) {
150 		print_gen_decls(h);
151 		print_otag(h, TAG_HTML, "");
152 		if (n->type == ROFFT_COMMENT)
153 			print_gen_comment(h, n);
154 		t = print_otag(h, TAG_HEAD, "");
155 		print_man_head(&man->meta, h);
156 		print_tagq(h, t);
157 		print_otag(h, TAG_BODY, "");
158 	}
159 
160 	man_root_pre(&man->meta, h);
161 	t = print_otag(h, TAG_DIV, "c", "manual-text");
162 	print_man_nodelist(&man->meta, n, h);
163 	print_tagq(h, t);
164 	man_root_post(&man->meta, h);
165 	print_tagq(h, NULL);
166 }
167 
168 static void
169 print_man_head(const struct roff_meta *man, struct html *h)
170 {
171 	char	*cp;
172 
173 	print_gen_head(h);
174 	mandoc_asprintf(&cp, "%s(%s)", man->title, man->msec);
175 	print_otag(h, TAG_TITLE, "");
176 	print_text(h, cp);
177 	free(cp);
178 }
179 
180 static void
181 print_man_nodelist(MAN_ARGS)
182 {
183 
184 	while (n != NULL) {
185 		print_man_node(man, n, h);
186 		n = n->next;
187 	}
188 }
189 
190 static void
191 print_man_node(MAN_ARGS)
192 {
193 	static int	 want_fillmode = MAN_fi;
194 	static int	 save_fillmode;
195 
196 	struct tag	*t;
197 	int		 child;
198 
199 	/*
200 	 * Handle fill mode switch requests up front,
201 	 * they would just cause trouble in the subsequent code.
202 	 */
203 
204 	switch (n->tok) {
205 	case MAN_nf:
206 	case MAN_EX:
207 		want_fillmode = MAN_nf;
208 		return;
209 	case MAN_fi:
210 	case MAN_EE:
211 		want_fillmode = MAN_fi;
212 		if (fillmode(h, 0) == MAN_fi)
213 			print_otag(h, TAG_BR, "");
214 		return;
215 	default:
216 		break;
217 	}
218 
219 	/* Set up fill mode for the upcoming node. */
220 
221 	switch (n->type) {
222 	case ROFFT_BLOCK:
223 		save_fillmode = 0;
224 		/* Some block macros suspend or cancel .nf. */
225 		switch (n->tok) {
226 		case MAN_TP:  /* Tagged paragraphs		*/
227 		case MAN_IP:  /* temporarily disable .nf	*/
228 		case MAN_HP:  /* for the head.			*/
229 			save_fillmode = want_fillmode;
230 			/* FALLTHROUGH */
231 		case MAN_SH:  /* Section headers		*/
232 		case MAN_SS:  /* permanently cancel .nf.	*/
233 			want_fillmode = MAN_fi;
234 			/* FALLTHROUGH */
235 		case MAN_PP:  /* These have no head.		*/
236 		case MAN_LP:  /* They will simply		*/
237 		case MAN_P:   /* reopen .nf in the body.	*/
238 		case MAN_RS:
239 		case MAN_UR:
240 		case MAN_MT:
241 			fillmode(h, MAN_fi);
242 			break;
243 		default:
244 			break;
245 		}
246 		break;
247 	case ROFFT_TBL:
248 		fillmode(h, MAN_fi);
249 		break;
250 	case ROFFT_ELEM:
251 		/*
252 		 * Some in-line macros produce tags and/or text
253 		 * in the handler, so they require fill mode to be
254 		 * configured up front just like for text nodes.
255 		 * For the others, keep the traditional approach
256 		 * of doing the same, for now.
257 		 */
258 		fillmode(h, want_fillmode);
259 		break;
260 	case ROFFT_TEXT:
261 		if (fillmode(h, want_fillmode) == MAN_fi &&
262 		    want_fillmode == MAN_fi &&
263 		    n->flags & NODE_LINE && *n->string == ' ' &&
264 		    (h->flags & HTML_NONEWLINE) == 0)
265 			print_otag(h, TAG_BR, "");
266 		if (*n->string != '\0')
267 			break;
268 		print_paragraph(h);
269 		return;
270 	case ROFFT_COMMENT:
271 		return;
272 	default:
273 		break;
274 	}
275 
276 	/* Produce output for this node. */
277 
278 	child = 1;
279 	switch (n->type) {
280 	case ROFFT_TEXT:
281 		t = h->tag;
282 		print_text(h, n->string);
283 		break;
284 	case ROFFT_EQN:
285 		t = h->tag;
286 		print_eqn(h, n->eqn);
287 		break;
288 	case ROFFT_TBL:
289 		/*
290 		 * This will take care of initialising all of the table
291 		 * state data for the first table, then tearing it down
292 		 * for the last one.
293 		 */
294 		print_tbl(h, n->span);
295 		return;
296 	default:
297 		/*
298 		 * Close out scope of font prior to opening a macro
299 		 * scope.
300 		 */
301 		if (HTMLFONT_NONE != h->metac) {
302 			h->metal = h->metac;
303 			h->metac = HTMLFONT_NONE;
304 		}
305 
306 		/*
307 		 * Close out the current table, if it's open, and unset
308 		 * the "meta" table state.  This will be reopened on the
309 		 * next table element.
310 		 */
311 		if (h->tblt)
312 			print_tblclose(h);
313 
314 		t = h->tag;
315 		if (n->tok < ROFF_MAX) {
316 			roff_html_pre(h, n);
317 			child = 0;
318 			break;
319 		}
320 
321 		assert(n->tok >= MAN_TH && n->tok < MAN_MAX);
322 		if (mans[n->tok].pre)
323 			child = (*mans[n->tok].pre)(man, n, h);
324 
325 		/* Some block macros resume .nf in the body. */
326 		if (save_fillmode && n->type == ROFFT_BODY)
327 			want_fillmode = save_fillmode;
328 
329 		break;
330 	}
331 
332 	if (child && n->child)
333 		print_man_nodelist(man, n->child, h);
334 
335 	/* This will automatically close out any font scope. */
336 	print_stagq(h, t);
337 
338 	if (fillmode(h, 0) == MAN_nf &&
339 	    n->next != NULL && n->next->flags & NODE_LINE)
340 		print_endline(h);
341 }
342 
343 /*
344  * MAN_nf switches to no-fill mode, MAN_fi to fill mode.
345  * Other arguments do not switch.
346  * The old mode is returned.
347  */
348 static int
349 fillmode(struct html *h, int want)
350 {
351 	struct tag	*pre;
352 	int		 had;
353 
354 	for (pre = h->tag; pre != NULL; pre = pre->next)
355 		if (pre->tag == TAG_PRE)
356 			break;
357 
358 	had = pre == NULL ? MAN_fi : MAN_nf;
359 
360 	if (want && want != had) {
361 		if (want == MAN_nf)
362 			print_otag(h, TAG_PRE, "");
363 		else
364 			print_tagq(h, pre);
365 	}
366 	return had;
367 }
368 
369 static int
370 a2width(const struct roff_node *n, struct roffsu *su)
371 {
372 	if (n->type != ROFFT_TEXT)
373 		return 0;
374 	return a2roffsu(n->string, su, SCALE_EN) != NULL;
375 }
376 
377 static void
378 man_root_pre(const struct roff_meta *man, struct html *h)
379 {
380 	struct tag	*t, *tt;
381 	char		*title;
382 
383 	assert(man->title);
384 	assert(man->msec);
385 	mandoc_asprintf(&title, "%s(%s)", man->title, man->msec);
386 
387 	t = print_otag(h, TAG_TABLE, "c", "head");
388 	tt = print_otag(h, TAG_TR, "");
389 
390 	print_otag(h, TAG_TD, "c", "head-ltitle");
391 	print_text(h, title);
392 	print_stagq(h, tt);
393 
394 	print_otag(h, TAG_TD, "c", "head-vol");
395 	if (NULL != man->vol)
396 		print_text(h, man->vol);
397 	print_stagq(h, tt);
398 
399 	print_otag(h, TAG_TD, "c", "head-rtitle");
400 	print_text(h, title);
401 	print_tagq(h, t);
402 	free(title);
403 }
404 
405 static void
406 man_root_post(const struct roff_meta *man, struct html *h)
407 {
408 	struct tag	*t, *tt;
409 
410 	t = print_otag(h, TAG_TABLE, "c", "foot");
411 	tt = print_otag(h, TAG_TR, "");
412 
413 	print_otag(h, TAG_TD, "c", "foot-date");
414 	print_text(h, man->date);
415 	print_stagq(h, tt);
416 
417 	print_otag(h, TAG_TD, "c", "foot-os");
418 	if (man->os)
419 		print_text(h, man->os);
420 	print_tagq(h, t);
421 }
422 
423 static int
424 man_SH_pre(MAN_ARGS)
425 {
426 	char	*id;
427 
428 	if (n->type == ROFFT_HEAD) {
429 		id = html_make_id(n);
430 		print_otag(h, TAG_H1, "cTi", "Sh", id);
431 		if (id != NULL)
432 			print_otag(h, TAG_A, "chR", "selflink", id);
433 		free(id);
434 	}
435 	return 1;
436 }
437 
438 static int
439 man_alt_pre(MAN_ARGS)
440 {
441 	const struct roff_node	*nn;
442 	int		 i;
443 	enum htmltag	 fp;
444 	struct tag	*t;
445 
446 	for (i = 0, nn = n->child; nn; nn = nn->next, i++) {
447 		switch (n->tok) {
448 		case MAN_BI:
449 			fp = i % 2 ? TAG_I : TAG_B;
450 			break;
451 		case MAN_IB:
452 			fp = i % 2 ? TAG_B : TAG_I;
453 			break;
454 		case MAN_RI:
455 			fp = i % 2 ? TAG_I : TAG_MAX;
456 			break;
457 		case MAN_IR:
458 			fp = i % 2 ? TAG_MAX : TAG_I;
459 			break;
460 		case MAN_BR:
461 			fp = i % 2 ? TAG_MAX : TAG_B;
462 			break;
463 		case MAN_RB:
464 			fp = i % 2 ? TAG_B : TAG_MAX;
465 			break;
466 		default:
467 			abort();
468 		}
469 
470 		if (i)
471 			h->flags |= HTML_NOSPACE;
472 
473 		if (fp != TAG_MAX)
474 			t = print_otag(h, fp, "");
475 
476 		print_text(h, nn->string);
477 
478 		if (fp != TAG_MAX)
479 			print_tagq(h, t);
480 	}
481 	return 0;
482 }
483 
484 static int
485 man_SM_pre(MAN_ARGS)
486 {
487 	print_otag(h, TAG_SMALL, "");
488 	if (MAN_SB == n->tok)
489 		print_otag(h, TAG_B, "");
490 	return 1;
491 }
492 
493 static int
494 man_SS_pre(MAN_ARGS)
495 {
496 	char	*id;
497 
498 	if (n->type == ROFFT_HEAD) {
499 		id = html_make_id(n);
500 		print_otag(h, TAG_H2, "cTi", "Ss", id);
501 		if (id != NULL)
502 			print_otag(h, TAG_A, "chR", "selflink", id);
503 		free(id);
504 	}
505 	return 1;
506 }
507 
508 static int
509 man_PP_pre(MAN_ARGS)
510 {
511 
512 	if (n->type == ROFFT_HEAD)
513 		return 0;
514 	else if (n->type == ROFFT_BLOCK)
515 		print_bvspace(h, n);
516 
517 	return 1;
518 }
519 
520 static int
521 man_IP_pre(MAN_ARGS)
522 {
523 	const struct roff_node	*nn;
524 
525 	if (n->type == ROFFT_BODY) {
526 		print_otag(h, TAG_DD, "c", "It-tag");
527 		return 1;
528 	} else if (n->type != ROFFT_HEAD) {
529 		print_otag(h, TAG_DL, "c", "Bl-tag");
530 		return 1;
531 	}
532 
533 	/* FIXME: width specification. */
534 
535 	print_otag(h, TAG_DT, "c", "It-tag");
536 
537 	/* For IP, only print the first header element. */
538 
539 	if (MAN_IP == n->tok && n->child)
540 		print_man_node(man, n->child, h);
541 
542 	/* For TP, only print next-line header elements. */
543 
544 	if (MAN_TP == n->tok) {
545 		nn = n->child;
546 		while (NULL != nn && 0 == (NODE_LINE & nn->flags))
547 			nn = nn->next;
548 		while (NULL != nn) {
549 			print_man_node(man, nn, h);
550 			nn = nn->next;
551 		}
552 	}
553 
554 	return 0;
555 }
556 
557 static int
558 man_HP_pre(MAN_ARGS)
559 {
560 	struct roffsu	 sum, sui;
561 	const struct roff_node *np;
562 
563 	if (n->type == ROFFT_HEAD)
564 		return 0;
565 	else if (n->type != ROFFT_BLOCK)
566 		return 1;
567 
568 	np = n->head->child;
569 
570 	if (np == NULL || !a2width(np, &sum))
571 		SCALE_HS_INIT(&sum, INDENT);
572 
573 	sui.unit = sum.unit;
574 	sui.scale = -sum.scale;
575 
576 	print_bvspace(h, n);
577 	print_otag(h, TAG_DIV, "csului", "Pp", &sum, &sui);
578 	return 1;
579 }
580 
581 static int
582 man_OP_pre(MAN_ARGS)
583 {
584 	struct tag	*tt;
585 
586 	print_text(h, "[");
587 	h->flags |= HTML_NOSPACE;
588 	tt = print_otag(h, TAG_SPAN, "c", "Op");
589 
590 	if (NULL != (n = n->child)) {
591 		print_otag(h, TAG_B, "");
592 		print_text(h, n->string);
593 	}
594 
595 	print_stagq(h, tt);
596 
597 	if (NULL != n && NULL != n->next) {
598 		print_otag(h, TAG_I, "");
599 		print_text(h, n->next->string);
600 	}
601 
602 	print_stagq(h, tt);
603 	h->flags |= HTML_NOSPACE;
604 	print_text(h, "]");
605 	return 0;
606 }
607 
608 static int
609 man_B_pre(MAN_ARGS)
610 {
611 	print_otag(h, TAG_B, "");
612 	return 1;
613 }
614 
615 static int
616 man_I_pre(MAN_ARGS)
617 {
618 	print_otag(h, TAG_I, "");
619 	return 1;
620 }
621 
622 static int
623 man_in_pre(MAN_ARGS)
624 {
625 	print_otag(h, TAG_BR, "");
626 	return 0;
627 }
628 
629 static int
630 man_ign_pre(MAN_ARGS)
631 {
632 
633 	return 0;
634 }
635 
636 static int
637 man_RS_pre(MAN_ARGS)
638 {
639 	struct roffsu	 su;
640 
641 	if (n->type == ROFFT_HEAD)
642 		return 0;
643 	else if (n->type == ROFFT_BODY)
644 		return 1;
645 
646 	SCALE_HS_INIT(&su, INDENT);
647 	if (n->head->child)
648 		a2width(n->head->child, &su);
649 
650 	print_otag(h, TAG_DIV, "sul", &su);
651 	return 1;
652 }
653 
654 static int
655 man_UR_pre(MAN_ARGS)
656 {
657 	char *cp;
658 	n = n->child;
659 	assert(n->type == ROFFT_HEAD);
660 	if (n->child != NULL) {
661 		assert(n->child->type == ROFFT_TEXT);
662 		if (n->tok == MAN_MT) {
663 			mandoc_asprintf(&cp, "mailto:%s", n->child->string);
664 			print_otag(h, TAG_A, "cTh", "Mt", cp);
665 			free(cp);
666 		} else
667 			print_otag(h, TAG_A, "cTh", "Lk", n->child->string);
668 	}
669 
670 	assert(n->next->type == ROFFT_BODY);
671 	if (n->next->child != NULL)
672 		n = n->next;
673 
674 	print_man_nodelist(man, n->child, h);
675 
676 	return 0;
677 }
678