]> git.cameronkatri.com Git - mandoc.git/blob - tag.c
Delete the statement that the default stylesheet only used CSS1
[mandoc.git] / tag.c
1 /* $Id: tag.c,v 1.37 2022/04/26 11:38:38 schwarze Exp $ */
2 /*
3 * Copyright (c) 2015, 2016, 2018, 2019, 2020, 2022
4 * Ingo Schwarze <schwarze@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
25 #include <assert.h>
26 #include <limits.h>
27 #include <stddef.h>
28 #include <stdint.h>
29 #include <stdlib.h>
30 #include <string.h>
31
32 #include "mandoc_aux.h"
33 #include "mandoc_ohash.h"
34 #include "roff.h"
35 #include "mdoc.h"
36 #include "roff_int.h"
37 #include "tag.h"
38
39 struct tag_entry {
40 struct roff_node **nodes;
41 size_t maxnodes;
42 size_t nnodes;
43 int prio;
44 char s[];
45 };
46
47 static void tag_move_href(struct roff_man *,
48 struct roff_node *, const char *);
49 static void tag_move_id(struct roff_node *);
50
51 static struct ohash tag_data;
52
53
54 /*
55 * Set up the ohash table to collect nodes
56 * where various marked-up terms are documented.
57 */
58 void
59 tag_alloc(void)
60 {
61 mandoc_ohash_init(&tag_data, 4, offsetof(struct tag_entry, s));
62 }
63
64 void
65 tag_free(void)
66 {
67 struct tag_entry *entry;
68 unsigned int slot;
69
70 if (tag_data.info.free == NULL)
71 return;
72 entry = ohash_first(&tag_data, &slot);
73 while (entry != NULL) {
74 free(entry->nodes);
75 free(entry);
76 entry = ohash_next(&tag_data, &slot);
77 }
78 ohash_delete(&tag_data);
79 tag_data.info.free = NULL;
80 }
81
82 /*
83 * Set a node where a term is defined,
84 * unless the term is already defined at a lower priority.
85 */
86 void
87 tag_put(const char *s, int prio, struct roff_node *n)
88 {
89 struct tag_entry *entry;
90 struct roff_node *nold;
91 const char *se;
92 size_t len;
93 unsigned int slot;
94
95 assert(prio <= TAG_FALLBACK);
96
97 /*
98 * If the node is already tagged, the existing tag is
99 * explicit and we are now about to add an implicit tag.
100 * Don't do that; just skip implicit tagging if the author
101 * specified an explicit tag.
102 */
103
104 if (n->flags & NODE_ID)
105 return;
106
107 /* Determine the implicit tag. */
108
109 if (s == NULL) {
110 if (n->child == NULL || n->child->type != ROFFT_TEXT)
111 return;
112 s = n->child->string;
113 switch (s[0]) {
114 case '-':
115 s++;
116 break;
117 case '\\':
118 switch (s[1]) {
119 case '&':
120 case '-':
121 case 'e':
122 s += 2;
123 break;
124 default:
125 break;
126 }
127 break;
128 default:
129 break;
130 }
131 }
132
133 /*
134 * Skip whitespace and escapes and whatever follows,
135 * and if there is any, downgrade the priority.
136 */
137
138 len = strcspn(s, " \t\\");
139 if (len == 0)
140 return;
141
142 se = s + len;
143 if (*se != '\0' && prio < TAG_WEAK)
144 prio = TAG_WEAK;
145
146 slot = ohash_qlookupi(&tag_data, s, &se);
147 entry = ohash_find(&tag_data, slot);
148
149 /* Build a new entry. */
150
151 if (entry == NULL) {
152 entry = mandoc_malloc(sizeof(*entry) + len + 1);
153 memcpy(entry->s, s, len);
154 entry->s[len] = '\0';
155 entry->nodes = NULL;
156 entry->maxnodes = entry->nnodes = 0;
157 ohash_insert(&tag_data, slot, entry);
158 }
159
160 /*
161 * Lower priority numbers take precedence.
162 * If a better entry is already present, ignore the new one.
163 */
164
165 else if (entry->prio < prio)
166 return;
167
168 /*
169 * If the existing entry is worse, clear it.
170 * In addition, a tag with priority TAG_FALLBACK
171 * is only used if the tag occurs exactly once.
172 */
173
174 else if (entry->prio > prio || prio == TAG_FALLBACK) {
175 while (entry->nnodes > 0) {
176 nold = entry->nodes[--entry->nnodes];
177 nold->flags &= ~NODE_ID;
178 free(nold->tag);
179 nold->tag = NULL;
180 }
181 if (prio == TAG_FALLBACK) {
182 entry->prio = TAG_DELETE;
183 return;
184 }
185 }
186
187 /* Remember the new node. */
188
189 if (entry->maxnodes == entry->nnodes) {
190 entry->maxnodes += 4;
191 entry->nodes = mandoc_reallocarray(entry->nodes,
192 entry->maxnodes, sizeof(*entry->nodes));
193 }
194 entry->nodes[entry->nnodes++] = n;
195 entry->prio = prio;
196 n->flags |= NODE_ID;
197 if (n->child == NULL || n->child->string != s || *se != '\0') {
198 assert(n->tag == NULL);
199 n->tag = mandoc_strndup(s, len);
200 }
201 }
202
203 int
204 tag_exists(const char *tag)
205 {
206 return ohash_find(&tag_data, ohash_qlookup(&tag_data, tag)) != NULL;
207 }
208
209 /*
210 * For in-line elements, move the link target
211 * to the enclosing paragraph when appropriate.
212 */
213 static void
214 tag_move_id(struct roff_node *n)
215 {
216 struct roff_node *np;
217
218 np = n;
219 for (;;) {
220 if (np->prev != NULL)
221 np = np->prev;
222 else if ((np = np->parent) == NULL)
223 return;
224 switch (np->tok) {
225 case MDOC_It:
226 switch (np->parent->parent->norm->Bl.type) {
227 case LIST_column:
228 /* Target the ROFFT_BLOCK = <tr>. */
229 np = np->parent;
230 break;
231 case LIST_diag:
232 case LIST_hang:
233 case LIST_inset:
234 case LIST_ohang:
235 case LIST_tag:
236 /* Target the ROFFT_HEAD = <dt>. */
237 np = np->parent->head;
238 break;
239 default:
240 /* Target the ROFF_BODY = <li>. */
241 break;
242 }
243 /* FALLTHROUGH */
244 case MDOC_Pp: /* Target the ROFFT_ELEM = <p>. */
245 if (np->tag == NULL) {
246 np->tag = mandoc_strdup(n->tag == NULL ?
247 n->child->string : n->tag);
248 np->flags |= NODE_ID;
249 n->flags &= ~NODE_ID;
250 }
251 return;
252 case MDOC_Sh:
253 case MDOC_Ss:
254 case MDOC_Bd:
255 case MDOC_Bl:
256 case MDOC_D1:
257 case MDOC_Dl:
258 case MDOC_Rs:
259 /* Do not move past major blocks. */
260 return;
261 default:
262 /*
263 * Move past in-line content and partial
264 * blocks, for example .It Xo or .It Bq Er.
265 */
266 break;
267 }
268 }
269 }
270
271 /*
272 * When a paragraph is tagged and starts with text,
273 * move the permalink to the first few words.
274 */
275 static void
276 tag_move_href(struct roff_man *man, struct roff_node *n, const char *tag)
277 {
278 char *cp;
279
280 if (n == NULL || n->type != ROFFT_TEXT ||
281 *n->string == '\0' || *n->string == ' ')
282 return;
283
284 cp = n->string;
285 while (cp != NULL && cp - n->string < 5)
286 cp = strchr(cp + 1, ' ');
287
288 /* If the first text node is longer, split it. */
289
290 if (cp != NULL && cp[1] != '\0') {
291 man->last = n;
292 man->next = ROFF_NEXT_SIBLING;
293 roff_word_alloc(man, n->line,
294 n->pos + (cp - n->string), cp + 1);
295 man->last->flags = n->flags & ~NODE_LINE;
296 *cp = '\0';
297 }
298
299 assert(n->tag == NULL);
300 n->tag = mandoc_strdup(tag);
301 n->flags |= NODE_HREF;
302 }
303
304 /*
305 * When all tags have been set, decide where to put
306 * the associated permalinks, and maybe move some tags
307 * to the beginning of the respective paragraphs.
308 */
309 void
310 tag_postprocess(struct roff_man *man, struct roff_node *n)
311 {
312 if (n->flags & NODE_ID) {
313 switch (n->tok) {
314 case MDOC_Pp:
315 tag_move_href(man, n->next, n->tag);
316 break;
317 case MDOC_Bd:
318 case MDOC_D1:
319 case MDOC_Dl:
320 tag_move_href(man, n->child, n->tag);
321 break;
322 case MDOC_Bl:
323 /* XXX No permalink for now. */
324 break;
325 default:
326 if (n->type == ROFFT_ELEM || n->tok == MDOC_Fo)
327 tag_move_id(n);
328 if (n->tok != MDOC_Tg)
329 n->flags |= NODE_HREF;
330 else if ((n->flags & NODE_ID) == 0) {
331 n->flags |= NODE_NOPRT;
332 free(n->tag);
333 n->tag = NULL;
334 }
335 break;
336 }
337 }
338 for (n = n->child; n != NULL; n = n->next)
339 tag_postprocess(man, n);
340 }