xref: /illumos-gate/usr/src/cmd/mandoc/html.c (revision 708dafbf52fa04021b9471a3582eaf6586ed6931)
1 /*	$Id: html.c,v 1.192 2016/01/04 12:45:29 schwarze Exp $ */
2 /*
3  * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
4  * Copyright (c) 2011-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 <stdarg.h>
25 #include <stdio.h>
26 #include <stdint.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <unistd.h>
30 
31 #include "mandoc.h"
32 #include "mandoc_aux.h"
33 #include "out.h"
34 #include "html.h"
35 #include "manconf.h"
36 #include "main.h"
37 
38 struct	htmldata {
39 	const char	 *name;
40 	int		  flags;
41 #define	HTML_CLRLINE	 (1 << 0)
42 #define	HTML_NOSTACK	 (1 << 1)
43 #define	HTML_AUTOCLOSE	 (1 << 2) /* Tag has auto-closure. */
44 };
45 
46 static	const struct htmldata htmltags[TAG_MAX] = {
47 	{"html",	HTML_CLRLINE}, /* TAG_HTML */
48 	{"head",	HTML_CLRLINE}, /* TAG_HEAD */
49 	{"body",	HTML_CLRLINE}, /* TAG_BODY */
50 	{"meta",	HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_META */
51 	{"title",	HTML_CLRLINE}, /* TAG_TITLE */
52 	{"div",		HTML_CLRLINE}, /* TAG_DIV */
53 	{"h1",		0}, /* TAG_H1 */
54 	{"h2",		0}, /* TAG_H2 */
55 	{"span",	0}, /* TAG_SPAN */
56 	{"link",	HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_LINK */
57 	{"br",		HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_BR */
58 	{"a",		0}, /* TAG_A */
59 	{"table",	HTML_CLRLINE}, /* TAG_TABLE */
60 	{"tbody",	HTML_CLRLINE}, /* TAG_TBODY */
61 	{"col",		HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_COL */
62 	{"tr",		HTML_CLRLINE}, /* TAG_TR */
63 	{"td",		HTML_CLRLINE}, /* TAG_TD */
64 	{"li",		HTML_CLRLINE}, /* TAG_LI */
65 	{"ul",		HTML_CLRLINE}, /* TAG_UL */
66 	{"ol",		HTML_CLRLINE}, /* TAG_OL */
67 	{"dl",		HTML_CLRLINE}, /* TAG_DL */
68 	{"dt",		HTML_CLRLINE}, /* TAG_DT */
69 	{"dd",		HTML_CLRLINE}, /* TAG_DD */
70 	{"blockquote",	HTML_CLRLINE}, /* TAG_BLOCKQUOTE */
71 	{"pre",		HTML_CLRLINE }, /* TAG_PRE */
72 	{"b",		0 }, /* TAG_B */
73 	{"i",		0 }, /* TAG_I */
74 	{"code",	0 }, /* TAG_CODE */
75 	{"small",	0 }, /* TAG_SMALL */
76 	{"style",	HTML_CLRLINE}, /* TAG_STYLE */
77 	{"math",	HTML_CLRLINE}, /* TAG_MATH */
78 	{"mrow",	0}, /* TAG_MROW */
79 	{"mi",		0}, /* TAG_MI */
80 	{"mo",		0}, /* TAG_MO */
81 	{"msup",	0}, /* TAG_MSUP */
82 	{"msub",	0}, /* TAG_MSUB */
83 	{"msubsup",	0}, /* TAG_MSUBSUP */
84 	{"mfrac",	0}, /* TAG_MFRAC */
85 	{"msqrt",	0}, /* TAG_MSQRT */
86 	{"mfenced",	0}, /* TAG_MFENCED */
87 	{"mtable",	0}, /* TAG_MTABLE */
88 	{"mtr",		0}, /* TAG_MTR */
89 	{"mtd",		0}, /* TAG_MTD */
90 	{"munderover",	0}, /* TAG_MUNDEROVER */
91 	{"munder",	0}, /* TAG_MUNDER*/
92 	{"mover",	0}, /* TAG_MOVER*/
93 };
94 
95 static	const char	*const htmlattrs[ATTR_MAX] = {
96 	"name", /* ATTR_NAME */
97 	"rel", /* ATTR_REL */
98 	"href", /* ATTR_HREF */
99 	"type", /* ATTR_TYPE */
100 	"media", /* ATTR_MEDIA */
101 	"class", /* ATTR_CLASS */
102 	"style", /* ATTR_STYLE */
103 	"id", /* ATTR_ID */
104 	"colspan", /* ATTR_COLSPAN */
105 	"charset", /* ATTR_CHARSET */
106 	"open", /* ATTR_OPEN */
107 	"close", /* ATTR_CLOSE */
108 	"mathvariant", /* ATTR_MATHVARIANT */
109 };
110 
111 static	const char	*const roffscales[SCALE_MAX] = {
112 	"cm", /* SCALE_CM */
113 	"in", /* SCALE_IN */
114 	"pc", /* SCALE_PC */
115 	"pt", /* SCALE_PT */
116 	"em", /* SCALE_EM */
117 	"em", /* SCALE_MM */
118 	"ex", /* SCALE_EN */
119 	"ex", /* SCALE_BU */
120 	"em", /* SCALE_VS */
121 	"ex", /* SCALE_FS */
122 };
123 
124 static	void	 bufncat(struct html *, const char *, size_t);
125 static	void	 print_ctag(struct html *, struct tag *);
126 static	int	 print_escape(char);
127 static	int	 print_encode(struct html *, const char *, int);
128 static	void	 print_metaf(struct html *, enum mandoc_esc);
129 static	void	 print_attr(struct html *, const char *, const char *);
130 
131 
132 void *
133 html_alloc(const struct manoutput *outopts)
134 {
135 	struct html	*h;
136 
137 	h = mandoc_calloc(1, sizeof(struct html));
138 
139 	h->tags.head = NULL;
140 	h->style = outopts->style;
141 	h->base_man = outopts->man;
142 	h->base_includes = outopts->includes;
143 	if (outopts->fragment)
144 		h->oflags |= HTML_FRAGMENT;
145 
146 	return h;
147 }
148 
149 void
150 html_free(void *p)
151 {
152 	struct tag	*tag;
153 	struct html	*h;
154 
155 	h = (struct html *)p;
156 
157 	while ((tag = h->tags.head) != NULL) {
158 		h->tags.head = tag->next;
159 		free(tag);
160 	}
161 
162 	free(h);
163 }
164 
165 void
166 print_gen_head(struct html *h)
167 {
168 	struct htmlpair	 tag[4];
169 	struct tag	*t;
170 
171 	tag[0].key = ATTR_CHARSET;
172 	tag[0].val = "utf-8";
173 	print_otag(h, TAG_META, 1, tag);
174 
175 	/*
176 	 * Print a default style-sheet.
177 	 */
178 	t = print_otag(h, TAG_STYLE, 0, NULL);
179 	print_text(h, "table.head, table.foot { width: 100%; }\n"
180 	      "td.head-rtitle, td.foot-os { text-align: right; }\n"
181 	      "td.head-vol { text-align: center; }\n"
182 	      "table.foot td { width: 50%; }\n"
183 	      "table.head td { width: 33%; }\n"
184 	      "div.spacer { margin: 1em 0; }\n");
185 	print_tagq(h, t);
186 
187 	if (h->style) {
188 		tag[0].key = ATTR_REL;
189 		tag[0].val = "stylesheet";
190 		tag[1].key = ATTR_HREF;
191 		tag[1].val = h->style;
192 		tag[2].key = ATTR_TYPE;
193 		tag[2].val = "text/css";
194 		tag[3].key = ATTR_MEDIA;
195 		tag[3].val = "all";
196 		print_otag(h, TAG_LINK, 4, tag);
197 	}
198 }
199 
200 static void
201 print_metaf(struct html *h, enum mandoc_esc deco)
202 {
203 	enum htmlfont	 font;
204 
205 	switch (deco) {
206 	case ESCAPE_FONTPREV:
207 		font = h->metal;
208 		break;
209 	case ESCAPE_FONTITALIC:
210 		font = HTMLFONT_ITALIC;
211 		break;
212 	case ESCAPE_FONTBOLD:
213 		font = HTMLFONT_BOLD;
214 		break;
215 	case ESCAPE_FONTBI:
216 		font = HTMLFONT_BI;
217 		break;
218 	case ESCAPE_FONT:
219 	case ESCAPE_FONTROMAN:
220 		font = HTMLFONT_NONE;
221 		break;
222 	default:
223 		abort();
224 	}
225 
226 	if (h->metaf) {
227 		print_tagq(h, h->metaf);
228 		h->metaf = NULL;
229 	}
230 
231 	h->metal = h->metac;
232 	h->metac = font;
233 
234 	switch (font) {
235 	case HTMLFONT_ITALIC:
236 		h->metaf = print_otag(h, TAG_I, 0, NULL);
237 		break;
238 	case HTMLFONT_BOLD:
239 		h->metaf = print_otag(h, TAG_B, 0, NULL);
240 		break;
241 	case HTMLFONT_BI:
242 		h->metaf = print_otag(h, TAG_B, 0, NULL);
243 		print_otag(h, TAG_I, 0, NULL);
244 		break;
245 	default:
246 		break;
247 	}
248 }
249 
250 int
251 html_strlen(const char *cp)
252 {
253 	size_t		 rsz;
254 	int		 skip, sz;
255 
256 	/*
257 	 * Account for escaped sequences within string length
258 	 * calculations.  This follows the logic in term_strlen() as we
259 	 * must calculate the width of produced strings.
260 	 * Assume that characters are always width of "1".  This is
261 	 * hacky, but it gets the job done for approximation of widths.
262 	 */
263 
264 	sz = 0;
265 	skip = 0;
266 	while (1) {
267 		rsz = strcspn(cp, "\\");
268 		if (rsz) {
269 			cp += rsz;
270 			if (skip) {
271 				skip = 0;
272 				rsz--;
273 			}
274 			sz += rsz;
275 		}
276 		if ('\0' == *cp)
277 			break;
278 		cp++;
279 		switch (mandoc_escape(&cp, NULL, NULL)) {
280 		case ESCAPE_ERROR:
281 			return sz;
282 		case ESCAPE_UNICODE:
283 		case ESCAPE_NUMBERED:
284 		case ESCAPE_SPECIAL:
285 		case ESCAPE_OVERSTRIKE:
286 			if (skip)
287 				skip = 0;
288 			else
289 				sz++;
290 			break;
291 		case ESCAPE_SKIPCHAR:
292 			skip = 1;
293 			break;
294 		default:
295 			break;
296 		}
297 	}
298 	return sz;
299 }
300 
301 static int
302 print_escape(char c)
303 {
304 
305 	switch (c) {
306 	case '<':
307 		printf("&lt;");
308 		break;
309 	case '>':
310 		printf("&gt;");
311 		break;
312 	case '&':
313 		printf("&amp;");
314 		break;
315 	case '"':
316 		printf("&quot;");
317 		break;
318 	case ASCII_NBRSP:
319 		printf("&nbsp;");
320 		break;
321 	case ASCII_HYPH:
322 		putchar('-');
323 		break;
324 	case ASCII_BREAK:
325 		break;
326 	default:
327 		return 0;
328 	}
329 	return 1;
330 }
331 
332 static int
333 print_encode(struct html *h, const char *p, int norecurse)
334 {
335 	size_t		 sz;
336 	int		 c, len, nospace;
337 	const char	*seq;
338 	enum mandoc_esc	 esc;
339 	static const char rejs[9] = { '\\', '<', '>', '&', '"',
340 		ASCII_NBRSP, ASCII_HYPH, ASCII_BREAK, '\0' };
341 
342 	nospace = 0;
343 
344 	while ('\0' != *p) {
345 		if (HTML_SKIPCHAR & h->flags && '\\' != *p) {
346 			h->flags &= ~HTML_SKIPCHAR;
347 			p++;
348 			continue;
349 		}
350 
351 		sz = strcspn(p, rejs);
352 
353 		fwrite(p, 1, sz, stdout);
354 		p += (int)sz;
355 
356 		if ('\0' == *p)
357 			break;
358 
359 		if (print_escape(*p++))
360 			continue;
361 
362 		esc = mandoc_escape(&p, &seq, &len);
363 		if (ESCAPE_ERROR == esc)
364 			break;
365 
366 		switch (esc) {
367 		case ESCAPE_FONT:
368 		case ESCAPE_FONTPREV:
369 		case ESCAPE_FONTBOLD:
370 		case ESCAPE_FONTITALIC:
371 		case ESCAPE_FONTBI:
372 		case ESCAPE_FONTROMAN:
373 			if (0 == norecurse)
374 				print_metaf(h, esc);
375 			continue;
376 		case ESCAPE_SKIPCHAR:
377 			h->flags |= HTML_SKIPCHAR;
378 			continue;
379 		default:
380 			break;
381 		}
382 
383 		if (h->flags & HTML_SKIPCHAR) {
384 			h->flags &= ~HTML_SKIPCHAR;
385 			continue;
386 		}
387 
388 		switch (esc) {
389 		case ESCAPE_UNICODE:
390 			/* Skip past "u" header. */
391 			c = mchars_num2uc(seq + 1, len - 1);
392 			break;
393 		case ESCAPE_NUMBERED:
394 			c = mchars_num2char(seq, len);
395 			if (c < 0)
396 				continue;
397 			break;
398 		case ESCAPE_SPECIAL:
399 			c = mchars_spec2cp(seq, len);
400 			if (c <= 0)
401 				continue;
402 			break;
403 		case ESCAPE_NOSPACE:
404 			if ('\0' == *p)
405 				nospace = 1;
406 			continue;
407 		case ESCAPE_OVERSTRIKE:
408 			if (len == 0)
409 				continue;
410 			c = seq[len - 1];
411 			break;
412 		default:
413 			continue;
414 		}
415 		if ((c < 0x20 && c != 0x09) ||
416 		    (c > 0x7E && c < 0xA0))
417 			c = 0xFFFD;
418 		if (c > 0x7E)
419 			printf("&#%d;", c);
420 		else if ( ! print_escape(c))
421 			putchar(c);
422 	}
423 
424 	return nospace;
425 }
426 
427 static void
428 print_attr(struct html *h, const char *key, const char *val)
429 {
430 	printf(" %s=\"", key);
431 	(void)print_encode(h, val, 1);
432 	putchar('\"');
433 }
434 
435 struct tag *
436 print_otag(struct html *h, enum htmltag tag,
437 		int sz, const struct htmlpair *p)
438 {
439 	int		 i;
440 	struct tag	*t;
441 
442 	/* Push this tags onto the stack of open scopes. */
443 
444 	if ( ! (HTML_NOSTACK & htmltags[tag].flags)) {
445 		t = mandoc_malloc(sizeof(struct tag));
446 		t->tag = tag;
447 		t->next = h->tags.head;
448 		h->tags.head = t;
449 	} else
450 		t = NULL;
451 
452 	if ( ! (HTML_NOSPACE & h->flags))
453 		if ( ! (HTML_CLRLINE & htmltags[tag].flags)) {
454 			/* Manage keeps! */
455 			if ( ! (HTML_KEEP & h->flags)) {
456 				if (HTML_PREKEEP & h->flags)
457 					h->flags |= HTML_KEEP;
458 				putchar(' ');
459 			} else
460 				printf("&#160;");
461 		}
462 
463 	if ( ! (h->flags & HTML_NONOSPACE))
464 		h->flags &= ~HTML_NOSPACE;
465 	else
466 		h->flags |= HTML_NOSPACE;
467 
468 	/* Print out the tag name and attributes. */
469 
470 	printf("<%s", htmltags[tag].name);
471 	for (i = 0; i < sz; i++)
472 		print_attr(h, htmlattrs[p[i].key], p[i].val);
473 
474 	/* Accommodate for "well-formed" singleton escaping. */
475 
476 	if (HTML_AUTOCLOSE & htmltags[tag].flags)
477 		putchar('/');
478 
479 	putchar('>');
480 
481 	h->flags |= HTML_NOSPACE;
482 
483 	if ((HTML_AUTOCLOSE | HTML_CLRLINE) & htmltags[tag].flags)
484 		putchar('\n');
485 
486 	return t;
487 }
488 
489 static void
490 print_ctag(struct html *h, struct tag *tag)
491 {
492 
493 	/*
494 	 * Remember to close out and nullify the current
495 	 * meta-font and table, if applicable.
496 	 */
497 	if (tag == h->metaf)
498 		h->metaf = NULL;
499 	if (tag == h->tblt)
500 		h->tblt = NULL;
501 
502 	printf("</%s>", htmltags[tag->tag].name);
503 	if (HTML_CLRLINE & htmltags[tag->tag].flags) {
504 		h->flags |= HTML_NOSPACE;
505 		putchar('\n');
506 	}
507 
508 	h->tags.head = tag->next;
509 	free(tag);
510 }
511 
512 void
513 print_gen_decls(struct html *h)
514 {
515 
516 	puts("<!DOCTYPE html>");
517 }
518 
519 void
520 print_text(struct html *h, const char *word)
521 {
522 
523 	if ( ! (HTML_NOSPACE & h->flags)) {
524 		/* Manage keeps! */
525 		if ( ! (HTML_KEEP & h->flags)) {
526 			if (HTML_PREKEEP & h->flags)
527 				h->flags |= HTML_KEEP;
528 			putchar(' ');
529 		} else
530 			printf("&#160;");
531 	}
532 
533 	assert(NULL == h->metaf);
534 	switch (h->metac) {
535 	case HTMLFONT_ITALIC:
536 		h->metaf = print_otag(h, TAG_I, 0, NULL);
537 		break;
538 	case HTMLFONT_BOLD:
539 		h->metaf = print_otag(h, TAG_B, 0, NULL);
540 		break;
541 	case HTMLFONT_BI:
542 		h->metaf = print_otag(h, TAG_B, 0, NULL);
543 		print_otag(h, TAG_I, 0, NULL);
544 		break;
545 	default:
546 		break;
547 	}
548 
549 	assert(word);
550 	if ( ! print_encode(h, word, 0)) {
551 		if ( ! (h->flags & HTML_NONOSPACE))
552 			h->flags &= ~HTML_NOSPACE;
553 		h->flags &= ~HTML_NONEWLINE;
554 	} else
555 		h->flags |= HTML_NOSPACE | HTML_NONEWLINE;
556 
557 	if (h->metaf) {
558 		print_tagq(h, h->metaf);
559 		h->metaf = NULL;
560 	}
561 
562 	h->flags &= ~HTML_IGNDELIM;
563 }
564 
565 void
566 print_tagq(struct html *h, const struct tag *until)
567 {
568 	struct tag	*tag;
569 
570 	while ((tag = h->tags.head) != NULL) {
571 		print_ctag(h, tag);
572 		if (until && tag == until)
573 			return;
574 	}
575 }
576 
577 void
578 print_stagq(struct html *h, const struct tag *suntil)
579 {
580 	struct tag	*tag;
581 
582 	while ((tag = h->tags.head) != NULL) {
583 		if (suntil && tag == suntil)
584 			return;
585 		print_ctag(h, tag);
586 	}
587 }
588 
589 void
590 print_paragraph(struct html *h)
591 {
592 	struct tag	*t;
593 	struct htmlpair	 tag;
594 
595 	PAIR_CLASS_INIT(&tag, "spacer");
596 	t = print_otag(h, TAG_DIV, 1, &tag);
597 	print_tagq(h, t);
598 }
599 
600 
601 void
602 bufinit(struct html *h)
603 {
604 
605 	h->buf[0] = '\0';
606 	h->buflen = 0;
607 }
608 
609 void
610 bufcat_style(struct html *h, const char *key, const char *val)
611 {
612 
613 	bufcat(h, key);
614 	bufcat(h, ":");
615 	bufcat(h, val);
616 	bufcat(h, ";");
617 }
618 
619 void
620 bufcat(struct html *h, const char *p)
621 {
622 
623 	/*
624 	 * XXX This is broken and not easy to fix.
625 	 * When using the -Oincludes option, buffmt_includes()
626 	 * may pass in strings overrunning BUFSIZ, causing a crash.
627 	 */
628 
629 	h->buflen = strlcat(h->buf, p, BUFSIZ);
630 	assert(h->buflen < BUFSIZ);
631 }
632 
633 void
634 bufcat_fmt(struct html *h, const char *fmt, ...)
635 {
636 	va_list		 ap;
637 
638 	va_start(ap, fmt);
639 	(void)vsnprintf(h->buf + (int)h->buflen,
640 	    BUFSIZ - h->buflen - 1, fmt, ap);
641 	va_end(ap);
642 	h->buflen = strlen(h->buf);
643 }
644 
645 static void
646 bufncat(struct html *h, const char *p, size_t sz)
647 {
648 
649 	assert(h->buflen + sz + 1 < BUFSIZ);
650 	strncat(h->buf, p, sz);
651 	h->buflen += sz;
652 }
653 
654 void
655 buffmt_includes(struct html *h, const char *name)
656 {
657 	const char	*p, *pp;
658 
659 	pp = h->base_includes;
660 
661 	bufinit(h);
662 	while (NULL != (p = strchr(pp, '%'))) {
663 		bufncat(h, pp, (size_t)(p - pp));
664 		switch (*(p + 1)) {
665 		case'I':
666 			bufcat(h, name);
667 			break;
668 		default:
669 			bufncat(h, p, 2);
670 			break;
671 		}
672 		pp = p + 2;
673 	}
674 	if (pp)
675 		bufcat(h, pp);
676 }
677 
678 void
679 buffmt_man(struct html *h, const char *name, const char *sec)
680 {
681 	const char	*p, *pp;
682 
683 	pp = h->base_man;
684 
685 	bufinit(h);
686 	while (NULL != (p = strchr(pp, '%'))) {
687 		bufncat(h, pp, (size_t)(p - pp));
688 		switch (*(p + 1)) {
689 		case 'S':
690 			bufcat(h, sec ? sec : "1");
691 			break;
692 		case 'N':
693 			bufcat_fmt(h, "%s", name);
694 			break;
695 		default:
696 			bufncat(h, p, 2);
697 			break;
698 		}
699 		pp = p + 2;
700 	}
701 	if (pp)
702 		bufcat(h, pp);
703 }
704 
705 void
706 bufcat_su(struct html *h, const char *p, const struct roffsu *su)
707 {
708 	double		 v;
709 
710 	v = su->scale;
711 	if (SCALE_MM == su->unit && 0.0 == (v /= 100.0))
712 		v = 1.0;
713 	else if (SCALE_BU == su->unit)
714 		v /= 24.0;
715 
716 	bufcat_fmt(h, "%s: %.2f%s;", p, v, roffscales[su->unit]);
717 }
718 
719 void
720 bufcat_id(struct html *h, const char *src)
721 {
722 
723 	/* Cf. <http://www.w3.org/TR/html5/dom.html#the-id-attribute>. */
724 
725 	for (; '\0' != *src; src++)
726 		bufncat(h, *src == ' ' ? "_" : src, 1);
727 }
728