Import mdocml-1.10.9
[dragonfly.git] / contrib / mdocml / html.c
CommitLineData
80387638
SW
1/* $Id: html.c,v 1.124 2010/12/27 21:41:05 schwarze Exp $ */
2/*
3 * Copyright (c) 2008, 2009, 2010 Kristaps Dzonsons <kristaps@bsd.lv>
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#ifdef HAVE_CONFIG_H
18#include "config.h"
19#endif
20
21#include <sys/types.h>
22
23#include <assert.h>
24#include <ctype.h>
25#include <stdarg.h>
26#include <stdio.h>
27#include <stdint.h>
28#include <stdlib.h>
29#include <string.h>
30#include <unistd.h>
31
32#include "mandoc.h"
33#include "out.h"
34#include "chars.h"
35#include "html.h"
36#include "main.h"
37
38struct 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
46static 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 {"p", HTML_CLRLINE | HTML_NOSTACK | HTML_AUTOCLOSE}, /* TAG_P */
72 {"pre", HTML_CLRLINE }, /* TAG_PRE */
73 {"b", 0 }, /* TAG_B */
74 {"i", 0 }, /* TAG_I */
75 {"code", 0 }, /* TAG_CODE */
76 {"small", 0 }, /* TAG_SMALL */
77};
78
79static const char *const htmlattrs[ATTR_MAX] = {
80 "http-equiv", /* ATTR_HTTPEQUIV */
81 "content", /* ATTR_CONTENT */
82 "name", /* ATTR_NAME */
83 "rel", /* ATTR_REL */
84 "href", /* ATTR_HREF */
85 "type", /* ATTR_TYPE */
86 "media", /* ATTR_MEDIA */
87 "class", /* ATTR_CLASS */
88 "style", /* ATTR_STYLE */
89 "width", /* ATTR_WIDTH */
90 "id", /* ATTR_ID */
91 "summary", /* ATTR_SUMMARY */
92 "align", /* ATTR_ALIGN */
93};
94
95static void print_spec(struct html *, enum roffdeco,
96 const char *, size_t);
97static void print_res(struct html *, const char *, size_t);
98static void print_ctag(struct html *, enum htmltag);
99static void print_doctype(struct html *);
100static void print_xmltype(struct html *);
101static int print_encode(struct html *, const char *, int);
102static void print_metaf(struct html *, enum roffdeco);
103static void print_attr(struct html *,
104 const char *, const char *);
105static void *ml_alloc(char *, enum htmltype);
106
107
108static void *
109ml_alloc(char *outopts, enum htmltype type)
110{
111 struct html *h;
112 const char *toks[4];
113 char *v;
114
115 toks[0] = "style";
116 toks[1] = "man";
117 toks[2] = "includes";
118 toks[3] = NULL;
119
120 h = calloc(1, sizeof(struct html));
121 if (NULL == h) {
122 perror(NULL);
123 exit((int)MANDOCLEVEL_SYSERR);
124 }
125
126 h->type = type;
127 h->tags.head = NULL;
128 h->symtab = chars_init(CHARS_HTML);
129
130 while (outopts && *outopts)
131 switch (getsubopt(&outopts, UNCONST(toks), &v)) {
132 case (0):
133 h->style = v;
134 break;
135 case (1):
136 h->base_man = v;
137 break;
138 case (2):
139 h->base_includes = v;
140 break;
141 default:
142 break;
143 }
144
145 return(h);
146}
147
148void *
149html_alloc(char *outopts)
150{
151
152 return(ml_alloc(outopts, HTML_HTML_4_01_STRICT));
153}
154
155
156void *
157xhtml_alloc(char *outopts)
158{
159
160 return(ml_alloc(outopts, HTML_XHTML_1_0_STRICT));
161}
162
163
164void
165html_free(void *p)
166{
167 struct tag *tag;
168 struct html *h;
169
170 h = (struct html *)p;
171
172 while ((tag = h->tags.head) != NULL) {
173 h->tags.head = tag->next;
174 free(tag);
175 }
176
177 if (h->symtab)
178 chars_free(h->symtab);
179
180 free(h);
181}
182
183
184void
185print_gen_head(struct html *h)
186{
187 struct htmlpair tag[4];
188
189 tag[0].key = ATTR_HTTPEQUIV;
190 tag[0].val = "Content-Type";
191 tag[1].key = ATTR_CONTENT;
192 tag[1].val = "text/html; charset=utf-8";
193 print_otag(h, TAG_META, 2, tag);
194
195 tag[0].key = ATTR_NAME;
196 tag[0].val = "resource-type";
197 tag[1].key = ATTR_CONTENT;
198 tag[1].val = "document";
199 print_otag(h, TAG_META, 2, tag);
200
201 if (h->style) {
202 tag[0].key = ATTR_REL;
203 tag[0].val = "stylesheet";
204 tag[1].key = ATTR_HREF;
205 tag[1].val = h->style;
206 tag[2].key = ATTR_TYPE;
207 tag[2].val = "text/css";
208 tag[3].key = ATTR_MEDIA;
209 tag[3].val = "all";
210 print_otag(h, TAG_LINK, 4, tag);
211 }
212}
213
214
215static void
216print_spec(struct html *h, enum roffdeco d, const char *p, size_t len)
217{
218 int cp;
219 const char *rhs;
220 size_t sz;
221
222 if ((cp = chars_spec2cp(h->symtab, p, len)) > 0) {
223 printf("&#%d;", cp);
224 return;
225 } else if (-1 == cp && DECO_SSPECIAL == d) {
226 fwrite(p, 1, len, stdout);
227 return;
228 } else if (-1 == cp)
229 return;
230
231 if (NULL != (rhs = chars_spec2str(h->symtab, p, len, &sz)))
232 fwrite(rhs, 1, sz, stdout);
233}
234
235
236static void
237print_res(struct html *h, const char *p, size_t len)
238{
239 int cp;
240 const char *rhs;
241 size_t sz;
242
243 if ((cp = chars_res2cp(h->symtab, p, len)) > 0) {
244 printf("&#%d;", cp);
245 return;
246 } else if (-1 == cp)
247 return;
248
249 if (NULL != (rhs = chars_res2str(h->symtab, p, len, &sz)))
250 fwrite(rhs, 1, sz, stdout);
251}
252
253
254static void
255print_metaf(struct html *h, enum roffdeco deco)
256{
257 enum htmlfont font;
258
259 switch (deco) {
260 case (DECO_PREVIOUS):
261 font = h->metal;
262 break;
263 case (DECO_ITALIC):
264 font = HTMLFONT_ITALIC;
265 break;
266 case (DECO_BOLD):
267 font = HTMLFONT_BOLD;
268 break;
269 case (DECO_ROMAN):
270 font = HTMLFONT_NONE;
271 break;
272 default:
273 abort();
274 /* NOTREACHED */
275 }
276
277 if (h->metaf) {
278 print_tagq(h, h->metaf);
279 h->metaf = NULL;
280 }
281
282 h->metal = h->metac;
283 h->metac = font;
284
285 if (HTMLFONT_NONE != font)
286 h->metaf = HTMLFONT_BOLD == font ?
287 print_otag(h, TAG_B, 0, NULL) :
288 print_otag(h, TAG_I, 0, NULL);
289}
290
291
292static int
293print_encode(struct html *h, const char *p, int norecurse)
294{
295 size_t sz;
296 int len, nospace;
297 const char *seq;
298 enum roffdeco deco;
299 static const char rejs[6] = { '\\', '<', '>', '&', ASCII_HYPH, '\0' };
300
301 nospace = 0;
302
303 for (; *p; p++) {
304 sz = strcspn(p, rejs);
305
306 fwrite(p, 1, sz, stdout);
307 p += /* LINTED */
308 sz;
309
310 if ('<' == *p) {
311 printf("&lt;");
312 continue;
313 } else if ('>' == *p) {
314 printf("&gt;");
315 continue;
316 } else if ('&' == *p) {
317 printf("&amp;");
318 continue;
319 } else if (ASCII_HYPH == *p) {
320 /*
321 * Note: "soft hyphens" aren't graphically
322 * displayed when not breaking the text; we want
323 * them to be displayed.
324 */
325 /*printf("&#173;");*/
326 putchar('-');
327 continue;
328 } else if ('\0' == *p)
329 break;
330
331 seq = ++p;
332 len = a2roffdeco(&deco, &seq, &sz);
333
334 switch (deco) {
335 case (DECO_RESERVED):
336 print_res(h, seq, sz);
337 break;
338 case (DECO_SSPECIAL):
339 /* FALLTHROUGH */
340 case (DECO_SPECIAL):
341 print_spec(h, deco, seq, sz);
342 break;
343 case (DECO_PREVIOUS):
344 /* FALLTHROUGH */
345 case (DECO_BOLD):
346 /* FALLTHROUGH */
347 case (DECO_ITALIC):
348 /* FALLTHROUGH */
349 case (DECO_ROMAN):
350 if (norecurse)
351 break;
352 print_metaf(h, deco);
353 break;
354 default:
355 break;
356 }
357
358 p += len - 1;
359
360 if (DECO_NOSPACE == deco && '\0' == *(p + 1))
361 nospace = 1;
362 }
363
364 return(nospace);
365}
366
367
368static void
369print_attr(struct html *h, const char *key, const char *val)
370{
371 printf(" %s=\"", key);
372 (void)print_encode(h, val, 1);
373 putchar('\"');
374}
375
376
377struct tag *
378print_otag(struct html *h, enum htmltag tag,
379 int sz, const struct htmlpair *p)
380{
381 int i;
382 struct tag *t;
383
384 /* Push this tags onto the stack of open scopes. */
385
386 if ( ! (HTML_NOSTACK & htmltags[tag].flags)) {
387 t = malloc(sizeof(struct tag));
388 if (NULL == t) {
389 perror(NULL);
390 exit((int)MANDOCLEVEL_SYSERR);
391 }
392 t->tag = tag;
393 t->next = h->tags.head;
394 h->tags.head = t;
395 } else
396 t = NULL;
397
398 if ( ! (HTML_NOSPACE & h->flags))
399 if ( ! (HTML_CLRLINE & htmltags[tag].flags)) {
400 /* Manage keeps! */
401 if ( ! (HTML_KEEP & h->flags)) {
402 if (HTML_PREKEEP & h->flags)
403 h->flags |= HTML_KEEP;
404 putchar(' ');
405 } else
406 printf("&#160;");
407 }
408
409 if ( ! (h->flags & HTML_NONOSPACE))
410 h->flags &= ~HTML_NOSPACE;
411 else
412 h->flags |= HTML_NOSPACE;
413
414 /* Print out the tag name and attributes. */
415
416 printf("<%s", htmltags[tag].name);
417 for (i = 0; i < sz; i++)
418 print_attr(h, htmlattrs[p[i].key], p[i].val);
419
420 /* Add non-overridable attributes. */
421
422 if (TAG_HTML == tag && HTML_XHTML_1_0_STRICT == h->type) {
423 print_attr(h, "xmlns", "http://www.w3.org/1999/xhtml");
424 print_attr(h, "xml:lang", "en");
425 print_attr(h, "lang", "en");
426 }
427
428 /* Accomodate for XML "well-formed" singleton escaping. */
429
430 if (HTML_AUTOCLOSE & htmltags[tag].flags)
431 switch (h->type) {
432 case (HTML_XHTML_1_0_STRICT):
433 putchar('/');
434 break;
435 default:
436 break;
437 }
438
439 putchar('>');
440
441 h->flags |= HTML_NOSPACE;
442
443 if ((HTML_AUTOCLOSE | HTML_CLRLINE) & htmltags[tag].flags)
444 putchar('\n');
445
446 return(t);
447}
448
449
450static void
451print_ctag(struct html *h, enum htmltag tag)
452{
453
454 printf("</%s>", htmltags[tag].name);
455 if (HTML_CLRLINE & htmltags[tag].flags) {
456 h->flags |= HTML_NOSPACE;
457 putchar('\n');
458 }
459}
460
461
462void
463print_gen_decls(struct html *h)
464{
465
466 print_xmltype(h);
467 print_doctype(h);
468}
469
470
471static void
472print_xmltype(struct html *h)
473{
474
475 if (HTML_XHTML_1_0_STRICT == h->type)
476 puts("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
477}
478
479
480static void
481print_doctype(struct html *h)
482{
483 const char *doctype;
484 const char *dtd;
485 const char *name;
486
487 switch (h->type) {
488 case (HTML_HTML_4_01_STRICT):
489 name = "HTML";
490 doctype = "-//W3C//DTD HTML 4.01//EN";
491 dtd = "http://www.w3.org/TR/html4/strict.dtd";
492 break;
493 default:
494 name = "html";
495 doctype = "-//W3C//DTD XHTML 1.0 Strict//EN";
496 dtd = "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd";
497 break;
498 }
499
500 printf("<!DOCTYPE %s PUBLIC \"%s\" \"%s\">\n",
501 name, doctype, dtd);
502}
503
504
505void
506print_text(struct html *h, const char *word)
507{
508
509 if (word[0] && '\0' == word[1])
510 switch (word[0]) {
511 case('.'):
512 /* FALLTHROUGH */
513 case(','):
514 /* FALLTHROUGH */
515 case(';'):
516 /* FALLTHROUGH */
517 case(':'):
518 /* FALLTHROUGH */
519 case('?'):
520 /* FALLTHROUGH */
521 case('!'):
522 /* FALLTHROUGH */
523 case(')'):
524 /* FALLTHROUGH */
525 case(']'):
526 if ( ! (HTML_IGNDELIM & h->flags))
527 h->flags |= HTML_NOSPACE;
528 break;
529 default:
530 break;
531 }
532
533 if ( ! (HTML_NOSPACE & h->flags)) {
534 /* Manage keeps! */
535 if ( ! (HTML_KEEP & h->flags)) {
536 if (HTML_PREKEEP & h->flags)
537 h->flags |= HTML_KEEP;
538 putchar(' ');
539 } else
540 printf("&#160;");
541 }
542
543 assert(NULL == h->metaf);
544 if (HTMLFONT_NONE != h->metac)
545 h->metaf = HTMLFONT_BOLD == h->metac ?
546 print_otag(h, TAG_B, 0, NULL) :
547 print_otag(h, TAG_I, 0, NULL);
548
549 assert(word);
550 if ( ! print_encode(h, word, 0))
551 if ( ! (h->flags & HTML_NONOSPACE))
552 h->flags &= ~HTML_NOSPACE;
553
554 if (h->metaf) {
555 print_tagq(h, h->metaf);
556 h->metaf = NULL;
557 }
558
559 h->flags &= ~HTML_IGNDELIM;
560
561 /*
562 * Note that we don't process the pipe: the parser sees it as
563 * punctuation, but we don't in terms of typography.
564 */
565 if (word[0] && '\0' == word[1])
566 switch (word[0]) {
567 case('('):
568 /* FALLTHROUGH */
569 case('['):
570 h->flags |= HTML_NOSPACE;
571 break;
572 default:
573 break;
574 }
575}
576
577
578void
579print_tagq(struct html *h, const struct tag *until)
580{
581 struct tag *tag;
582
583 while ((tag = h->tags.head) != NULL) {
584 if (tag == h->metaf)
585 h->metaf = NULL;
586 print_ctag(h, tag->tag);
587 h->tags.head = tag->next;
588 free(tag);
589 if (until && tag == until)
590 return;
591 }
592}
593
594
595void
596print_stagq(struct html *h, const struct tag *suntil)
597{
598 struct tag *tag;
599
600 while ((tag = h->tags.head) != NULL) {
601 if (suntil && tag == suntil)
602 return;
603 if (tag == h->metaf)
604 h->metaf = NULL;
605 print_ctag(h, tag->tag);
606 h->tags.head = tag->next;
607 free(tag);
608 }
609}
610
611
612void
613bufinit(struct html *h)
614{
615
616 h->buf[0] = '\0';
617 h->buflen = 0;
618}
619
620
621void
622bufcat_style(struct html *h, const char *key, const char *val)
623{
624
625 bufcat(h, key);
626 bufncat(h, ":", 1);
627 bufcat(h, val);
628 bufncat(h, ";", 1);
629}
630
631
632void
633bufcat(struct html *h, const char *p)
634{
635
636 bufncat(h, p, strlen(p));
637}
638
639
640void
641buffmt(struct html *h, const char *fmt, ...)
642{
643 va_list ap;
644
645 va_start(ap, fmt);
646 (void)vsnprintf(h->buf + (int)h->buflen,
647 BUFSIZ - h->buflen - 1, fmt, ap);
648 va_end(ap);
649 h->buflen = strlen(h->buf);
650}
651
652
653void
654bufncat(struct html *h, const char *p, size_t sz)
655{
656
657 if (h->buflen + sz > BUFSIZ - 1)
658 sz = BUFSIZ - 1 - h->buflen;
659
660 (void)strncat(h->buf, p, sz);
661 h->buflen += sz;
662}
663
664
665void
666buffmt_includes(struct html *h, const char *name)
667{
668 const char *p, *pp;
669
670 pp = h->base_includes;
671
672 while (NULL != (p = strchr(pp, '%'))) {
673 bufncat(h, pp, (size_t)(p - pp));
674 switch (*(p + 1)) {
675 case('I'):
676 bufcat(h, name);
677 break;
678 default:
679 bufncat(h, p, 2);
680 break;
681 }
682 pp = p + 2;
683 }
684 if (pp)
685 bufcat(h, pp);
686}
687
688
689void
690buffmt_man(struct html *h,
691 const char *name, const char *sec)
692{
693 const char *p, *pp;
694
695 pp = h->base_man;
696
697 /* LINTED */
698 while (NULL != (p = strchr(pp, '%'))) {
699 bufncat(h, pp, (size_t)(p - pp));
700 switch (*(p + 1)) {
701 case('S'):
702 bufcat(h, sec ? sec : "1");
703 break;
704 case('N'):
705 buffmt(h, name);
706 break;
707 default:
708 bufncat(h, p, 2);
709 break;
710 }
711 pp = p + 2;
712 }
713 if (pp)
714 bufcat(h, pp);
715}
716
717
718void
719bufcat_su(struct html *h, const char *p, const struct roffsu *su)
720{
721 double v;
722 const char *u;
723
724 v = su->scale;
725
726 switch (su->unit) {
727 case (SCALE_CM):
728 u = "cm";
729 break;
730 case (SCALE_IN):
731 u = "in";
732 break;
733 case (SCALE_PC):
734 u = "pc";
735 break;
736 case (SCALE_PT):
737 u = "pt";
738 break;
739 case (SCALE_EM):
740 u = "em";
741 break;
742 case (SCALE_MM):
743 if (0 == (v /= 100))
744 v = 1;
745 u = "em";
746 break;
747 case (SCALE_EN):
748 u = "ex";
749 break;
750 case (SCALE_BU):
751 u = "ex";
752 break;
753 case (SCALE_VS):
754 u = "em";
755 break;
756 default:
757 u = "ex";
758 break;
759 }
760
761 /*
762 * XXX: the CSS spec isn't clear as to which types accept
763 * integer or real numbers, so we just make them all decimals.
764 */
765 buffmt(h, "%s: %.2f%s;", p, v, u);
766}
767
768
769void
770html_idcat(char *dst, const char *src, int sz)
771{
772 int ssz;
773
774 assert(sz > 2);
775
776 /* Cf. <http://www.w3.org/TR/html4/types.html#h-6.2>. */
777
778 /* We can't start with a number (bah). */
779
780 if ('#' == *dst) {
781 dst++;
782 sz--;
783 }
784 if ('\0' == *dst) {
785 *dst++ = 'x';
786 *dst = '\0';
787 sz--;
788 }
789
790 for ( ; *dst != '\0' && sz; dst++, sz--)
791 /* Jump to end. */ ;
792
793 for ( ; *src != '\0' && sz > 1; src++) {
794 ssz = snprintf(dst, (size_t)sz, "%.2x", *src);
795 sz -= ssz;
796 dst += ssz;
797 }
798}