/* GtkIconThemeParser - a parser of icon-theme files * gtk-icon-theme-parser.c Copyright (C) 2002, 2003 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ #include "config.h" #include #include #include #include "gtkiconthemeparser.h" typedef struct _GtkIconThemeFileSection GtkIconThemeFileSection; typedef struct _GtkIconThemeFileLine GtkIconThemeFileLine; typedef struct _GtkIconThemeFileParser GtkIconThemeFileParser; struct _GtkIconThemeFileSection { GQuark section_name; /* 0 means just a comment block (before any section) */ gint n_lines; GtkIconThemeFileLine *lines; }; struct _GtkIconThemeFileLine { GQuark key; /* 0 means comment or blank line in value */ char *locale; gchar *value; }; struct _GtkIconThemeFile { gint n_sections; GtkIconThemeFileSection *sections; char *current_locale[2]; }; struct _GtkIconThemeFileParser { GtkIconThemeFile *df; gint current_section; gint n_allocated_lines; gint n_allocated_sections; gint line_nr; char *line; }; #define VALID_KEY_CHAR 1 #define VALID_LOCALE_CHAR 2 static const guchar valid[256] = { 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x3 , 0x2 , 0x0 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x0 , 0x0 , 0x0 , 0x0 , 0x2 , 0x0 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , }; static void report_error (GtkIconThemeFileParser *parser, char *message, GtkIconThemeFileParseError error_code, GError **error); static GtkIconThemeFileSection *lookup_section (GtkIconThemeFile *df, const char *section); static GtkIconThemeFileLine * lookup_line (GtkIconThemeFile *df, GtkIconThemeFileSection *section, const char *keyname, const char *locale); GQuark _gtk_icon_theme_file_parse_error_quark (void) { static GQuark quark; if (!quark) quark = g_quark_from_static_string ("g_desktop_parse_error"); return quark; } static void parser_free (GtkIconThemeFileParser *parser) { _gtk_icon_theme_file_free (parser->df); } static void gtk_icon_theme_file_line_free (GtkIconThemeFileLine *line) { g_free (line->locale); g_free (line->value); } static void gtk_icon_theme_file_section_free (GtkIconThemeFileSection *section) { int i; for (i = 0; i < section->n_lines; i++) gtk_icon_theme_file_line_free (§ion->lines[i]); g_free (section->lines); } void _gtk_icon_theme_file_free (GtkIconThemeFile *df) { int i; for (i = 0; i < df->n_sections; i++) gtk_icon_theme_file_section_free (&df->sections[i]); g_free (df->sections); g_free (df->current_locale[0]); g_free (df->current_locale[1]); g_free (df); } static void grow_lines (GtkIconThemeFileParser *parser) { int new_n_lines; GtkIconThemeFileSection *section; if (parser->n_allocated_lines == 0) new_n_lines = 1; else new_n_lines = parser->n_allocated_lines*2; section = &parser->df->sections[parser->current_section]; section->lines = g_realloc (section->lines, sizeof (GtkIconThemeFileLine) * new_n_lines); parser->n_allocated_lines = new_n_lines; } static void grow_sections (GtkIconThemeFileParser *parser) { int new_n_sections; if (parser->n_allocated_sections == 0) new_n_sections = 1; else new_n_sections = parser->n_allocated_sections*2; parser->df->sections = g_renew (GtkIconThemeFileSection, parser->df->sections, new_n_sections); parser->n_allocated_sections = new_n_sections; } static gchar * unescape_string (gchar *str, gint len) { gchar *res; gchar *p, *q; gchar *end; /* len + 1 is enough, because unescaping never makes the * string longer */ res = g_new (gchar, len + 1); p = str; q = res; end = str + len; while (p < end) { if (*p == 0) { /* Found an embedded null */ g_free (res); return NULL; } if (*p == '\\') { p++; if (p >= end) { /* Escape at end of string */ g_free (res); return NULL; } switch (*p) { case 's': *q++ = ' '; break; case 't': *q++ = '\t'; break; case 'n': *q++ = '\n'; break; case 'r': *q++ = '\r'; break; case '\\': *q++ = '\\'; break; default: /* Invalid escape code */ g_free (res); return NULL; } p++; } else *q++ = *p++; } *q = 0; return res; } static gchar * escape_string (const gchar *str, gboolean escape_first_space) { gchar *res; char *q; const gchar *p; const gchar *end; /* len + 1 is enough, because unescaping never makes the * string longer */ res = g_new (gchar, strlen (str)*2 + 1); p = str; q = res; end = str + strlen (str); while (*p) { if (*p == ' ') { if (escape_first_space && p == str) { *q++ = '\\'; *q++ = 's'; } else *q++ = ' '; } else if (*p == '\\') { *q++ = '\\'; *q++ = '\\'; } else if (*p == '\t') { *q++ = '\\'; *q++ = 't'; } else if (*p == '\n') { *q++ = '\\'; *q++ = 'n'; } else if (*p == '\r') { *q++ = '\\'; *q++ = 'r'; } else *q++ = *p; p++; } *q = 0; return res; } static void open_section (GtkIconThemeFileParser *parser, const char *name) { int n; if (parser->n_allocated_sections == parser->df->n_sections) grow_sections (parser); if (parser->current_section == 0 && parser->df->sections[0].section_name == 0 && parser->df->sections[0].n_lines == 0) { if (!name) g_warning ("non-initial NULL section\n"); /* The initial section was empty. Piggyback on it. */ parser->df->sections[0].section_name = g_quark_from_string (name); return; } n = parser->df->n_sections++; if (name) parser->df->sections[n].section_name = g_quark_from_string (name); else parser->df->sections[n].section_name = 0; parser->df->sections[n].n_lines = 0; parser->df->sections[n].lines = NULL; parser->current_section = n; parser->n_allocated_lines = 0; grow_lines (parser); } static GtkIconThemeFileLine * new_line (GtkIconThemeFileParser *parser) { GtkIconThemeFileSection *section; GtkIconThemeFileLine *line; section = &parser->df->sections[parser->current_section]; if (parser->n_allocated_lines == section->n_lines) grow_lines (parser); line = §ion->lines[section->n_lines++]; memset (line, 0, sizeof (GtkIconThemeFileLine)); return line; } static gboolean is_blank_line (GtkIconThemeFileParser *parser) { gchar *p; p = parser->line; while (*p && *p != '\n') { if (!g_ascii_isspace (*p)) return FALSE; p++; } return TRUE; } static void parse_comment_or_blank (GtkIconThemeFileParser *parser) { GtkIconThemeFileLine *line; gchar *line_end; line_end = strchr (parser->line, '\n'); if (line_end == NULL) line_end = parser->line + strlen (parser->line); line = new_line (parser); line->value = g_strndup (parser->line, line_end - parser->line); parser->line = (line_end) ? line_end + 1 : NULL; parser->line_nr++; } static gboolean parse_section_start (GtkIconThemeFileParser *parser, GError **error) { gchar *line_end; gchar *section_name; line_end = strchr (parser->line, '\n'); if (line_end == NULL) line_end = parser->line + strlen (parser->line); if (line_end - parser->line <= 2 || line_end[-1] != ']') { report_error (parser, "Invalid syntax for section header", GTK_ICON_THEME_FILE_PARSE_ERROR_INVALID_SYNTAX, error); parser_free (parser); return FALSE; } section_name = unescape_string (parser->line + 1, line_end - parser->line - 2); if (section_name == NULL) { report_error (parser, "Invalid escaping in section name", GTK_ICON_THEME_FILE_PARSE_ERROR_INVALID_ESCAPES, error); parser_free (parser); return FALSE; } open_section (parser, section_name); parser->line = (line_end) ? line_end + 1 : NULL; parser->line_nr++; g_free (section_name); return TRUE; } static gboolean parse_key_value (GtkIconThemeFileParser *parser, GError **error) { GtkIconThemeFileLine *line; gchar *line_end; gchar *key_start; gchar *key_end; gchar *key; gchar *locale_start = NULL; gchar *locale_end = NULL; gchar *value_start; gchar *value; gchar *p; line_end = strchr (parser->line, '\n'); if (line_end == NULL) line_end = parser->line + strlen (parser->line); p = parser->line; key_start = p; while (p < line_end && (valid[(guchar)*p] & VALID_KEY_CHAR)) p++; key_end = p; if (key_start == key_end) { report_error (parser, "Empty key name", GTK_ICON_THEME_FILE_PARSE_ERROR_INVALID_SYNTAX, error); parser_free (parser); return FALSE; } if (p < line_end && *p == '[') { p++; locale_start = p; while (p < line_end && *p != ']') p++; locale_end = p; if (p == line_end) { report_error (parser, "Unterminated locale specification in key", GTK_ICON_THEME_FILE_PARSE_ERROR_INVALID_SYNTAX, error); parser_free (parser); return FALSE; } p++; } /* Skip space before '=' */ while (p < line_end && *p == ' ') p++; if (p < line_end && *p != '=') { report_error (parser, "Invalid characters in key name", GTK_ICON_THEME_FILE_PARSE_ERROR_INVALID_CHARS, error); parser_free (parser); return FALSE; } if (p == line_end) { report_error (parser, "No '=' in key/value pair", GTK_ICON_THEME_FILE_PARSE_ERROR_INVALID_SYNTAX, error); parser_free (parser); return FALSE; } /* Skip the '=' */ p++; /* Skip space after '=' */ while (p < line_end && *p == ' ') p++; value_start = p; value = unescape_string (value_start, line_end - value_start); if (value == NULL) { report_error (parser, "Invalid escaping in value", GTK_ICON_THEME_FILE_PARSE_ERROR_INVALID_ESCAPES, error); parser_free (parser); return FALSE; } line = new_line (parser); key = g_strndup (key_start, key_end - key_start); line->key = g_quark_from_string (key); g_free (key); if (locale_start) line->locale = g_strndup (locale_start, locale_end - locale_start); line->value = value; parser->line = (line_end) ? line_end + 1 : NULL; parser->line_nr++; return TRUE; } static void report_error (GtkIconThemeFileParser *parser, char *message, GtkIconThemeFileParseError error_code, GError **error) { GtkIconThemeFileSection *section; const gchar *section_name = NULL; section = &parser->df->sections[parser->current_section]; if (section->section_name) section_name = g_quark_to_string (section->section_name); if (error) { if (section_name) *error = g_error_new (GTK_ICON_THEME_FILE_PARSE_ERROR, error_code, "Error in section %s at line %d: %s", section_name, parser->line_nr, message); else *error = g_error_new (GTK_ICON_THEME_FILE_PARSE_ERROR, error_code, "Error at line %d: %s", parser->line_nr, message); } } GtkIconThemeFile * _gtk_icon_theme_file_new_from_string (char *data, GError **error) { GtkIconThemeFileParser parser; parser.df = g_new0 (GtkIconThemeFile, 1); parser.current_section = -1; parser.n_allocated_lines = 0; parser.n_allocated_sections = 0; parser.line_nr = 1; parser.line = data; /* Put any initial comments in a NULL segment */ open_section (&parser, NULL); while (parser.line && *parser.line) { if (*parser.line == '[') { if (!parse_section_start (&parser, error)) return NULL; } else if (is_blank_line (&parser) || *parser.line == '#') parse_comment_or_blank (&parser); else { if (!parse_key_value (&parser, error)) return NULL; } } return parser.df; } char * _gtk_icon_theme_file_to_string (GtkIconThemeFile *df) { GtkIconThemeFileSection *section; GtkIconThemeFileLine *line; GString *str; char *s; int i, j; str = g_string_sized_new (800); for (i = 0; i < df->n_sections; i ++) { section = &df->sections[i]; if (section->section_name) { g_string_append_c (str, '['); s = escape_string (g_quark_to_string (section->section_name), FALSE); g_string_append (str, s); g_free (s); g_string_append (str, "]\n"); } for (j = 0; j < section->n_lines; j++) { line = §ion->lines[j]; if (line->key == 0) { g_string_append (str, line->value); g_string_append_c (str, '\n'); } else { g_string_append (str, g_quark_to_string (line->key)); if (line->locale) { g_string_append_c (str, '['); g_string_append (str, line->locale); g_string_append_c (str, ']'); } g_string_append_c (str, '='); s = escape_string (line->value, TRUE); g_string_append (str, s); g_free (s); g_string_append_c (str, '\n'); } } } return g_string_free (str, FALSE); } static GtkIconThemeFileSection * lookup_section (GtkIconThemeFile *df, const char *section_name) { GtkIconThemeFileSection *section; GQuark section_quark; int i; section_quark = g_quark_try_string (section_name); if (section_quark == 0) return NULL; for (i = 0; i < df->n_sections; i ++) { section = &df->sections[i]; if (section->section_name == section_quark) return section; } return NULL; } static GtkIconThemeFileLine * lookup_line (GtkIconThemeFile *df, GtkIconThemeFileSection *section, const char *keyname, const char *locale) { GtkIconThemeFileLine *line; GQuark key_quark; int i; key_quark = g_quark_try_string (keyname); if (key_quark == 0) return NULL; for (i = 0; i < section->n_lines; i++) { line = §ion->lines[i]; if (line->key == key_quark && ((locale == NULL && line->locale == NULL) || (locale != NULL && line->locale != NULL && strcmp (locale, line->locale) == 0))) return line; } return NULL; } gboolean _gtk_icon_theme_file_get_raw (GtkIconThemeFile *df, const char *section_name, const char *keyname, const char *locale, char **val) { GtkIconThemeFileSection *section; GtkIconThemeFileLine *line; *val = NULL; section = lookup_section (df, section_name); if (!section) return FALSE; line = lookup_line (df, section, keyname, locale); if (!line) return FALSE; *val = g_strdup (line->value); return TRUE; } void _gtk_icon_theme_file_foreach_section (GtkIconThemeFile *df, GtkIconThemeFileSectionFunc func, gpointer user_data) { GtkIconThemeFileSection *section; int i; for (i = 0; i < df->n_sections; i ++) { section = &df->sections[i]; (*func) (df, g_quark_to_string (section->section_name), user_data); } return; } void _gtk_icon_theme_file_foreach_key (GtkIconThemeFile *df, const char *section_name, gboolean include_localized, GtkIconThemeFileLineFunc func, gpointer user_data) { GtkIconThemeFileSection *section; GtkIconThemeFileLine *line; int i; section = lookup_section (df, section_name); if (!section) return; for (i = 0; i < section->n_lines; i++) { line = §ion->lines[i]; (*func) (df, g_quark_to_string (line->key), line->locale, line->value, user_data); } return; } static void calculate_locale (GtkIconThemeFile *df) { char *p, *lang; #ifdef HAVE_LC_MESSAGES lang = g_strdup (setlocale (LC_MESSAGES, NULL)); #else lang = g_strdup (setlocale (LC_CTYPE, NULL)); #endif if (lang) { p = strchr (lang, '.'); if (p) *p = '\0'; p = strchr (lang, '@'); if (p) *p = '\0'; } else lang = g_strdup ("C"); p = strchr (lang, '_'); if (p) { df->current_locale[0] = g_strdup (lang); *p = '\0'; df->current_locale[1] = lang; } else { df->current_locale[0] = lang; df->current_locale[1] = NULL; } } gboolean _gtk_icon_theme_file_get_locale_string (GtkIconThemeFile *df, const char *section, const char *keyname, char **val) { gboolean res; if (df->current_locale[0] == NULL) calculate_locale (df); if (df->current_locale[0] != NULL) { res = _gtk_icon_theme_file_get_raw (df,section, keyname, df->current_locale[0], val); if (res) return TRUE; } if (df->current_locale[1] != NULL) { res = _gtk_icon_theme_file_get_raw (df,section, keyname, df->current_locale[1], val); if (res) return TRUE; } return _gtk_icon_theme_file_get_raw (df, section, keyname, NULL, val); } gboolean _gtk_icon_theme_file_get_string (GtkIconThemeFile *df, const char *section, const char *keyname, char **val) { return _gtk_icon_theme_file_get_raw (df, section, keyname, NULL, val); } gboolean _gtk_icon_theme_file_get_integer (GtkIconThemeFile *df, const char *section, const char *keyname, int *val) { gboolean res; char *str; *val = 0; res = _gtk_icon_theme_file_get_raw (df, section, keyname, NULL, &str); if (!res) return FALSE; *val = atoi (str); g_free (str); return TRUE; }