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