From 96a053e025f4b5a7286844df3d691ebb508953ab Mon Sep 17 00:00:00 2001 From: Ryan Lortie Date: Sat, 26 Oct 2013 18:57:55 -0400 Subject: [PATCH] Add g_settings_schema_key_get_summary/description Add an API to read the summary and description from the .xml schema files. This will be used by dconf-editor and gnome-tweak-tool. This API is a bit heavy -- it parses the XML and builds a table. It also loads gettext domains for translation. It only does these things if it is used, however, so it will not impact normal applications. We store the summary/description in a pair of hash tables on the schema source (which we have a backref to as of a few commits ago). We can't use a global table because people might want to request summary and description from non-default sources. We don't want to use per-schema tables because we'd have to reparse the directory every time (since we cannot guess which file a schema may have been in). https://bugzilla.gnome.org/show_bug.cgi?id=668232 --- docs/reference/gio/gio-sections.txt | 4 + gio/gsettingsschema.c | 355 ++++++++++++++++++++++++++++ gio/gsettingsschema.h | 5 + 3 files changed, 364 insertions(+) diff --git a/docs/reference/gio/gio-sections.txt b/docs/reference/gio/gio-sections.txt index be7da5137..1923ef159 100644 --- a/docs/reference/gio/gio-sections.txt +++ b/docs/reference/gio/gio-sections.txt @@ -2412,6 +2412,10 @@ g_settings_schema_get_key g_settings_schema_key_ref g_settings_schema_key_unref + +g_settings_schema_key_get_summary +g_settings_schema_key_get_description + g_settings_schema_get_type g_settings_schema_key_get_type diff --git a/gio/gsettingsschema.c b/gio/gsettingsschema.c index 5919ff6da..8ee2ad09e 100644 --- a/gio/gsettingsschema.c +++ b/gio/gsettingsschema.c @@ -179,6 +179,7 @@ struct _GSettingsSchemaSource GSettingsSchemaSource *parent; gchar *directory; GvdbTable *table; + GHashTable **text_tables; gint ref_count; }; @@ -224,6 +225,13 @@ g_settings_schema_source_unref (GSettingsSchemaSource *source) gvdb_table_unref (source->table); g_free (source->directory); + if (source->text_tables) + { + g_hash_table_unref (source->text_tables[0]); + g_hash_table_unref (source->text_tables[1]); + g_free (source->text_tables); + } + g_slice_free (GSettingsSchemaSource, source); } } @@ -288,6 +296,7 @@ g_settings_schema_source_new_from_directory (const gchar *directory, source = g_slice_new (GSettingsSchemaSource); source->directory = g_strdup (directory); source->parent = parent ? g_settings_schema_source_ref (parent) : NULL; + source->text_tables = NULL; source->table = table; source->ref_count = 1; @@ -426,6 +435,283 @@ g_settings_schema_source_lookup (GSettingsSchemaSource *source, return schema; } +typedef struct +{ + GHashTable *summaries; + GHashTable *descriptions; + GSList *gettext_domain; + GSList *schema_id; + GSList *key_name; + GString *string; +} TextTableParseInfo; + +static const gchar * +get_attribute_value (GSList *list) +{ + GSList *node; + + for (node = list; node; node = node->next) + if (node->data) + return node->data; + + return NULL; +} + +static void +pop_attribute_value (GSList **list) +{ + gchar *top; + + top = (*list)->data; + *list = g_slist_remove (*list, top); + + g_free (top); +} + +static void +push_attribute_value (GSList **list, + const gchar *value) +{ + *list = g_slist_prepend (*list, g_strdup (value)); +} + +static void +start_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer user_data, + GError **error) +{ + TextTableParseInfo *info = user_data; + const gchar *gettext_domain = NULL; + const gchar *schema_id = NULL; + const gchar *key_name = NULL; + gint i; + + for (i = 0; attribute_names[i]; i++) + { + if (g_str_equal (attribute_names[i], "gettext-domain")) + gettext_domain = attribute_values[i]; + else if (g_str_equal (attribute_names[i], "id")) + schema_id = attribute_values[i]; + else if (g_str_equal (attribute_names[i], "name")) + key_name = attribute_values[i]; + } + + push_attribute_value (&info->gettext_domain, gettext_domain); + push_attribute_value (&info->schema_id, schema_id); + push_attribute_value (&info->key_name, key_name); + + if (info->string) + { + g_string_free (info->string, TRUE); + info->string = NULL; + } + + if (g_str_equal (element_name, "summary") || g_str_equal (element_name, "description")) + info->string = g_string_new (NULL); +} + +static gchar * +normalise_whitespace (const gchar *orig) +{ + /* We normalise by the same rules as in intltool: + * + * sub cleanup { + * s/^\s+//; + * s/\s+$//; + * s/\s+/ /g; + * return $_; + * } + * + * $message = join "\n\n", map &cleanup, split/\n\s*\n+/, $message; + * + * Where \s is an ascii space character. + * + * We aim for ease of implementation over efficiency -- this code is + * not run in normal applications. + */ + static GRegex *cleanup[3]; + static GRegex *splitter; + gchar **lines; + gchar *result; + gint i; + + if (g_once_init_enter (&splitter)) + { + GRegex *s; + + cleanup[0] = g_regex_new ("^\\s+", 0, 0, 0); + cleanup[1] = g_regex_new ("\\s+$", 0, 0, 0); + cleanup[2] = g_regex_new ("\\s+", 0, 0, 0); + s = g_regex_new ("\\n\\s*\\n+", 0, 0, 0); + + g_once_init_leave (&splitter, s); + } + + lines = g_regex_split (splitter, orig, 0); + for (i = 0; lines[i]; i++) + { + gchar *a, *b, *c; + + a = g_regex_replace_literal (cleanup[0], lines[i], -1, 0, "", 0, 0); + b = g_regex_replace_literal (cleanup[1], a, -1, 0, "", 0, 0); + c = g_regex_replace_literal (cleanup[2], b, -1, 0, " ", 0, 0); + g_free (lines[i]); + g_free (a); + g_free (b); + lines[i] = c; + } + + result = g_strjoinv ("\n\n", lines); + g_strfreev (lines); + + return result; +} + +static void +end_element (GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, + GError **error) +{ + TextTableParseInfo *info = user_data; + + pop_attribute_value (&info->gettext_domain); + pop_attribute_value (&info->schema_id); + pop_attribute_value (&info->key_name); + + if (info->string) + { + GHashTable *source_table = NULL; + const gchar *gettext_domain; + const gchar *schema_id; + const gchar *key_name; + + gettext_domain = get_attribute_value (info->gettext_domain); + schema_id = get_attribute_value (info->schema_id); + key_name = get_attribute_value (info->key_name); + + if (g_str_equal (element_name, "summary")) + source_table = info->summaries; + else if (g_str_equal (element_name, "description")) + source_table = info->descriptions; + + if (source_table && schema_id && key_name) + { + GHashTable *schema_table; + gchar *normalised; + + schema_table = g_hash_table_lookup (source_table, schema_id); + + if (schema_table == NULL) + { + schema_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + g_hash_table_insert (source_table, g_strdup (schema_id), schema_table); + } + + normalised = normalise_whitespace (info->string->str); + + if (gettext_domain) + { + gchar *translated; + + translated = g_strdup (g_dgettext (gettext_domain, normalised)); + g_free (normalised); + normalised = translated; + } + + g_hash_table_insert (schema_table, g_strdup (key_name), normalised); + } + + g_string_free (info->string, TRUE); + info->string = NULL; + } +} + +static void +text (GMarkupParseContext *context, + const gchar *text, + gsize text_len, + gpointer user_data, + GError **error) +{ + TextTableParseInfo *info = user_data; + + if (info->string) + g_string_append_len (info->string, text, text_len); +} + +static void +parse_into_text_tables (const gchar *directory, + GHashTable *summaries, + GHashTable *descriptions) +{ + GMarkupParser parser = { start_element, end_element, text }; + TextTableParseInfo info = { summaries, descriptions }; + const gchar *basename; + GDir *dir; + + dir = g_dir_open (directory, 0, NULL); + while ((basename = g_dir_read_name (dir))) + { + gchar *filename; + gchar *contents; + gsize size; + + filename = g_build_filename (directory, basename, NULL); + if (g_file_get_contents (filename, &contents, &size, NULL)) + { + GMarkupParseContext *context; + + context = g_markup_parse_context_new (&parser, G_MARKUP_TREAT_CDATA_AS_TEXT, &info, NULL); + if (g_markup_parse_context_parse (context, contents, size, NULL)) + g_markup_parse_context_end_parse (context, NULL); + g_markup_parse_context_free (context); + + /* Clean up dangling stuff in case there was an error. */ + g_slist_free_full (info.gettext_domain, g_free); + g_slist_free_full (info.schema_id, g_free); + g_slist_free_full (info.key_name, g_free); + + info.gettext_domain = NULL; + info.schema_id = NULL; + info.key_name = NULL; + + if (info.string) + { + g_string_free (info.string, TRUE); + info.string = NULL; + } + + g_free (contents); + } + + g_free (filename); + } +} + +static GHashTable ** +g_settings_schema_source_get_text_tables (GSettingsSchemaSource *source) +{ + if (g_once_init_enter (&source->text_tables)) + { + GHashTable **text_tables; + + text_tables = g_new (GHashTable *, 2); + text_tables[0] = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_hash_table_unref); + text_tables[1] = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_hash_table_unref); + + if (source->directory) + parse_into_text_tables (source->directory, text_tables[0], text_tables[1]); + + g_once_init_leave (&source->text_tables, text_tables); + } + + return source->text_tables; +} + static gboolean steal_item (gpointer key, gpointer value, @@ -1149,3 +1435,72 @@ g_settings_schema_get_key (GSettingsSchema *schema, return key; } + +/** + * g_settings_schema_key_get_summary: + * @key: a #GSettingsSchemaKey + * + * Gets the summary for @key. + * + * If no summary has been provided in the schema for @key, returns + * %NULL. + * + * The summary is a short description of the purpose of the key; usually + * one short sentence. Summaries can be translated and the value + * returned from this function is is the current locale. + * + * This function is slow. The summary and description information for + * the schemas is not stored in the compiled schema database so this + * function has to parse all of the source XML files in the schema + * directory. + * + * Returns: the summary for @key, or %NULL + * + * Since: 2.34 + **/ +const gchar * +g_settings_schema_key_get_summary (GSettingsSchemaKey *key) +{ + GHashTable **text_tables; + GHashTable *summaries; + + text_tables = g_settings_schema_source_get_text_tables (key->schema->source); + summaries = g_hash_table_lookup (text_tables[0], key->schema->id); + + return summaries ? g_hash_table_lookup (summaries, key->name) : NULL; +} + +/** + * g_settings_schema_key_get_description: + * @key: a #GSettingsSchemaKey + * + * Gets the description for @key. + * + * If no description has been provided in the schema for @key, returns + * %NULL. + * + * The description can be one sentence to several paragraphs in length. + * Paragraphs are delimited with a double newline. Descriptions can be + * translated and the value returned from this function is is the + * current locale. + * + * This function is slow. The summary and description information for + * the schemas is not stored in the compiled schema database so this + * function has to parse all of the source XML files in the schema + * directory. + * + * Returns: the description for @key, or %NULL + * + * Since: 2.34 + **/ +const gchar * +g_settings_schema_key_get_description (GSettingsSchemaKey *key) +{ + GHashTable **text_tables; + GHashTable *descriptions; + + text_tables = g_settings_schema_source_get_text_tables (key->schema->source); + descriptions = g_hash_table_lookup (text_tables[1], key->schema->id); + + return descriptions ? g_hash_table_lookup (descriptions, key->name) : NULL; +} diff --git a/gio/gsettingsschema.h b/gio/gsettingsschema.h index e84029efb..ee70a3f28 100644 --- a/gio/gsettingsschema.h +++ b/gio/gsettingsschema.h @@ -77,6 +77,11 @@ GSettingsSchemaKey * g_settings_schema_key_ref (GSettin GLIB_AVAILABLE_IN_2_40 void g_settings_schema_key_unref (GSettingsSchemaKey *key); +GLIB_AVAILABLE_IN_2_40 +const gchar * g_settings_schema_key_get_summary (GSettingsSchemaKey *key); +GLIB_AVAILABLE_IN_2_40 +const gchar * g_settings_schema_key_get_description (GSettingsSchemaKey *key); + G_END_DECLS #endif /* __G_SETTINGS_SCHEMA_H__ */