+ assert(n->tag == NULL);
+ n->tag = mandoc_strndup(s, len);
+ }
+}
+
+int
+tag_exists(const char *tag)
+{
+ return ohash_find(&tag_data, ohash_qlookup(&tag_data, tag)) != NULL;
+}
+
+/*
+ * For in-line elements, move the link target
+ * to the enclosing paragraph when appropriate.
+ */
+static void
+tag_move_id(struct roff_node *n)
+{
+ struct roff_node *np;
+
+ np = n;
+ for (;;) {
+ if (np->prev != NULL)
+ np = np->prev;
+ else if ((np = np->parent) == NULL)
+ return;
+ switch (np->tok) {
+ case MDOC_It:
+ switch (np->parent->parent->norm->Bl.type) {
+ case LIST_column:
+ /* Target the ROFFT_BLOCK = <tr>. */
+ np = np->parent;
+ break;
+ case LIST_diag:
+ case LIST_hang:
+ case LIST_inset:
+ case LIST_ohang:
+ case LIST_tag:
+ /* Target the ROFFT_HEAD = <dt>. */
+ np = np->parent->head;
+ break;
+ default:
+ /* Target the ROFF_BODY = <li>. */
+ break;
+ }
+ /* FALLTHROUGH */
+ case MDOC_Pp: /* Target the ROFFT_ELEM = <p>. */
+ if (np->tag == NULL) {
+ np->tag = mandoc_strdup(n->tag == NULL ?
+ n->child->string : n->tag);
+ np->flags |= NODE_ID;
+ n->flags &= ~NODE_ID;
+ }
+ return;
+ case MDOC_Sh:
+ case MDOC_Ss:
+ case MDOC_Bd:
+ case MDOC_Bl:
+ case MDOC_D1:
+ case MDOC_Dl:
+ case MDOC_Rs:
+ /* Do not move past major blocks. */
+ return;
+ default:
+ /*
+ * Move past in-line content and partial
+ * blocks, for example .It Xo or .It Bq Er.
+ */
+ break;
+ }
+ }
+}
+
+/*
+ * When a paragraph is tagged and starts with text,
+ * move the permalink to the first few words.
+ */
+static void
+tag_move_href(struct roff_man *man, struct roff_node *n, const char *tag)
+{
+ char *cp;
+
+ if (n == NULL || n->type != ROFFT_TEXT ||
+ *n->string == '\0' || *n->string == ' ')
+ return;
+
+ cp = n->string;
+ while (cp != NULL && cp - n->string < 5)
+ cp = strchr(cp + 1, ' ');
+
+ /* If the first text node is longer, split it. */
+
+ if (cp != NULL && cp[1] != '\0') {
+ man->last = n;
+ man->next = ROFF_NEXT_SIBLING;
+ roff_word_alloc(man, n->line,
+ n->pos + (cp - n->string), cp + 1);
+ man->last->flags = n->flags & ~NODE_LINE;
+ *cp = '\0';