r3768: Updated years.
[rox-filer.git] / ROX-Filer / src / gtkiconthemeparser.c
blob1bcacc44b861310b2b9278a7f567874fb3857ba3
1 /* GtkIconThemeParser - a parser of icon-theme files
2 * gtk-icon-theme-parser.c Copyright (C) 2002, 2003 Red Hat, Inc.
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 * Boston, MA 02111-1307, USA.
20 #include "config.h"
22 #include <string.h>
23 #include <locale.h>
24 #include <stdlib.h>
26 #include "gtkiconthemeparser.h"
28 typedef struct _GtkIconThemeFileSection GtkIconThemeFileSection;
29 typedef struct _GtkIconThemeFileLine GtkIconThemeFileLine;
30 typedef struct _GtkIconThemeFileParser GtkIconThemeFileParser;
32 struct _GtkIconThemeFileSection {
33 GQuark section_name; /* 0 means just a comment block (before any section) */
34 gint n_lines;
35 GtkIconThemeFileLine *lines;
38 struct _GtkIconThemeFileLine {
39 GQuark key; /* 0 means comment or blank line in value */
40 char *locale;
41 gchar *value;
44 struct _GtkIconThemeFile {
45 gint n_sections;
46 GtkIconThemeFileSection *sections;
47 char *current_locale[2];
50 struct _GtkIconThemeFileParser {
51 GtkIconThemeFile *df;
52 gint current_section;
53 gint n_allocated_lines;
54 gint n_allocated_sections;
55 gint line_nr;
56 char *line;
59 #define VALID_KEY_CHAR 1
60 #define VALID_LOCALE_CHAR 2
61 static const guchar valid[256] = {
62 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 ,
63 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 ,
64 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x3 , 0x2 , 0x0 ,
65 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 ,
66 0x0 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 ,
67 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x0 , 0x0 , 0x0 , 0x0 , 0x2 ,
68 0x0 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 ,
69 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x3 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 ,
70 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 ,
71 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 ,
72 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 ,
73 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 ,
74 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 ,
75 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 ,
76 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 ,
77 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 ,
80 static void report_error (GtkIconThemeFileParser *parser,
81 char *message,
82 GtkIconThemeFileParseError error_code,
83 GError **error);
84 static GtkIconThemeFileSection *lookup_section (GtkIconThemeFile *df,
85 const char *section);
86 static GtkIconThemeFileLine * lookup_line (GtkIconThemeFile *df,
87 GtkIconThemeFileSection *section,
88 const char *keyname,
89 const char *locale);
92 GQuark
93 _rox_icon_theme_file_parse_error_quark (void)
95 static GQuark quark;
96 if (!quark)
97 quark = g_quark_from_static_string ("g_desktop_parse_error");
99 return quark;
102 static void
103 parser_free (GtkIconThemeFileParser *parser)
105 _rox_icon_theme_file_free (parser->df);
108 static void
109 rox_icon_theme_file_line_free (GtkIconThemeFileLine *line)
111 g_free (line->locale);
112 g_free (line->value);
115 static void
116 rox_icon_theme_file_section_free (GtkIconThemeFileSection *section)
118 int i;
120 for (i = 0; i < section->n_lines; i++)
121 rox_icon_theme_file_line_free (&section->lines[i]);
123 g_free (section->lines);
126 void
127 _rox_icon_theme_file_free (GtkIconThemeFile *df)
129 int i;
131 for (i = 0; i < df->n_sections; i++)
132 rox_icon_theme_file_section_free (&df->sections[i]);
133 g_free (df->sections);
134 g_free (df->current_locale[0]);
135 g_free (df->current_locale[1]);
137 g_free (df);
140 static void
141 grow_lines (GtkIconThemeFileParser *parser)
143 int new_n_lines;
144 GtkIconThemeFileSection *section;
146 if (parser->n_allocated_lines == 0)
147 new_n_lines = 1;
148 else
149 new_n_lines = parser->n_allocated_lines*2;
151 section = &parser->df->sections[parser->current_section];
153 section->lines = g_realloc (section->lines,
154 sizeof (GtkIconThemeFileLine) * new_n_lines);
155 parser->n_allocated_lines = new_n_lines;
158 static void
159 grow_sections (GtkIconThemeFileParser *parser)
161 int new_n_sections;
163 if (parser->n_allocated_sections == 0)
164 new_n_sections = 1;
165 else
166 new_n_sections = parser->n_allocated_sections*2;
168 parser->df->sections = g_renew (GtkIconThemeFileSection,
169 parser->df->sections,
170 new_n_sections);
171 parser->n_allocated_sections = new_n_sections;
174 static gchar *
175 unescape_string (gchar *str, gint len)
177 gchar *res;
178 gchar *p, *q;
179 gchar *end;
181 /* len + 1 is enough, because unescaping never makes the
182 * string longer */
183 res = g_new (gchar, len + 1);
184 p = str;
185 q = res;
186 end = str + len;
188 while (p < end)
190 if (*p == 0)
192 /* Found an embedded null */
193 g_free (res);
194 return NULL;
196 if (*p == '\\')
198 p++;
199 if (p >= end)
201 /* Escape at end of string */
202 g_free (res);
203 return NULL;
206 switch (*p)
208 case 's':
209 *q++ = ' ';
210 break;
211 case 't':
212 *q++ = '\t';
213 break;
214 case 'n':
215 *q++ = '\n';
216 break;
217 case 'r':
218 *q++ = '\r';
219 break;
220 case '\\':
221 *q++ = '\\';
222 break;
223 default:
224 /* Invalid escape code */
225 g_free (res);
226 return NULL;
228 p++;
230 else
231 *q++ = *p++;
233 *q = 0;
235 return res;
238 static gchar *
239 escape_string (const gchar *str, gboolean escape_first_space)
241 gchar *res;
242 char *q;
243 const gchar *p;
244 const gchar *end;
246 /* len + 1 is enough, because unescaping never makes the
247 * string longer */
248 res = g_new (gchar, strlen (str)*2 + 1);
250 p = str;
251 q = res;
252 end = str + strlen (str);
254 while (*p)
256 if (*p == ' ')
258 if (escape_first_space && p == str)
260 *q++ = '\\';
261 *q++ = 's';
263 else
264 *q++ = ' ';
266 else if (*p == '\\')
268 *q++ = '\\';
269 *q++ = '\\';
271 else if (*p == '\t')
273 *q++ = '\\';
274 *q++ = 't';
276 else if (*p == '\n')
278 *q++ = '\\';
279 *q++ = 'n';
281 else if (*p == '\r')
283 *q++ = '\\';
284 *q++ = 'r';
286 else
287 *q++ = *p;
288 p++;
290 *q = 0;
292 return res;
296 static void
297 open_section (GtkIconThemeFileParser *parser,
298 const char *name)
300 int n;
302 if (parser->n_allocated_sections == parser->df->n_sections)
303 grow_sections (parser);
305 if (parser->current_section == 0 &&
306 parser->df->sections[0].section_name == 0 &&
307 parser->df->sections[0].n_lines == 0)
309 if (!name)
310 g_warning ("non-initial NULL section\n");
312 /* The initial section was empty. Piggyback on it. */
313 parser->df->sections[0].section_name = g_quark_from_string (name);
315 return;
318 n = parser->df->n_sections++;
320 if (name)
321 parser->df->sections[n].section_name = g_quark_from_string (name);
322 else
323 parser->df->sections[n].section_name = 0;
324 parser->df->sections[n].n_lines = 0;
325 parser->df->sections[n].lines = NULL;
327 parser->current_section = n;
328 parser->n_allocated_lines = 0;
329 grow_lines (parser);
332 static GtkIconThemeFileLine *
333 new_line (GtkIconThemeFileParser *parser)
335 GtkIconThemeFileSection *section;
336 GtkIconThemeFileLine *line;
338 section = &parser->df->sections[parser->current_section];
340 if (parser->n_allocated_lines == section->n_lines)
341 grow_lines (parser);
343 line = &section->lines[section->n_lines++];
345 memset (line, 0, sizeof (GtkIconThemeFileLine));
347 return line;
350 static gboolean
351 is_blank_line (GtkIconThemeFileParser *parser)
353 gchar *p;
355 p = parser->line;
357 while (*p && *p != '\n')
359 if (!g_ascii_isspace (*p))
360 return FALSE;
362 p++;
364 return TRUE;
367 static void
368 parse_comment_or_blank (GtkIconThemeFileParser *parser)
370 GtkIconThemeFileLine *line;
371 gchar *line_end;
373 line_end = strchr (parser->line, '\n');
374 if (line_end == NULL)
375 line_end = parser->line + strlen (parser->line);
377 line = new_line (parser);
379 line->value = g_strndup (parser->line, line_end - parser->line);
381 parser->line = (line_end) ? line_end + 1 : NULL;
382 parser->line_nr++;
385 static gboolean
386 parse_section_start (GtkIconThemeFileParser *parser, GError **error)
388 gchar *line_end;
389 gchar *section_name;
391 line_end = strchr (parser->line, '\n');
392 if (line_end == NULL)
393 line_end = parser->line + strlen (parser->line);
395 if (line_end - parser->line <= 2 ||
396 line_end[-1] != ']')
398 report_error (parser, "Invalid syntax for section header", GTK_ICON_THEME_FILE_PARSE_ERROR_INVALID_SYNTAX, error);
399 parser_free (parser);
400 return FALSE;
403 section_name = unescape_string (parser->line + 1, line_end - parser->line - 2);
405 if (section_name == NULL)
407 report_error (parser, "Invalid escaping in section name", GTK_ICON_THEME_FILE_PARSE_ERROR_INVALID_ESCAPES, error);
408 parser_free (parser);
409 return FALSE;
412 open_section (parser, section_name);
414 parser->line = (line_end) ? line_end + 1 : NULL;
415 parser->line_nr++;
417 g_free (section_name);
419 return TRUE;
422 static gboolean
423 parse_key_value (GtkIconThemeFileParser *parser, GError **error)
425 GtkIconThemeFileLine *line;
426 gchar *line_end;
427 gchar *key_start;
428 gchar *key_end;
429 gchar *key;
430 gchar *locale_start = NULL;
431 gchar *locale_end = NULL;
432 gchar *value_start;
433 gchar *value;
434 gchar *p;
436 line_end = strchr (parser->line, '\n');
437 if (line_end == NULL)
438 line_end = parser->line + strlen (parser->line);
440 p = parser->line;
441 key_start = p;
442 while (p < line_end &&
443 (valid[(guchar)*p] & VALID_KEY_CHAR))
444 p++;
445 key_end = p;
447 if (key_start == key_end)
449 report_error (parser, "Empty key name", GTK_ICON_THEME_FILE_PARSE_ERROR_INVALID_SYNTAX, error);
450 parser_free (parser);
451 return FALSE;
454 if (p < line_end && *p == '[')
456 p++;
457 locale_start = p;
458 while (p < line_end && *p != ']')
459 p++;
460 locale_end = p;
462 if (p == line_end)
464 report_error (parser, "Unterminated locale specification in key", GTK_ICON_THEME_FILE_PARSE_ERROR_INVALID_SYNTAX, error);
465 parser_free (parser);
466 return FALSE;
469 p++;
472 /* Skip space before '=' */
473 while (p < line_end && *p == ' ')
474 p++;
476 if (p < line_end && *p != '=')
478 report_error (parser, "Invalid characters in key name", GTK_ICON_THEME_FILE_PARSE_ERROR_INVALID_CHARS, error);
479 parser_free (parser);
480 return FALSE;
483 if (p == line_end)
485 report_error (parser, "No '=' in key/value pair", GTK_ICON_THEME_FILE_PARSE_ERROR_INVALID_SYNTAX, error);
486 parser_free (parser);
487 return FALSE;
490 /* Skip the '=' */
491 p++;
493 /* Skip space after '=' */
494 while (p < line_end && *p == ' ')
495 p++;
497 value_start = p;
499 value = unescape_string (value_start, line_end - value_start);
500 if (value == NULL)
502 report_error (parser, "Invalid escaping in value", GTK_ICON_THEME_FILE_PARSE_ERROR_INVALID_ESCAPES, error);
503 parser_free (parser);
504 return FALSE;
507 line = new_line (parser);
508 key = g_strndup (key_start, key_end - key_start);
509 line->key = g_quark_from_string (key);
510 g_free (key);
511 if (locale_start)
512 line->locale = g_strndup (locale_start, locale_end - locale_start);
513 line->value = value;
515 parser->line = (line_end) ? line_end + 1 : NULL;
516 parser->line_nr++;
518 return TRUE;
522 static void
523 report_error (GtkIconThemeFileParser *parser,
524 char *message,
525 GtkIconThemeFileParseError error_code,
526 GError **error)
528 GtkIconThemeFileSection *section;
529 const gchar *section_name = NULL;
531 section = &parser->df->sections[parser->current_section];
533 if (section->section_name)
534 section_name = g_quark_to_string (section->section_name);
536 if (error)
538 if (section_name)
539 *error = g_error_new (GTK_ICON_THEME_FILE_PARSE_ERROR,
540 error_code,
541 "Error in section %s at line %d: %s", section_name, parser->line_nr, message);
542 else
543 *error = g_error_new (GTK_ICON_THEME_FILE_PARSE_ERROR,
544 error_code,
545 "Error at line %d: %s", parser->line_nr, message);
550 GtkIconThemeFile *
551 _rox_icon_theme_file_new_from_string (char *data,
552 GError **error)
554 GtkIconThemeFileParser parser;
556 parser.df = g_new0 (GtkIconThemeFile, 1);
557 parser.current_section = -1;
559 parser.n_allocated_lines = 0;
560 parser.n_allocated_sections = 0;
561 parser.line_nr = 1;
563 parser.line = data;
565 /* Put any initial comments in a NULL segment */
566 open_section (&parser, NULL);
568 while (parser.line && *parser.line)
570 if (*parser.line == '[') {
571 if (!parse_section_start (&parser, error))
572 return NULL;
573 } else if (is_blank_line (&parser) ||
574 *parser.line == '#')
575 parse_comment_or_blank (&parser);
576 else
578 if (!parse_key_value (&parser, error))
579 return NULL;
583 return parser.df;
586 char *
587 _rox_icon_theme_file_to_string (GtkIconThemeFile *df)
589 GtkIconThemeFileSection *section;
590 GtkIconThemeFileLine *line;
591 GString *str;
592 char *s;
593 int i, j;
595 str = g_string_sized_new (800);
597 for (i = 0; i < df->n_sections; i ++)
599 section = &df->sections[i];
601 if (section->section_name)
603 g_string_append_c (str, '[');
604 s = escape_string (g_quark_to_string (section->section_name), FALSE);
605 g_string_append (str, s);
606 g_free (s);
607 g_string_append (str, "]\n");
610 for (j = 0; j < section->n_lines; j++)
612 line = &section->lines[j];
614 if (line->key == 0)
616 g_string_append (str, line->value);
617 g_string_append_c (str, '\n');
619 else
621 g_string_append (str, g_quark_to_string (line->key));
622 if (line->locale)
624 g_string_append_c (str, '[');
625 g_string_append (str, line->locale);
626 g_string_append_c (str, ']');
628 g_string_append_c (str, '=');
629 s = escape_string (line->value, TRUE);
630 g_string_append (str, s);
631 g_free (s);
632 g_string_append_c (str, '\n');
637 return g_string_free (str, FALSE);
640 static GtkIconThemeFileSection *
641 lookup_section (GtkIconThemeFile *df,
642 const char *section_name)
644 GtkIconThemeFileSection *section;
645 GQuark section_quark;
646 int i;
648 section_quark = g_quark_try_string (section_name);
649 if (section_quark == 0)
650 return NULL;
652 for (i = 0; i < df->n_sections; i ++)
654 section = &df->sections[i];
656 if (section->section_name == section_quark)
657 return section;
659 return NULL;
662 static GtkIconThemeFileLine *
663 lookup_line (GtkIconThemeFile *df,
664 GtkIconThemeFileSection *section,
665 const char *keyname,
666 const char *locale)
668 GtkIconThemeFileLine *line;
669 GQuark key_quark;
670 int i;
672 key_quark = g_quark_try_string (keyname);
673 if (key_quark == 0)
674 return NULL;
676 for (i = 0; i < section->n_lines; i++)
678 line = &section->lines[i];
680 if (line->key == key_quark &&
681 ((locale == NULL && line->locale == NULL) ||
682 (locale != NULL && line->locale != NULL && strcmp (locale, line->locale) == 0)))
683 return line;
686 return NULL;
689 gboolean
690 _rox_icon_theme_file_get_raw (GtkIconThemeFile *df,
691 const char *section_name,
692 const char *keyname,
693 const char *locale,
694 char **val)
696 GtkIconThemeFileSection *section;
697 GtkIconThemeFileLine *line;
699 *val = NULL;
701 section = lookup_section (df, section_name);
702 if (!section)
703 return FALSE;
705 line = lookup_line (df,
706 section,
707 keyname,
708 locale);
710 if (!line)
711 return FALSE;
713 *val = g_strdup (line->value);
715 return TRUE;
719 void
720 _rox_icon_theme_file_foreach_section (GtkIconThemeFile *df,
721 GtkIconThemeFileSectionFunc func,
722 gpointer user_data)
724 GtkIconThemeFileSection *section;
725 int i;
727 for (i = 0; i < df->n_sections; i ++)
729 section = &df->sections[i];
731 (*func) (df, g_quark_to_string (section->section_name), user_data);
733 return;
736 void
737 _rox_icon_theme_file_foreach_key (GtkIconThemeFile *df,
738 const char *section_name,
739 gboolean include_localized,
740 GtkIconThemeFileLineFunc func,
741 gpointer user_data)
743 GtkIconThemeFileSection *section;
744 GtkIconThemeFileLine *line;
745 int i;
747 section = lookup_section (df, section_name);
748 if (!section)
749 return;
751 for (i = 0; i < section->n_lines; i++)
753 line = &section->lines[i];
755 (*func) (df, g_quark_to_string (line->key), line->locale, line->value, user_data);
758 return;
762 static void
763 calculate_locale (GtkIconThemeFile *df)
765 char *p, *lang;
767 #ifdef HAVE_LC_MESSAGES
768 lang = g_strdup (setlocale (LC_MESSAGES, NULL));
769 #else
770 lang = g_strdup (setlocale (LC_CTYPE, NULL));
771 #endif
773 if (lang)
775 p = strchr (lang, '.');
776 if (p)
777 *p = '\0';
778 p = strchr (lang, '@');
779 if (p)
780 *p = '\0';
782 else
783 lang = g_strdup ("C");
785 p = strchr (lang, '_');
786 if (p)
788 df->current_locale[0] = g_strdup (lang);
789 *p = '\0';
790 df->current_locale[1] = lang;
792 else
794 df->current_locale[0] = lang;
795 df->current_locale[1] = NULL;
799 gboolean
800 _rox_icon_theme_file_get_locale_string (GtkIconThemeFile *df,
801 const char *section,
802 const char *keyname,
803 char **val)
805 gboolean res;
807 if (df->current_locale[0] == NULL)
808 calculate_locale (df);
810 if (df->current_locale[0] != NULL)
812 res = _rox_icon_theme_file_get_raw (df,section, keyname,
813 df->current_locale[0], val);
814 if (res)
815 return TRUE;
818 if (df->current_locale[1] != NULL)
820 res = _rox_icon_theme_file_get_raw (df,section, keyname,
821 df->current_locale[1], val);
822 if (res)
823 return TRUE;
826 return _rox_icon_theme_file_get_raw (df, section, keyname, NULL, val);
829 gboolean
830 _rox_icon_theme_file_get_string (GtkIconThemeFile *df,
831 const char *section,
832 const char *keyname,
833 char **val)
835 return _rox_icon_theme_file_get_raw (df, section, keyname, NULL, val);
838 gboolean
839 _rox_icon_theme_file_get_integer (GtkIconThemeFile *df,
840 const char *section,
841 const char *keyname,
842 int *val)
844 gboolean res;
845 char *str;
847 *val = 0;
849 res = _rox_icon_theme_file_get_raw (df, section, keyname, NULL, &str);
850 if (!res)
851 return FALSE;
854 *val = atoi (str);
855 g_free (str);
857 return TRUE;