70403ff77e0d5218e26b01e522ec341fb0b91af1
[dragonfly.git] / contrib / mdocml / html.c
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
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         {"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
79 static  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
95 static  void              print_spec(struct html *, enum roffdeco,
96                                 const char *, size_t);
97 static  void              print_res(struct html *, const char *, size_t);
98 static  void              print_ctag(struct html *, enum htmltag);
99 static  void              print_doctype(struct html *);
100 static  void              print_xmltype(struct html *);
101 static  int               print_encode(struct html *, const char *, int);
102 static  void              print_metaf(struct html *, enum roffdeco);
103 static  void              print_attr(struct html *, 
104                                 const char *, const char *);
105 static  void             *ml_alloc(char *, enum htmltype);
106
107
108 static void *
109 ml_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
148 void *
149 html_alloc(char *outopts)
150 {
151
152         return(ml_alloc(outopts, HTML_HTML_4_01_STRICT));
153 }
154
155
156 void *
157 xhtml_alloc(char *outopts)
158 {
159
160         return(ml_alloc(outopts, HTML_XHTML_1_0_STRICT));
161 }
162
163
164 void
165 html_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
184 void
185 print_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
215 static void
216 print_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
236 static void
237 print_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
254 static void
255 print_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
292 static int
293 print_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
368 static void
369 print_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
377 struct tag *
378 print_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
450 static void
451 print_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
462 void
463 print_gen_decls(struct html *h)
464 {
465
466         print_xmltype(h);
467         print_doctype(h);
468 }
469
470
471 static void
472 print_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
480 static void
481 print_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
505 void
506 print_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
578 void
579 print_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
595 void
596 print_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
612 void
613 bufinit(struct html *h)
614 {
615
616         h->buf[0] = '\0';
617         h->buflen = 0;
618 }
619
620
621 void
622 bufcat_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
632 void
633 bufcat(struct html *h, const char *p)
634 {
635
636         bufncat(h, p, strlen(p));
637 }
638
639
640 void
641 buffmt(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
653 void
654 bufncat(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
665 void
666 buffmt_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
689 void
690 buffmt_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
718 void
719 bufcat_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
769 void
770 html_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 }