/* GIMP - The GNU Image Manipulation Program * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * The GIMP Help plug-in * Copyright (C) 1999-2008 Sven Neumann * Michael Natterer * Henrik Brix Andersen * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /* This code is written so that it can also be compiled standalone. * It shouldn't depend on libgimp. */ #include "config.h" #include #include #include #include "gimphelp.h" #include "gimphelpprogress-private.h" #ifdef DISABLE_NLS #define _(String) (String) #else #include "libgimp/stdplugins-intl.h" #endif #ifdef PLATFORM_OSX #include #endif /* local function prototypes */ static void locale_set_error (GError **error, const gchar *format, GFile *file); /* public functions */ GimpHelpLocale * gimp_help_locale_new (const gchar *locale_id) { GimpHelpLocale *locale = g_slice_new0 (GimpHelpLocale); locale->locale_id = g_strdup (locale_id); return locale; } void gimp_help_locale_free (GimpHelpLocale *locale) { g_return_if_fail (locale != NULL); if (locale->help_id_mapping) g_hash_table_destroy (locale->help_id_mapping); g_free (locale->locale_id); g_free (locale->help_missing); g_list_free (locale->toplevel_items); g_slice_free (GimpHelpLocale, locale); } const gchar * gimp_help_locale_map (GimpHelpLocale *locale, const gchar *help_id) { g_return_val_if_fail (locale != NULL, NULL); g_return_val_if_fail (help_id != NULL, NULL); if (locale->help_id_mapping) { GimpHelpItem *item = g_hash_table_lookup (locale->help_id_mapping, help_id); if (item) return item->ref; } return NULL; } /* the locale mapping parser */ typedef enum { LOCALE_START, LOCALE_IN_HELP, LOCALE_IN_ITEM, LOCALE_IN_MISSING, LOCALE_IN_UNKNOWN } LocaleParserState; typedef struct { GFile *file; LocaleParserState state; LocaleParserState last_known_state; gint markup_depth; gint unknown_depth; GString *value; GimpHelpLocale *locale; const gchar *help_domain; gchar *id_attr_name; } LocaleParser; static gboolean locale_parser_parse (GMarkupParseContext *context, GimpHelpProgress *progress, GInputStream *stream, goffset size, GCancellable *cancellable, GError **error); 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); static void locale_parser_end_element (GMarkupParseContext *context, const gchar *element_name, gpointer user_data, GError **error); static void locale_parser_error (GMarkupParseContext *context, GError *error, gpointer user_data); static void locale_parser_start_unknown (LocaleParser *parser); static void locale_parser_end_unknown (LocaleParser *parser); static void locale_parser_parse_namespace (LocaleParser *parser, const gchar **names, const gchar **values); static void locale_parser_parse_item (LocaleParser *parser, const gchar **names, const gchar **values); static void locale_parser_parse_missing (LocaleParser *parser, const gchar **names, const gchar **values); static const GMarkupParser markup_parser = { locale_parser_start_element, locale_parser_end_element, NULL, /* characters */ NULL, /* passthrough */ locale_parser_error }; gboolean gimp_help_locale_parse (GimpHelpLocale *locale, const gchar *uri, const gchar *help_domain, GimpHelpProgress *progress, GError **error) { GMarkupParseContext *context; GFile *file = NULL; GCancellable *cancellable = NULL; LocaleParser parser = { NULL, }; #ifdef PLATFORM_OSX NSURL *fileURL; NSString *nsUri; NSData *data; const gchar *str; #else GFileInputStream *stream; goffset size = 0; #endif gboolean success; g_return_val_if_fail (locale != NULL, FALSE); g_return_val_if_fail (uri != NULL, FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); if (locale->help_id_mapping) { g_hash_table_destroy (locale->help_id_mapping); locale->help_id_mapping = NULL; } if (locale->help_missing) { g_free (locale->help_missing); locale->help_missing = NULL; } #ifdef GIMP_HELP_DEBUG g_printerr ("help (%s): parsing '%s' for \"%s\"\n", locale->locale_id, uri, help_domain); #endif file = g_file_new_for_uri (uri); if (progress) { gchar *name = g_file_get_parse_name (file); cancellable = g_cancellable_new (); _gimp_help_progress_start (progress, cancellable, _("Loading index from '%s'"), name); g_clear_object (&cancellable); g_free (name); } #ifdef PLATFORM_OSX nsUri = [NSString stringWithUTF8String: uri]; fileURL = [NSURL URLWithString: nsUri]; [nsUri release]; if (progress) _gimp_help_progress_pulse (progress); /* Load the data from the remote URL into the NSData object */ data = [NSData dataWithContentsOfURL:fileURL]; [fileURL release]; if (! data) { locale_set_error (error, _("Could not load data from '%s': %s"), file); g_object_unref (file); return FALSE; } if (progress) _gimp_help_progress_pulse (progress); #else /* PLATFORM_OSX */ if (progress) { GFileInfo *info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_SIZE, 0, cancellable, error); if (! info) { locale_set_error (error, _("Could not open '%s' for reading: %s"), file); g_object_unref (file); return FALSE; } size = g_file_info_get_size (info); g_object_unref (info); } stream = g_file_read (file, cancellable, error); if (! stream) { locale_set_error (error, _("Could not open '%s' for reading: %s"), file); g_object_unref (file); return FALSE; } #endif /* ! PLATFORM_OSX */ parser.file = file; parser.value = g_string_new (NULL); parser.locale = locale; parser.help_domain = help_domain; parser.id_attr_name = g_strdup ("id"); context = g_markup_parse_context_new (&markup_parser, 0, &parser, NULL); #ifdef PLATFORM_OSX str = (const char *)[data bytes]; if (! g_markup_parse_context_parse (context, str, [data length], error)) success = FALSE; else success = g_markup_parse_context_end_parse (context, error); [data release]; #else /* PLATFORM_OSX */ success = locale_parser_parse (context, progress, G_INPUT_STREAM (stream), size, cancellable, error); g_object_unref (stream); #endif /* ! PLATFORM_OSX */ if (progress) _gimp_help_progress_finish (progress); g_markup_parse_context_free (context); g_string_free (parser.value, TRUE); g_free (parser.id_attr_name); if (! success) locale_set_error (error, _("Parse error in '%s':\n%s"), file); g_object_unref (file); return success; } static gboolean locale_parser_parse (GMarkupParseContext *context, GimpHelpProgress *progress, GInputStream *stream, goffset size, GCancellable *cancellable, GError **error) { gssize len; goffset done = 0; gchar buffer[4096]; while ((len = g_input_stream_read (stream, buffer, sizeof (buffer), cancellable, error)) != -1) { switch (len) { case 0: return g_markup_parse_context_end_parse (context, error); default: done += len; if (progress) { if (size > 0) _gimp_help_progress_update (progress, (gdouble) done / size); else _gimp_help_progress_pulse (progress); } if (! g_markup_parse_context_parse (context, buffer, len, error)) return FALSE; } } return FALSE; } 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) { LocaleParser *parser = (LocaleParser *) user_data; switch (parser->state) { case LOCALE_START: if (strcmp (element_name, "gimp-help") == 0) { parser->state = LOCALE_IN_HELP; locale_parser_parse_namespace (parser, attribute_names, attribute_values); } else locale_parser_start_unknown (parser); break; case LOCALE_IN_HELP: if (strcmp (element_name, "help-item") == 0) { parser->state = LOCALE_IN_ITEM; locale_parser_parse_item (parser, attribute_names, attribute_values); } else if (strcmp (element_name, "help-missing") == 0) { parser->state = LOCALE_IN_MISSING; locale_parser_parse_missing (parser, attribute_names, attribute_values); } else locale_parser_start_unknown (parser); break; case LOCALE_IN_ITEM: case LOCALE_IN_MISSING: case LOCALE_IN_UNKNOWN: locale_parser_start_unknown (parser); break; } } static void locale_parser_end_element (GMarkupParseContext *context, const gchar *element_name, gpointer user_data, GError **error) { LocaleParser *parser = (LocaleParser *) user_data; switch (parser->state) { case LOCALE_START: g_warning ("locale_parser: This shouldn't happen."); break; case LOCALE_IN_HELP: parser->state = LOCALE_START; break; case LOCALE_IN_ITEM: case LOCALE_IN_MISSING: parser->state = LOCALE_IN_HELP; break; case LOCALE_IN_UNKNOWN: locale_parser_end_unknown (parser); break; } } static void locale_parser_error (GMarkupParseContext *context, GError *error, gpointer user_data) { LocaleParser *parser = (LocaleParser *) user_data; gchar *name = g_file_get_parse_name (parser->file); g_printerr ("help (parsing %s): %s", name, error->message); g_free (name); } static void locale_parser_start_unknown (LocaleParser *parser) { if (parser->unknown_depth == 0) parser->last_known_state = parser->state; parser->state = LOCALE_IN_UNKNOWN; parser->unknown_depth++; } static void locale_parser_end_unknown (LocaleParser *parser) { g_assert (parser->unknown_depth > 0 && parser->state == LOCALE_IN_UNKNOWN); parser->unknown_depth--; if (parser->unknown_depth == 0) parser->state = parser->last_known_state; } static void locale_parser_parse_namespace (LocaleParser *parser, const gchar **names, const gchar **values) { for (; *names && *values; names++, values++) { if (! strncmp (*names, "xmlns:", 6) && ! strcmp (*values, parser->help_domain)) { g_free (parser->id_attr_name); parser->id_attr_name = g_strdup_printf ("%s:id", *names + 6); #ifdef GIMP_HELP_DEBUG g_printerr ("help (%s): id attribute name for \"%s\" is \"%s\"\n", parser->locale->locale_id, parser->help_domain, parser->id_attr_name); #endif } } } static void locale_parser_parse_item (LocaleParser *parser, const gchar **names, const gchar **values) { const gchar *id = NULL; const gchar *ref = NULL; const gchar *title = NULL; const gchar *sort = NULL; /* optional sort key provided by doc team */ const gchar *parent = NULL; for (; *names && *values; names++, values++) { if (! strcmp (*names, parser->id_attr_name)) id = *values; if (! strcmp (*names, "ref")) ref = *values; if (! strcmp (*names, "title")) title = *values; if (! strcmp (*names, "sort")) sort = *values; if (! strcmp (*names, "parent")) parent = *values; } if (id && ref) { if (! parser->locale->help_id_mapping) parser->locale->help_id_mapping = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) gimp_help_item_free); g_hash_table_insert (parser->locale->help_id_mapping, g_strdup (id), gimp_help_item_new (ref, title, sort, parent)); #ifdef GIMP_HELP_DEBUG g_printerr ("help (%s): added mapping \"%s\" -> \"%s\"\n", parser->locale->locale_id, id, ref); #endif } } static void locale_parser_parse_missing (LocaleParser *parser, const gchar **names, const gchar **values) { const gchar *ref = NULL; for (; *names && *values; names++, values++) { if (! strcmp (*names, "ref")) ref = *values; } if (ref && parser->locale->help_missing == NULL) { parser->locale->help_missing = g_strdup (ref); #ifdef GIMP_HELP_DEBUG g_printerr ("help (%s): added fallback for missing help -> \"%s\"\n", parser->locale->locale_id, ref); #endif } } static void locale_set_error (GError **error, const gchar *format, GFile *file) { if (error && *error) { gchar *name = g_file_get_parse_name (file); gchar *msg; msg = g_strdup_printf (format, name, (*error)->message); g_free (name); g_free ((*error)->message); (*error)->message = msg; } }