=================================================================== RCS file: /cvs/mandoc/html.c,v retrieving revision 1.202 retrieving revision 1.251 diff -u -p -r1.202 -r1.251 --- mandoc/html.c 2017/01/26 18:28:18 1.202 +++ mandoc/html.c 2019/01/11 12:56:42 1.251 @@ -1,7 +1,7 @@ -/* $Id: html.c,v 1.202 2017/01/26 18:28:18 schwarze Exp $ */ +/* $Id: html.c,v 1.251 2019/01/11 12:56:42 schwarze Exp $ */ /* * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons - * Copyright (c) 2011-2015, 2017 Ingo Schwarze + * Copyright (c) 2011-2015, 2017-2019 Ingo Schwarze * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -18,18 +18,22 @@ #include "config.h" #include +#include #include #include #include +#include #include #include #include #include #include -#include "mandoc.h" #include "mandoc_aux.h" +#include "mandoc_ohash.h" +#include "mandoc.h" +#include "roff.h" #include "out.h" #include "html.h" #include "manconf.h" @@ -58,6 +62,7 @@ static const struct htmldata htmltags[TAG_MAX] = { {"meta", HTML_NOSTACK | HTML_AUTOCLOSE | HTML_NLALL}, {"title", HTML_NLAROUND}, {"div", HTML_NLAROUND}, + {"div", 0}, {"h1", HTML_NLAROUND}, {"h2", HTML_NLAROUND}, {"span", 0}, @@ -65,8 +70,6 @@ static const struct htmldata htmltags[TAG_MAX] = { {"br", HTML_NOSTACK | HTML_AUTOCLOSE | HTML_NLALL}, {"a", 0}, {"table", HTML_NLALL | HTML_INDENT}, - {"tbody", HTML_NLALL | HTML_INDENT}, - {"col", HTML_NOSTACK | HTML_AUTOCLOSE | HTML_NLALL}, {"tr", HTML_NLALL | HTML_INDENT}, {"td", HTML_NLAROUND}, {"li", HTML_NLAROUND | HTML_INDENT}, @@ -75,7 +78,10 @@ static const struct htmldata htmltags[TAG_MAX] = { {"dl", HTML_NLALL | HTML_INDENT}, {"dt", HTML_NLAROUND}, {"dd", HTML_NLAROUND | HTML_INDENT}, + {"p", HTML_NLAROUND | HTML_INDENT}, {"pre", HTML_NLALL | HTML_NOINDENT}, + {"var", 0}, + {"cite", 0}, {"b", 0}, {"i", 0}, {"code", 0}, @@ -84,6 +90,7 @@ static const struct htmldata htmltags[TAG_MAX] = { {"math", HTML_NLALL | HTML_INDENT}, {"mrow", 0}, {"mi", 0}, + {"mn", 0}, {"mo", 0}, {"msup", 0}, {"msub", 0}, @@ -99,20 +106,9 @@ static const struct htmldata htmltags[TAG_MAX] = { {"mover", 0}, }; -static const char *const roffscales[SCALE_MAX] = { - "cm", /* SCALE_CM */ - "in", /* SCALE_IN */ - "pc", /* SCALE_PC */ - "pt", /* SCALE_PT */ - "em", /* SCALE_EM */ - "em", /* SCALE_MM */ - "ex", /* SCALE_EN */ - "ex", /* SCALE_BU */ - "em", /* SCALE_VS */ - "ex", /* SCALE_FS */ -}; +/* Avoid duplicate HTML id= attributes. */ +static struct ohash id_unique; -static void a2width(const char *, struct roffsu *); static void print_byte(struct html *, char); static void print_endword(struct html *); static void print_indent(struct html *); @@ -122,7 +118,6 @@ static void print_ctag(struct html *, struct tag *); static int print_escape(struct html *, char); static int print_encode(struct html *, const char *, const char *, int); static void print_href(struct html *, const char *, const char *, int); -static void print_metaf(struct html *, enum mandoc_esc); void * @@ -132,13 +127,20 @@ html_alloc(const struct manoutput *outopts) h = mandoc_calloc(1, sizeof(struct html)); - h->tags.head = NULL; + h->tag = NULL; h->style = outopts->style; - h->base_man = outopts->man; + if ((h->base_man1 = outopts->man) == NULL) + h->base_man2 = NULL; + else if ((h->base_man2 = strchr(h->base_man1, ';')) != NULL) + *h->base_man2++ = '\0'; h->base_includes = outopts->includes; if (outopts->fragment) h->oflags |= HTML_FRAGMENT; + if (outopts->toc) + h->oflags |= HTML_TOC; + mandoc_ohash_init(&id_unique, 4, 0); + return h; } @@ -147,15 +149,22 @@ html_free(void *p) { struct tag *tag; struct html *h; + char *cp; + unsigned int slot; h = (struct html *)p; - - while ((tag = h->tags.head) != NULL) { - h->tags.head = tag->next; + while ((tag = h->tag) != NULL) { + h->tag = tag->next; free(tag); } - free(h); + + cp = ohash_first(&id_unique, &slot); + while (cp != NULL) { + free(cp); + cp = ohash_next(&id_unique, &slot); + } + ohash_delete(&id_unique); } void @@ -164,9 +173,14 @@ print_gen_head(struct html *h) struct tag *t; print_otag(h, TAG_META, "?", "charset", "utf-8"); + if (h->style != NULL) { + print_otag(h, TAG_LINK, "?h??", "rel", "stylesheet", + h->style, "type", "text/css", "media", "all"); + return; + } /* - * Print a default style-sheet. + * Print a minimal embedded style sheet. */ t = print_otag(h, TAG_STYLE, ""); @@ -177,14 +191,26 @@ print_gen_head(struct html *h) print_text(h, "td.head-vol { text-align: center; }"); print_endline(h); print_text(h, "div.Pp { margin: 1ex 0ex; }"); + print_endline(h); + print_text(h, "div.Nd, div.Bf, div.Op { display: inline; }"); + print_endline(h); + print_text(h, "span.Pa, span.Ad { font-style: italic; }"); + print_endline(h); + print_text(h, "span.Ms { font-weight: bold; }"); + print_endline(h); + print_text(h, "dl.Bl-diag "); + print_byte(h, '>'); + print_text(h, " dt { font-weight: bold; }"); + print_endline(h); + print_text(h, "code.Nm, code.Fl, code.Cm, code.Ic, " + "code.In, code.Fd, code.Fn,"); + print_endline(h); + print_text(h, "code.Cd { font-weight: bold; " + "font-family: inherit; }"); print_tagq(h, t); - - if (h->style) - print_otag(h, TAG_LINK, "?h??", "rel", "stylesheet", - h->style, "type", "text/css", "media", "all"); } -static void +void print_metaf(struct html *h, enum mandoc_esc deco) { enum htmlfont font; @@ -202,12 +228,15 @@ print_metaf(struct html *h, enum mandoc_esc deco) case ESCAPE_FONTBI: font = HTMLFONT_BI; break; + case ESCAPE_FONTCW: + font = HTMLFONT_CW; + break; case ESCAPE_FONT: case ESCAPE_FONTROMAN: font = HTMLFONT_NONE; break; default: - abort(); + return; } if (h->metaf) { @@ -229,62 +258,117 @@ print_metaf(struct html *h, enum mandoc_esc deco) h->metaf = print_otag(h, TAG_B, ""); print_otag(h, TAG_I, ""); break; + case HTMLFONT_CW: + h->metaf = print_otag(h, TAG_SPAN, "c", "Li"); + break; default: break; } } -int -html_strlen(const char *cp) +void +html_close_paragraph(struct html *h) { - size_t rsz; - int skip, sz; + struct tag *t; - /* - * Account for escaped sequences within string length - * calculations. This follows the logic in term_strlen() as we - * must calculate the width of produced strings. - * Assume that characters are always width of "1". This is - * hacky, but it gets the job done for approximation of widths. - */ - - sz = 0; - skip = 0; - while (1) { - rsz = strcspn(cp, "\\"); - if (rsz) { - cp += rsz; - if (skip) { - skip = 0; - rsz--; - } - sz += rsz; + for (t = h->tag; t != NULL; t = t->next) { + if (t->tag == TAG_P || t->tag == TAG_PRE) { + print_tagq(h, t); + break; } - if ('\0' == *cp) + } +} + +/* + * ROFF_nf switches to no-fill mode, ROFF_fi to fill mode. + * TOKEN_NONE does not switch. The old mode is returned. + */ +enum roff_tok +html_fillmode(struct html *h, enum roff_tok want) +{ + struct tag *t; + enum roff_tok had; + + for (t = h->tag; t != NULL; t = t->next) + if (t->tag == TAG_PRE) break; - cp++; - switch (mandoc_escape(&cp, NULL, NULL)) { - case ESCAPE_ERROR: - return sz; - case ESCAPE_UNICODE: - case ESCAPE_NUMBERED: - case ESCAPE_SPECIAL: - case ESCAPE_OVERSTRIKE: - if (skip) - skip = 0; - else - sz++; + + had = t == NULL ? ROFF_fi : ROFF_nf; + + if (want != had) { + switch (want) { + case ROFF_fi: + print_tagq(h, t); break; - case ESCAPE_SKIPCHAR: - skip = 1; + case ROFF_nf: + html_close_paragraph(h); + print_otag(h, TAG_PRE, ""); break; - default: + case TOKEN_NONE: break; + default: + abort(); } } - return sz; + return had; } +char * +html_make_id(const struct roff_node *n, int unique) +{ + const struct roff_node *nch; + char *buf, *bufs, *cp; + unsigned int slot; + int suffix; + + for (nch = n->child; nch != NULL; nch = nch->next) + if (nch->type != ROFFT_TEXT) + return NULL; + + buf = NULL; + deroff(&buf, n); + if (buf == NULL) + return NULL; + + /* + * In ID attributes, only use ASCII characters that are + * permitted in URL-fragment strings according to the + * explicit list at: + * https://url.spec.whatwg.org/#url-fragment-string + */ + + for (cp = buf; *cp != '\0'; cp++) + if (isalnum((unsigned char)*cp) == 0 && + strchr("!$&'()*+,-./:;=?@_~", *cp) == NULL) + *cp = '_'; + + if (unique == 0) + return buf; + + /* Avoid duplicate HTML id= attributes. */ + + bufs = NULL; + suffix = 1; + slot = ohash_qlookup(&id_unique, buf); + cp = ohash_find(&id_unique, slot); + if (cp != NULL) { + while (cp != NULL) { + free(bufs); + if (++suffix > 127) { + free(buf); + return NULL; + } + mandoc_asprintf(&bufs, "%s_%d", buf, suffix); + slot = ohash_qlookup(&id_unique, bufs); + cp = ohash_find(&id_unique, slot); + } + free(buf); + buf = bufs; + } + ohash_insert(&id_unique, slot, buf); + return buf; +} + static int print_escape(struct html *h, char c) { @@ -320,16 +404,17 @@ static int print_encode(struct html *h, const char *p, const char *pend, int norecurse) { char numbuf[16]; - size_t sz; - int c, len, nospace; const char *seq; + size_t sz; + int c, len, breakline, nospace; enum mandoc_esc esc; - static const char rejs[9] = { '\\', '<', '>', '&', '"', + static const char rejs[10] = { ' ', '\\', '<', '>', '&', '"', ASCII_NBRSP, ASCII_HYPH, ASCII_BREAK, '\0' }; if (pend == NULL) pend = strchr(p, '\0'); + breakline = 0; nospace = 0; while (p < pend) { @@ -340,34 +425,49 @@ print_encode(struct html *h, const char *p, const char } for (sz = strcspn(p, rejs); sz-- && p < pend; p++) - if (*p == ' ') - print_endword(h); - else - print_byte(h, *p); + print_byte(h, *p); + if (breakline && + (p >= pend || *p == ' ' || *p == ASCII_NBRSP)) { + print_otag(h, TAG_BR, ""); + breakline = 0; + while (p < pend && (*p == ' ' || *p == ASCII_NBRSP)) + p++; + continue; + } + if (p >= pend) break; + if (*p == ' ') { + print_endword(h); + p++; + continue; + } + if (print_escape(h, *p++)) continue; esc = mandoc_escape(&p, &seq, &len); - if (ESCAPE_ERROR == esc) - break; - switch (esc) { case ESCAPE_FONT: case ESCAPE_FONTPREV: case ESCAPE_FONTBOLD: case ESCAPE_FONTITALIC: case ESCAPE_FONTBI: + case ESCAPE_FONTCW: case ESCAPE_FONTROMAN: - if (0 == norecurse) + if (0 == norecurse) { + h->flags |= HTML_NOSPACE; print_metaf(h, esc); + h->flags &= ~HTML_NOSPACE; + } continue; case ESCAPE_SKIPCHAR: h->flags |= HTML_SKIPCHAR; continue; + case ESCAPE_ERROR: + continue; default: break; } @@ -392,6 +492,15 @@ print_encode(struct html *h, const char *p, const char if (c <= 0) continue; break; + case ESCAPE_UNDEF: + c = *seq; + break; + case ESCAPE_DEVICE: + print_word(h, "html"); + continue; + case ESCAPE_BREAK: + breakline = 1; + continue; case ESCAPE_NOSPACE: if ('\0' == *p) nospace = 1; @@ -408,7 +517,7 @@ print_encode(struct html *h, const char *p, const char (c > 0x7E && c < 0xA0)) c = 0xFFFD; if (c > 0x7E) { - (void)snprintf(numbuf, sizeof(numbuf), "&#%d;", c); + (void)snprintf(numbuf, sizeof(numbuf), "&#x%.4X;", c); print_word(h, numbuf); } else if (print_escape(h, c) == 0) print_byte(h, c); @@ -420,9 +529,21 @@ print_encode(struct html *h, const char *p, const char static void print_href(struct html *h, const char *name, const char *sec, int man) { + struct stat sb; const char *p, *pp; + char *filename; - pp = man ? h->base_man : h->base_includes; + if (man) { + pp = h->base_man1; + if (h->base_man2 != NULL) { + mandoc_asprintf(&filename, "%s.%s", name, sec); + if (stat(filename, &sb) == -1) + pp = h->base_man2; + free(filename); + } + } else + pp = h->base_includes; + while ((p = strchr(pp, '%')) != NULL) { print_encode(h, pp, p, 1); if (man && p[1] == 'S') { @@ -445,23 +566,20 @@ struct tag * print_otag(struct html *h, enum htmltag tag, const char *fmt, ...) { va_list ap; - struct roffsu mysu, *su; - char numbuf[16]; struct tag *t; const char *attr; - char *s; - double v; - int i, have_style, tflags; + char *arg1, *arg2; + int style_written, tflags; tflags = htmltags[tag].flags; - /* Push this tags onto the stack of open scopes. */ + /* Push this tag onto the stack of open scopes. */ if ((tflags & HTML_NOSTACK) == 0) { t = mandoc_malloc(sizeof(struct tag)); t->tag = tag; - t->next = h->tags.head; - h->tags.head = t; + t->next = h->tag; + h->tag = t; } else t = NULL; @@ -471,7 +589,7 @@ print_otag(struct html *h, enum htmltag tag, const cha print_indent(h); else if ((h->flags & HTML_NOSPACE) == 0) { if (h->flags & HTML_KEEP) - print_word(h, " "); + print_word(h, " "); else { if (h->flags & HTML_PREKEEP) h->flags |= HTML_KEEP; @@ -491,15 +609,12 @@ print_otag(struct html *h, enum htmltag tag, const cha va_start(ap, fmt); - have_style = 0; - while (*fmt != '\0') { - if (*fmt == 's') { - print_word(h, " style=\""); - have_style = 1; - fmt++; - break; - } - s = va_arg(ap, char *); + while (*fmt != '\0' && *fmt != 's') { + + /* Parse attributes and arguments. */ + + arg1 = va_arg(ap, char *); + arg2 = NULL; switch (*fmt++) { case 'c': attr = "class"; @@ -511,122 +626,62 @@ print_otag(struct html *h, enum htmltag tag, const cha attr = "id"; break; case '?': - attr = s; - s = va_arg(ap, char *); + attr = arg1; + arg1 = va_arg(ap, char *); break; default: abort(); } + if (*fmt == 'M') + arg2 = va_arg(ap, char *); + if (arg1 == NULL) + continue; + + /* Print the attributes. */ + print_byte(h, ' '); print_word(h, attr); print_byte(h, '='); print_byte(h, '"'); switch (*fmt) { - case 'M': - print_href(h, s, va_arg(ap, char *), 1); + case 'I': + print_href(h, arg1, NULL, 0); fmt++; break; - case 'I': - print_href(h, s, NULL, 0); + case 'M': + print_href(h, arg1, arg2, 1); fmt++; break; case 'R': print_byte(h, '#'); + print_encode(h, arg1, NULL, 1); fmt++; - /* FALLTHROUGH */ + break; default: - print_encode(h, s, NULL, 1); + print_encode(h, arg1, NULL, 1); break; } print_byte(h, '"'); } - /* Print out styles. */ - - s = NULL; - su = &mysu; - while (*fmt != '\0') { - - /* First letter: input argument type. */ - - switch (*fmt++) { - case 'h': - i = va_arg(ap, int); - SCALE_HS_INIT(su, i); - break; - case 's': - s = va_arg(ap, char *); - break; - case 'u': - su = va_arg(ap, struct roffsu *); - break; - case 'v': - i = va_arg(ap, int); - SCALE_VS_INIT(su, i); - break; - case 'w': - case 'W': - s = va_arg(ap, char *); - a2width(s, su); - if (fmt[-1] == 'W') - su->scale *= -1.0; - break; - default: - abort(); - } - - /* Second letter: style name. */ - - switch (*fmt++) { - case 'b': - attr = "margin-bottom"; - break; - case 'h': - attr = "height"; - break; - case 'i': - attr = "text-indent"; - break; - case 'l': - attr = "margin-left"; - break; - case 't': - attr = "margin-top"; - break; - case 'w': - attr = "width"; - break; - case 'W': - attr = "min-width"; - break; - case '?': - print_word(h, s); - print_byte(h, ':'); - print_byte(h, ' '); - print_word(h, va_arg(ap, char *)); - print_byte(h, ';'); - if (*fmt != '\0') - print_byte(h, ' '); + style_written = 0; + while (*fmt++ == 's') { + arg1 = va_arg(ap, char *); + arg2 = va_arg(ap, char *); + if (arg2 == NULL) continue; - default: - abort(); + print_byte(h, ' '); + if (style_written == 0) { + print_word(h, "style=\""); + style_written = 1; } - v = su->scale; - if (su->unit == SCALE_MM && (v /= 100.0) == 0.0) - v = 1.0; - else if (su->unit == SCALE_BU) - v /= 24.0; - print_word(h, attr); + print_word(h, arg1); print_byte(h, ':'); print_byte(h, ' '); - (void)snprintf(numbuf, sizeof(numbuf), "%.2f", v); - print_word(h, numbuf); - print_word(h, roffscales[su->unit]); + print_word(h, arg2); print_byte(h, ';'); - if (*fmt != '\0') - print_byte(h, ' '); } - if (have_style) + if (style_written) print_byte(h, '"'); va_end(ap); @@ -681,7 +736,7 @@ print_ctag(struct html *h, struct tag *tag) if (tflags & HTML_NLAFTER) print_endline(h); - h->tags.head = tag->next; + h->tag = tag->next; free(tag); } @@ -693,6 +748,32 @@ print_gen_decls(struct html *h) } void +print_gen_comment(struct html *h, struct roff_node *n) +{ + int wantblank; + + print_word(h, "") == NULL && + (wantblank || *n->string != '\0')) { + print_endline(h); + print_indent(h); + print_word(h, n->string); + wantblank = *n->string != '\0'; + } + n = n->next; + } + if (wantblank) + print_endline(h); + print_word(h, " -->"); + print_endline(h); + h->indent = 0; +} + +void print_text(struct html *h, const char *word) { if (h->col && (h->flags & HTML_NOSPACE) == 0) { @@ -701,7 +782,7 @@ print_text(struct html *h, const char *word) h->flags |= HTML_KEEP; print_endword(h); } else - print_word(h, " "); + print_word(h, " "); } assert(NULL == h->metaf); @@ -716,6 +797,9 @@ print_text(struct html *h, const char *word) h->metaf = print_otag(h, TAG_B, ""); print_otag(h, TAG_I, ""); break; + case HTMLFONT_CW: + h->metaf = print_otag(h, TAG_SPAN, "c", "Li"); + break; default: print_indent(h); break; @@ -742,35 +826,33 @@ print_tagq(struct html *h, const struct tag *until) { struct tag *tag; - while ((tag = h->tags.head) != NULL) { + while ((tag = h->tag) != NULL) { print_ctag(h, tag); - if (until && tag == until) + if (tag == until) return; } } +/* + * Close out all open elements up to but excluding suntil. + * Note that a paragraph just inside stays open together with it + * because paragraphs include subsequent phrasing content. + */ void print_stagq(struct html *h, const struct tag *suntil) { struct tag *tag; - while ((tag = h->tags.head) != NULL) { - if (suntil && tag == suntil) + while ((tag = h->tag) != NULL) { + if (tag == suntil || + (tag->next == suntil && + (tag->tag == TAG_P || tag->tag == TAG_PRE))) return; print_ctag(h, tag); } } -void -print_paragraph(struct html *h) -{ - struct tag *t; - t = print_otag(h, TAG_DIV, "c", "Pp"); - print_tagq(h, t); -} - - /*********************************************************************** * Low level output functions. * They implement line breaking using a short static buffer. @@ -882,19 +964,4 @@ print_word(struct html *h, const char *cp) { while (*cp != '\0') print_byte(h, *cp++); -} - -/* - * Calculate the scaling unit passed in a `-width' argument. This uses - * either a native scaling unit (e.g., 1i, 2m) or the string length of - * the value. - */ -static void -a2width(const char *p, struct roffsu *su) -{ - if (a2roffsu(p, su, SCALE_MAX) < 2) { - su->unit = SCALE_EN; - su->scale = html_strlen(p); - } else if (su->scale < 0.0) - su->scale = 0.0; }