]> git.cameronkatri.com Git - mandoc.git/blobdiff - tag.c
we already parse the GNU tbl(7) "nospaces" option,
[mandoc.git] / tag.c
diff --git a/tag.c b/tag.c
index 14327415dee4260b3ea3edae5d46ef387e7ef024..fcaad99f2e2ea050521e73096a6e69aefb6979f6 100644 (file)
--- a/tag.c
+++ b/tag.c
@@ -1,4 +1,4 @@
-/* $Id: tag.c,v 1.30 2020/03/21 00:17:31 schwarze Exp $ */
+/* $Id: tag.c,v 1.36 2020/04/19 16:36:16 schwarze Exp $ */
 /*
  * Copyright (c) 2015,2016,2018,2019,2020 Ingo Schwarze <schwarze@openbsd.org>
  *
 #include <assert.h>
 #include <limits.h>
 #include <stddef.h>
+#include <stdint.h>
 #include <stdlib.h>
 #include <string.h>
 
 #include "mandoc_aux.h"
 #include "mandoc_ohash.h"
 #include "roff.h"
+#include "mdoc.h"
+#include "roff_int.h"
 #include "tag.h"
 
 struct tag_entry {
@@ -40,6 +43,10 @@ struct tag_entry {
        char     s[];
 };
 
+static void             tag_move_href(struct roff_man *,
+                               struct roff_node *, const char *);
+static void             tag_move_id(struct roff_node *);
+
 static struct ohash     tag_data;
 
 
@@ -79,6 +86,7 @@ void
 tag_put(const char *s, int prio, struct roff_node *n)
 {
        struct tag_entry        *entry;
+       struct roff_node        *nold;
        const char              *se;
        size_t                   len;
        unsigned int             slot;
@@ -151,9 +159,12 @@ tag_put(const char *s, int prio, struct roff_node *n)
         */
 
        else if (entry->prio > prio || prio == TAG_FALLBACK) {
-               while (entry->nnodes > 0)
-                       entry->nodes[--entry->nnodes]->flags &= ~NODE_ID;
-
+               while (entry->nnodes > 0) {
+                       nold = entry->nodes[--entry->nnodes];
+                       nold->flags &= ~NODE_ID;
+                       free(nold->tag);
+                       nold->tag = NULL;
+               }
                if (prio == TAG_FALLBACK) {
                        entry->prio = TAG_DELETE;
                        return;
@@ -171,21 +182,146 @@ tag_put(const char *s, int prio, struct roff_node *n)
        entry->prio = prio;
        n->flags |= NODE_ID;
        if (n->child == NULL || n->child->string != s || *se != '\0') {
-               assert(n->string == NULL);
-               n->string = mandoc_strndup(s, len);
+               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';
        }
+
+       assert(n->tag == NULL);
+       n->tag = mandoc_strdup(tag);
+       n->flags |= NODE_HREF;
 }
 
-enum tag_result
-tag_check(const char *test_tag)
+/*
+ * When all tags have been set, decide where to put
+ * the associated permalinks, and maybe move some tags
+ * to the beginning of the respective paragraphs.
+ */
+void
+tag_postprocess(struct roff_man *man, struct roff_node *n)
 {
-       unsigned int slot;
-
-       if (ohash_first(&tag_data, &slot) == NULL)
-               return TAG_EMPTY;
-       else if (test_tag != NULL && ohash_find(&tag_data,
-           ohash_qlookup(&tag_data, test_tag)) == NULL)
-               return TAG_MISS;
-       else
-               return TAG_OK;
+       if (n->flags & NODE_ID) {
+               switch (n->tok) {
+               case MDOC_Pp:
+                       tag_move_href(man, n->next, n->tag);
+                       break;
+               case MDOC_Bd:
+               case MDOC_D1:
+               case MDOC_Dl:
+                       tag_move_href(man, n->child, n->tag);
+                       break;
+               case MDOC_Bl:
+                       /* XXX No permalink for now. */
+                       break;
+               default:
+                       if (n->type == ROFFT_ELEM || n->tok == MDOC_Fo)
+                               tag_move_id(n);
+                       if (n->tok != MDOC_Tg)
+                               n->flags |= NODE_HREF;
+                       else if ((n->flags & NODE_ID) == 0) {
+                               n->flags |= NODE_NOPRT;
+                               free(n->tag);
+                               n->tag = NULL;
+                       }
+                       break;
+               }
+       }
+       for (n = n->child; n != NULL; n = n->next)
+               tag_postprocess(man, n);
 }