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