1/*	$Id: man_html.c,v 1.174 2019/04/30 15:53:00 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
40struct	man_html_act {
41	int		(*pre)(MAN_ARGS);
42	int		(*post)(MAN_ARGS);
43};
44
45static	void		  print_man_head(const struct roff_meta *,
46				struct html *);
47static	void		  print_man_nodelist(MAN_ARGS);
48static	void		  print_man_node(MAN_ARGS);
49static	char		  list_continues(const struct roff_node *,
50				const struct roff_node *);
51static	int		  man_B_pre(MAN_ARGS);
52static	int		  man_IP_pre(MAN_ARGS);
53static	int		  man_I_pre(MAN_ARGS);
54static	int		  man_OP_pre(MAN_ARGS);
55static	int		  man_PP_pre(MAN_ARGS);
56static	int		  man_RS_pre(MAN_ARGS);
57static	int		  man_SH_pre(MAN_ARGS);
58static	int		  man_SM_pre(MAN_ARGS);
59static	int		  man_SY_pre(MAN_ARGS);
60static	int		  man_UR_pre(MAN_ARGS);
61static	int		  man_abort_pre(MAN_ARGS);
62static	int		  man_alt_pre(MAN_ARGS);
63static	int		  man_ign_pre(MAN_ARGS);
64static	int		  man_in_pre(MAN_ARGS);
65static	void		  man_root_post(const struct roff_meta *,
66				struct html *);
67static	void		  man_root_pre(const struct roff_meta *,
68				struct html *);
69
70static	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
111void
112html_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
140static void
141print_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
152static void
153print_man_nodelist(MAN_ARGS)
154{
155	while (n != NULL) {
156		print_man_node(man, n, h);
157		n = n->next;
158	}
159}
160
161static void
162print_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 (h->metac != ESCAPE_FONTROMAN) {
207			h->metal = h->metac;
208			h->metac = ESCAPE_FONTROMAN;
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
263static void
264man_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
291static void
292man_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
309static int
310man_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
342static int
343man_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
388static int
389man_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
397static int
398man_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
418static char
419list_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
445static int
446man_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
520static int
521man_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
547static int
548man_B_pre(MAN_ARGS)
549{
550	print_otag(h, TAG_B, "");
551	return 1;
552}
553
554static int
555man_I_pre(MAN_ARGS)
556{
557	print_otag(h, TAG_I, "");
558	return 1;
559}
560
561static int
562man_in_pre(MAN_ARGS)
563{
564	print_otag(h, TAG_BR, "");
565	return 0;
566}
567
568static int
569man_ign_pre(MAN_ARGS)
570{
571	return 0;
572}
573
574static int
575man_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
592static int
593man_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
614static int
615man_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
639static int
640man_abort_pre(MAN_ARGS)
641{
642	abort();
643}
644