xref: /netbsd-src/external/bsd/mdocml/dist/man_html.c (revision bd1f9e680783606ba327ccadf6002a15ffbdf514)
1 /*	$Vendor-Id: man_html.c,v 1.14 2009/10/26 08:18:16 kristaps Exp $ */
2 /*
3  * Copyright (c) 2008, 2009 Kristaps Dzonsons <kristaps@kth.se>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 #include <sys/types.h>
18 
19 #include <assert.h>
20 #include <ctype.h>
21 #include <err.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 
26 #include "out.h"
27 #include "html.h"
28 #include "man.h"
29 #include "main.h"
30 
31 /* TODO: preserve ident widths. */
32 /* FIXME: have PD set the default vspace width. */
33 
34 #define	INDENT		  5
35 #define	HALFINDENT	  3
36 
37 #define	MAN_ARGS	  const struct man_meta *m, \
38 			  const struct man_node *n, \
39 			  struct html *h
40 
41 struct	htmlman {
42 	int		(*pre)(MAN_ARGS);
43 	int		(*post)(MAN_ARGS);
44 };
45 
46 static	void		  print_man(MAN_ARGS);
47 static	void		  print_man_head(MAN_ARGS);
48 static	void		  print_man_nodelist(MAN_ARGS);
49 static	void		  print_man_node(MAN_ARGS);
50 
51 static	int		  a2width(const struct man_node *,
52 				struct roffsu *);
53 
54 static	int		  man_alt_pre(MAN_ARGS);
55 static	int		  man_br_pre(MAN_ARGS);
56 static	int		  man_ign_pre(MAN_ARGS);
57 static	void		  man_root_post(MAN_ARGS);
58 static	int		  man_root_pre(MAN_ARGS);
59 static	int		  man_B_pre(MAN_ARGS);
60 static	int		  man_HP_pre(MAN_ARGS);
61 static	int		  man_I_pre(MAN_ARGS);
62 static	int		  man_IP_pre(MAN_ARGS);
63 static	int		  man_PP_pre(MAN_ARGS);
64 static	int		  man_RS_pre(MAN_ARGS);
65 static	int		  man_SB_pre(MAN_ARGS);
66 static	int		  man_SH_pre(MAN_ARGS);
67 static	int		  man_SM_pre(MAN_ARGS);
68 static	int		  man_SS_pre(MAN_ARGS);
69 
70 #ifdef __linux__
71 extern	size_t	  	  strlcpy(char *, const char *, size_t);
72 extern	size_t	  	  strlcat(char *, const char *, size_t);
73 #endif
74 
75 static	const struct htmlman mans[MAN_MAX] = {
76 	{ man_br_pre, NULL }, /* br */
77 	{ NULL, NULL }, /* TH */
78 	{ man_SH_pre, NULL }, /* SH */
79 	{ man_SS_pre, NULL }, /* SS */
80 	{ man_IP_pre, NULL }, /* TP */
81 	{ man_PP_pre, NULL }, /* LP */
82 	{ man_PP_pre, NULL }, /* PP */
83 	{ man_PP_pre, NULL }, /* P */
84 	{ man_IP_pre, NULL }, /* IP */
85 	{ man_HP_pre, NULL }, /* HP */
86 	{ man_SM_pre, NULL }, /* SM */
87 	{ man_SB_pre, NULL }, /* SB */
88 	{ man_alt_pre, NULL }, /* BI */
89 	{ man_alt_pre, NULL }, /* IB */
90 	{ man_alt_pre, NULL }, /* BR */
91 	{ man_alt_pre, NULL }, /* RB */
92 	{ NULL, NULL }, /* R */
93 	{ man_B_pre, NULL }, /* B */
94 	{ man_I_pre, NULL }, /* I */
95 	{ man_alt_pre, NULL }, /* IR */
96 	{ man_alt_pre, NULL }, /* RI */
97 	{ NULL, NULL }, /* na */
98 	{ NULL, NULL }, /* i */
99 	{ man_br_pre, NULL }, /* sp */
100 	{ NULL, NULL }, /* nf */
101 	{ NULL, NULL }, /* fi */
102 	{ NULL, NULL }, /* r */
103 	{ NULL, NULL }, /* RE */
104 	{ man_RS_pre, NULL }, /* RS */
105 	{ man_ign_pre, NULL }, /* DT */
106 	{ man_ign_pre, NULL }, /* UC */
107 	{ man_ign_pre, NULL }, /* PD */
108 };
109 
110 
111 void
112 html_man(void *arg, const struct man *m)
113 {
114 	struct html	*h;
115 	struct tag	*t;
116 
117 	h = (struct html *)arg;
118 
119 	print_gen_doctype(h);
120 
121 	t = print_otag(h, TAG_HTML, 0, NULL);
122 	print_man(man_meta(m), man_node(m), h);
123 	print_tagq(h, t);
124 
125 	printf("\n");
126 }
127 
128 
129 static void
130 print_man(MAN_ARGS)
131 {
132 	struct tag	*t;
133 	struct htmlpair	 tag;
134 
135 	t = print_otag(h, TAG_HEAD, 0, NULL);
136 
137 	print_man_head(m, n, h);
138 	print_tagq(h, t);
139 	t = print_otag(h, TAG_BODY, 0, NULL);
140 
141 	tag.key = ATTR_CLASS;
142 	tag.val = "body";
143 	print_otag(h, TAG_DIV, 1, &tag);
144 
145 	print_man_nodelist(m, n, h);
146 
147 	print_tagq(h, t);
148 }
149 
150 
151 /* ARGSUSED */
152 static void
153 print_man_head(MAN_ARGS)
154 {
155 
156 	print_gen_head(h);
157 	bufinit(h);
158 	buffmt(h, "%s(%d)", m->title, m->msec);
159 
160 	print_otag(h, TAG_TITLE, 0, NULL);
161 	print_text(h, h->buf);
162 }
163 
164 
165 static void
166 print_man_nodelist(MAN_ARGS)
167 {
168 
169 	print_man_node(m, n, h);
170 	if (n->next)
171 		print_man_nodelist(m, n->next, h);
172 }
173 
174 
175 static void
176 print_man_node(MAN_ARGS)
177 {
178 	int		 child;
179 	struct tag	*t;
180 
181 	child = 1;
182 	t = h->tags.head;
183 
184 	bufinit(h);
185 
186 	switch (n->type) {
187 	case (MAN_ROOT):
188 		child = man_root_pre(m, n, h);
189 		break;
190 	case (MAN_TEXT):
191 		print_text(h, n->string);
192 		break;
193 	default:
194 		if (mans[n->tok].pre)
195 			child = (*mans[n->tok].pre)(m, n, h);
196 		break;
197 	}
198 
199 	if (child && n->child)
200 		print_man_nodelist(m, n->child, h);
201 
202 	print_stagq(h, t);
203 
204 	bufinit(h);
205 
206 	switch (n->type) {
207 	case (MAN_ROOT):
208 		man_root_post(m, n, h);
209 		break;
210 	case (MAN_TEXT):
211 		break;
212 	default:
213 		if (mans[n->tok].post)
214 			(*mans[n->tok].post)(m, n, h);
215 		break;
216 	}
217 }
218 
219 
220 static int
221 a2width(const struct man_node *n, struct roffsu *su)
222 {
223 
224 	if (MAN_TEXT != n->type)
225 		return(0);
226 	if (a2roffsu(n->string, su, SCALE_BU))
227 		return(1);
228 
229 	return(0);
230 }
231 
232 
233 /* ARGSUSED */
234 static int
235 man_root_pre(MAN_ARGS)
236 {
237 	struct htmlpair	 tag[2];
238 	struct tag	*t, *tt;
239 	char		 b[BUFSIZ], title[BUFSIZ];
240 
241 	b[0] = 0;
242 	if (m->vol)
243 		(void)strlcat(b, m->vol, BUFSIZ);
244 
245 	(void)snprintf(title, BUFSIZ - 1,
246 			"%s(%d)", m->title, m->msec);
247 
248 	PAIR_CLASS_INIT(&tag[0], "header");
249 	bufcat_style(h, "width", "100%");
250 	PAIR_STYLE_INIT(&tag[1], h);
251 	t = print_otag(h, TAG_TABLE, 2, tag);
252 	tt = print_otag(h, TAG_TR, 0, NULL);
253 
254 	bufinit(h);
255 	bufcat_style(h, "width", "10%");
256 	PAIR_STYLE_INIT(&tag[0], h);
257 	print_otag(h, TAG_TD, 1, tag);
258 	print_text(h, title);
259 	print_stagq(h, tt);
260 
261 	bufinit(h);
262 	bufcat_style(h, "width", "80%");
263 	bufcat_style(h, "white-space", "nowrap");
264 	bufcat_style(h, "text-align", "center");
265 	PAIR_STYLE_INIT(&tag[0], h);
266 	print_otag(h, TAG_TD, 1, tag);
267 	print_text(h, b);
268 	print_stagq(h, tt);
269 
270 	bufinit(h);
271 	bufcat_style(h, "width", "10%");
272 	bufcat_style(h, "text-align", "right");
273 	PAIR_STYLE_INIT(&tag[0], h);
274 	print_otag(h, TAG_TD, 1, tag);
275 	print_text(h, title);
276 	print_tagq(h, t);
277 	return(1);
278 }
279 
280 
281 /* ARGSUSED */
282 static void
283 man_root_post(MAN_ARGS)
284 {
285 	struct htmlpair	 tag[2];
286 	struct tag	*t, *tt;
287 	char		 b[DATESIZ];
288 
289 	time2a(m->date, b, DATESIZ);
290 
291 	PAIR_CLASS_INIT(&tag[0], "footer");
292 	bufcat_style(h, "width", "100%");
293 	PAIR_STYLE_INIT(&tag[1], h);
294 	t = print_otag(h, TAG_TABLE, 2, tag);
295 	tt = print_otag(h, TAG_TR, 0, NULL);
296 
297 	bufinit(h);
298 	bufcat_style(h, "width", "50%");
299 	PAIR_STYLE_INIT(&tag[0], h);
300 	print_otag(h, TAG_TD, 1, tag);
301 	print_text(h, b);
302 	print_stagq(h, tt);
303 
304 	bufinit(h);
305 	bufcat_style(h, "width", "50%");
306 	bufcat_style(h, "text-align", "right");
307 	PAIR_STYLE_INIT(&tag[0], h);
308 	print_otag(h, TAG_TD, 1, tag);
309 	if (m->source)
310 		print_text(h, m->source);
311 	print_tagq(h, t);
312 }
313 
314 
315 
316 /* ARGSUSED */
317 static int
318 man_br_pre(MAN_ARGS)
319 {
320 	struct roffsu	 su;
321 	struct htmlpair	 tag;
322 
323 	SCALE_VS_INIT(&su, 1);
324 
325 	if (MAN_sp == n->tok && n->child)
326 		a2roffsu(n->child->string, &su, SCALE_VS);
327 	else if (MAN_br == n->tok)
328 		su.scale = 0;
329 
330 	bufcat_su(h, "height", &su);
331 	PAIR_STYLE_INIT(&tag, h);
332 	print_otag(h, TAG_DIV, 1, &tag);
333 	return(0);
334 }
335 
336 
337 /* ARGSUSED */
338 static int
339 man_SH_pre(MAN_ARGS)
340 {
341 	struct htmlpair	 tag[2];
342 	struct roffsu	 su;
343 
344 	if (MAN_BODY == n->type) {
345 		SCALE_HS_INIT(&su, INDENT);
346 		bufcat_su(h, "margin-left", &su);
347 		PAIR_CLASS_INIT(&tag[0], "sec-body");
348 		PAIR_STYLE_INIT(&tag[1], h);
349 		print_otag(h, TAG_DIV, 2, tag);
350 		return(1);
351 	} else if (MAN_BLOCK == n->type) {
352 		PAIR_CLASS_INIT(&tag[0], "sec-block");
353 		if (n->prev && MAN_SH == n->prev->tok)
354 			if (NULL == n->prev->body->child) {
355 				print_otag(h, TAG_DIV, 1, tag);
356 				return(1);
357 			}
358 
359 		SCALE_VS_INIT(&su, 1);
360 		bufcat_su(h, "margin-top", &su);
361 		if (NULL == n->next)
362 			bufcat_su(h, "margin-bottom", &su);
363 		PAIR_STYLE_INIT(&tag[1], h);
364 		print_otag(h, TAG_DIV, 2, tag);
365 		return(1);
366 	}
367 
368 	PAIR_CLASS_INIT(&tag[0], "sec-head");
369 	print_otag(h, TAG_DIV, 1, tag);
370 	return(1);
371 }
372 
373 
374 /* ARGSUSED */
375 static int
376 man_alt_pre(MAN_ARGS)
377 {
378 	const struct man_node	*nn;
379 	struct tag		*t;
380 	int			 i;
381 	struct htmlpair		 tagi, tagb, *tagp;
382 
383 	PAIR_CLASS_INIT(&tagi, "italic");
384 	PAIR_CLASS_INIT(&tagb, "bold");
385 
386 	for (i = 0, nn = n->child; nn; nn = nn->next, i++) {
387 		switch (n->tok) {
388 		case (MAN_BI):
389 			tagp = i % 2 ? &tagi : &tagb;
390 			break;
391 		case (MAN_IB):
392 			tagp = i % 2 ? &tagb : &tagi;
393 			break;
394 		case (MAN_RI):
395 			tagp = i % 2 ? &tagi : NULL;
396 			break;
397 		case (MAN_IR):
398 			tagp = i % 2 ? NULL : &tagi;
399 			break;
400 		case (MAN_BR):
401 			tagp = i % 2 ? NULL : &tagb;
402 			break;
403 		case (MAN_RB):
404 			tagp = i % 2 ? &tagb : NULL;
405 			break;
406 		default:
407 			abort();
408 			/* NOTREACHED */
409 		}
410 
411 		if (i)
412 			h->flags |= HTML_NOSPACE;
413 
414 		if (tagp) {
415 			t = print_otag(h, TAG_SPAN, 1, tagp);
416 			print_man_node(m, nn, h);
417 			print_tagq(h, t);
418 		} else
419 			print_man_node(m, nn, h);
420 	}
421 
422 	return(0);
423 }
424 
425 
426 /* ARGSUSED */
427 static int
428 man_SB_pre(MAN_ARGS)
429 {
430 	struct htmlpair	 tag;
431 
432 	PAIR_CLASS_INIT(&tag, "small bold");
433 	print_otag(h, TAG_SPAN, 1, &tag);
434 	return(1);
435 }
436 
437 
438 /* ARGSUSED */
439 static int
440 man_SM_pre(MAN_ARGS)
441 {
442 	struct htmlpair	 tag;
443 
444 	PAIR_CLASS_INIT(&tag, "small");
445 	print_otag(h, TAG_SPAN, 1, &tag);
446 	return(1);
447 }
448 
449 
450 /* ARGSUSED */
451 static int
452 man_SS_pre(MAN_ARGS)
453 {
454 	struct htmlpair	 tag[3];
455 	struct roffsu	 su;
456 
457 	SCALE_VS_INIT(&su, 1);
458 
459 	if (MAN_BODY == n->type) {
460 		PAIR_CLASS_INIT(&tag[0], "ssec-body");
461 		if (n->parent->next && n->child) {
462 			bufcat_su(h, "margin-bottom", &su);
463 			PAIR_STYLE_INIT(&tag[1], h);
464 			print_otag(h, TAG_DIV, 2, tag);
465 			return(1);
466 		}
467 
468 		print_otag(h, TAG_DIV, 1, tag);
469 		return(1);
470 	} else if (MAN_BLOCK == n->type) {
471 		PAIR_CLASS_INIT(&tag[0], "ssec-block");
472 		if (n->prev && MAN_SS == n->prev->tok)
473 			if (n->prev->body->child) {
474 				bufcat_su(h, "margin-top", &su);
475 				PAIR_STYLE_INIT(&tag[1], h);
476 				print_otag(h, TAG_DIV, 2, tag);
477 				return(1);
478 			}
479 
480 		print_otag(h, TAG_DIV, 1, tag);
481 		return(1);
482 	}
483 
484 	SCALE_HS_INIT(&su, INDENT - HALFINDENT);
485 	bufcat_su(h, "margin-left", &su);
486 	PAIR_CLASS_INIT(&tag[0], "ssec-head");
487 	PAIR_STYLE_INIT(&tag[1], h);
488 	print_otag(h, TAG_DIV, 2, tag);
489 	return(1);
490 }
491 
492 
493 /* ARGSUSED */
494 static int
495 man_PP_pre(MAN_ARGS)
496 {
497 	struct htmlpair	 tag;
498 	struct roffsu	 su;
499 	int		 i;
500 
501 	if (MAN_BLOCK != n->type)
502 		return(1);
503 
504 	i = 0;
505 
506 	if (MAN_ROOT == n->parent->tok) {
507 		SCALE_HS_INIT(&su, INDENT);
508 		bufcat_su(h, "margin-left", &su);
509 		i++;
510 	}
511 	if (n->next && n->next->child) {
512 		SCALE_VS_INIT(&su, 1);
513 		bufcat_su(h, "margin-bottom", &su);
514 		i++;
515 	}
516 
517 	PAIR_STYLE_INIT(&tag, h);
518 	print_otag(h, TAG_DIV, i ? 1 : 0, &tag);
519 	return(1);
520 }
521 
522 
523 /* ARGSUSED */
524 static int
525 man_IP_pre(MAN_ARGS)
526 {
527 	struct roffsu		 su;
528 	struct htmlpair	 	 tag;
529 	const struct man_node	*nn;
530 	int			 width;
531 
532 	/*
533 	 * This scattering of 1-BU margins and pads is to make sure that
534 	 * when text overruns its box, the subsequent text isn't flush
535 	 * up against it.  However, the rest of the right-hand box must
536 	 * also be adjusted in consideration of this 1-BU space.
537 	 */
538 
539 	if (MAN_BODY == n->type) {
540 		SCALE_HS_INIT(&su, INDENT);
541 		bufcat_su(h, "margin-left", &su);
542 		PAIR_STYLE_INIT(&tag, h);
543 		print_otag(h, TAG_DIV, 1, &tag);
544 		return(1);
545 	}
546 
547 	nn = MAN_BLOCK == n->type ?
548 		n->head->child : n->parent->head->child;
549 
550 	SCALE_HS_INIT(&su, INDENT);
551 	width = 0;
552 
553 	if (MAN_IP == n->tok && NULL != nn)
554 		if (NULL != (nn = nn->next)) {
555 			for ( ; nn->next; nn = nn->next)
556 				/* Do nothing. */ ;
557 			width = a2width(nn, &su);
558 		}
559 
560 	if (MAN_TP == n->tok && NULL != nn)
561 		width = a2width(nn, &su);
562 
563 	if (MAN_BLOCK == n->type) {
564 		bufcat_su(h, "margin-left", &su);
565 		SCALE_VS_INIT(&su, 1);
566 		bufcat_su(h, "margin-top", &su);
567 		bufcat_style(h, "clear", "both");
568 		PAIR_STYLE_INIT(&tag, h);
569 		print_otag(h, TAG_DIV, 1, &tag);
570 		return(1);
571 	}
572 
573 	bufcat_su(h, "min-width", &su);
574 	SCALE_INVERT(&su);
575 	bufcat_su(h, "margin-left", &su);
576 	SCALE_HS_INIT(&su, 1);
577 	bufcat_su(h, "margin-right", &su);
578 	bufcat_style(h, "clear", "left");
579 
580 	if (n->next && n->next->child)
581 		bufcat_style(h, "float", "left");
582 
583 	PAIR_STYLE_INIT(&tag, h);
584 	print_otag(h, TAG_DIV, 1, &tag);
585 
586 	/* With a length string, manually omit the last child. */
587 
588 	if ( ! width)
589 		return(1);
590 
591 	if (MAN_IP == n->tok)
592 		for (nn = n->child; nn->next; nn = nn->next)
593 			print_man_node(m, nn, h);
594 	if (MAN_TP == n->tok)
595 		for (nn = n->child->next; nn; nn = nn->next)
596 			print_man_node(m, nn, h);
597 
598 	return(0);
599 }
600 
601 
602 /* ARGSUSED */
603 static int
604 man_HP_pre(MAN_ARGS)
605 {
606 	const struct man_node	*nn;
607 	struct htmlpair	 	 tag;
608 	struct roffsu		 su;
609 
610 	if (MAN_HEAD == n->type)
611 		return(0);
612 
613 	nn = MAN_BLOCK == n->type ?
614 		n->head->child : n->parent->head->child;
615 
616 	SCALE_HS_INIT(&su, INDENT);
617 
618 	if (NULL != nn)
619 		(void)a2width(nn, &su);
620 
621 	if (MAN_BLOCK == n->type) {
622 		bufcat_su(h, "margin-left", &su);
623 		SCALE_VS_INIT(&su, 1);
624 		bufcat_su(h, "margin-top", &su);
625 		bufcat_style(h, "clear", "both");
626 		PAIR_STYLE_INIT(&tag, h);
627 		print_otag(h, TAG_DIV, 1, &tag);
628 		return(1);
629 	}
630 
631 	bufcat_su(h, "margin-left", &su);
632 	SCALE_INVERT(&su);
633 	bufcat_su(h, "text-indent", &su);
634 
635 	PAIR_STYLE_INIT(&tag, h);
636 	print_otag(h, TAG_DIV, 1, &tag);
637 	return(1);
638 }
639 
640 
641 /* ARGSUSED */
642 static int
643 man_B_pre(MAN_ARGS)
644 {
645 	struct htmlpair	 tag;
646 
647 	PAIR_CLASS_INIT(&tag, "bold");
648 	print_otag(h, TAG_SPAN, 1, &tag);
649 	return(1);
650 }
651 
652 
653 /* ARGSUSED */
654 static int
655 man_I_pre(MAN_ARGS)
656 {
657 	struct htmlpair	 tag;
658 
659 	PAIR_CLASS_INIT(&tag, "italic");
660 	print_otag(h, TAG_SPAN, 1, &tag);
661 	return(1);
662 }
663 
664 
665 /* ARGSUSED */
666 static int
667 man_ign_pre(MAN_ARGS)
668 {
669 
670 	return(0);
671 }
672 
673 
674 /* ARGSUSED */
675 static int
676 man_RS_pre(MAN_ARGS)
677 {
678 	struct htmlpair	 tag;
679 	struct roffsu	 su;
680 
681 	if (MAN_HEAD == n->type)
682 		return(0);
683 	else if (MAN_BODY == n->type)
684 		return(1);
685 
686 	SCALE_HS_INIT(&su, INDENT);
687 	bufcat_su(h, "margin-left", &su);
688 
689 	if (n->head->child) {
690 		SCALE_VS_INIT(&su, 1);
691 		a2width(n->head->child, &su);
692 		bufcat_su(h, "margin-top", &su);
693 	}
694 
695 	PAIR_STYLE_INIT(&tag, h);
696 	print_otag(h, TAG_DIV, 1, &tag);
697 	return(1);
698 }
699