Home | History | Annotate | Line # | Download | only in dist
      1 /* Id: tag.c,v 1.36 2020/04/19 16:36:16 schwarze Exp  */
      2 /*
      3  * Copyright (c) 2015, 2016, 2018, 2019, 2020, 2022, 2023
      4  *               Ingo Schwarze <schwarze (at) openbsd.org>
      5  *
      6  * Permission to use, copy, modify, and distribute this software for any
      7  * purpose with or without fee is hereby granted, provided that the above
      8  * copyright notice and this permission notice appear in all copies.
      9  *
     10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     17  *
     18  * Functions to tag syntax tree nodes.
     19  * For internal use by mandoc(1) validation modules only.
     20  */
     21 #include "config.h"
     22 
     23 #include <sys/types.h>
     24 #include <sys/stat.h>
     25 
     26 #include <assert.h>
     27 #include <limits.h>
     28 #include <stddef.h>
     29 #include <stdint.h>
     30 #include <stdio.h>
     31 #include <stdlib.h>
     32 #include <string.h>
     33 
     34 #include "mandoc_aux.h"
     35 #include "mandoc_ohash.h"
     36 #include "mandoc.h"
     37 #include "roff.h"
     38 #include "mdoc.h"
     39 #include "roff_int.h"
     40 #include "tag.h"
     41 
     42 struct tag_entry {
     43 	struct roff_node **nodes;
     44 	size_t	 maxnodes;
     45 	size_t	 nnodes;
     46 	int	 prio;
     47 	char	 s[];
     48 };
     49 
     50 static void		 tag_move_href(struct roff_man *,
     51 				struct roff_node *, const char *);
     52 static void		 tag_move_id(struct roff_node *);
     53 
     54 static struct ohash	 tag_data;
     55 
     56 
     57 /*
     58  * Set up the ohash table to collect nodes
     59  * where various marked-up terms are documented.
     60  */
     61 void
     62 tag_alloc(void)
     63 {
     64 	mandoc_ohash_init(&tag_data, 4, offsetof(struct tag_entry, s));
     65 }
     66 
     67 void
     68 tag_free(void)
     69 {
     70 	struct tag_entry	*entry;
     71 	unsigned int		 slot;
     72 
     73 	if (tag_data.info.free == NULL)
     74 		return;
     75 	entry = ohash_first(&tag_data, &slot);
     76 	while (entry != NULL) {
     77 		free(entry->nodes);
     78 		free(entry);
     79 		entry = ohash_next(&tag_data, &slot);
     80 	}
     81 	ohash_delete(&tag_data);
     82 	tag_data.info.free = NULL;
     83 }
     84 
     85 /*
     86  * Set a node where a term is defined,
     87  * unless the term is already defined at a lower priority.
     88  */
     89 void
     90 tag_put(const char *s, int prio, struct roff_node *n)
     91 {
     92 	struct tag_entry	*entry;
     93 	struct roff_node	*nold;
     94 	const char		*se, *src;
     95 	char			*cpy;
     96 	size_t			 len;
     97 	unsigned int		 slot;
     98 	int			 changed;
     99 
    100 	assert(prio <= TAG_FALLBACK);
    101 
    102 	/*
    103 	 * If the node is already tagged, the existing tag is
    104 	 * explicit and we are now about to add an implicit tag.
    105 	 * Don't do that; just skip implicit tagging if the author
    106 	 * specified an explicit tag.
    107 	 */
    108 
    109 	if (n->flags & NODE_ID)
    110 		return;
    111 
    112 	/* Determine the implicit tag. */
    113 
    114 	changed = 1;
    115 	if (s == NULL) {
    116 		if (n->child == NULL || n->child->type != ROFFT_TEXT)
    117 			return;
    118 		s = n->child->string;
    119 		switch (s[0]) {
    120 		case '-':
    121 			s++;
    122 			break;
    123 		case '\\':
    124 			switch (s[1]) {
    125 			case '&':
    126 			case '-':
    127 			case 'e':
    128 				s += 2;
    129 				break;
    130 			default:
    131 				return;
    132 			}
    133 			break;
    134 		default:
    135 			changed = 0;
    136 			break;
    137 		}
    138 	}
    139 
    140 	/*
    141 	 * Translate \- and ASCII_HYPH to plain '-'.
    142 	 * Skip whitespace and escapes and whatever follows,
    143 	 * and if there is any, downgrade the priority.
    144 	 */
    145 
    146 	cpy = mandoc_malloc(strlen(s) + 1);
    147 	for (src = s, len = 0; *src != '\0'; src++, len++) {
    148 		switch (*src) {
    149 		case '\t':
    150 		case ' ':
    151 			changed = 1;
    152 			break;
    153 		case ASCII_HYPH:
    154 			cpy[len] = '-';
    155 			changed = 1;
    156 			continue;
    157 		case '\\':
    158 			if (src[1] != '-')
    159 				break;
    160 			src++;
    161 			changed = 1;
    162 			/* FALLTHROUGH */
    163 		default:
    164 			cpy[len] = *src;
    165 			continue;
    166 		}
    167 		break;
    168 	}
    169 	if (len == 0)
    170 		goto out;
    171 	cpy[len] = '\0';
    172 
    173 	if (*src != '\0' && prio < TAG_WEAK)
    174 		prio = TAG_WEAK;
    175 
    176 	s = cpy;
    177 	se = cpy + len;
    178 	slot = ohash_qlookupi(&tag_data, s, &se);
    179 	entry = ohash_find(&tag_data, slot);
    180 
    181 	/* Build a new entry. */
    182 
    183 	if (entry == NULL) {
    184 		entry = mandoc_malloc(sizeof(*entry) + len + 1);
    185 		memcpy(entry->s, s, len + 1);
    186 		entry->nodes = NULL;
    187 		entry->maxnodes = entry->nnodes = 0;
    188 		ohash_insert(&tag_data, slot, entry);
    189 	}
    190 
    191 	/*
    192 	 * Lower priority numbers take precedence.
    193 	 * If a better entry is already present, ignore the new one.
    194 	 */
    195 
    196 	else if (entry->prio < prio)
    197 		goto out;
    198 
    199 	/*
    200 	 * If the existing entry is worse, clear it.
    201 	 * In addition, a tag with priority TAG_FALLBACK
    202 	 * is only used if the tag occurs exactly once.
    203 	 */
    204 
    205 	else if (entry->prio > prio || prio == TAG_FALLBACK) {
    206 		while (entry->nnodes > 0) {
    207 			nold = entry->nodes[--entry->nnodes];
    208 			nold->flags &= ~NODE_ID;
    209 			free(nold->tag);
    210 			nold->tag = NULL;
    211 		}
    212 		if (prio == TAG_FALLBACK) {
    213 			entry->prio = TAG_DELETE;
    214 			goto out;
    215 		}
    216 	}
    217 
    218 	/* Remember the new node. */
    219 
    220 	if (entry->maxnodes == entry->nnodes) {
    221 		entry->maxnodes += 4;
    222 		entry->nodes = mandoc_reallocarray(entry->nodes,
    223 		    entry->maxnodes, sizeof(*entry->nodes));
    224 	}
    225 	entry->nodes[entry->nnodes++] = n;
    226 	entry->prio = prio;
    227 	n->flags |= NODE_ID;
    228 	if (changed) {
    229 		assert(n->tag == NULL);
    230 		n->tag = mandoc_strndup(s, len);
    231 	}
    232 
    233  out:
    234 	free(cpy);
    235 }
    236 
    237 int
    238 tag_exists(const char *tag)
    239 {
    240 	return ohash_find(&tag_data, ohash_qlookup(&tag_data, tag)) != NULL;
    241 }
    242 
    243 /*
    244  * For in-line elements, move the link target
    245  * to the enclosing paragraph when appropriate.
    246  */
    247 static void
    248 tag_move_id(struct roff_node *n)
    249 {
    250 	struct roff_node *np;
    251 
    252 	np = n;
    253 	for (;;) {
    254 		if (np->prev != NULL)
    255 			np = np->prev;
    256 		else if ((np = np->parent) == NULL)
    257 			return;
    258 		switch (np->tok) {
    259 		case MDOC_It:
    260 			switch (np->parent->parent->norm->Bl.type) {
    261 			case LIST_column:
    262 				/* Target the ROFFT_BLOCK = <tr>. */
    263 				np = np->parent;
    264 				break;
    265 			case LIST_diag:
    266 			case LIST_hang:
    267 			case LIST_inset:
    268 			case LIST_ohang:
    269 			case LIST_tag:
    270 				/* Target the ROFFT_HEAD = <dt>. */
    271 				np = np->parent->head;
    272 				break;
    273 			default:
    274 				/* Target the ROFF_BODY = <li>. */
    275 				break;
    276 			}
    277 			/* FALLTHROUGH */
    278 		case MDOC_Pp:	/* Target the ROFFT_ELEM = <p>. */
    279 			if (np->tag == NULL) {
    280 				np->tag = mandoc_strdup(n->tag == NULL ?
    281 				    n->child->string : n->tag);
    282 				np->flags |= NODE_ID;
    283 				n->flags &= ~NODE_ID;
    284 			}
    285 			return;
    286 		case MDOC_Sh:
    287 		case MDOC_Ss:
    288 		case MDOC_Bd:
    289 		case MDOC_Bl:
    290 		case MDOC_D1:
    291 		case MDOC_Dl:
    292 		case MDOC_Rs:
    293 			/* Do not move past major blocks. */
    294 			return;
    295 		default:
    296 			/*
    297 			 * Move past in-line content and partial
    298 			 * blocks, for example .It Xo or .It Bq Er.
    299 			 */
    300 			break;
    301 		}
    302 	}
    303 }
    304 
    305 /*
    306  * When a paragraph is tagged and starts with text,
    307  * move the permalink to the first few words.
    308  */
    309 static void
    310 tag_move_href(struct roff_man *man, struct roff_node *n, const char *tag)
    311 {
    312 	char	*cp;
    313 
    314 	if (n == NULL || n->type != ROFFT_TEXT ||
    315 	    *n->string == '\0' || *n->string == ' ')
    316 		return;
    317 
    318 	cp = n->string;
    319 	while (cp != NULL && cp - n->string < 5)
    320 		cp = strchr(cp + 1, ' ');
    321 
    322 	/* If the first text node is longer, split it. */
    323 
    324 	if (cp != NULL && cp[1] != '\0') {
    325 		man->last = n;
    326 		man->next = ROFF_NEXT_SIBLING;
    327 		roff_word_alloc(man, n->line,
    328 		    n->pos + (cp - n->string), cp + 1);
    329 		man->last->flags = n->flags & ~NODE_LINE;
    330 		*cp = '\0';
    331 	}
    332 
    333 	assert(n->tag == NULL);
    334 	n->tag = mandoc_strdup(tag);
    335 	n->flags |= NODE_HREF;
    336 }
    337 
    338 /*
    339  * When all tags have been set, decide where to put
    340  * the associated permalinks, and maybe move some tags
    341  * to the beginning of the respective paragraphs.
    342  */
    343 void
    344 tag_postprocess(struct roff_man *man, struct roff_node *n)
    345 {
    346 	if (n->flags & NODE_ID) {
    347 		switch (n->tok) {
    348 		case MDOC_Pp:
    349 			tag_move_href(man, n->next, n->tag);
    350 			break;
    351 		case MDOC_Bd:
    352 		case MDOC_D1:
    353 		case MDOC_Dl:
    354 			tag_move_href(man, n->child, n->tag);
    355 			break;
    356 		case MDOC_Bl:
    357 			/* XXX No permalink for now. */
    358 			break;
    359 		default:
    360 			if (n->type == ROFFT_ELEM || n->tok == MDOC_Fo)
    361 				tag_move_id(n);
    362 			if (n->tok != MDOC_Tg)
    363 				n->flags |= NODE_HREF;
    364 			else if ((n->flags & NODE_ID) == 0) {
    365 				n->flags |= NODE_NOPRT;
    366 				free(n->tag);
    367 				n->tag = NULL;
    368 			}
    369 			break;
    370 		}
    371 	}
    372 	for (n = n->child; n != NULL; n = n->next)
    373 		tag_postprocess(man, n);
    374 }
    375