=================================================================== RCS file: /cvs/mandoc/html.c,v retrieving revision 1.266 retrieving revision 1.270 diff -u -p -r1.266 -r1.270 --- mandoc/html.c 2020/04/07 22:56:02 1.266 +++ mandoc/html.c 2020/04/20 13:07:24 1.270 @@ -1,4 +1,4 @@ -/* $Id: html.c,v 1.266 2020/04/07 22:56:02 schwarze Exp $ */ +/* $Id: html.c,v 1.270 2020/04/20 13:07:24 schwarze Exp $ */ /* * Copyright (c) 2011-2015, 2017-2020 Ingo Schwarze * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons @@ -112,6 +112,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 *); @@ -146,7 +151,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; } @@ -155,17 +160,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); } @@ -174,7 +179,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 @@ -329,23 +334,27 @@ html_fillmode(struct html *h, enum roff_tok want) * 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. - * If the "unique" argument is 0, the caller is responsible for - * free(3)ing the returned string after using it. + * 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 and owns the returned string, so the - * caller must not free(3) it. In this case, it will be freed - * automatically by html_reset() or html_free(). + * 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; - if (n->string != NULL) - buf = mandoc_strdup(n->string); + if (n->tag != NULL) + buf = mandoc_strdup(n->tag); else { switch (n->tok) { case MDOC_Sh: @@ -374,11 +383,12 @@ html_make_id(const struct roff_node *n, int unique) * 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) + strchr("!$&'()*+,-./:;=?@_", *cp) == NULL) *cp = '_'; if (unique == 0) @@ -386,25 +396,21 @@ html_make_id(const struct roff_node *n, int unique) /* 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; } @@ -786,7 +792,7 @@ print_otag_id(struct html *h, enum htmltag elemtype, c if (n->flags & NODE_ID) id = html_make_id(n, 1); if (n->flags & NODE_HREF) - href = id == NULL ? html_make_id(n, 0) : id; + 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); @@ -804,6 +810,7 @@ print_otag_id(struct html *h, enum htmltag elemtype, c print_otag(h, TAG_A, "chR", "permalink", href); } } + free(id); if (id == NULL) free(href); return ret; @@ -878,6 +885,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. @@ -898,13 +914,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; @@ -915,7 +938,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; }