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