1 /* tag.c -- Functions to handle Info tags (that is, the special
2 construct for images, not the "tag table" of starting position.)
3
4 Copyright 2012-2023 Free Software Foundation, Inc.
5
6 This program is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program. If not, see <http://www.gnu.org/licenses/>. */
18
19 #include "info.h"
20 #include "tag.h"
21 #include "scan.h"
22 #include "util.h"
23
24 struct tag_handler
25 {
26 const char *name;
27 size_t len;
28 int (*handler) (char *, struct text_buffer *);
29 };
30
31 struct info_tag
32 {
33 struct info_tag *next;
34 char *kw;
35 char *val;
36 };
37
38 static void
39 info_tag_free (struct info_tag *tag)
40 {
41 while (tag)
42 {
43 struct info_tag *next = tag->next;
44 free (tag->kw);
45 free (tag->val);
46 free (tag);
47 tag = next;
48 }
49 }
50
51
52 /* See if KW is one of the tags in the list starting at TAG. */
53
54 static struct info_tag *
55 info_tag_find (struct info_tag *tag, const char *kw)
56 {
57 for (; tag; tag = tag->next)
58 if (strcmp (tag->kw, kw) == 0)
59 return tag;
60 return NULL;
61 }
62
63
64 /* Found a keyword when parsing the full tag string: alt, text, etc.
65 Return the new tag, update *TMPBUF_PTR and set *KW. */
66
67 static struct info_tag *
68 tag_found_keyword (struct text_buffer *tmpbuf_ptr, char **kw)
69 {
70 struct info_tag *tag = xmalloc (sizeof (*tag));
71 tag->next = NULL; /* have to update in caller */
72
73 text_buffer_add_char (tmpbuf_ptr, 0);
74 if (*kw != tmpbuf_ptr->base) { /* in case tmpbuf got realloc-ed */
75 *kw = tmpbuf_ptr->base; /* ick */
76 }
77 tag->kw = xstrdup (*kw);
78 tag->val = xstrdup (*kw + strlen(*kw) + 1);
79 text_buffer_reset (tmpbuf_ptr);
80
81 return tag;
82 }
83
84 /* Handle the image tag. */
85
86 static int
87 tag_image (char *text, struct text_buffer *outbuf)
88 {
89 mbi_iterator_t iter;
90 enum { state_kw, state_val, state_qstr, state_delim } state = state_kw;
91 struct text_buffer tmpbuf;
92 char *kw;
93 struct info_tag *tag_head = NULL, *tag;
94 int escaped = 0;
95
96 text_buffer_init (&tmpbuf);
97 for (mbi_init (iter, text, strlen (text)); mbi_avail (iter);
98 mbi_advance (iter))
99 {
100 const char *cur_ptr;
101 size_t cur_len;
102
103 if (mb_isspace (mbi_cur (iter)))
104 {
105 if (state == state_val)
106 {
107 struct info_tag *new_kw = tag_found_keyword (&tmpbuf, &kw);
108 new_kw->next = tag_head;
109 tag_head = new_kw;
110 state = state_delim;
111 continue;
112 }
113 if (state == state_delim)
114 continue;
115 }
116 else if (state == state_delim)
117 state = state_kw;
118 cur_len = mb_len (mbi_cur (iter));
119 cur_ptr = mbi_cur_ptr (iter);
120
121 if (state == state_qstr && escaped)
122 {
123 escaped = 0;
124 }
125 else if (cur_len == 1)
126 {
127 switch (*cur_ptr)
128 {
129 case '=':
130 if (state != state_kw)
131 break;
132 text_buffer_add_char (&tmpbuf, 0);
133 kw = tmpbuf.base;
134 if (!mbi_avail (iter))
135 break;
136 mbi_advance (iter);
137 state = state_val;
138 cur_len = mb_len (mbi_cur (iter));
139 cur_ptr = mbi_cur_ptr (iter);
140 if (!(cur_len == 1 && *cur_ptr == '"'))
141 break;
142 /* fall through */
143
144 case '"':
145 if (state == state_val)
146 {
147 state = state_qstr;
148 continue;
149 }
150 if (state == state_qstr)
151 {
152 struct info_tag *new_kw = tag_found_keyword (&tmpbuf, &kw);
153 new_kw->next = tag_head;
154 tag_head = new_kw;
155 state = state_delim;
156 continue;
157 }
158 break;
159
160 case '\\':
161 if (state == state_qstr)
162 {
163 escaped = 1;
164 continue;
165 }
166 }
167 }
168 text_buffer_add_string (&tmpbuf, cur_ptr, cur_len);
169 }
170
171 tag = info_tag_find (tag_head, "text");
172 if (!tag)
173 tag = info_tag_find (tag_head, "alt");
174
175 if (tag)
176 {
177 text_buffer_add_string (outbuf, tag->val, strlen (tag->val));
178 }
179
180 text_buffer_free (&tmpbuf);
181 info_tag_free (tag_head);
182 return 0;
183 }
184
185
186 /* We don't do anything with the index tag; it'll just be ignored. */
187
188 static struct tag_handler tagtab[] = {
189 { "image", 5, tag_image },
190 { "index", 5, NULL },
191 { NULL }
192 };
193
194 static struct tag_handler *
195 find_tag_handler (char *tag, size_t taglen)
196 {
197 struct tag_handler *tp;
198
199 for (tp = tagtab; tp->name; tp++)
200 if (taglen >= tp->len && strncmp (tp->name, tag, tp->len) == 0)
201 return tp;
202 return NULL;
203 }
204
205 /* Expand \b[...\b] construct at *INPUT. If encountered, append the
206 expanded text to OUTBUF, advance *INPUT past the tag, and return 1.
207 Otherwise, return 0. If it is an index tag, set IS_INDEX to 1.
208 *INPUT points into a null-terminated area which may however contain other
209 null characters. INPUT_END points to the end of this area. */
210 int
211 tag_expand (char **input, char *input_end,
212 struct text_buffer *outbuf, int *is_index)
213 {
214 char *p = *input;
215 char *q;
216 size_t len;
217 struct tag_handler *tp;
218
219 if (p >= input_end - 3
220 || memcmp(p, "\0\b[", 3) != 0) /* opening magic? */
221 return 0;
222
223 p += 3;
224 q = p + strlen (p);
225 if (q >= input_end - 3
226 || memcmp (q + 1, "\b]", 2)) /* closing magic? */
227 return 0; /* Not a proper tag. */
228
229 /* Output is different for index nodes */
230 if (!strncmp ("index", p, strlen ("index")))
231 *is_index = 1;
232
233 len = strcspn (p, " \t"); /* tag name */
234 tp = find_tag_handler (p, len);
235 if (tp && tp->handler)
236 {
237 while (p[len] == ' ' || p[len] == '\t')
238 ++len; /* move past whitespace */
239
240 tp->handler (p + len, outbuf);
241 }
242 *input = q + 3;
243 return 1;
244 }