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