xref: /illumos-gate/usr/src/cmd/mandoc/man_html.c (revision 78801af7286cd73dbc996d470f789e75993cf15d)
1 /*	$Id: man_html.c,v 1.173 2019/03/02 16:30:53 schwarze Exp $ */
2 /*
3  * Copyright (c) 2008-2012, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
4  * Copyright (c) 2013-2015, 2017-2019 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 #define	MAN_ARGS	  const struct roff_meta *man, \
37 			  const struct roff_node *n, \
38 			  struct html *h
39 
40 struct	man_html_act {
41 	int		(*pre)(MAN_ARGS);
42 	int		(*post)(MAN_ARGS);
43 };
44 
45 static	void		  print_man_head(const struct roff_meta *,
46 				struct html *);
47 static	void		  print_man_nodelist(MAN_ARGS);
48 static	void		  print_man_node(MAN_ARGS);
49 static	char		  list_continues(const struct roff_node *,
50 				const struct roff_node *);
51 static	int		  man_B_pre(MAN_ARGS);
52 static	int		  man_IP_pre(MAN_ARGS);
53 static	int		  man_I_pre(MAN_ARGS);
54 static	int		  man_OP_pre(MAN_ARGS);
55 static	int		  man_PP_pre(MAN_ARGS);
56 static	int		  man_RS_pre(MAN_ARGS);
57 static	int		  man_SH_pre(MAN_ARGS);
58 static	int		  man_SM_pre(MAN_ARGS);
59 static	int		  man_SY_pre(MAN_ARGS);
60 static	int		  man_UR_pre(MAN_ARGS);
61 static	int		  man_abort_pre(MAN_ARGS);
62 static	int		  man_alt_pre(MAN_ARGS);
63 static	int		  man_ign_pre(MAN_ARGS);
64 static	int		  man_in_pre(MAN_ARGS);
65 static	void		  man_root_post(const struct roff_meta *,
66 				struct html *);
67 static	void		  man_root_pre(const struct roff_meta *,
68 				struct html *);
69 
70 static	const struct man_html_act man_html_acts[MAN_MAX - MAN_TH] = {
71 	{ NULL, NULL }, /* TH */
72 	{ man_SH_pre, NULL }, /* SH */
73 	{ man_SH_pre, NULL }, /* SS */
74 	{ man_IP_pre, NULL }, /* TP */
75 	{ man_IP_pre, NULL }, /* TQ */
76 	{ man_abort_pre, NULL }, /* LP */
77 	{ man_PP_pre, NULL }, /* PP */
78 	{ man_abort_pre, NULL }, /* P */
79 	{ man_IP_pre, NULL }, /* IP */
80 	{ man_PP_pre, NULL }, /* HP */
81 	{ man_SM_pre, NULL }, /* SM */
82 	{ man_SM_pre, NULL }, /* SB */
83 	{ man_alt_pre, NULL }, /* BI */
84 	{ man_alt_pre, NULL }, /* IB */
85 	{ man_alt_pre, NULL }, /* BR */
86 	{ man_alt_pre, NULL }, /* RB */
87 	{ NULL, NULL }, /* R */
88 	{ man_B_pre, NULL }, /* B */
89 	{ man_I_pre, NULL }, /* I */
90 	{ man_alt_pre, NULL }, /* IR */
91 	{ man_alt_pre, NULL }, /* RI */
92 	{ NULL, NULL }, /* RE */
93 	{ man_RS_pre, NULL }, /* RS */
94 	{ man_ign_pre, NULL }, /* DT */
95 	{ man_ign_pre, NULL }, /* UC */
96 	{ man_ign_pre, NULL }, /* PD */
97 	{ man_ign_pre, NULL }, /* AT */
98 	{ man_in_pre, NULL }, /* in */
99 	{ man_SY_pre, NULL }, /* SY */
100 	{ NULL, NULL }, /* YS */
101 	{ man_OP_pre, NULL }, /* OP */
102 	{ NULL, NULL }, /* EX */
103 	{ NULL, NULL }, /* EE */
104 	{ man_UR_pre, NULL }, /* UR */
105 	{ NULL, NULL }, /* UE */
106 	{ man_UR_pre, NULL }, /* MT */
107 	{ NULL, NULL }, /* ME */
108 };
109 
110 
111 void
112 html_man(void *arg, const struct roff_meta *man)
113 {
114 	struct html		*h;
115 	struct roff_node	*n;
116 	struct tag		*t;
117 
118 	h = (struct html *)arg;
119 	n = man->first->child;
120 
121 	if ((h->oflags & HTML_FRAGMENT) == 0) {
122 		print_gen_decls(h);
123 		print_otag(h, TAG_HTML, "");
124 		if (n != NULL && n->type == ROFFT_COMMENT)
125 			print_gen_comment(h, n);
126 		t = print_otag(h, TAG_HEAD, "");
127 		print_man_head(man, h);
128 		print_tagq(h, t);
129 		print_otag(h, TAG_BODY, "");
130 	}
131 
132 	man_root_pre(man, h);
133 	t = print_otag(h, TAG_DIV, "c", "manual-text");
134 	print_man_nodelist(man, n, h);
135 	print_tagq(h, t);
136 	man_root_post(man, h);
137 	print_tagq(h, NULL);
138 }
139 
140 static void
141 print_man_head(const struct roff_meta *man, struct html *h)
142 {
143 	char	*cp;
144 
145 	print_gen_head(h);
146 	mandoc_asprintf(&cp, "%s(%s)", man->title, man->msec);
147 	print_otag(h, TAG_TITLE, "");
148 	print_text(h, cp);
149 	free(cp);
150 }
151 
152 static void
153 print_man_nodelist(MAN_ARGS)
154 {
155 	while (n != NULL) {
156 		print_man_node(man, n, h);
157 		n = n->next;
158 	}
159 }
160 
161 static void
162 print_man_node(MAN_ARGS)
163 {
164 	struct tag	*t;
165 	int		 child;
166 
167 	if (n->type == ROFFT_COMMENT || n->flags & NODE_NOPRT)
168 		return;
169 
170 	html_fillmode(h, n->flags & NODE_NOFILL ? ROFF_nf : ROFF_fi);
171 
172 	child = 1;
173 	switch (n->type) {
174 	case ROFFT_TEXT:
175 		if (*n->string == '\0') {
176 			print_endline(h);
177 			return;
178 		}
179 		if (*n->string == ' ' && n->flags & NODE_LINE &&
180 		    (h->flags & HTML_NONEWLINE) == 0)
181 			print_endline(h);
182 		else if (n->flags & NODE_DELIMC)
183 			h->flags |= HTML_NOSPACE;
184 		t = h->tag;
185 		t->refcnt++;
186 		print_text(h, n->string);
187 		break;
188 	case ROFFT_EQN:
189 		t = h->tag;
190 		t->refcnt++;
191 		print_eqn(h, n->eqn);
192 		break;
193 	case ROFFT_TBL:
194 		/*
195 		 * This will take care of initialising all of the table
196 		 * state data for the first table, then tearing it down
197 		 * for the last one.
198 		 */
199 		print_tbl(h, n->span);
200 		return;
201 	default:
202 		/*
203 		 * Close out scope of font prior to opening a macro
204 		 * scope.
205 		 */
206 		if (HTMLFONT_NONE != h->metac) {
207 			h->metal = h->metac;
208 			h->metac = HTMLFONT_NONE;
209 		}
210 
211 		/*
212 		 * Close out the current table, if it's open, and unset
213 		 * the "meta" table state.  This will be reopened on the
214 		 * next table element.
215 		 */
216 		if (h->tblt != NULL)
217 			print_tblclose(h);
218 		t = h->tag;
219 		t->refcnt++;
220 		if (n->tok < ROFF_MAX) {
221 			roff_html_pre(h, n);
222 			t->refcnt--;
223 			print_stagq(h, t);
224 			return;
225 		}
226 		assert(n->tok >= MAN_TH && n->tok < MAN_MAX);
227 		if (man_html_acts[n->tok - MAN_TH].pre != NULL)
228 			child = (*man_html_acts[n->tok - MAN_TH].pre)(man,
229 			    n, h);
230 		break;
231 	}
232 
233 	if (child && n->child != NULL)
234 		print_man_nodelist(man, n->child, h);
235 
236 	/* This will automatically close out any font scope. */
237 	t->refcnt--;
238 	if (n->type == ROFFT_BLOCK &&
239 	    (n->tok == MAN_IP || n->tok == MAN_TP || n->tok == MAN_TQ)) {
240 		t = h->tag;
241 		while (t->tag != TAG_DL && t->tag != TAG_UL)
242 			t = t->next;
243 		/*
244 		 * Close the list if no further item of the same type
245 		 * follows; otherwise, close the item only.
246 		 */
247 		if (list_continues(n, n->next) == '\0') {
248 			print_tagq(h, t);
249 			t = NULL;
250 		}
251 	}
252 	if (t != NULL)
253 		print_stagq(h, t);
254 
255 	if (n->flags & NODE_NOFILL && n->tok != MAN_YS &&
256 	    (n->next != NULL && n->next->flags & NODE_LINE)) {
257 		/* In .nf = <pre>, print even empty lines. */
258 		h->col++;
259 		print_endline(h);
260 	}
261 }
262 
263 static void
264 man_root_pre(const struct roff_meta *man, struct html *h)
265 {
266 	struct tag	*t, *tt;
267 	char		*title;
268 
269 	assert(man->title);
270 	assert(man->msec);
271 	mandoc_asprintf(&title, "%s(%s)", man->title, man->msec);
272 
273 	t = print_otag(h, TAG_TABLE, "c", "head");
274 	tt = print_otag(h, TAG_TR, "");
275 
276 	print_otag(h, TAG_TD, "c", "head-ltitle");
277 	print_text(h, title);
278 	print_stagq(h, tt);
279 
280 	print_otag(h, TAG_TD, "c", "head-vol");
281 	if (man->vol != NULL)
282 		print_text(h, man->vol);
283 	print_stagq(h, tt);
284 
285 	print_otag(h, TAG_TD, "c", "head-rtitle");
286 	print_text(h, title);
287 	print_tagq(h, t);
288 	free(title);
289 }
290 
291 static void
292 man_root_post(const struct roff_meta *man, struct html *h)
293 {
294 	struct tag	*t, *tt;
295 
296 	t = print_otag(h, TAG_TABLE, "c", "foot");
297 	tt = print_otag(h, TAG_TR, "");
298 
299 	print_otag(h, TAG_TD, "c", "foot-date");
300 	print_text(h, man->date);
301 	print_stagq(h, tt);
302 
303 	print_otag(h, TAG_TD, "c", "foot-os");
304 	if (man->os != NULL)
305 		print_text(h, man->os);
306 	print_tagq(h, t);
307 }
308 
309 static int
310 man_SH_pre(MAN_ARGS)
311 {
312 	const char	*class;
313 	char		*id;
314 	enum htmltag	 tag;
315 
316 	if (n->tok == MAN_SH) {
317 		tag = TAG_H1;
318 		class = "Sh";
319 	} else {
320 		tag = TAG_H2;
321 		class = "Ss";
322 	}
323 	switch (n->type) {
324 	case ROFFT_BLOCK:
325 		html_close_paragraph(h);
326 		print_otag(h, TAG_SECTION, "c", class);
327 		break;
328 	case ROFFT_HEAD:
329 		id = html_make_id(n, 1);
330 		print_otag(h, tag, "ci", class, id);
331 		if (id != NULL)
332 			print_otag(h, TAG_A, "chR", "permalink", id);
333 		break;
334 	case ROFFT_BODY:
335 		break;
336 	default:
337 		abort();
338 	}
339 	return 1;
340 }
341 
342 static int
343 man_alt_pre(MAN_ARGS)
344 {
345 	const struct roff_node	*nn;
346 	struct tag	*t;
347 	int		 i;
348 	enum htmltag	 fp;
349 
350 	for (i = 0, nn = n->child; nn != NULL; nn = nn->next, i++) {
351 		switch (n->tok) {
352 		case MAN_BI:
353 			fp = i % 2 ? TAG_I : TAG_B;
354 			break;
355 		case MAN_IB:
356 			fp = i % 2 ? TAG_B : TAG_I;
357 			break;
358 		case MAN_RI:
359 			fp = i % 2 ? TAG_I : TAG_MAX;
360 			break;
361 		case MAN_IR:
362 			fp = i % 2 ? TAG_MAX : TAG_I;
363 			break;
364 		case MAN_BR:
365 			fp = i % 2 ? TAG_MAX : TAG_B;
366 			break;
367 		case MAN_RB:
368 			fp = i % 2 ? TAG_B : TAG_MAX;
369 			break;
370 		default:
371 			abort();
372 		}
373 
374 		if (i)
375 			h->flags |= HTML_NOSPACE;
376 
377 		if (fp != TAG_MAX)
378 			t = print_otag(h, fp, "");
379 
380 		print_text(h, nn->string);
381 
382 		if (fp != TAG_MAX)
383 			print_tagq(h, t);
384 	}
385 	return 0;
386 }
387 
388 static int
389 man_SM_pre(MAN_ARGS)
390 {
391 	print_otag(h, TAG_SMALL, "");
392 	if (n->tok == MAN_SB)
393 		print_otag(h, TAG_B, "");
394 	return 1;
395 }
396 
397 static int
398 man_PP_pre(MAN_ARGS)
399 {
400 	switch (n->type) {
401 	case ROFFT_BLOCK:
402 		html_close_paragraph(h);
403 		break;
404 	case ROFFT_HEAD:
405 		return 0;
406 	case ROFFT_BODY:
407 		if (n->child != NULL &&
408 		    (n->child->flags & NODE_NOFILL) == 0)
409 			print_otag(h, TAG_P, "c",
410 			    n->tok == MAN_PP ? "Pp" : "Pp HP");
411 		break;
412 	default:
413 		abort();
414 	}
415 	return 1;
416 }
417 
418 static char
419 list_continues(const struct roff_node *n1, const struct roff_node *n2)
420 {
421 	const char *s1, *s2;
422 	char c1, c2;
423 
424 	if (n1 == NULL || n1->type != ROFFT_BLOCK ||
425 	    n2 == NULL || n2->type != ROFFT_BLOCK)
426 		return '\0';
427 	if ((n1->tok == MAN_TP || n1->tok == MAN_TQ) &&
428 	    (n2->tok == MAN_TP || n2->tok == MAN_TQ))
429 		return ' ';
430 	if (n1->tok != MAN_IP || n2->tok != MAN_IP)
431 		return '\0';
432 	n1 = n1->head->child;
433 	n2 = n2->head->child;
434 	s1 = n1 == NULL ? "" : n1->string;
435 	s2 = n2 == NULL ? "" : n2->string;
436 	c1 = strcmp(s1, "*") == 0 ? '*' :
437 	     strcmp(s1, "\\-") == 0 ? '-' :
438 	     strcmp(s1, "\\(bu") == 0 ? 'b' : ' ';
439 	c2 = strcmp(s2, "*") == 0 ? '*' :
440 	     strcmp(s2, "\\-") == 0 ? '-' :
441 	     strcmp(s2, "\\(bu") == 0 ? 'b' : ' ';
442 	return c1 != c2 ? '\0' : c1 == 'b' ? '*' : c1;
443 }
444 
445 static int
446 man_IP_pre(MAN_ARGS)
447 {
448 	const struct roff_node	*nn;
449 	const char		*list_class;
450 	enum htmltag		 list_elem, body_elem;
451 	char			 list_type;
452 
453 	nn = n->type == ROFFT_BLOCK ? n : n->parent;
454 	if ((list_type = list_continues(nn->prev, nn)) == '\0') {
455 		/* Start a new list. */
456 		if ((list_type = list_continues(nn, nn->next)) == '\0')
457 			list_type = ' ';
458 		switch (list_type) {
459 		case ' ':
460 			list_class = "Bl-tag";
461 			list_elem = TAG_DL;
462 			break;
463 		case '*':
464 			list_class = "Bl-bullet";
465 			list_elem = TAG_UL;
466 			break;
467 		case '-':
468 			list_class = "Bl-dash";
469 			list_elem = TAG_UL;
470 			break;
471 		default:
472 			abort();
473 		}
474 	} else {
475 		/* Continue a list that was started earlier. */
476 		list_class = NULL;
477 		list_elem = TAG_MAX;
478 	}
479 	body_elem = list_type == ' ' ? TAG_DD : TAG_LI;
480 
481 	switch (n->type) {
482 	case ROFFT_BLOCK:
483 		html_close_paragraph(h);
484 		if (list_elem != TAG_MAX)
485 			print_otag(h, list_elem, "c", list_class);
486 		return 1;
487 	case ROFFT_HEAD:
488 		if (body_elem == TAG_LI)
489 			return 0;
490 		print_otag(h, TAG_DT, "");
491 		break;
492 	case ROFFT_BODY:
493 		print_otag(h, body_elem, "");
494 		return 1;
495 	default:
496 		abort();
497 	}
498 
499 	switch(n->tok) {
500 	case MAN_IP:  /* Only print the first header element. */
501 		if (n->child != NULL)
502 			print_man_node(man, n->child, h);
503 		break;
504 	case MAN_TP:  /* Only print next-line header elements. */
505 	case MAN_TQ:
506 		nn = n->child;
507 		while (nn != NULL && (NODE_LINE & nn->flags) == 0)
508 			nn = nn->next;
509 		while (nn != NULL) {
510 			print_man_node(man, nn, h);
511 			nn = nn->next;
512 		}
513 		break;
514 	default:
515 		abort();
516 	}
517 	return 0;
518 }
519 
520 static int
521 man_OP_pre(MAN_ARGS)
522 {
523 	struct tag	*tt;
524 
525 	print_text(h, "[");
526 	h->flags |= HTML_NOSPACE;
527 	tt = print_otag(h, TAG_SPAN, "c", "Op");
528 
529 	if ((n = n->child) != NULL) {
530 		print_otag(h, TAG_B, "");
531 		print_text(h, n->string);
532 	}
533 
534 	print_stagq(h, tt);
535 
536 	if (n != NULL && n->next != NULL) {
537 		print_otag(h, TAG_I, "");
538 		print_text(h, n->next->string);
539 	}
540 
541 	print_stagq(h, tt);
542 	h->flags |= HTML_NOSPACE;
543 	print_text(h, "]");
544 	return 0;
545 }
546 
547 static int
548 man_B_pre(MAN_ARGS)
549 {
550 	print_otag(h, TAG_B, "");
551 	return 1;
552 }
553 
554 static int
555 man_I_pre(MAN_ARGS)
556 {
557 	print_otag(h, TAG_I, "");
558 	return 1;
559 }
560 
561 static int
562 man_in_pre(MAN_ARGS)
563 {
564 	print_otag(h, TAG_BR, "");
565 	return 0;
566 }
567 
568 static int
569 man_ign_pre(MAN_ARGS)
570 {
571 	return 0;
572 }
573 
574 static int
575 man_RS_pre(MAN_ARGS)
576 {
577 	switch (n->type) {
578 	case ROFFT_BLOCK:
579 		html_close_paragraph(h);
580 		break;
581 	case ROFFT_HEAD:
582 		return 0;
583 	case ROFFT_BODY:
584 		print_otag(h, TAG_DIV, "c", "Bd-indent");
585 		break;
586 	default:
587 		abort();
588 	}
589 	return 1;
590 }
591 
592 static int
593 man_SY_pre(MAN_ARGS)
594 {
595 	switch (n->type) {
596 	case ROFFT_BLOCK:
597 		html_close_paragraph(h);
598 		print_otag(h, TAG_TABLE, "c", "Nm");
599 		print_otag(h, TAG_TR, "");
600 		break;
601 	case ROFFT_HEAD:
602 		print_otag(h, TAG_TD, "");
603 		print_otag(h, TAG_CODE, "c", "Nm");
604 		break;
605 	case ROFFT_BODY:
606 		print_otag(h, TAG_TD, "");
607 		break;
608 	default:
609 		abort();
610 	}
611 	return 1;
612 }
613 
614 static int
615 man_UR_pre(MAN_ARGS)
616 {
617 	char *cp;
618 
619 	n = n->child;
620 	assert(n->type == ROFFT_HEAD);
621 	if (n->child != NULL) {
622 		assert(n->child->type == ROFFT_TEXT);
623 		if (n->tok == MAN_MT) {
624 			mandoc_asprintf(&cp, "mailto:%s", n->child->string);
625 			print_otag(h, TAG_A, "ch", "Mt", cp);
626 			free(cp);
627 		} else
628 			print_otag(h, TAG_A, "ch", "Lk", n->child->string);
629 	}
630 
631 	assert(n->next->type == ROFFT_BODY);
632 	if (n->next->child != NULL)
633 		n = n->next;
634 
635 	print_man_nodelist(man, n->child, h);
636 	return 0;
637 }
638 
639 static int
640 man_abort_pre(MAN_ARGS)
641 {
642 	abort();
643 }
644