1 /* GIMP - The GNU Image Manipulation Program
2 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3 *
4 * The GIMP Help plug-in
5 * Copyright (C) 1999-2008 Sven Neumann <sven@gimp.org>
6 * Michael Natterer <mitch@gimp.org>
7 * Henrik Brix Andersen <brix@gimp.org>
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 /* This code is written so that it can also be compiled standalone.
24 * It shouldn't depend on libgimp.
25 */
26
27 #include "config.h"
28
29 #include <string.h>
30
31 #include <glib-object.h>
32 #include <gio/gio.h>
33
34 #include "gimphelp.h"
35 #include "gimphelpprogress-private.h"
36
37 #ifdef DISABLE_NLS
38 #define _(String) (String)
39 #else
40 #include "libgimp/stdplugins-intl.h"
41 #endif
42
43
44 /* local function prototypes */
45
46 static void locale_set_error (GError **error,
47 const gchar *format,
48 GFile *file);
49
50
51 /* public functions */
52
53 GimpHelpLocale *
gimp_help_locale_new(const gchar * locale_id)54 gimp_help_locale_new (const gchar *locale_id)
55 {
56 GimpHelpLocale *locale = g_slice_new0 (GimpHelpLocale);
57
58 locale->locale_id = g_strdup (locale_id);
59
60 return locale;
61 }
62
63 void
gimp_help_locale_free(GimpHelpLocale * locale)64 gimp_help_locale_free (GimpHelpLocale *locale)
65 {
66 g_return_if_fail (locale != NULL);
67
68 if (locale->help_id_mapping)
69 g_hash_table_destroy (locale->help_id_mapping);
70
71 g_free (locale->locale_id);
72 g_free (locale->help_missing);
73
74 g_list_free (locale->toplevel_items);
75
76 g_slice_free (GimpHelpLocale, locale);
77 }
78
79 const gchar *
gimp_help_locale_map(GimpHelpLocale * locale,const gchar * help_id)80 gimp_help_locale_map (GimpHelpLocale *locale,
81 const gchar *help_id)
82 {
83 g_return_val_if_fail (locale != NULL, NULL);
84 g_return_val_if_fail (help_id != NULL, NULL);
85
86 if (locale->help_id_mapping)
87 {
88 GimpHelpItem *item = g_hash_table_lookup (locale->help_id_mapping,
89 help_id);
90
91 if (item)
92 return item->ref;
93 }
94
95 return NULL;
96 }
97
98
99 /* the locale mapping parser */
100
101 typedef enum
102 {
103 LOCALE_START,
104 LOCALE_IN_HELP,
105 LOCALE_IN_ITEM,
106 LOCALE_IN_MISSING,
107 LOCALE_IN_UNKNOWN
108 } LocaleParserState;
109
110 typedef struct
111 {
112 GFile *file;
113 LocaleParserState state;
114 LocaleParserState last_known_state;
115 gint markup_depth;
116 gint unknown_depth;
117 GString *value;
118
119 GimpHelpLocale *locale;
120 const gchar *help_domain;
121 gchar *id_attr_name;
122 } LocaleParser;
123
124 static gboolean locale_parser_parse (GMarkupParseContext *context,
125 GimpHelpProgress *progress,
126 GInputStream *stream,
127 goffset size,
128 GCancellable *cancellable,
129 GError **error);
130 static void locale_parser_start_element (GMarkupParseContext *context,
131 const gchar *element_name,
132 const gchar **attribute_names,
133 const gchar **attribute_values,
134 gpointer user_data,
135 GError **error);
136 static void locale_parser_end_element (GMarkupParseContext *context,
137 const gchar *element_name,
138 gpointer user_data,
139 GError **error);
140 static void locale_parser_error (GMarkupParseContext *context,
141 GError *error,
142 gpointer user_data);
143 static void locale_parser_start_unknown (LocaleParser *parser);
144 static void locale_parser_end_unknown (LocaleParser *parser);
145 static void locale_parser_parse_namespace (LocaleParser *parser,
146 const gchar **names,
147 const gchar **values);
148 static void locale_parser_parse_item (LocaleParser *parser,
149 const gchar **names,
150 const gchar **values);
151 static void locale_parser_parse_missing (LocaleParser *parser,
152 const gchar **names,
153 const gchar **values);
154
155 static const GMarkupParser markup_parser =
156 {
157 locale_parser_start_element,
158 locale_parser_end_element,
159 NULL, /* characters */
160 NULL, /* passthrough */
161 locale_parser_error
162 };
163
164 gboolean
gimp_help_locale_parse(GimpHelpLocale * locale,const gchar * uri,const gchar * help_domain,GimpHelpProgress * progress,GError ** error)165 gimp_help_locale_parse (GimpHelpLocale *locale,
166 const gchar *uri,
167 const gchar *help_domain,
168 GimpHelpProgress *progress,
169 GError **error)
170 {
171 GMarkupParseContext *context;
172 GFile *file;
173 GFileInputStream *stream;
174 GCancellable *cancellable = NULL;
175 LocaleParser parser = { NULL, };
176 goffset size = 0;
177 gboolean success;
178
179 g_return_val_if_fail (locale != NULL, FALSE);
180 g_return_val_if_fail (uri != NULL, FALSE);
181 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
182
183 if (locale->help_id_mapping)
184 {
185 g_hash_table_destroy (locale->help_id_mapping);
186 locale->help_id_mapping = NULL;
187 }
188
189 if (locale->help_missing)
190 {
191 g_free (locale->help_missing);
192 locale->help_missing = NULL;
193 }
194
195 #ifdef GIMP_HELP_DEBUG
196 g_printerr ("help (%s): parsing '%s' for \"%s\"\n",
197 locale->locale_id, uri, help_domain);
198 #endif
199
200 file = g_file_new_for_uri (uri);
201
202 if (progress)
203 {
204 gchar *name = g_file_get_parse_name (file);
205
206 cancellable = g_cancellable_new ();
207 _gimp_help_progress_start (progress, cancellable,
208 _("Loading index from '%s'"), name);
209
210 g_object_unref (cancellable);
211 g_free (name);
212 }
213
214 if (progress)
215 {
216 GFileInfo *info = g_file_query_info (file,
217 G_FILE_ATTRIBUTE_STANDARD_SIZE, 0,
218 cancellable, error);
219 if (! info)
220 {
221 locale_set_error (error,
222 _("Could not open '%s' for reading: %s"), file);
223 g_object_unref (file);
224
225 return FALSE;
226 }
227
228 size = g_file_info_get_size (info);
229
230 g_object_unref (info);
231 }
232
233 stream = g_file_read (file, cancellable, error);
234
235 if (! stream)
236 {
237 locale_set_error (error,
238 _("Could not open '%s' for reading: %s"), file);
239 g_object_unref (file);
240
241 return FALSE;
242 }
243
244 parser.file = file;
245 parser.value = g_string_new (NULL);
246 parser.locale = locale;
247 parser.help_domain = help_domain;
248 parser.id_attr_name = g_strdup ("id");
249
250 context = g_markup_parse_context_new (&markup_parser, 0, &parser, NULL);
251
252 success = locale_parser_parse (context, progress,
253 G_INPUT_STREAM (stream), size,
254 cancellable, error);
255
256 if (progress)
257 _gimp_help_progress_finish (progress);
258
259 g_markup_parse_context_free (context);
260 g_object_unref (stream);
261
262 g_string_free (parser.value, TRUE);
263 g_free (parser.id_attr_name);
264
265 if (! success)
266 locale_set_error (error, _("Parse error in '%s':\n%s"), file);
267
268 g_object_unref (file);
269
270 return success;
271 }
272
273 static gboolean
locale_parser_parse(GMarkupParseContext * context,GimpHelpProgress * progress,GInputStream * stream,goffset size,GCancellable * cancellable,GError ** error)274 locale_parser_parse (GMarkupParseContext *context,
275 GimpHelpProgress *progress,
276 GInputStream *stream,
277 goffset size,
278 GCancellable *cancellable,
279 GError **error)
280 {
281 gssize len;
282 goffset done = 0;
283 gchar buffer[4096];
284
285 while ((len = g_input_stream_read (stream, buffer, sizeof (buffer),
286 cancellable, error)) != -1)
287 {
288 switch (len)
289 {
290 case 0:
291 return g_markup_parse_context_end_parse (context, error);
292
293 default:
294 done += len;
295
296 if (progress)
297 {
298 if (size > 0)
299 _gimp_help_progress_update (progress, (gdouble) done / size);
300 else
301 _gimp_help_progress_pulse (progress);
302 }
303
304 if (! g_markup_parse_context_parse (context, buffer, len, error))
305 return FALSE;
306 }
307 }
308
309 return FALSE;
310 }
311
312 static void
locale_parser_start_element(GMarkupParseContext * context,const gchar * element_name,const gchar ** attribute_names,const gchar ** attribute_values,gpointer user_data,GError ** error)313 locale_parser_start_element (GMarkupParseContext *context,
314 const gchar *element_name,
315 const gchar **attribute_names,
316 const gchar **attribute_values,
317 gpointer user_data,
318 GError **error)
319 {
320 LocaleParser *parser = (LocaleParser *) user_data;
321
322 switch (parser->state)
323 {
324 case LOCALE_START:
325 if (strcmp (element_name, "gimp-help") == 0)
326 {
327 parser->state = LOCALE_IN_HELP;
328 locale_parser_parse_namespace (parser,
329 attribute_names, attribute_values);
330 }
331 else
332 locale_parser_start_unknown (parser);
333 break;
334
335 case LOCALE_IN_HELP:
336 if (strcmp (element_name, "help-item") == 0)
337 {
338 parser->state = LOCALE_IN_ITEM;
339 locale_parser_parse_item (parser,
340 attribute_names, attribute_values);
341 }
342 else if (strcmp (element_name, "help-missing") == 0)
343 {
344 parser->state = LOCALE_IN_MISSING;
345 locale_parser_parse_missing (parser,
346 attribute_names, attribute_values);
347 }
348 else
349 locale_parser_start_unknown (parser);
350 break;
351
352 case LOCALE_IN_ITEM:
353 case LOCALE_IN_MISSING:
354 case LOCALE_IN_UNKNOWN:
355 locale_parser_start_unknown (parser);
356 break;
357 }
358 }
359
360 static void
locale_parser_end_element(GMarkupParseContext * context,const gchar * element_name,gpointer user_data,GError ** error)361 locale_parser_end_element (GMarkupParseContext *context,
362 const gchar *element_name,
363 gpointer user_data,
364 GError **error)
365 {
366 LocaleParser *parser = (LocaleParser *) user_data;
367
368 switch (parser->state)
369 {
370 case LOCALE_START:
371 g_warning ("locale_parser: This shouldn't happen.");
372 break;
373
374 case LOCALE_IN_HELP:
375 parser->state = LOCALE_START;
376 break;
377
378 case LOCALE_IN_ITEM:
379 case LOCALE_IN_MISSING:
380 parser->state = LOCALE_IN_HELP;
381 break;
382
383 case LOCALE_IN_UNKNOWN:
384 locale_parser_end_unknown (parser);
385 break;
386 }
387 }
388
389 static void
locale_parser_error(GMarkupParseContext * context,GError * error,gpointer user_data)390 locale_parser_error (GMarkupParseContext *context,
391 GError *error,
392 gpointer user_data)
393 {
394 LocaleParser *parser = (LocaleParser *) user_data;
395 gchar *name = g_file_get_parse_name (parser->file);
396
397 g_printerr ("help (parsing %s): %s", name, error->message);
398
399 g_free (name);
400 }
401
402 static void
locale_parser_start_unknown(LocaleParser * parser)403 locale_parser_start_unknown (LocaleParser *parser)
404 {
405 if (parser->unknown_depth == 0)
406 parser->last_known_state = parser->state;
407
408 parser->state = LOCALE_IN_UNKNOWN;
409 parser->unknown_depth++;
410 }
411
412 static void
locale_parser_end_unknown(LocaleParser * parser)413 locale_parser_end_unknown (LocaleParser *parser)
414 {
415 g_assert (parser->unknown_depth > 0 && parser->state == LOCALE_IN_UNKNOWN);
416
417 parser->unknown_depth--;
418
419 if (parser->unknown_depth == 0)
420 parser->state = parser->last_known_state;
421 }
422
423 static void
locale_parser_parse_namespace(LocaleParser * parser,const gchar ** names,const gchar ** values)424 locale_parser_parse_namespace (LocaleParser *parser,
425 const gchar **names,
426 const gchar **values)
427 {
428 for (; *names && *values; names++, values++)
429 {
430 if (! strncmp (*names, "xmlns:", 6) &&
431 ! strcmp (*values, parser->help_domain))
432 {
433 g_free (parser->id_attr_name);
434 parser->id_attr_name = g_strdup_printf ("%s:id", *names + 6);
435
436 #ifdef GIMP_HELP_DEBUG
437 g_printerr ("help (%s): id attribute name for \"%s\" is \"%s\"\n",
438 parser->locale->locale_id,
439 parser->help_domain,
440 parser->id_attr_name);
441 #endif
442 }
443 }
444 }
445
446 static void
locale_parser_parse_item(LocaleParser * parser,const gchar ** names,const gchar ** values)447 locale_parser_parse_item (LocaleParser *parser,
448 const gchar **names,
449 const gchar **values)
450 {
451 const gchar *id = NULL;
452 const gchar *ref = NULL;
453 const gchar *title = NULL;
454 const gchar *sort = NULL; /* optional sort key provided by doc team */
455 const gchar *parent = NULL;
456
457 for (; *names && *values; names++, values++)
458 {
459 if (! strcmp (*names, parser->id_attr_name))
460 id = *values;
461
462 if (! strcmp (*names, "ref"))
463 ref = *values;
464
465 if (! strcmp (*names, "title"))
466 title = *values;
467
468 if (! strcmp (*names, "sort"))
469 sort = *values;
470
471 if (! strcmp (*names, "parent"))
472 parent = *values;
473 }
474
475 if (id && ref)
476 {
477 if (! parser->locale->help_id_mapping)
478 parser->locale->help_id_mapping =
479 g_hash_table_new_full (g_str_hash,
480 g_str_equal,
481 g_free,
482 (GDestroyNotify) gimp_help_item_free);
483
484 g_hash_table_insert (parser->locale->help_id_mapping,
485 g_strdup (id),
486 gimp_help_item_new (ref, title, sort, parent));
487
488 #ifdef GIMP_HELP_DEBUG
489 g_printerr ("help (%s): added mapping \"%s\" -> \"%s\"\n",
490 parser->locale->locale_id, id, ref);
491 #endif
492 }
493 }
494
495 static void
locale_parser_parse_missing(LocaleParser * parser,const gchar ** names,const gchar ** values)496 locale_parser_parse_missing (LocaleParser *parser,
497 const gchar **names,
498 const gchar **values)
499 {
500 const gchar *ref = NULL;
501
502 for (; *names && *values; names++, values++)
503 {
504 if (! strcmp (*names, "ref"))
505 ref = *values;
506 }
507
508 if (ref &&
509 parser->locale->help_missing == NULL)
510 {
511 parser->locale->help_missing = g_strdup (ref);
512
513 #ifdef GIMP_HELP_DEBUG
514 g_printerr ("help (%s): added fallback for missing help -> \"%s\"\n",
515 parser->locale->locale_id, ref);
516 #endif
517 }
518 }
519
520 static void
locale_set_error(GError ** error,const gchar * format,GFile * file)521 locale_set_error (GError **error,
522 const gchar *format,
523 GFile *file)
524 {
525 if (error && *error)
526 {
527 gchar *name = g_file_get_parse_name (file);
528 gchar *msg;
529
530 msg = g_strdup_printf (format, name, (*error)->message);
531 g_free (name);
532
533 g_free ((*error)->message);
534 (*error)->message = msg;
535 }
536 }
537