1 %{
2 /*
3 * This is a plug-in for GIMP.
4 *
5 * Generates clickable image maps.
6 *
7 * Copyright (C) 1998-2005 Maurits Rijk lpeek.mrijk@consunet.nl
8 *
9 * This program is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 3 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 *
22 */
23
24 #include <stdlib.h>
25 #include <string.h>
26
27 #include <glib/gstdio.h>
28
29 #include <gtk/gtk.h>
30
31 #include "imap_circle.h"
32 #include "imap_file.h"
33 #include "imap_main.h"
34 #include "imap_polygon.h"
35 #include "imap_rectangle.h"
36 #include "imap_string.h"
37
38 extern int csim_lex(void);
39 extern int csim_restart(FILE *csim_in);
40 static void csim_error(char* s);
41 static gchar * unescape_text(gchar *input);
42
43 static enum {UNDEFINED, RECTANGLE, CIRCLE, POLYGON} current_type;
44 static Object_t *current_object;
45 static MapInfo_t *_map_info;
46
47 %}
48
49 %union {
50 int val;
51 double value;
52 char *id;
53 }
54
55 %token<val> IMG SRC WIDTH HEIGHT BORDER USEMAP
56 %token<val> START_MAP END_MAP NAME AREA SHAPE COORDS ALT HREF NOHREF
57 %token<val> TARGET ONMOUSEOVER ONMOUSEOUT ONFOCUS ONBLUR
58 %token<val> AUTHOR DESCRIPTION BEGIN_COMMENT END_COMMENT
59 %token<value> FLOAT
60 %token<id> STRING
61
62 %type<val> integer_value
63
64 %%
65
66 csim_file : image start_map comment_lines area_list end_map
67 ;
68
69 image : '<' IMG SRC '=' STRING image_tags xhtml_close
70 {
71 g_strreplace(&_map_info->image_name, $5);
72 g_free ($5);
73 }
74 ;
75
76 image_tags : /* Empty */
77 | image_tags image_tag
78 ;
79
80 image_tag : image_width
81 | image_height
82 | BORDER '=' integer_value {}
83 | USEMAP '=' STRING { g_free ($3); }
84 | ALT '=' STRING { g_free ($3); }
85 ;
86
87 image_width : WIDTH '=' integer_value
88 {
89 _map_info->old_image_width = $3;
90 }
91 ;
92
93 image_height : HEIGHT '=' integer_value
94 {
95 _map_info->old_image_height = $3;
96 }
97 ;
98
99 integer_value : FLOAT
100 {
101 $$ = (gint) $1;
102 }
103 | STRING
104 {
105 $$ = (gint) g_ascii_strtod ($1, NULL);
106 g_free ($1);
107 }
108 ;
109
110 start_map : '<' START_MAP NAME '=' STRING '>'
111 {
112 g_strreplace(&_map_info->title, $5);
113 g_free ($5);
114 }
115 ;
116
117 comment_lines : /* empty */
118 | comment_lines comment_line
119 ;
120
121 comment_line : author_line
122 | description_line
123 | real_comment
124 ;
125
126 real_comment : BEGIN_COMMENT STRING END_COMMENT
127 {
128 g_free ($2);
129 }
130 ;
131
132 author_line : AUTHOR STRING END_COMMENT
133 {
134 g_strreplace(&_map_info->author, $2);
135 g_free ($2);
136 }
137 ;
138
139 description_line: DESCRIPTION STRING END_COMMENT
140 {
141 gchar *description;
142
143 description = g_strconcat(_map_info->description, $2, "\n",
144 NULL);
145 g_strreplace(&_map_info->description, description);
146 g_free ($2);
147 }
148 ;
149
150 area_list : /* empty */
151 | area_list area
152 ;
153
154 area : '<' AREA tag_list xhtml_close
155 {
156 if (current_type != UNDEFINED)
157 add_shape(current_object);
158 }
159 ;
160
161 xhtml_close : '>'
162 | '/' '>'
163 ;
164
165 tag_list : /* Empty */
166 | tag_list tag
167 ;
168
169 tag : shape_tag
170 | coords_tag
171 | href_tag
172 | nohref_tag
173 | alt_tag
174 | target_tag
175 | onmouseover_tag
176 | onmouseout_tag
177 | onfocus_tag
178 | onblur_tag
179 ;
180
181 shape_tag : SHAPE '=' STRING
182 {
183 if (!g_ascii_strcasecmp($3, "RECT")) {
184 current_object = create_rectangle(0, 0, 0, 0);
185 current_type = RECTANGLE;
186 } else if (!g_ascii_strcasecmp($3, "CIRCLE")) {
187 current_object = create_circle(0, 0, 0);
188 current_type = CIRCLE;
189 } else if (!g_ascii_strcasecmp($3, "POLY")) {
190 current_object = create_polygon(NULL);
191 current_type = POLYGON;
192 } else if (!g_ascii_strcasecmp($3, "DEFAULT")) {
193 current_type = UNDEFINED;
194 }
195 g_free ($3);
196 }
197 ;
198
199 coords_tag : COORDS '=' STRING
200 {
201 char *p;
202 if (current_type == RECTANGLE) {
203 Rectangle_t *rectangle;
204
205 rectangle = ObjectToRectangle(current_object);
206 p = strtok($3, ",");
207 rectangle->x = atoi(p);
208 p = strtok(NULL, ",");
209 rectangle->y = atoi(p);
210 p = strtok(NULL, ",");
211 rectangle->width = atoi(p) - rectangle->x;
212 p = strtok(NULL, ",");
213 rectangle->height = atoi(p) - rectangle->y;
214 } else if (current_type == CIRCLE) {
215 Circle_t *circle;
216
217 circle = ObjectToCircle(current_object);
218 p = strtok($3, ",");
219 circle->x = atoi(p);
220 p = strtok(NULL, ",");
221 circle->y = atoi(p);
222 p = strtok(NULL, ",");
223 circle->r = atoi(p);
224 } else if (current_type == POLYGON) {
225 Polygon_t *polygon = ObjectToPolygon(current_object);
226 GList *points;
227 GdkPoint *point, *first;
228 gint x, y;
229
230 p = strtok($3, ",");
231 x = atoi(p);
232 p = strtok(NULL, ",");
233 y = atoi(p);
234 point = new_point(x, y);
235 points = g_list_append(NULL, (gpointer) point);
236
237 while(1) {
238 p = strtok(NULL, ",");
239 if (!p)
240 break;
241 x = atoi(p);
242 p = strtok(NULL, ",");
243 y = atoi(p);
244 point = new_point(x, y);
245 points = g_list_append(points, (gpointer) point);
246 }
247 /* Remove last point if duplicate */
248 first = (GdkPoint*) points->data;
249 polygon->points = points;
250 if (first->x == point->x && first->y == point->y)
251 polygon_remove_last_point(polygon);
252 polygon->points = points;
253 }
254
255 g_free ($3);
256 }
257 ;
258
259 href_tag : HREF '=' STRING
260 {
261 if (current_type == UNDEFINED) {
262 g_strreplace(&_map_info->default_url, $3);
263 } else {
264 object_set_url(current_object, unescape_text($3));
265 }
266 g_free ($3);
267 }
268 ;
269
270 nohref_tag : NOHREF optional_value
271 {
272 }
273 ;
274
275 optional_value : /* Empty */
276 | '=' STRING
277 {
278 g_free ($2);
279 }
280 ;
281
282 alt_tag : ALT '=' STRING
283 {
284 object_set_comment(current_object, unescape_text($3));
285 g_free ($3);
286 }
287 ;
288
289 target_tag : TARGET '=' STRING
290 {
291 object_set_target(current_object, unescape_text($3));
292 g_free ($3);
293 }
294 ;
295
296 onmouseover_tag : ONMOUSEOVER '=' STRING
297 {
298 object_set_mouse_over(current_object, unescape_text($3));
299 g_free ($3);
300 }
301 ;
302
303 onmouseout_tag : ONMOUSEOUT '=' STRING
304 {
305 object_set_mouse_out(current_object, unescape_text($3));
306 g_free ($3);
307 }
308 ;
309
310 onfocus_tag : ONFOCUS '=' STRING
311 {
312 object_set_focus(current_object, unescape_text($3));
313 g_free ($3);
314 }
315 ;
316
317 onblur_tag : ONBLUR '=' STRING
318 {
319 object_set_blur(current_object, unescape_text($3));
320 g_free ($3);
321 }
322 ;
323
324 end_map : '<' END_MAP '>'
325 ;
326
327 %%
328
329 static void
330 csim_error(char* s)
331 {
332 extern FILE *csim_in;
333 csim_restart(csim_in);
334 }
335
336 gboolean
load_csim(const char * filename)337 load_csim (const char* filename)
338 {
339 gboolean status;
340 extern FILE *csim_in;
341 csim_in = g_fopen(filename, "r");
342 if (csim_in) {
343 _map_info = get_map_info();
344 status = !csim_parse();
345 fclose(csim_in);
346 } else {
347 status = FALSE;
348 }
349 return status;
350 }
351
352 static gchar*
unescape_text(gchar * input)353 unescape_text (gchar *input)
354 {
355 /*
356 * We "unescape" simple things "in place", knowing that unescaped
357 * strings always are shorter than the original input.
358 *
359 * It is a shame there is no g_markup_unescape_text() function, but
360 * instead you have to create a full GMarkupParser/Context.
361 */
362 struct token {
363 const char *escaped;
364 const char unescaped;
365 };
366 const struct token tab[] = {
367 { """, '"' },
368 { "'", '\'' },
369 { "&", '&' },
370 { "<", '<' },
371 { ">", '>' }
372 };
373
374 size_t i;
375 for (i = 0; i < (sizeof tab / sizeof tab[0]); i++)
376 {
377 const size_t escaped_len = strlen (tab[i].escaped);
378 char *p;
379
380 /* FIXME: The following code does not perform a UTF-8 substring
381 search. */
382 for (p = strstr (input, tab[i].escaped);
383 p != NULL;
384 p = strstr (p, tab[i].escaped))
385 {
386 size_t copy_len;
387 *p++ = tab[i].unescaped;
388 copy_len = strlen (p) - escaped_len + 2;
389 memmove (p, p + escaped_len - 1, copy_len);
390 if (*p == 0)
391 break;
392 }
393 }
394
395 return input;
396 }
397