=================================================================== RCS file: /cvs/mandoc/html.c,v retrieving revision 1.262 retrieving revision 1.279 diff -u -p -r1.262 -r1.279 --- mandoc/html.c 2020/01/19 18:02:00 1.262 +++ mandoc/html.c 2022/08/09 11:23:11 1.279 @@ -1,7 +1,8 @@ -/* $Id: html.c,v 1.262 2020/01/19 18:02:00 schwarze Exp $ */ +/* $Id: html.c,v 1.279 2022/08/09 11:23:11 schwarze Exp $ */ /* * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons - * Copyright (c) 2011-2015, 2017-2019 Ingo Schwarze + * Copyright (c) 2011-2015, 2017-2021 Ingo Schwarze + * Copyright (c) 2022 Anna Vyalkova * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -14,6 +15,9 @@ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Common functions for mandoc(1) HTML formatters. + * For use by individual formatters and by the main program. */ #include "config.h" @@ -64,8 +68,10 @@ static const struct htmldata htmltags[TAG_MAX] = { {"style", HTML_NLALL | HTML_INDENT}, {"title", HTML_NLAROUND}, {"body", HTML_NLALL}, + {"main", HTML_NLALL}, {"div", HTML_NLAROUND}, {"section", HTML_NLALL}, + {"nav", HTML_NLALL}, {"table", HTML_NLALL | HTML_INDENT}, {"tr", HTML_NLALL | HTML_INDENT}, {"td", HTML_NLAROUND}, @@ -75,10 +81,10 @@ static const struct htmldata htmltags[TAG_MAX] = { {"dl", HTML_NLALL | HTML_INDENT}, {"dt", HTML_NLAROUND}, {"dd", HTML_NLAROUND | HTML_INDENT}, - {"h1", HTML_TOPHRASE | HTML_NLAROUND}, {"h2", HTML_TOPHRASE | HTML_NLAROUND}, + {"h3", HTML_TOPHRASE | HTML_NLAROUND}, {"p", HTML_TOPHRASE | HTML_NLAROUND | HTML_INDENT}, - {"pre", HTML_TOPHRASE | HTML_NLALL | HTML_NOINDENT}, + {"pre", HTML_TOPHRASE | HTML_NLAROUND | HTML_NOINDENT}, {"a", HTML_INPHRASE | HTML_TOPHRASE}, {"b", HTML_INPHRASE | HTML_TOPHRASE}, {"cite", HTML_INPHRASE | HTML_TOPHRASE}, @@ -88,7 +94,8 @@ static const struct htmldata htmltags[TAG_MAX] = { {"span", HTML_INPHRASE | HTML_TOPHRASE}, {"var", HTML_INPHRASE | HTML_TOPHRASE}, {"br", HTML_INPHRASE | HTML_NOSTACK | HTML_NLALL}, - {"mark", HTML_INPHRASE | HTML_NOSTACK }, + {"hr", HTML_INPHRASE | HTML_NOSTACK}, + {"mark", HTML_INPHRASE }, {"math", HTML_INPHRASE | HTML_NLALL | HTML_INDENT}, {"mrow", 0}, {"mi", 0}, @@ -109,6 +116,11 @@ static const struct htmldata htmltags[TAG_MAX] = { }; /* Avoid duplicate HTML id= attributes. */ + +struct id_entry { + int ord; /* Ordinal number of the latest occurrence. */ + char id[]; /* The id= attribute without any ordinal suffix. */ +}; static struct ohash id_unique; static void html_reset_internal(struct html *); @@ -132,6 +144,7 @@ html_alloc(const struct manoutput *outopts) h = mandoc_calloc(1, sizeof(struct html)); h->tag = NULL; + h->metac = h->metal = ESCAPE_FONTROMAN; h->style = outopts->style; if ((h->base_man1 = outopts->man) == NULL) h->base_man2 = NULL; @@ -143,7 +156,7 @@ html_alloc(const struct manoutput *outopts) if (outopts->toc) h->oflags |= HTML_TOC; - mandoc_ohash_init(&id_unique, 4, 0); + mandoc_ohash_init(&id_unique, 4, offsetof(struct id_entry, id)); return h; } @@ -152,17 +165,17 @@ static void html_reset_internal(struct html *h) { struct tag *tag; - char *cp; + struct id_entry *entry; unsigned int slot; while ((tag = h->tag) != NULL) { h->tag = tag->next; free(tag); } - cp = ohash_first(&id_unique, &slot); - while (cp != NULL) { - free(cp); - cp = ohash_next(&id_unique, &slot); + entry = ohash_first(&id_unique, &slot); + while (entry != NULL) { + free(entry); + entry = ohash_next(&id_unique, &slot); } ohash_delete(&id_unique); } @@ -171,7 +184,7 @@ void html_reset(void *p) { html_reset_internal(p); - mandoc_ohash_init(&id_unique, 4, 0); + mandoc_ohash_init(&id_unique, 4, offsetof(struct id_entry, id)); } void @@ -187,6 +200,8 @@ print_gen_head(struct html *h) struct tag *t; print_otag(h, TAG_META, "?", "charset", "utf-8"); + print_otag(h, TAG_META, "??", "name", "viewport", + "content", "width=device-width, initial-scale=1.0"); if (h->style != NULL) { print_otag(h, TAG_LINK, "?h??", "rel", "stylesheet", h->style, "type", "text/css", "media", "all"); @@ -229,8 +244,10 @@ html_setfont(struct html *h, enum mandoc_esc font) case ESCAPE_FONTITALIC: case ESCAPE_FONTBOLD: case ESCAPE_FONTBI: - case ESCAPE_FONTCW: case ESCAPE_FONTROMAN: + case ESCAPE_FONTCR: + case ESCAPE_FONTCB: + case ESCAPE_FONTCI: break; case ESCAPE_FONT: font = ESCAPE_FONTROMAN; @@ -261,9 +278,17 @@ print_metaf(struct html *h) h->metaf = print_otag(h, TAG_B, ""); print_otag(h, TAG_I, ""); break; - case ESCAPE_FONTCW: + case ESCAPE_FONTCR: h->metaf = print_otag(h, TAG_SPAN, "c", "Li"); break; + case ESCAPE_FONTCB: + h->metaf = print_otag(h, TAG_SPAN, "c", "Li"); + print_otag(h, TAG_B, ""); + break; + case ESCAPE_FONTCI: + h->metaf = print_otag(h, TAG_SPAN, "c", "Li"); + print_otag(h, TAG_I, ""); + break; default: break; } @@ -321,59 +346,91 @@ html_fillmode(struct html *h, enum roff_tok want) return had; } +/* + * Allocate a string to be used for the "id=" attribute of an HTML + * element and/or as a segment identifier for a URI in an element. + * The function may fail and return NULL if the node lacks text data + * to create the attribute from. + * The caller is responsible for free(3)ing the returned string. + * + * If the "unique" argument is non-zero, the "id_unique" ohash table + * is used for de-duplication. If the "unique" argument is 1, + * it is the first time the function is called for this tag and + * location, so if an ordinal suffix is needed, it is incremented. + * If the "unique" argument is 2, it is the second time the function + * is called for this tag and location, so the ordinal suffix + * remains unchanged. + */ char * html_make_id(const struct roff_node *n, int unique) { const struct roff_node *nch; - char *buf, *bufs, *cp; + struct id_entry *entry; + char *buf, *cp; + size_t len; unsigned int slot; - int suffix; - for (nch = n->child; nch != NULL; nch = nch->next) - if (nch->type != ROFFT_TEXT) - return NULL; + if (n->tag != NULL) + buf = mandoc_strdup(n->tag); + else { + switch (n->tok) { + case MDOC_Sh: + case MDOC_Ss: + case MDOC_Sx: + case MAN_SH: + case MAN_SS: + 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; + break; + default: + if (n->child == NULL || n->child->type != ROFFT_TEXT) + return NULL; + buf = mandoc_strdup(n->child->string); + break; + } + } - 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 + * In addition, reserve '~' for ordinal suffixes. */ - for (cp = buf; *cp != '\0'; cp++) - if (isalnum((unsigned char)*cp) == 0 && - strchr("!$&'()*+,-./:;=?@_~", *cp) == NULL) + for (cp = buf; *cp != '\0'; cp++) { + if (*cp == ASCII_HYPH) + *cp = '-'; + else 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; + if ((entry = ohash_find(&id_unique, slot)) == NULL) { + len = strlen(buf) + 1; + entry = mandoc_malloc(sizeof(*entry) + len); + entry->ord = 1; + memcpy(entry->id, buf, len); + ohash_insert(&id_unique, slot, entry); + } else if (unique == 1) + entry->ord++; + + if (entry->ord > 1) { + cp = buf; + mandoc_asprintf(&buf, "%s~%d", cp, entry->ord); + free(cp); } - ohash_insert(&id_unique, slot, buf); return buf; } @@ -463,8 +520,10 @@ print_encode(struct html *h, const char *p, const char case ESCAPE_FONTBOLD: case ESCAPE_FONTITALIC: case ESCAPE_FONTBI: - case ESCAPE_FONTCW: case ESCAPE_FONTROMAN: + case ESCAPE_FONTCR: + case ESCAPE_FONTCB: + case ESCAPE_FONTCI: if (0 == norecurse) { h->flags |= HTML_NOSPACE; if (html_setfont(h, esc)) @@ -655,6 +714,9 @@ print_otag(struct html *h, enum htmltag tag, const cha case 'i': attr = "id"; break; + case 'r': + attr = "role"; + break; case '?': attr = arg1; arg1 = va_arg(ap, char *); @@ -736,6 +798,49 @@ print_otag(struct html *h, enum htmltag tag, const cha return t; } +/* + * Print an element with an optional "id=" attribute. + * If the element has phrasing content and an "id=" attribute, + * also add a permalink: outside if it can be in phrasing context, + * inside otherwise. + */ +struct tag * +print_otag_id(struct html *h, enum htmltag elemtype, const char *cattr, + struct roff_node *n) +{ + struct roff_node *nch; + struct tag *ret, *t; + char *id, *href; + + ret = NULL; + id = href = NULL; + if (n->flags & NODE_ID) + id = html_make_id(n, 1); + if (n->flags & NODE_HREF) + href = id == NULL ? html_make_id(n, 2) : id; + if (href != NULL && htmltags[elemtype].flags & HTML_INPHRASE) + ret = print_otag(h, TAG_A, "chR", "permalink", href); + t = print_otag(h, elemtype, "ci", cattr, id); + if (ret == NULL) { + ret = t; + if (href != NULL && (nch = n->child) != NULL) { + /* man(7) is safe, it tags phrasing content only. */ + if (n->tok > MDOC_MAX || + htmltags[elemtype].flags & HTML_TOPHRASE) + nch = NULL; + else /* For mdoc(7), beware of nested blocks. */ + while (nch != NULL && nch->type == ROFFT_TEXT) + nch = nch->next; + if (nch == NULL) + print_otag(h, TAG_A, "chR", "permalink", href); + } + } + free(id); + if (id == NULL) + free(href); + return ret; +} + static void print_ctag(struct html *h, struct tag *tag) { @@ -805,6 +910,15 @@ print_gen_comment(struct html *h, struct roff_node *n) void print_text(struct html *h, const char *word) { + print_tagged_text(h, word, NULL); +} + +void +print_tagged_text(struct html *h, const char *word, struct roff_node *n) +{ + struct tag *t; + char *href; + /* * Always wrap text in a paragraph unless already contained in * some flow container; never put it directly into a section. @@ -825,13 +939,20 @@ print_text(struct html *h, const char *word) } /* - * Print the text, optionally surrounded by HTML whitespace, - * optionally manually switching fonts before and after. + * Optionally switch fonts, optionally write a permalink, then + * print the text, optionally surrounded by HTML whitespace. */ assert(h->metaf == NULL); print_metaf(h); print_indent(h); + + if (n != NULL && (href = html_make_id(n, 2)) != NULL) { + t = print_otag(h, TAG_A, "chR", "permalink", href); + free(href); + } else + t = NULL; + if ( ! print_encode(h, word, NULL, 0)) { if ( ! (h->flags & HTML_NONOSPACE)) h->flags &= ~HTML_NOSPACE; @@ -842,7 +963,8 @@ print_text(struct html *h, const char *word) if (h->metaf != NULL) { print_tagq(h, h->metaf); h->metaf = NULL; - } + } else if (t != NULL) + print_tagq(h, t); h->flags &= ~HTML_IGNDELIM; }