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